1. 程式人生 > >物件屬性的遍歷(一)遍歷物件基礎的三個API

物件屬性的遍歷(一)遍歷物件基礎的三個API

       我們在做JavaScript開發的時候,可能會遇到這樣的情況,在我們自己的程式碼中,某個物件objC引用一個全域性物件objB的某個屬性propB。然後讀取屬性propB之後,再在我們自己的物件中,對propB的值做些加工。

// 全域性物件B ,包含屬性b
const objB = {
    propB : 100;
}
//類A,
class A(){
    constructor(){
        this.tmp1 = objB.propB ;
        this.tmp2 = 20;
    }
    func1(){
        return this.tmp1 + this.tmp2;
    } 
}

var objC = new A();objC.func1(); // 120;
       暫時來看這段程式碼是能夠正常執行的,無非是類A的例項objC和全域性物件objB之間有著一種隱含的強耦合關係罷了。但是這裡有個問題,因為objB是全域性的,這代表著,其他的程式碼可以隨意的更改objB,一旦在某段程式碼中,刪除了propB屬性的話,之後再執行objC.func1();就會出現錯誤。
       暫且不考慮全域性的物件objB,在編寫Class A的時候,我們就應該在引用屬性之前,先檢視一下,這個屬性是否存在,一旦不存在,就log一個錯誤,這樣便於我們debug。所以,類A應該增加一段程式碼:
//類A,
class A(){
    constructor(){
        //這裡應該先判斷下,objB的PropB是否存在,如果不存在的話,增加一個預設值0。
        if(objB.keys(PropB)){
           this.tmp1 = objB.propB || '0';  // 0是預設值,防止objB的propB屬性值是undefined。
        }else{
           console.log('objB物件的propB屬性不存在!');
        }
        this.tmp2 = 20;
    }
    func1(){
        return this.tmp1 + this.tmp2;
    } 
}
       上面的程式碼只是一個小demo,起重點並不在與程式碼內部的邏輯,而在於書寫程式碼時,我們往往需要一種手段來遍歷某個物件內部的所有屬性。幸運的是ECMAScript標準介面裡,包含這種手段。

Object.keys()

       Object.keys()是ECMAScript 5版本中,為Object新增的內建方法,這個方法會返回一個數組,陣列的成員是物件中所有可以遍歷的屬性的鍵名,需要注意的是,Object.keys只能返回物件自身的可遍歷屬性鍵名,無法返回物件通過繼承獲取來的屬性鍵名。  
       所以當我們再次引用一個陌生物件的時候,我們就可以用這個介面來,獲取這個物件的作者想要暴露給我們的所有屬性名(物件的開發者可以設定某些屬性為“不可遍歷”屬性,這樣Object.keys()也無法獲取這些屬性,從而達到了隱藏的效果),對比一下設計文件,如果我們找到了所需的屬性,就可以放心引用了。
var demo1 = {
   prop1 : "prop1",
   prop2 : 100,
   prop3 : null;
}
Object.keys(demo1);
//得到結果如下:
// ["prop1","prop2","prop3"]

Object.values()

var demo1 = {
   prop1 : "prop1",
   prop2 : 100,
   prop3 : null;
}
Object.values(demo1);
//得到結果如下:
// ["prop1",100,null]
      雖然這個API設計,看起來沒什麼新意,但實際上還是有很多需要注意的地方的。
      首先,由於返回陣列中,包含的是物件屬性的鍵值,所以,陣列成員在陣列中的位置是受物件中鍵名的影響的,例如:
   
const demo2 = {
    1 : "a",
    3 : "c",
    2 : "b"
};
Object.values(demo2);
//返回結果如下:
//["a","b","c"]
       我們發現,物件中屬性的鍵名不是字串,而是數字。那麼在Object.values()介面呼叫這個物件時,返回陣列成員會按照,成員對應的屬性名的值,從小到大排列,而不是按照物件中的書寫順序排列。

其次,對於Symbol值作為鍵名的屬性,Object.values()是無法遍歷的。

Object.values({ [Symbol()]: 123, foo: 'abc' });
// ['abc']
      再次,和Object.kes一樣,Object.values()同樣不能遍歷,物件的不可遍歷屬性。
const obj = Object.create({}, {p: {value: 42}});  //屬性p是後加的屬性,預設不可遍歷
Object.values(obj) // []
const obj = Object.create({}, {p:
  {
    value: 42,
    enumerable: true  // 此時設定p屬性為可遍歷屬性
  }
});
Object.values(obj) // [42] values()介面得到了p屬性的屬性值
      最後,要提醒大家注意的是,Object.values();介面是為Object設計的,所以接收到引數後,會在內部自動將引數轉換成Object型別。如果接收的引數是字串,Object.values();返回的將使由字串中,每個字元組成的陣列。如果接收到的是null或者undefined值的話,就會報錯。

Object.entries()

      現在我們有了Object.keys()介面來獲得由屬性鍵名組成的陣列,有了Object.values()介面來獲得由屬性鍵值組成的陣列。自然而然的就會想到有介面會獲得二者的結合。這個介面就是Object.entries()。它會法返回一個數組,成員是引數物件自身的(不含繼承的)所有可遍歷(enumerable)屬性的鍵值對陣列。
const obj = { foo: 'bar', baz: 42 };
Object.entries(obj)
// [ ["foo", "bar"], ["baz", 42] ]
 

      Object.entries()介面的行為和Object.values()介面的行為基本一致。例如,對於屬性鍵名是Symbol值型別的屬性,Object.entries()介面就不會得到該屬性的遍歷結果:

Object.entries({ [Symbol()]: 123, foo: 'abc' });
// [ [ 'foo', 'abc' ] ]

      顯而易見,我們可以用Object.entries()介面來遍歷物件的屬性:

let demo3 = {
    prop1 : 1,
    prop2 : 2,
    prop3 : 3
};
// 用for...of迴圈,呼叫Object.entries()介面,將其結果陣列中的每項成員用JSON形式展示打印出來。
for(let[key,value] of Object.entries(demo3)){
   console.log(`${JSON.stringify(key)} : ${JSON.stringify(value)}`);
}
//得到結果如下:
// "prop1" : 1
// "prop2" : 2
// "prop3" : 3
      有時候,我們希望遍歷物件屬性的過程是非同步的,簡言之,我們希望用Generator函式的形式來遍歷,屬性。那麼我們就需要用Object.keys()介面,手動實現一個Oject.entries():
function * myEntries(obj){
    for(let key of Object.keys(obj)){
       yield [key,obj[key]];
    }
}
      Object.entries()介面還有一個用途就是將物件轉換為Map解構:
var demo4 = {
    prop1 : "prop1",
    prop2 : "prop2",
    prop3 : "prop3",
    prop4 : "prop4"
};
var myMap = new Map(Object.entries(demo4));
//得到結果:
{prop1 : "prop1",prop2 : "prop2",prop3 : "prop3",prop4 : "prop4",}