原型可以看作是对象的基类,几乎所有的函数都有一个名为prototype的属性,原型对象用来创建新的对象实例。所有对象创建的对象实例共享该原型对象,并且这些实例可以访问原型对象的属性。
例如hasOwnProperty()方法被定义在泛用对象Object的原型对象中,但它可以被任何对象当作自己的属性访问:
var book = {
name: "Red and Black"
};
console.log("name" in book); //true
//hasOwnProperty()方法是判断该属性是否为当前对象的自有属性
console.log(book.hasOwnProperty(name)); //true
console.log("hasOwnProperty" in book); //true
console.log(book.hasOwnProperty("hasOwnProperty"); //false
console.log(Object.prototype.hasOwnProperty("hasOwnProperty"); //true
[[Prototype]]
JS中的对象有一个特殊的[[Prototype]]内置属性,其实就是对于其他对象的引用。一个对象实例可以根据内部属性[[Prototype]]跟踪其原型对象(该属性指向该实例使用的原型对象)。几乎所有对象被创建时[[Prototype]]都会被赋予一个非空值。例如下面的例子:
如果要引用对象的属性sayName,JS引擎首先会检查对象本身(实例对象)是否有这个属性,如果有就直接使用,否则就需要使用[[Prototype]]链,类似作用域链。[[Prototype]]链的终点是Object.prototype,如果这里也没有,那么就会出现问题。所有的对象都源于Object.prototype,因为它包含了javascript许多通用功能。
属性设置与屏蔽
给对象设置一个属性并不仅仅是添加一个新属性或则修改这个属性。
myObject.name = "Genius";
如果对象myObject中包含名为name的普通数据访问属性,那么这条语句会修改已有的属性。
如果属性name不是直接存在于对象myObject中,那么就会遍历原型链查找这个属性,如果找不到就会直接创建在myObject中。
如果属性name存在与对象myObject中,也存在与原型链上层,那么就会发生屏蔽。myObject的属性会屏蔽所有原型链上层的同名属性。
如果这个属性只存在于原型链上层,那么就会出现三种情况:
1.如果在原型链上层存在名为name的普通数据访问属性,并且没有被标记为只读,那么就会直接在myObject中添加一个名为foo的新属性,它是屏蔽属性。
2.如果在原型链上层存在name,但它被标记为只读,那么无法修改已有属性或则在myObject创建屏蔽属性。严格模式下,会抛出一个错误。
3.如果在原型链上层存在name并且它是一个setter,那就一定会调用这个setter。name不会被添加到myObject,也不会重新定义name这个setter。
如果第二种和第三种情况下也需要屏蔽name,就不能用=操作符来赋值,而是使用Object.defineProperty(...)来向myObject添加属性name。真奇怪。。。
修改委托属性会导致隐式屏蔽
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++; //隐式屏蔽
myObject.a; //3
anotherObject.a; //2
myObject.hasOwnProperty("a"); //true
myObject.a++想通过委托查找并修改原型链上层的属性,但是myObject.a++ 等价于 myObject.a = myObject.a + 1;因此会先获得原型链上层的属性,然后增加1,最后用[Put]]把这个3赋值给新创建的屏蔽属性a。
为什么一个对象需要关联到另一个对象,因为JavaScript中只有对象,不存在类。(ES6新增了类(class),刺激)