讀《你不知道的JavaScript》筆記(二)

本文是系列(二)樟遣,記錄的是《你不知道的JavaScript》上卷第二部分的1-3章燃箭,第一篇連接http://www.reibang.com/p/23815e221ffe
本文目錄

  • 第二部分 this和對(duì)象原型
    • 第1章 關(guān)于this
    • 第2章 this全面解析
    • 第3章 對(duì)象

第二部分 this和對(duì)象原型

第1章 關(guān)于this

誤解:函數(shù)中的this指向函數(shù)自身
看下面的代碼

function foo(num) {
    console.log("foo: " + num); // 記錄 foo 被調(diào)用的次數(shù)
    this.count++;
}
foo.count = 0;
var i;
for (i = 0; i < 10; i++) {
    if (i > 5) {
        foo(i);
    }
}
// foo: 6 
// foo: 7 
// foo: 8 
// foo: 9 
// foo 被調(diào)用了多少次府瞄? 
console.log(foo.count); // 0

console.log 語(yǔ)句產(chǎn)生了 4 條輸出敷矫,證明 foo(..) 確實(shí)被調(diào)用了 4 次础芍,但是 foo.count 仍然 是 0灸叼。顯然this并不指向函數(shù)自身神汹。
解決方法之一是使用 foo 標(biāo)識(shí)符替代 this 來(lái)引用函數(shù) 對(duì)象:

function foo(num) {
    console.log("foo: " + num);
    // 記錄 foo 被調(diào)用的次數(shù) foo.count++; 
}
foo.count = 0
var i;
for (i = 0; i < 10; i++) {
    if (i > 5) {
        foo(i);
    }
}
// foo: 6 
// foo: 7 
// foo: 8
// foo: 9 
// foo 被調(diào)用了多少次?
console.log(foo.count); // 4

從某種角度來(lái)說(shuō)這個(gè)方法確實(shí)“解決”了問(wèn)題古今,但可惜它忽略了真正的問(wèn)題——無(wú)法理解 this 的含義和工作原理——而是返回舒適區(qū)屁魏,且完全依賴于變量 foo 的詞法作用域。
另一種方法是強(qiáng)制 this 指向 foo 函數(shù)對(duì)象:

function foo(num) {
    console.log("foo: " + num);
    // 記錄 foo 被調(diào)用的次數(shù) 
    // 注意捉腥,在當(dāng)前的調(diào)用方式下(參見(jiàn)下方代碼)氓拼,this 確實(shí)指向 foo
    this.count++;
}
foo.count = 0;
var i;
for (i = 0; i < 10; i++) {
    if (i > 5) {
        // 使用 call(..) 可以確保 this 指向函數(shù)對(duì)象 foo 本身 
        foo.call(foo, i);
    }
}
// foo: 6 
// foo: 7 
// foo: 8
// foo: 9 
// foo 被調(diào)用了多少次?
console.log(foo.count); // 4

這次我們接受了 this抵碟,沒(méi)有回避它桃漾。如果你仍然感到困惑的話,不用擔(dān)心立磁,之后我們會(huì)詳 細(xì)解釋具體的原理呈队。

下面這段代碼非常的經(jīng)典

function foo() {
    var a = 2;
    this.bar();
}
function bar() {
    console.log(this.a);
}
foo(); // ReferenceError: a is not defined

這段代碼的錯(cuò)誤不止一個(gè),非常完美(同時(shí)也令人傷感) 地展示了 this 多么容易誤導(dǎo)人唱歧。
之前我們說(shuō)過(guò) this 是在運(yùn)行時(shí)進(jìn)行綁定的宪摧,并不是在編寫(xiě)時(shí)綁定粒竖,它的上下文取決于函數(shù)調(diào) 用時(shí)的各種條件。this 的綁定和函數(shù)聲明的位置沒(méi)有任何關(guān)系几于,只取決于函數(shù)的調(diào)用方式蕊苗。

第2章 this全面解析

通常來(lái)說(shuō),尋找調(diào)用位置就是尋找“函數(shù)被調(diào)用的位置”沿彭,但是做起來(lái)并沒(méi)有這么簡(jiǎn)單朽砰, 因?yàn)槟承┚幊棠J娇赡軙?huì)隱藏真正的調(diào)用位置。
思考下面的代碼:

