一篇文章图文并茂地带你轻松学完 JavaScript 继承
JavaScript 继承
在阅读本文章之前,已经默认你了解了基础的 JavaScript
语法知识,基础的 ES6
语法知识 。
继承种类
简单的继承种类可以分为
- 构造函数继承
- 原型链继承
- class继承
- 寄生继承
其中 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";
}
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
就可以了
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
语法有什么优势呢?
- 最大的优势是在于可以继承原生构造函数
原生构造函数
- Boolean
- Number
- String
- Array
- Date
- Function
- RegExp
- Error
- 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
- 是否一定具有
__proto__
在原型和原型链章节中,我们说到实例的 __proto__
指向构造函数的 prototype
实际上并不是所有浏览器都是支持 __proto__
的,而 class
作为构造函数的语法糖,一定有这两个属性。
- 更严格的控制
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继承
应该是目前来看最为优质的继承方式。 为了能看懂他人的代码,以及更好的兼容性,其他的继承方式也要有所了解。
一篇文章图文并茂地带你轻松学完 JavaScript 继承的更多相关文章
- 一篇文章图文并茂地带你轻松学完 JavaScript 设计模式(一)
JavaScript 设计模式(一) 本文需要读者至少拥有基础的 ES6 知识,包括 Proxy, Reflect 以及 Generator 函数等. 至于这次为什么分了两篇文章,有损传统以及标题的正 ...
- 一篇文章图文并茂地带你轻松学完 JavaScript 设计模式(二)
JavaScript 设计模式(二) 本篇文章是 JavaScript 设计模式的第二篇文章,如果没有看过我上篇文章的读者,可以先看完 上篇文章 后再看这篇文章,当然两篇文章并没有过多的依赖性. 5. ...
- 一篇文章图文并茂地带你轻松学完 JavaScript 原型和原型链
JavaScript 原型和原型链 在阅读本文章之前,已经默认你了解了基础的 JavaScript 语法知识,基础的 ES6 语法知识 . 本篇文章旨在为 JavaScript继承 打下基础 原型 在 ...
- 一篇文章图文并茂地带你轻松学完 JavaScript 事件循环机制(event loop)
JavaScript 事件循环机制 (event loop) 本篇文章已经默认你有了基础的 ES6 和 javascript语法 知识. 本篇文章比较细致,如果已经对同步异步,单线程等概念比较熟悉的读 ...
- 一篇文章图文并茂地带你轻松学完 JavaScript 闭包
JavaScript 闭包 为了更好地理解 JavaScript 闭包,笔者将先从 JavaScript 执行上下文以及 JavaScript 作用域开始写起,如果读者对这方面已经了解了,可以直接跳过 ...
- 一篇文章图文并茂地带你轻松实践 HTML5 history api
HTML5 history api 前言 由于笔者在网络上没有找到比较好的关于 history api 的实践案例,有的案例过于杂乱,没有重点,有些案例只是告诉读者 api 是什么,却没告诉怎么用,本 ...
- 一篇文章图文并茂地带你轻松学会 HTML5 storage
html5 storage api localStorage 和 sessionStorage 是 html5 新增的用来存储数据的对象,他们让我们可以以键值对的形式存储信息. 为什么要有 stora ...
- 一篇文章让你快速入门 学懂Shell脚本
Shell脚本,就是利用Shell的命令解释的功能,对一个纯文本的文件进行解析,然后执行这些功能,也可以说Shell脚本就是一系列命令的集合. Shell可以直接使用在win/Unix/Linux上面 ...
- 学完JavaScript基础有感
紧接上一篇回来了,这几天一直学js,会不自觉的和其他的编程语言联系在一起,在没有学jQuery之前,结合我所学的c,java,数据结构,数据库以及部分html感觉到JavaScript里面又很多相似的 ...
随机推荐
- virsh常见命令笔记
[基本命令] virsh start 启动 shutdown 关闭 destroy 强制断电 suspend 挂起 resume 恢复 undefine 删除 dominfo 查看配置信息 domif ...
- 查找linux系统下的端口被占用进程的两种方法 【转】
在linux下开发时,你的软件可能要使用某一个端口,或者想查找某一个端口是否被占用.需要怎么做呢??这的确是一个比较烦恼的问题,我也此为这个苦恼过.但是通过查找man手册,还是同事的交流.总结出来两种 ...
- idea中文注释出现乱码,我靠自己解决了
如果你像我一样️,查遍google百度,半天下来还是找不到解决方案,说不定这篇博客能帮助你顺利解决呢 好了,那么开始说说我是怎么解决麻烦的. 首先,我想打开一份java文稿.光预览,它是没有任何问题的 ...
- 【Linux】如何查找命令及历史记录history
如何查找命令及历史记录 文章目录 如何查找命令及历史记录 1.如何找到一个命令 2.命令的历史记录 3.一些实用的快捷键 4.小结 5.参考资料 如何找到一个命令.命令的历史记录.一些实用的快捷键.总 ...
- kubernets之Replication Controller
一 Replication Controller的介绍 pod可能会由于各种原因消失和多出来,例如node节点去除集群或者人为的手工创建,所以为了方便和管理pod的数量,k8s里面 的另外 ...
- buuctf刷题之旅—web—随便注
打开环境 根据提示应该是sql注入 查看数据库名,和数据表 1';show databases;# 1';show tables;# 查看表内字段(1';desc `1919810931114514` ...
- linux设备文件
一.前言 在调用了alloc_chrdev_region函数或register_chrdev_region函数之后可以在/proc/devices中看到该设备的主设备号,比如我注册的hello模块的主 ...
- C++ unordered_map/unordered_set 自定义键类型
1. unordered_map 和 unordered_set template < class Key, // unordered_map::key_type class T, // uno ...
- 计算机网络安全 —— C# 使用谷歌身份验证器(Google Authenticator)(五)
一.Google Authenticator 基本概念 Google Authenticator是谷歌推出的一款动态口令工具,旨在解决大家Google账户遭到恶意攻击的问题,在手机端生成动态口令后, ...
- LVS负载均衡IP隧道模式原理介绍以及配置实战
LVS 基本工作原理 当用户向负载均衡调度器(Director Server)发起请求,调度器将请求发往至内核空间 PREROUTING 链首先会接收到用户请求,判断目标 IP 确定是本机 IP,将数 ...