JS 有个神奇的东西叫做 Prototype,这导致它与主流的面向对象的语言相差甚远,甚至有一个「基于对象的语言」专有名词给他。
认识原型
访问原型
根据 ECMAScript 规范,当提及原型时使用的标识符号为 someObject.[[Prototype]]
获取原型的方法是利用 Object.getPrototypeOf 或 Reflect.getPrototypeOf 函数来访问
在 ES5 中,传递给 Object.getPrototypeOf 的参数如果不是对象将抛出异常;在 ES2015 中将会返回对应的包装类型
使用 Reflect.getPrototypeOf 时则无论 ES5 还是 ES2015 中,只要参数不是对象都会抛出异常
大多情况下,原型也可以通过对象的 __proto__ 属性访问到(已弃用)
函数可能拥有 prototype
属性,这个属性不是这个函数本身的 Prototype,而是当利用这个函数作为构造函数的情况下所构造的对象的 Prototype
设置原型
原型是可以被动态修改的,利用 Object.setPrototypeOf 或 Reflect.setPrototypeOf 即可
根据 ECMAScript 规范,当提及设置原型时使用的标识符号为 someObject.[[SetPrototypeOf]]
Object.setPrototypeOf 与 Reflect.setPrototypeOf 的区别仅在于返回值与异常行为:
Object.setPrototypeOf 返回被设置原型的对象,无法设置原型时抛出异常
Reflect.setPrototypeOf 返回是否修改成功(true / false)
应当避免修改原型,修改原型的速度很慢,另外可能会导致各种微妙的问题
如果必须使用动态原型,应当尽量采用先创建一个无原型的对象(Object.create(null)
)再设置原型的方案
也可以通过 __proto__ 属性修改原型,但与访问这个属性相同,使用这个属性来设置原型是被弃用且不推荐的
初始化原型
可以利用 { __proto__: ... } 或 Object.create(...) 初始化一个指定原型的对象
obj.__proto__
不在规范中且已被弃用,而 { __proto__: ... }
是标准中的一部分,可以正常使用
类的本质是原型,本节不含初始化类对象的方案
特别注意:字面初始化时的 __proto__
与对象的 __proto__
属性不同,即 { __proto__: {} }
和 { ['__proto__']: {} }
是不一样的(前者是设置了对象的 prototype,后者是设置了一个普通的属性),可在此查看效果
普通的属性可以在初始化时定义多次(例如 {a: 1, a: 2}
),但是 __proto__
只能定义一次({__proto__:{}, __proto__: {}}
是不合法的),可在此查看对比