你不了解的JS笔记 - 第二部分- 原型

JS中的对象有一个特殊的[[Prototype]]内置属性,其实就是对于其他对象的引用。几乎所有的对象在创建时[[Prototype]]属性都会被赋予一个非空的值

当试图引用对象的属性时就会触发[[Get]]操作,对于默认的[[Get]]操作来说,第一步是检查对象本身是否有这个属性,如果有的话就使用它。但是如果不在,就需要使用对象的[[Prototype]]链了。对于默认的[[Get]]操作来说,如果无法在对象本身找到需要的属性,就会继续访问对象的[[Prototype]]链。这个过程会持续到找到匹配的属性名或者查找完整条[[Prototype]]链,如果是后者的话,[[Get]]操作的返回值就是undefined

使用for...in遍历对象时,原理和查找[[Prototype]]链类似,任何可以通过原型链访问到并且是enumerable的属性都会被枚举

使用in操作符来检查属性在对象中是否存在时,同样会查找对象的整条原型链,无论属性是否可枚举

所有普通的[[Prototype]]链最终都会指向内置的Object.prototype。由于所有普通对象都源于这个Object.prototype对象,所以它包含JS中许多通用的功能

myObject.foo = 'bar'

如果myObject对象中包含名为foo的普通数据访问属性,这条赋值语句只会修改已有的属性值

如果foo不是直接存在于myObject中,[[Prototype]]链就会被遍历,类似[[Get]]操作,如果原型链上找不到foo,foo就会被直接添加到myObject上

如果属性名既出现在myObject中也出现在myObject的[[Prototype]]链上层,那么就会发生屏蔽,myObject中包含的foo属性会屏蔽原型链上层的所有foo属性,因为myObject.foo总是会选择原型链中最底层的foo属性

如果foo不直接存在于myObject中而是存在于原型链上层时myObject.foo = 'bar'会出现三种情况:

  • 如果在[[Prototype]]链上层存在名为foo的普通数据访问属性并且没有被标记为只读,那就会直接在myObject中添加一个名为foo的新属性,它是屏蔽属性
  • 如果在[[Prototype]]链上层存在foo,但是它被标记为只读,那么无法修改已有属性或者在myObject上创建屏蔽属性,如果运行在严格模式下,代码会报错,否则,这天赋值语句会被忽略
  • 如果在[[Prototype]]链上层存在foo并且它是一个setter,那就一定会调用这个setter。foo不会被添加到myObject,也不会重新定义foo这个setter

如果希望在第二或三种情况下也屏蔽foo,那就不能使用=操作符来赋值,而是使用Object.defineProperty()来向myObject添加foo


在JS中,类无法描述对象的行为,因为根本不存在类,对象直接定义自己的行为,JS中只有对象

所有函数默认都会拥有一个名为prototype的公有并且不可枚举的属性,它会指向另一个对象,这个对象通常被称为该函数的原型,因为我们通过名为Xxx.prototype的属性引用来访问它

通过调用new Xxx()创建的每个对象最终被[[Prototype]]链接到这个Xxx.prototype对象

在面向类的语言中,类可以被实例化多次,但是JS没有类似的复制机制,不能创建一个类的多个实例,只能创建多个对象,它们[[Prototype]]关联的是同一个对象。但是在默认情况下并不会进行赋值,因此这些对象之间并不会完全失去联系,它们是互相关联的

new函数调用实际上并没有直接创建关联,这关联只是一个意外的副作用,它只是间接完成了我们的目标:一个关联到其他对象的新对象

在JS中,我们并不会讲一个对象复制到另一个对象,只是将它们关联起来,这个机制通常被称为原型继承,也可以叫做委托

函数本身并不是构造函数,但当你在普通函数调用前面加上new关键字之后,就会把这个函数调用变成一个构造函数调用。

new会劫持所有普通函数并用构造对象的形式来调用它

Xxx.prototype默认有一个公有且不可枚举的属性.constructor,这个属性引用的是对象关联的函数。Xxx.prototype的.constructor属性只是Xxx函数在声明时的默认属性,如果创建了一个新对象并替换了函数默认的.prototype对象引用,那么新对象并不会自动获得.constructor属性。

实际上,对象的.constructor属性默认指向一个函数,而这个函数也有一个叫做.prototype的引用指向这个对象。

constructor并不是一个不可变的属性,它是不可枚举的,但是它的值是可写的,可以给任意[[Prototype]]链中的任意对象添加一个名为constructor的属性或者对其进行修改,可以任意对其赋值


调用Object.create()会凭空创建一个新对象并把新对象内部的[[Prototype]]关联到指定的对象

Object.setPrototypeOf()可以用标准且可靠的方法来修改对象的[[Prototype]]关联

检查一个实例的继承祖先通常被称为内省或者反射

a instanceof Foo

instanceof操作符的左操作数是一个普通的对象,右操作数是一个函数,instanceof回答的问题是:在a的整条[[Prototype]]链中是否有Foo.prototype指向的对象

Foo.prototype.isPrototypeOf(a)

在a的整条[[Prototype]]链中是否出现过Foo.prototype

也可以直接获取一个对象的[[Prototype]]链

Object.getPrototypeOf(a) === Foo.prototype

绝大多数浏览器也支持一种非标准方法来访问内部的[[Prototype]]属性:

a.__proto__ === Foo.prototype

这个__proto__属性引用了内部的[[Prototype]]对象,这个属性并不存在于正在使用的对象中,而是存在于内置的Object.prototype中,并且是不可枚举的,它更像一个getter/setter,是可设置属性,但通常不需要修改已有对象的[[Prototype]]


[[Prototype]]机制就是存在于对象中的一个内部链接,它会引用其他对象,这个链接的作用是:如果在对象上没有找到需要的属性或者方法引用,引擎就会继续在[[Prototype]]关联的对象上进行查找,同理,如果在后者中也没有找到需要的引用就会继续查找它的[[Prototype]],以此类推,这一系列对象的链接被称为原型链

Object.create()会创建一个新对象,并把它关联到我们指定的对象,这样可以充分发挥[[Prototype]]机制的威力并且避免不必要的麻烦

Object.create(null)会创建一个拥有空[[Prototype]]链接的对象,这个对象无法进行委托。这些空[[Prototype]]对象通常被称作字典,他们完全不会受到原型链的干扰,非常适合用来存储数据

posted @ 2025-06-15 11:47  永生辉皇  阅读(4)  评论(0)    收藏  举报