最近在看《JavaSrcipt编程实践与语言精髓》,被里面的原型搞得差点就想撞南墙,记录一下整理一下思路,有错的地方欢迎指出。
一、为什么要弄清楚原型?
为什么要弄清楚原型,那就得了解原型的含义:
在书里的第3章的 3.2.1.1:空白对象是所有对象的基础 中提到:
对象的“构造过程”可以简单地理解为“对原型的复制”
原型的含义是指:如果构造器(Object)有一个原型对象(object.prototype),则由该构造器创建的实例(obj)都必然复制自该原型对象。换言之,所谓“原型”,就是构造器用于生成实例的模板。
所以我创建一个MyObjectEx的类,继承自MyObject类
之后将MyObjectEx的类的原型改成MyObject1,但是MyObjectEx类创建的实例的原型还是指向MyObject,原因就是实例的原型指向没有被改动,改到的只是构造器的原型链。
在这里要说明一点就是,构造器的原型链和实例的原型链不是一样的。
所以标题所指的__proto__,prototype属性都是指实例的原型,虽然prototype属性是构造器的属性。
二、了解各属性的含义
1. prototype是什么东西?
书里在第三章的 3.2.1.3:从函数到构造器 这一小节指出: 函数只是函数,尽管它有一个prototype成员。在默认的情况下,所有函数的这个成员总是指向标准的object()构造器的实例--空白对象,不过该实例创建后,constuctor属性总是会被先赋值为当前函数
我对这句话的理解就是函数有一个prototype属性,值是一个对象,里面有一个constuctor属性,值是函数本身,值里面还有你挂载在prototype上的内容,还有一个[[Prototype]]的内部属性。
2. __proto__是什么东西?
__proto__其实是Object.prototype的一个不可遍历出来的一个属性
属性不可以通过Object.keys这些方法拿出来,因为设置了enumerable为false:
由于大部分情况的对象的原型链上都有Object这个构造器的原型,所以大部分情况下__proto__这个值都是有效的,指向当前值的原型;当然如果原型链上不再有Object的原型的话,那么__proto__的值就是undefined,也就不再指向当前值的原型了。
3. [[Prototype]]是什么东西?
在书里的第三章的3.6.1.1:运行期侵入的核心机制这一小节表示:基本对象的内部槽有且仅有两个,分别是[[Prototype]]和[[Extensible]]...当访问属性时,如果在自由属性表中没有找到指定属性,且[[Prototype]]为非null值时就表明可以上溯到该原型的对象的自有属性表查找。
所以[[Prototype]]这个内部槽(内部属性)里面存着的才是原型,__proto__也只是一个引用指向[[Prototype]]。 另外值得一说的是,操作[[Prototype]]这个内部槽的方法是:
(1)Object.getPrototypeOf(obj)---取obj的[[Prototype]]的值
(2)Object.setPrototypeOf(obj,...)---设置obj的[[Prototype]]的值
4. 判断对象是否继承某一个构造器
有2个方法,一个是b instanceOf A,一个是A.isPrototypeOf(b); 讲讲2个方法的不同: b.instanceOf A--是判断A.prototype是否在b的原型链上 A.isPrototypeOf(b)--是判断b的原型链上是否有A
5. 总结
一般来说,当我们没有去改原型链的时候,类实例的__proto__指向类构造器的prototype属性,类实例的__proto__指向实例的[[Prototype]]
三、extends实现的功能
在没有class之前的原型继承,子类的原型是父类的一个实例,要实现类继承得通过以下几步:
function MyObject(){};
function MyObjectEx(){};
MyObjectEx.prototype = new MyObject;
MyObjectEx.prototype.constructor = MyObjectEx;
获取一个实例,看对应的原型:
用class实现的原型继承只要2行代码:
class MyObject{};
class MyObjectEx extends MyObject{};
同样还是获取一个实例,看对应的原型,还是一样:
在书里的第3章的3.3.3:类是用构造器(函数)来实现的这一小节指出:而在JavaScript实现类继承时并不通过(类似如上)的动态过程来构造原型链,而是简单地重置了原型的原型,如下:
用extends实现原型链,实际上是走了3步:
//实现class MyObjectEx extends MyObject{};
//1.声明一个构造器/构造方法
function MyObjectEx(){};
//2.置原型链
Object.setPrototypeOf(MyObjectEx.prototype,MyObject.prototype)
//3.修改类的原型
Object.setPrototypeOf(MyObjectEx,MyObject)
四、当你类继承的时候设置父类是null
类构造器继承null的时候是不能构造实例的
在书里的第3章3.3.4:父类的默认值与null值这一小节指出:然而也存在super指向null的情况,这意味着没有父类,也没有父类的构造器。这种情况下的class声明为:
class MyObject extends null {}
这个class声明很特别。我们知道它的父类是null,不能作为函数调用,因此显然代码super()是无效的。进一步地,由于在有extends声明的构造方法constructor中:
由于要引用this,则需要先调用super(),所以不能引用this;并且
由于super.xxx()调用需要绑定this,所以也不能使用;并且
由于super.xxx事实上无法访问到有效成员,因此也不能使用。
那么显然与super和this相关的一切性质在构造方法声明中都失效了。需要强调的是。这种失效不是在语法分析期发生的,而是在执行期发生的。
所以这就是为什么声明的时候没有错误,等到了创建实例的时候会出错的原因。
那么构造器的实例的原型是怎么样的呢?
我们可以发现在这里是没有[[Prototype]]这个属性了。
如果真的要设置父类为null,那么可以在构造器函数中返回一个创建的实例:
class MyObject extends null{
constructor(){
return Object.create(new.target.prototype)
}
}
这时候生成一个实例,可以看到实例的原型链上没有了Object.prototype,所以这时候你去拿实例的原型,用__proto__属性是拿不到的,但是用Object.getPrototypeOf()是可以的