本文已参与「掘力星计划」,赢取创作大礼包,挑战创作激励金。
1 前言
前面写了一篇
js 原生语法之 prototype,__proto__和 constructor把它三者之间的关系搞清楚了, 也理明白了js是怎样通过prototype实现继承的,这篇文章就讲一讲继承的具体实现方式. 其实就是怎样将想要继承的对象放置到需要继承对象的原型链上.
2 原型链继承
就是直接修改
Function存放的共享数据prototype
const Animal = function () {}
Animal.prototype.eat = function () {
console.log('i eat')
}
const Dog = function () {}
// Dog.prototype = new Animal()
// Object.setPrototypeOf(Dog.prototype, new Animal())
Dog.prototype = Object.create(new Animal())
Dog.prototype.constructor = Dog // 最好修复一下
const dog = new Dog()
dog.eat()
子类构造出来的实例, 在自身上找不到的属性
eat会到它的原型对象的Dog.prototype上去找, 就是new Animal(),Animal实例身上也没有eat属性, 会到它的原型对象Animal.prototype上找到eat
3 其它六种继承方式
- 借用构造函数继承
- 组合继承
- 原型式继承
- 寄生式继承
- 寄生组合式继承
- 混入继承多个对象
都是垃圾, 都是糟粕, 其实就是我不想记了...
4 class 继承
主要要学习
class的继承, 它也是通过prototype实现的继承, 是一个语法糖.
如果可以, 我还是希望代码中不要直接修改
prototype的属性, 例如prototype.XXX = XXX, 这样的代码真的很难阅读, 并且实现继承的代码做好在最前面.
// 父类
class Animal {
constructor(options) {
typeof options === 'object' && Object.assign(this, options)
}
say() {
console.log('i ...')
}
}
// 子类 , 继承父类
class Dog extends Animal {
constructor(options) {
// 在子类中的构造函数中, 在使用 this 之前, 需要 super(), 一下, 去先将父类中的构造函数调用一下, 得到父类中的实例属性
super(options)
this.options = options
}
eat() {
console.log('i eat')
}
}
tsc转ts到js之后, 简化代码的结果
/**
* 继承方法
* @param {*} child 子类
* @param {*} b 父类
*/
var __extends = function (child, parent) {
if (typeof b !== 'function' && parent !== null)
throw new TypeError(
'Class extends value ' + String(parent) + ' is not a constructor or null'
)
// 这里是相当于复制父类的静态方法到子类上, 因为这样会在子类 child 上读取不到的属性, 会到 父类 parent 上去读取
Object.setPrototypeOf(child, parent)
if (parent === null) {
// 如果父类是 null, 则原型 设置为 null
child.prototype = Object.create(parent)
} else {
// 如果父类是一个函数,
// 直接使用 原型链继承
child.prototype = new parent()
child.prototype.constructor = child
}
}
// 为啥是一个立即执行函数, 因为 class 不会提升, 可以使用这种方式做到.
// 但是 const 就模拟不出来了
var Animal = /** @class */ (function () {
// class 实际上也就是一个 function,
// constructor 就是 函数本体
function Animal(options) {
typeof options === 'object' && Object.assign(this, options)
}
// 方法通过 原型实现
Animal.prototype.say = function () {
console.log('i ...')
}
return Animal
})()
var Dog = /** @class */ (function (_super) {
// 继承父类
__extends(Dog, _super)
function Dog(options) {
// 实例化父类, 得到父类的实例属性
var _this = _super.call(this, options) || this
_this.options = options
return _this
}
Dog.prototype.eat = function () {
console.log('i eat')
}
return Dog
})(Animal)
5 diy 继承
纯属 diy
const protoArr = Symbol('protoArr')
const addProto = (child, parent) => {
if (!child[protoArr]) {
// 因为不能修改 child.prototype 的指向, 所以变通了一下, 去修改 child.prototype 的原型对象的指向
const originProto = Object.getPrototypeOf(child.prototype)
// 将它原先的原型对象, 和要添加的原型对象保存起来
child[protoArr] = new Set([originProto, parent.prototype])
const newProp = new Proxy(
{},
{
get(target, key) {
// 到保存的各个原型对象上去读取需要获取的属性
const value = [...child[protoArr]].find((i) => i?.[key] !== void 0)?.[
key
]
return value
},
}
)
// 如果在 child.prototype 上没有读取到的属性, 回到它的原型对象上去读取, 然后就读取到了这个代理的对象, 然后就可以被拦截到
Object.setPrototypeOf(child.prototype, newProp)
} else {
// 直接将需要添加的原型链加到保存的地方
child[protoArr].add(parent.prototype)
}
}
const delProto = (child, parent) => {
if (!child[protoArr]) {
// 没有新增过这个原型对象
return
} else {
// 删除即可
child[protoArr].delete(parent.prototype)
}
}
class Style1 {
width() {
return 100
}
}
class Style2 {
height() {
return 100
}
}
class Style3 {
color() {
return '#ccc'
}
}
class Style4 {}
Style4.prototype.fontSize = '12px'
class Style5 {
backgroundColor() {
return 'red'
}
}
const getStyle = (style) => {
const keys = ['width', 'height', 'color', 'fontSize', 'backgroundColor']
return keys.reduce((total, cur) => {
if (style[cur]) {
total[cur] = typeof style[cur] === 'function' ? style[cur]() : style[cur]
}
return total
}, {})
}
class Style {}
const style = new Style()
console.log(getStyle(style))
// {} 此时, 它和它的原型链上读取不到任何属性
addProto(Style, Style1)
addProto(Style, Style2)
addProto(Style, Style3)
addProto(Style, Style4)
addProto(Style, Style5)
console.log(getStyle(style))
// {
// width: 100,
// height: 100,
// color: '#ccc',
// fontSize: '12px',
// backgroundColor: 'red',
// }
delProto(Style, Style2)
console.log(getStyle(style))
// {
// width: 100,
// color: '#ccc',
// fontSize: '12px',
// backgroundColor: 'red',
// }
6 总结
继承就是通过修改原型对象实现的.
7 最后
继承就记这么多, 如果有给你带来一些启示, 欢迎点赞, 评论, 关注, 收藏...
按照惯例, 附上之前写的几篇文章