您的位置:首页 > Web前端 > JavaScript

一篇文章图文并茂地带你轻松学完 JavaScript 继承

2021-02-04 14:04 926 查看

JavaScript 继承

在阅读本文章之前,已经默认你了解了基础的

JavaScript
语法知识,基础的
ES6
语法知识 。

继承种类

简单的继承种类可以分为

  1. 构造函数继承
  2. 原型链继承
  3. class继承
  4. 寄生继承

其中

class
继承是
ES6
后提供的一种语法糖,方便其他面向对象语言的程序员更好的接受
JavaScript
中的继承,本质上还是原型链继承。

1. 构造函数继承

function Person() {
this.name = "name";
this.eat = function() {
console.log("eat");
}
}

function Student() {
Person.call(this); // 继承
this.age = 20;
}

const student = new Student();
console.log(student);

2. 原型链继承

原型与原型链前置相关内容可以参考 点这里

function Person() {
this.name = "name";
}

function Student() {
this.age = 20;
}

Student.prototype = new Person();
Student.prototype.constructor = Student;	// 指利用 Student 构造函数 进行实例初始化

const stu = new Student();
console.log(stu.name); // "name"
console.log(stu);

利用在原型和原型链所学知识

Student
实例对象的
__proto__
将会指向
Person
实例,从而实现继承的效果

stu
:

3. class继承

constructor
是构造函数,可以结合原型链中的
constructor
属性看

class People {
constructor() {
this.name = "name";
}
}

class Student extends People {
constructor() {
super()
this.age = 20;
}
}

console.log(new Student())

可以发现,其实就是基于原型链继承,只不过

constructor
class Student

4. 寄生继承

JavaScript
设计模式中,有
工厂模式
,具体可以上网查询

工厂模式
意味着只要传入适当的参数 (加工),就会给予一个实例,就像工厂制造东西一样。

而寄生继承,用的就是工厂模式的思想

function People() {}

People.prototype.eat = function() {
console.log("eat");
}

function createInstance() {
const obj = Object.create(People.prototype)
Object.assign(obj, ...arguments);
return obj;
}

const stu1 = createInstance({ age: 20 });
console.log(stu1);
const stu2 = createInstance({ age: 30 });
console.log(stu2);

下面是

stu1
的打印结果

继承优化

1. 构造函数继承

利用

Student
构造出来的实例,属性和方法是不共享的

function People(name) {
this.name = name;
this.eat = function () {
console.log("eat");
};
}

function Student(name) {
People.call(this, name);
this.age = 20;
}

const stu1 = new Student("huro");
const stu2 = new Student("lero");
console.log(stu1.name === stu2.name);	// false
console.log(stu1.eat === stu2.eat);		// false

对于方法来说我们希望是共享的,否则实际上浪费了很多内存。

2. 组合继承

基于原型的方法是实例共享的,我们将方法放入原型,而属性放在构造函数内,这样就叫做组合继承,组合继承可以解决浪费多余内存的问题。

function People(name) {
this.name = name;
}

People.prototype.sayName = function() {
console.log(this.name);
}

function Student() {
People.call(this);
this.age = "20";
}

S
ad8
tudent.prototype = new People();
Student.prototype.constructor = Student;

const stu1 = new Student();
const stu2 = new Student();
console.log(stu1.sayName === stu2.sayName);

然而,还是有个缺点,我们打印

stu1

__proto__
中 有个
name
属性,这个属性其实我们是不需要的,我们希望每个实例能够独享属性,这个
name
属性的存在不但加大了内存开销,还导致当
delete stu1.name
的时候,出现还能使用
stu1.name
的情况,这是我们不想要的

3. 组合寄生继承

顾名思义,组合寄生继承就是结合组合继承和寄生继承

function People(name) {
this.name = name;
}

People.prototype.sayName = function() {
console.log(this.name);
}

function Student() {
People.call(this);
this.age = "20";
}

Student.prototype = Object.create(People.prototype); // 实际上只变化这一行
Student.prototype.constructor = Student;

const stu1 = new Student();
const stu2 = new Student();
console.log(stu1.sayName === stu2.sayName);

通过这种方式创造的继承,弥补了组合继承的不足,节省了内存,并且使得实例共享方法独享属性。

那么

ES6
语法提供的
class
是否也有这种 "聪明" 的设计呢?如果有的话,我们直接利用
class
就可以了

  1. class
    继承
class People {
constructor() {
this.name = "name";
}
eat() {
console.log("eat");
}
}

class Student extends People {
constructor() {
super()
this.age = 20;
}
}

const stu1 = new Student();
const stu2 = new Student();
console.log(stu1.eat === stu2.eat); // true

extends
继承的是原型链的方法

super
继承的是独享的属性和方法

可以发现其实是和组合寄生继承类似的

哦哦,那肯定啊,不然

ES6
不被喷死啊。

继承优势 (选择)

ES6
class
语法有什么优势呢?

  1. 最大的优势是在于可以继承原生构造函数
  2. 原生构造函数

  3. Boolean
  4. Number
  5. String
  6. Array
  7. Date
  8. Function
  9. RegExp
  10. Error
  11. Object

ES5
语法中,你无法原生构造函数的属性,你可能会尝试这样写

const MyArray() {
Array.apply(this, arguments);
}
MyArray.prototype = Object.create(Array.prototype);
MyArray.prototype.constructor = MyArray;

当用这种方式继承的时候,你会发现他与

Array
这个类的行为完全不一致

const names = new MyArray();
names[0] = "huro";
console.log(names.length); // 0

原生构造函数无法绑定

this

class继承
可以

class MyArray extends Array {}

const names = new MyArray();
names[0] = "huro";
console.log(names.length); // 1
  1. 是否一定具有
    __proto__

在原型和原型链章节中,我们说到实例的

__proto__
指向构造函数的
prototype

实际上并不是所有浏览器都是支持

__proto__
的,而
class
作为构造函数的语法糖,一定有这两个属性。

  1. 更严格的控制
function People(name) {
this.name = name;
this.eat = function () {
console.log("eat");
};
}

function Student(name) {
People.call(this, name);
this.age = 20;
}

const stu1 = Student("huro"); // new?
console.log(stu1);

利用构造函数实例化对象的时候,如果忘传了

new
会怎么样,这个时候显然也成立,因为会被当做一个函数看待,由于是全局调用,因此
this
在浏览器环境下就是
window

这样相当于给

window
赋值了
name
eat

这个时候解释器也不会报错,因为没有任何方法可以区分一个函数是否是构造函数,因此可能出现意想不到的错误。

而用

class
方式继承,好处就是如果不写
new
直接报错。

class MyArray extends Array {}

const names = MyArray(); // class constructor MyArray cannot be invoked without "new"

除此之外,在继承的构造函数里,如果没写

super
关键字或
super
不在构造函数顶部也会报错

class MyArray extends Array {
constructor(){
// Must call super constructor in derived class before accessing 'this' or returning from derived constructor
}
}

总结

更严格的语法检查,更多的优化,使得

class继承
应该是目前来看最为优质的继承方式。 为了能看懂他人的代码,以及更好的兼容性,其他的继承方式也要有所了解。

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: