JS原型继承的六种方式

89 阅读3分钟

方式一:原型链的直接继承

重写原型对象,代之以一个新类型的实例

Idol.prototype.lastName = 'cai'
function Idol () {
    this.name = 'kunkun'
    this.hobby = ['rap', 'dance', 'basketball']
}
var idol = new Idol()
Fan.prototype = idol
function Fan () {}
var fan1 = new Fan()
var fan2 = new Fan()
console.log(fan1) // {},原型上的属性是看不到的
console.log(fan1.hobby)  // ['rap', 'dance', 'basketball'] 
console.log(fan1.name)   // 'kunkun' 我只想继承哥哥的爱好。 但是哥哥的名字也继承过来了。继承了过多的东西了。
fan2.hobby.push('sing')
console.log(fan1.hobby)  // [ 'rap', 'dance', 'basketball', 'sing' ] fan2的爱好也被添加到fan1了,修改引用中的值,会相互影响

直接修改原型链的弊端:

1、原型链过多继承了没用的属性,某些属性(继承的属性)是看不到的;

2、修改原型链中引用类型的值,会相互影响;

方式二:借用构造函数

使用父类的构造函数来增强子类实例,等同于复制父类的实例给子类

function Idol () {
    this.name = 'kunkun'
    this.hobby = ['rap', 'dance', 'basketball']
}
function Fan () {
    Idol.call(this)
    this.age = 18
}
var fan = new Fan()
console.log(fan.hobby)  // ['rap', 'dance', 'basketball'] 
console.log(fan.name)   // 'kunkun' 我只想继承哥哥的爱好。 但是哥哥的名字也继承过来了。继承了过多的东西了。

借用构造函数的弊端:

1、不能继承借用构造函数的原型;

2、每次构造都要多走一个函数,走Fan 还要走Idol

3、call 改变 this 指向,借用别人的函数,实现自己的功能。只能在你的需求完全涵盖别人的时候才能使用,如果不想要name这个属性,就不能使用这种方法(同样是继承了过多的东西)

方式三:组合式继承

用原型链实现对原型属性和方法的继承,用借用构造函数技术来实现实例属性的继承。

Idol.prototype.lastName = 'Xu'
function Idol () {
    this.name = 'kunkun'
    this.hobby = ['rap', 'dance', 'basketball']
}
function Fan () {
    Idol.call(this)
    this.age = 18
}
Fan..prototype = Idol.prototype
var fan = new Fan()
console.log(fan.lastName) // 'Xu'

方式四:原型式继承

利用一个空对象作为中介,并指定它的原型,这样可以通过原型链继承他的属性和方法。

// 方法一:
var obj = {
    name: 'cxk',
    age: 18,
    friends: ['wyf']
}

// 原型式继承函数
function createObj1(o) {
    var newObj = {}
    // 为某个对象设置原型
    Object.setPrototypeOf(newObj, o)
    return newObj
}

var info = createObj1(obj)
console.log(info.name) // 'cxk'

// 方法二:
function createObj2(o) {
     function Fn() {}
     Fn.prototype = o
     return new Fn()
}
var info = createObj2(obj)
console.log(info.__proto__) // { name: 'cxk', age: 18, friends: [ 'wyf' ] }
console.log(info.name) // 'cxk'

// 方法三:
新创建一个对象,并将某个对象作为新创建的对象的原型
var info = Object.create(obj)
console.log(info)
console.log(info.__proto__) // { name: 'cxk', age: 18, friends: [ 'wyf' ] }
console.log(info.name) // 'cxk'

弊端:

原型链继承多个实例的引用类型属性指向相同,存在篡改的可能。

无法传递参数

方式五:寄生式继承

寄生式继承的思路是结合原型式继承和工厂模式的一种方式; 即创建一个封装继承过程的函数, 该函数在内部以某种方式来增强对象,最后再将这个对象返回。

function objectCopy(obj) {
    function Fun() { };
    Fun.prototype = obj;
    return new Fun();
}

var idolObj = {
    dancing: function() {
        console.log('dancing')
    }
}
// 工厂函数
function createFan(name) {
    var fan = objectCopy(idolObj)
    // 以下为增强对象属性
    fan.name = name
    fan.studying = function() {
        console.log('singing')
    }
    return fan
}
var fanObj1 = createFan('wyf')
var fanObj2 = createFan('cxk')
console.log(fanObj1) // { name: 'wyf', studying: [Function (anonymous)] }
console.log(fanObj2) // { name: 'cxk', studying: [Function (anonymous)] }

方式六:寄生组合式继承

结合借用构造函数传递参数和寄生模式实现继承,寄生组合式继承是在原型式继承的基础上有改变。

function inherit(Target, Origin) {
    function F() {}
    F.prototype = Origin.prototype
    Target.prototype = new F()
    // 原型上的constructor是不可遍历的
    // Target.prototype.constructor = Target;
    Object.defineProperty(Target.prototype, "constructor", {
        enumerable: false,
        configurable: true,
        writable: true,
        value: Target
    })
}
Idol.prototype.lastName = "cai"
function Idol() {

}
function Fan() {

}
inherit(Fan, Idol);
var fan = new Fan();
var idol = new Idol();
Fan.prototype.sex = "male";
console.log(fan.lastName); // 'cai'
console.log(fan.sex); // 'male'
console.log(idol.sex);  // undefined, 既修改了自己的原型也不会影响到Idol的原型