本文为转载,原文链接:clvsit的博客
- 避免“空比较"
在 JavaScript 中,我们常会看到这种代码:变量与 null 的比较,用来判断变量是否被赋予一个合理的值。比如:
var Controller = {
process : function(items) {
// 不好的写法
if(items !== null) {
items.sort();
items.forEach(function(item) {
// 执行一些逻辑
});
}
}
};
- 意图:如果参数 items 不是一个数组,则停止接下来的操作。
- 问题:和 null 的比较并不能真正避免错误的发生。
- 原因:items 的值可以是 1,也可以是字符串,甚至可以是任意对象。这些值都和 null 不想等,进而会导致 process() 方法一旦执行到 sort() 时就会出错。
- 结论:仅仅和 null 比较并不能提供足够的信息来判断后续代码的执行是否真的安全。
检测原始值
如果你希望一个值是字符串、数字、布尔值或 undefined,最佳选择是使用 typeof 运算符。typeof 运算符会返回一个表示值的类型的字符串。
- 字符串:typeof 返回 “string”。
- 数字:typeof 返回 “number”。
- 布尔值:typeof 返回 “boolean”。
- undefined:typeof 返回 “undefined”。
typeof 语法:
typeof variable /* * 尽管这是合法的 JavaScript 语法,这种用法让 typeof * 看起来像一个函数而非运算符。因此,更推荐无括号的写法 */ typeof(variable)
检测 4 种原始值的做法:
// 检测字符串 if(typeof name === "string") { anotherName = name.substring(3); }
// 检测数字 if(typeof count === "number") { unpdateCount(count); } // 检测布尔值 if(typeof found === "boolean" && found) { message("Found!"); } // 检测 undefined if(typeof MyApp === "undefined") { MyApp = { // 其他的代码 }; }
typeof 运算符用于一个未声明的变量也不会报错。未定义的变量和值为 undefined 的变量通过 typeof 都将返回 “undefined”。
关于 null:
- 概述:原始值 null 一般不应用于检测,因为简单地和 null 比较通常不会包含足够的信息以判断值的类型是否合法。但有个例子,如果所期望的值真的是 null,则可以直接和 null 进行比较。这时应当使用 === 或者 !== 来和 null 进行比较。
- 示例:
// 如果你需要检测 null,则使用这种方法 var element = document.getElementById("my-div"); if(element !== null) { element.className = "found"; } /* * 如果 DOM 元素不存在,则获得的节点的值为 null。这个方法要么返回一个节点,要么返回 null。 * 由于这时 null 是可预见的一种输出,则可以使用 !== 来检测返回结果。
- 注意:运行 typeof null 则返回 “object”,这是一种低效的判断 null 的方法,甚至被认为是标准规范的严重 bug,因此在编程时杜绝使用 typeof 来检测 null 的类型。如果你需要检测 null,则直接使用恒等运算符(===)或非恒等运算符(!==)。
检测引用值
引用值也称作对象(object),在 JavaScript 中除了原始值之外的值都是引用。
常见的内置引用类型:
- Object
- Array
- Date
- RegExp
- Error
对引用值的检测:
- typeof 运算符在判断这些引用类型以及 null 时都会返回 “object”,因此 typeof 不适合检测引用值。
var arr = [1, 2], obj = {name: "clvsit"}, date = Date(), reg = RegExp(), err = Error(); console.log(typeof arr === "object"); // true console.log(typeof obj === "object"); // true console.log(typeof date === "object"); // false console.log(typeof reg === "object"); // true console.log(typeof err === "object"); // true
从上述示例可以发现,typeof date === “object” 返回 false,为什么会这样?
console.log(typeof date); // 'string' console.log(date.toString()); // "Sat Jan 05 2019 15:06:28 GMT+0800 (中国标准时间)" console.log(date); // "Sat Jan 05 2019 15:06:28 GMT+0800 (中国标准时间)"
实践可知,在使用 Date 对象时会自动调用 Date 对象的 toString() 方法,因此 typeof date 得到的结果为 ‘string’。
- 检测某个引用值的类型的最好方法是使用 instanceof 运算符。
instanceof:
- 语法:
value instanceof constructor
- 示例:
// 检测日期 if(value instanceof Date) { console.log(value.getFullYear()); }
// 检测正则表达式
if(value instanceof ReqExp) {
if(value.test(anotherValue)) {
console.log(“Matches”);
}
}// 检测 Error
if(value instanceof Error) {
throw value;
}- 注意:
- instanceof 不仅检测构造这个对象的构造器,还检测原型链。而原型链包含了很多信息,包括定义对象所采用的继承模式。比如,默认情况下,每个对象都继承来自 Object,因此每个对象的 value instanceof Object 都会返回 true(使用 value instanceof Object 来判断对象是否属于某个特定类型的做法并非最佳)。
var now = new Date();
console.log(now instanceof Object); // true
console.log(now instanceof Date); // true- instanceof 运算符也可以检测自定义的类型(最好的做法)。
function Person(name) { this.name = name; }
var me = new Person(“Nicholas”);
console.log(me instanceof Object); // true
console.log(me instanceof Person); // true存在的限制:
- 情景:假设一个浏览器帧(frameA) 里的一个对象被传入到另一个浏览器帧(frameB)中,两个帧里都定义了构造函数 Person。如果来自帧 A 的对象是 帧 A 的 Person 的实例,则如下规则成立:
// true frameAPersonInstance instanceof frameAPerson;
// false
frameAPersonInstance instanceof frameBPerson;- 原因:
每个帧都拥有 Person 的一份拷贝,被认为是该帧中 Person 的拷贝的实例,尽管两个定义可能完全一样。 - 注意:
这个问题不仅出现在自定义类型上,其他两个非常重要的内置类型也存在这个问题:函数和数组。对于这两个类型来说,一般用不着使用 instanceof。
检测函数
从技术上讲,JavaScript 中的函数是引用类型,同样存在 Function 构造函数,每个函数都是其实例。
function myFunc() {}
// 不好的写法
console.log(myFunc instanceof Function); // true- 注意:
- instanceof 不能跨帧(frame)使用,因为每个帧都有各自的 Function 构造函数。
- typeof 运算符也是可以用于函数的,返回 “function”。检测函数最好的方法是使用 typeof,因为它可以跨帧使用。
function myFunc() {}
// 好的写法
console.log(typeof myFunc === “function”); // true- typeof 检测函数的限制:在 IE 8 和更早版本的 IE 浏览器中,使用 typeof 来检测 DOM 节点(比如 document.getElementById())中的函数都返回 “object” 而不是 “function”。之所以出现这种现象是因为浏览器对 DOM 的实现有差异。简言之,早期的 IE 并没有将 DOM 实现为内置的 JavaScript 方法,导致内置 typeof 运算符将这些函数识别为对象。
- 在 IE 8 及早期版本,开发者往往通过 in 运算符来检测 DOM 的方法。
// 检测 DOM 方法 if("querySelectorAll" in document) { images = document.querySelectorAll("img"); }
- 尽管使用 in 检测 DOM 方法不是最理想的方法,但如果想在 IE 8 及更早浏览器中检测 DOM 方法是否存在,这是最安全的做法。在其他所有情形中,typeof 运算符是检测 JavaScript 函数的最佳选择。
检测数组
JavaScript 最古老的跨域问题之一就是在帧之间来回传递数组。因为每个帧都有各自的 Array 构造函数,因此一个帧中的实例在另外一个帧里面不会被识别。
- 检测方法:
- 鸭式辨型(duck typing):
// 采用鸭式辨型的方法检测数组 function isArray(value) { return typeof value.sort === "function"; } /* * 缺陷:数组是唯一包含 sort() 方法的对象。 * 如果传入 isArray() 的参数是一个包含 sort() * 方法的对象,也会返回 true。 */ var obj = { sort: function () {
// ... }
};
isArray(obj) // true;- Kangax 方案:
function isArray(value) { return Object.prototype.toString.call(value) === "[object Array]"; }
- 解释:
调用某个值的内置 toString() 方法在所有浏览器中都会返回标准的字符串结果。对于数组来说,返回的字符串为 “[object Array]”,也不用考虑数组实例实在哪个帧中被构造出来。 - 缺陷:
对于自定义对象使用这种方法会存在问题,比如内置 JSON 对象将返回 “[object JSON]”。 - 后续:
ECMAScript5 将 Array.isArray() 正式引入 JavaScript。唯一的目的就是准确地检测一个值是否为数组。Array.isArray() 可以检测跨帧传递的值。同样很多 JavaScript 类库都实现了这个方法。
function isArray(value) { if(typeof Array.isArray === "function") { return Array.isArray(value); } else { return Object.prototype.toString.call(value) === "[object Array]"; } }
检测属性
另外一种用到 null(以及 undefined)的场景是当检测一个属性是否在对象中存在时。
// 不好的写法:检测假值 if(object[propertyName]) { // 一些代码 }
// 不好的写法:和 null 相比较
if(object[propertyName] !== null) {
// 一些代码
}// 不好的写法:和 undefined 比较
if(object[propertyName] !== undefined) {
// 一些代码
}-
解释:
上面这段代码里的每个判断,实际上是通过给定的名字来检查属性的值,而非判断给定的名字所指的属性是否存在。因为当属性值为假值(false value)时结果会出错,比如 0、""、false、null 和 undefined。毕竟,这些都是属性的合法值。比如,如果属性记录了一个数字,则这个值可以是零,这样的话,上段代码中的第一个判断就会导致错误。以此类推,如果属性值为 null 或者 undefined 时,三个判断都会导致错误。 -
合适的方式:
判断属性是否存在的最好方法是使用 in 运算符。因为 in运算符仅仅会简单地判断属性是否存在,而不会去读属性的值,这样就可以避免解释部分提到的问题。如果实例对象的属性存在、或者继承自对象的原型,in 运算符都会返回 true。
var object = { count : 0, related : null }
// 好的写法
if(“count” in object) {
// 这里的代码会执行
}// 不好的写法
if(object[“count”]) {
// 这里的代码不会执行
}// 好的写法
if(“related” in object) {
// 这里的代码会执行
}// 不好的写法:检测是否为 null
if(object[“related”] !== null) {
// 这里代码不会执行
}hasOwnProperty():
- 概述:
如果只想检查实例对象的某个属性是否存在,可以使用 hasOwnProperty() 方法,所有继承自 Object 的 JavaScript 对象都有这个方法。如果实例中存在这个属性则返回 true(如果这个属性只存在于原型里,则返回 false)。 - 注意:
在 IE 8 及早期版本,DOM 对象并非继承自 Object,因此也不包含这个方法。也就是说,你在调用 DOM 对象的 hasOwnProperty() 方法之前应当先检测其是否存在(假如你已经知道对象不是 DOM,则可以省略这一步)。
// 对于所有非 DOM 对象来说,这是好的写法 if(object.hasOwnProperty("related")) { // 执行这里的代码 }
// 如果你不确定是否为 DOM 对象,则这样来写
if(“hasOwnProperty” in object && object.hasOwnProperty(“related”)) {
// 执行这里的代码
}- 总结:
因为存在 IE 8 以及更早版本 IE 的情形,在判断实例对象的属性是否存在时,推荐使用 in 运算符。只有在需要判断实例属性时才会用到 hasOwnProperty()。
</div> <link href="https://csdnimg.cn/release/phoenix/mdeditor/markdown_views-df60374684.css" rel="stylesheet"> </div>