搞懂原型链

搞懂原型链

Apr 11, 2022
前端, JavaScript

与一般基于类的面向对象语言(C++、Java)不同,JavaScript 是基于原型的语言。

原型 [[Prototype]] #

打开浏览器的控制台,输入 let b = {"hello": "world"} ,然后查看 b ,你会发现一个 [[Prototype]] 属性。这是每个对象都有的隐藏属性,它要么为 null,要么是对另一个对象的引用,那个对象就被称为 b原型(proto type)。

当我们从一个对象中读取一个缺失的属性时,JavaScript 会自动从对象的原型中获取该属性,如果原型上也没有该属性,就依次层层向上搜索,直到找到属性或者遇到原型对象为 null,即原型链的尾端。这种属性查找的方式被称为原型链(prototype chain)。

虽然有这个属性,但是你却没有办法通过 b.[[Prototpye]] 进行使用,因为它是隐式的,只能在文档中用于表示指向 b 的原型,而在代码中要通过 b.__proto__ 进行显式访问。

非标准的 __proto__ #

[[Prototpye]]__proto__ 并不是等价关系,从上图也可以看出,__proto__ 只是 b.[[Prototype]] 的 getter/setter。然而 __proto__ 被认为是一种非标准的访问 [[Prototype]] 的方式,从 ES6 开始,可以通过 Object.getPrototypeOf()Object.setPrototypeOf() 来访问 b.[[Prototype]]

还有需要注意的是,当使用 for .. in .. 遍历对象属性的时候,也会将其继承自其他对象的属性一并遍历。而通过 Object.keys() 得到的属性只有对象自己的属性。还可以通过 obj.hasOwnProperty(prop) 来判断属性是自己的还是继承的。

函数的 prototype 属性 #

现在还要介绍一种名为 prototype (不是 [[Prototype]])的常规属性,这就开始有点乱了,所以请先耐心看完再思考。

__proto__每个对象都有的一个属性,而 prototype函数才会有的属性。JavaScript 的每个函数都有一个 prototype 属性,默认的 prototype 是一个包含 constructor 属性的对象,并且 constructor 指向了自身。

let f = function(){}
f.prototype //{constructor: f}

let a = new f()
a.__proto__ //{constructor: f}

默认情况下,JavaScript 在 new F() 时通过访问一个函数的 F.prototpye.constructor 来获取一个对象的构造函数,然后生成新的对象。这样的话,所有从 new f() 得到的对象,都继承了 F.prototype ,并将其引用赋值给 [[Prototype]]

a.__proto__ === f.prototype // true

这应该也一定程度解释了 Object 的类型也需要是 function。而 newclass 只是一个模仿类思想的语法糖。

typeof Object //'function'

你可能听说过 Object.create() 方法,newObject.create() 不一样。new 就使用 f() 作为构造函数,创建的对象包含了 f() 的所有属性和方法,即实现了继承,而 Object.create(proto, propertiesObject) 的参数可以另外指定新对象的 [[Prototype]] 和其它 properties,未指定的东西就不会获得,没有继承的概念,更像是拷贝一个恢复了出厂设置的自己。如果 protonull ,创建的对象就没有原型。

let animal = {
  eats: true
};

let rabbit = Object.create(animal);

let p = Object.create(null);

很多内建对象比如 Function、Array、Map 等都实现了自己的 prototype,不是只有单纯的 constructor 属性,我们绝大部分对内建类型的数据进行操作的方法基本都来自其 prototype 属性中的实现。

本文共 1061 字,上次修改于 Jan 28, 2024,以 CC 署名-非商业性使用-禁止演绎 4.0 国际 协议进行许可。

相关文章

» JS 加号运算符的运用

» JS 非空判断

» WebSocket 是什么?

» 跨域相关问题

» 了解下 MobX 概念