Author: Nicholas C. Zakas
第 9 章, 将配置数据从代码中分离出来
什么是配置数据
配置数据就是在应用中写死 (hardcoded) 的值, 如警告信息, 地址, 某个循环次数限制等.
抽离配置数据
// 将配置数据埋在代码中
function validate(value) {
if (!value) {
alert("数据不合法");
location.href = "/errors/invalid.php";
}
}
// 抽离配置数据
let config = {
MSG_INVALID_VALUE: "数据不合法",
URL_INVALID: "/errors/invalid.php",
};
function validate(value) {
if (!value)
alert(config.MSG_INVALID_VALUE);
location.href = config.URL_INVALID;
}
}
这样人们可以放心地修改这些配置数据而不用担心引起代码错误.
保存配置数据
配置数据最好放在单独的文件中, 以便清晰地分隔数据和应用逻辑. 用 Java 属性文件, JSON 文件, 或 JSONP 文件中都是可取的选项.
第 10 章, 抛出自定义错误
在 JavaScript 中抛出错误
用throw
语句在 JavaScript 中抛出一个错误, 错误可以是任意类型, 但是推荐以Error
对象的形式抛出.
错误的好处
在合适的地方抛出恰当的错误可以使调试变得无比简单.
错误不一定要在程序中被 catch, 因为我们的任务不是避免和处理错误情况, 而是在错误发生时更加容易调试.
何时抛出错误
应该在最容易导致程序失败的地方抛出错误. 以下是一些抛出错误的良好经验:
- 在修复一个很难高度的错误时, 在错误发生的地方抛出错误, 可以使下次发生相同错误时更容易调试
- 在 "如果这地方出错, 逻辑会变得很混乱" 的地方抛出错误
- 在会被别人使用的代码的容易误用的地方抛出错误
try-catch 语句
错误只应该在应用程序栈最深的部分抛出. 任何处理应用程序特定逻辑的代码都应该有错误处理有能力, 并且捕获从底层组件中抛出的错误.
错误类型
ECMA-262 规范指出了 7 种错误类型:
Error
: 所有错误的基本类型, 引擎本身不会抛出此类型错误.
EvalError
: 通过eval()
函数执行代码发生错误时抛出.
RangeError
: 一个数字超出其边界时抛出.
ReferenceError
: 期望的对象不存在时抛出, 如试图在 null 对象上调用一个函数.
SyntaxError
: 给eval()
函数传递的代码中有语法错误时抛出.
TypeError
: 变量不是期望的类型时抛出, 如"prop" in 3
.
URIError
: 给encodeURI()
, encodeURIComponent()
, decodeURI()
或者decodeURIComponent()
等函数传递格式非法的 URI 字符串时抛出.
自定义错误类型:
function MyError(message) {
this.message = message;
}
MyError.prototype = new Error();
// 使用
throw new MyError("Something Wrong!");
第 11 章, 不是你的对象不要动
JavaScript 的一大特性, 或者说一大缺陷是, 所有东西都是可以修改的, 只要能接触到的东西, 都可以被无节制地修改. 因此, 统一一些代码的原则至关重要.
什么是你的
只有你创建的对象是你的!
原则
- 不要覆盖别人的方法
- 不要向别人的对象添加方法
- 不要删除别人的对象的方法
更好的途径
1. 基于对象的继承
let person = {
name: "Nicholas",
sayName: function() {
alert(this.name);
}
};
let myPerson = Object.create(person, {
name: {
value: "Greg"
}
});
myPerson.sayName();
2. 基于类型的继承
function MyError(message) {
this.message = message;
}
MyError.prototype = new Error();
相较于原生类型, 继承开发者定义构造函数的类型的继承一般需要两步: 原型继承, 和构造器继承:
function Person(name) {
this.name = name;
this.sayHi = function () {
alert(this.name);
}
}
function Author(name) {
Person.call(this, name); // 继承构造器
}
Author.prototype = new Person();
let me = new Author("Jack");
me.sayHi();
3. 门面模式
门面模式是一种流行的设计模式, 它为一个已存在的对象创建一个新的接口. 门面是一个全新的对象, 其背后有一个已存在的对象在工作. 门面有时也叫包装器, 它们用不同的接口来包装已存在的对象.
function DOMWrapper(element) {
this.element = element;
}
DOMWrapper.prototype.addClass = function (className) {
element.className += " " + className;
};
DOMWrapper.prototype.remove = function () {
this.element.parentNode.removeChild(this.element)
};
// 用法
let wrapper = new DOMWrapper(document.getElementById("my-div"));
wrapper.addClass("selected");
wrapper.remove();
阻止修改
ECMAScript 5 引入了几个方法来防止对对象的修改. 包括:
- 防止扩展,
Object.preventExtension(person);
, 禁止向对象添加属性和方法; - 密封,
Object.seal(person);
, 禁止删除对象属性和方法, 同时防止扩展; - 冻结,
Object.freeze(person);
, 禁止修改任何对象属性和方法, 同时密封;
let person = {
name: "Nicholas"
};
Object.freeze(person);
console.log(Object.isExtensible(person)); // false
console.log(Object.isSealed(person)); // true
console.log(Object.isFrozen(person)); // true
person.name = "Greg"; // 正常情况下默默失败, strict 模式下抛出错误
person.age = 25; // 同上
delete person.name; // 同上
第 12 章, 浏览器嗅探
浏览器嗅探是保证页面正常工作的有效手段. 通过各种方法进行浏览器嗅探, 可以根据客户端的不同作出不同的响应.
User-Agent 检测
用户代理检测根据 user-agent 字符串navigator.userAgent
来确定浏览器的类型. 该方法的缺陷在于用户代理可以被任意修改, 而且对于新浏览器的检测始终需要更新判断规则. 推荐只用在识别老旧版本浏览器的场景.
特性检测
特性检测是指直接调用某项特性, 根据返回结果正确与否判断浏览器类型的方法. 在某些情况下这是最优的浏览器嗅探手段.
特性推断 (应该避免)
特性推断指在特性检测外, 根据一项特性存在与否判断另一项特性存在与否的方法. 特性推断有很大的不稳定性, 不建议实际使用.
浏览器推断 (应该避免)
浏览器推荐和特性推断类似并且更加武断, 根据一项特性存在与否直接判断浏览器类型, 并默认该浏览器支持的特性全部可用. 同样不建议使用.
实际上, 因为逻辑不严密 (命题与其反命题无确定联系) , 使用特性推断和浏览器推断是不可能严谨地保证代码正常工作的.