編寫高質量JavaScript代碼之數組和字典

參考書本:《Effective JavaScript》

數組和字典

對象是JavaScript中最全能的數據結構。取決於差別的環境,對象可以示意一個天真的鍵值關聯紀錄,一個繼續了要領的面向對象數據籠統,一個麋集或希罕的數組,或一個散列表。

運用Object的直接實例組織輕量級的字典

JavaScript對象的中心是一個字符串屬性稱號和屬性值的映照表。這使得運用對象完成字典輕而易舉,因為字典就是可變長的字符串與值的映照鳩合。

JavaScript供應了羅列一個對象屬性名的利器,for ... in輪迴,然則其除了羅列出對象“本身”的屬性外,還會羅列出繼續過來的屬性。

假如我們豎立一個自定義的字典並將其元素作為該字典對象本身的屬性。

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數據結構的牢固屬性(count和toString)和特定字典的變化條目(alice、bob和chris)。因而,當挪用count來羅列字典的一切屬性時,它會羅列出一切的屬性(count、toString、alice、bob和chris),而不是僅僅羅列出我們體貼的條目。

一個相似的毛病是運用數組範例來示意字典。

var dict = new Array();

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

dict.bob; // 24

上述代碼面臨原型污染時很軟弱。原型污問鼎當羅列字典的條目時,原型對象中的屬性可以會致使湧現一些不希冀的屬性。比方,應用程序中的其他庫可以決議增添一些方便的要領到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']

這通知我們將對象作為輕量級字典的主要原則是:應該僅僅將Object的直接實例作為字典,而不是其子類(比方,NaiveDict),固然也不是數組。

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']

固然,這依然不能保證關於原型污染時平安的,因為任何人依然能增添屬性到Object.prototype中,然則經由過程運用Object的直接實例,我們可以將風險僅僅局限於Object.prototype

提醒:

  • 運用對象字面量構建輕量級字典。
  • 輕量級字典應該是Object.prototype的直接子類,以使for ... in輪迴免收原型污染。

運用null原型以防備原型污染

在ES5未宣布之前,你可以會嘗試設置一個組織函數的原型屬性為null或許undefined來豎立一個空原型的新對象。

但實例化該組織函數依然獲得的是Object的實例。

function C() {}
C.prototype = null;

var o = new C();
Object.getPrototypeOf(o) === null; // false
Object.getPrototypeOf(o) === Object.prototype; // true

ES5起首供應了規範要領來豎立一個沒有原型的對象。

var o = Object.create(null);

Object.getPrototypeOf(o) === null; // true

一些不支撐Object.create函數的舊的JavaScript環境可以支撐另一種值得一提的體式格局。

var o = { __proto__: null };

o instanceof Object; // false (no-standard)

提醒:

  • 在ES5環境中,運用Object.create(null)豎立的自在原型的空對象是不太輕易被污染的。
  • 在一些老的環境中,斟酌運用{ __proto__: null }
  • 然則注重__proto__既不規範,已不是完整可移植的,而且可以在將來的JavaScript環境中去除。
  • 毫不要運用“__proto__”名作為字典的key,因為一些環境將其作為特別的屬性看待。

運用hasOwnProperty要領以防止原型污染

JavaScript的對象操縱老是以繼續的體式格局事情,縱然是一個空的對象字面量也繼續了Object.prototype的大批屬性。

var dict = {};

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

榮幸的是,Object.prototype供應了hasOwnProperty要領,當測試字典條目時它可以防止原型污染。

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作為字典的要領來挪用。

var hasOwn = Object.prototype.hasOwnProperty;
// 或許,var hasOwn = {}.hasOwnProperty;

hasOwn.call(dict, 'alice');

為了防止在一切查找屬性的處所都插進去這段樣本代碼,我們可以將該形式籠統到Dict的組織函數中。該組織函數封裝了一切在單一數據範例定義中編寫硬朗字典的技術細節

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默許的對象語法更硬朗,而且也一樣方便運用。

在一些JavaScript的環境中,特別的屬性名__proto__可以致使其本身的污染題目。

  • 在某些環境中,__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)
  • 在其他的環境中,只要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)
  • 不幸的是,某些環境會因為存在一個實例屬性__proto__而永遠地污染一切的對象。

    var empty = Object.create(null);
    '__proto__' in empty; // true (in some environments)
    
    var hasOwn = {}.hasOwnProperty;
    hasOwn.call(empty, '__proto__'); // true (in some environments)

