《编写可维护的 JavaScript》读书笔记第11章:不是你的对象不要动

半城伤御伤魂 提交于 2019-11-27 16:45:50

1. 什么是你的

只要维护代码是你的责任,那么你就拥有这些对象。

如果你的代码没有创建这些对象,不要修改它们,包括:

  • 原生对象(Object、Array 等)

  • DOM 对象(例如,document)

  • 浏览器对象模型(BOM)对象(例如,window)

  • 类库的对象

2. 原则

把已存在的 JavaScript 对象如一个实用工具函数库一样来对待。

  • 不覆盖方法

  • 不新增方法

  • 不删除方法

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 的锁定修改的保护功能。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!