JavaScript中你所不知道的Object(一)


  Object實在是JavaScript中很基礎的東西了,在工作中,它只有那么貧瘠的幾個用法,讓人感覺不過爾爾,但是我們真的了解它嗎?

  1. 當我們習慣用

var a = {
    name: 'tarol',
    age: 18
};
console.log(a.age); //18
a.age = 19;
console.log(a.age); //19

  初始化和訪問對象的時候,誰會在意這種方式也是合法的:

var a = {
  name: 'tarol',
  _age: 18,
  set age(value) {
    this._age = value;
  },
  get age() {
    return this._age;
  }
};
console.log(a.age); //18
a.age = 19;
console.log(a.age); //19 

  2. 當我們習慣用

function A() {
  this.name = 'tarol';
}

var a = new A();

function B() {
  this.age = 18;
}

B.prototype = a;

var b = new B();

console.log(b.name);  //tarol

  實現繼承的時候,誰會在意其實也可以這樣:

var a = {
  name: 'tarol'
};
var b = Object.create(a);
b.age = 18;

console.log(b.name); //tarol

  3. 當我們知道原型鏈以后,想惡作劇修改內置函數的原型,卻發現沒有辦法

var a = {};

Object.prototype = a;

console.log(Object.prototype === a);  //false

  如果你感興趣,那么我從頭說起:

  首先,JavaScript中的對象是什么?ES5中只給出一句話,對象是屬性的集合。它只是一個盒子,它能做什么,取決於盒子里有什么。

  那么,屬性是什么,一般看來,屬性是一個key, value對,這個說法是對的嗎?我們來剖析下屬性。

  從一個程序員的角度來說,屬性分為可通過JS調用的的和不可通過JS調用的。不可調用的叫做內部屬性,那么可調用的我們對應着叫外部屬性吧。內部屬性是JS解釋器實現各種接口的時候使用的算法中需要調用的屬性,舉個栗子,有個內部屬性叫[[Put]],這是一個內部方法,傳入屬性名和值,它的作用就是為屬性賦值。所以當我們使用a.age = 18的時候,實際就調用到了這個內部屬性。而外部屬性又分為兩種,一種是數據屬性,一種是訪問器屬性。上面的例一中,第二種方式給對象a添加了三個屬性,其中name、_age是數據屬性,age是訪問器屬性。當屬性是數據屬性的時候,屬性是key、value對的說法好像是對的,但當屬性是訪問器屬性的時候,這個說法好像有問題了,因為一個key對應的是一個setter和一個getter。所以,這個說法是錯的?

  其實,屬性不是我們看到的那樣,單單就一個key對應一個數據或者一個setter加一個getter。屬性還存在其他一些狀態,我們稱之為特性,無論是數據屬性還是訪問器屬性,都存在四個特性。數據屬性的特性為:[[Value]]、[[Writable]]、[[Enumerable]]、[[Configuration]],訪問器屬性的特性為:[[Get]]、[[Set]]、[[Enumerable]]、[[Configuration]]。其中[[Value]]、[[Get]]、[[Set]]相信已經很好理解了,[[Writable]]描述數據屬性是否可被重新賦值,[[Enumerable]]描述屬性是否可被for-in遍歷,[[Configuration]]描述屬性特性是否可被修改(一旦設置為false則不可以再修改此特性)。

  JS開放了三個接口用於設置和獲取屬性的特性,分別是Object.defineProperty、Object.defineProperties和Object.getOwnPropertyDescriptor。

var a = {
  name : 'tarol',
  age : 18,
  job : 'coder'
};
Object.defineProperty(a, 'name', {
  value: 'ctarol',
  writable: true,
  enumerable: true,
  configuration: true
});
Object.defineProperties(a, {
  age: {
    value: 19,
    writable: true,
    enumerable: true,
    configuration: true
  },
  job: {
    value: 'mental',
    writable: true,
    enumerable: true,
    configuration: true
  }
});
console.log(a.name);  //tarol
console.log(a.age); //19
console.log(Object.getOwnPropertyDescriptor(a, 'job')); //Object {value: "mental", writable: true, enumerable: true, configurable: true} 

  總的看來,屬性還是可以作為一個key, value對的,但這個value不是我們賦的值,而是整個屬性特性的集合,我們稱之為屬性描述

  外部屬性的問題解決了,內部屬性我們還只是蜻蜓點水般淺嘗輒止,所以接下來我們開始從內部屬性入手,對JS中的對象做一個更深刻的認識。以下是內部屬性的表格:

