JavaScipt中三种常用的继承方法

更新时间:2019-11-18 09:27:36点击次数:251次
JS中的继承:一个对象可以使用另一个对象的成员(属性和方法)

目的:方便代码的复用

继承的三种主要方式

原型链继承
借用构造函数继承
组合继承
原型链继承
原型链继承的方法有两种:
1.往原型对象身上逐一添加属性和方法

//创建一个构造函数Person,并且身上并没有任何属性和方法
function Person() {}

//给构造函数的原型上添加了一些属性和方法
Person.prototype.color = "lime";
Person.prototype.sayHi = function () {
    console.log("hello");
};
Person.prototype.work = function () {
    console.log("work");
};
Person.prototype.running = function () {
    console.log("running");
};
Person.prototype.eat = function () {
    console.log("eat");
};

//通过new创建实例对象p
var p = new Person();
//此时实例对象p调用sayHi方法
p.sayHi();
//输出值为hello,p继承了原型对象身上的方法

在上述代码中我们可以看到实例对象p本身并没有任何属性方法,却可以调用sayHi方法,这就是所谓的原型链继承。

优势:少量添加成员比较方便
弊端:添加的成员太多会导致代码重复问题

2.原型替换
把原型替换成新的对象,给新的对象添加成员。

//创建一个构造函数Person,并且身上并没有任何属性和方法
function Person() {}

//此时把构造函数的原型替换成了一个新的对象,并且在对象上添加了属性和方法
Person.prototype = {
//constructor属性丢失,需要重新添加这一属性,让原型对象指向构造函数
    constructor: Person,
    color: "lime",
    sayHi: function () {
        console.log("hello");
    },
    work: function () {
        console.log("work");
    },
    // ...
}


var p = new Person();
    // 实例对象p继承自 Person的原型。
    // console.log(p);
p.sayHi();
//输出值为hello,实例对象p继承了新的原型对象上的属性和方法

优势:大量添加成员的时候,可以节约代码
弊端:替换新的原型对象会导致constructor属性丢失,需要在新的对象中添加这一属性,让新原型对象重新指向构造函数

借用构造函数继承
//创建一个Person构造函数,函数身上有name,age,gender三个属性
function Person(name, age, gender){
    this.name = name;
    this.age = age;
    this.gender = gender;
}

//创建一个Chinese构造函数,函数身上有skin这一属性
function Chinese(name, age, gender, skin){
    this.skin = skin;
}

var xm = new Chinese("xm", 20, "male", "黄");
console.log(xm);
//输出结果Chinese {skin: '黄'}
//此时想让实例对象xm能够拥有name,age,gender三个属性

此时可以使用上下文调用模式(借用方法模式)中的call和apply方法
戳这里详细了解call,apply,bind

function Person(name, age, gender){
    this.name = name;
    this.age = age;
    this.gender = gender;
}

function Chinese(name, age, gender, skin){
    //  让xm借用了构造函数Person,从而继承到了Person添加的属性。
    // call实现借用
    // 1. call把Person函数给调用了
    // 2. call的第一个参数this指向了 xm
    // 3. call的第一个参数可以用来修改函数内的this指向,Person函数内的this指向了xm
    // 4. call的其他参数用来给函数传递实参
    Person.call(this, name, age, gender);
    
    // apply实现借用
    //Person.apply(this, [name, age, gender]);
    
    this.skin = skin;
}
var xm = new Chinese("xm", 20, "male", "黄");
console.log(xm);
//输出结果:Chinese {name: "xm", age: 20, gender: "male", skin: "黄"}

组合继承
原型链继承+借用构造函数继承

//创建一个Person构造函数,函数身上有name,age,gender三个属性
function Person(name, age, gender){
    this.name = name;
    this.age = age;
    this.gender = gender;
}

//此时给Person的原型添加了一个sayHi方法
Person.prototype.sayHi = function () {
    console.log("hello, 我是 " + this.name);
}

//创建一个Chinese构造函数,函数身上有skin这一属性
function Chinese(name, age, gender, skin){
    this.skin = skin;
}

var xm = new Chinese("xm", 20, "male", "黄");
xm.sayHi();
//输出结果:报错

上述代码中我们可以看到,实例对象xm身上并没有这个方法,原型链上也没有这个方法,所以会导致代码报错。

此时我们可以使用原型替换代码如下:

//创建一个Person构造函数,函数身上有name,age,gender三个属性
function Person(name, age, gender){
    this.name = name;
    this.age = age;
    this.gender = gender;
}

//此时给Person的原型添加了一个sayHi方法
Person.prototype.sayHi = function () {
    console.log("hello, 我是 " + this.name);
}

//创建一个Chinese构造函数,函数身上有skin这一属性
function Chinese(name, age, gender, skin){
// 借用构造函数继承
    //  让xm借用了构造函数Person,从而继承到了Person添加的属性。
    Person.call(this, name, age, gender);
    this.skin = skin;
}

//xm原来的原型链:xm ==> Chinese.prototype ==> Object.prototype ==> null;
//替换后xm的原型链:xm ==> Person.prototype ==> Object.prototype ==> null;
Chinese.prototype = Person.prototype;

var xm = new Chinese("xm", 20, "male", "黄");
xm.sayHi();
//输出结果:hello, 我是 xm

这种方法虽然成功让xm调用了sayHi的方法,但是有弊端。
弊端:此时给Chinese的原型对象添加一个sayHi方法,但是Chinese的原型对象变成了Person.prototype,所有会把Person.prototype的方法覆盖。

优化代码如下:

//创建一个Person构造函数,函数身上有name,age,gender三个属性
function Person(name, age, gender){
    this.name = name;
    this.age = age;
    this.gender = gender;
}

//此时给Person的原型添加了一个sayHi方法
Person.prototype.sayHi = function () {
    console.log("hello, 我是 " + this.name);
}

//创建一个Chinese构造函数,函数身上有skin这一属性
function Chinese(name, age, gender, skin){
// 借用构造函数继承
    //  让xm借用了构造函数Person,从而继承到了Person添加的属性。
    Person.call(this, name, age, gender);
    this.skin = skin;
}

//xm原来的原型链:xm ==> Chinese.prototype ==> Object.prototype ==> null;
//替换后xm的原型链:xm ==> Person的实例对象 ==> Person.prototype ==> Object.prototype ==> null;
Chinese.prototype = new Person();
//优化后的目的:
//1. xm还可以使用sayHi方法
//2. 操作Chinese.prototype,不会影响Person.prototype

//同样替换新的原型对象会导致constructor属性丢失,手动添加constructor属性
Chinese.prototype.constructor = Chinese;

var xm = new Chinese("xm", 20, "male", "黄");
xm.sayHi();
//输出结果:hello, 我是 xm

最后说下组合继承的优势
优势:借用构造函数继承主要为了继承属性,原型链继承主要继承方法,组合继承结合了两者的优势,既能继承属性也能继承方法

本站文章版权归原作者及原出处所有 。内容为作者个人观点, 并不代表本站赞同其观点和对其真实性负责,本站只提供参考并不构成任何投资及应用建议。本站是一个个人学习交流的平台,网站上部分文章为转载,并不用于任何商业目的,我们已经尽可能的对作者和来源进行了通告,但是能力有限或疏忽,造成漏登,请及时联系我们,我们将根据著作权人的要求,立即更正或者删除有关内容。本站拥有对此声明的最终解释权。

  • 项目经理 点击这里给我发消息
  • 项目经理 点击这里给我发消息
  • 项目经理 点击这里给我发消息
  • 项目经理 点击这里给我发消息