大家好,我是林一一,明天这篇文章是对于 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 Childdir(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.prototypeChild.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 = ChildChild.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+原型继承
完结
更多的面试系列的文章
Vue 高频原理面试篇+具体解答
面试 |call, apply, bind的模仿实现和经典面试题
面试 | JS 闭包经典应用场景和含闭包必刷题
面试 | 你不得不懂的 JS this 指向
面试 | JS 事件循环 event loop 经典面试题含答案
......
github文章合集
感激浏览到这里,如果文章能对你有帮忙或启发欢送 star 我是林一一,下次见。