function foo() {
    console.log(this.a);
}
var obj = {
    a: 2,
    foo: foo
};
var bar = obj.foo; // 函數(shù)別名喉刘!
var a = "oops, global"; // a 是全局對(duì)象的屬性 
bar(); // "oops, global"

雖然 bar 是 obj.foo 的一個(gè)引用瞧柔,但是實(shí)際上,它引用的是 foo 函數(shù)本身睦裳,因此此時(shí)的 bar() 其實(shí)是一個(gè)不帶任何修飾的函數(shù)調(diào)用造锅,因此應(yīng)用了默認(rèn)綁定。
一種更微妙廉邑、更常見(jiàn)并且更出乎意料的情況發(fā)生在傳入回調(diào)函數(shù)時(shí):

function foo() {
    console.log(this.a);
}
function doFoo(fn) {
    // fn 其實(shí)引用的是 foo 
    fn(); // <-- 調(diào)用位置哥蔚! 
}
var obj = {
    a: 2,
    foo: foo
};
var a = "oops, global"; // a 是全局對(duì)象的屬性 
doFoo(obj.foo); // "oops, global"

參數(shù)傳遞其實(shí)就是一種隱式賦值,因此我們傳入函數(shù)時(shí)也會(huì)被隱式賦值蛛蒙,所以結(jié)果和上一 個(gè)例子一樣糙箍。
如果把函數(shù)傳入語(yǔ)言內(nèi)置的函數(shù)而不是傳入你自己聲明的函數(shù),會(huì)發(fā)生什么呢牵祟?結(jié)果是一 樣的深夯,沒(méi)有區(qū)別:

function foo() {
    console.log(this.a);
}
var obj = {
    a: 2,
    foo: foo
};
var a = "oops, global"; // a 是全局對(duì)象的屬性 
setTimeout(obj.foo, 100); // "oops, global"

JavaScript 環(huán)境中內(nèi)置的 setTimeout() 函數(shù)實(shí)現(xiàn)和下面的偽代碼類(lèi)似:

function setTimeout(fn, delay) {
    // 等待 delay 毫秒 fn(); // <-- 調(diào)用位置! 
}

第三方庫(kù)的許多函數(shù)诺苹,以及 JavaScript 語(yǔ)言和宿主環(huán)境中許多新的內(nèi)置函數(shù)塌西,都提供了一 個(gè)可選的參數(shù),通常被稱為“上下文”(context)筝尾,其作用和 bind(..) 一樣,確保你的回調(diào) 函數(shù)使用指定的 this办桨。
舉例來(lái)說(shuō):

function foo(el) {
    console.log(el, this.id);
}
var obj = {
    id: "awesome"
}; // 調(diào)用 foo(..) 時(shí)把 this 綁定到 obj 
[1, 2, 3].forEach(foo, obj); // 1 awesome 2 awesome 3 awesome

這些函數(shù)實(shí)際上就是通過(guò) call(..) 或者 apply(..) 實(shí)現(xiàn)了顯式綁定筹淫,這樣你可以少些一些 代碼。如果我們使用forEach不傳第二個(gè)可選參數(shù)呢撞,結(jié)果會(huì)是1 undefined 2 undefined 3 undefined

在 JavaScript 中损姜,構(gòu)造函數(shù)只是一些 使用 new 操作符時(shí)被調(diào)用的函數(shù)。它們并不會(huì)屬于某個(gè)類(lèi)殊霞,也不會(huì)實(shí)例化一個(gè)類(lèi)摧阅。實(shí)際上, 它們甚至都不能說(shuō)是一種特殊的函數(shù)類(lèi)型绷蹲,它們只是被 new 操作符調(diào)用的普通函數(shù)而已棒卷。
使用 new 來(lái)調(diào)用函數(shù)顾孽,或者說(shuō)發(fā)生構(gòu)造函數(shù)調(diào)用時(shí),會(huì)自動(dòng)執(zhí)行下面的操作比规。

    1. 創(chuàng)建(或者說(shuō)構(gòu)造)一個(gè)全新的對(duì)象若厚。
    1. 這個(gè)新對(duì)象會(huì)被執(zhí)行 [[ 原型 ]] 連接。
    1. 這個(gè)新對(duì)象會(huì)綁定到函數(shù)調(diào)用的 this蜒什。
    1. 如果函數(shù)沒(méi)有返回其他對(duì)象测秸,那么 new 表達(dá)式中的函數(shù)調(diào)用會(huì)自動(dòng)返回這個(gè)新對(duì)象。

