對象----《你不知道的JS》

最近在拜讀《你不知道的js》,而此篇是對於《你不知道的js》中對象部分的筆記整理,但願能有效的梳理,而且深刻理解對象算法

1、語法

對象兩種定義形式:聲明(文字)形式、構造形式json

聲明(文字)形式

var myObj = {
   key: value,
   ...
}
複製代碼

構造形式

var myObj = new Object();
myObj.key = value;
複製代碼

構造形式與文字形式生成的對象同樣數組

區別:文字聲明中能夠添加多個鍵/值對,構造形式中必須逐個添加屬性安全

2、類型

在JavaScript中一共有6中主要類型:bash

  • string
  • number
  • boolean
  • null
  • undefined
  • object

注意:簡單基本類型(string、boolean、number、null、undefined)自己不是對象。null有時會被當作一種對象類型,typeof null返回‘object’。實際上,null是基本類型函數

內置對象

內置對象:對象子類型ui

  • String
  • Number
  • Boolean
  • Object
  • Function
  • Array
  • Date
  • RegExp
  • Error

3、內容

對象的內容是由一些存儲在特定命名位置的(任意類型的)值組成,咱們稱爲屬性。this

var myObject = {
    a: 2
};
myObject.a;// 2
myObject['a']; // 2
複製代碼

若要訪問myObject中a位置上的值,需使用.操做符或[]操做符。.a語法稱爲屬性訪問(最多見的方式),['a']語法稱爲鍵訪問spa

區別:.操做符要求屬性名知足標識符的命名規範,而[".."]語法能夠接受任意UTF-8/Unicode字符串做爲屬性名,如名稱爲「Super-Fun」的屬性,就必須使用["Super-Fun"]語法訪問prototype

因爲['..']語法使用字符串來訪問屬性,因此能夠在程序中構造這個字符串,如:

var myObject = {
    a: 2
};
var idx;
if(wantA) {
    idx = "a";
}
console.log(myObject[idx]); // 2
複製代碼

在對象中,屬性名永遠都是字符串。若使用string(字面量)之外的其餘值做爲屬性名,那它首先會被轉換爲一個字符串。

var myObject = {};
myObject[true] = 'foo';
myObject[3] = "bar";
myObject[myObject]="baz";
myObject["true"]; // 'foo'
myObject['3']; //"bar"
myObject["object object"]; // "baz"
複製代碼

一、可計算屬性名

ES6增長了可計算屬性名:

var prefix = "foo";
var myObject = {
    [prefix + "bar"]: "hello",
    [prefix + "baz"]: "world"
};
myObject["foobar"];// hello
myObject["foobaz"];// world
複製代碼

二、屬性與方法

在其餘語言中,屬於對象(也稱爲「類」)的函數一般被稱爲「方法」,有時屬性方位也稱爲「方法訪問」。

不管返回值是什麼類型,每次訪問對象的屬性就是屬性訪問。若屬性訪問返回一個函數,那它也並非一個「方法」。屬性訪問返回的函數和其餘函數沒有任何區別

function foo() {
    console.log("foo");
}
var someFoo = foo; // 對foo變量的引用
var myObject = {
    someFoo: foo,
}
foo; // function foo() {}
someFoo; //  function foo() {}
myObject.someFoo; // function foo() {}
複製代碼

someFoo與myObject.someFoo只是對於同一個函數的不一樣引用,並不能說明這個函數是特別的或「屬於」某個對象

三、數組

數組也支持[]訪問形式,經過數值下標,即存儲位置(索引)訪問,是非負整數,如:0,42:

var myArray = ["foo", 42, "bar"];
myArray.length; // 3
myArray[0]; // "foo"
myArray[2]; // "bar"
複製代碼

數組也是對象,也能夠給數組添加屬性:

var myArray = ["foo", 42, "bar"];
myArray.baz = "baz";
myArray.length; // 3
myArray.baz; // "baz"
複製代碼

雖然添加了命名屬性,數組的length值並未發生變化。 若你試圖向數組添加一個屬性,但屬性名「看起來」像數字,那它會變成一個數值下標:

var myArray = ["foo", 42, "bar"];
myArray["3"] = "baz";
myArray.length; // 4
myArray[3] = "baz";
複製代碼

四、複製對象

function anotherFunction() {/*..*/}
var anotherObject = {
    c: true
}
var anotherArray = [];
var myObject = {
    a: 2,
    b: anotherObject, // 引用,不是複本
    c: anotherArray, // 另外一個引用
    d: anotherFunction
}
anotherArray.push(anotherObject, myObject);
複製代碼

如何準確地表示myObject的複製呢?

首先判斷它是淺複製仍是深複製。

