最近在拜讀《你不知道的js》,而此篇是對於《你不知道的js》中對象部分的筆記整理,但願能有效的梳理,而且深刻理解對象算法
對象兩種定義形式:聲明(文字)形式、構造形式json
var myObj = {
key: value,
...
}
複製代碼
var myObj = new Object();
myObj.key = value;
複製代碼
構造形式與文字形式生成的對象同樣數組
區別:文字聲明中能夠添加多個鍵/值對,構造形式中必須逐個添加屬性安全
在JavaScript中一共有6中主要類型:bash
注意:簡單基本類型(string、boolean、number、null、undefined)自己不是對象。null有時會被當作一種對象類型,typeof null返回‘object’。實際上,null是基本類型函數
內置對象:對象子類型ui
對象的內容是由一些存儲在特定命名位置的(任意類型的)值組成,咱們稱爲屬性。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(...),但可能會在無心中凍結其餘(共享)對象
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]]算法大體會檢查下面這些內容: 1)屬性是不是訪問描述符?若是是而且存在setter就調用setter 2)屬性的數據描述符中writable是不是false?若是是,在非嚴格模式下靜默註冊失敗,在嚴格模式下拋出TypeError異常
在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(..)只會查找對象直接包含的屬性
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
複製代碼