摘自js李炎恢老师视频笔记
先来看一个要理解的核心点(原型链继承核心):
Desk.prototype = new Box()
==>
Desk.prototype.__proto__ = Box.prototype
即 Box.prototype 在 Desk.__proto__ 原型链上
继承是面向对象中一个比较核心的概念。其他正统面向对象语言都会用两种方式实现继
承:一个是接口实现,一个是继承。而 ECMAScript 只支持继承,不支持接口实现,而实现
继承的方式【1.依靠原型链】完成。
function Box() { //Box 构造
this.name = 'Lee';
}
function Desk() { //Desk 构造
this.age = 100;
}
Desk.prototype = new Box(); //Desk 继承了 Box,通过原型,形成链条
var desk = new Desk();
alert(desk.age);//100
alert(desk.name); //"Lee" 得到被继承的属性
function Table() { //Table 构造
this.level = 'AAAAA';
}
Table.prototype = new Desk(); //继续原型链继承
var table = new Table();
alert(table.name); //"Lee" 继承了 Box 和 Desk
如果要实例化 table,那么 Desk 实例中有 age=100,原型中增加相同的属性 age=200,
最后结果是多少呢?
Desk.prototype.age = 200; //实例和原型中均包含 age
PS :以上原型链继承还缺少一环,那就是 Obejct,所有的构造函数都继承自 Object。而
继承 Object 是自动完成的,并不需要程序员手动继承。
经过继承后的实例,他们的从属关系会怎样呢?
*//子类型从属于自己或者是他的超类型
alert(desk instanceof Desk);//true
alert(desk instanceof Box);//true
alert(table instanceof Object); //true
alert(desk instanceof Table); //false,desk 是 table 的超类
alert(table instanceof Desk); //true
alert(table instanceof Box); //true*
在 JavaScript 里,被继承的函数称为超类型(父类,基类也行,其他语言叫法),继承的
函数称为子类型(子类,派生类)。继承也有之前问题,比如字面量重写原型会中断关系,使用引用类型的原型,并且子类型还无法给超类型传递参数。
为了解决引用共享(①)
和超类型无法传参(②)
的问题,我们采用一种叫**【2.借用构造函数】的技术,或者成为对象冒充**(伪造对象、经典继承)的技术来解决这两种问题。
function Box(age) {
this.name = ['Lee', 'Jack', 'Hello']
this.age = age;
}
function Desk(age) {
Box.call(this, age); //对象冒充,给超类型传参(2)
}
var desk = new Desk(200);
alert(desk.age);//200
alert(desk.name);//Lee,Jack,Hello
desk.name.push('AAA'); //添加的新数据,只给 desk
alert(desk.name);//Lee,Jack,Hello,AAA
var desk2=new Desk(300)
alert(desk2.name);//Lee,Jack,Hello(1)引用放到构造函数中不会被共享
function Box(age) {
this.name = [‘Lee’, ‘Jack’, ‘Hello’]
this.age = age;
}
Box.prototype.family=‘家庭’;
function Desk(age) {
Box.call(this, age); //对象冒充,只能继承构造里的信息,继承不到原型里面的
}
var desk = new Desk(200);
alert(desk.family); //undefined
借用构造函数虽然解决了刚才两种问题(引用共享和超类型无法传参
),但没有原型,函数方法复用则无从谈起
。所以,我们需要原型链+借用构造函数的模式,这种模式成为**【3.组合继承】**。
组合继承(原型链+借用构造函数)解决了:引用共享、超类型无法传参、没有原型
实现:引用在构造函数里面不被共享、构造函数(对象冒充)传参、原型方法的共享
function Box(age) {
this.name = ['Lee', 'Jack', 'Hello']
this.age = age;
}
Box.prototype.run = function () {
return this.name + this.age;
};
function Desk(age) {
Box.call(this, age); //对象冒充
}
Desk.prototype = new Box(); //原型链继承
var desk = new Desk(100);
alert(desk.age);//100 (对象冒充只继承构造里面的)
alert(desk.run());//Lee,Jack,Hello100 (原型链继承只继承原型里面的)
接上面:
desk.name.push(‘AAA’); //添加的新数据,只给 desk
alert(desk.name);//Lee,Jack,Hello,AAA
var desk2=new Desk(300)
alert(desk2.name);//Lee,Jack,Hello(1)引用放到构造函数中不会被共享
还有一种继承模式叫做:【4.原型式继承】;这种继承借助原型并基于已有的对象创建新对象,
同时还不必因此创建自定义类型。
原型式继承其实是原型链继承的改写版,意思是一样的
//临时中转函数
//o表示将要传递进入的一个对象
function obj(o) { //传递一个字面量函数
//F构造是一个临时创建的对象,用来存储传递过来的对象
function F() {} //创建一个构造函数
//将o对象实例赋值给F构造的原型对象
F.prototype = o; //把字面量函数赋值给构造函数的原型
//最后返回这个得到传递过来对象的对象实例
return new F(); //最终返回出实例化的构造函数
}
//F.prototype=o其实就相当于Desk.prototype=new Box();Desk继承了Box
//new F()相当于desk
//这是字面量的声明方式,相当于var box=new Box()
var box = { //字面量对象
name : 'Lee',
arr : ['哥哥','妹妹','姐姐']
};
var box1 = obj(box); //传递
alert(box1.name);//Lee
box1.name = 'Jack';
alert(box1.name);//Jack
alert(box1.arr);//哥哥,妹妹,姐姐
box1.arr.push('父母');
alert(box1.arr);//哥哥,妹妹,姐姐,父母
var box2 = obj(box); //传递
alert(box2.name);//Lee (基本类型没有共享)
alert(box2.arr); //哥哥,妹妹,姐姐,父母 【引用类型共享了】
【5.寄生式继承】把原型式+工厂模式结合而来,目的是为了封装创建对象的过程。寄生式继承的思路是创建一个用于封装继承过程的函数,通过传入一个对象,然后复制一个对象的副本,然后对象进行扩展,最后返回这个对象。这个扩展的过程就可以理解是一种继承。这种继承的优点就是对一个简单对象实现继承,如果这个对象不是自定义类型时。缺点是没有办法实现函数的复用。
function create(o) { //封装创建过程
var f= obj(o);
f.run = function () {
return this.arr; //同样,会共享引用
};
return f;
}
function obj(o) { //传递一个字面量函数
function F() {} //创建一个构造函数
F.prototype = o; //把字面量函数赋值给构造函数的原型
return new F(); //最终返回出实例化的构造函数
}
function create(o) { //封装创建过程
var f= obj(o);
f.run=function(){return this.name+‘方法’;} //寄生函数可以对 f 进行扩展
return f;
}
var box = { //字面量对象
name : ‘Lee’,
arr : [‘哥哥’,‘妹妹’,‘姐姐’]
};
var box1 = create(box);
alert(box1.name);//Lee
alert(box1.run())//Lee方法
组合式继承是 JavaScript 最常用的继承模式
;但,组合式继承也有一点小问题,就是超类型在使用过程中会被调用两次:一次是创建子类型的时候,另一次是在子类型构造函数的
内部,造成了子类型的原型中多了很多不必要的属性。
function Box(name) {
this.name = name;
this.arr = ['哥哥','妹妹','父母'];
}
Box.prototype.run = function () {
return this.name;
};
function Desk(name, age) {
Box.call(this, name); //第二次调用 Box
this.age = age;
}
Desk.prototype = new Box(); //第一次调用 Box
以上代码是之前的组合继承,那么**【6.寄生组合继承】**,解决了两次调用的问题。寄生式组合继承的方式是使用超类型的原型的副本来作为子类型的原型,这样就避免了创建不必要的属性。
function obj(o) {
function F() {}
F.prototype = o;
return new F();
}
function create(box, desk) {
var f = obj(box.prototype);
f.constructor = desk;
desk.prototype = f;
}
function Box(name) {
this.name = name;
this.arr = ['哥哥','妹妹','父母'];
}
Box.prototype.run = function () {
return this.name;
};
function Desk(name, age) {
Box.call(this, name);
this.age = age;
}
create(Box, Desk); //通过这里实现继承
var desk = new Desk('Lee',100);
desk.arr.push('姐姐');
alert(desk.arr);
alert(desk.run()); //只共享了方法
var desk2 = new Desk('Jack', 200);
alert(desk2.arr); //引用问题解决