JS中经常会有变量与null比较。来判断变量是否被赋予合理的值。
var Controller = {
process : function( items ){
if( items !== null ){
items.sort();
item.forEach(function( item ){
// ... Code
});
}
}
}
很明显我们本意是需要检测,传入的参数是否为数组,但是仅仅和null进行判断并不能够提供足够的信息来保证后续代码执行的安全性。还好,灵活的JS提供了多种检测的方式。
8.1 检测原始值
5种原始类型 字符串、数字、布尔值、null和undefined。
typeof 可以检测各个原始值的类型,并且返回相应类型字符串。而 typeof null 会返回 "object"。
if( typeof str === "string" ){
// ... Code
}
需要注意的是,typeof 一个未声明的变量也不会报错,会返回 "undefined" ,所以也需要注意该种情况,对于检测 null,则直接与 null 进行比较即可。
var element = document.getElementById("my-div");
if( element === null ){
element.className = "abc";
}
8.2 检测引用值
对于检测引用值,typeof会力不从心,基本都会返回"object",那么对于引用值检测最好的方法是使用 instanceof。
if( value instanceof Date ){
// ... Code
}
if( value instanceof RegExp ){
// ... Code
}
if( value instanceof Error){
// ... Code
}
instanceof 还有个特性就是,它不只是检测构造这个对象的构造器,还检测原型链。
var now = new Date();
now instanceof Date;
now instanceof Object;
均为 true。由于这个原因也使得使用 instanceof 来检测引用值也不是最佳的方法。
对于自定义的构造函数来说,instancdof 检测是唯一的方法。当然需要注意一下 跨帧 使用时的问题。
8.2.1 检测函数
JS中同样存在 Function 构造函数,每个函数均是其实例。
myFunc instanceof Function
当然也是不适用与跨帧的情况。
还有一个好的写法就是使用 typeof
typeof myFunc === "function"
但是对于 IE8以及以下浏览器来说,
typeof document.getElementById 会返回 "object".( 还有几个其他的DOM操作 )。
8.2.2 检测数组
JS最古老的跨域问题在于 帧 frame 之间来回传递数组。每个帧有自己的 数组构造函数,所以在一个帧中的数组实例是不被另外帧所承认的。
优雅的解决方案:
Object.prototype.toString.call( value ) === "[object Array]"
对于检测数组的需求 ES5 中引入了 Array.isArray() 方法来检测。
优雅方案
function idArray( value ){
if( typeof Array.isArray === "function" ){
return Array.isArray( value );
}else{
return Object.prototype.toString.call( value ) === "[object Array]";
}
}
8.3 检测属性
很多时候我们会使用与 null undfined 对比来判断是否属性存在。
if( obj[prop] ){ }
if( obj[prop] != null ){ }
if( obj[prop] != undefined){ }
这样会导致代码有漏洞,不能将所有的情况都覆盖到。
对于属性的检测,最好的方法是使用 in,in运算符仅仅会简单的判断 属性 是否存在,而不会去读属性的值。当然 in是会在原型链中进行查找的。
如果只是希望检测实例属性,那么使用 hasOwnPrototype() 进行检测。
注意,在IE8以及之前浏览器,DOM元素并非继承自 Object 所以没有该方法。
最后,不管什么时候需要检测属性是否存在的时候,请使用 in 或者 hasOwnProperty()。
第九章 将配置数据从代码中分离出来
代码就是一些计算机运行的指令,当我们传递数据进入计算机的时候,指令对数据进行操作产生结果。
那么当我们在修改一些数据问题的时候就会带来一些源代码引入BUG的风险,所以对于应用来说,应当将一些关键数据从主要的源码中抽离出来,这样就可以是我们在修改数据或者源码的时候更放心。
9.1 什么是配置数据
配置数据就是我们在应用中写死(hardcoded)的值。
// 将配置数据藏于源码之中
function validate( value ){
if( !value ){
alert("Invalid value");
location.href = "/errors/invalid.html";
}
}
上面的函数具有2个配置数据片段, 字符串 "Invalid value" 和 URL地址 "/errors/invalid.html"。
之所以将其认为是配置数据是因为它们都写死在代码里,并且在将来可能需要修改。下面是一些配置数据的例子:
1. URL
2. 需要展示给用户看的字符串
3. 重复的值
4. 设置的值,一些配置选项
5.任何可能发生变化的值
我们需要记住,配置数据随时都有可能做修改,我们不希望因为有人需要修改页面的提示信息而需要修改JS源码。
9.2 抽离配置数据
其实,抽离配置数据非常简单,只需要创建一个管理整体的配置数据的对象即可。
var config = {
MSG_INVALID_VALUE : "Invalid value",
URL_INVALID : "/errors/invalid.html"
}
再在需要使用配置数据的地方使用即可。
注意统一在配置数据对象中的命名。将配置数据提取出来之后任何人需要做修改都不会引起一些代码的逻辑错误问题,对于很大的系统,可以将配置数据专门使用其他单独文件,使其于代码隔离。
9.3 保存配置数据
配置数据最好放在单独的文件之中,以便清晰地分隔数据和应用逻辑。一种值得尝试的方法是将这些配置数据放于非JS的文件中。
1. 使用各种流行的属性文件来存放。(如 Java属性文件)
2. JSONP,将JSON结构使用函数调用包装起来。
3. 纯JS对象
还有一些专门可以管理配置文件的库可以使用。
第十章 抛出自定义错误
编程语言具有“创建”错误的能力,在JS中抛出错误是一门艺术。再合适的时间抛出错误可以大大减少我们调试的事件,对代码的满意度也将急剧提升。
10.1 错误的本质
当某些非期望的事情发生的时候,程序就会要发一个错误。这也许是传入了非法的值,或许是遇到无效的操作符等等。
编程语言定义了一组基本的规则,当偏离了这些规则的时候将导致错误,只有当错误被抛出来时,我们才能有地方入手解决,如果错误无声无息,那么解决的代价可想而知。所以错误时开发者的朋友而非敌人。
JS错误消息信息稀少,并且在一些老版本的IE中更是令人深痛恶绝。所以在代码中的特殊之处有计划的抛出一个错误总比在所有地方有可能抛出错误简单得多。
10.2 在 JS 中抛出错误
毫无疑问,在 JS 中抛出错误比任何语言中做同样的事情更有价值,这要归结于Web端调试的复杂性。可以使用 throw 操作符,将提供一个对象作为错误抛出,Error对象时最常用的。
throw new Error("bad happened");
还有种方式就是直接抛出字符串
throw "bad happened";
但会有兼容性的问题,所以不建议使用。
10.3 抛出错误的好处
可以看一下一下两个函数的优略:
function getDivs( element ){
return element.getElementByTagName("div");
}
function getDivs( element ){
if( element && element.getElementsByTagName ){
return element.getElementByTagName("div");
} else {
throw new Error("getDivs() : Argument must be a DOM element");
}
}
虽然chrome等控制台以及对于错误的抛出做的非常好了,但是也还是有一些浏览器在一些特殊情况下会起到很大作用。
10.4 何时抛出错误
// 不好的写法 检测太多
function addClass(element, className){
if( !element || typeof element.className != "string" ){
throw new Error("addClass() : First argument must be a DOM element");
}
if( typeof element.className != "string" ){
throw new Error("addClass() : First argument must be a DOM element");
}
element.className +=" " + className;
}
原本只是需要加一个class 但是加了那么多的抛出错误,就会适得其反。会引起过度杀伤。
// 好的写法
function addClass(element, className){
if( !element || typeof element.className != "string" ){
throw new Error("addClass() : First argument must be a DOM element");
}
element.className +=" " + className;
}
1. 当修复了一个难以调试的错误的时候,尝试增加一两个自定义错误,当再次发生错误的时候有助于解决问题。
2. 如果正在编写代码,思考一下我希望哪些事情不会发生,如果发生将引发较大问题,在这个某些问题上抛出错误。
3. 如果在编写别人的代码,思考下他们的使用方式在特定的情况下抛出错误。
这些我们所做的都不是为了防止错误,而是在有错误 时候更快更容易的找到错误的原因。
10.5 try-catch 语句
try-catch 可以在浏览器抛出错误之前解析,将可能引发错误的代码放在 try 块中,将处理错误的代码放在 catch 中。当然还可以加入 finally 块。
在catch中不要为空,应该总要写点声明老处理错误,不然就会依旧不知道错误在哪里。
10.6 错误的类型
Error : 所有错误的基本类型,引擎不会抛出此类型。
EvalError : 通过 eval() 函数执行的代码发生错误。
RangeError : 一个数字超出边界时抛出,试图创建一个长度为-20的数组。
ReferenceError : 期望对象不存在,例如试图在null上调用函数。
SyntaxError : 语法上的错误。
TypeError : 变量不是期望类型的时候。
URIError : 给一些内置函数传入非法URL时抛出的错误。