《编写可维护的 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 的锁定修改的保护功能。

相关文章
相关标签/搜索