深入瞭解JavaScript中的物件

romin發表於2019-03-01

大家都知道迴圈一個物件,可以用for ..in..來做,比如:

var myObject = {foo: `bar`};
for(var name in myObject) {
   // 這裡能得到你想要的屬性名
   // 也可以得到對應屬性值: myObject[propertyName]
}
複製程式碼

這是顯而易見的,但是如果有人在這個物件的原型上新增了一些屬性或方法呢?

Object.prototype.baz = `quux`;
for (var name in myObject) {
  alert(name); //foo   baz
}
複製程式碼

myObject也會跟著改,那麼別人改動原型,對自己的程式碼造成很大的影響,結果肯定是不能接受的。

既然物件存在繼承,那麼我們必須考慮哪些是我自己寫的(私有的),哪些是從原型上繼承的,單純的for ..in..迴圈已經不能滿足需求 了。

hasOwnProperty就是用來判斷是否是私有屬性,而不是繼承過來的

myObject.hasOwnProperty(`foo`); // true
myObject.hasOwnProperty(`baz`); // false
複製程式碼

如果僅僅想要私有的屬性或方法,我們就可以這麼寫了

for(var name in myObject) {
  if(myObject.hasOwnProperty(name)){
    alert(name);// `foo`
  }
}
複製程式碼

for...in..迴圈,中間還要做判斷,是不是很麻煩,不過我們有專門的方法來解決這些麻煩事。

Object.keys(myObject);//[`foo`] 返回一個物件,是一個私有屬性的集合 
複製程式碼

說到這裡,肯定還有一些人會疑惑,既然for...in能遍歷出物件自身的屬性或方法,也能遍歷出繼承過來的屬性或方法,那麼Object類上還有很多的屬性和方法的,比如說toString,這是被myObject繼承的,但是沒有被for...in遍歷到啊。

`toString` in myObject; //true
myObject.toString();//"[object Object]"
複製程式碼

這裡引入一個概念“可列舉”enumerable,這個概念的最初目的,就是讓某些屬性可以規避掉for...in操作,不然所有內部屬性和方法都被遍歷到,就可以放肆的更改了。

寫到這裡,你應該會明白了,上面提到的toString都是不可列舉的。你會不會有這樣的問題?如果我在物件裡面寫了一些屬性,但是不想被遍歷出來,是不是也可以把這些屬性變成不可列舉的呢?當然是可以的了。

Object.defineProperty(myObject, `name`, {
    value : `cover`,
    enumerable: false//不可列舉
})
Object.prototype.baz = `quux`;
// myObject:{foo: "bar",  name: "cover"}
for (var name in myObject) {
  alert(name); //foo   baz
}
複製程式碼

通過這種方法定義出來的屬性或方法就可以變成不可列舉。

新的知識點:Object.defineProperty,顧名思義,就是為物件定義屬性。
定義:直接在一個物件上定義一個新的屬性,或者是修改已存在的屬性。最終這個方法會返回該物件。

引數

  • object必需。 要在其上新增或修改屬性的物件。 這可能是一個本機JavaScript物件(即使用者定義的物件或內建物件)或DOM物件。
  • propertyname 必需。 一個包含屬性名稱的字串。
  • descriptor 必需。 屬性描述符。 它可以針對資料屬性或訪問器屬性。

其中descriptor的引數值是我們需要重點關注的,該屬性可設定的值有:

  • [value] 屬性的值,預設為undefined
  • [writable] 該屬性是否可寫,如果設定成 false,則任何對該屬性改寫的操作都無效(但不會報錯),對於像前面例子中直接在物件上定義的屬性,這個屬性該特性預設值為為 true
Object.defineProperty(myObject, `age`, {
    value : 18,
    writable:false
});
myObject.age =19;
console.log(myObject.age);//18;
複製程式碼
  • [configurable]如果為false,則任何嘗試刪除目標屬性或修改屬性的行為將被無效化,對於像前面例子中直接在物件上定義的屬性,這個屬性該特性預設值為為 true
Object.defineProperty(myObject, "name", {
    value:"gogo" ,
    configurable: false 
});  
delete myObject.name; 
console.log(myObject.name);// 輸出 gogo
myObject.name = "gg";
console.log(myObject.name); // 輸出gogo
複製程式碼
  • [enumerable] 是否能在for-in迴圈中遍歷出來或在Object.keys中列舉出來。對於像前面例子中直接在物件上定義的屬性,這個屬性該特性預設值為為 true。

在呼叫Object.defineProperty()方法時,如果不指定, configurableenumerablewritable特性的預設值都是false,這跟之前所 說的對於像前面例子中直接在物件上定義的屬性,這個特性預設值為為true。並不衝突,如下程式碼所示:

//呼叫Object.defineProperty()方法時,如果不指定
var someOne = { };
someOne.name = `coverguo`;
console.log(Object.getOwnPropertyDescriptor(someOne, `name`));
//輸出 Object {value: "coverguo", writable: true, enumerable: true, configurable: true}

//直接在物件上定義的屬性,這個特性預設值為為 true
var otherOne = {};
Object.defineProperty(otherOne, "name", {
    value:"coverguo" 
});  
console.log(Object.getOwnPropertyDescriptor(otherOne, `name`));
//輸出 Object {value: "coverguo", writable: false, enumerable: false, configurable: false}
複製程式碼

Object.getOwnPropertyDescriptor方法會返回某個物件屬性的描述物件(descriptor)。ES2017 引入了Object.getOwnPropertyDescriptors方法,返回指定物件所有自身屬性(非繼承屬性)的描述物件。

  • [get] 修改器,一旦目標物件訪問該屬性,就會呼叫這個方法, 並返回結果。預設為undefined
  • [set] 獲取器,一旦目標物件設定該屬性,就對呼叫這個方法。預設為undefined
    其實,在vue中就用到了 Object.defineProperty方法,主要用於雙向資料繫結,下一篇文章用講到,(請勿拍磚。。)

扯得有點遠了,我們再回來看,如何判斷物件上的屬性是否可列舉?

  • obj.propertyIsEnumerable(prop)
 Object.defineProperty(myObject, "name", {
    value:"gogo" ,
    configurable: false ,
    enumerable:false,
});
myObject.age =18
myObject.propertyIsEnumerable(`name`); //false
myObject.propertyIsEnumerable(`age`);//true
複製程式碼

另外介紹一個方法:

  • Object.getOwnPropertyNames(obj)

Object.getOwnPropertyNames返回一個陣列,包含物件自身的所有屬性(不含 Symbol 屬性,但是包括不可列舉屬性)的鍵名。是不是和Object.keys(obj)有點像呢?

最後,我們再來總結一下,有四個操作會忽略enumerablefalse的屬性。

  • for...in迴圈:只遍歷物件自身的和繼承的可列舉的屬性。
  • Object.keys():返回物件自身的所有可列舉的屬性的鍵名。
  • JSON.stringify():只序列化物件自身的可列舉的屬性。
  • Object.assign(): 忽略enumerablefalse的屬性,只拷貝物件自身的可列舉的屬性。

相關文章