[Effective JavaScript 筆記]第48條:避免在枚舉期間修改對象

来源:http://www.cnblogs.com/wengxuesong/archive/2016/06/28/5622204.html
-Advertisement-
Play Games

for...in迴圈在運行時出錯了錯誤,並沒有要求枚舉對象的修改與當前保持一致。事實上,ES對併發修改在不同js環境下的行為的規範留有餘地。標準規定: 如果被枚舉的對象在枚舉期間添加了新的屬性,那麼在枚舉期間並不能保證新添加的屬性能被訪問。 上面規範的實際後果:如果我們修改了被枚舉的對象,則不能保證... ...


註冊列表示例

一個社交網路有一組成員,每個成員有一個存儲其朋友信息的註冊列表。

function Member(name){
    this.name=name;
    this.friends=[];
}
var a=new Member('鐘二'),
    b=new Member('張三'),
    c=new Member('趙四'),
    d=new Member('王五'),
    e=new Member('阮六'),
    f=new Member('耿七');
a.friends.push(b);
b.friends.push(c);
c.friends.push(e);
d.friends.push(b);
e.friends.push(d,f);

搜索該網路意味著需要遍歷該社交網路圖。
1467075315501
通常通過工作集來實現。工作集以單個根節點開始,然後添加發現的節點,移除訪問過的節點。

for...in遍歷圖

使用for...in迴圈來實現該遍歷是很方便的。

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];
        if(name in visited){
           continue;
        }
        visited[name]=member;
        if(member===other){
            return true;
        }
        member.friends.forEach(function(friend){
            workset[friend.name]=friend;
        });
    }
    return false;
}

上面代碼有什麼問題嘛?在大多環境下可以工作,但有一些環境這段代碼就不能工作了。

a.inNetwork(f);//false

這裡為什麼呢。這裡說明for...in迴圈在運行時出錯了錯誤,並沒有要求枚舉對象的修改與當前保持一致。事實上,ES對併發修改在不同js環境下的行為的規範留有餘地。標準規定:
如果被枚舉的對象在枚舉期間添加了新的屬性,那麼在枚舉期間並不能保證新添加的屬性能被訪問。
上面規範的實際後果:如果我們修改了被枚舉的對象,則不能保證for...in迴圈的行為是可預見的。

另一種遍歷圖

自己管得迴圈控制。當使用迴圈時,應該使用自己的字典抽象以避免原型污染。可以將字典放置在WorkSet類中來追蹤當前集合中的元素數量。

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

為了提取集合的任意一個元素,給Dict類添加一個新方法

Dict.prototype.pick=function(){
    for(var key in this.elements){
       if(this.has(key)){
           return key;
       }
    }
    throw new Error('empty dictionary');
};
WorkSet.prototype.pick=function(){
    return this.entries.pick();
};

下麵改寫上一版本的inNetwork方法,這裡使用while來迴圈。每次選擇任意一個元素並從工作集中刪除。

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){
           continue;
        }
        visited[name]=member;
        if(member === other){
           return true;
        }
        member.friends.forEach(function(friend){
           workset.add(friend.name,friend);
        })
    }
    return false;
};

其中pick方法是一個不確定性的例子。不確定性是指一個操作並不能保證使用語言的主義產生一個單一的可預見的結果。這個不確定性是因為for...in迴圈可能在不同的js環境中選擇不同的枚舉順序。使用不確定性可能會使你的程式引入一個不可預測的元素。測試可能在某個平臺通過,某些平臺不通過,或同一平臺不同時候,結果也可能不同。

工用列表演算法

不確定性的來源是難以避免的,考慮使用一個確定的工作集演算法替代方案。即工作列表演算法。將工作條目存儲到數組中而不是集合中,則inNetwork方法,將總是以相同的順序遍歷圖。

Member.prototype.inNetwork=function(other){
    var visited={};
    var worklist=[this];
    while(worklist.length>0){
        var member=worklist.pop();
        if(member.name in visited){
           continue;
        }
        visited[memeber.name]=member;
        if(member === other){
           return true;
        }
        member.friends.forEach(function(friend){
           worklist.push(friend);
        })
    }
    return false;
};

