2016-10-31 34 views
15

我讀過一些文章,建議在JavaScript中擴展內置對象是一個壞主意。例如說,我添加了一個first功能Array ...擴展JavaScript的內置類型 - 它是邪惡的嗎?

Array.prototype.first = function(fn) { 
    return this.filter(fn)[0]; 
}; 

大,所以現在我可以得到基於謂詞的第一個元素。但是,當ECMAScript-20xx決定首先添加到規範並實現它不同時會發生什麼? - 好了,一下子,我的代碼假設一個非標準的實現,開發商失去信心等

於是我決定創建自己的類型......

var Enumerable = (function() { 
    function Enumerable(array) { 
     this.array = array; 
    } 
    Enumerable.prototype.first = function (fn) { 
     return this.array.filter(fn)[0]; 
    }; 
    return Enumerable; 
}()); 

所以現在,我可以將一個數組傳遞給一個新的Enumerable,並首先調用Enumerable實例。大!我尊重ECMAScript-20xx規範,我仍然可以做我想做的事情。

然後發佈ES20XX + 1規範,它引入了一個Enumerable類型,它甚至沒有第一種方法。現在會發生什麼?

本文的癥結歸結於此;擴展內置類型有多糟,以及我們如何避免將來的實現衝突?

注意:命名空間的使用可能是解決這個問題的一種方法,但是它又不是這樣!

var Collection = { 
    Enumerable: function() { ... } 
}; 

當ECMAScript規範引入Collection時會發生什麼?

+0

永遠不能避免碰撞。你所能做的最好的事情就是儘可能地爲你的實體命名,儘可能少地使用全局元素,比如命名空間。並且交叉你的手指。 –

+1

