js_继承&new

在ES6之前,js实现继承有六种方式:

一、原型链继承

原型链继承是通过将父构造函数实例作为子构造函数的原型对象,从而通过原型链查找的方式继承父构造函数实例上的属性和方法以及父构造函数原型对象上的属性和方法(原型链查找是根据隐式原型找的)。
在这里插入图片描述

function Father(){
    this.name='fatherName';
    this.age=50;
}
Father.prototype.sayName=function(){
    console.log(this.name);
}
function Son(){}
Son.prototype=new Father();//将子构造函数的原型对象修改为父构造函数的实例
Son.prototype.constructer=Son;//改变Son原型对象的constructer,使其指向自己的构造函数
//!!!!!!!!!实例和原型对象身上都有constructer属性,都指向构造函数!!!!!!!!!

const s=new Son();
s.sayName();

为什么不用Son.prototype=Father.prototype?因为这是浅拷贝,这样一来Son的原型对象和Father的原型对象是同一个地址,往Son的原型对象上添加方法也会同时添加到Father的原型对象上。
缺点
1,多个子实例之间共享从父类中继承的引用类型,比如数组,使得多个子实例都可以修改这个引用类型。2,新实例无法向父类构造函数传参
如:

function Parent() {
  this.newArr = ["heyushuo", "kebi"];
}

function Child() {
  this.name = "kebi";
}
Child.prototype = new Parent();
var Person1 = new Child();
Person1.newArr.push("kuli");
console.log(Person1.newArr); // ["heyushuo","kebi","kuli"]
var Person2 = new Child();
console.log(Person2.newArr); // ["heyushuo","kebi","kuli"]

二、使用构造函数继承

每次创建子实例时,都会调用父类构造函数,并通过调用call把父类属性和方法添加到子类this上,创建的每个子实例都会将父构造函数中的属性拷贝一份。

function Father(name){
    this.name=name;
    this.money=['house','car'];
}
function Son(name){
    Father.call(this,name);//调用父类构造函数
}
const s1=new Son('liming');//每次创建Son的实例时都会调用Father构造函数,所以Son的每个实例都会将Father中的属性复制一份
s1.money.push('payment');
console.log(s1.money);//['house','car','payment']
const s2=new Son();
s2.money.push('rent');
console.log(s2.money);//['house','car','rent']
//实例中的数据是父类数据的副本,修改不会影响到其他实例

优点:1,避免了引用类型的属性被所有实例共享,2,子类构造函数可以向父类构造函数传参。
缺点
1,只能继承父类实例上的属性和方法,继承不到父类原型对象上的方法/属性
2,必须在构造函数中定义方法,无法实现复用,每个子类都有父类实例函数的副本,影响性能

三、组合继承

用原型链继承来继承父构造函数实例以及其原型对象上的属性和方法,用构造函数继承来继承父构造函数实例的属性。

function Father(name){
    this.name=name;
}
Father.prototype.sayName=function(){
    console.log(this.name);
}
function Son(name){
    Father.call(this,name);//调用一次Father构造函数!!!
}
Son.prototype=new Father();//又调用了一次Father构造函数!!!
Son.prototype.constructer=Son;

const s=new Son('sonname');
console.log(s.name);//sonname
s.sayName();//sonname

缺点:1,效率问题:调用了两次父类构造函数(耗内存);2,会造成属性的重复继承,创建的实例和原型上存在两份相同的属性。
组合继承导致属性的重复继承是因为原型链继承继承了一次(继承在子实例的杠杠proto上)然后构造函数继承也继承了一次(继承在子实例自身,这两个属性会屏蔽掉原型上同名的属性)。
在这里插入图片描述

四、原型式继承

即ES5中Object.create()方法。本质上是对传入的对象进行了一次浅复制。这个传入的对象相当于父构造函数实例。
和原型链继承类似,不过是通过封装一个函数容器,容器接收形参为父构造函数实例,在里面定义一个临时构造函数(相当于子构造函数),然后让这个临时构造函数的prototype指向父构造函数的实例(即传入的形参),然后返回这个临时类型的实例(即子构造函数实例)。
你有一个对象(传入父构造函数实例),想在它基础上再创建一个新对象(返回子构造函数实例。)适用于不需要单独创建构造函数仍需要对象间共享信息的场合。
在这里插入图片描述
手写Object.create():

function Father(name){
    this.name=name;
}
function content(obj){
    function Son(){};
    Son.prototype=obj;
    return new Son();
}
const s1=content(new Father('fathername'));
console.log(s1.name);

