翻笔记的时候,翻到js继承的一张图,想起js继承的方式,写个总结记录一下~
先说一个老生常谈的问题,_proto_和prototype属性的区别
_proto_和prototype
一图解所有:
图片来自:www.zhihu.com/question/34…
解释
首先,要明确几个点:
对象具有属性_proto_,可称为隐式原型,一个对象的隐式原型指向构造该对象的构造函数的原型。
方法这个特殊的对象,除了和其他对象一样有上述
proto属性之外,还有自己特有的属性——原型属性(prototype),它指向一个原型对象。原型对象也有一个属性,叫做constructor,这个属性包含了一个指针,指回原构造函数。
现在看看上面这副图
构造函数Foo()
构造函数的原型属性Foo.prototype指向了原型对象,在原型对象里有共有的方法,所有构造函数声明的实例(这里是f1,f2)都可以共享这个方法。
原型对象Foo.prototype
Foo.prototype保存着实例共享的方法,有一个指针constructor指回构造函数。
new foo()
f1和f2是Foo这个对象的两个实例,这两个对象有属性_proto_,指向构造函数Foo()的原型对象。也是因为这样,实例才可以用Foo这个对象的原型链上的方法和属性;
构造函数Foo()除了是方法,也是对象,它也有_proto_属性。它指向它的构造函数的原型对象。函数的构造函数就是Function,因此这里的_proto_指向了Function.prototype。
步入正题
简单原型链的继承
基本思想就是让子类的原型对象指向父类的实例;
function Father() { this.v = "father"; this.aa = [1,2]; } function Son() { this.a = "son"; } Son.prototype = new Father(); var son1 = new Son(); var son2 = new Son(); son1.aa.push(3);//改变son1的原型的数组也会改变son2 console.log(son1.aa);//[ 1, 2, 3 ] console.log(son2.aa);//[ 1, 2, 3 ]复制代码
这种方法就是要注意给子类的原型添加方法时,不用采用字面量形式,防止覆盖了子类的原型对象;
缺点:这种方式对值类型的修改没有问题,而对引用类型的赋值会带来问题;
如果你通过点运算:
son1.aa = ["a"];
这种方式是你自己给自己的实例增加了一个aa属性;
借用构造函数
在子类中借住apply()和 call()方法来改变对象的执行上下文;
function Father() {
this.v = "father";
this.aa = ['a','b','c'];
}
function Son() {
Father.call(this);//引用类型也变成私有了
}
var son1 = new Son();
var father1 = new Father();
son1.a.push("d");
console.log(son1.a); //[ 'a', 'b', 'c', 'd' ]
console.log(father1.a); //[ 'a', 'b', 'c' ]复制代码
由上面也可见,对于引用类型,子类实例的改变并没有造成其他影响;
借用构造函数还有一个好处就是可以传递参数;
缺点:但是这种方法无法实现函数的复用,对于属性指向函数的,每次子类实例都要重新生成此函数;
组合继承(原型链+构造函数)
使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承;
解决了函数复用的问题;
缺点:通过这种方法要生成一个父类的实例,而子类通过super.call又要调用一次父类构造函数,浪费内存;
function Father(name) {
this.name = name;
this.a = ['a','b','c'];
}
Father.prototype.sayName = function () {
console.log(this.name)
}
function Son(name) {
//继承属性
Father.call(this,name);//第一次调用父类构造函数
}
//继承方法
Son.prototype = new Father();//第二次调用父类构造函数复制代码
寄生组合继承
基于已有的对象创建新对象;
砍掉了组合继承里那份多余的父类实例;
function object(o) {
function F() {}
F.prototype = o
return new F()//F的_proto_属性指向F.prototype
}
function Super(){
// 只在此处声明基本属性和引用属性
this.val = 1;
this.arr = [1];
}
// 在此处声明函数
Super.prototype.fun1 = function(){};
Super.prototype.fun2 = function(){};
//Super.prototype.fun3...
function Sub(){
Super.call(this); // 核心
// ...
}
var proto = object(Super.prototype); // 核心
proto.constructor = Sub; // 核心
Sub.prototype = proto; // 核心
var sub = new Sub();复制代码
原型式
子类实例需要自己逐步增强;
function object(obj){ // 生孩子函数 beget:龙beget龙,凤beget凤。
var F = function(){};
F.prototype = obj;
return new F();
}
function Super(){
this.val = 1;
this.arr = [1];
}
// 拿到父类对象
var sup = new Super();
// 生孩子
var sub = object(sup); // 核心
sub.attr1 = 1;
sub.attr2 = 2;复制代码
ES5提供了Object.create()函数,内部就是原型式继承,IE9+支持;
缺点是原型引用属性会被所有实例共享,因为是用整个父类对象来充当了子类原型对象,所以这个缺陷无可避免
其他
剩下一种寄生式就不讲啦,下面的链接有,有兴趣的可以看看,个人觉得上面几种理解透就够啦~