這意味着,在差別的環境中,下面的代碼可以有差別的結果。

var dict = new Dict();
dict.has('__proto__'); // ?

為了到達最大的可移植性和平安性,我們只能為每一個Dict要領的“__proto__”關鍵字增添一種慣例。

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 

不論環境是不是處置懲罰__proto__屬性,該完成保證是可事情的。

提醒:

  • 運用hasOwnProperty要領防止原型污染。
  • 運用詞法作用域和call要領防止掩蓋hasOwnProperty要領。
  • 斟酌在封裝hasOwnProperty測試榜樣代碼的類中完成字典操縱。
  • 運用字典類防止將“__proto__”作為key來運用。

運用數組而不要運用字典來存儲有序鳩合

直觀地說,一個JavaScript對象是一個無序的屬性鳩合。ECMAScript規範併為劃定屬性存儲的任何特定遞次,以至關於羅列對象也沒觸及。

這致使的題目是,for ... in輪迴會遴選肯定的遞次來羅列對象的屬性。一個罕見的毛病是供應一個API,請求一個對象示意一個從字符串到值的有序映照,比方,豎立一個有序的報表。

function report(highScores) {
    var result = '';
    var i = 1;

    for (var name in highScores) { // unpredictable order
        result += i + '. ' + name + ': ' + highScores[name] + '\n';
        i++;
    }

    return result;
}

report([{ name: 'Hank', points: 1110100 },
    { name: 'Steve', points: 1064500 },
    { name: 'Billy', points: 1050200 }]); // ?

因為差別的環境可以挑選以差別的遞次來存儲和羅列對象屬性,所以這個函數會致使發生差別的字符串,獲得遞次雜沓的“最高分”報表。

假如你須要依靠一個數據結構中的條目遞次,請運用數組而不是字典。假如上述例子中的report函數的API運用一個對象數組而不是單個對象,那末它完整可以事情在任何JavaScript環境中。

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 + '\n';
    }

    return result;
}

report([{ name: 'Hank', points: 1110100 },
    { name: 'Steve', points: 1064500 },
    { name: 'Billy', points: 1050200 }]); // 1. Hank: 1110100\n2. Steve: 1064500\n3. Billy: 1050200\n

一個玄妙的遞次依靠的典範例子是浮點型運算。假設有一個映照題目和品級的影戲字典。

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

浮點型算術運算的四舍五入會致使盤算遞次的玄妙依靠。當組合未定義遞次的羅列時,可以會致使輪迴不可預知。

現實證明,盛行的JavaScript環境實際上運用差別的遞次實行這個輪迴。

  • 一些環境依據到場對象的遞次來羅列對象的key

    (0.8 + 0.7 + 0.6 + 0.9) / 4 // 0.75
  • 其他環境老是先羅列潛伏的數組索引,然後才是其他key。比方,影戲“21”的名字恰好是一個可行的數組索引。

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

這類情況下,更好的示意體式格局是在字典中運用整數值。

(8 + 7 + 6 + 9) / 4 / 10 // 0.75
(6 + 8 + 7 + 9) / 4 / 10 // 0.75

提醒:

  • 運用for ... in輪迴來羅列對象屬性應該與遞次無關。
  • 假如群集運算字典中的數據,確保群集操縱與數據無關。
  • 運用數組而不是字典來存儲有序鳩合。

毫不要在Object.prototype中增添可羅列的屬性

for ... in輪迴異常方便,但它很輕易遭到原型污染的影響。比方,假如我們增添一個發生對象屬性名數組的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定義為一個函數而不是要領。

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

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

    return result;
}

假如你確切想在Object.prototype增添屬性,ES5供應了一種越發友愛的機制。

Object.defineProperty要領可以定義一個對象的屬性並指定該屬性的元數據。

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中增添屬性。
  • 斟酌編寫一個函數替換Object.prototype要領。
  • 假如你確切須要在Object.prototype中增添屬性,請運用ES5中的Object.defineProperty要領將它們定義為不可羅列的屬性。

防止在羅列時期修正對象

一個交際收集有一組成員,每一個成員有一個存儲其朋儕信息的註冊列表。

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);

