成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

編寫高質(zhì)量JavaScript代碼之?dāng)?shù)組和字典

izhuhaodev / 644人閱讀

摘要:但實(shí)例化該構(gòu)造函數(shù)仍然得到的是的實(shí)例。或者,為了避免在所有查找屬性的地方都插入這段樣本代碼,我們可以將該模式抽象到的構(gòu)造函數(shù)中。該構(gòu)造函數(shù)封裝了所有在單一數(shù)據(jù)類型定義中編寫健壯字典的技術(shù)細(xì)節(jié)。

參考書籍:《Effective JavaScript》

數(shù)組和字典

對(duì)象是JavaScript中最萬(wàn)能的數(shù)據(jù)結(jié)構(gòu)。取決于不同的環(huán)境,對(duì)象可以表示一個(gè)靈活的鍵值關(guān)聯(lián)記錄,一個(gè)繼承了方法的面向?qū)ο髷?shù)據(jù)抽象,一個(gè)密集或稀疏的數(shù)組,或一個(gè)散列表。

使用Object的直接實(shí)例構(gòu)造輕量級(jí)的字典

JavaScript對(duì)象的核心是一個(gè)字符串屬性名稱和屬性值的映射表。這使得使用對(duì)象實(shí)現(xiàn)字典易如反掌,因?yàn)樽值渚褪强勺冮L(zhǎng)的字符串與值的映射集合。

JavaScript提供了枚舉一個(gè)對(duì)象屬性名的利器,for ... in循環(huán),但是其除了枚舉出對(duì)象“自身”的屬性外,還會(huì)枚舉出繼承過(guò)來(lái)的屬性。

如果我們創(chuàng)建一個(gè)自定義的字典并將其元素作為該字典對(duì)象自身的屬性。

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

上述代碼的問(wèn)題在于我們使用同一個(gè)對(duì)象來(lái)存儲(chǔ)NaiveDict數(shù)據(jù)結(jié)構(gòu)的固定屬性(count和toString)和特定字典的變化條目(alice、bob和chris)。因此,當(dāng)調(diào)用count來(lái)枚舉字典的所有屬性時(shí),它會(huì)枚舉出所有的屬性(count、toString、alice、bob和chris),而不是僅僅枚舉出我們關(guān)心的條目。

一個(gè)相似的錯(cuò)誤是使用數(shù)組類型來(lái)表示字典。

var dict = new Array();

dict.alice = 34;
dict.bob = 24;
dict.chris = 62;

dict.bob; // 24

上述代碼面對(duì)原型污染時(shí)很脆弱。原型污染指當(dāng)枚舉字典的條目時(shí),原型對(duì)象中的屬性可能會(huì)導(dǎo)致出現(xiàn)一些不期望的屬性。例如,應(yīng)用程序中的其他庫(kù)可能決定增加一些便利的方法到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"]

這告訴我們將對(duì)象作為輕量級(jí)字典的首要原則是:應(yīng)該僅僅將Object的直接實(shí)例作為字典,而不是其子類(例如,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)然,這仍然不能保證對(duì)于原型污染時(shí)安全的,因?yàn)槿魏稳巳匀荒茉黾訉傩缘?b>Object.prototype中,但是通過(guò)使用Object的直接實(shí)例,我們可以將風(fēng)險(xiǎn)僅僅局限于Object.prototype。

提示:

使用對(duì)象字面量構(gòu)建輕量級(jí)字典。

輕量級(jí)字典應(yīng)該是Object.prototype的直接子類,以使for ... in循環(huán)免收原型污染。

使用null原型以防止原型污染

在ES5未發(fā)布之前,你可能會(huì)嘗試設(shè)置一個(gè)構(gòu)造函數(shù)的原型屬性為null或者undefined來(lái)創(chuàng)建一個(gè)空原型的新對(duì)象。

但實(shí)例化該構(gòu)造函數(shù)仍然得到的是Object的實(shí)例。

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)方法來(lái)創(chuàng)建一個(gè)沒(méi)有原型的對(duì)象。

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)建的自由原型的空對(duì)象是不太容易被污染的。

在一些老的環(huán)境中,考慮使用{ __proto__: null }。