缺点:1,多个子实例之间共享继承的对象的引用,使得多个子实例都可以修改继承的对象(所以是浅拷贝)。2,新实例无法向父类构造函数传参

五、寄生式继承

寄生式继承是在原型式继承的基础上(即先拷贝对象),在返回的新对象基础上添加方法,解决了浅拷贝。

function Father(name){
    this.name=name;
}
function content(obj){
    function Son(){};
    Son.prototype=obj;
    return new Son();
}
function enhance(obj) {
  var clone = content(obj);
  clone.sayName = function() {
    console.log('Hi')
  }
  return clone;
}
const s2=enhance(new Father('fathername'));
s2.sayName();//新返回的enhance对象不仅具有Father的所有属性和方法,也具有一个新方法sayName();

寄生式继承同样适合主要关注对象,而不在乎类型和构造函数的场景。
缺点:通过寄生式继承给对象添加函数会导致函数难以重用,这点与构造函数继承类似。

六、寄生组合式继承

解决了组合继承中父类构造函数调用两次的问题。基本思路:不通过调用父类构造函数来给子类构造函数原型赋值(即废弃原型链继承),而是取得父类原型的一个副本 ,说到底是使用寄生式继承来继承父类原型对象,然后将返回的新对象赋值给子类原型(解决了构造函数基础继承不到父类原型对象上的属性方法的问题)。然后保留构造函数继承。

function inheritPrototype(Son,Father){//寄生式继承
    const prototype=content(Father.prototype);//创建对象。创建父类构造函数原型的一个副本。
    prototype.constructer=Son;//增强对象。给返回的prototype对象设置constructer属性,解决由于重写原型导致默认constructer丢失的问题。
    Son.prototype=prototype;//赋值对象。将新创建的原型赋值给子构造函数的原型。
}
function Father(name){
    this.name=name;
}
Father.prototype.sayName=function(){
    console.log(this.name);
}
function Son(name){
    Father.call(this,name);//构造函数继承
}
inheritPrototype(Son,Father);

const s=new Son('liming');
console.log(s.name);

几乎没有缺点,只调用了一次超类 Father的构造函数,避免了在 Son.prototype 上面创建不必要的多余的属性,是引用类型最理想的继承模式。

ES6 extends继承

extends主要用于类的继承,内部实现原理和六、寄生组合式继承一样。

class Father{
    constructer(x,y){
        this.x=x;
        this.y=y;
    }
    toString(){
        return '('+this.x+','+this.y+')';
    }
}
class Son extends Father{
}
const s=new Son();
console.log(s.x);//undefined

总结

函数声明和类声明的区别:函数声明会提升,类声明不会
ES5继承和ES6继承的区别
ES5的继承实质上是先创建子类的实例对象this再把父类的方法和属性添加到this(Father.call(this))(见下面new的底层原理)
ES6的继承实质上是先创建父类的实例对象this(故先调用super()方法),再用子类的构造函数修改this,因为子类没有自己的this对象,只能继承自父类的 this 对象,所以必须先调用父类的super()方法,super( )就是将父类中的this对象继承给子类的,没有 super,子类就得不到 this 对象,新建实例就会报错。

new

在js中,new用于创建一个构造函数的实例对象或用户定义的对象类型的实例。
new的底层机制,new一个对象时发生了什么

  1. 创建一个空对象并将其作为this;
  2. 继承原型上的方法:将空对象的原型指向构造函数的原型对象
  3. 添加父类属性到新的对象上并初始化并保存执行结果(运行该构造函数)
  4. 如果执行结果有返回值并且是一个引用类型, 就返回引用类型, 如果是值类型, 返回新创建的对象。

手写new:

function mynew(Func,...args){
    //const obj={};//创建一个新对象,其实就是实例对象
    //obj.__proto__=Func.prototype;//实例对象原型指向构造函数原型对象
    const obj=Object.create(Func.prototype);//基于Func的原型创建一个新的对象
    const res=Func.call(obj,...args);//添加属性到新创建的newObj上, 并获取Func函数执行的结果.
    return res instanceof Object ? res:obj;//如果执行结果有返回值并且是一个对象, 返回执行的结果, 如果是值类型, 返回刚才新创建的对象obj
}

测试一下:

const f1 = new Func('Tianbao');
console.log(f1);
const f2 = mynew(Func,'Tianbao');
console.log(f2);//f1和f2输出结果一样

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值