ECMAScript 5.1 物件(Object)與原型

Hausen發表於2017-10-15

一、物件的定義

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深入系列

相關文章