判斷this
現(xiàn)在我們可以根據(jù)優(yōu)先級(jí)來(lái)判斷函數(shù)在某個(gè)調(diào)用位置應(yīng)用的是哪條規(guī)則灾常■耄可以按照下面的 順序來(lái)進(jìn)行判斷:

    1. 函數(shù)是否在 new 中調(diào)用(new 綁定)?如果是的話 this 綁定的是新創(chuàng)建的對(duì)象钞瀑。 var bar = new foo()
    1. 函數(shù)是否通過(guò) call沈撞、apply(顯式綁定)或者硬綁定調(diào)用?如果是的話仔戈,this 綁定的是 指定的對(duì)象关串。 var bar = foo.call(obj2) - - 3. 函數(shù)是否在某個(gè)上下文對(duì)象中調(diào)用(隱式綁定)?如果是的話监徘,this 綁定的是那個(gè)上 下文對(duì)象晋修。 var bar = obj1.foo()
    1. 如果都不是的話,使用默認(rèn)綁定凰盔。如果在嚴(yán)格模式下墓卦,就綁定到 undefined,否則綁定到 全局對(duì)象户敬。 var bar = foo()

就是這樣落剪。對(duì)于正常的函數(shù)調(diào)用來(lái)說(shuō),理解了這些知識(shí)你就可以明白 this 的綁定原理了尿庐。 不過(guò)……凡事總有例外忠怖。
如果你把 null 或者 undefined 作為 this 的綁定對(duì)象傳入 call、apply 或者 bind抄瑟,這些值 在調(diào)用時(shí)會(huì)被忽略凡泣,實(shí)際應(yīng)用的是默認(rèn)綁定規(guī)則:

function foo() {
    console.log(this.a);
}
var a = 2;
foo.call(null); // 2

那么什么情況下你會(huì)傳入 null 呢?
一種非常常見(jiàn)的做法是使用 apply(..) 來(lái)“展開(kāi)”一個(gè)數(shù)組皮假,并當(dāng)作參數(shù)傳入一個(gè)函數(shù)鞋拟。 類(lèi)似地,bind(..) 可以對(duì)參數(shù)進(jìn)行柯里化(預(yù)先設(shè)置一些參數(shù))惹资,這種方法有時(shí)非常有用:

function foo(a, b) {
    console.log("a:" + a + ", b:" + b);
}
// 把數(shù)組“展開(kāi)”成參數(shù) 
foo.apply(null, [2, 3]); // a:2, b:3
// 使用 bind(..) 進(jìn)行柯里化
var bar = foo.bind(null, 2);
bar(3); // a:2, b:3

這兩種方法都需要傳入一個(gè)參數(shù)當(dāng)作 this 的綁定對(duì)象贺纲。如果函數(shù)并不關(guān)心 this 的話,你 仍然需要傳入一個(gè)占位值褪测,這時(shí) null 可能是一個(gè)不錯(cuò)的選擇猴誊,就像代碼所示的那樣潦刃。
在 ES6 中,可以用 ... 操作符代替 apply(..) 來(lái)“展 開(kāi)”數(shù)組稠肘,foo(...[1,2]) 和 foo(1,2) 是一樣的福铅,這樣可以避免不必要的 this 綁定∠钜酰可惜滑黔,在 ES6 中沒(méi)有柯里化的相關(guān)語(yǔ)法,因此還是需要使用 bind(..)环揽。
然而略荡,總是使用 null 來(lái)忽略 this 綁定可能產(chǎn)生一些副作用。如果某個(gè)函數(shù)確實(shí)使用了 this(比如第三方庫(kù)中的一個(gè)函數(shù))歉胶,那默認(rèn)綁定規(guī)則會(huì)把 this 綁定到全局對(duì)象(在瀏覽 器中這個(gè)對(duì)象是 window)汛兜,這將導(dǎo)致不可預(yù)計(jì)的后果(比如修改全局對(duì)象)。

