目录
什么是原型链 ?
每个对象拥有一个原型对象,对象以其原型为模板,从原型继承属性和方法。当然 原型对象 也可能拥有原型,并从中继承属性和方法,依此类推,这种关系被称为原型链。
准确地说,这些属性和方法定义在Object的构造器函数(constructor functions)之上的prototype属性上,而非对象实例本身。
原型链的七种常见继承模式
一、原型链继承
利用原型链来实现继承,父类的一个实例作为子类的原型
function child(){
this.xx = 'xx'; //子类自己的定义
}
child.prototype = new parent();
//这里new parent()父类对象并没有constructor属性,需要后面加上
child.prototype.constructor = child
原理: 子类的原型对象指向父类的实例, 当子类实例找不到属性和方法时,会沿着原型链往上找。
优点:简单,既是子类实例也是父类实例,父类新加原型方法或者属性,子类都能访问到
缺点:
① 不能实现多继承,所有子类的实例的原型都共享一个父类实例属性和方法
② 不能传参
例子1:
function Show() {
this.name = "run";
}
function Run() {
this.age = "20"; //Run继承了Show,通过原型,形成链条
}
Run.prototype = new Show();
var show = new Run();
alert(show.name); //结果:run
例子2:
function SuperType() {
this.colors = ["red", "blue", "green"];
}
SuperType.prototype.Fun = function () {};
function SubType() {}
//继承了SuperType
SubType.prototype = new SuperType();
var instance1 = new SubType();
instance1.colors.push("black");
alert(instance1.colors); //"red,blue,green,black"
var instance2 = new SubType();
alert(instance2.colors); //"red,blue,green,black"
优点:能通过 instanceOf 和 isPrototypeOf 的检测
注意:给原型添加方法的语句一定要放在原型替换SubType.prototype = new SuperType();之后
缺点:
(1) SuperType中的属性(不是方法)也变成了SubType的prototype中的公用属性,如上面例子中的color属性,可以同时被instance1和instance2修改。
(2) 创建子类型的时候,不能向父类型的构造函数中传递参数。
二、构造函数继承
通过使用call、apply方法可以在新创建的对象上执行构造函数,用父类的构造函数来增加子类的实例。
// 创建子类、添加子类属性。
function arrange(name) {
Person.call(this, name); // 执行父构造,将This指向本身,拉取父私有属性;
}
原理: 子类的构造函数中执行父类的构造函数,并且改变子类的this绑定。
优点:简单,直接父类的属性和方法
缺点:无法继承原型链上的属性方法
构造函数继承(对象冒充继承):为了解决引用共享和超类型无法传参的问题,我们采用一种叫借用构造函数的技术,或者成为对象冒充(伪造对象、经典继承)的技术来解决这两种问题。
function Box(age) { this.name = ["Lee", "Jack", "Hello"]; this.age = age; } function Desk(age) { Box.call(this, age); //对象冒充,给超类型传参 } 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']
例子:
function SuperType() {
this.colors = ["red", "blue", "green"];
}
function SubType() {
//继承了SuperType
SuperType.call(this);
}
var instance1 = new SubType();
instance1.colors.push("black");
alert(instance1.colors); //"red,blue,green,black"
var instance2 = new SubType();
alert(instance2.colors); //"red,blue,green"
function SuperType(name) {
this.name = name;
}
function SubType() {
//继承了SuperType,同时还传递了参数
SuperType.call(this, "Nicholas"); //实例属性
this.age = 29;
}
var instance = new SubType();
alert(instance.name); //"Nicholas";
alert(instance.age); //29
原理:在子类型构造函数的内部调用超类型构造函数
优点:解决了superType中的私有属性变公有的问题,可以传递参数
缺点:方法在函数中定义,无法得到复用
三、组合继承
组合继承(原型链继承+构造函数继承):借用构造函数虽然解决了刚才两种问题, 但没有原型, 复用则无从谈起。 所以,我们需要原型链+借用构造函数的模式,这种模式成为组合继承。
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.run());
例子:
function SuperType(name) {
this.name = name;
this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function () {
alert(this.name);
};
function SubType(name, age) {
SuperType.call(this, name); //借用构造函数继承属性,二次调用
this.age = age;
}
SubType.prototype = new SuperType(); //借用原型链继承方法,一次调用
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function () {
alert(this.age);
};
var instance1 = new SubType("Nicholas", 29);
instance1.colors.push("black");
alert(instance1.colors); //"red,blue,green,black"
instance1.sayName(); //"Nicholas";
instance1.sayAge(); //29
var instance2 = new SubType("Greg", 27);
alert(instance2.colors); //"red,blue,green"
instance2.sayName(); //"Greg";
instance2.sayAge(); //27
优点:继承前两者的优点,能通过 instanceOf 和 isPrototypeOf 的检测
缺点:两次调用父构造器函数,浪费内存。
四、原型式继承
这种继承借助原型并基于已有的对象创建新对象,同时还不必因此创建自定义类型。
function obj(o) {
//传递一个字面量函数
function F() {} //创建一个构造函数
F.prototype = o; //把字面量函数赋值给构造函数的原型
return new F(); //最终返回出实例化的构造函数
}
var box = {
//字面量对象
name: "Lee",
arr: ["哥哥", "妹妹", "姐姐"],
};
var box1 = obj(box); //传递
alert(box1.name);
box1.name = "Jack";
alert(box1.name);
alert(box1.arr);
box1.arr.push("父母");
alert(box1.arr);
var box2 = obj(box); //传递
alert(box2.name);
alert(box2.arr); //引用类型共享了
例子:
function object(o){
function F(){}
F.prototype=0;
retrurn new F();
}
使用场合:没必要构建构造函数,仅仅是想模拟一个对象的时候
五、寄生式继承
创建一个仅仅用于封装继承过程的函数,然后在内部以某种方式增强对象,最后返回对象。
function child(obj) {
function F() {}
F.prototype = obj;
return new F();
}
function createSubObj(superInstance) {
var clone = child(superInstance);
return clone;
}
优点:原型式继承的一种拓展。
缺点:依旧没有类的概念。
例子:
function createAnother(original) {
var clone = object(original); //通过调用函数创建一个新对象
clone.sayHi = function () {
//以某种方式来增强这个对象
alert("hi");
};
return clone; //返回这个对象
}
var person = {
name: "Nicholas",
friends: ["Shelby", "Court", "Van"],
};
var anotherPerson = createAnother(person);
anotherPerson.sayHi(); //"hi"
缺点:方法在函数中定义,无法得到复用
六、寄生组合式继承(最理想)
将父类原型对象直接赋值给子类的构造函数,再将空属性的构造函数实例赋值给子类原型对象
function Parent(name){
this.name = name
}
Parent.prototype.getName = function(){
return this.name
}
// 创建子类、添加子类属性。
function Child(name){
Parent.call(this, name)// 执行父构造,将This指向本身,拉取父私有属性;
}
// 子类的原型对象指向父类的原型对象
Child.prototype = Parent.prototype
// 将constructor指向本身,保证原型链不断。
Child.prototype.constructor = Child
优点:完美实现继承,解决了组合式继承带两份属性的问题; new Child的时候不用每次都 new Parent
缺点:子类的prototype添加方法会影响 父类的prototype;
例子1:
结合寄生式继承和组合式继承,完美实现不带两份超类属性的继承方式
function inheritPrototype(Super,Sub){
var superProtoClone = Object.Create(Super.prototype)
superProtoClone.constructor = Sub
Sub.prototype = Super
}
function Sub(){
Super.call()
Sub.property = 'Sub Property'
}
inheritPrototype(Super,Sub)
例子2:
寄生组合式继承解决了两次调用的问题,组合式继承就会有两次调用的情况。
function object(o) {
function F() {}
F.prototype = o;
return new F();
}
function inheritPrototype(subType, superType) {
var prototype = object(superType.prototype); //创建对象
prototype.constructor = subType; //增强对象
subType.prototype = prototype; //指定对象
}
优点:完美实现继承,解决了组合式继承带两份属性的问题。
缺点:过于繁琐,故不如组合继承。
◆ 寄生组合式继承(改进)
将父类原型对象直接赋值给子类的构造函数,再将空属性的构造函数实例赋值给子类原型对象
function Parent(name){
this.name = name
}
Parent.prototype.getName = function(){
return this.name
}
// 创建子类、添加子类属性。
function Child(name){
Parent.call(this, name)// 执行父构造,将This指向本身,拉取父私有属性;
}
// 子类的原型对象指向父类的原型对象
// 浅拷贝 解决问题
Child.prototype = Object.create(Parent.prototype)
// 将constructor指向本身,保证原型链不断。
Child.prototype.constructor = Child
优点:完美实现继承,解决了组合式继承带两份属性的问题; new Child的时候不用每次都 new Parent
例子:
function inheritPrototype(subType, superType) {
var prototype = object(superType.prototype); //创建对象
prototype.constructor = subType; //增强对象
subType.prototype = prototype; //指定对象
}
function SuperType(name) {
this.name = name;
this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function () {
alert(this.name);
};
function SubType(name, age) {
SuperType.call(this, name);
this.age = age;
}
inheritPrototype(SubType, SuperType); //实现继承
SubType.prototype.sayAge = function () {
alert(this.age);
};
七、克隆原型链继承
将等待继承的原型对象克隆,再赋值给继承的原型对象
// 创建子类、添加子类属性。
function arrange(name) {
this.name = name;
this.goShop = function (food) {
console.log(name + "叫你去买" + food);
};
}
// 创建克隆类型
function Clone(obj) {
for (var key in obj) {
this[key] = typeof obj[key] == "object" ? new Clone(obj[key]) : obj[key];
}
}
// 使用Clone构造函数继承原型
arrange.prototype = new Clone(Person.prototype);
// 将constructor指向本身,保证原型链不断。
arrange.prototype.constructor = arrange;
//创建arrange新实例,也是Clone实例,却不在是Person实例;
var newObj = new arrange("李四");
console.log(newObj instanceof Person); //false
console.log(newObj instanceof arrange); //true
console.log(newObj instanceof Clone); //true
// 克隆成功,可以访问克隆对象的原型对象;
console.log(newObj);
console.log(newObj.name); // 李四
newObj.eat("苹果"); // 李四正在吃:苹果
newObj.goShop("香蕉"); // 李四叫你去买香蕉
优点:直接通过对象生成一个继承该对象的对象。
缺点:不是类式继承,而是原型式基础,缺少了类的概念。
参考资料
JS原型链及继承的几种方式 - 编程之家 | js原型继承的几种方式 - 全网最菜的鸟 - 博客园
原型的继承,几种常用的继承方法-优快云博客 | 常见原型继承的四种方式-优快云博客