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())

可以发现,其实就是基于原型链继承,只不过 constructorclass 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";
} Student.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 不被喷死啊。

继承优势 (选择)

ES6class 语法有什么优势呢?

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

原生构造函数

  1. Boolean
  2. Number
  3. String
  4. Array
  5. Date
  6. Function
  7. RegExp
  8. Error
  9. 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 赋值了 nameeat

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

而用 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继承 应该是目前来看最为优质的继承方式。 为了能看懂他人的代码,以及更好的兼容性,其他的继承方式也要有所了解。

一篇文章图文并茂地带你轻松学完 JavaScript 继承的更多相关文章

  1. 一篇文章图文并茂地带你轻松学完 JavaScript 设计模式(一)

    JavaScript 设计模式(一) 本文需要读者至少拥有基础的 ES6 知识,包括 Proxy, Reflect 以及 Generator 函数等. 至于这次为什么分了两篇文章,有损传统以及标题的正 ...

  2. 一篇文章图文并茂地带你轻松学完 JavaScript 设计模式(二)

    JavaScript 设计模式(二) 本篇文章是 JavaScript 设计模式的第二篇文章,如果没有看过我上篇文章的读者,可以先看完 上篇文章 后再看这篇文章,当然两篇文章并没有过多的依赖性. 5. ...

  3. 一篇文章图文并茂地带你轻松学完 JavaScript 原型和原型链

    JavaScript 原型和原型链 在阅读本文章之前,已经默认你了解了基础的 JavaScript 语法知识,基础的 ES6 语法知识 . 本篇文章旨在为 JavaScript继承 打下基础 原型 在 ...

  4. 一篇文章图文并茂地带你轻松学完 JavaScript 事件循环机制(event loop)

    JavaScript 事件循环机制 (event loop) 本篇文章已经默认你有了基础的 ES6 和 javascript语法 知识. 本篇文章比较细致,如果已经对同步异步,单线程等概念比较熟悉的读 ...

  5. 一篇文章图文并茂地带你轻松学完 JavaScript 闭包

    JavaScript 闭包 为了更好地理解 JavaScript 闭包,笔者将先从 JavaScript 执行上下文以及 JavaScript 作用域开始写起,如果读者对这方面已经了解了,可以直接跳过 ...

  6. 一篇文章图文并茂地带你轻松实践 HTML5 history api

    HTML5 history api 前言 由于笔者在网络上没有找到比较好的关于 history api 的实践案例,有的案例过于杂乱,没有重点,有些案例只是告诉读者 api 是什么,却没告诉怎么用,本 ...

  7. 一篇文章图文并茂地带你轻松学会 HTML5 storage

    html5 storage api localStorage 和 sessionStorage 是 html5 新增的用来存储数据的对象,他们让我们可以以键值对的形式存储信息. 为什么要有 stora ...

  8. 一篇文章让你快速入门 学懂Shell脚本

    Shell脚本,就是利用Shell的命令解释的功能,对一个纯文本的文件进行解析,然后执行这些功能,也可以说Shell脚本就是一系列命令的集合. Shell可以直接使用在win/Unix/Linux上面 ...

  9. 学完JavaScript基础有感

    紧接上一篇回来了,这几天一直学js,会不自觉的和其他的编程语言联系在一起,在没有学jQuery之前,结合我所学的c,java,数据结构,数据库以及部分html感觉到JavaScript里面又很多相似的 ...

随机推荐

  1. virsh常见命令笔记

    [基本命令] virsh start 启动 shutdown 关闭 destroy 强制断电 suspend 挂起 resume 恢复 undefine 删除 dominfo 查看配置信息 domif ...

  2. 查找linux系统下的端口被占用进程的两种方法 【转】

    在linux下开发时,你的软件可能要使用某一个端口,或者想查找某一个端口是否被占用.需要怎么做呢??这的确是一个比较烦恼的问题,我也此为这个苦恼过.但是通过查找man手册,还是同事的交流.总结出来两种 ...

  3. idea中文注释出现乱码,我靠自己解决了

    如果你像我一样️,查遍google百度,半天下来还是找不到解决方案,说不定这篇博客能帮助你顺利解决呢 好了,那么开始说说我是怎么解决麻烦的. 首先,我想打开一份java文稿.光预览,它是没有任何问题的 ...

  4. 【Linux】如何查找命令及历史记录history

    如何查找命令及历史记录 文章目录 如何查找命令及历史记录 1.如何找到一个命令 2.命令的历史记录 3.一些实用的快捷键 4.小结 5.参考资料 如何找到一个命令.命令的历史记录.一些实用的快捷键.总 ...

  5. kubernets之Replication Controller

    一  Replication Controller的介绍      pod可能会由于各种原因消失和多出来,例如node节点去除集群或者人为的手工创建,所以为了方便和管理pod的数量,k8s里面 的另外 ...

  6. buuctf刷题之旅—web—随便注

    打开环境 根据提示应该是sql注入 查看数据库名,和数据表 1';show databases;# 1';show tables;# 查看表内字段(1';desc `1919810931114514` ...

  7. linux设备文件

    一.前言 在调用了alloc_chrdev_region函数或register_chrdev_region函数之后可以在/proc/devices中看到该设备的主设备号,比如我注册的hello模块的主 ...

  8. C++ unordered_map/unordered_set 自定义键类型

    1. unordered_map 和 unordered_set template < class Key, // unordered_map::key_type class T, // uno ...

  9. 计算机网络安全 —— C# 使用谷歌身份验证器(Google Authenticator)(五)

    一.Google Authenticator 基本概念  Google Authenticator是谷歌推出的一款动态口令工具,旨在解决大家Google账户遭到恶意攻击的问题,在手机端生成动态口令后, ...

  10. LVS负载均衡IP隧道模式原理介绍以及配置实战

    LVS 基本工作原理 当用户向负载均衡调度器(Director Server)发起请求,调度器将请求发往至内核空间 PREROUTING 链首先会接收到用户请求,判断目标 IP 确定是本机 IP,将数 ...