js的继承方法

Posted 小顺石

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了js的继承方法相关的知识,希望对你有一定的参考价值。

1、原型链的继承

// 原型链继承

/**
 * 核心:将父类的实例作为子类的原型
 * 优点:方法复用
 *     由于方法定义在父类的原型上,复用了父类的构造函数方法,比如say方法
 * 缺点:
 *     创建子类实例的时候,不能向父类穿参数
 *     子类实例共享了父类的构造函数的引用属性,比如arr属性
 *     无法实现多继承
 *     
 * 
 * */ 

 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
console.log( Child.prototype.constructor === Parent) //  true
// 一个完整的原型对象必须有constructor
Child.prototype.constructor = Child // 修正constructor 指向
let boy1 = new Child()
let boy2 = new Child()
// 优点:共享了父类构造函数的say方法
console.log(boy1.say()) // hello
console.log(boy2.say())  // hello
console.log(boy1.say() === boy2.say())   // true

// 缺点1   不能传参
console.log(boy1.name) // 父类
console.log(boy2.name) //父类
console.log(boy1.name === boy2.name)  // true

// 缺点2   子类实例共享了父类的引用属性 比如arr
boy1.arr.push(2)
// 修改了boy1的arr ,影响了boy2的arr
console.log(boy2.arr)  // [1,2]

2、借用构造函数继承

// 借用构造函数继承

/**
 * 核心:借用父类的构造函数,增强子类的实例,等于复制父类的实例属性给子类
 * 优点
 *    1,可以传参
 *    2,子类实例不共享父类的引用属性
 *    3,可以是多继承  通过call继承多个父类
 * 
 * 
 * 缺点
 *    1,父类的方法不能复用
 *      由于父类的方法在构造函数中,导致方法不能复用,每一次创建实例,都会生成一个方法
 *    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) //小红
console.log(boy2.name)// 小明
// 优点2,不共享父类的引用属性

boy1.arr.push(2)
console.log(boy1.arr,boy2.arr) // [ 1, 2 ] [ 1 ]

// 缺点1  不能共享父类的方法
console.log(boy1.say === boy2.say) // false  说明方法不同

// 缺点2  不继承父类原型上的方法
Parent.prototype.walk = function(){
    console.log(\'walk\')
}
boy1.walk; // undefined

3.组合继承

/**
 * 核心:通过调用父类构造函数,继承父类的属性并保留传参的优点;
 *           然后通过将父类实例作为子类原型,实现函数复用。
 * 优点:
 *     1.保留构造函数的优点:创建子类实例,可以向父类构造函数传参数。
 *     2.保留原型链的优点:父类的实例方法定义在父类的原型对象上,可以实现方法复用。
 *     3.不共享父类的引用属性。比如arr属性
 * 缺点:
 *     由于调用了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 = new Parent() // 核心   第一次

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属性。

console.log(boy1.constructor); // Parent 你会发现实例的构造函数是Parent。
Child.prototype.constructor = Child;

4、组合继承的优化

// 核心:
//     通过这种方式,砍掉父类的实例属性,这样在调用父类的构造函数的时候,就不会初始化两次实例,避免组合继承的缺点。
// 优点:
//      1.只调用一次父类构造函数。
//      2.保留构造函数的优点:创建子类实例,可以向父类构造函数传参数。
//      3.保留原型链的优点:父类的实例方法定义在父类的原型对象上,可以实现方法复用。


// 缺点:
//      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 // 核心  子类原型和父类原型,实质上是同一个


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

// 缺点1:当修复子类构造函数的指向后,父类实例的构造函数指向也会跟着变了。
// 具体原因:因为是通过原型来实现继承的,Child.prototype的上面是没有constructor属性的,就会往上找,这样就找到了Parent.prototype上面的constructor属性;当你修改了子类实例的construtor属性,所有的constructor的指向都会发生变化。

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

5.寄生组合继承 --- 完美方式

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) // 核心  通过创建中间对象,子类原型和父类原型,就会隔离开。不是同一个啦,有效避免了方式4的缺点。

Child.prototype.constructor = Child

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


// 注意:这种方法也要修复构造函数的
// 修复代码:
Child.prototype.constructor = Child
// 修复之后:
console.log(boy1.constructor); // Child
console.log(p1.constructor);// Parent  完美

以上是关于js的继承方法的主要内容,如果未能解决你的问题,请参考以下文章

几个关于js数组方法reduce的经典片段

AJAX相关JS代码片段和部分浏览器模型

js实现继承

JS如何实现继承?

VSCode自定义代码片段——JS中的面向对象编程

谷歌浏览器调试jsp 引入代码片段,如何调试代码片段中的js