原型链梳理

原型链与类

最近参加58到家面试,一面就被pass了。一道原型链的题直接卡住了。回来仔细想想,题目一点也不复杂,只是平时不关注罢了。受《你不知道的js》一书影响,基本上没有用原型实现过继承,所以不会也是有一部分原因的(借口)。

原型链的构成

原理链是JS的特殊属性:prototype。由于使用Object.create或者使用new时会将函数的prototype上的方法和属性挂载在新生成对象上。所以:

function Foo (name) {
  this.name = name
}
Foo.prototype.getName = function () {
  return this.name
}

var obj = new Foo('xxx')
obj.name // xxx

例子中是最简单的方法。来看下constructor:

  1. constructor指向了构造函数,不管是prototype下的constructor或者是constructor属性。
  2. new的时候会执行constructor。而我们知道所以的函数非prototype属性和方法是不能进入原型链,所以是不是挂载在prototype下的constructor会执行呢?看下面的代码:
function Foo(name){this.name = name}
function Bar (name) { this.name = name + 'xx'}
Foo.prototype = {
  constructor: Bar,
  getName: function () {return this.name}
}

var obj = new Foo('22')
obj.getName() // 22

var Func1 = Object.create(Foo)
typeof Func1 // object

看到没?自己定义的constructor在实际new的时候根本没有用到!!而create只是原型指向Foo,生成的是对象,而不是函数。

所以,constructor是挂载到生成的对象里面的__proto__里面的。随便new一个函数,发现proto里面的constructor都是指向的这个函数。指定是无效的。

来看下难倒我的题目:

function Foo (name) {this.name = name}
Foo.prototype = {
  constructor: Foo,
  getName: function () {return this.name}
}

var obj = new Foo('xx')
obj.__proto__
obj.__proto__.__proto__
obj.__proto__.__proto__.__proto__

// 结果
obj.__proto__ === Foo.prototype
obj.__proto__.__proto__ === Object.prototype
obj.__proto__.__proto__ === null

为什么会让我迷惑呢?其实还是因为显式的写了constructor。这个constructor写不写根本就没有不影响构建,也不影响生成的对象。看下之前我们交叉写constructor:

function Foo(name){this.name = name}
function Bar (name) { this.name = name + 'xx'}
Foo.prototype = {
  constructor: Bar,
  getName: function () {return this.name}
}

var obj = new Foo('22')
obj.__proto__ === Bar.prototype // false
obj.__proto__.constructor === Bar // true
obj.__proto__ === Foo.prototype // true
obj.__proto__.__proto__ === Object.prototype // true

从例子中可以看出,我们为Prototype设置的constructor属性是被保留的,指向了Bar函数(非prototype)。这样赋值有什么用呢?其实还是有用处的,调用super时,使用调用原型连上一层的constructor,这样就可以自定义了。

用原型类模拟类

好吧,这个我没用过。在好多书中也不推荐这种方式。现在简单研究下。

function Foo (name) {this.name = name}

function Bar () {}

Bar.prototype = Foo.prototype

var obj = new Bar()

obj.__proto__.__proto__ === Object.prototype
obj.__proto__ === Foo.prototype

可以看出,Bar的prototype指向的是Foo的prototype。而__proto__下面的constructor却不包含Bar,如果设置

Bar.prototype.constructor = Bar
obj.__proto__ === Bar.prototype// true
obj.__proto__.constructor === Bar
obj.__proto__.__proto__ === Object.prototype

则Bar的prototype的一下层直接指向了Object。这和我们想要的继承不一样。因此我们定义一个中间函数。将中间函数的prototype指向Foo,再new一下,挂载prototype。

function Foo () {}
Foo.prototype.getName = function () {}
var F = function () {}
F.prototype = Foo.prototype
function Bar () {}
Bar.prototype = new F()
Bar.prototype.constructor = Bar

var obj = new Bar()

obj.__proto__.__proto__ === Foo.prototype // true

可以看出,这样写法下,我们的Proto发生就有三级了,实现了继承。用它写个extend函数:

function extend (target, from) {
  var F = function () {}
  F.prototype = from.prototype
  target.prototype = new F()
  target.prototype.constructor = target
  return target
}

function Foo () {}
Foo.prototype.getName = function () {return 1}

var Bar = extend(function () {}, Foo)

var obj = new Bar()
obj.getName() // 1


// 被覆盖
function Bar2 () {}
Bar2.prototype.getName = function () {return 2}
extend(Bar2, Foo)
var obj2 = new Bar2()
obj2.getName()

看来出,之前定义的Bar2已经被覆盖了。可以想其他办法。

posted @ 2017-12-26 18:11  无梦灬  阅读(169)  评论(0)    收藏  举报