类似直播平台网站的建设费用,欧莱雅网站建设与推广方案,有赞小程序登录入口,h5开发是什么意思1、[[Prototype]]
JS中的对象有一个特殊的 [[Prototype]] 内置属性#xff0c;其实就是对于其他对象的引用。几乎所有的对象在创建时 [[Prototype]] 属性都会被赋予一个非空的值。
var anotherObject {a:2
};
// 创建一个关联到 anotherObject 的对象
var myObject Object…1、[[Prototype]]
JS中的对象有一个特殊的 [[Prototype]] 内置属性其实就是对于其他对象的引用。几乎所有的对象在创建时 [[Prototype]] 属性都会被赋予一个非空的值。
var anotherObject {a:2
};
// 创建一个关联到 anotherObject 的对象
var myObject Object.create( anotherObject );
myObject.a; // 2Object.create(…) 的原理现在只需要知道它会创建一个对象并把这个对象的 [[Prototype]] 关联到指定的对象。
myObject 对象的 [[Prototype]] 关联到了 anotherObject。显然 myObject.a 并不存在但是尽管如此属性访问仍然成功地在 anotherObject 中找到了值 2。
使用 for…in 遍历对象时原理和查找 [[Prototype]] 链类似任何可以通过原型链访问到并且是 enumerable的属性都会被枚举。使用 in 操作符来检查属性在对象中是否存在时同样会查找对象的整条原型链无论属性是否可枚举
所有普通的 [[Prototype]] 链最终都会指向内置的 Object.prototype。由于所有的“普通”内置不是特定主机的扩展对象都“源于”或者说把 [[Prototype]] 链的顶端设置为这个 Object.prototype 对象所以它包含 JavaScript 中许多通用的功能。 比 如 说 .toString()、.valueOf()以及.hasOwnProperty(…)。稍后我们还会介绍 .isPrototypeOf(…)
1.1、属性设置和屏蔽
myObject.foo bar如果 myObject 对象中包含名为 foo 的普通数据访问属性这条赋值语句只会修改已有的属性值。 如果 foo 不是直接存在于 myObject 中[[Prototype]] 链就会被遍历类似 [[Get]] 操作。 如果原型链上找不到 foofoo 就会被直接添加到 myObject 上。 然而如果 foo 存在于原型链上层赋值语句 myObject.foo “bar” 的行为就会有些不同而且可能很出人意料。 如 果 属 性 名 foo 既 出 现 在 myObject 中 也 出 现 在 myObject 的 [[Prototype]] 链 上 层 那么就会发生屏蔽。myObject 中包含的 foo 属性会屏蔽原型链上层的所有 foo 属性因为myObject.foo 总是会选择原型链中最底层的 foo 属性。
foo不直接存在于myObject中而是存在于原型链上层时myObject.foo bar会出现三种情况
如果在 [[Prototype]] 链上层存在名为 foo 的普通数据访问属性并且没有被标记为只读writable:false那就会直接在 myObject 中添加一个名为 foo 的新属性它是屏蔽属性。如果在 [[Prototype]] 链上层存在 foo但是它被标记为只读writable:false那么无法修改已有属性或者在 myObject 上创建屏蔽属性。这个限制只存在于 赋值中使用 Object.defineProperty(…) 并不会受到影响如果在 [[Prototype]] 链上层存在 foo 并且它是一个 setter那就一定会调用这个 setter。foo 不会被添加到或者说屏蔽于myObject也不会重新定义 foo 这个 setter。
我们应尽量少使用属性屏蔽有些情况下会隐式产生屏蔽看以下代码
var anotherObject {a:2
};
var myObject Object.create( anotherObject );
anotherObject.a; // 2
myObject.a; // 2
anotherObject.hasOwnProperty( a ); // true
myObject.hasOwnProperty( a ); // false
myObject.a; // 隐式屏蔽
anotherObject.a; // 2
myObject.a; // 3
myObject.hasOwnProperty( a ); // true尽管 myObject.a 看起来应该通过委托查找并增加 anotherObject.a 属性但是别忘了 操作相当于 myObject.a myObject.a 1。因此 操作首先会通过 [[Prototype]]查找属性 a 并从 anotherObject.a 获取当前属性值 2然后给这个值加 1接着用 [[Put]]将值 3 赋给 myObject 中新建的屏蔽属性 a。如果想让anotherObject.a 的 值 增 加唯一的办法是anotherObject.a。
2、“类’‘
JS和面向类的语言不同它并没有类来作为对象的抽象模式或者说蓝图。JS中只有对象。实际上JS才是真正应该被称为“面向对象”的语言因为它是少有的可以不通过类直接创建对象的语言。在 JavaScript 中类无法描述对象的行因为根本就不存在类对象直接定义自己的行为。再说一遍JavaScript 中只有对象
2.1、“类”函数
JS中并没有类这个概念但是JS一直在进行模仿类这种模仿行为实际上运用了函数的一种特殊特性所有的函数默认都会拥有一个名为prototype的共有并且不可枚举的属性它会指向另一个对象这个对象通常被称为Foo的原型。
那么这个对象到底是什么呢最直接的解释就是通过new Foo创建的每个对象将最终被[Prototype]链接到这个Foo.Prototype对象
在面向类的语言中类可以被复制多次就像使用模具制作东西一样。但是在JS中并没有类的复制机制你不能创建一个类的多个实例只能创建多个对象它们[Prototype]关联的是用同一个对象。new Foo会生成一个对象这个新对象的内部链接[[Prototype]]关联的是foo.Prototype对象。但是实际上new Foo函数调用实际上并内有直接创建关联这个关联只是一个意外的副作用。new Foo只是间接完成了我们的目标一个关联到其他对象的新对象Object.create(…)方法可以更直接的做到这个
在JS中我们并不会将一个对象“类”复制到另一个对象“实例”只是将他们关联起来 这个机制通常被称为原型继承但是这违背了动态脚本中对应的语义。继承意味着复制操作但是JS并不会复制对象属性JS会在两个对象之间创建一个关联这样一个对象就可以通过委托访问玲一个对象的属性和函数。委托可以更加准确的描述JS中对象的关联机制
2.2、“构造函数”
看以下代码
function Foo(){...
}
var a new Foo()到底是什么原因让给我们觉得Foo是一个类呢其中一个原因是因为我们看到了new关键字在面向类的语言中构造类实例时也会用到它。另一个原因是看起来我们执行了类的构造函数方法Foo的调用方式很像初始化类时类构造函数的调用方式
function Foo(){...
}
Foo.prototype.constructor Foo // true
var a new Foo()
a.constructor Foo // true可以看出Foo.prototype默认有一个公有并且不可枚举的属性.constructor这个属性引用的是对象关联的函数即Foo
此外可以看到通过构造函数调用new Foo创建的对象也有一个.constructor属性指向“创建这个对象的函数”
但是注意实际上 a 本身并没有 .constructor 属性。而且虽然 a.constructor 确实指向 Foo 函数但是这个属性并不是表示 a 由Foo“构造”
简单来说如果没有手动修改原型链大部分情况下.constructor属性会指向对象创建时的构造函数
上一段代码很容易让人认为 Foo 是一个构造函数因为我们使用 new 来调用它并且看到它“构造”了一个对象。实际上Foo 和你程序中的其他函数没有任何区别。函数本身并不是构造函数然而当你在普通的函数调用前面加上 new 关键字之后就会把这个函数调用变成一个“构造函数调用”。实际上new 会劫持所有普通函数并用构造对象的形式来调用它。
比如说
function NothingSpecial() {console.log( Dont mind me! );
}
var a new NothingSpecial();
// Dont mind me!
a; // {}NothingSpecial 只是一个普通的函数但是使用 new 调用时它就会构造一个对象并赋值给 a这看起来像是 new 的一个副作用无论如何都会构造一个对象。这个调用是一个构造函数调用但是 NothingSpecial 本身并不是一个构造函数。
换句话说在 JavaScript 中对于“构造函数”最准确的解释是所有带 new 的函数调用。函数不是构造函数但是当且仅当使用 new 时函数调用会变成“构造函数调用”。
2.3、技术
function Foo(name) {this.name name;
}
Foo.prototype.myName function() {return this.name;
};
var a new Foo( a );
var b new Foo( b );
a.myName(); // a
b.myName(); // b在这段代码中看起来似乎创建 a 和 b 时会把 Foo.prototype 对象复制到这两个对象中然而事实并不是这样。
在创建的过程中a 和 b 的内部 [[Prototype]] 都会关联到 Foo.prototype 上。当 a和 b 中无法找到 myName 时它会通过委托在 Foo.prototype 上找到。
实际上对象的 .constructor 会默认指向一个函数这个函数可以通过对象的 .prototype引用。
.constructor 并不是一个不可变属性。它是不可枚举参见上面的代码的但是它的值是可写的可以被修改。此外你可以给任意 [[Prototype]] 链中的任意对象添加一个名为 constructor 的属性或者对其进行修改你可以任意对其赋值。
这句话指的是在 JavaScript 中对象通常拥有一个 .constructor 属性它默认指向一个函数。这个函数可以通过对象的 .prototype 属性来引用。
当你使用构造函数创建对象时JavaScript 在创建对象的同时会给这个对象添加一个特殊的属性 .constructor用来指向创建该对象的构造函数。
举个例子
function Car(make, model) {this.make make;this.model model;
}let myCar new Car(Toyota, Corolla);
console.log(myCar.constructor); // 输出[Function: Car]在这个例子中myCar 对象的 .constructor 属性指向了 Car 这个构造函数。
另外.constructor 属性也可以通过对象的 .prototype 属性来访问构造函数。JavaScript 中的每个函数都有一个 .prototype 属性它是一个对象包含了一个 constructor 属性指向该函数自身。
例如
function Animal(name) {this.name name;
}console.log(Animal.prototype.constructor); // 输出[Function: Animal]在这里Animal.prototype.constructor 会返回 Animal 这个构造函数因为 Animal.prototype 是一个对象它包含了 constructor 属性指向了 Animal 函数本身。
所以.constructor 属性可以帮助你追踪对象是通过哪个构造函数创建的并且通过对象的 .prototype.constructor 可以直接访问到该构造函数。
3、原型继承 图中由下到上的箭头表明这是委托关联不是复制操作
function Foo(name) {this.name name;
}
Foo.prototype.myName function() {return this.name;
};
function Bar(name,label) {Foo.call( this, name );this.label label;
}
// 我们创建了一个新的 Bar.prototype 对象并关联到 Foo.prototype
Bar.prototype Object.create( Foo.prototype );
// 注意现在没有 Bar.prototype.constructor 了
// 如果你需要这个属性的话可能需要手动修复一下它
Bar.prototype.myLabel function() {return this.label;
};
var a new Bar( a, obj a );
a.myName(); // a
a.myLabel(); // obj a这段代码的核心部分就是语句 Bar.prototype Object.create( Foo.prototype )。调用Object.create(…) 会凭空创建一个“新”对象并把新对象内部的 [[Prototype]] 关联到你指定的对象本例中是 Foo.prototype。换句话说这条语句的意思是“创建一个新的 Bar.prototype 对象并把它关联到 Foo.prototype”。
要创建一个合适的关联对象我们必须使用 Object.create(…) 而不是使用具有副作用的 Foo(…)。
我们来对比一下两种把 Bar.prototype 关联到 Foo.prototype 的方法 ES6 之前需要抛弃默认的 Bar.prototype Bar.ptototype Object.create( Foo.prototype ); ES6 开始可以直接修改现有的 Bar.prototype Object.setPrototypeOf( Bar.prototype, Foo.prototype ); 如果忽略掉 Object.create(…) 方法带来的轻微性能损失抛弃的对象需要进行垃圾回收它实际上比 ES6 及其之后的方法更短而且可读性更高。不过无论如何这是两种完全不同的语法。
3.1、检查类
在JS中检查一个实例对象的继承祖先委托关联通常被称为内省或者反射
function Foo(){...
}
Foo.Prototype.blah ...;
var a new Foo()我们如何通过内省找出 a 的“祖先”委托关联呢第一种方法是站在“类”的角度来判断
a instanceof Foo; // trueinstanceof 操作符的左操作数是一个普通的对象右操作数是一个函数。instanceof 回答的问题是在 a 的整条 [[Prototype]] 链中是否有指向 Foo.prototype 的对象 可惜这个方法只能处理对象a和函数带 .prototype 引用的 Foo之间的关系。如果你想判断两个对象比如 a 和 b之间是否通过 [[Prototype]] 链关联只用 instanceof无法实现
下面是第二种判断 [[Prototype]] 反射的方法它更加简洁
Foo.prototype.isPrototypeOf( a ); // trueisPrototypeOf(…) 回答的问题是在 a 的整条 [[Prototype]] 链中是否出现过 Foo.prototype
我们只需要两个对象就可以判断它们之间的关系。举例来说
// 非常简单b 是否出现在 c 的 [[Prototype]] 链中
b.isPrototypeOf( c );4、对象关联
现在我们知道了[[Prototype]] 机制就是存在于对象中的一个内部链接它会引用其他对象。
通常来说这个链接的作用是如果在对象上没有找到需要的属性或者方法引用引擎就会继续在 [[Prototype]] 关联的对象上进行查找。同理如果在后者中也没有找到需要的引用就会继续查找它的 [[Prototype]]以此类推。这一系列对象的链接被称为“原型链”。
4.1、创建关联
Object.create(…) 会创建一个新对象bar并把它关联到我们指定的对象foo这样我们就可以充分发挥 [[Prototype]] 机制的威力委托并且避免不必要的麻烦比如使用 new 的构造函数调用会生成 .prototype 和 .constructor 引用
Object.create(null) 会 创 建 一 个 拥 有 空 或 者 说 null[[Prototype]]链接的对象这个对象无法进行委托。由于这个对象没有原型链所以instanceof 操作符之前解释过无法进行判断因此总是会返回 false。这些特殊的空 [[Prototype]] 对象通常被称作“字典”它们完全不会受到原型链的干扰因此非常适合用来存储数据。
4.2、关联关系是备用
看起来对象之间的关联关系是处理“缺失”属性或者方法时的一种备用选项。这个说法有点道理但是我认为这并不是 [[Prototype]] 的本质。
思考下面的代码
var anotherObject {cool: function() {console.log( cool! );}
};
var myObject Object.create( anotherObject );
myObject.cool(); // cool!由于存在 [[Prototype]] 机制这段代码可以正常工作
但是当你给开发者设计软件时假设要调用 myObject.cool()如果 myObject 中不存在 cool()时这条语句也可以正常工作的话那你的 API 设计就会变得很“神奇”对于未来维护你软件的开发者来说这可能不太好理解。
但是你可以让你的 API 设计不那么“神奇”同时仍然能发挥 [[Prototype]] 关联的威力
var anotherObject {cool: function() {console.log( cool! );}
};
var myObject Object.create( anotherObject );
myObject.doCool function() {this.cool(); // 内部委托
};
myObject.doCool(); // cool!5、总结
如果要访问对象中并不存在的一个属性[[Get]] 操作就会查找对象内部[[Prototype]] 关联的对象。这个关联关系实际上定义了一条“原型链”有点像嵌套的作用域链在查找属性时会对它进行遍历。
所有普通对象都有内置的 Object.prototype指向原型链的顶端比如说全局作用域如果在原型链中找不到指定的属性就会停止。toString()、valueOf() 和其他一些通用的功能都存在于 Object.prototype 对象上因此语言中所有的对象都可以使用它们。
关联两个对象最常用的方法是使用 new 关键词进行函数调用在调用的 4 个步骤中会创建一个关联其他对象的新对象。
使用 new 调用函数时会把新对象的 .prototype 属性关联到“其他对象”。带 new 的函数调用通常被称为“构造函数调用”尽管它们实际上和传统面向类语言中的类构造函数不一样。
虽然这些 JavaScript 机制和传统面向类语言中的“类初始化”和“类继承”很相似但是 JavaScript 中的机制有一个核心区别那就是不会进行复制对象之间是通过内部的[[Prototype]] 链关联的。
出于各种原因以“继承”结尾的术语包括“原型继承”和其他面向对象的术语都无法帮助你理解 JavaScript 的真实机制不仅仅是限制我们的思维模式。相比之下“委托”是一个更合适的术语因为对象之间的关系不是复制而是委托。