一、JS 三大公理
网上有一个很复杂的一个原型链图,看起来太绕了,但是其实只要掌握三个公理即可
- 对象.proto===其构造函数.prototype
- Function 函数构造所有的函数
- Object.prototype 是所有对象的(直接或者间接)原型
由此可推出
[1, 2, 3].__proto__ === Array.prototype; obj.__proto__ === Object.prototype; Object.__proto__ === Function.prototype;
如果注意细节的小伙伴可能会问,既然所有构造函数(包括 Object 函数)都是由 Function 构造的,那为什么说 Object.prototype 是所有对象的直接或间接原型?不应该是 Function 吗?
二、js 世界构造顺序
现在我们知道,Object.prototype 和 Object.prototype 对象(根对象)并没有直接的关系,只是在名称上做了指向(相当于给根对象加了名字)
所以说 Object.prototype 是所有对象的直接或间接原型。
三、new 出实例对象 a 的时候,js 做了什么?
function Fun(name) { (this.name = name), (this.say = function () { return this.name; }); } var a = new Fun("hu");
简单来说分为三步,
var a={} a.__proto__=Fun.prototype Fun.call(a)
首先我们创建了一个空对象,然后将这个空对象的 proto 指向了 Fun.prototype,然后 this 指向这个对象,运行里面的函数
四、旧方式继承
//父构造函数 function Father(value){ this.value=value this.name='yiling' } //子构造函数 function Son(value){ Father.call(this,value) } let obj2=new Son(0) obj2.value // 0 obj2.name // yiling
上面代码中,在子构造函数中调用父构造函数,并使用 call 函数传入 this,这样子构造函数就会继承父构造函数的方法。
然后通过Son.prototype = Father.prototype
这种方式直接把父构造函数的原型传给子构造函数,但是这种方式有个缺点,那就是当我修改了 Son 的原型时,也会修改父构造函数的原型,因为这种方式是把原型地址赋值给了 Son
Son.prototype=Object.create(Father.prototype) Son.prototype.constructor = Son
采取 Object.create 就相当于覆盖了原来 Son.prototype 的值,并让 Son.prototype.proto指向父构造函数的原型,由于覆盖原因,为了避免突发的情况发生所以要让 Son.prototype.constructor 指回 Son 构造函数
五、基于 es6 的 class 继承
class Father{ constructor(value){ this.value=value this.name="qiuyanxi" } say(){ console.log(123)} } class Son extends Father{ //使用extends关键字来继承 constructor(value,age){ super(value) //这里要使用super方法来继承父构造函数的属性 this.age=age } say(){ super.say()}//这里同样要使用super方式来继承父构造函数的属性 }
不过这种方式还产生了新的知识,那就是让 Son.proto === Father
总的来说,ES6 的演化给我们产生不少语法糖,让我们无需过多关注 JS 的设计,不过个人依然认为不管语法怎样变化,JS 原型链的设计模式是不变的,这对于学习这门语言非常有好处