原型式继承中构造器被覆盖的问题

2014-09-19  本文已影响97人  江枫

看下面代码

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;
};
  1. 看到了吧,和上面我们实验的结果是一致的。Object.create 的只是fn的原型prototype,由于原型中丢失了constructor属性,所以我们在访问的时候就会出现找到了父亲的constructor属性。

  2. 再看_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 {}
    

符合预期。

上一篇下一篇

猜你喜欢

热点阅读