javascript 原型继承
1、区分类继承和实例化的差别
首先来看两个例子:
非常常用的js继承是这个样子的:1
B.prototype = new A()
这时候特别容易和实例化给混淆了:1
b = new A()
感觉特别的像有木有!这个时候开始好奇new到底干了什么(理解原理非常重要)
new一个对象的时候发生了什么
1 | b = new A() |
- 创建空对象
- 把this指向该空对象,同时还继承了该函数的原型,即临时对象的proto指向构造函数的prototype
- 执行构造函数中的代码(为这个对象添加属性),也就是赋值。
- 返回新对象
1
2
3
4
5
6
7function New (A) {
var b = { '__proto__': A.prototype }; /*第一步*/
return function () {
A.apply(b, arguments); /*第二步*/
return b; /*第三步*/
};
}
那么怎么区分什么时候是类继承,什么时候是实例化呢?
无论是实例化还是继承,本质上都是拥有A的属性,所以要像清楚js是怎么查找一个东西的属性:
当查找一个对象的属性时,JavaScript会向上遍历原型链,直到找到给定名称的属性为止。—–出自JavaScript秘密花园
用代码表示如下:1
2
3
4
5
6
7
8
9function getProperty(obj, prop) {
if (obj.hasOwnProperty(prop)){
return obj[prop]
}else if (obj.__proto__ !== null){
return getProperty(obj.__proto__, prop)
}else{
return undefined
}
}
嗯,这下可以解释两者区别了:B.prototype = new A()
:B.prototype要找自己属性的时候,先看看自己有没有–>看看自己的proto(也就是A.prototype)有没有–>一路往上。b = new A()
:b找属性的时候,先看看自己有没有–>看看自己的proto(也就是A.prototype)有没有–>一路往上。
2、实例化
众所周知,JavaScript里面所有的数据类型都是对象,我们需要一种机制,将所有的对象联系起来,因此,new命令引入了
JavaScript,用来从原型对象生成一个实例对象。在JavaScript语言中,new命令后面跟的不是类,而是构造函数。
具体来说
两个相关的概念:
- 类:比如 人类
- 实例:比如 王小二
那么,王小二的父母孕育他到他直到出生的过程,就叫:实例化1
2
3
4
5function Human(name){
this.name = name;
}
var wangxiaoer = new Human('王小二'); //这一步叫作 实例化
3、继承
众所周知,JavaScript的继承是实现继承,而没有java中的接口继承。这是因为JavaScript中函数没有签名,而实现继承依靠的是原型链来实现的。
原型继承到底继承了什么?
实际上就是继承了构造函数和原型链两个东西。其中构造函数继承使用apply()
实现的,这就意味着仅仅是把父类里面的属性复制了
一遍,对其进行任何的更改,都不会影响其他的实例,在继承之后对父类进行任何更改也不会影响其子类。而对于原型链的修改,
则是表示子类和父类公用原型链上的属性和方法,对于原型链的更改只能从上游源头进行修改,当然子类可以重写父类的方法,不过这样做实际上
就是先给子类增添一个重名的方法,而导致JS引擎先调用此方法而不去调用原型链的方法。
原型继承的方法
有四种方式可以实现构造函数的继承:
- 1.调用apply或者call方法
1
2
3
4
5
6
7
8
9
10
11
12
13function Animal() {
this.species = '动物'
}
Animal.prototype.getName = function() {
console.log('我是动物')
}
function Cat() {
Animal.apply(this, arguments)
}
var cat = new Cat()
cat.species // 动物
cat.getName() // undefined
这种方法可以继承父类构造函数的属性,但是无法继承prototype属性,即父类中共享的方法和属性。
- 2.改写prototype对象
1
2Cat.prototype = new Animal()
Cat.prototype.constructor = Cat
这是最常用的方法来模拟单继承,缺点是始终要保留Animal的对象,如果Animal对象比较大时,会消耗部分内存(其实很少),并且没有实现多继承。这种方法构造函数和prototype都会被继承。
- 3.直接继承prototype
1
2Cat.prototype = Animal.prototype
Cat.prototype.constructor = Cat
缺点是当修改了Cat.prototype上的方法时会影响Animal.prototype,也无法访问到构造函数的属性。这种方法可以继承prototype属性,但是无法继承构造函数的属性。
- 4.利用空对象作中介
1
2
3
4var F = function(){}
F.prototype = Animal.prototype
Cat.prototype = new F()
Cat.prototype.constructor = Cat
缺点是无法继承父类封装的属性,无法访问到构造函数里面的属性,但是可以继承prototype的属性。
若要实现封装属性和共享同时继承到子类中,就需要同时结合上面的1和4,请使用jqury的extend方法或者其他深拷贝方法。
参考:
js继承与实例化-博客专用马甲
Javascript继承机制的设计思想-阮一峰
JavaScript 继承的那些事- Ahonn