你需要知道的 JS 继承和模拟实现 new
大家好,我是林一一,今天这篇文章是关于 JS 中的继承和模拟实现 new 的,我尽量将文章讲的通俗易懂,我们开始阅读吧 😁
001 继承
继承指的是,子类继承父类的方法。JS 中的继承是基于原型和原型链实现的。对原型和原型链不熟悉的先看看 面试|你不得不懂得 JS 原型和原型链
- 继承的目的:让子类的实例也同样具备父类的属性和公共方法。
思考1:实例 c1 具备哪些属性和方法
function Parent(){
this.name = 'parent'
}
Parent.prototype.getParentName = function() {
console.log('Parent')
}
function Child(){
this.name = '一一'
var name = '二二'
}
Child.prototype.getChildName = function() {
console.log('Child')
}
var c1 = new Child
dir(c1)
实例 c1 具备name="林一一"
,和原型链上的getChildName
(这里忽略Object
上的属性方法)。对这里有疑问的可以看看 __面试|你不得不懂得 JS 原型和原型链__。如果 c1 想获取 Parent 中的属性和方法该怎么获取?
最简单的原型继承
子类的原型等于父类的实例即可实现。原因通过原型链的向上查找机制,子类可以获取父类的方法和属性。
// 一句话一句代码即可
Child.prototype = new Parent
prototype
原型继承中父类的私有属性和公共属性都会变成子类的公共方法。原型继承是指向查找的过程不是拷贝实现的。需要注意的是,继承的父类实例是堆内存地址是唯一的,堆内存中的某一个属性值改变后,子类的实例继承到的就是改变后的属性。
- 缺陷:原型继承是把父类的私有属性和共有属性都定义成了子类原型上的共有属性,如果想要父类的私有属性成为子类的私有属性,原型继承是不能实现的。
call 继承
使用 call 继承解决私有属性私有化之前要明白,构造函数是怎样创建私有属性的,构造函数中通过 this 指向才可以给实例创建私有属性,那么使用 call 就可以改变父类中 this 的指向
function Child(){
Parent.call(this)
this.name = '一一'
var name = '二二'
}
上面 Parent 中的 this 就会被写入到子类中,实例化子类时就可以创建私有的属性。
- 缺陷:call 继承只能继承父类的私有属性不能继承父类的共有属性。call 继承相当于拷贝了一份父类的私有属性。
组合继承1(call继承+子类原型链__proto__指向)
上面提到过 call 继承只能实现子类继承父类的私有属性,那么我们可以只获取父类的共有属性赋予给子类的原型即可。即Child.prototype.__proto__ = Parent.prototype
function Parent(){
this.name = 'parent'
}
Parent.prototype.getParentName = function() {
console.log('Parent')
}
function Child(){
this.name = '一一'
var name = '二二'
Parent.call(this)
}
Child.prototype.__proto__ = Parent.prototype
Child.prototype.getChildName = function() {
console.log('Child')
}
var c1 = new Child()
dir(c1)
- 缺陷:__proto__并不是所有浏览器都提供的,IE第版本就不支持
组合继承2(call继承 + Object.create()) 推荐使用
先介绍一下 Object.create(obj),这个方法可以创建一个空对象,且这个空对象的原型链__proto__可以指向 obj,即换句话说使用 Object.create() 可以拷贝一份对象的属性,所以这个方法也可以作为浅拷贝的一种。
let obj = { name = '林一一' } let a = Object.create(obj) console.log(a.__proto__)
函数的 prototype 属性也是一个对象,同样使用 Object.create() 也可以拷贝父类原型的共有属性和方法。这句话相当于
Child.prototype = Object.create(Parent.prototype)
function Parent() { this.name = 'parent' } Parent.prototype.getParentName = function() { console.log('Parent') } function Child() { this.name = '一一' Parent.call(this) } Child.prototype = Object.create(Parent.prototype) // 子类的 constructor 被覆盖,可以重新加上 Child.prototype.constructor = Child Child.prototype.getChildName = function() { console.log('Child') }
class 中的 extend
ES6 中的 class 实现其实是基于 JS 中的原型和原型链的。
class Parent{
constructor(){
this.name = 'parent'
}
// 等价于 Parent.prototype.getName = function(){...}
getParentName() {
console.log(this.name)
}
}
class Child extend Parent{
constructor(){
super()
this.age = 18
}
getChildName() {
console.log(this.name)
}
}
总结
- 原型继承是 JS 继承中最简单的实现方式,但是不能区分私有属性和共有属性
- 组合继承中,使用 call 继承+改变子类 proto 指向的继承是最合适的方式。缺点是 IE 不支持
__proto__
- 组合继承使用 call 继承和 Object.create() 可以浅拷贝一份父类原型上的方法。
002 new 构造函数
new 构造函数执行相当于普通函数执行。
function Person() {
this.name = '林一一'
}
new Person()
new Person() 过程中发生了什么
- new 为构造函数创建了一个堆内存也就是实例对象
- 执行构造函数,将构造函数的 this 指向这个堆内存地址(实例对象)
将创建好的实例对象返回
需要注意的是,在构造函数中使用 return 没有意义。return 一个基本类型不会阻碍实例的返回,但是 return 一个 object 会覆盖返回的实例。更详细的内容请看 面试| JS 原型和原型链
(阿里)面试题,实现一个 _new(),得到预期的结果
function Dog(name) {
this.name = name
}
Dog.prototype.bark = function() {
console.log('wang wang')
}
Dog.prototype.sayName = function() {
console.log('my name is ' + this.name)
}
function _new() {
// code
}
let sanmao = _new(Dog, '三毛')
sanmao.bark(); // => 'wang wang'
sanmao.sayName(); // => 'my name is 三毛'
console.log(sanmao instanceof Dog) // true
分析:分析这道题其实就是实现 new 的过程。按照上面 new 构造函数中发生的过程可以实现如下
function _new(ctor, ...params) {
// 创建一个堆内存地址,继承原型上的共有属性
let obj = {}
obj.__proto__ = ctor.prototype
// 确定 this 指向堆内存地址,同时使用 call 将构造函数的私有属性指向到 obj 实例中,实现私有属性继承
let res = ctor.call(obj, ...params)
// 返回创建的实例,考虑到构造函数本身执行后返回值是对象的话会覆盖返回的实例,需要先判断
if(res !== null && typeof res === 'object') return res
return obj
}
执行结果输出无误。上面的模拟实现 new 过程中使用了组合继承 call+原型继承
结束
更多的面试系列的文章
面试 |call, apply, bind的模拟实现和经典面试题
面试 | JS 事件循环 event loop 经典面试题含答案
......
感谢阅读到这里,如果文章能对你有帮助或启示欢迎 star 我是林一一,下次见。
154 声望
5 粉丝
推荐阅读
2021 年林一一同学的字节,百度,京东秋招记录
大家好,我是林一一。有一段时间没有写博客了,因为最近几个月的时间都放在秋招上面了,想了好久,秋招的经历不知道写还是不写。最近思绪很乱,心情也比较浮躁,想躺平一段时间,但总感觉有很多事情要做。下面的...
林一一赞 1阅读 1.1k
从零搭建 Node.js 企业级 Web 服务器(零):静态服务
过去 5 年,我前后在菜鸟网络和蚂蚁金服做开发工作,一方面支撑业务团队开发各类业务系统,另一方面在自己的技术团队做基础技术建设。期间借着 Node.js 的锋芒做了不少 Web 系统,有的至今生气蓬勃、有的早已夭折...
乌柏木赞 141阅读 11.9k评论 10
从零搭建 Node.js 企业级 Web 服务器(十五):总结与展望
总结截止到本章 “从零搭建 Node.js 企业级 Web 服务器” 主题共计 16 章内容就更新完毕了,回顾第零章曾写道:搭建一个 Node.js 企业级 Web 服务器并非难事,只是必须做好几个关键事项这几件必须做好的关键事项就...
乌柏木赞 60阅读 6k评论 16
再也不学AJAX了!(二)使用AJAX ① XMLHttpRequest
「再也不学 AJAX 了」是一个以 AJAX 为主题的系列文章,希望读者通过阅读本系列文章,能够对 AJAX 技术有更加深入的认识和理解,从此能够再也不用专门学习 AJAX。本篇文章为该系列的第二篇,最近更新于 2023 年 1...
libinfs赞 39阅读 6.2k评论 12
从零搭建 Node.js 企业级 Web 服务器(一):接口与分层
分层规范从本章起,正式进入企业级 Web 服务器核心内容。通常,一块完整的业务逻辑是由视图层、控制层、服务层、模型层共同定义与实现的,如下图:从上至下,抽象层次逐渐加深。从下至上,业务细节逐渐清晰。视图...
乌柏木赞 39阅读 7.1k评论 6
【关于Javascript】--- 正则表达式篇
基础知识一、元字符 {代码...} 二、量词 {代码...} 三、集合 字符类 {代码...} 四、分支 {代码...} 五、边界 开始结束 {代码...} 六、修饰符 {代码...} 七、贪婪模式和非贪婪模式js默认贪婪模式即最大可能的匹配...
Jerry赞 35阅读 2.9k
从零搭建 Node.js 企业级 Web 服务器(二):校验
校验就是对输入条件的约束,避免无效的输入引起异常。Web 系统的用户输入主要为编辑与提交各类表单,一方面校验要做在编辑表单字段与提交的时候,另一方面接收表单的接口也要做足校验行为,通过前后端共同控制输...
乌柏木赞 32阅读 6.1k评论 9
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。