一道关于原型链继承方面的js面试题

Javascipt中关于继承实现的关键是prototype机制,正确的理解原型链机制可以帮助我们在复杂的场景下解决关于继承的一些问题。下面摘取了来自segmentfault上的一道关于原型链继承方面的面试题,深入分析如何正确的使用原型链实现继承。

题目如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
//片段1:

var A = function() {
this.name = 'apple';
};

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

/**
* 补充代码实现以下的功能:通过A.extend接口即可实现B对A的继承。
*/


var B = A.extend({
initialize: function() {
this.superclass.initialize.call(this);
this.total = 3;
},
say: function() {
return '我有' + this.total + '个' + this.getName()
}
});

var b = new B();
console.log(b.say()); //我有3个apple

分析:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 片段2

/**
* 需要转化为以下的B
*/
var A = function() {
this.name = 'apple';
};
A.prototype.getName = function() {
return this.name;
};

var B = function() {
A.call(this);
this.total = 3;
};
B.prototype = Object.create(A.prototype);
B.prototype.say = function() {
return '我有' + this.total + '个' + this.getName()
};
var b = new B();
console.log(b.say()); //我有3个apple

由题目可知,题目的目的在于对原型链的理解。关键在于子类B如何获得自己的实例属性和原型上的属性。代码片段2相信大家都比较熟悉,一段非常经典的关于原型链继承的实现代码。B通过在构造函数中调用A.call(this)获取到父类A的实例属性,通过B.prototype = Object.create(A.prototype);获取父类的A的原型属性,从而实现继承的目的。代码片段1如果要达到最后的输出效果应该将B操作为和片段2中的B一样的实例属性和原型属性。转换的关键包括:

  • A函数直接调用extend接口,该接口如何添加
  • extend接口中传入了一个对象,对象中包含了两个函数,say函数按照常理来讲应该作为B的原型属性添加在B的prototype原型对象上。initialize函数用来处理B的实例属性,包括将total作为实例属性添加在自己的构造函数中以及在B的构造函数中调用父类A的构造函数。

补充代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Function.prototype.extend= Function.prototype.extend || function(obj) {

var self=this;

function SubClass(){
this.superclass = {initialize:self};
if (obj.initialize)obj.initialize.call(this)
}

SubClass.prototype = new self();
SubClass.prototype.constructor = SubClass;

for(var key in obj){
if(key !== 'initialize'){
SubClass.prototype[key] = obj[key]
}
}
return SubClass;
}

如上所示,补充代码做了以下工作:

  • A作为Function的实例存在,因此如果A可以直接调用一个函数的话,可以将该函数作为原型属性添加在Function的原型对象中。
  • 临时函数SubClass的构造函数中添加了superclass属性,用来保存Athis值,用于后面obj.initialize调用时,A构造函数的调用。obj.initialize.call(this)的调用保证了将total属性作为实例属性添加在SubClass的构造函数中,从而返回给B
  • SubClass.prototype = new self();保证了SubClass的实例将继承A的实例属性和原型属性。
  • 依次遍历参数中除initialize属性外的其他属性,并将其添加在SubClassprototype上,并返回给B。

经过这样的处理后,运行var b = new B(),此时实例b包括以下属性:

1
2
3
4
5
6
7
8
9
10
11
name: 'apple' // 来自于obj.initialize.call(this)
superClass: Object
total: 3 // 来自于obj.initialize.call(this)
__proto__: { // 来自于SubClass.prototype = new self();
constructor: SubClass,
name: 'apple',
say: function(){...},
__proto__: {
getName: function(){...}
}
}
作者

monster1935

发布于

2017-03-22

更新于

2024-09-25

许可协议