原型式继承中构造器被覆盖的问题
看下面代码
function Person(){}
function Man(){}
function Woman(){}
Woman.prototype = new Person
var a = new Man
var b = new Woman
a.constructor
b.constructor
问 a.constructor和b.constructor分别是什么?
a.constructor
-> function Man(){}
b.constructor
-> function Person(){}
会不会觉得 b.constructor = function Person(){} 好像不太对劲呢?
接下来你会干的一件事情是什么呢?既然是通过new对应的构造函数得到的对象,我猜你应该会打印下面的语句
Man.constructor
-> function Function() { [native code] }
Woman.constructor
-> function Function() { [native code] }
Person.constructor
-> function Function() { [native code] }
可能你会反应个2-3秒,然后哈哈一笑,Man是函数,函数不都是Function构造的吗!
现在距离真相就只差一步之遥了,接下来做什么呢?
Man.prototype.constructor
-> function Man(){}
Woman.prototype.constructor
-> function Person(){}
Person.prototype.constructor
-> function Person(){}
原来 Woman.prototype = new Person这个操作把Woman自己的constructor属性覆盖了,为什么说是覆盖了呢?因为constructor属性在函数定义之时就已经存在了。
这样当b访问constructor属性时在当前的Women的prototype上就找不到对应的constructor属性,进而就会向上访问对象的原型链,最终找到的是Person对象的constructor属性。
既然知道了原因,要修正这个问题就简单了
Woman.prototype.constructor = Woman
-> function Woman(){}
var c = new Woman
-> undefined
c.constructor
-> function Woman(){}
new 操作符
*ECMA262中对new操作符有下面的定义 *
11.2.2 The new Operator
The production NewExpression : new NewExpression is evaluated as follows:
- Let ref be the result of evaluating NewExpression.
- Let constructor be GetValue(ref).
- If Type(constructor) is not Object, throw a TypeError exception.
- If constructor does not implement the [[Construct]] internal method, throw a TypeErrorexception.
- Return the result of calling the [[Construct]] internal method on constructor, providing no arguments (that is, an empty list of arguments).
- The production MemberExpression : new MemberExpression Arguments is evaluated as follows:
- Let ref be the result of evaluating MemberExpression.
- Let constructor be GetValue(ref).
- Let argList be the result of evaluating Arguments, producing an internal list of argument values (11.2.4).
- If Type(constructor) is not Object, throw a TypeError exception.
- If constructor does not implement the [[Construct]] internal method, throw a TypeErrorexception.
- Return the result of calling the [[Construct]] internal method on constructor, providing the listargList as the argument values.
上面的意思就是先求NewExpression的值为ref,然后在ref上调用内部的[[construct]]方法,还是比较抽象是吧,stackoverflow上有人给出了new操作符的执行流程。
var _new = function(fn) {
var obj = Object.create(fn.prototype);
var result = fn.apply(obj);
return result != null && typeof result === 'object' ? result : obj;
};
-
看到了吧,和上面我们实验的结果是一致的。Object.create 的只是fn的原型prototype,由于原型中丢失了constructor属性,所以我们在访问的时候就会出现找到了父亲的constructor属性。
-
再看_new中的fn.apply(obj),new Woman的时候fn是Woman构造函数呢还是Person构造函数呢,这个我们验证一下.
function A(){ console.log('in A'); } -> undefined function B(){ console.log('in B'); } -> undefined B.prototype = new A(); -> in A -> A {} b = new B() -> in B -> B {}
符合预期。