1)淺複製 複製出的新對象中a的值會複製就對象中a的值,即2,但新對象中b、c、d三個屬性其實只是三個引用,和就對象中b、c、d引用的對象同樣 2)深複製 除了複製myObject之外還會複製anotherObject和anotherArray

問題:anotherArray引用了anotherObject和myObject,因此又須要複製,myObject,這樣會因爲循環引用致使死循環

如何解決?

1)對於json安全的對象來講:

var newObj = JSON.parse(JSON.stringify(someObj));
複製代碼

這種方法須要保證對象是json安全的,因此只適用於部分狀況。

2)ES6定義了Object.assign(..)方法來實現淺複製。Object.assign(..)方法的第一個參數是目標對象,以後還能夠跟一個或多個源對象。它會遍歷一個或多個源對象的全部可枚舉的自有鍵並把它們複製(使用 = 操做符賦值)到目標對象:

var newObj = Object.assign({}, myObject);
newObj.a;// 2
newObj.b === anotherObject; // true
newObj.c === anotherArray; // true
newObj.d === anotherFunction; // true
複製代碼

注:因爲Object.assign(...)使用 = 操做符來賦值,因此源對象屬性的一些特性不會被複制到目標對象。

五、屬性描述符

在ES5以前,JavaScript語言自己並無提供可直接檢測屬性特性的方法,如判斷屬性是不是隻讀。從ES5開始,全部的屬性都具有了屬性描述符。

var myObject = {
    a: 2
}
Object.getOwnPropertyDescriptor(myObject, "a");
// {
//   value: 2,
//   writable: true, // 可寫
//   enumerable: true, // 可枚舉
//   configurable: true // 可配置
// }
複製代碼

在建立普通屬性時屬性描述符會使用默認值,可以使用Object.defineProperty(...)來添加一個新屬性或修改一個已有屬性,並對特性進行設置。

var myObject = {};
Object.defineProperty(myObject, "a", {
    value: 2,
    writable: true,
    configurable: true,
    enumerable: true
});
myObject.a; // 2
複製代碼

1)writable 決定是否能夠修改屬性的值

var myObject = {};
Object.defineProperty(myObject, "a", {
    value: 2,
    writable: false, // 不可寫
    configurable: true,
    enumerable: true
});
myObject.a = 3;
myObject.a; // 2
複製代碼

在嚴格模式下會報錯

"use strict"
var myObject = {};
Object.defineProperty(myObject, "a", {
    value: 2,
    writable: false, // 不可寫
    configurable: true,
    enumerable: true
});
myObject.a = 3; // TypeError
複製代碼

2)Configurable 只要屬性可配置,就能夠用defineProperty(...)方法修改屬性描述符:

var myObject = {
    a: 2
};
myObject.a = 3;
myObject.a; // 3
Object.defineProperty(myObject, "a", {
    value: 4,
    writable: true,
    configurable: false, // 不可配置
    enumerable: true
});
myObject.a; // 4
myObject.a = 5;
myObject.a; // 5
Object.defineProperty(myObject, "a", {
    value: 6,
    writable: true,
    configurable: true,
    enumerable: true
}); // TypeError
複製代碼

不管是否處於嚴格模式,嘗試修改一個不可配置的屬性描述符都會出錯。

注:把Configurable 修改爲false是單向操做,沒法撤銷; 即使屬性是configurable:false,咱們仍是能夠把writable的狀態由true改成false,但沒法由false改成true。

除了沒法修改,configurable: false還會禁止刪除這個屬性:

var myObject = {
    a: 2
};
myObject.a = 2;
delete myObject.a;
myObject.a; // undefined
Object.defineProperty(myObject, "a", {
    value: 2,
    writable: true,
    configurable: false, // 不可配置
    enumerable: true
});
myObject.a; // 2
delete myObject.a;
myObject.a; // 2
複製代碼

在本例中,delete只用來直接刪除對象的(可刪除)屬性。若對象的某個屬性是某個對象/函數的最後一個引用者,對這個屬性執行delete操做後,這個對象/函數就能夠被垃圾回收

3)enumerable 控制屬性是否出如今對象的屬性枚舉類中,如for...in循環,若把enumerable設置爲false,屬性就不會出如今枚舉中,雖然仍能夠正常訪問它

六、不變性

ES5中全部方法建立的都是淺不變性,即它們只會影響目標對象和它的直接屬性。若是目標對象引用了其餘對象(數組、對象、函數等),其餘對象的內容不受影響,還是可變的:

myImmutableObject.foo; // [1,2,3]
myImmutableObject.foo.push(4);
myImmutableObject.foo; // [1,2,3,4]
複製代碼

1)對象常量

結合writable:false和configurable:false就能夠建立一個真正的常量屬性(不可修改、從新定義或者刪除)

