《編寫可維護的 JavaScript》讀書筆記第11章:不是你的對象不要動

1. 什麼是你的

只要維護代碼是你的責任,那麼你就擁有這些對象。數組

若是你的代碼沒有建立這些對象,不要修改它們,包括:瀏覽器

  • 原生對象(Object、Array 等)安全

  • DOM 對象(例如,document)app

  • 瀏覽器對象模型(BOM)對象(例如,window)函數

  • 類庫的對象
    工具

2. 原則

把已存在的 JavaScript 對象如一個實用工具函數庫同樣來對待。this

  • 不覆蓋方法spa

  • 不新增方法prototype

  • 不刪除方法插件

2.1 不覆蓋方法

// 很差的寫法
document._orginalGetElementById = document.getElementById;
document.getElementById = function(id) {
    if (id == "window") {
        return window;
    } else {
        return document._originalGetElementById(id);
    }
};

在一個大型的項目中,一個此類問題會致使浪費大量時間和金錢。

2.2 不新增方法

爲非本身擁有的對象增長方法,會致使命名衝突。由於一個對象此刻沒有某個方法不表明它將來也沒有。若是未來原生的方法和你的方法行爲不一致,你將陷入一場代碼維護的噩夢。

大多數 JavaScript 庫代碼有一個插件機制,容許爲代碼庫安全地新增一些功能,這是最佳最可維護的途徑。

2.3 不刪除方法

最簡單地刪除一個方法的方式就是將其賦值爲 null。

// 很差的寫法 - 刪除了 DOM 方法
document.getElementById = null;

也能夠用 delete 操做符來刪除對象的屬性或方法,但在 prototype 的屬性或方法上是不起做用的。

var person = {
    name: "Nicholas"
};
delete person.name;
console.log(person.name); // undefined

刪除一個已存在對象的方法是糟糕的實踐。

3. 更好的途徑

在 JavaScript 中有兩種基本的繼承方式:基於對象的繼承和基於類型的繼承。

在 JavaScript 中,繼承仍然有一些很大的限制:

  • 不能從 DOM 或 BOM 對象繼承

  • 繼承 Array 不能正常工做

3.1 基於對象的繼承

也叫原型繼承。ECMAScript5 的 Object.create() 方法是實現這種繼承的最簡單的方式。

var person = {
    name: "Nicholas",
    sayName: function() {
        alert(this.name);
    }
};

var myPerson = Object.create(person);
myPerson.sayName(); // 彈出 "Nicholas"

從新定義 myPerson.sayName() 會自動切斷對 person.sayName() 的訪問。

Object.create() 方法能夠指定第二個參數,爲新對象添加新的屬性和方法:

var myPerson = Object.create(person, {
    name: {
        value: "Greg"
    }
});

myPerson.sayName(); // 彈出 "Greg"
person.sayName(); // 彈出 "Nicholas"

新對象能夠隨意修改。

3.2 基於類型的繼承

基於類型的繼承是經過構造函數實現的,而非對象。

function MyError(message) {
    this.message = message;
}

MyError.prototype = new Error();

var error = new MyError("Something bad happened.");

console.log(error instanceof Error); // true
console.log(error instanceof MyError); // true

3.3 門面模式

門面模式爲一個已存在的對象建立一個新的接口。門面有時也叫包裝器。

jQuery 和 YUI 的 DOM 接口都使用了門面。

function DOMWrapper(element) {
    this.element = element;
}

DOMWrapper.prototype.addClass = function(className) {
    element.className += " " + className;
};

DOMWrapper.prototype.remove = function() {
    this.element.parentNode.removeChild(this.element);
};

// 用法 
var wrapper = new DOMWrapper(document.getElementById("my-div"));
// 添加一個 className
wrapper.addClass("selected");
// 刪除元素
wrapper.remove();

門面和適配器惟一的不一樣是前者建立新接口,後者實現已存在的接口。

4. 關於 Polyfill 的註解

polyfill 是對某種功能的模擬,這些功能在新版本的瀏覽器中有完整的定義和原生實現。例如 ECMAScript5 爲數組增長了 forEach() 函數。該方法在 ECMAScript3 中有模擬實現,這樣就能夠在老版本瀏覽器上使用這個方法了。

polyfills 的關鍵在於它們的模擬實現要與瀏覽器原生實現保持徹底兼容。爲了達到這個目的,polyfills 常常會給非本身擁有的對象新增一些方法。

從最佳的可維護性角度而言,避免使用 polyfills。

5. 阻止修改

ECMAScript5 引入了幾個防止對象修改的方法。有三種鎖定修改的級別:

  • 防止擴展:禁止爲對象「添加」屬性和方法,但已存在屬性和方法能夠被修改或刪除

  • 密封:在防止擴展的基礎上,進一步禁止爲對象「刪除」已存在屬性和方法

  • 凍結:在密封基礎上,進一步禁止爲對象「修改」已存在屬性和方法(全部字段均只讀)

var person = {
    name: "Nicholas"
};

// 鎖定對象
Object.preventExtension(person);
console.log(Object.isExtensible(person)); // false
person.age = 25; // 正常狀況悄悄地失敗,除非在嚴格模式下拋出錯誤

// 密封對象
Object.seal(person);
console.log(Object.isExtensible(person)); // false
console.log(Object.isSealed(person)); // true
delete person.name; // 正常狀況悄悄地失敗,除非在嚴格模式下拋出錯誤
person.age = 25; // 同上

// 凍結對象
Object.freeze(person);
console.log(Object.isExtensible(person)); // false
console.log(Object.isSealed(person)); // true
console.log(Object.isFrozen(person)); // true
person.name = "Greg"; // 正常悄悄地失敗,除非在嚴格模式下拋出錯誤
person.age = 25; // 同上
delete person.name; // 同上

若是決定將你的對象鎖定修改,強烈建議使用嚴格模式。

未來,原生 JavaScript 對象和 DOM 對象頗有可能都將統一內置使用 ECMAScript5 的鎖定修改的保護功能。

相關文章
相關標籤/搜索