JS 提供了创建类的语法糖 “class”,以及用与创建子类的关键字 “extends”,那么,问题来了,如果我们不用这些语法糖,我们应该如何实现 JS 中的原型继承呢?
原型链是个什么链
这是我们知道的:
值.proto === 值的构造函数.prototype
这句话的意思是,每个值都有一个隐藏的 proto 属性绑定它的构造函数的 prototype 属性
如果是一个数组,那么
[].proto === Array.prototype
从这个式子表达了一个数组是如何继承数组的原型属性的,而,进一步,数组作为对象的那一部分是怎么继承下来的呢?
答案是:
Array.prototype.proto === Object.prototype
绑定 Object.prototype 的是 Array 构造函数的 prototype 属性。
于是,所谓的“原型链”,本质上就是多个 prototype 串起来的链。
prototype 是个什么东西?
prototype 这个东西,当我们声明一个函数的时候,这个函数就自带一个 prototype 属性,而对象是没有默认的 prototype 属性的(这也是为什么我们说,对象没法被继承,如果还是想继承,需要用到 Object.setPrototypeOf 属性)
继承.png在没有 class 之前,我们是这么给构造函数写原型属性的:
fn.prototype.a = function(){}
当我们这么写的时候,实际上就意味着,prototype 是一个对象,
于是,所谓的“原型链”,更本质上就是串起来的对象。
proto 与 prototype
当然,仅仅把对象串起来,并不能自动实现“继承”,在整个继承中的关键,其实是“proto”,也就是说,通过 __proto__
把连起来的对象串,自带“继承”的特效~
到了这里,我们不得不严格区分一下 proto 与 prototype
__proto__
怎么看都是 prototype 的缩写,而且 值.__proto__ === 构造函数.prototype
不是说明他们完全等同吗?
但他们确实不是一回事,比如:
1.如果我们不主动设置的话,对象并没有 prototype 属性,但是对象天生就有 proto;
2.我们可以把 __proto__
理解为有继承的作用,但是 prototype 没有;
3.也就是说,__proto__
更偏向于表达“绑定、继承”这些动作,而 prototype 表示,被绑定被继承的那堆东西。
进而,如何绑定 __proto__
? JS 不允许我们手动设置 __proto__
。
对于我们来说,实现一个有继承效果的 __proto__
就是使用 new, 别忘了,new 的作用之一就是把新生成的对象 proto 属性绑定原型
构造函数怎么办?
说了半天,我们还没有提构造函数,我们可以这么认为,上面说讲的内容就是构建一个对象的内在关系,而构造函数所要做的就是让我们的对象“实体化” —— 它就是用来产生对象的。
当我们 new 一个构造函数的时候,就会产生一个新的空对象,并把这个对象作为 this 把构造函数执行一遍。
class A {
constructor(){
this.x = 1
console.log("aaa")
}
}
class B extends A {
constructor() {
super()
this.y = 2
console.log("bbb")
}
}
let c = new B
//→ aaa
//→ bbb
console.log(c) //→ {x: 1, y: 2}
看上面的代码,我们发现,子类用于创建对象的时候,不仅子构造函数会执行,父类的构造函数也会执行。
到底我们该怎么做?
于是,如果我们想实现“继承”,应该如何做?关键点在于:
1.子构造函数要调用父构造函数;
2.用 new 实现原型链的继承;
// B 继承 A
function A (){
this.a = "a"
}
A.prototype.fa = function(){console.log("fa")}
function B(){
A.apply(this, arguments) // 调用 A 构造函数
this.b = "b"
}
let f = function(){}
f.prototype = A.prototype
B.prototype = new fn() // 通过 new, B.prototype 就变成了一个有 __proto__ 属性指向 A.prototype 的空对象
B.prototype.fb = function(){console.log("fb")} // 设置其它属性 ……
网友评论