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 的锁定修改的保护功能。
来源:oschina
链接:https://my.oschina.net/u/1378524/blog/187049