ECMAScript 5.1 对象(Object)与原型

734 阅读11分钟

一、对象的定义

JS中的对象(Object)是一个基本数据类型,是一种复合值,它将很多值(原始值或者其他对象)聚合在一起,可通过名字访问这些值(属性)的无序集合。每个对象都有一个原型,原型可以为空。

Object每个属性既可以是一个命名的数据属性,也可以是一个命名的访问器属性,或者是一个内部属性:

  • 命名的数据属性(named data property)有一个名字与一个ECMAScript语言值和一个Boolean属性集合组成
  • 命名的访问器属性(named accessor property)由一个名字与一个或者两个访问器函数,和一个Boolean属性组成。访问器函数用于存取一个与该属性相关联的ECMAScript语言值
  • 内部属性(internal property)没有名字,且不能直接通过ECMAScript语言操作。内部属性的存在纯粹为了规范的目的

    1、Object类型里面数据属性具有特性,如下表1

    image
    image

2、 Object类型里面访问器属性的特性,如下表2

image
image

上面两个列表中属性的默认值为(如下表3)

image
image

每一个对象数据属性都具有表1的特性,每一个对象里面的方法属性都具有表3里面的特性,这些特性可以通过ECMAScript内置对象Object构造器方法Object.defineProperty来修改。如a.x属性的Enumerable被修改为false时,对象在for-in循环里将迭代不出该属性。其他的属性这里不详细讲解

3、Object内部属性和方法

所有的Object对象都必须具有下表的属性(表4)

image
image

[[prototype]]用于实现继承,[[Class]]用于区分对象的种类,也就是对象的名字。如String对象的[[Class]]值为"String"。创建对象时,对象的内部属性的[[Classs]]是除了"Arguments","Date","Array"....等内部对象名之外的值。所以我们在定义对象的时候不能重名和用内置对象的名字。

ECMAScript不同的对象的行为不同,对应实现上表这些属性的方式也是略有不同。但是必须都具备上述属性。不同的内置对象具有不同的属性,下表是只有在某些对象里面才具有属性(表5)

image
image

image
image

相对于Object内置对象Number,Date对象多实现[[primitiveValue]],Function对象多实现[[Code]]等属性,而通过Function.prototype.bind方法创建的Function对象还多实现[[TargetFunction]]方法,RegExp对象比Object多实现了[[Match]]属性等。而如果是可用于构造的对象则必须实现[[construct]]属性。

二、创建对象的方法

1、对象字面量创建

var obj = {
    name: 'lyq',
    age: 18
}
console.log(obj.name) //lyq

直接复制操作。不详细讲解,因为涉及到JS中的“=、{}”操作符的运行过程,详细讲解的话又是一个话题。

2、构造函数创建

2.1、JS原始(自带的)对象Object

var obj = new Object();
obj.name = 'lyq';
console.log(obj.name); //lyq

2.2、通过function 定义的构造函数创建

function Obj (name) {
    this.name = name;
    this.age = 18;
}
var obj = new Obj('lyq');
console.log(obj.name); //lyq
console.log(obj.age); //18

2.3、创建过程

2.3.1 通过JS原始(自带的)对象Object创建的过程

  • 如果提供了value则
    • 如果Type(value)是Object,则
      • value是原声ECMAScript对象(Array,Date这些),不创建新对象,简单返回value
      • 如果value是宿主对象,则采取动作和放回依赖实现的结果的方式可以使依赖于宿主对象(不纠结字面意思,简单的说就是返回宿主对象)
    • 如果Type(value)是String类型,返回ToObject(value).
    • 如果Type(value)是Boolean类型,返回ToObject(value).
    • 如果Type(value)是Number类型,返回ToObject(value).
  • 未提供参数value或者类型是Null或者Undefined
    • 令obj为一个新创建的原声ECMAScript对象
    • 设定obj的[[prototype]]内部属性为标准内置的Object的prototype。
    • 设定obj的[[Class]]内部属性为"Object“
    • 设定obj的[[Extensible]]内部属性为true
    • 设定obj的表1指定的所有内部方法
    • 返回obj。

2.3.2、通过function 定义的构造函数创建过程

