这是我参与8月更文挑战的第16天,活动详情查看:8月更文挑战
这个系列也没啥花头,就是来整平时面试的一些手写函数,考这些简单实现的好处是能看出基本编码水平,且占用时间不长,更全面地看出你的代码实力如何。一般不会出有很多边界条件的问题,那样面试时间不够用,考察不全面。
平时被考到的 api 如果不知道或不清楚,直接问面试官就行, api 怎么用这些 Google 下谁都能马上了解的知识也看不出水平。关键是在实现过程,和你的编码状态、习惯、思路清晰程度等。
注意是简单实现,不是完整实现,重要的是概念清晰和实现思路清晰,建议
先解释清楚概念
=>写用例
=>写伪代码
=>再实现具体功能
,再优化
,一步步来。
27. 各类继承方式的手写(上)
问题是什么
继承是 OOP (面向对象编程)(Object-oriented programming) 的核心概念,本文是介绍js 中各种继承的简单手写。
关于原型相关知识请看我的核心概念系列一文说透JS中的原型和继承(上) 继承相关核心概念正在写作中,敬请期待。本篇只是列举下手写方式,作为核心概念的实例补充。那篇会针对各种继承方式进行详细分析。
我们先简单理解,继承就是为了重用
, 比如你有 people
这个对象及其属性和方法,并希望将 student
和 worker
作为基于 people
稍加修改的变体。我们想重用
people
中的内容,而不是复制/重新实现它的方法,而只是在其之上构建一个新的对象。那么我们看下面几种继承方式的简单实现。
上篇我们主要讲
- 原型链继承
- 构造函数继承
- 组合继承
原型链继承
function Parent() {
this.name = 'ParentName';
this.actions = ['eat', 'sleep'];
}
function Child() {}
// 我们把 Child 的 prototype 属性设置为 Parent 的一个实例
Child.prototype = new Parent();
Child.prototype.constructor = Child;
// 原型链继承的继承方式都要修改子类构造函数的指向,
// 否则子类实例的构造函数会指向父类构造函数。
let child1 = new Child()
// 从原型链继承了父类的属性
console.log(child1.name) // ParentName
console.log(child1.actions) // [ 'eat', 'sleep' ]
存在的问题
- 引用类型的属性被所有实例共享
- 在创建 Child 的实例时,不能向 Parent 传参
function Parent() {
this.name = 'ParentName';
this.actions = ['eat', 'sleep'];
}
function Child() {}
// 我们把 Child 的 prototype 属性设置为 Parent 的一个实例
Child.prototype = new Parent();
Child.prototype.constructor = Child;
let child1 = new Child()
// 引用类型属性修改
child1.actions.push('coding');
console.log(child1.actions) // [ 'eat', 'sleep', 'coding' ]
// 接下来 Child 创建的对象就共享相同属性
let child2 = new Child()
console.log(child2.actions) // [ 'eat', 'sleep', 'coding' ]
那么如何避免这两个问题呢,我们来看下面的构造函数继承。
构造函数继承
function Parent (name, age) {
this.name = name;
this.age = age;
this.actions = ['eat', 'sleep'];
this.play = function () {}
}
// function Child (name, age) {
// Parent.call(this, name, age);
// }
// 或者用 arguments 更简单
function Child() {
Parent.apply(this, arguments);
}
const child1 = new Child('hh', 25);
child1.actions.push('coding')
const child2 = new Child('cd', 1);
console.log(child1.name) // hh
console.log(child1.age) // 25
console.log(child1.actions) // [ 'eat', 'sleep', 'coding' ]
console.log(child2.name) // cd
console.log(child2.age) // 1
console.log(child2.actions) // [ 'eat', 'sleep' ]
console.log(child1.play === child2.play); // false
关注几个点:
- 其实核心就是通过
Parent.call/apply
继承了父类的属性 - 引用类型的属性没有被所有实例共享
- 而且可以传参给 Parent
但注意最后一行 child1.play === child2.play
是 false
说明了 play
这个方法占用了两份内存空间。或者说,这个相同的方法被复制了一份, 而不是引用的同一个方法。 原因是我们用到构造函数,而方法都在构造函数中定义
,每次创建实例都会创建一遍方法, 造成内存浪费。
那么我们就需要继续改进,融合上面两种继承方式,组合继承出现
组合继承
function Parent (name, actions) {
this.name = name;
this.actions = actions;
}
// 直接在原型链上挂方法,就避免了内存浪费
Parent.prototype.play = function () {
console.log(`${this.name} is playing`)
}
function Child (name, age, actions) {
// 注意这是第一次调用构造函数
Parent.call(this, name, actions);
this.age = age;
}
// 这是第二次调用构造函数
Child.prototype = new Parent();
Child.prototype.constructor = Child;
let child1 = new Child('k', 25, ['eat', 'sleep', 'coding']);
console.log(child1.name); // k
console.log(child1.age); // 25
console.log(child1.actions); // ['eat', 'sleep', 'coding']
let child2 = new Child('cd', 1, ['eat', 'sleep']);
console.log(child2.name); // cd
console.log(child2.age); // 1
console.log(child2.actions); // ['eat', 'sleep']
child2.play(); // cd is playing
console.log(child1.play === child2.play) // true
注意
child1.play === child2.play
说明方法指向一处内存- 结合了上面两种继承的优势
那么组合继承这么好用是不是没缺点了, 我们思考下, 其实我代码已经标出,调用了2次构造函数,我们该如何优化
今天就到这,明天继续接下来的几种继承,且听下回分解
另外向大家着重推荐下另一个系列的文章,非常深入浅出,对前端进阶的同学非常有作用,墙裂推荐!!!核心概念和算法拆解系列 记得点赞哈
今天就到这儿,想跟我一起刷题的小伙伴可以加我微信哦 点击此处交个朋友
Or 搜索我的微信号infinity_9368
,可以聊天说地
加我暗号 "天王盖地虎" 下一句的英文
,验证消息请发给我
presious tower shock the rever monster
,我看到就通过,加了之后我会尽我所能帮你,但是注意提问方式,建议先看这篇文章:提问的智慧
参考
- juejin.cn/post/684490…
- MDN
- 高程