但是注意__proto__既不標(biāo)準(zhǔn),已不是完全可移植的,并且可能在未來(lái)的JavaScript環(huán)境中去除。

絕不要使用“__proto__”名作為字典的key,因?yàn)橐恍┉h(huán)境將其作為特殊的屬性對(duì)待。

使用hasOwnProperty方法以避免原型污染

JavaScript的對(duì)象操作總是以繼承的方式工作,即使是一個(gè)空的對(duì)象字面量也繼承了Object.prototype的大量屬性。

var dict = {};

"alice" in dict; // false
"toString" in dict; // true

幸運(yùn)的是,Object.prototype提供了hasOwnProperty方法,當(dāng)測(cè)試字典條目時(shí)它可以避免原型污染。

dict.hasOwnProperty("alice"); // false
dict.hasOwnProperty("toString"); // false

我們還可以通過(guò)在屬性查找時(shí)使用一個(gè)測(cè)試來(lái)防止其受污染的影響。

dict.hasOwnProperty("alice") ? dict.alice : undefined;

hasOwnProperty方法繼承自Object.prototype對(duì)象,但是如果在字典中存儲(chǔ)一個(gè)同為“hasOwnProperty”名稱的條目,那么原型中的hasOwnProperty方法不能再被獲取到。

dict.hasOwnProperty = 10;
dict.hasOwnProperty("alice"); // error: dict.hasOwnProperty is not a function

此時(shí)我們可以采用call方法,而不用將hasOwnProperty作為字典的方法來(lái)調(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)的對(duì)象語(yǔ)法更健壯,而且也同樣方便使用。

在一些JavaScript的環(huán)境中,特殊的屬性名__proto__可能導(dǎo)致其自身的污染問(wèn)題。

在某些環(huán)境中,__proto__屬性只是簡(jiǎn)單地繼承自Object.prototype,因此空對(duì)象是真正的空對(duì)象。

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)境會(huì)因?yàn)榇嬖谝粋€(gè)實(shí)例屬性__proto__而永久地污染所有的對(duì)象。

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á)到最大的可移植性和安全性,我們只能為每個(gè)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__屬性,該實(shí)現(xiàn)保證是可工作的。

提示:

使用hasOwnProperty方法避免原型污染。

使用詞法作用域和call方法避免覆蓋hasOwnProperty方法。

考慮在封裝hasOwnProperty測(cè)試樣板代碼的類中實(shí)現(xiàn)字典操作。

使用字典類避免將“__proto__”作為key來(lái)使用。

使用數(shù)組而不要使用字典來(lái)存儲(chǔ)有序集合

直觀地說(shuō),一個(gè)JavaScript對(duì)象是一個(gè)無(wú)序的屬性集合。ECMAScript標(biāo)準(zhǔn)并為規(guī)定屬性存儲(chǔ)的任何特定順序,甚至對(duì)于枚舉對(duì)象也沒(méi)涉及。

這導(dǎo)致的問(wèn)題是,for ... in循環(huán)會(huì)挑選一定的順序來(lái)枚舉對(duì)象的屬性。一個(gè)常見(jiàn)的錯(cuò)誤是提供一個(gè)API,要求一個(gè)對(duì)象表示一個(gè)從字符串到值的有序映射,例如,創(chuàng)建一個(gè)有序的報(bào)表。

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)境可以選擇以不同的順序來(lái)存儲(chǔ)和枚舉對(duì)象屬性,所以這個(gè)函數(shù)會(huì)導(dǎo)致產(chǎn)生不同的字符串,得到順序混亂的“最高分”報(bào)表。

如果你需要依賴一個(gè)數(shù)據(jù)結(jié)構(gòu)中的條目順序,請(qǐng)使用數(shù)組而不是字典。如果上述例子中的report函數(shù)的API使用一個(gè)對(duì)象數(shù)組而不是單個(gè)對(duì)象,那么它完全可以工作在任何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

一個(gè)微妙的順序依賴的典型例子是浮點(diǎn)型運(yùn)算。假設(shè)有一個(gè)映射標(biāo)題和等級(jí)的電影字典。

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; // ?

