在Js中,当试图引用对象的某个属性时,会进行GET操作,第一步会检查对象本身是否拥有这个属性,如果有的话就使用它,否则就会往对象的原型链上查询,一直查到该属性或者到达原型链的最顶端(与作用域类似,作用域查询变量,原形链查询属性)。如下代码:
var anotherObject = {a:2}; var myObject = Object.create(anotherObject); console.log(myObject.a); //2
myObject本身是没有a属性的,但是它关联到了anotherObject,anotherObject.prototype即作为它的上层原型。
注:使用(for..in)遍历对象时,原理和查找原型链类似,任何可以在原型链中找到的属性并且是可枚举的都会被枚举,如下
var anotherObject = {a:2}; var myObject = Object.create(anotherObject); for(k in myObject){ console.log(k); //2 }
所有普通的原型链最终都会指向内置的Object.prototype.
在JS原型链中,假设对myObject.a进行赋值操作,如 myObject.a = 3; 通常情况下,我们会认为myObject会新创建一个a属性,并遮蔽上层的anotherObject.a,使得下次访问myObject.a的值时是为3,但事实并非如此,当我们进行这种操作时,引擎会根据三种情况进行不同工作。
1.当myObject的上层原型链(本例子即为anotherObject.prototype)中的a属性是只读的(即writable = false),该语句会被忽略(如果在严格模式下,即会抛出一个错误);
2.当myObject的上层原型链(本例子即为anotherObject.prototype)中的a属性是一个setter,即myObject.a=3的实质是对anotherObject.prototype.a =3的相同操作,不会为myObject新建一个属性。
3.当myObject的上层原型链(本例子即为anotherObject.prototype)中的a属性是一个普通属性,并且不是只读的,则会进行上述遮蔽操作。
如果想在第一种和第二种情况下进行遮蔽操作,那应该使用Object.defineProperty(...),而不是使用=符号赋值。
Js中并没有像面向类语言一样的复制机制,Js没有实质的类,不能复制多个实例,只能创建多个对象,并且他们的原型都会关联到同一个对象,如下代码:
function foo(){ ... }; var a = new foo(); var b = new foo(); Object.getPrototypeOf(a) === foo.prototype //(true) Object.getPrototypeOf(b) === foo.prototype //(true)
实际上,new操作带来的对象关联只是一个副作用,他的实际上调用foo函数,比较好的关联操作应该是选择Object.create(...)(下面介绍),而且new操作会带来一些误解,会让人误以为foo是一个类,并且有一个奇怪的constructor构造属性,如a.construtor === foo (true) ,foo.prototype.construtor === foo(true);实际上a本身并没有construtor这个属性,它是在找不到的时候往上一层查询在foo.prototype中找到。
举例来说,foo.prototype的construtor属性只是foo函数声明的时候的默认属性,与a是没有任何关系的,如果你创建了一个新对象代替foo.prototype,那么新对象不会自动获取该属性,如下
function foo(){ }; foo.prototype = {...}; var a = new foo(); a.construtor === foo; //false a.construtor === Object; //true
这是一种委托行为。a并没有construtor属性,所以他会委托给原型链上的foo.prototype,但是这个对象也没有这个属性,foo,prototype就会再委托给他的原型链上的Object.prototype,并在Object.prototype对象中找到这个属性。
Js最好的继承机制应该是Object.create(...),如下:
function foo(){ ... }; var a = Object.create(foo.prototype);
调用Object.create函数会创建一个新对象,并把新对象的原型链关联到你指定的对象(本例子中为foo,prototype).
注意,下面是两种常见的错误操作:
1.bar.prototype = foo.prototype;(和想要的机制不一样,是引用,而不是委托)
2.var bar.prototype = new foo();(会产生一些副作用,如foo会被执行,可能与预期结果不一样)
另外,在E5之前是没有Obect.create函数的,可以采用以下代码:
if(!Object.create){ Object.create = function(o){ function F(){}; F.prototype = o; return new F(); } }