var myObject = {};
Object.defineProperty(myObject, "FAVORITE_NUMBER", {
    value: 42,
    writable: false,
    configurable: false, // 不可配置
});
複製代碼

2)禁止擴展

Object.preventExtensions(...):禁止一個對象添加新屬性而且保留已有屬性

var myObject = {
    a: 2
};
Object.preventExtensions(myObject);
myObject.b = 3;
myObject.b; // undefined
複製代碼

非嚴格模式下,建立屬性b會靜默失敗,在嚴格模式下,將拋出TypeError

3)密封

Object.seal(...)會建立一個「密封」對象,這個方法實際上會在一個現有對象上調用Object.preventExtensions(...)並把全部現有屬性標記爲configurable:false。因此密封后不只不能添加新屬性,也不能從新配置或刪除任何現有屬性(雖然能夠修改屬性的值)

4)凍結 Object.freeze(...)會建立一個凍結對象,實際上會在一個現有對象上調用Object.seal(...)並把全部「數據訪問」屬性標記爲writable:false,這樣就沒法修改值

這個方法可應用在對象上的級別最高的不可變性,它會禁止對象自己及其任意直接屬性的修改,這個對象的引用的其餘對象是不受影響的

深度凍結方法:首先在這個對象上調用Object.freeze(...),而後遍歷它引用的全部對象並在這些對象上調用Object.freeze(...),但可能會在無心中凍結其餘(共享)對象

七、[[Get]]

var myObject = {
    a: 2
}
myObject.a; // 2
複製代碼

myObject.a在myObject上實際是實現了[[Get]]操做。對象默認的內置[[Get]]操做首先在對象中查找是否有名稱相同的屬性,若是找到就會返回這個屬性的值,若沒找到,按照[[Get]]實驗法的定義會執行另一種很是重要的行爲,即遍歷可能存在的[[Prototype]]鏈,也就是原型鏈。

若是不管如何都沒有找到名稱相同的屬性,那[[Get]]操做會返回undefined

var myObject = {
    a: 2
}
myObject.b; // undefined
複製代碼

注:這種方法和訪問變量時是不同的,若你引用了一個當前詞法做用域中不存在的變量,並不會像對象屬性同樣返回undefined,而是會拋出ReferenceError異常:

var myObject = {
    a: undefined
}
myObject.a; // undefined
myObject.b; // undefined 因爲根據返回值沒法判斷出到底變量的值爲undefined仍是變量不存在,因此[[Get]]操做返回了undefined
複製代碼

八、[[Put]]

[[Put]]被觸發時,實際行爲取決於許多因素,包括對象中是否已經存在這個屬性(最重要的因素)

若是已經存在這個屬性,[[Put]]算法大體會檢查下面這些內容: 1)屬性是不是訪問描述符?若是是而且存在setter就調用setter 2)屬性的數據描述符中writable是不是false?若是是,在非嚴格模式下靜默註冊失敗,在嚴格模式下拋出TypeError異常

九、Getter和Setter

在ES5中可以使用getter和setter部分改寫默認操做,但只能應用在單個屬性上,沒法應用在整個對象上。getter是一個隱藏函數,會在獲取屬性值時調用。setter也是一個隱藏函數,會在設置屬性時調用。

當你給一個屬性定義getter和setter或者二者都有時,這個屬性會被定義爲「訪問描述符」,對於訪問描述符來講,js會忽略它們的value和writable特性,取而代之的關心set和get(還有configurable和enumerable)

var myObject = {
    get a() {
        return 2;
    }
}
Object.defineProperty(
    myObject, // 目標對象
    "b", // 屬性名
    {
        get: function() {
            return this.a * 2
        },
        enumerable: true
    }
);
myOject.a; // 2
myObject.b; // 4
複製代碼

兩種方式都會在對象中建立一個不包含值的屬性,對於這個屬性的訪問會自動調用一個隱藏函數,它的返回值會被當作屬性訪問的返回值:

var myObject = {
    get a() {
        return 2;
    }
}
myOject.a = 3;
myObject.a; // 2
複製代碼

因爲咱們只定義了a的getter,因此對a的值進行設置時set操做會忽略賦值操做,不會拋出錯誤。

一般來講getter和setter是成對出現的

var myObject = {
    get a() {
        return this._a_;
    }
    set a(val) {
        this._a_ = val * 2;
    }
}
myOject.a = 2;
myObject.a; // 4
複製代碼

十、存在性

屬性訪問返回值多是undefined,如何區分這是屬性中存儲的undefined,仍是屬性不存在而返回的undefined ?

var myObject = {
    a: 2
};
("a" in myObject); // true
("b" in myObject); // false
myObject.hasOwnProperty("a"); // true
myObject.hasOwnProperty("b"); // false
複製代碼