一種“更安全”的做法是傳入一個(gè)特殊的對(duì)象通今,把 this 綁定到這個(gè)對(duì)象不會(huì)對(duì)你的程序 產(chǎn)生任何副作用粥谬。就像網(wǎng)絡(luò)(以及軍隊(duì))一樣,我們可以創(chuàng)建一個(gè)“DMZ”(demilitarized zone辫塌,非軍事區(qū))對(duì)象——它就是一個(gè)空的非委托的對(duì)象漏策。
如果我們?cè)诤雎?this 綁定時(shí)總是傳入一個(gè) DMZ 對(duì)象,那就什么都不用擔(dān)心了臼氨,因?yàn)槿魏?對(duì)于 this 的使用都會(huì)被限制在這個(gè)空對(duì)象中掺喻,不會(huì)對(duì)全局對(duì)象產(chǎn)生任何影響。
無(wú)論你叫它什么储矩,在 JavaScript 中創(chuàng)建一個(gè)空對(duì)象最簡(jiǎn)單的方法都是 Object.create(null) ( 詳 細(xì) 介 紹 請(qǐng) 看 第 5 章 )感耙。Object.create(null) 和 {} 很 像, 但 是 并 不 會(huì) 創(chuàng) 建 Object. prototype 這個(gè)委托持隧,所以它比 {}“更空”:

function foo(a, b) {
    console.log("a:" + a + ", b:" + b);
}
// 我們的 DMZ 空對(duì)象 
var ? = Object.create(null);
// 把數(shù)組展開(kāi)成參數(shù) 
foo.apply(?, [2, 3]); // a:2, b:3 
// 使用 bind(..) 進(jìn)行柯里化
var bar = foo.bind(?, 2);
bar(3); // a:2, b:3

使用變量名 ? 不僅讓函數(shù)變得更加“安全”即硼,而且可以提高代碼的可讀性,因?yàn)?? 表示 “我希望 this 是空”屡拨,這比 null 的含義更清楚谦絮。不過(guò)再說(shuō)一遍,你可以用任何喜歡的名字 來(lái)命名 DMZ 對(duì)象洁仗。
另一個(gè)需要注意的是,你有可能(有意或者無(wú)意地)創(chuàng)建一個(gè)函數(shù)的“間接引用”性锭,在這 種情況下赠潦,調(diào)用這個(gè)函數(shù)會(huì)應(yīng)用默認(rèn)綁定規(guī)則。 間接引用最容易在賦值時(shí)發(fā)生:

function foo() {
    console.log(this.a);
}
var a = 2;
var o = {
    a: 3,
    foo: foo
};
var p = {
    a: 4
};
o.foo(); // 3 
(p.foo = o.foo)(); // 2

賦值表達(dá)式 p.foo = o.foo 的返回值是目標(biāo)函數(shù)的引用草冈,因此調(diào)用位置是 foo() 而不是 p.foo() 或者 o.foo()她奥。根據(jù)我們之前說(shuō)過(guò)的瓮增,這里會(huì)應(yīng)用默認(rèn)綁定。 注意:對(duì)于默認(rèn)綁定來(lái)說(shuō)哩俭,決定 this 綁定對(duì)象的并不是調(diào)用位置是否處于嚴(yán)格模式绷跑,而是 函數(shù)體是否處于嚴(yán)格模式。如果函數(shù)體處于嚴(yán)格模式凡资,this 會(huì)被綁定到 undefined砸捏,否則 this 會(huì)被綁定到全局對(duì)象。

我們來(lái)看看箭頭函數(shù)的詞法作用域:

function foo() { // 返回一個(gè)箭頭函數(shù)
    return (a) => { //this 繼承自 foo() 
        console.log(this.a);
    };
}
var obj1 = {
    a: 2
};
var obj2 = {
    a: 3
};
var bar = foo.call(obj1);
bar.call(obj2); // 2, 不是 3 隙赁!

foo() 內(nèi)部創(chuàng)建的箭頭函數(shù)會(huì)捕獲調(diào)用時(shí) foo() 的 this垦藏。由于 foo() 的 this 綁定到 obj1, bar(引用箭頭函數(shù))的 this 也會(huì)綁定到 obj1伞访,箭頭函數(shù)的綁定無(wú)法被修改掂骏。(new 也不 行!)
箭頭函數(shù)最常用于回調(diào)函數(shù)中厚掷,例如事件處理器或者定時(shí)器:

function foo() {
    setTimeout(() => {
        // 這里的 this 在此法上繼承自 foo() 
        console.log(this.a);
    }, 100);
}
var obj = {
    a: 2
};
foo.call(obj); // 2

