JS继承的探讨与复习

在学习js继承方式之前首先需要掌握一些基本的预备知识:

  • 构造函数的属性
function A(name) {
   this.name = name ; //实例基本属性(该属性,强调私有,不共享)
   this.arr = [1];//实例引用属性(该属性,强调私有,不共享)
   this.say = function(){ //实例引用属性(该属性,强调复用 ,需要共享)
       console.log('hello')
   }
}

//注意数组和方法都属于'实例引用属性',但是数组强调私有、不共享的。方法需要复用、共享。
//在构造函数中,一般很少有数组形式的引用属性,大部分情况都是:基本属性+方法。

  • 什么是原型对象
//简单来说,每个函数都有prototype属性,它就是原型对象,
//通过函数实例化出来的对象有个__proto__属性,指向原型对象。

let a = new A()

a.__proto__ == A.prototype



//prototype的结构如下

A.prototype = {

constructor:A,

...其他的原型属性和方法

}
  • 原型对象的作用

原型对象的用途是为每个是每个实例对象存储共享的方法和属性,他仅仅是一个普通对象而已。并且所有的实例是共享同一个原型对象,因此有别于实例方法或属性,原型对象仅有一份。而实例有很多份,且实例属性和方法是独立的。在构造函数中:为了属性(实例基本属性)的私有性、以及方法(实例引用属性)的复用、共享。我们提倡:     

  • 将属性封装在构造函数中

  • 将方法定义在原型对象上

function  A(name) {
    this.name = name ;//(该属性,强调私有,不共享)
}
A.prototype.say = function(){
    console.log('hello')
}

//不推荐的写法:【原因】 怕覆盖了原型上的constructor
A.prototype = {
    say:function(){
        console.log('hello')
    }
}

接下来就分别介绍一下物种js继承方法

 方式1、原型链继承

  • 核心:将父类实例作为子类原型
  • 优点:方法复用(由于方法定义在父类的原型上,复用了父类构造函数的方法。比如say方法)
  • 缺点(

       1.创建子类实例的时候,不能传父类的参数(比如name)

       2.子类实例共享了父类构造函数的引用属性,比如arr属性。

       3.无法实现多继承

       )

function Parent(name) {
    this.name = name || '父亲' ;//实例基本属性(该属性,强调私有,不共享)
    this.arr = [1];
}

Parent.prototype.say = function(){
    console.log('hello')
}

function Child(like) {
    this.like =like;
}

Child.prototype  = new Parent() //核心,但此时Child.prototype.constructor == Parent
Child.prototype.constructor = Child //修正constructor指向


let boy1 =new Child();
let boy2 =new Child();

//优点:共享了父类构造函数的say方法
console.log(boy1.say(),boy2.say(), boy1.say===boy2.say()); //hello,hello,true

//缺点1:不能向父类构造函数传参
console.log(boy1.name,boy2.name,boy1.name===boy2.name);//父亲、父亲、true

//缺点2:子类实例共享了父类构造函数的引用属性,比如arr属性
boy1.arr.push(2);//修改了boy1的arr属性,boy2的arr属性也会跟着变化,因为两个实例的原型上(Child.prototype)
//有了父类构造函数的实例属性arr
console.log(boy2.arr); //[1,2]

// 注意1:修改boy1的name属性,是不会影响到boy2.name。因为设置boy1.name相当于在子类实例新增了name属性

方式2、借用构造函数

  • 核心:借用父类构造函数来增强子类实例,等于是复制父类的实例属性给子类。
  • 优点:实例之间独立  

      1.创建子类实例,可以向父类构造函数传参。

      2.子类实例不共享父类构造函数的引用属性,比如arr属性

      3.可实现多继承(通过多个call或者apply继承多个父类)

  • 缺点:

      1.父类的方法不能复用 

由于方法在父构造函数中定义,导致方法不能复用(因为每次创建子类实例都要创建一边方法)。

比如say方法。(方法应该要复用、共享)

      2.子类实例,继承不了父类原型上的属性。(因为没有用到原型)

function Parent(name) {
    this.name =name ;
    this.arr= [1];
    this.say  = function(){
        console.log('hello')
    }
}

function Child(name,like) {
    Parent.call(this.name); //核心,拷贝了父类的实例属性和方法
    this.like = like;
}

let boy1  = new Child('小红','apple');
let boy2 = new Child('小明','orange');


//优点1:可以向父类构造函数传参
console.log(boy1.name,boy2.name);//小红,小明

//优点2:不共享父类构造函数的引用属性
boy1.arr.push(2);
console.log(boy1.arr,boy2.arr); //[1,2],[1]

//缺点1:方法不能复用
console.log(boy1.say ===boy2.say) //false (说明,boy1和boy2的say方法是独立,不是共享的)

//缺点2:不能继承父类原型上的方法
Parent.prototype.walk = function() {
    console.log('我会走路')
}

boy1.walk; //undefined(说明实例,不能获得父类原型上的方法)

方式3、组合继承

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

          1.保留构造函数的优点:创建子类实例,可以向父类构造函数传参。

          2.保留原型链的优点:父类的方法定义在父类的原型对象上,可以实现方法复用。

          3.不共享父类的引用属性,比如arr属性

  • 缺点:

由于调用了两次父类的构造方法,会存在一份多余的父类实例属性,这种‘组合继承的’方式,要记得修复Child.prototype.constructor指向

第一次Parent.call(this);从父类拷贝一份父类实例属性,作为子类的实例属性,第二次Child/prototype = new Parent();创建父类实例作为子类原型,Child.prototype中的父类属性和方法会被第一次拷贝来的实例属性屏蔽掉,所以多余。

function Parent(name){
    this.name= name;
    this.arr =[1];
}

Parent.prototype.say =function(){
    console.log('hello')
}

function Child(name,like){
    Parent.call(this.name,like); //核心 第二次
    this.like=like
}

Child.prototype  =new Parent(); //核心,第一次

Child.prototype.constructor = Child //修正 constructor指向

let boy1 =new Child('小红',apple);
let boy2 =new Child('小明',orange)

//优点1:可以向父类构造函数传递参数
console.log(boy1.name,boy1.like) //小红,apple
//优点2 :可以服用父类原型上的方法
console.log(boy1.say===boy2.say)  //true
//优点3:不共享父类的引用属性,如arr属性
boy1.arr.push(2)
console.log(boy1.arr,boy2.arr); //[1,2] [1]  可以看出没有共享arr属性

//缺点:由于两次调用了父类的构造函数,会存在一份多余的父类实例属性

方式4、组合继承优化1

  • 核心:
  •    优点
  •      只调用一次父类构造函数
  •      保留了构造函数的优点:创建子类实例,可以向父类构造函数传参
  •      保留了原型链的优点:父类的实例方法定义在父类的原型对象上,可以实现方法复用。
  •    缺点: 
  •       修正构造函数只想之后,父类实例的构造函数只想,同时也发生变化******,最大的弊端
function Parent(name) {
    this.name = name;
    this.arr = [1];
}

Parent.prototype.say = function() {
    console.log('hello')
}

function Child(name, like) {
    Parent.call(this.name, like); //核心 第二次
    this.like = like
}

Child.prototype = Parent.prototype; //核心,第一次

Child.prototype.constructor = Child //修正 constructor指向

let boy1 = new Child('小红', apple);
let boy2 = new Child('小明', orange)
let p1 = new Parent('小爸爸')

//优点1:可以向父类构造函数传递参数
console.log(boy1.name, boy1.like) //小红,apple
    //优点2 :可以服用父类原型上的方法
console.log(boy1.say === boy2.say) //true
    //优点3:不共享父类的引用属性,如arr属性
boy1.arr.push(2)
console.log(boy1.arr, boy2.arr); //[1,2] [1]  可以看出没有共享arr属性

//缺点:当修复子类构造函数的指向后,父类实例的构造函数只想也会跟着改变

// 没修复之前:console.log(boy1.constructor);//Parent
// 修复代码Child.prototype.constructor = Child
// 修复之后:console.log(boy1.constructor);//Child
//  console.log(p1.constructor);//Child(这就是存在的问题,我们希望是Parent)

方式5、组合继承优化2 又称 寄生组合继承---完美方式

   优点:完美

function Parent(name) {
    this.name = name;
    this.arr = [1];
}

Parent.prototype.say = function() {
    console.log('hello')
}

function Child(name, like) {
    Parent.call(this.name, like); //核心 第二次
    this.like = like
}

Child.prototype = Object.create(Parent.prototype); //核心,第一次

Child.prototype.constructor = Child //修正 constructor指向

let boy1 = new Child('小红', apple);
let boy2 = new Child('小明', orange)
let p1 = new Parent('小爸爸')

//优点1:可以向父类构造函数传递参数
console.log(boy1.name, boy1.like) //小红,apple
    //优点2 :可以服用父类原型上的方法
console.log(boy1.say === boy2.say) //true
    //优点3:不共享父类的引用属性,如arr属性
boy1.arr.push(2)
console.log(boy1.arr, boy2.arr); //[1,2] [1]  可以看出没有共享arr属性

//缺点:当修复子类构造函数的指向后,父类实例的构造函数只想也会跟着改变

// 没修复之前:console.log(boy1.constructor);//Parent
// 修复代码Child.prototype.constructor = Child
// 修复之后:console.log(boy1.constructor);//Child
//  console.log(p1.constructor);//Parent(完美)

其他相关问题:

1.Object.create(object,propertiedObject)

Object.create()方法创建一个新对象,使用第一个参数来提供新创建对象的__proto__(一第一次个参数作为新对象的构造函数的原型对象);

方法还有第二个可选参数,是添加到新创建对象的属性,写法如下:


const a = Object.create(Person.prototype,{
    age:{
        value:12,
        Writable:true,
        configurable:true,
    }
})

2.new与Object.create()的区别?

new产生的实例,有限获取构造函数上的属性;构造函数上没有对应的属性,才回去原型上找;

如果构造函数中以及原型中 都没有对应的属性,就会报错.Object.create()产生的对象,只会在原型上进行查找属性,原型上没有对应的属性,就会报错.

let Base1 = function() 
{ this.a = 1 }

let o1 = new Base1() 
let o2 = Object.create(Base1.prototype) 

console.log(o1.a); // 1 
console.log(o2.a); // undefined 

let Base2 = function() {} 
Base2.prototype.a = 'aa' 

let o3 = new Base2() 
let o4 = Object.create(Base2.prototype) 

console.log(o3.a); // aa 
console.log(o4.a); // aa 

let Base3 = function() { 
    this.a = 1 
}

Base3.prototype.a = 'aa' 

let o5 = new Base3() 
let o6 = Object.create(Base3.prototype)

console.log(o5.a); // 1 
console.log(o6.a); // aa

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值