in操做符會檢查屬性是否在對象及其[[Prototype]]原型鏈中,hasOwnProperty(..)只會檢查屬性是否在myObject對象中,不會檢查[[Prototype]]鏈

注:in是檢查某個屬性名是否存在,如:4 in [2,4,6] = false, 由於這個數組中包含的屬性名爲0 ,1, 2

全部的普通對象均可以經過對於 Object.prototype 的委託來訪問hasOwnProperty(..),但有的對象可能沒有鏈接到 Object.prototype (經過Object.create(null)建立),則myObject.hasOwnProperty就會失敗

此時可採用 Object.prototype.hasOwnProperty.call(myObject, "a") 進行判斷,它借用基礎的hasOwnProperty(..)方法並把它顯示綁定在myObject上

1)枚舉

「可枚舉」至關於「能夠出如今對象屬性的遍歷中」

var myObject = {};
Object.defineProperty(
    myObject, 
    "a",
    // 讓a像普通對象同樣可枚舉
    { enumerable: true, value: 2 }
);
Object.defineProperty(
    myObject, 
    "B",
    // 讓b不可枚舉
    { enumerable: false, value: 3 }
);
myObject.b; // 3
("b" in myObject); // true
myObject.hasOwnProperty("b"); // true

for (var k in myObject) {
    console.log(k, myObject[k]);
}
// "a" 2
複製代碼

for...in枚舉不只會包含全部索引,還會包含全部可枚舉屬性,因此最好只在對象上引用for ... in 循環,若遍歷數組就使用for循環。但它沒法直接獲取屬性值,需手動獲取

也可用propertyIsEnumerable(..)來區分屬性是否可枚舉

var myObject = {};
Object.defineProperty(
    myObject, 
    "a",
    // 讓a像普通對象同樣可枚舉
    { enumerable: true, value: 2 }
);
Object.defineProperty(
    myObject, 
    "B",
    // 讓b不可枚舉
    { enumerable: false, value: 3 }
);
myObject.propertyIsEnumerable("a"); // true
myObject.propertyIsEnumerable("b"); // false

Object.keys(myObject); // ["a"]
Object.getOwnPropertyNames(myObject); // ["a", "b"]
複製代碼

propertyIsEnumerable(..)會檢查給定的屬性名是否直接存在於對象中(而非原型鏈上),而且知足enumerable: true

Object.keys(..)會返回數組,包含全部可枚舉屬性。Object.getOwnPropertyNames(..)只會查找對象直接包含的屬性

4、遍歷

for...in用來遍歷對象的可枚舉屬性列表,如何遍歷屬性值呢?

對於數值索引的數組來講,可用for循環。另外ES5也增長了一些數組輔助迭代器:forEach(..)、every(..)、some(..),他們均可以接受一個回調函數並把它應用在數組的每一個元素上,區別就是它們對於回調函數返回值的處理方式不一樣

forEach(..):遍歷數組全部值並忽略回調函數的返回值

every(..):會一直運行到回調函數返回false(或「假」值)

some(..):會一直運行直到回調函數返回true(或「真」值)

遍歷數組下標時採用的數字順序,但遍歷對象屬性時順序不肯定,在不一樣的js引擎中可能不同

如何直接遍歷值而不是數組下標?

使用for..of (ES6增長的語法)

var myArray = [1, 2, 3];
for(var v of myArray) {
    console.log(v);
}
// 1
// 2
// 3
複製代碼

for..of首先會向被訪問對象請求一個迭代器對象,而後經過調用迭代器對象的next()方法來遍歷全部返回值

數組有內置的@@iterator,也可直接應用在數組上。

var myArray = [1, 2, 3];
var it = myArray[Symbol.iterator]();
it.next(); // {value: 1, done: false}
it.next(); // {value: 2, done: false}
it.next(); // {value: 3, done: false}
it.next(); // {done: true}
複製代碼

value是遍歷值,done是布爾值,表示是否還有可遍歷的值

普通對象中沒有內置的@@iterator,因此沒法自動完成for..of,但能夠結合for..of循環與自定義迭代器來操做對象

var myObject = {
    a: 2,
    b: 3
}
Object.defineProperty(myObject, Symbol.iterator, {
  enumerable: false,
  writable: false,
  configurable: true,
  value: function() {
      var o = this;
      var idx = 0;
      var ks = Object.keys(o);
      return {
          next:function() {
              return {
                  value: o[ks[idx++]],
                  done: (idx > ks.length)
              }
          }
      }
  }
});
// 手動遍歷
var it = myObject[Symbol.iterator]();
it.next(); // {value: 2, done: false}
it.next(); // {value: 3, done: false}
it.next(); // {value: undefined, done: true}

// for .. of
for (var v of myObject) {
    conosle.log(v);
}
// 2
// 3
複製代碼
相關文章
相關標籤/搜索