第3章 對(duì)象

有一種常見(jiàn)的錯(cuò)誤說(shuō)法是“JavaScript 中萬(wàn)物皆是對(duì)象”弟灼,這顯然是錯(cuò)誤的。
實(shí)際上冒黑,JavaScript 中有許多特殊的對(duì)象子類(lèi)型田绑,我們可以稱之為復(fù)雜基本類(lèi)型。
函數(shù)就是對(duì)象的一個(gè)子類(lèi)型(從技術(shù)角度來(lái)說(shuō)就是“可調(diào)用的對(duì)象”)薛闪。JavaScript 中的函 數(shù)是“一等公民”辛馆,因?yàn)樗鼈儽举|(zhì)上和普通的對(duì)象一樣(只是可以調(diào)用),所以可以像操作 其他對(duì)象一樣操作函數(shù)(比如當(dāng)作另一個(gè)函數(shù)的參數(shù))豁延。
數(shù)組也是對(duì)象的一種類(lèi)型昙篙,具備一些額外的行為。數(shù)組中內(nèi)容的組織方式比一般的對(duì)象要 稍微復(fù)雜一些诱咏。

思考下面的代碼:

var myObject = { a: 2 };
myObject.a; // 2 
myObject["a"]; // 2

如果要訪問(wèn) myObject 中 a 位置上的值苔可,我們需要使用 . 操作符或者 [] 操作符。.a 語(yǔ)法通 常被稱為“屬性訪問(wèn)”袋狞,["a"] 語(yǔ)法通常被稱為“鍵訪問(wèn)”焚辅。
這兩種語(yǔ)法的主要區(qū)別在于 . 操作符要求屬性名滿足標(biāo)識(shí)符的命名規(guī)范,而 [".."] 語(yǔ)法 可以接受任意 UTF-8/Unicode 字符串作為屬性名苟鸯。舉例來(lái)說(shuō)同蜻,如果要引用名稱為 "Super- Fun!" 的屬性,那就必須使用 ["Super-Fun!"] 語(yǔ)法訪問(wèn)早处,因?yàn)?Super-Fun! 并不是一個(gè)有效 的標(biāo)識(shí)符屬性名
在對(duì)象中湾蔓,屬性名永遠(yuǎn)都是字符串。如果你使用 string(字面量)以外的其他值作為屬性 名砌梆,那它首先會(huì)被轉(zhuǎn)換為一個(gè)字符串默责。即使是數(shù)字也不例外贬循,雖然在數(shù)組下標(biāo)中使用的的 確是數(shù)字,但是在對(duì)象屬性名中數(shù)字會(huì)被轉(zhuǎn)換成字符串

數(shù)組也是對(duì)象桃序,所以雖然每個(gè)下標(biāo)都是整數(shù)杖虾,你仍然可以給數(shù)組添加屬性:

var myArray = ["foo", 42, "bar"];
myArray.baz = "baz";
console.log(myArray.length); // 3 
console.log(myArray.baz); // "baz"
console.log(myArray) //["foo", 42, "bar", baz: "baz"]

可以看到雖然添加了命名屬性(無(wú)論是通過(guò) . 語(yǔ)法還是 [] 語(yǔ)法),數(shù)組的 length 值并未發(fā) 生變化媒熊。
你完全可以把數(shù)組當(dāng)作一個(gè)普通的鍵 / 值對(duì)象來(lái)使用奇适,并且不添加任何數(shù)值索引,但是這 并不是一個(gè)好主意泛释。數(shù)組和普通的對(duì)象都根據(jù)其對(duì)應(yīng)的行為和用途進(jìn)行了優(yōu)化滤愕,所以最好 只用對(duì)象來(lái)存儲(chǔ)鍵 / 值對(duì),只用數(shù)組來(lái)存儲(chǔ)數(shù)值下標(biāo) / 值對(duì)怜校。
注意:如果你試圖向數(shù)組添加一個(gè)屬性间影,但是屬性名“看起來(lái)”像一個(gè)數(shù)字,那它會(huì)變成 一個(gè)數(shù)值下標(biāo)(因此會(huì)修改數(shù)組的內(nèi)容而不是添加一個(gè)屬性):

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

在 ES5 之前茄茁,JavaScript 語(yǔ)言本身并沒(méi)有提供可以直接檢測(cè)屬性特性的方法魂贬,比如判斷屬 性是否是只讀。

var myObject = {
    a: 2
};
Object.getOwnPropertyDescriptor(myObject, "a");
// { 
// value: 2,
// writable: true,
// enumerable: true,
// configurable: true
// }

如你所見(jiàn)裙顽,這個(gè)普通的對(duì)象屬性對(duì)應(yīng)的屬性描述符(也被稱為“數(shù)據(jù)描述符”付燥,因?yàn)樗?只保存一個(gè)數(shù)據(jù)值)可不僅僅只是一個(gè) 2。它還包含另外三個(gè)特性:writable(可寫(xiě))愈犹、 enumerable(可枚舉)和 configurable(可配置)键科。
在創(chuàng)建普通屬性時(shí)屬性描述符會(huì)使用默認(rèn)值,我們也可以使用 Object.defineProperty(..) 來(lái)添加一個(gè)新屬性或者修改一個(gè)已有屬性(如果它是 configurable)并對(duì)特性進(jìn)行設(shè)置漩怎。
舉例來(lái)說(shuō):

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

我們使用 defineProperty(..) 給 myObject 添加了一個(gè)普通的屬性并顯式指定了一些特性勋颖。 然而,一般來(lái)說(shuō)你不會(huì)使用這種方式勋锤,除非你想修改屬性描述符饭玲。
注意:即便屬性是 configurable:false,我們還是可以 把 writable 的狀態(tài)由 true 改為 false叁执,但是無(wú)法由 false 改為 true茄厘。除了無(wú)法修改,configurable:false 還會(huì)禁止刪除這個(gè)屬性谈宛。

不可變性

在 JavaScript 程序中很少需要深不可變性次哈,但是我們需要做一下了解:
1. 對(duì)象常量
結(jié)合 writable:false 和 configurable:false 就可以創(chuàng)建一個(gè)真正的常量屬性(不可修改、 重定義或者刪除)
2. 禁止擴(kuò)展
如 果 你 想 禁 止 一 個(gè) 對(duì) 象 添 加 新 屬 性 并 且 保 留 已 有 屬 性吆录, 可 以 使 用 Object.prevent Extensions(..):

var myObject = {
    a: 2
};
Object.preventExtensions(myObject);
myObject.b = 3;
console.log(myObject.b); // undefined

3. 密封
Object.seal(..) 會(huì)創(chuàng)建一個(gè)“密封”的對(duì)象亿乳,這個(gè)方法實(shí)際上會(huì)在一個(gè)現(xiàn)有對(duì)象上調(diào)用 Object.preventExtensions(..) 并把所有現(xiàn)有屬性標(biāo)記為 configurable:false。(不可以修改屬性,但是可以修改值)
4. 凍結(jié)
Object.freeze(..) 會(huì)創(chuàng)建一個(gè)凍結(jié)對(duì)象葛假,這個(gè)方法實(shí)際上會(huì)在一個(gè)現(xiàn)有對(duì)象上調(diào)用 Object.seal(..) 并把所有“數(shù)據(jù)訪問(wèn)”屬性標(biāo)記為 writable:false,這樣就無(wú)法修改它們 的值滋恬。(不可以修改值聊训,但是可以修改屬性)

思考下面的代碼:

var myObject = { // 給 a 定義一個(gè) getter 
    get a() {
        return 2;
    }
};
Object.defineProperty(
    myObject, // 目標(biāo)對(duì)象 
    "b", // 屬性名
    {
        // 描述符 
        // 給 b 設(shè)置一個(gè) getter 
        get: function () {
            return this.a * 2
        },
        // 確保 b 會(huì)出現(xiàn)在對(duì)象的屬性列表中 
        enumerable: true
    });
console.log(myObject.a); // 2 
console.log(myObject.b); // 4

不管是對(duì)象文字語(yǔ)法中的 get a() { .. },還是 defineProperty(..) 中的顯式定義恢氯,二者 都會(huì)在對(duì)象中創(chuàng)建一個(gè)不包含值的屬性带斑,對(duì)于這個(gè)屬性的訪問(wèn)會(huì)自動(dòng)調(diào)用一個(gè)隱藏函數(shù), 它的返回值會(huì)被當(dāng)作屬性訪問(wèn)的返回值:

var myObject = {
    // 給 a 定義一個(gè) getter
    get a() {
        return 2;
    }
};
myObject.a = 3;
console.log(myObject.a); // 2

由于我們只定義了 a 的 getter勋拟,所以對(duì) a 的值進(jìn)行設(shè)置時(shí) set 操作會(huì)忽略賦值操作勋磕,不會(huì)拋 出錯(cuò)誤。而且即便有合法的 setter敢靡,由于我們自定義的 getter 只會(huì)返回 2挂滓,所以 set 操作是 沒(méi)有意義的。
為了讓屬性更合理啸胧,定義getter的同時(shí)還應(yīng)當(dāng)定義 setter赶站。通常來(lái)說(shuō) getter 和 setter 是成對(duì)出現(xiàn)的(只定義一個(gè)的話 通常會(huì)產(chǎn)生意料之外的行為)

存在性

myObject.a 的屬性訪問(wèn)返回值可能是 undefined,但是這個(gè)值有可能 是屬性中存儲(chǔ)的 undefined纺念,也可能是因?yàn)閷傩圆淮嬖谒苑祷?undefined贝椿。那么如何區(qū)分 這兩種情況呢?
我們可以在不訪問(wèn)屬性值的情況下判斷對(duì)象中是否存在這個(gè)屬性:

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

in 操作符會(huì)檢查屬性是否在對(duì)象及其 [[Prototype]] 原型鏈中陷谱。相比之下烙博, hasOwnProperty(..) 只會(huì)檢查屬性是否在 myObject 對(duì)象中,不會(huì)檢查 [[Prototype]] 鏈烟逊。
看起來(lái) in 操作符可以檢查容器內(nèi)是否有某個(gè)值渣窜,但是它實(shí)際上檢查的是某 個(gè)屬性名是否存在。對(duì)于數(shù)組來(lái)說(shuō)這個(gè)區(qū)別非常重要焙格,4 in [2, 4, 6] 的結(jié) 果并不是你期待的 True图毕,因?yàn)?[2, 4, 6] 這個(gè)數(shù)組中包含的屬性名是 0、1眷唉、 2予颤,沒(méi)有 4。

之前介紹 enumerable 屬性描述符特性時(shí)我們簡(jiǎn)單解釋過(guò)什么是“可枚舉性”冬阳,現(xiàn)在詳細(xì)介紹一下:

var myObject = {};
Object.defineProperty(myObject, "a",
    // 讓 a 像普通屬性一樣可以枚舉 
    {
        enumerable: true,
        value: 2
    });
Object.defineProperty(myObject, "b",
    // 讓 b 不可枚舉 
    {
        enumerable: false,
        value: 3
    });
console.log(myObject.b); // 3 
console.log("b" in myObject); // true
console.log(myObject.hasOwnProperty("b")); // true // ....... 
for (var k in myObject) {
    console.log(k, myObject[k]);
} // "a" 2
console.log(Object.keys(myObject)) //["a"]
console.log(Object.getOwnPropertyNames(myObject)) //["a", "b"]

可以看到蛤虐,myObject.b 確實(shí)存在并且有訪問(wèn)值,但是卻不會(huì)出現(xiàn)在 for..in 循環(huán)中(盡管 可以通過(guò) in 操作符來(lái)判斷是否存在)肝陪。原因是“可枚舉”就相當(dāng)于“可以出現(xiàn)在對(duì)象屬性 的遍歷中”驳庭。
Object.keys(..) 會(huì)返回一個(gè)數(shù)組,包含所有可枚舉屬性,Object.getOwnPropertyNames(..) 會(huì)返回一個(gè)數(shù)組饲常,包含所有屬性蹲堂,無(wú)論它們是否可枚舉。

注意:
在數(shù)組上應(yīng)用 for..in 循環(huán)有時(shí)會(huì)產(chǎn)生出人意料的結(jié)果贝淤,因?yàn)檫@種枚舉不 僅會(huì)包含所有數(shù)值索引柒竞,還會(huì)包含所有可枚舉屬性。最好只在對(duì)象上應(yīng)用 for..in 循環(huán)播聪,如果要遍歷數(shù)組就使用傳統(tǒng)的 for 循環(huán)來(lái)遍歷數(shù)值索引朽基。
使用 for..in 遍歷對(duì)象是無(wú)法直接獲取屬性值的,因?yàn)樗鼘?shí)際上遍歷的是對(duì)象中的所有可 枚舉屬性离陶,你需要手動(dòng)獲取屬性值稼虎。
那么如何直接遍歷值而不是數(shù)組下標(biāo)(或者對(duì)象屬性)呢?幸好招刨,ES6 增加了一種用來(lái)遍 歷數(shù)組的 for..of 循環(huán)語(yǔ)法(如果對(duì)象本身定義了迭代器的話也可以遍歷對(duì)象):

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

for..of 循環(huán)首先會(huì)向被訪問(wèn)對(duì)象請(qǐng)求一個(gè)迭代器對(duì)象霎俩,然后通過(guò)調(diào)用迭代器對(duì)象的 next() 方法來(lái)遍歷所有返回值。
和數(shù)組不同计济,普通的對(duì)象沒(méi)有內(nèi)置的 @@iterator茸苇,所以無(wú)法自動(dòng)完成 for..of 遍歷。之所 以要這樣做沦寂,有許多非常復(fù)雜的原因学密,不過(guò)簡(jiǎn)單來(lái)說(shuō),這樣做是為了避免影響未來(lái)的對(duì)象 類(lèi)型传藏。
當(dāng)然腻暮,你可以給任何想遍歷的對(duì)象定義 @@iterator,舉例來(lái)說(shuō):

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)
                };
            }
        };
    }
});
// 手動(dòng)遍歷 myObject 
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 遍歷 myObject
for (var v of myObject) {
    console.log(v);
} // 2 // 3

小結(jié):
屬性不一定包含值——它們可能是具備 getter/setter 的“訪問(wèn)描述符”毯侦。此外哭靖,屬性可以是 可枚舉或者不可枚舉的,這決定了它們是否會(huì)出現(xiàn)在 for..in 循環(huán)中侈离。

該看121頁(yè)了 3.4遍歷

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末试幽,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子卦碾,更是在濱河造成了極大的恐慌铺坞,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,817評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件洲胖,死亡現(xiàn)場(chǎng)離奇詭異济榨,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)绿映,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,329評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén)擒滑,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)腐晾,“玉大人,你說(shuō)我怎么就攤上這事丐一≡逄牵” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 157,354評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵库车,是天一觀的道長(zhǎng)颖御。 經(jīng)常有香客問(wèn)我,道長(zhǎng)凝颇,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,498評(píng)論 1 284
  • 正文 為了忘掉前任疹鳄,我火速辦了婚禮拧略,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘瘪弓。我一直安慰自己垫蛆,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,600評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布腺怯。 她就那樣靜靜地躺著袱饭,像睡著了一般。 火紅的嫁衣襯著肌膚如雪呛占。 梳的紋絲不亂的頭發(fā)上虑乖,一...
    開(kāi)封第一講書(shū)人閱讀 49,829評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音晾虑,去河邊找鬼疹味。 笑死,一個(gè)胖子當(dāng)著我的面吹牛帜篇,可吹牛的內(nèi)容都是我干的糙捺。 我是一名探鬼主播,決...
    沈念sama閱讀 38,979評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼笙隙,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼洪灯!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起竟痰,我...
    開(kāi)封第一講書(shū)人閱讀 37,722評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤签钩,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后凯亮,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體边臼,經(jīng)...
    沈念sama閱讀 44,189評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,519評(píng)論 2 327
  • 正文 我和宋清朗相戀三年假消,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了柠并。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,654評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖臼予,靈堂內(nèi)的尸體忽然破棺而出鸣戴,到底是詐尸還是另有隱情,我是刑警寧澤粘拾,帶...
    沈念sama閱讀 34,329評(píng)論 4 330
  • 正文 年R本政府宣布窄锅,位于F島的核電站,受9級(jí)特大地震影響缰雇,放射性物質(zhì)發(fā)生泄漏入偷。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,940評(píng)論 3 313
  • 文/蒙蒙 一械哟、第九天 我趴在偏房一處隱蔽的房頂上張望疏之。 院中可真熱鬧,春花似錦暇咆、人聲如沸锋爪。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,762評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)其骄。三九已至,卻和暖如春扯旷,著一層夾襖步出監(jiān)牢的瞬間拯爽,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,993評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工薄霜, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留某抓,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,382評(píng)論 2 360
  • 正文 我出身青樓惰瓜,卻偏偏與公主長(zhǎng)得像否副,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子崎坊,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,543評(píng)論 2 349