面试 | 你需要知道的 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)
组合继承call和父类原型.jpg
  • 缺陷: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__)
Object.create.jpg
  • 函数的 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)
    }
}
classExtends.jpg

总结

  • 原型继承是 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+原型继承

结束

更多的面试系列的文章

Vue 高频原理面试篇+详细解答

面试 |call, apply, bind的模拟实现和经典面试题

面试 | JS 闭包经典使用场景和含闭包必刷题

面试 | 你不得不懂的 JS this 指向

面试 | JS 事件循环 event loop 经典面试题含答案

......

github文章合集

感谢阅读到这里,如果文章能对你有帮助或启示欢迎 star 我是林一一,下次见。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,457评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,837评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,696评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,183评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,057评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,105评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,520评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,211评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,482评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,574评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,353评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,897评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,489评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,683评论 2 335

推荐阅读更多精彩内容

  • 前言 针对面试的 JavaScript 知识点整理 1.介绍一下js的数据类型有哪些,值是如何存储的 JavaSc...
    Moon_f3e1阅读 226评论 0 0
  • 1. 闭包 闭包是指有权访问另一个函数作用域中变量的函数,创建闭包的最常见的方式就是在函数内创建函数,通过函数访问...
    Marvel_Dreamer阅读 3,489评论 0 21
  • 缘起 笔者最近在近两周的面试中, 遇到了大大小小形形色色的面试题。很多问题都是知道但是说不出来,所以想记录下来, ...
    haoxn阅读 115评论 0 1
  • 1 MVC 和 MVVM 区别 MVC MVC 全名是 Model View Controller,是模型(mod...
    c88cfe19384a阅读 1,539评论 0 1
  • 表情是什么,我认为表情就是表现出来的情绪。表情可以传达很多信息。高兴了当然就笑了,难过就哭了。两者是相互影响密不可...
    Persistenc_6aea阅读 123,150评论 2 7