浮點(diǎn)型算術(shù)運(yùn)算的四舍五入會(huì)導(dǎo)致計(jì)算順序的微妙依賴。當(dāng)組合未定義順序的枚舉時(shí),可能會(huì)導(dǎo)致循環(huán)不可預(yù)知。

事實(shí)證明,流行的JavaScript環(huán)境實(shí)際上使用不同的順序執(zhí)行這個(gè)循環(huán)。

一些環(huán)境根據(jù)加入對(duì)象的順序來(lái)枚舉對(duì)象的key

(0.8 + 0.7 + 0.6 + 0.9) / 4 // 0.75

其他環(huán)境總是先枚舉潛在的數(shù)組索引,然后才是其他key。例如,電影“21”的名字恰好是一個(gè)可行的數(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)來(lái)枚舉對(duì)象屬性應(yīng)當(dāng)與順序無(wú)關(guān)。

如果聚集運(yùn)算字典中的數(shù)據(jù),確保聚集操作與數(shù)據(jù)無(wú)關(guān)。

使用數(shù)組而不是字典來(lái)存儲(chǔ)有序集合。

絕不要在Object.prototype中增加可枚舉的屬性

for ... in循環(huán)非常便利,但它很容易受到原型污染的影響。例如,如果我們?cè)黾右粋€(gè)產(chǎn)生對(duì)象屬性名數(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定義為一個(gè)函數(shù)而不是方法。

function allKeys(obj) {
    var result = [];

    for (var key in obj) {
        result.push(key);
    }

    return result;
}

如果你確實(shí)想在Object.prototype增加屬性,ES5提供了一種更加友好的機(jī)制。

Object.defineProperty方法可以定義一個(gè)對(duì)象的屬性并指定該屬性的元數(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中增加屬性。

考慮編寫一個(gè)函數(shù)代替Object.prototype方法。

如果你確實(shí)需要在Object.prototype中增加屬性,請(qǐng)使用ES5中的Object.defineProperty方法將它們定義為不可枚舉的屬性。

避免在枚舉期間修改對(duì)象

一個(gè)社交網(wǎng)絡(luò)有一組成員,每個(gè)成員有一個(gè)存儲(chǔ)其朋友信息的注冊(cè)列表。

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ò)圖。這通常通過(guò)工作集(work-set)來(lái)實(shí)現(xiàn)。工作集以單個(gè)根節(jié)點(diǎn)開始,然后添加發(fā)現(xiàn)的節(jié)點(diǎn),移除訪問(wèn)過(guò)的節(jié)點(diǎn)。

Member.prototype.inNetwork = function (other) {
    var visited = {};
    var workset = {};

    workset[this.name] = this; // 工作集以單個(gè)根節(jié)點(diǎn)開始

    for (var name in workset) {
        var member = workset[name];
        delete workset[name]; // modified while enumerating 移除訪問(wèn)過(guò)的節(jié)點(diǎn)
 
        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é)點(diǎn)
            workset[friend.name] = friend;
        });
    }

    return false;
};

不幸的是,在許多JavaScript環(huán)境中這段代碼根本不能工作。

a.inNetwork(f); // false

事實(shí)上,ECMAScript對(duì)并發(fā)修改在不同JavaScript環(huán)境下的行為規(guī)定了:如果被枚舉的對(duì)象在枚舉期間添加了新的屬性,那么在枚舉期間并不能保證新添加的屬性能夠被訪問(wèn)。也就是,如果我們修改了被枚舉的對(duì)象,則不能保證for ... in循環(huán)的行為是可預(yù)見(jiàn)的。

讓我們進(jìn)行另一種遍歷圖的嘗試。這次自己管理循環(huán)控制。當(dāng)我們使用循環(huán)時(shí),應(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)在我們可以使用簡(jiǎn)單的while循環(huán)來(lái)實(shí)現(xiàn)inNetwork方法。

Member.prototype.inNetwork = function (other) {
    var visited = {};
    var workset = new WorkSet();
    workset.add(this.name, this); // 工作集以單個(gè)根節(jié)點(diǎn)開始
    
    while (!workset.isEmpty()) {
        var name = workset.pick();
        var member = workset.get(name);
        workset.remove(name); // 移除訪問(wèn)過(guò)的節(jié)點(diǎn)

        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é)點(diǎn)
            workset.add(friend.name, friend);
        });
    }

    return false;
};

pick方法是一個(gè)不確定性的例子。不確定性指的是一個(gè)操作并不能保證使用語(yǔ)言的語(yǔ)義產(chǎn)生一個(gè)單一的可預(yù)見(jiàn)的結(jié)果。這個(gè)不確定性來(lái)源于這樣一個(gè)事實(shí):for ... in循環(huán)可能在不同的JavaScript環(huán)境中選擇不同的枚舉順序。

將工作條目存儲(chǔ)到數(shù)組中而不是集合中,則inNetwork方法將總是以完全相同的順序遍歷圖。

Member.prototype.inNetwork = function (other) {
    var visited = {};
    var worklist = [this]; // 工作集以單個(gè)根節(jié)點(diǎn)開始

    while (worklist.length > 0) {
        var member = worklist.pop(); // 移除訪問(wèn)過(guò)的節(jié)點(diǎn)

        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é)點(diǎn)
            worklist.push(friend); // add to work-list
        });
    }

    return false;
};

提示:

當(dāng)使用for ... in循環(huán)枚舉一個(gè)對(duì)象的屬性時(shí),確保不要修改該對(duì)象。

當(dāng)?shù)粋€(gè)對(duì)象時(shí),如果該對(duì)象的內(nèi)容可能會(huì)在循環(huán)期間被改變,應(yīng)該使用while循環(huán)或經(jīng)典的for循環(huán)來(lái)代替for ... in循環(huán)。

為了在不斷變化的數(shù)據(jù)結(jié)構(gòu)中能夠預(yù)測(cè)枚舉,考慮使用一個(gè)有序的數(shù)據(jù)結(jié)構(gòu),例如數(shù)組,而不要使用字典對(duì)象。

數(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ù)組的索引屬性,對(duì)象屬性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ù)組的長(zhǎng)度存儲(chǔ)在一個(gè)局部變量中以避免重新計(jì)算數(shù)組長(zhǎng)度。

迭代方法優(yōu)于循環(huán)

JavaScript的for循環(huán)相當(dāng)簡(jiǎn)潔。但是搞清楚終止條件是一個(gè)累贅。

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是其中最簡(jiǎn)單的一個(gè)。

for (var i = 0, n = players.length; i < n; i++) {
    players[i].score++;
}

// 可用以下代碼替代上面的循環(huán)
players.forEach(function (p) {
    p.score++;
});

另一種常見(jiàn)的模式是對(duì)數(shù)組的每個(gè)元素進(jìn)行一些操作后建立一個(gè)新的數(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());
});

通過(guò)現(xiàn)有的數(shù)組建立一個(gè)新的數(shù)組的模式是如此的普遍,所以ES5引入了Array.prototype.map方法使該模式更簡(jiǎn)單、更優(yōu)雅。

var trimmed = input.map(function (s) {
    return s.trim();
});

另一個(gè)種常見(jiàn)的模式是計(jì)算一個(gè)新的數(shù)組,該數(shù)組只包含現(xiàn)有數(shù)組的一些元素。Array.prototype.filter使其變得很簡(jiǎn)便。

listings.filter(function (listing) {
    return listing.price >= min && listing.price <= max;
});

我們可以定義自己的迭代抽象。例如,提取出滿足謂詞的數(shù)組的前幾個(gè)元素。

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中使其作為一個(gè)方法(前參閱前面關(guān)于對(duì)類似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)只有一點(diǎn)優(yōu)于迭代函數(shù),那就是前者有控制流操作,如break和continue。舉例來(lái)說(shuō),使用forEach方法來(lái)實(shí)現(xiàn)takeWhile函數(shù)將是一個(gè)尷尬的嘗試。

function takeWhile(a, pred) {
    var result = [];

    a.forEach(function (x, i) {
        if (!pred(x)) {
            // ?
        }

        result[i] = x;
    });

    return result;
}

我們可以使用一個(gè)內(nèi)部異常來(lá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方法返回一個(gè)布爾值表示其回調(diào)對(duì)數(shù)組的任何一個(gè)元素是否返回了一個(gè)真值。

[1, 10, 100].some(function (x) {
    return x > 5;
}); // true

[1, 10, 100].some(function (x) {
    return x < 0;
}); // false

every方法返回一個(gè)布爾值表示其回調(diào)是否對(duì)數(shù)組的所有元素返回了一個(gè)真值。

[1, 2, 3, 4, 5].every(function (x) {
    return x > 0;
}); // true

[1, 2, 3, 4, 5].some(function (x) {
    return x < 3;
}); // false

這兩個(gè)方法都是短路循環(huán)(short-circuiting)。如果對(duì)some方法的回調(diào)一旦產(chǎn)生了一個(gè)真值,則some方法會(huì)直接返回,不會(huì)執(zhí)行其余的元素。相似的,every方法的回調(diào)一旦產(chǎn)生了假值,則會(huì)立即返回。

可以使用every實(shí)現(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.forEachArray.prototype.map)替代for循環(huán)使得代碼更可讀,并且避免了重復(fù)循環(huán)控制邏輯。

使用自定義的迭代函數(shù)來(lái)抽象未被標(biāo)準(zhǔn)庫(kù)支持的常見(jiàn)循環(huán)模式。

在需要提前終止循環(huán)的情況下,仍然推薦使用傳統(tǒng)的循環(huán)。另外,some和every方法也可用于提前退出。

在類數(shù)組對(duì)象上復(fù)用通用的數(shù)組方法

Array.prototype中的標(biāo)準(zhǔn)方法被設(shè)計(jì)成其他對(duì)象可復(fù)用的方法,即使這些對(duì)象并沒(méi)有繼承Array。

例如,函數(shù)的arguments對(duì)象沒(méi)有繼承Array.prototype,但是我們可以提取出forEach方法對(duì)象的引用并使用call方法來(lái)遍歷每一個(gè)參數(shù)。

function highlight() {
    [].forEach.call(arguments, function (widget) {
        widget.setBackground("yellow");
    });
}

在Web平臺(tái),DOM(Document Object Model)的NodeList類是另一個(gè)類數(shù)組對(duì)象的實(shí)例。

數(shù)組對(duì)象的基本契約總共有兩個(gè)簡(jiǎn)單的規(guī)則:

具有一個(gè)范圍在0到22^32 - 1的整型length屬性。

length屬性大于該對(duì)象的最大索引。

這就是一個(gè)對(duì)象需要實(shí)現(xiàn)的與Array.prototype中任一方法兼容的所有行為。

一個(gè)簡(jiǎn)單的對(duì)象字面量可以用來(lái)創(chuàng)建一個(gè)類數(shù)組對(duì)象。

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ù)組,因?yàn)樗鼈兪强伤饕?,并且其長(zhǎng)度也可以通過(guò)length屬性獲取。

var result = Array.prototype.map.call("abc", function (s) {
    return s.toUpperCase();
}); // ["A", "B, "C"]

模擬JavaScript數(shù)組的所有行為很精妙,這要?dú)w功于數(shù)組行為的兩個(gè)方面。

將length屬性值設(shè)為小于n的值會(huì)自動(dòng)地刪除索引值大于或等于n的所有屬性。

增加一個(gè)索引值為n(大于或等于length屬性值)的屬性會(huì)自動(dòng)地設(shè)置length屬性為n + 1。

幸運(yùn)的是,對(duì)于使用Array.prototype中的方法,這兩條規(guī)則都不是必須的,因?yàn)樵谠黾踊騽h除索引屬性的時(shí)候它們都會(huì)強(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 }

只有一個(gè)Array方法不是完全通用的,即數(shù)組連接方法concat。

function namesColumn() {
    return ["Names"].concat(arguments);
}
namesColumn("Alice", "Bob", "Chris"); // ["Names", { 0: "Alice", 1: "Bob", 2: "Chris" }]

為了使concat方法將一個(gè)類數(shù)組對(duì)象視為真正的數(shù)組,我們不得不自己轉(zhuǎn)換該數(shù)組。

function namesColumn() {
    return ["names"].concat([].slice.call(arguments));
}
namesColumn("Alice", "Bob", "Chris"); // ["Names", "Alice", "Bob", "Chris"]