另請參見[我可以安全地擴展JavaScript內置類?](http://stackoverflow.com/q/8262751/1048572),[擴展本地內置](http://perfectionkills.com/extending-native-builtins), [JavaScript:擴展Array.prototype有什麼危險?](http://stackoverflow.com/q/8859828/1048572),[使用Prototype擴展本地對象不好?](http://stackoverflow.com/q/10197174/1048572),[爲什麼擴展原生對象是一種不好的做法?](http://stackoverflow.com/q/14034180/1048572),[在ECMAScript 5之後重新擴展原生原型](http://stackoverflow.com/q/11781878/1048572)等等。 – Bergi

+0

提示:您的'第一個'方法已經可用於名稱['find'](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find)自ES6以來。 – Bergi

回答

14

這就是爲什麼您必須儘可能少地污染全局命名空間的原因。完全避免任何形式的衝突的唯一途徑是通過IIFE中定義的所有內容:

(function() { 
    let Collection = ... 
})(); 

當且僅當您需要定義全局對象,比如因爲你是一個庫,你想使用由第三方,你應該定義一個名字,這是非常不可能發生衝突,例如,因爲它是你的公司名稱:

new google.maps.Map(...) 

你定義一個全局對象的任何時間,其中包括對現有的類型,你的新方法運行一些其他圖書館或未來ECMAScript標準的風險,試圖在未來某個時候選擇這個名字。

6

第一種方法的問題是,您在頁面上擴展了所有腳本的原型。

如果您要包含依賴新的本地ES-20xx Array.prototype.first方法的第三方腳本,那麼您可以使用代碼將其分解。

第二個例子只是一個真正的問題,如果你要使用全局變量(我希望你沒有這樣做......)。那麼同樣的問題可能會發生。

事情是,spec authors are increasingly wary of not destroying the existing web,所以他們必須重新命名未來的功能,如果太多的現有網站中斷。(這也是爲什麼你不應該爲網絡平臺規範創建polyfills,但還沒有最終確定,順便說一句)

如果你創建一個庫或一個框架來擴展本地對象,並被100s或1000個網站。那時你開始阻礙標準流程。

如果你在一個函數或塊範圍內使用你的變量,並且你不依賴未來的功能,你應該沒問題。

函數作用域例如:

(function() { 
    var Enumerable = {}; 
})(); 

塊範圍例如:

{ 
    const Enumerable = {}; 
} 
2

警告:剛愎自用答案。

我不相信擴展JavaScript對象是「邪惡的」。顯然存在危險,你需要知道這些(如你清楚)。我唯一需要警惕的是,如果你這樣做,你必須引入一種警告你自己的衝突方法。

換句話說,而不是簡單地對數組原型定義first功能,你應該先看看Array.prototype.first定義,如果是,則拋出(或提醒自己這場衝突以其他方式)。只有當它沒有被定義時,你才允許你的代碼來定義它,否則你會替換每個人的定義。

+1

這個工具(擴展Javascript對象)在定義上是道德中立的。榮譽指出。 – Mindwin

-1

這個問題和這些答案看起來非常ECMA5導向,而不是6。當ECMA6帶來類的時候,使用原型是沒用的。沒有太多的理由不會添加到內置的原型中,但更主要的是,'如果你可以避免它,你應該'。

首先,當您添加到內置類型時,您可能會破壞舊版本的瀏覽器,也可能會因'... in'在基本級別上被調用而中斷'for(i in array)'格式,它可能會有意想不到的結果。

在我看來,主要原因是在ECMA6中有一個非常好的選擇,它是子類。如果不使用任何原型,只是用這樣的:

class myArray extends array { 
    constructor() { ... } 

    getFirst() { 
     return this[0]; 
    } 

    // Or make a getter 

    get first() { 
     return this[0]; 
    } 
} 

你可以把你的吸氣劑需要的任何功能,但它仍然是獨立的代碼,不會對未來的命名空間侵入。但是,由於您正在進行分類,您不必重寫數組。所以,它不是純粹的邪惡,但更多的是更好的編碼,而不是這樣做,就像在一個長頁面中編寫所有的JavaScript並不一定是邪惡的一樣,但如果你能避免它,它是更好的標準做法。

+0

JavaScript一直是面嚮對象語言,繼承一直是可能的。 ES6類僅僅是更好的語法。 – deceze

+0

正確...因此,downvote是因爲替代是無關緊要的?延伸是我的答案。每個答案都是關於原型的,就像問題一樣,我的回答是他們構建了很好的語法,以便可以擴展內置類型而不損壞它。 – EvSunWoodard

+1

這個'myArray'類如何等於任何OP的例子? OP的第一個代碼示例的優點是你可以執行'[1,2,3] .first()',這對你的代碼來說顯然是不可能的。此外,您的答案不回答實際問題。 – idmean

3

這裏的危險要比碰撞未來的ES20xx標準要微妙得多。至少在那裏你可以刪除自己的代碼,並處理任何潛在的行爲差異的影響。

危險是您和其他人都決定擴展具有不一致行爲的內置類型

考慮這樣的事情

// In my team's codebase 
// Empty strings are empty 
String.prototype.isEmpty = function() { return this.length === 0; } 

// In my partner team's codebase 
// Strings consisting any non-whitespace chars are non-empty 
String.prototype.isEmpty = function() { return this.trim().length === 0; } 

你有isEmpty兩個同樣有效的定義和最後一個勝場。想象一下,當你的單元測試通過,你的合作伙伴團隊的單元測試通過,但你不能將你的代碼庫合併到一個單獨的網頁中,而不會發生真正奇怪的崩潰,這取決於哪些庫被加載持續。這是一個可維護性噩夢

然後,你開始和你的夥伴團隊進入一個房間,並爭論了六個小時,他們對isEmpty的定義是「正確的」,會做出一個任意的決定,你們兩個人中的一個會看到每一個在您的代碼庫中單個實例isEmpty以確定如何使其與新函數一起工作。你可能會在這個過程中引入一些新的錯誤。再次,當你發現你無論在數量想要一個toInteger功能,但沒有想到要舉辦什麼用負數

// -3.1 --> -4  
Number.prototype.toInteger = function() { return Math.floor(this); } 

// -3.1 --> -3  
Number.prototype.toInteger = function() { return Math.trunc(this); } 
1

作爲一個做了全公司的會議

然後重複整個過程除了提供的所有驚人答案之外,您可以做與polyfills相同的操作。

通常,當有人寫一些與原型混淆的東西時,會檢查是否已經定義了一個值。

下面是一個例子:

if(!Array.prototype.first) { 
    Array.prototype.first = function(fn) { 
     return this.filter(fn)[0]; 
    }; 
} 

這是罰款的代碼小塊,但是會提供大型圖書館的問題。
實現一個匿名的自我調用函數可以在這方面幫助,像這樣:

(function(){ 
    // Leave if it is already defined 
    if(Array.prototype.first) { 
     return; 
    } 

    Array.prototype.first = function(fn) { 
     return this.filter(fn)[0]; 
    }; 
})(); 

這種情況以前提供的所有解決方案的benefict。

如果您想減少下載代碼的佔用空間,可以動態加載所需的所需腳本。
如果創建了新的Enumerable,則可以使用瀏覽器所具有的或您自己的(如果沒有)。
這非常依賴於您的代碼庫。

+0

但是,沒有標準的第一個''方法,所以你不能'填充'它,只有當它不存在時才覆蓋它的方法不適用,如果它稍後被標準化爲不同的東西。 – Bergi

+0

@Bergi這是一個缺點:/但有一個體面的工具包(瀏覽器控制檯),你可以嘗試追溯所有發生的錯誤。取決於代碼的大小,可以通過適中的難度找到。 –