对象和原型

对象 Objects

对象是一个用 {} 括起来的 property name-value 对的集合。property 指属性,分数据属性(data property,将键与值相关联)和访问属性(accessor property,将键与两个访问器函数 get 和 set 相关联,以获取或者设置值)。value 指的是 property 的值,可以是各种对象。

ECMAScript 认可两类对象:原生(Native)对象和宿主(Host)对象,原生对象属于语言,而宿主对象由环境提供,比如文档对象、DOM 等。原生对象具有松散和动态的命名属性(对于某些实现的内置对象子类别而言,动态性是受限的)。对象的命名属性用于保存值,该值可以是指向另一个对象的引用,也可以是一些基本的数据类型,比如:String、Number、Boolean、Null 或 Undefined。

构造器 Constructor

JavaScript 开始没有class,对象通过构造器创建。预定义的两大构造器:

对象初始化三种方式:

// new 实例化 
const obj1 = new Object() 

// create 创建 
const obj2 = Object.create() 

// 字面量 
const obj3 = {}

对象赋值

对象的属性可以通过为该属性赋值来创建,或重新赋值。

const obj = {} 
obj.x = 1 
obj['x'] = 2 

对象值读取

通过属性访问器(property accessor)读取。

const val = obj.x 

原型 prototypes

每个对象都有一个内置属性,prototype,叫做此对象的原型,因为原型本身也是对象,所以原型也可能有它自己原型,这样就构成了所谓的原型链(prototype chain)。原型链终止于链中原型为 null 的对象。

ECMAScript 为 Object 类型定义了一个内部 [[prototype]] 属性。这个属性不能通过脚本直接访问,但在属性访问器解析过程中,需要用到这个内部 [[prototype]] 属性所引用的对象链。

function Obj1(param) { 
    // 给创建的对象添加一个名为 x 的属性并将赋值形参 param 
    this.x = param 
} 

function Obj2(param) { 
    // 给创建的对象添加一个名为 y 的属性并将赋值形参 param 
    this.y = param 
} 

// 用 Obj1 类的实例替换与 Obj2 类的实例相关联的原型。而且,为 Obj1 构造函数传递了参数 1 
Obj2.prototype = new Obj( 1 ) 
// 将2作为构造函数的第一个参数,创建一个 Obj2 的实例,并赋给变量 a 
const a = new Obj2( 2 ) 
// 被变量 a 所引用的 Obj2 的实例拥有一个原型链。该链中的第一个对象是在创建后被指定给 Obj2 构造函数的 prototype 属性的 Obj1
的一个实例。 
// Obj1 的实例也有一个原型,即与 Object.prototype 所引用的对象对应的默认的 Object 对象的原型。 
// 最后, Object.prototype 有一个值为 null 的原型,这条原型链到此结束。 
// 因为 a 所引用的 Obj2 的实例有一个名为 y 的属性,因此被设置为 2 的该属性的值被赋给了变量 val。 
let val = a.y 
// 不能从 Obj2 实例自身中读取到 x 命名属性值,因为该实例没有这个属性。 
// 然而,在该实例中查找相应的命名属性失败后,解释程序会继续检查其原型对象。
// 而该实例的原型对象是 Obj1 的实例有一个名为 x 的属性并且值为 1,所以这个属性访问器最后会取得值 1。 

val = a.x
// 当对象的 a 中没有 toString 属性时,会搜索其原型对象,而当原型对象中不存在该属性时,则会继续搜索原型的原型。
// 而原型链中最终的原型是 Object.prototype,这个对象确实有一个 toString 方法,因此该方法的引用被返回。 
val = a.toString 
val = a.z 
// 返回 undefined,因为在搜索原型链的过程中,直至 Object.prototype 的原型
null,都没有找到任何对象有名为 z 的属性,因此最终返回 undefined。 
a.x = 3 
// 此时 a 自身也会创建一个名为 x 的属性。这时属性访问器不会再进一步搜索原型链,Obj1 实例的 x
属性值也不会被修改,这种行为叫作遮蔽(shadowing)属性。

原型操作

构造器对象可以通过一个公共的 prototype 属性,来与内部的 [[prototype]] 属性对应的原型对象进行访问和设置。访问对象原型的标准方法是 Object.getPrototypeOf(),也可以用私有属性__proto__。

通过 Object.hasOwn() 静态方法,可以区分属性是否是自身属性(own properties):

const obj = { x: 1 } 
Object.hasOwn(obj, 'x') 
// 不推荐 hasOwnProperty 方式,其使用的是原型链
Object.prototype.hasOwnProperty() 
obj.hasOwnProperty('x')