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__: {}}
是不合法的),可在此查看对比
Object 原型
空对象
往往,当我们谈及空对象时,可能指的通常是 {}
—— 但它其实「没有那么空」,它存在着 Object 作为它的原型,因此实际存在着若干的属性
可以利用 console.log(Object.getPrototypeOf({}))
来查看一个空对象中所拥有的属性
如果我们想创建一个「真正的」空对象,需要确保它的原型也为空,可以利用 Object.create(null)
来获得它
Object 原型
JS 中基本所有的非基本类型的对象的原型最终都是 Object(可点此实验,除了 null
undefined
Symbol 和特意自定义了原型的对象,包括自定义类的实例、各种基础类型在内的所有对象最初始的原型都是 Object)。
因为原型是一种链式结构,而几乎所有的对象的原型链最头上都是 Object,因此 Object 的方法几乎可以被所有对象所调用(在没有被同名属性屏蔽的情况下)
可以通过 console.log(Object.prototype)
获取到 Object 原型的内容,它包括
与值相关的属性 TODO
与 property 相关的属性
(已被弃用的)__defineGetter__ 和 __defineSetter__ 属性用于为对象定义 getter 和 setter,现已建议用 Object.defineProperty 来替代
(已被弃用的)__lookupGetter__ 和 __lookupSetter__ 属性用于获取给对象定义的属性,现已建议用 Object.getOwnPropertyDescriptor 替代
hasOwnProperty 属性函数用于判断对象是否存在某一自有属性
自有属性的含义:是属于这个对象的,而不是属于其原型链上的
与 in 不同:in 会检查所有原型链上的继承属性,而 hasOwnProperty 不会检查原型链
现在已建议使用 Object.hasOwn 来替代
propertyIsEnumerable 属性函数用于判断对应的属性是否为自有属性 + 可枚举属性
虽然函数命名上仅展示了可枚举性,但实际上只有同时为自有属性该函数才会返回 true
关于可枚举属性的含义:各种对象的属性后面会具体再写,简单说就是可以被循环到的属性,可阅读文档 https://developer.mozilla.org/en-US/docs/Web/JavaScript/Enumerability_and_ownership_of_properties 详细了解
可利用 Object.getOwnPropertyDescriptor + 判断返回的 enumerable 属性来替代
与 prototype 相关的属性
(已经被弃用的)__proto__ 属性,用于获取或设置 prototype(上面说过了)
isPrototypeOf 属性:一个函数,用于判断调用当前函数的 this 是否存在于另一个对象的原型链中
使用方法应该为
A.prototype.isPrototypeOf(b)
,即调用isPrototypeOf
的对象应当为一个原型对象、而它的参数应该为存在原型的对象现在通常使用
instanceof
,通常A.prototype.isPrototypeOf(b)
可以被b instanceof A
替代
对象的 property
TODO