根据表5创建Function对象,必须实现多些属性,其中包括Function对象需拥有 FormalParameterList 为可选参数列表,FunctionBody 为函数体,词法环境 Scope ,严格模式标记Strict。通过Function构造器创建Function对象的步骤如下:

  • 令argCount为传给这个函数调用的参数总数
  • 令P为空字符串
  • 如果argCount=0,令body为空字符串
  • 否则如果argCount=1,令body为那个参数
  • 否则argCount>1
    • 令firstArg为第一个参数
      • 令P为ToString(firstArg)
      • 令k为2
      • 只要k<argCount就重复
        • 令nextArg为第k个参数
        • 令P为之前的P值。字符串“,”(逗号),ToString(nextArg)串联结构
        • k递增
      • 令body为第k个参数
      • 令body为ToString(body)
      • 如果P不可解析为一个FormalParameterListopt,则抛出一个SyntaxError异常
      • 如果body不可解析为FunctionBody,则抛出一个SyntaxError异常
      • 创建一个新的 ECMAScript 原生对象,令 F 为此对象。
      • 依照 表1 描述设定 F 的除 [[Get]] 以外的所有内部方法
      • 设定 F 的 [[Class]] 内部属性为 "Function"。
      • 设定 F 的 [[Prototype]] 内部属性为指定的标准内置 Function 对象的 prototype 属性。
      • 设定 F 的 [[Get]] 内部属性。
      • 设定 F 的 [[Call]] 内部属性。
      • 设定 F 的 [[Construct]] 内部属性。
      • 设定 F 的 [[HasInstance]] 内部属性。
      • 设定 F 的 [[Scope]] 全局环境。
      • 设定 F 的 [[FormalParameters]] 内部属性为 P。
      • 设定 F 的 [[Code]] 内部属性为 body 解析后的 FunctionBody。
      • 设定 F 的 [[Extensible]] 内部属性为 true。
      • 令 argCount为 FormalParameterList 指定的形式参数的个数。
      • 以参数 "length",属性描述符 {[[Value]]: len, [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: false},false 调用 F 的 [[DefineOwnProperty]] 内部方法。
      • 令 proto 为仿佛使用 new Object() 表达式创建新对象的结果,其中 Object 是标准内置构造器名。
      • 以参数 "constructor", 属性描述符 {[[Value]]: F, { [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: true}, false 调用 proto 的 [[DefineOwnProperty]] 内部方法。
      • 以参数 "prototype", 属性描述符 {[[Value]]: proto, { [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: false}, false 调用 F 的 [[DefineOwnProperty]] 内部方法。
      • 如果body是严格模式代码。则令strict为true,否则令strict为false
      • 如果strict是true,按照严格模式下的代码解析判断是否抛出异常
      • 返回 F。

上述创建内部方法的步骤都没有详细标出,但是不碍理解对象的创建过程。对象创建中的内存分配是由构造函数的内部方法[[Construct]]负责的。该内部方法的行为是ECMAScript定义好的,所有的构造函数都是使用该方法来为新对象分配内存的。而如果被new的对象没有实现[[contruct]]这个属性将会抛出异常。接下来补充上面Function对象的创建过程中省略的创建[[construct]]属性的过程。

2.3.3、ECMAScript定义的Function对象创建[[construct]]属性的步骤

当以一个可能的空的参数列表调用函数对象 F 的 [[Construct]] 内部方法,采用以下步骤:

  • 令 obj 为新创建的 ECMAScript 原生对象。
  • 依照 8.12 设定 obj 的所有内部方法。
  • 设定 obj 的 [[Class]] 内部方法为 "Object"。
  • 设定 obj 的 [[Extensible]] 内部方法为 true。
  • 令 proto 为以参数 "prototype" 调用 F 的 [[Get]] 内部属性的值。
  • 如果 Type(proto) 是 Object,设定 obj 的 [[Prototype]] 内部属性为 proto。
  • 如果 Type(proto) 不是 Object,设定 obj 的 [[Prototype]] 内部属性为 标准的内置对象
  • 以 obj 为 this 值,调用 [[Construct]] 的参数列表为 args,调用 F 的 [[Call]] 内部属性,令 result 为调用结果。
  • 如果 Type(result) 是 Object,则返回 result。
  • 返回 obj

回到前面实例2,Obj 是一个Function对象,实现了[[contruct]]属性,可以通过 new Obj()来创建一个新的对象。而此时new Obj() 的过程就能new Object()原理一样。

三、原型与原型链

1、对象属性的增、删、查、改

1.1 增,即给对象添加属性,给对象的一个属性赋值,如果该属性原先不存在就会创建一个新属性,并将值赋予该属性

var obj = {};
console.log(obj.name); //undefined
obj.name = 'lyq';
console.log(obj.name); // lyq

1.2、删,通过delete操作可以删除掉对象的属性

 var obj = {
     name : 'lyl'
 };
 console.log(obj.name); //lyl
 delete obj.name; 
 console.log(obj.name); //undefined

1.3、查,对象为无序组合,访问对象的属性要通过属性名(key)访问,如果对象不存在对应的key值,则返回undefined值。可以使用“."操作符来访问,也可以通过"[]"操作符来访问

var obj = {
    name: 'lyq'
};
// 第一种方法
console.log(obj['name']); //lyq
//  第二种方法
console.log(obj.name); // lyq
console.log(obj.age); // undefined

1.4、改、更改对象属性的值

var obj = {
    name: 'lyq'
};
console.log(obj.name); // lyq
obj.name = 'lee';
console.log(obj.name); // lee

2、对象在原型上的操作

2.1、什么是原型和原型链

var person = function Person(){};
Person.prototype.x = 1var person = new Person();

根据上述创建对象的过程,当我通过new创建一个实例对象的时候,会先判断构造函数的prototype是否存在,同时是一个对象,如果是则将这个prototype对象赋值给新创建的对象的prototype。如果不存在和不是一个对象,则赋给新创建出来的对象一个标准内置的prototype。上述Person.prototype就是实例对象的原型。(通过上述函数对象的创建过程中能够看出,每个函数都会自动创建一个prototype对象,默认为标准的内置对象,用于满足函数会被当做构造函数的可能性。)

image
image

构造函数创建出来的实例对象共有一个prototype对象,而不是每个实例都复制一份prototype出来,每个实例对象都有一个指向原型的对象为proto
构造函数的prototype里面有一个指向构造函数本身的属性为constructor,标识每个实例是通过哪个构造函数创建出来的,如:

Person === person.__proto__.constructor //true;

prototype也是一个对象,也具有prototype属性,上例中Person.prototype的prototype属性是Object对象的内置prototype属性。而Object对象的内置prototype的prototype为null。对象的原型图如下

image
image

当要获取person对象的属性时,就会照图中蓝色的链来查找属性,这条蓝色的链即可理解为原型链(这逼其实就是js中的原型链)。

image
image

js在查找值的时候就是通过表1中的[[get]]方法来获取的,设置值是通过[[put]]方法来设置。这里不深入讲解这两个方法,有兴趣可以继续深入了解。ECMAScript中[[get]]会照着原型链获取属性,而[[put]]是直接将属性设置在person对象上,所以即使对象原型上存在的属性,在设置的时候如果对象本身不存在该属性,则直接创建新属性,而不会影响原型,如下图

image
image

所以当对对象进行增删改操作时不会影响到对象的原型和构造函数,当对对象进行查操作时,如果在自身获取不到,则会继续在原型链上查找,直到获取到该对象或到根原型为null为止

四、ECMAScript5 Object对象的方法

1、构造器属性方法

  • Object.prototype —— 对象原型
  • Object.getPrototypeOf(o) —— 返回o的内部属性值
  • Object.getOwnPropertyDescriptor ( O, P ) —— 返回对象O上面P属性的描述符
  • Object.create ( O [, Properties] ) —— 创建一个拥有指定原型和若干属性的对象
  • Object.defineProperty ( O, P, Attributes ) —— 在对象上添加或者修改一个属性,并返回这个对象,其中obj为要修改的对象,p为要修改或者添加的属性,Attributes将被定义或者修改的属性的描述符
  • Object.defineProperties ( O, Properties ) —— 添加或者修改多个属性,操作多个属性不能更改描述符
  • Object.seal ( O ) —— 密封对象
  • Object.freeze ( O ) —— 冻结对象中的某个属性,使该属性无法进行增删改查
  • Object.preventExtensions ( O ) ——禁止对象不能扩展,这样对象就永远不能添加更改属性
  • Object.isSealed ( O ) —— 判断一个对象是否被密封
  • Object.isFrozen ( O ) —— 判断一个对象是否被冻结
  • Object.isExtensible ( O ) —— 判断对象是否可以扩展

2、Object 的prototype上的属性

  • Object.prototype.constructor —— 标准内置的Object构造器
  • Object.prototype.toString ( ) —— 返回对象的字符串表示
  • Object.prototype.toLocaleString ( ) —— 返回对象的本地字符串表示
  • Object.prototype.valueOf ( ) —— 返回指定对象的原始值
  • Object.prototype.hasOwnProperty (V) —— 返回一个布尔值,表示对象是否包含有V这个属性,该属性为对象本身属性,不包括原型链上的属性
  • Object.prototype.isPrototypeOf (V) —— 返回一个布尔值,表示对象是否包含V这个属性,包含自身和原型链上的
  • Object.prototype.propertyIsEnumerable (V) —— 返回一个布尔值,表示V属性是否可以枚举

本文的参考文章

ECMAScript 5.1 中文文档

JavaScript深入系列