屬性名 用途 屬性類型

方法返回值

(僅適用方法)

他處引用

(僅適用數據)

他處賦值

(僅適用數據)

他處調用

(僅適用方法)

調用其他

(僅適用方法)

[[Prototype]] 對象原型 Object  

__proto__

etc.

     
[[Class]] 對象類型 String   Object.prototype.toString()      
[[Extensible]] 可否添加屬性 Boolean    

Object.seal(obj) --> false

Object.freeze(obj) --> false

Object.preventExtensions(obj) --> false

   
[[GetOwnProperty]] 返回自身指定的屬性描述 func('prop') 屬性描述    

Object.getOwnPropertyDescriptor(obj, 'prop')

[[GetProperty]]

 
[[GetProperty]] 返回原型鏈上指定的屬性描述 func('prop') 屬性描述       [[GetOwnProperty]]
[[HasProperty]] 返回原型鏈上是否有指定屬性 func('prop') Boolean       [[GetProperty]]
[[DefineOwnProperty]] 創建或修改自身的屬性描述 func('prop', desc, Boolean) Boolean    

Object.defineProperty(obj, 'prop', desc)

Object.defineProperties(obj,  descs)

 
[[DefaultValue]] 將對象轉換為對應的基礎類型 func(String/Number) String / Number      

toString()

valueOf()

[[Delete]] 刪除對象的屬性 func('prop', Boolean) Boolean       [[GetOwnProperty]]
[[CanPut]] 可否設置屬性的值 func('prop') Boolean       

[[GetOwnProperty]]

[[GetProperty]]

[[Extensible]]

[[Get]] 獲取屬性的值 func('prop') mixin       [[GetProperty]]
[[Put]] 設置屬性的值 func('prop', mixin, Boolean) Boolean      

[[CanPut]]

[[GetOwnProperty]]

[[GetProperty]]

[[DefineOwnProperty]]

   上面的表格稍顯晦澀,看不懂不要緊,我們來分下類。內部屬性中除了[[Class]]、[[DefaultValue]]用於展示信息以外,其他都是用來操作外部屬性的,可見對象的核心就是屬性。其中我列出[[CanPut]]和[[Put]]的算法實現,因為這兩個方法的實現涵蓋了基本所有的屬性操作和思想。

  [[CanPut]]:

  [[Put]]:

  前面提到過,我們使用a.age=18進行賦值的時候,調用的就是[[Put]]這個內部方法。由上圖算法可知,當對屬性賦值時,只要這個屬性不是原型鏈上的訪問器屬性,那么就會修改或產生自身的數據屬性,即不存在一種情況,就是修改原型鏈上的數據屬性。我們測試下:

var a = {
  name: 'tarol',
  _age: 18,
  set age(value) {
    this._age = value;
  },
  get age() {
    return this._age;
  }
};
var b = Object.create(a);
console.log(b.hasOwnProperty('name'));  //false
console.log(b.hasOwnProperty('_age'));  //false
console.log(b.hasOwnProperty('age'));  //false
b.name = 'okal';
b.age = 19;
console.log(b.hasOwnProperty('name'));  //true
console.log(b.hasOwnProperty('_age'));  //true
console.log(b.hasOwnProperty('age'));  //false
console.log(a.name);  //tarol
console.log(a.age);  //18

   由結果可知,我們在對name這個原型鏈上的數據屬性進行賦值時,實際上是重新創建了一個自身屬性,對原型上的數據屬性是沒有影響的。而調用訪問器屬性age的[[Set]]方法的時候,傳入的this也是當前的對象而不是訪問器屬性的擁有者,所以在當前對象上創建了一個自身屬性_age。

  好了,上面說的是通用的內部屬性,即Object類型的內部屬性,而像Boolean、Date、Number、String、Function等擁有更多的內部屬性,就留到下一篇再說。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM