摘要:但實例化該構(gòu)造函數(shù)仍然得到的是的實例。或者,為了避免在所有查找屬性的地方都插入這段樣本代碼,我們可以將該模式抽象到的構(gòu)造函數(shù)中。該構(gòu)造函數(shù)封裝了所有在單一數(shù)據(jù)類型定義中編寫健壯字典的技術(shù)細(xì)節(jié)。
參考書籍:《Effective JavaScript》
數(shù)組和字典對象是JavaScript中最萬能的數(shù)據(jù)結(jié)構(gòu)。取決于不同的環(huán)境,對象可以表示一個靈活的鍵值關(guān)聯(lián)記錄,一個繼承了方法的面向?qū)ο髷?shù)據(jù)抽象,一個密集或稀疏的數(shù)組,或一個散列表。
使用Object的直接實例構(gòu)造輕量級的字典JavaScript對象的核心是一個字符串屬性名稱和屬性值的映射表。這使得使用對象實現(xiàn)字典易如反掌,因為字典就是可變長的字符串與值的映射集合。
JavaScript提供了枚舉一個對象屬性名的利器,for ... in循環(huán),但是其除了枚舉出對象“自身”的屬性外,還會枚舉出繼承過來的屬性。
如果我們創(chuàng)建一個自定義的字典并將其元素作為該字典對象自身的屬性。
function NaiveDict() { } NaiveDict.prototype.count = function () { var i = 0; for (var name in this) { // counts every property i++; } return i; }; NaiveDict.prototype.toString = function () { return "[object NaiveDict]"; }; var dict = new NaiveDict(); dict.alice = 34; dict.bob = 24; dict.chris = 62; dict.count(); // 5
上述代碼的問題在于我們使用同一個對象來存儲NaiveDict數(shù)據(jù)結(jié)構(gòu)的固定屬性(count和toString)和特定字典的變化條目(alice、bob和chris)。因此,當(dāng)調(diào)用count來枚舉字典的所有屬性時,它會枚舉出所有的屬性(count、toString、alice、bob和chris),而不是僅僅枚舉出我們關(guān)心的條目。
一個相似的錯誤是使用數(shù)組類型來表示字典。
var dict = new Array(); dict.alice = 34; dict.bob = 24; dict.chris = 62; dict.bob; // 24
上述代碼面對原型污染時很脆弱。原型污染指當(dāng)枚舉字典的條目時,原型對象中的屬性可能會導(dǎo)致出現(xiàn)一些不期望的屬性。例如,應(yīng)用程序中的其他庫可能決定增加一些便利的方法到Array.prototype中。
Array.prototype.first = function () { return this[0]; }; Array.prototype.last = function () { return this[this.length - 1]; }; var names = []; for (var name in dict) { names.push(name); } names; // ["alice", "bob", "chris", "first", "last"]
這告訴我們將對象作為輕量級字典的首要原則是:應(yīng)該僅僅將Object的直接實例作為字典,而不是其子類(例如,NaiveDict),當(dāng)然也不是數(shù)組。
var dict = {}; dict.alice = 34; dict.bob = 24; dict.chris = 62; var names = []; for (var name in dict) { names.push(name); } names; // ["alice", "bob", "chris"]
當(dāng)然,這仍然不能保證對于原型污染時安全的,因為任何人仍然能增加屬性到Object.prototype中,但是通過使用Object的直接實例,我們可以將風(fēng)險僅僅局限于Object.prototype。
提示:
使用對象字面量構(gòu)建輕量級字典。
輕量級字典應(yīng)該是Object.prototype的直接子類,以使for ... in循環(huán)免收原型污染。
使用null原型以防止原型污染在ES5未發(fā)布之前,你可能會嘗試設(shè)置一個構(gòu)造函數(shù)的原型屬性為null或者undefined來創(chuàng)建一個空原型的新對象。
但實例化該構(gòu)造函數(shù)仍然得到的是Object的實例。
function C() {} C.prototype = null; var o = new C(); Object.getPrototypeOf(o) === null; // false Object.getPrototypeOf(o) === Object.prototype; // true
ES5首先提供了標(biāo)準(zhǔn)方法來創(chuàng)建一個沒有原型的對象。
var o = Object.create(null); Object.getPrototypeOf(o) === null; // true
一些不支持Object.create函數(shù)的舊的JavaScript環(huán)境可能支持另一種值得一提的方式。
var o = { __proto__: null }; o instanceof Object; // false (no-standard)
提示:
在ES5環(huán)境中,使用Object.create(null)創(chuàng)建的自由原型的空對象是不太容易被污染的。
在一些老的環(huán)境中,考慮使用{ __proto__: null }。
但是注意__proto__既不標(biāo)準(zhǔn),已不是完全可移植的,并且可能在未來的JavaScript環(huán)境中去除。
絕不要使用“__proto__”名作為字典的key,因為一些環(huán)境將其作為特殊的屬性對待。
使用hasOwnProperty方法以避免原型污染JavaScript的對象操作總是以繼承的方式工作,即使是一個空的對象字面量也繼承了Object.prototype的大量屬性。
var dict = {}; "alice" in dict; // false "toString" in dict; // true
幸運的是,Object.prototype提供了hasOwnProperty方法,當(dāng)測試字典條目時它可以避免原型污染。
dict.hasOwnProperty("alice"); // false dict.hasOwnProperty("toString"); // false
我們還可以通過在屬性查找時使用一個測試來防止其受污染的影響。
dict.hasOwnProperty("alice") ? dict.alice : undefined;
hasOwnProperty方法繼承自Object.prototype對象,但是如果在字典中存儲一個同為“hasOwnProperty”名稱的條目,那么原型中的hasOwnProperty方法不能再被獲取到。
dict.hasOwnProperty = 10; dict.hasOwnProperty("alice"); // error: dict.hasOwnProperty is not a function
此時我們可以采用call方法,而不用將hasOwnProperty作為字典的方法來調(diào)用。
var hasOwn = Object.prototype.hasOwnProperty; // 或者,var hasOwn = {}.hasOwnProperty; hasOwn.call(dict, "alice");
為了避免在所有查找屬性的地方都插入這段樣本代碼,我們可以將該模式抽象到Dict的構(gòu)造函數(shù)中。該構(gòu)造函數(shù)封裝了所有在單一數(shù)據(jù)類型定義中編寫健壯字典的技術(shù)細(xì)節(jié)。
function Dict(elements) { // allow an optional initial table this.elements = elements || {}; // simple Object } Dict.prototype.has = function (key) { // own property only return {}.hasOwnProperty.call(this.elements, key); }; Dict.prototype.get = function (key) { // own property only return this.has(key) ? this.elements[key] : undefined; }; Dict.prototype.set = function (key, val) { this.elements[key] = val; }; Dict.prototype.remove = function (key) { delete this.elements[key]; }; var dict = new Dict({ alice: 34, bob: 24, chris: 62 }); dict.has("alice"); // true dict.get("bob"); // 24 dict.has("toString"); // false
上述代碼比使用JavaScript默認(rèn)的對象語法更健壯,而且也同樣方便使用。
在一些JavaScript的環(huán)境中,特殊的屬性名__proto__可能導(dǎo)致其自身的污染問題。
在某些環(huán)境中,__proto__屬性只是簡單地繼承自Object.prototype,因此空對象是真正的空對象。
var empty = Object.create(null); "__proto__" in empty; // false (in some environments) var hasOwn = {}.hasOwnProperty; hasOwn.call(empty, "__proto__"); // false (in some environments)
在其他的環(huán)境中,只有in操作符輸入為true。
var empty = Object.create(null); "__proto__" in empty; // true (in some environments) var hasOwn = {}.hasOwnProperty; hasOwn.call(empty, "__proto__"); // false (in some environments)
不幸的是,某些環(huán)境會因為存在一個實例屬性__proto__而永久地污染所有的對象。
var empty = Object.create(null); "__proto__" in empty; // true (in some environments) var hasOwn = {}.hasOwnProperty; hasOwn.call(empty, "__proto__"); // true (in some environments)
這意味著,在不同的環(huán)境中,下面的代碼可能有不同的結(jié)果。
var dict = new Dict(); dict.has("__proto__"); // ?
為了達(dá)到最大的可移植性和安全性,我們只能為每個Dict方法的“__proto__”關(guān)鍵字增加一種特例。
function Dict(elements) { // allow an optional initial table this.elements = elements || {}; // simple Object this.hasSpecialProto = false; // has "__proto__" key? this.specialProto = undefined; // "__proto__" element } Dict.prototype.has = function (key) { if (key === "__proto__") { return this.hasSpecialProto; } // own property only return {}.hasOwnProperty.call(this.elements, key); }; Dict.prototype.get = function (key) { if (key === "__proto__") { return this.specialProto; } // own property only return this.has(key) ? this.elements[key] : undefined; }; Dict.prototype.set = function (key, val) { if (key === "__proto__") { this.hasSpecialProto = true; this.specialProto = val; } else { this.elements[key] = val; }; } Dict.prototype.remove = function (key) { if (key === "__proto__") { this.hasSpecialProto = false; this.specialProto = undefined; } else { delete this.elements[key]; } }; var dict = new Dict(); dict.has("__proto__"); // false
不管環(huán)境是否處理__proto__屬性,該實現(xiàn)保證是可工作的。
提示:
使用hasOwnProperty方法避免原型污染。
使用詞法作用域和call方法避免覆蓋hasOwnProperty方法。
考慮在封裝hasOwnProperty測試樣板代碼的類中實現(xiàn)字典操作。
使用字典類避免將“__proto__”作為key來使用。
使用數(shù)組而不要使用字典來存儲有序集合直觀地說,一個JavaScript對象是一個無序的屬性集合。ECMAScript標(biāo)準(zhǔn)并為規(guī)定屬性存儲的任何特定順序,甚至對于枚舉對象也沒涉及。
這導(dǎo)致的問題是,for ... in循環(huán)會挑選一定的順序來枚舉對象的屬性。一個常見的錯誤是提供一個API,要求一個對象表示一個從字符串到值的有序映射,例如,創(chuàng)建一個有序的報表。
function report(highScores) { var result = ""; var i = 1; for (var name in highScores) { // unpredictable order result += i + ". " + name + ": " + highScores[name] + " "; i++; } return result; } report([{ name: "Hank", points: 1110100 }, { name: "Steve", points: 1064500 }, { name: "Billy", points: 1050200 }]); // ?
由于不同的環(huán)境可以選擇以不同的順序來存儲和枚舉對象屬性,所以這個函數(shù)會導(dǎo)致產(chǎn)生不同的字符串,得到順序混亂的“最高分”報表。
如果你需要依賴一個數(shù)據(jù)結(jié)構(gòu)中的條目順序,請使用數(shù)組而不是字典。如果上述例子中的report函數(shù)的API使用一個對象數(shù)組而不是單個對象,那么它完全可以工作在任何JavaScript環(huán)境中。
function report(highScores) { var result = ""; for (var i = 0, n = highScores.length; i < n; i++) { var score = highScores[i]; result += (i + 1) + ". " + score.name + ": " + score.points + " "; } return result; } report([{ name: "Hank", points: 1110100 }, { name: "Steve", points: 1064500 }, { name: "Billy", points: 1050200 }]); // 1. Hank: 1110100 2. Steve: 1064500 3. Billy: 1050200
一個微妙的順序依賴的典型例子是浮點型運算。假設(shè)有一個映射標(biāo)題和等級的電影字典。
var ratings = { "Good Will Hunting": 0.8, "Mystic River": 0.7, "21": 0.6, "Doubt": 0.9 }; var total = 0, count = 0; for (var key in ratings) { // unpredictable order total += ratings[key]; count++; } total /= count; total; // ?
浮點型算術(shù)運算的四舍五入會導(dǎo)致計算順序的微妙依賴。當(dāng)組合未定義順序的枚舉時,可能會導(dǎo)致循環(huán)不可預(yù)知。
事實證明,流行的JavaScript環(huán)境實際上使用不同的順序執(zhí)行這個循環(huán)。
一些環(huán)境根據(jù)加入對象的順序來枚舉對象的key
(0.8 + 0.7 + 0.6 + 0.9) / 4 // 0.75
其他環(huán)境總是先枚舉潛在的數(shù)組索引,然后才是其他key。例如,電影“21”的名字恰好是一個可行的數(shù)組索引。
(0.6 + 0.8 + 0.7 + 0.9) / 4 // 0.7499999999999999
這種情況下,更好的表示方式是在字典中使用整數(shù)值。
(8 + 7 + 6 + 9) / 4 / 10 // 0.75 (6 + 8 + 7 + 9) / 4 / 10 // 0.75
提示:
使用for ... in循環(huán)來枚舉對象屬性應(yīng)當(dāng)與順序無關(guān)。
如果聚集運算字典中的數(shù)據(jù),確保聚集操作與數(shù)據(jù)無關(guān)。
使用數(shù)組而不是字典來存儲有序集合。
絕不要在Object.prototype中增加可枚舉的屬性for ... in循環(huán)非常便利,但它很容易受到原型污染的影響。例如,如果我們增加一個產(chǎn)生對象屬性名數(shù)組的allKeys方法。
Object.prototype.allKeys = function () { var result = []; for (var key in this) { result.push(key); } return result; }; ({ a: 1, b: 2, c: 3 }).allKeys(); // ["a", "b", "c", "allKeys"]
遺憾的是,該方法也污染了其自身。
更為友好的是將allKeys定義為一個函數(shù)而不是方法。
function allKeys(obj) { var result = []; for (var key in obj) { result.push(key); } return result; }
如果你確實想在Object.prototype增加屬性,ES5提供了一種更加友好的機(jī)制。
Object.defineProperty方法可以定義一個對象的屬性并指定該屬性的元數(shù)據(jù)。
Object.defineProperty(Object.prototype, "allKeys", { value: function () { var result = []; for (var key in this) { result.push(key); } return result; }, wirtable: true, enumerable: false, configurable: true });
提示:
避免在Object.prototype中增加屬性。
考慮編寫一個函數(shù)代替Object.prototype方法。
如果你確實需要在Object.prototype中增加屬性,請使用ES5中的Object.defineProperty方法將它們定義為不可枚舉的屬性。
避免在枚舉期間修改對象一個社交網(wǎng)絡(luò)有一組成員,每個成員有一個存儲其朋友信息的注冊列表。
function Member(name) { this.name = name; this.friends = []; } var a = new Member("Alice"), b = new Member("Bob"), c = new Member("Carol"), d = new Member("Dieter"), e = new Member("Eli"), f = new Member("Fatima"); a.friends.push(b); b.friends.push(c); c.friends.push(e); d.friends.push(b); e.friends.push(d, f);
搜索該網(wǎng)絡(luò)意味著需要遍歷該社交網(wǎng)絡(luò)圖。這通常通過工作集(work-set)來實現(xiàn)。工作集以單個根節(jié)點開始,然后添加發(fā)現(xiàn)的節(jié)點,移除訪問過的節(jié)點。
Member.prototype.inNetwork = function (other) { var visited = {}; var workset = {}; workset[this.name] = this; // 工作集以單個根節(jié)點開始 for (var name in workset) { var member = workset[name]; delete workset[name]; // modified while enumerating 移除訪問過的節(jié)點 if (name in visited) { // don"t revisit members continue; } visited[name] = member; if (member === other) { // found? return true; } member.friends.forEach(function (friend) { // 添加發(fā)現(xiàn)的節(jié)點 workset[friend.name] = friend; }); } return false; };
不幸的是,在許多JavaScript環(huán)境中這段代碼根本不能工作。
a.inNetwork(f); // false
事實上,ECMAScript對并發(fā)修改在不同JavaScript環(huán)境下的行為規(guī)定了:如果被枚舉的對象在枚舉期間添加了新的屬性,那么在枚舉期間并不能保證新添加的屬性能夠被訪問。也就是,如果我們修改了被枚舉的對象,則不能保證for ... in循環(huán)的行為是可預(yù)見的。
讓我們進(jìn)行另一種遍歷圖的嘗試。這次自己管理循環(huán)控制。當(dāng)我們使用循環(huán)時,應(yīng)該使用自己的字典抽象以避免原型污染。
function WorkSet() { this.entries = new Dict(); this.count = 0; } Workset.prototype.isEmpty = function () { return this.count === 0; }; WorkSet.prototype.add = function (key, val) { if (this.entries.has(key)) { return; } this.entries.set(key, val); this.count++; }; WorkSet.prototype.get = function (key) { return this.entries.get(key); }; WorkSet.prototype.remove = function (key) { if (!this.entries.has(key)) { return; } this.entries.remove(key); this.count--; }; WorkSet.prototype.pick = function () { return this.entries.pick(); }; Dict.prototype.pick = function () { for (var key in this.elements) { if (this.has(key)) { return key; } } throw new Error("empty dictionary"); };
現(xiàn)在我們可以使用簡單的while循環(huán)來實現(xiàn)inNetwork方法。
Member.prototype.inNetwork = function (other) { var visited = {}; var workset = new WorkSet(); workset.add(this.name, this); // 工作集以單個根節(jié)點開始 while (!workset.isEmpty()) { var name = workset.pick(); var member = workset.get(name); workset.remove(name); // 移除訪問過的節(jié)點 if (name in visited) { // don"t revisit members continue; } visited[name] = member; if (member === other) { // found? return true; } member.friends.forEach(function (friend) { // 添加發(fā)現(xiàn)的節(jié)點 workset.add(friend.name, friend); }); } return false; };
pick方法是一個不確定性的例子。不確定性指的是一個操作并不能保證使用語言的語義產(chǎn)生一個單一的可預(yù)見的結(jié)果。這個不確定性來源于這樣一個事實:for ... in循環(huán)可能在不同的JavaScript環(huán)境中選擇不同的枚舉順序。
將工作條目存儲到數(shù)組中而不是集合中,則inNetwork方法將總是以完全相同的順序遍歷圖。
Member.prototype.inNetwork = function (other) { var visited = {}; var worklist = [this]; // 工作集以單個根節(jié)點開始 while (worklist.length > 0) { var member = worklist.pop(); // 移除訪問過的節(jié)點 if (member.name in visited) { // don"t revisit continue; } visited[member.name] = member; if (member === other) { // found? return true; } member.friends.forEach(function (friend) { // 添加發(fā)現(xiàn)的節(jié)點 worklist.push(friend); // add to work-list }); } return false; };
提示:
當(dāng)使用for ... in循環(huán)枚舉一個對象的屬性時,確保不要修改該對象。
當(dāng)?shù)粋€對象時,如果該對象的內(nèi)容可能會在循環(huán)期間被改變,應(yīng)該使用while循環(huán)或經(jīng)典的for循環(huán)來代替for ... in循環(huán)。
為了在不斷變化的數(shù)據(jù)結(jié)構(gòu)中能夠預(yù)測枚舉,考慮使用一個有序的數(shù)據(jù)結(jié)構(gòu),例如數(shù)組,而不要使用字典對象。
數(shù)組迭代要優(yōu)先使用for循環(huán)而不是for...in循環(huán)var scores = [98, 74, 85, 77, 93, 100, 89]; var total = 0; for (var score in scores) { total += score; } var mean = total / scores.length; mean; // ?
for ... in循環(huán)始終枚舉所有的key,即使是數(shù)組的索引屬性,對象屬性key始終是字符串。所以最終mean值為17636.571428571428。
迭代數(shù)組內(nèi)容的正確方法是使用傳統(tǒng)的for循環(huán)。
var scores = [98, 74, 85, 77, 93, 100, 89]; var total = 0; for (var i = 0, n = scores.length; i < n; i++) { total += scores[i]; } var mean = total / scores.length; mean; // 88
提示:
迭代數(shù)組的索引屬性應(yīng)當(dāng)總是使用for循環(huán)而不是for ... in循環(huán)。
考慮在循環(huán)之前將數(shù)組的長度存儲在一個局部變量中以避免重新計算數(shù)組長度。
迭代方法優(yōu)于循環(huán)JavaScript的for循環(huán)相當(dāng)簡潔。但是搞清楚終止條件是一個累贅。
for (var i = 0; i <= n; i++) { ... } // extra end iteration for (var i = 1; i < n; i++) { ... } // missing first iteration for (var i = n; i >= 0; i--) { ... } // extra start iteration for (var i = n - 1; i > 0; i--) { ... } // missing last iteration
ES5為最常用的一些模式提供了便利的方法。
Array.prototype.forEach是其中最簡單的一個。
for (var i = 0, n = players.length; i < n; i++) { players[i].score++; } // 可用以下代碼替代上面的循環(huán) players.forEach(function (p) { p.score++; });
另一種常見的模式是對數(shù)組的每個元素進(jìn)行一些操作后建立一個新的數(shù)組。
var trimmed = []; for (var i = 0, n = input.length; i < n; i++) { trimmed.push(input[i].trim()); } // 可用以下代碼替代上面的循環(huán) var trimmed = []; input.forEach(function (s) { trimmed.push(s.trim()); });
通過現(xiàn)有的數(shù)組建立一個新的數(shù)組的模式是如此的普遍,所以ES5引入了Array.prototype.map方法使該模式更簡單、更優(yōu)雅。
var trimmed = input.map(function (s) { return s.trim(); });
另一個種常見的模式是計算一個新的數(shù)組,該數(shù)組只包含現(xiàn)有數(shù)組的一些元素。Array.prototype.filter使其變得很簡便。
listings.filter(function (listing) { return listing.price >= min && listing.price <= max; });
我們可以定義自己的迭代抽象。例如,提取出滿足謂詞的數(shù)組的前幾個元素。
function takeWhile(a, pred) { var result = []; for (var i = 0, n = a.length; i < n; i++) { if (!pred(a[i], i)) { break; } result[i] = a[i]; } return result; } var prefix = takeWhile([1, 2, 4, 8, 16, 32], function (n) { return n < 10; }); // [1, 2, 4, 8]
我們也可以將takeWhile函數(shù)添加到Array.prototype中使其作為一個方法(前參閱前面關(guān)于對類似Array.prototype的標(biāo)準(zhǔn)原型添加猴子補(bǔ)丁的影響的討論)。
Array.prototype.takeWhile = function (pred) { var result = []; for (var i = 0, n = this.length; i < n; i++) { if (!pred(this[i], i)) { break; } result[i] = this[i]; } return result; }; var prefix = [1, 2, 4, 8, 16, 32].takeWhile(function (n) { return n < 10; }); // [1, 2, 4, 8]
循環(huán)只有一點優(yōu)于迭代函數(shù),那就是前者有控制流操作,如break和continue。舉例來說,使用forEach方法來實現(xiàn)takeWhile函數(shù)將是一個尷尬的嘗試。
function takeWhile(a, pred) { var result = []; a.forEach(function (x, i) { if (!pred(x)) { // ? } result[i] = x; }); return result; }
我們可以使用一個內(nèi)部異常來提前終止該循環(huán),但是這既尷尬有效率低下。
function takeWhile(a, pred) { var result = []; var earlyExit = {}; // unique value signaling loop break try { a.forEach(function (x, i) { if (!pred(x)) { throw earlyExit; } result[i] = x; }); } catch (e) { if (e !== earlyExit) { // only catch earlyExit throw e; } } return result; }
此外,ES5的數(shù)組方法some和every可以用于提前終止循環(huán)。
some方法返回一個布爾值表示其回調(diào)對數(shù)組的任何一個元素是否返回了一個真值。
[1, 10, 100].some(function (x) { return x > 5; }); // true [1, 10, 100].some(function (x) { return x < 0; }); // false
every方法返回一個布爾值表示其回調(diào)是否對數(shù)組的所有元素返回了一個真值。
[1, 2, 3, 4, 5].every(function (x) { return x > 0; }); // true [1, 2, 3, 4, 5].some(function (x) { return x < 3; }); // false
這兩個方法都是短路循環(huán)(short-circuiting)。如果對some方法的回調(diào)一旦產(chǎn)生了一個真值,則some方法會直接返回,不會執(zhí)行其余的元素。相似的,every方法的回調(diào)一旦產(chǎn)生了假值,則會立即返回。
可以使用every實現(xiàn)takeWhile函數(shù)。
function takeWhile(a, pred) { var result = []; a.every(function(x, i) { if (!pred(x)) { return false; // break } result[i] = x; return true; // continue }); return result; } var arr = [1, 2, 4, 8, 16, 32]; // arr數(shù)組里的元素須從小到大排序 var prefix = takeWhile(arr, function (n) { return n < 10; }); // [1, 2, 4, 8]
提示:
使用迭代方法(如Array.prototype.forEach和Array.prototype.map)替代for循環(huán)使得代碼更可讀,并且避免了重復(fù)循環(huán)控制邏輯。
使用自定義的迭代函數(shù)來抽象未被標(biāo)準(zhǔn)庫支持的常見循環(huán)模式。
在需要提前終止循環(huán)的情況下,仍然推薦使用傳統(tǒng)的循環(huán)。另外,some和every方法也可用于提前退出。
在類數(shù)組對象上復(fù)用通用的數(shù)組方法Array.prototype中的標(biāo)準(zhǔn)方法被設(shè)計成其他對象可復(fù)用的方法,即使這些對象并沒有繼承Array。
例如,函數(shù)的arguments對象沒有繼承Array.prototype,但是我們可以提取出forEach方法對象的引用并使用call方法來遍歷每一個參數(shù)。
function highlight() { [].forEach.call(arguments, function (widget) { widget.setBackground("yellow"); }); }
在Web平臺,DOM(Document Object Model)的NodeList類是另一個類數(shù)組對象的實例。
數(shù)組對象的基本契約總共有兩個簡單的規(guī)則:
具有一個范圍在0到22^32 - 1的整型length屬性。
length屬性大于該對象的最大索引。
這就是一個對象需要實現(xiàn)的與Array.prototype中任一方法兼容的所有行為。
一個簡單的對象字面量可以用來創(chuàng)建一個類數(shù)組對象。
var arrayLike = { 0: "a", 1: "b", 2: "c", length: 3, }; var result = Array.prototype.map.call(arrayLike, function (s) { return s.toUpperCase(); }); // ["A", "B, "C"]
字符串也表現(xiàn)為不可變的數(shù)組,因為它們是可索引的,并且其長度也可以通過length屬性獲取。
var result = Array.prototype.map.call("abc", function (s) { return s.toUpperCase(); }); // ["A", "B, "C"]
模擬JavaScript數(shù)組的所有行為很精妙,這要歸功于數(shù)組行為的兩個方面。
將length屬性值設(shè)為小于n的值會自動地刪除索引值大于或等于n的所有屬性。
增加一個索引值為n(大于或等于length屬性值)的屬性會自動地設(shè)置length屬性為n + 1。
幸運的是,對于使用Array.prototype中的方法,這兩條規(guī)則都不是必須的,因為在增加或刪除索引屬性的時候它們都會強(qiáng)制地更新length屬性。
var arrayLike = { 0: "a", 1: "b", 2: "c", length: 3, }; Array.prototype.pop.call(arrayLike); arrayLike; // { 0: "a", 1: "b", length: 2 }
只有一個Array方法不是完全通用的,即數(shù)組連接方法concat。
function namesColumn() { return ["Names"].concat(arguments); } namesColumn("Alice", "Bob", "Chris"); // ["Names", { 0: "Alice", 1: "Bob", 2: "Chris" }]
為了使concat方法將一個類數(shù)組對象視為真正的數(shù)組,我們不得不自己轉(zhuǎn)換該數(shù)組。
function namesColumn() { return ["names"].concat([].slice.call(arguments)); } namesColumn("Alice", "Bob", "Chris"); // ["Names", "Alice", "Bob", "Chris"]
提示:
對于類數(shù)組對象,通過提取方法對象并使用其call方法來復(fù)用通用的Array方法。
任意一個具有索引屬性和恰當(dāng)length屬性的對象都可以使用通用的Array方法。
數(shù)組字面量優(yōu)于數(shù)組構(gòu)造函數(shù)字面量是一種表示數(shù)組的優(yōu)雅的方法。
var a = [1, 2, 3, 4, 5]; // 也可以使用數(shù)組構(gòu)造函數(shù)來替代 var a = new Array(1, 2, 3, 4, 5);
事實證明,Array構(gòu)造函數(shù)存在一些微妙的問題。
首先,你必須確保,沒有人重新包裝過Array類。
function f(Array) { return new Array(1, 2, 3, 4, 5); } f(String); // new String(1)
你還必須確保沒有人修改過全局的Array變量。
Array = String; new Array(1, 2, 3, 4, 5); // new String(1)
如果使用單個數(shù)字來調(diào)用Array構(gòu)造函數(shù),效果完全不同。
var arr1 = [17]; // 創(chuàng)建一個元素只有17的數(shù)組,其長度屬性為1 var arr2 = new Array(17); // 創(chuàng)建一個沒有元素的數(shù)組,但其長度屬性為17
提示:
如果數(shù)組構(gòu)造函數(shù)的第一個參數(shù)是數(shù)字則數(shù)組的構(gòu)造函數(shù)行為是不同的。
使用數(shù)組字面量替代數(shù)組構(gòu)造函數(shù)。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/94745.html
摘要:是開發(fā)的引擎它是開源的,而且是用編寫的。本文的目的是展示和理解如何工作,以便為客戶端或服務(wù)器端應(yīng)用程序生成優(yōu)化的代碼。將如何處理這種情況事實上,每當(dāng)構(gòu)造函數(shù)聲明一個屬性并跟蹤隱藏類的變化時,就會創(chuàng)建一個新的隱藏類。 V8是google開發(fā)的JavaScript引擎, 它是開源的 ,而且是用C++編寫的。它是用于客戶端(Google Chrome)和服務(wù)器端(node.js)JavaSc...
摘要:鋪墊已了,進(jìn)入今天的正題,貓薦書系列之五高性能編程本書適合已入門還想要進(jìn)階和提高的讀者閱讀。書中列舉了兩個慘痛的教訓(xùn)華爾街公司騎士資本由于軟件升級引入的錯誤,損失億美元公司小時全球中斷的嚴(yán)重事故。 showImg(https://segmentfault.com/img/bVbm92w?w=6720&h=4480); 稍微關(guān)心編程語言的使用趨勢的人都知道,最近幾年,國內(nèi)最火的兩種語言非...
摘要:鋪墊已了,進(jìn)入今天的正題,貓薦書系列之五高性能編程本書適合已入門還想要進(jìn)階和提高的讀者閱讀。書中列舉了兩個慘痛的教訓(xùn)華爾街公司騎士資本由于軟件升級引入的錯誤,損失億美元公司小時全球中斷的嚴(yán)重事故。 showImg(https://segmentfault.com/img/bVbm92w?w=6720&h=4480); 稍微關(guān)心編程語言的使用趨勢的人都知道,最近幾年,國內(nèi)最火的兩種語言非...
閱讀 3346·2021-11-25 09:43
閱讀 3134·2021-10-11 10:58
閱讀 2735·2021-09-27 13:59
閱讀 3074·2021-09-24 09:55
閱讀 2166·2019-08-30 15:52
閱讀 1826·2019-08-30 14:03
閱讀 2256·2019-08-30 11:11
閱讀 2020·2019-08-28 18:12