提示:

對(duì)于類數(shù)組對(duì)象,通過(guò)提取方法對(duì)象并使用其call方法來(lái)復(fù)用通用的Array方法。

任意一個(gè)具有索引屬性和恰當(dāng)length屬性的對(duì)象都可以使用通用的Array方法。

數(shù)組字面量?jī)?yōu)于數(shù)組構(gòu)造函數(shù)

字面量是一種表示數(shù)組的優(yōu)雅的方法。

var a = [1, 2, 3, 4, 5];

// 也可以使用數(shù)組構(gòu)造函數(shù)來(lái)替代
var a = new Array(1, 2, 3, 4, 5);

事實(shí)證明,Array構(gòu)造函數(shù)存在一些微妙的問(wèn)題。

首先,你必須確保,沒(méi)有人重新包裝過(guò)Array類。

function f(Array) {
    return new Array(1, 2, 3, 4, 5);
}
f(String); // new String(1)

你還必須確保沒(méi)有人修改過(guò)全局的Array變量。

Array = String;
new Array(1, 2, 3, 4, 5); // new String(1)

如果使用單個(gè)數(shù)字來(lái)調(diào)用Array構(gòu)造函數(shù),效果完全不同。

var arr1 = [17]; // 創(chuàng)建一個(gè)元素只有17的數(shù)組,其長(zhǎng)度屬性為1

var arr2 = new Array(17); // 創(chuàng)建一個(gè)沒(méi)有元素的數(shù)組,但其長(zhǎng)度屬性為17

提示:

如果數(shù)組構(gòu)造函數(shù)的第一個(gè)參數(shù)是數(shù)字則數(shù)組的構(gòu)造函數(shù)行為是不同的。

使用數(shù)組字面量替代數(shù)組構(gòu)造函數(shù)。

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/94745.html

相關(guān)文章

  • V8引擎是如何工作?

    摘要:是開發(fā)的引擎它是開源的,而且是用編寫的。本文的目的是展示和理解如何工作,以便為客戶端或服務(wù)器端應(yīng)用程序生成優(yōu)化的代碼。將如何處理這種情況事實(shí)上,每當(dāng)構(gòu)造函數(shù)聲明一個(gè)屬性并跟蹤隱藏類的變化時(shí),就會(huì)創(chuàng)建一個(gè)新的隱藏類。 V8是google開發(fā)的JavaScript引擎, 它是開源的 ,而且是用C++編寫的。它是用于客戶端(Google Chrome)和服務(wù)器端(node.js)JavaSc...

    不知名網(wǎng)友 評(píng)論0 收藏0
  • Python貓薦書系列五:Python高性能編程

    摘要:鋪墊已了,進(jìn)入今天的正題,貓薦書系列之五高性能編程本書適合已入門還想要進(jìn)階和提高的讀者閱讀。書中列舉了兩個(gè)慘痛的教訓(xùn)華爾街公司騎士資本由于軟件升級(jí)引入的錯(cuò)誤,損失億美元公司小時(shí)全球中斷的嚴(yán)重事故。 showImg(https://segmentfault.com/img/bVbm92w?w=6720&h=4480); 稍微關(guān)心編程語(yǔ)言的使用趨勢(shì)的人都知道,最近幾年,國(guó)內(nèi)最火的兩種語(yǔ)言非...

    channg 評(píng)論0 收藏0
  • Python貓薦書系列五:Python高性能編程

    摘要:鋪墊已了,進(jìn)入今天的正題,貓薦書系列之五高性能編程本書適合已入門還想要進(jìn)階和提高的讀者閱讀。書中列舉了兩個(gè)慘痛的教訓(xùn)華爾街公司騎士資本由于軟件升級(jí)引入的錯(cuò)誤,損失億美元公司小時(shí)全球中斷的嚴(yán)重事故。 showImg(https://segmentfault.com/img/bVbm92w?w=6720&h=4480); 稍微關(guān)心編程語(yǔ)言的使用趨勢(shì)的人都知道,最近幾年,國(guó)內(nèi)最火的兩種語(yǔ)言非...

    馬永翠 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<