js几种常见的继承方式

ES6博采众长终于原生实现了类(class)这个概念,当然他的继承也变得相对来说简单了,也是extends关键字即可如:

class Person{
    constructor(name,gender){
        this.name = name;
        this.gender = gender;
    }
    changeName = (name)=>{
        this.name=name;
    }
    changeGender = (gender)=>{
        this.gender = gender;
    }    
}

class Student extends Person{
    constructor(name,gender,age){
        super(name,gender);
        this.age = age;
    }
    changeAge = (age)=>{
        this.age = age;
    }
}

在ES5的时代我们没有原生实现的类,于是我们只能模拟类和继承来实现相同效果。并且有助于我们理解js原理。

类的实现:

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

Person.prototype.changeName=function(name){
    this.name = name;
    console.log(this.name);
}

Person.prototype.changeGender=function(gender){
    this.gender = gender;
    console.log(this.gender);
}

ES5实现类的继承,这里介绍主要的三种继承方式:寄生组合继承(原型继承)、浅拷贝继承、深拷贝继承。

首先声明一个Student类的构造函数再函数中通过new方法绑定this,实现构造函数的继承,但是并没有继承Person原型链上的方法。

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

如果直接使用 Student.prototype = Person.prototype会导致直接赋值,是传址,一个改变会导致另一个改变。

如果使用Student.prototype = new Person()会将构造函数中的属性(如this.name,this.gender)也一并拷贝到原型链上。

1.组合继承

通过调用父类构造,继承父类的属性并保留传参的优点,然后通过将父类实例作为子类原型,实现函数复用

Student.prototype = new Person();
Student.prototype.constructor = Student;

2.寄生组合继承(推荐)

①原理重新声明一个空函数,没有任何的属性,用作连接原型对象使用

②使用Object.create达到①相同效果

function extend(faClass){
    function Proxy(){};
    proxy.prototype = faClass.prototype;
    return new Proxy(); 
}

Student.prototype = extend(Person);

//构造函数
Student.prototype.constructor = Student;

let s = new Student('a','nv',18);
console.log(s);
s.changeName('b')

使用Object.create()方法:

//我们创建一个新的Student.prototype对象,并关联到Student.prototype上。

Student.prototype = Object.create(Person.prototype);
//构造函数
Student.prototype.constructor = Student;
let s = new Student('a','nv',18);
console.log(s);
s.changeName('c')

上面的继承中没有了Student.prototype.constructor属性,所以需要我们手动修复一下他:Student.prototype.constructor = Student。

上述代码的核心部分就是语句:Student.prototype = Object.create(Person.prototype)。调用Object.create(...)会凭空创建一个“新”的对象并把新对象内部的[[Prototype]]关联到你指定的对象(本例中是Student.prototype)。

换句话说,这条语句的意思是:“创建一个新的Student.prototype对象并把它关联到Person.prototype”。

声明function Student(){...}时,和其他函数一样,Student会有一个.prototye关联到默认的对象,但是这个对象并不是我们想要的Person.prototype。因此我们创建了一个新对象并把它关联到我们希望的对象上,直接把原始的关联对象抛弃掉。

注意,下面这两种方式时常见的错误做法,实际上他们都存在一些问题:

//和你想要的机制不一样
Student.prototype = Person.prototype;


//基本上满足你的需求,但是可能会产生一些副作用
Student.prototype = new Person();

Student.prototype = Person.prototype并不会创建一个关联到Student.prototype的新对象,他只是让Student.prototype直接引用Person.prototype对象。因此当你执行类似Student.prototype.changeName=...的赋值语句时会直接修改Student.prototype对象本身。显然这不是你想要的结果,否则你根本不需要Student对象,直接使用Person就可以了,这样代码也会更简单一些。

Student.prototype = new Person()的确会创建一个关联到Student.prototype的新对象。但是它使用了Person(...)的“构造函数调用”,如果函数Person有一些副作用(比如写日志、修改状态、注册到其他对象、给this添加数据属性,等等)的话,就会影响到Student()的“后代”,后果不堪设想。

因此要创建一个合适的关联对象,我们必须使用Object.create(...)而不是使用狙有副作用的Person(...)。这样做的唯一缺点就是需要创建一个新对象然后把旧对象抛弃掉,不能直接修改已有的默认对象。

如果能有一个标准并且可靠的方法来修改对象的[[prototype]]关联就好了。再ES6之前,我们只能通过设置__proto__属性来实现,但是这个方法并不是标准并且无法兼容所有浏览器。ES6添加了辅助函数Object.setPrototypeOf(..),可以用标准并且可靠的方法来修改关联。

我们来对比一下两种把Student.prototype关联到Person.prototype的方法:

//再ES6之前需要抛弃默认的Student.prototype
Student.prototype = Object.create(Person.prototype);

//ES6开始可以直接修改现有的Student.prototype
Object.setPrototypeOf(Student.prototype,Person.prototype);

如果忽略掉Object.create(..)方法带来的轻微性能损失(抛弃的对象需要进行垃圾回收机制回收),它实际上比ES6及其之后的方法更短而且可读性更高。不过无论如何,这是两种完全不同的语法。

3.浅拷贝方式

即将需要继承的类(函数)上的原型上的方法拷贝到当前类上的原型即可,当然浅拷贝对于嵌套的对象就是传址了,对于简单的继承可以使用。将父类的属性和方法拷贝一份到子类中

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

for(let key in Person.prototype){
    Student.prototype[key] = Person.prototype[key];
}

let s = new Student('a','nv',18);
console.log(s);
s.changeName('c')

 

4.深拷贝方式

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

Person.prototype.changeName=function(name){
    this.name = name;
    console.log(this.name);
}

Person.prototype.changeGender=function(gender){
    this.gender = gender;
    console.log(this.gender);
}
Person.prototype.children=[
    {
        name:'a'
    },{
        name:'b'
    }
];

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

function deepCody(resource){
    let target = Array.isArray(resource) ? [] : {};
    for(let key in resource){
        if(resource.hasOwnProperty(key)){
            if(typeof resource[key] === 'object'){
                target[key] = deepCody(resource[key])
            }else{
                target[key] = resource[key];
            }
        }
    }
    return target;
}
Student.prototype = deepCody(Person.prototype);
//这里改变了Student原型上的嵌套对象的值,对比发现没有改变Person相应的值,实现了拷贝
Student.prototype.children[0].name = 'c';
console.log(new Person);
let s = new Student('a','nv',18);
console.log(s);
s.changeName('c')

5.实例继承

为父类实例添加新特性,作为子类实例返回

function Student(){
    let instance = new Person('a','b');
    instance.age = 18;
    return instance;
}

6.构造继承

实质是利用 call 来改变 Cat 中的 this 指向,可以实现多继承,不能继承原型属性/方法。一般和组合继承和寄生组合继承结合使用。

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

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值