這一版本inNetwork方法會確定性地添加和刪除工作條目。無論發現什麼路徑,該方法對於連接的成員總是返回true,所以最終結果是一樣的。

提示

  • 當使用for...in迴圈枚舉一個對象的屬性時,確保不要修改對象

  • 當迭代一個對象時,如果該對象的內容可能會在迴圈期間被改變,應該使用while迴圈或經典的for迴圈來代替for...in迴圈

  • 為了在不斷變化的數據結構中能夠預測枚舉,考慮使用一個有序的數據結構,例如數組,而不要使用字典

附錄:示例完整版

function Member(name){
    this.name=name;
    this.friends=[];
}
Member.prototype.inNetwork=function(other){
    var visited={};
    var worklist=[this];
    while(worklist.length>0){
        var member=worklist.pop();
        if(member.name in visited){
           continue;
        }
        visited[member.name]=member;
        if(member === other){
           return true;
        }
        member.friends.forEach(function(friend){
           worklist.push(friend);
        })
    }
    return false;
};


//測試代碼
var a=new Member('鐘二'),
    b=new Member('張三'),
    c=new Member('趙四'),
    d=new Member('王五'),
    e=new Member('阮六'),
    f=new Member('耿七');
a.friends.push(b);
b.friends.push(c);
c.friends.push(e);
d.friends.push(b);
e.friends.push(d,f);

a.inNetwork(f);//true
a.inNetwork(d);//true
f.inNetwork(a);//false

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 題目:從上往下列印出二叉樹的每個結點,同一層的結點按照從左到右的順序列印。 思路:每一次列印一個結點的時候,如果該結點有子結點,則把該結點的子結點放到一個隊列的末尾。接下來到隊列的頭部取出最早進入隊列的結點,重覆前面的列印操作,直至隊列中所有的結點都被列印出來為止。 ...
  • 利用Python的os.walk()方法對文件目錄進行歷遍操作,得到文件目錄信息,方便下一步操作 ...
  • 題目:判斷一數字序列是否為這些數字入棧的一種出棧方式(前提:棧中的數字不重覆) 思路1:如果下一個彈出的數字剛好是棧頂數字,那麼直接彈出。如果下一個彈出的數字不在棧頂,我們把壓棧序列還沒有入棧的數字壓入輔助棧,知道把下一個要彈出的數字壓入棧頂為止。如果所有的數字都壓入了仍然沒有找到下一個彈出的數字, ...
  • 轉自:http://www.cnblogs.com/Braveliu/p/5100018.html 轉在瞭解了《phpcms V9 URL訪問解析》之後,我們已經知道首頁最終執行的是content模塊下index控制器的init方法。 下麵, 我們逐步分析過程如下: 第一、首頁預設執行的是index... ...
  • 最近在搞一個小程式,會用到動態修改配置文件來進行處理,在百度上找了很多辦法,但是始終達不到我預想的效果,先列出程式運行環境和開發工具版本: 開發工具:VS2010 .Net 運行環境:4.0 有兩種方式,分別如下: 第一種方式:只能在程式運行和調試時有效,在程式打包成安裝包並安裝之後會出現問題,完整 ...
  • 以字元串的形式輸出。 1、response.getWriter().write("您好中國hello"); 如果這樣輸出的話。則瀏覽器結果為: 2、加上代碼 response.setCharacterEncoding("UTF-8"); response.getWriter().write("您好中 ...
  • #include <DbgHelp.h> #pragma comment(lib, "dbghelp.lib") LONG WINAPI TopLevelExceptionFilter(struct _EXCEPTION_POINTERS *pExceptionInfo) { //cout << " ...
  • 近日,Yelp的SRE工程師Dmitriy Samovskiy發表了一篇標題為 《新時代的運維》 的文章,文章中他簡要介紹了在雲計算大環境下,運維工作重心和角色的變化。 其實在6年前,Dmitriy就寫過一篇關於 DevOps趨勢 的文章,在那篇文章中,他認為系統管理人員需要具有簡單腳本之外的開發能 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...