搜刮該收集意味着須要遍歷該交際收集圖。這一般經由過程事情集(work-set)來完成。事情集以單個根節點最先,然後增加發明的節點,移除接見過的節點

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

    workset[this.name] = this; // 事情集以單個根節點最先

    for (var name in workset) {
        var member = workset[name];
        delete workset[name]; // modified while enumerating 移除接見過的節點
 
        if (name in visited) { // don't revisit members
            continue;
        }
        visited[name] = member;

        if (member === other) { // found?
            return true;
        }

        member.friends.forEach(function (friend) { // 增加發明的節點
            workset[friend.name] = friend;
        });
    }

    return false;
};

不幸的是,在很多JavaScript環境中這段代碼基礎不能事情。

a.inNetwork(f); // false

現實上,ECMAScript對併發修正在差別JavaScript環境下的行動劃定了:假如被羅列的對象在羅列時期增加了新的屬性,那末在羅列時期並不能保證新增加的屬性可以被接見。也就是,假如我們修正了被羅列的對象,則不能保證for ... in輪迴的行動是可預感的。

讓我們舉行另一種遍歷圖的嘗試。此次本身治理輪迴掌握。當我們運用輪迴時,應該運用本身的字典籠統以防止原型污染。

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');
};

如今我們可以運用簡樸的while輪迴來完成inNetwork要領。

Member.prototype.inNetwork = function (other) {
    var visited = {};
    var workset = new WorkSet();
    workset.add(this.name, this); // 事情集以單個根節點最先
    
    while (!workset.isEmpty()) {
        var name = workset.pick();
        var member = workset.get(name);
        workset.remove(name); // 移除接見過的節點

        if (name in visited) { // don't revisit members
            continue;
        }

        visited[name] = member;

        if (member === other) { // found?
            return true;
        }

        member.friends.forEach(function (friend) { // 增加發明的節點
            workset.add(friend.name, friend);
        });
    }

    return false;
};

pick要領是一個不確定性的例子。不確定性指的是一個操縱並不能保證運用言語的語義發生一個單一的可預感的結果。這個不確定性來源於如許一個現實:for ... in輪迴可以在差別的JavaScript環境中挑選差別的羅列遞次。

將事情條目存儲到數組中而不是鳩合中,則inNetwork要領將老是以完整相同的遞次遍歷圖。

Member.prototype.inNetwork = function (other) {
    var visited = {};
    var worklist = [this]; // 事情集以單個根節點最先

    while (worklist.length > 0) {
        var member = worklist.pop(); // 移除接見過的節點

        if (member.name in visited) { // don't revisit
            continue;
        }
        visited[member.name] = member;
        
        if (member === other) { // found? 
            return true;
        }

        member.friends.forEach(function (friend) { // 增加發明的節點
            worklist.push(friend); // add to work-list
        });
    }

    return false;
};

提醒:

  • 當運用for ... in輪迴羅列一個對象的屬性時,確保不要修正該對象。
  • 當迭代一個對象時,假如該對象的內容可以會在輪迴時期被轉變,應該運用while輪迴或典範的for輪迴來替換for ... in輪迴。
  • 為了在不停變化的數據結構中可以展望羅列,斟酌運用一個有序的數據結構,比方數組,而不要運用字典對象。

數組迭代要優先運用for輪迴而不是for…in輪迴

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輪迴一直羅列一切的key,縱然是數組的索引屬性,對象屬性key一直是字符串。所以終究mean值為17636.571428571428。

迭代數組內容的準確要領是運用傳統的for輪迴。

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

提醒:

  • 迭代數組的索引屬性應該老是運用for輪迴而不是for ... in輪迴。
  • 斟酌在輪迴之前將數組的長度存儲在一個局部變量中以防止從新盤算數組長度。

迭代要領優於輪迴

JavaScript的for輪迴相稱簡約。然則搞清楚停止前提是一個累墜。

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++;
}

// 可用以下代碼替換上面的輪迴
players.forEach(function (p) {
    p.score++;
});

另一種罕見的形式是對數組的每一個元素舉行一些操縱后豎立一個新的數組。

var trimmed = [];

for (var i = 0, n = input.length; i < n; i++) {
    trimmed.push(input[i].trim());
}

// 可用以下代碼替換上面的輪迴
var trimmed = [];

input.forEach(function (s) {
    trimmed.push(s.trim());
});

經由過程現有的數組豎立一個新的數組的形式是云云的廣泛,所以ES5引入了Array.prototype.map要領使該形式更簡樸、更文雅。

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

另一個種罕見的形式是盤算一個新的數組,該數組只包括現有數組的一些元素。Array.prototype.filter使其變得很輕便。

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

我們可以定義本身的迭代籠統。比方,提掏出滿足謂詞的數組的前幾個元素。

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函數增加到Array.prototype中使其作為一個要領(前參閱前面關於對相似Array.prototype的規範原型增加猴子補丁的影響的議論)。

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]

輪迴只要一點優於迭代函數,那就是前者有掌握流操縱,如break和continue。舉例來說,運用forEach要領來完成takeWhile函數將是一個為難的嘗試。

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

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

        result[i] = x;
    });

    return result;
}

我們可以運用一個內部異常來提早停止該輪迴,然則這既為難有效率低下。

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的數組要領some和every可以用於提早停止輪迴。

some要領返回一個布爾值示意其回調對數組的任何一個元素是不是返回了一個真值。

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

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

every要領返回一個布爾值示意其回調是不是對數組的一切元素返回了一個真值。

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

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

這兩個要領都是短路輪迴(short-circuiting)。假如對some要領的回調一旦發生了一個真值,則some要領會直接返回,不會實行其他的元素。相似的,every要領的回調一旦發生了假值,則會馬上返回。

可以運用every完成takeWhile函數。

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數組裡的元素須從小到大排序
var prefix = takeWhile(arr, function (n) {
    return n < 10;
}); // [1, 2, 4, 8]

提醒:

  • 運用迭代要領(如Array.prototype.forEachArray.prototype.map)替換for輪迴使得代碼更可讀,而且防止了反覆輪迴掌握邏輯。
  • 運用自定義的迭代函數來籠統未被規範庫支撐的罕見輪迴形式。
  • 在須要提早停止輪迴的情況下,依然引薦運用傳統的輪迴。別的,some和every要領也可用於提早退出。

在類數組對象上復用通用的數組要領

Array.prototype中的規範要領被設想成其他對象可復用的要領,縱然這些對象並沒有繼續Array。

比方,函數的arguments對象沒有繼續Array.prototype,然則我們可以提掏出forEach要領對象的援用並運用call要領來遍歷每一個參數。

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

在Web平台,DOM(Document Object Model)的NodeList類是另一個類數組對象的實例。

數組對象的基礎左券總共有兩個簡樸的劃定規矩:

  • 具有一個範圍在0到22^32 – 1的整型length屬性。
  • length屬性大於該對象的最大索引。

這就是一個對象須要完成的與Array.prototype中任一要領兼容的一切行動。

  • 一個簡樸的對象字面量可以用來豎立一個類數組對象。

    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']
  • 字符串也表現為不可變的數組,因為它們是可索引的,而且其長度也可以經由過程length屬性獵取。

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

模仿JavaScript數組的一切行動很精巧,這要歸功於數組行動的兩個方面。

  • 將length屬性值設為小於n的值會自動地刪除索引值大於或即是n的一切屬性。
  • 增添一個索引值為n(大於或即是length屬性值)的屬性會自動地設置length屬性為n + 1。

榮幸的是,關於運用Array.prototype中的要領,這兩條劃定規矩都不是必需的,因為在增添或刪除索引屬性的時刻它們都邑強迫地更新length屬性。

var arrayLike = {
    0: 'a',
    1: 'b',
    2: 'c',
    length: 3,
};
Array.prototype.pop.call(arrayLike);
arrayLike; // { 0: 'a', 1: 'b', length: 2 }

只要一個Array要領不是完整通用的,即數組銜接要領concat。

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

為了使concat要領將一個類數組對象視為真正的數組,我們不能不本身轉換該數組。

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

提醒:

  • 關於類數組對象,經由過程提取要領對象並運用其call要領來複用通用的Array要領。
  • 恣意一個具有索引屬性和適當length屬性的對象都可以運用通用的Array要領。

數組字面量優於數組組織函數

字面量是一種示意數組的文雅的要領。

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

// 也可以運用數組組織函數來替換
var a = new Array(1, 2, 3, 4, 5);

現實證明,Array組織函數存在一些玄妙的題目。

  • 起首,你必需確保,沒有人從新包裝過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)
  • 假如運用單個数字來挪用Array組織函數,結果完整差別。

    var arr1 = [17]; // 豎立一個元素只要17的數組,其長度屬性為1
    
    var arr2 = new Array(17); // 豎立一個沒有元素的數組,但其長度屬性為17

提醒:

  • 假如數組組織函數的第一個參數是数字則數組的組織函數行動是差別的。
  • 運用數組字面量替換數組組織函數。
    原文作者:3santiago3
    原文地址: https://segmentfault.com/a/1190000014744300
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