Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JavaScript 基础 - 继承 #10

Open
wwyx778 opened this issue Jul 29, 2020 · 0 comments
Open

JavaScript 基础 - 继承 #10

wwyx778 opened this issue Jul 29, 2020 · 0 comments

Comments

@wwyx778
Copy link
Owner

wwyx778 commented Jul 29, 2020

JavaScript 继承

JavaScript 语言最初只是一门脚本语言,是没有类class和继承extends的概念的。不过这些都可以依靠 JavaScript 基于 原型 这一特性来模拟实现。

ES6中引入了classextends关键字,但那只是语法糖,JavaScript 仍然是基于原型的。

本文介绍几种 JavaScript 继承的实现方式。首先我们来创建一个"父类"。

function Animal(name = 'Animal', voice){
  this.name = name;
  this.voice = voice;
  this.action = ['say'];
}
Animal.prototype.isAnimal = true;
Animal.prototype.say = function (){ 
  console.log(`The ${this.name} saying: ${this.voice}!`) 
}

1.原型继承

function Dog(name){
  this.name = name;
}
Dog.prototype = new Animal();
Dog.prototype.constructor = Dog; //修复子类构造函数

var tom = new Dog('tom');
console.log(tom.name);          // "tom"
console.log(tom.voice);         // undefined 
console.log(tom.action);        // ["say"]
tom.say();                      // The tom saying: undefined!

var bob = new Dog('bob');
bob.action.push('sleep');
console.log(bob.action);        // ["say", "sleep"]
console.log(tom.action);        // ["say", "sleep"]
  • 核心:将父类的实例作为子类的原型,子类便可以顺着原型链去到父类的属性。
  • 特点:
    • 创建对象时无法向父类的构造函数传参
    • 继承父类的所有属性是浅拷贝,引用类型会相互有副作用
    • 无法实现多继承

2.构造函数继承

function Dog(name){
  Animal.call(this, name, 'WONNG')
  this.name = name;
}

var tom = new Dog('tom');
console.log(tom.name);          // "tom"
console.log(tom.voice);         // "WONNG" 
console.log(tom.action);        // ["say"]
console.log(tom.say);           // undefined

var bob = new Dog('bob');
bob.action.push('sleep');
console.log(bob.action);        // ["say", "sleep"]
console.log(tom.action);        // ["say"]
  • 核心:在子类的构造函数中执行父类的构造函数
  • 特点:
    • 可以实现多继承
    • 无法继承父类原型上的属性
    • 子类的每个属性都是父类的深拷贝,互相没有副作用

3.组合继承

function Dog(name){
  Animal.call(this, name, 'WONNG')
  this.name = name;
}
Dog.prototype = new Animal();
Dog.prototype.constructor = Dog; //修复子类构造函数

var tom = new Dog('tom');
console.log(tom.name);          // "tom"
console.log(tom.voice);         // "WONNG" 
console.log(tom.action);        // ["say"]
tom.say();                      // The tom saying: WONNG!

var bob = new Dog('bob');
bob.action.push('sleep');
console.log(bob.action);        // ["say", "sleep"]
console.log(tom.action);        // ["say"]
  • 核心:在子类的构造函数中执行父类的构造函数
  • 特点:
    • 可以实现多继承
    • 继承了父类原型上的属性
    • 子类的每个属性都是父类的深拷贝,互相没有副作用
    • 子类在构造函数和原型上分别创建了两份父类实例,子类构造函数上的父类实例覆盖了子类原型上的父类实例

至此组合继承已经近乎完美的实现了 JavaScript 的继承,但是还是不够优雅,因为在组合继承的实现中,子类原型上的父类实例是多余的。

console.log(bob);           // {name: "bob", voice: "WONNG", action: ["say", "sleep"]}
console.log(bob.__proto__); // {name: "Animal", voice: undefined, action: ["say"], constructor: ƒ Dog(name)}

可以看到bob.__proto__上的name voice action属性都是多余的,甚至当我们删除bob的某个属性时,可能会从子类原型上得到意料之外的父类实例属性。

delete bob.name
console.log(bob.name);      // "Animal"

所以下面介绍另一个继承方式来解决这个问题。

4.寄生组合继承

function Dog(name){
  Animal.call(this, name, 'WONNG')
  this.name = name;
}
function Mid(){}                 // 没有实例属性的中间类
Mid.prototype = Animal.prototype;
Dog.prototype = new Mid();
Dog.prototype.constructor = Dog; // 修复子类构造函数

var tom = new Dog('tom');
console.log(tom.name);          // "tom"
console.log(tom.voice);         // "WONNG"
console.log(tom.action);        // ["say"]
tom.say();                      // The tom saying: WONNG!

var bob = new Dog('bob');
bob.action.push('sleep');
console.log(bob.action);        // ["say", "sleep"]
console.log(tom.action);        // ["say"]

console.log(bob);               // {name: "bob", voice: "WONNG", action: ["say", "sleep"]}
console.log(bob.__proto__);     // {constructor: ƒ Dog(name)}
  • 核心:用一个没有实例属性的中间类抽离掉原型上的父类实例属性
  • 特点:
    • 可以实现多继承
    • 继承了父类原型上的属性
    • 子类的每个属性都是父类的深拷贝,互相没有副作用
    • 原型上干净,只有来自父类的原型属性

这种方式就是现阶段很完美,也是最常用的继承方式了。代码上还可以根据各种情况优化一下,比如将 call 改成 apply,我们就可以用原生的 arguments 数组更方便的向父类的构造函数传参,
定义中间类的时候也可以用一个立即执行的匿名函数包裹一下,让中间类处在函数的局部作用域,防止全局作用域下的同名变量或者函数污染。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant