本文主要参考Douglas Crockford的专著《Javascript语言精粹》的附录A和B。熟练掌握它们,将会使我们写出的代码更加优雅。
1. 全局变量
Javascript的全局变量,在所有作用域中都可见。它在很小的程序中可能不会有问题,甚至能够给我们带来方便。但是,在较大的程序中,它会变得难以处理且降低了程序的可靠性。之所以这么说,是因为,任何一个函数内部都可以访问、修改和生成全局变量,一旦我们生成或修改的变量和已存在的全局变量同名,那么它们将会互相冲突,甚至导致程序无法运行。而这种情况,往往都是难以调试的。
定义全局变量的方式有三种,我们要谨慎注意无意间定义的全局变量:
- 在所有函数之外,用 var 定义一个变量
- 直接在全局对象上添加一个属性。在Web浏览器环境下,全局对象名为window。
- 直接使用未经声明的变量(也称为隐式的全局变量)。
<script>
var a = '使用 var 将 foo 变量要定义在任何函数之外';
window.a2 = '在全局对象上直接添加属性';
a3 = '未经声明的变量';
console.log('window.a-->', window.a);
console.log('window.a2-->', window.a2);
console.log('window.a3-->', window.a3);
console.log('window', window);
</script>
复制代码
2. ==
我们都知道Javascript有两组相等运算符:== 和 !=,以及 === 和 !==。两者的不同之处在于,前者用来判断两个值是否相等时,若是两个值类型不同,会发生自动转换,这个转换的规则不仅复杂且难以记忆,其得到的结果有时也是出乎意料。而后者会严格按照两个值类型来判断,不会发生自动转换。因此,推荐任何时候都使用 === 和 !== 运算符。
console.log("" == "0"); // false
console.log(0 == ""); // true
console.log(0 == "0"); // true
console.log(false == "false"); // false
console.log(false == "0"); // true
console.log(false == undefined); // false
console.log(false == null); // false
console.log(null == undefined); // true
console.log("\t\r\n" == 0); // true
复制代码
3. 加号运算符
加号运算符( ‘+’ ),即可用于加法运算,也可用于字符串的连接。虽然,这种设计使得Javascript变得更加灵活,但往往也是bug的常见来源。所以,在使用时,一定要谨慎小心。
console.log(1 + 1); // 2
console.log("1" + "0"); // 10
// 若是两个操作项,一个是字符串,另一个是数字,则数字自动转化为字符串。
alert(1 + "1"); // 11
复制代码
4. 自动插入行尾分号
Javascript的所有语句,都必须以分号结尾。但是,它有一个机制:通过自动插入分号来修正有缺陷的程序。换句话说,就是当你忘记加分号时,解释器会为你自动加上分号,而不是报错。然而,这种机制有时会导致一些难以发现的错误。建议,在每行代码后手动加上分号,而不是依赖这个机制,为你自动添加。
下面这个例子就会导致出现无法预期的结果,它会返回undefined,而不是一个对象。其原因,就是这个机制,在return语句后自动插入了分号。
function err() {
return
{
a: '会返回undefined'
};
}
复制代码
要想避免出现这个问题,就得把{
放在一行的尾部。
function err() {
return {
a: '问题解决'
};
}
复制代码
5. NaN
NaN(详细介绍)表示不是一个数字。然而,通过 typeof 判断它时,得到值是’number’。
console.log(typeof NaN);
console.log(typeof NaN === 'number'); // true
复制代码
它还有一些其它的奇怪特性:
NaN === NaN; // false
NaN !== NaN; // true
console.log( 1 + NaN ); // NaN
复制代码
上面这段代码总结起来就是:
- NaN 与所有值都不相等,包括它自己。
- 如果 NaN 是数学运算中的一个运算数,那么结果就是 NaN
6. 浮点数
由于Javascript遵循二进制浮点数算数标准(IEEE 754),而二进制的浮点数并不能正确地处理十进制的小数,因此出现一种怪异的数学运算问题:0.1 + 0.2 不等于 0.3 。但值得庆幸的是,浮点数中的整数运算是精确的,我们可以通过它来规避小数运算带来的bug。
console.log( 0.1 + 0.2 === 0.3 ); // false
console.log( ( 0.1 * 10 + 0.2 * 10 ) / 10 === 0.3 ); // true
复制代码
7. typeof
typeof运算符返回一个用于识别其运算数类型的字符串。但不幸的是,typeof并不能区分null和对象,以及数组和对象。这是因为,在Javascript中,null和数组都属于对象。
console.log( typeof null ); // object
console.log( typeof [] ); // object
复制代码
- 区分null和对象
const val = null;
// 利用所有对象(包括 '{}')都是真值,而null是假值来判断
if (val && typeof val === 'object') {
console.log('我是一个对象或数组');
} else {
console.log('我是null');
}
复制代码
- 区分数组和对象
Javascript中用于区分数组和对象的方式还有很多,这里仅列举了三种。
// 方法一:Object,prototype.toString.call
const toStr = Object.prototype.toString;
const val = {};
const valType = toStr.call(val);
if (val && typeof val === 'object' && valType === '[object Object]') {
console.log('我是对象', valType);
} else {
console.log('我是数组', valType);
}
// 方法二:ES6 中的 Array.isArray 用于确定传递的值是否是一个 Array
const bool = Array.isArray(val);
if (val && typeof val === 'object' && bool) {
console.log('我是数组');
} else {
console.log('我是对象');
}
// 方法三:通过调用constructor来识别
if (val && typeof val === 'object' && val.constructor === Array) {
console.log('我是数组——Array');
} else {
console.log('我是对象——Object');
}
复制代码
8. 假值
Javascript拥有很多假值,若是不熟悉它们,很可能在代码时造成意料之外的错误。
值 | 类型 |
---|---|
0 | Number |
”(空字符串) | String |
false | Boolean |
null | Object |
undefined | Undefined |
NaN(非数字) | Number |
上面的值全都等同于假,而且它们不可互换。另外要注意,undefined 和 NaN 是全局变量,而不是常量,这意味着你可以改变它们的值,但千万不要这么做。
9. 基本类型的包装对象
在Javascript中,数字、字符串和布尔值,这三个基本数据类型都有相应的包装对象,用于生成数字对象、字符串对象和布尔值对象。这些对象都有一个valueof方法,用来返回被包装的值。
const num = new Number(100);
const bool = new Boolean(true);
const str = new String('Hello World');
console.log('数字:' + num.valueOf());
console.log('布尔值:' + bool.valueOf());
console.log('字符串:' + str.valueOf());
复制代码
然而在实际的编程中,它们的作用并不大且易造成混淆。建议,不要使用它们(感觉没必要,也没啥用)。同时建议,尽量避免使用 new Array 和 new Object,用 [] 和 {} 代替。
10. 缺少块的语句
在Javascript中,if、for、while 或 do 语句,不仅能接受一个括在花括号中的代码块,也能接受单行语句。就像下面这样:
if (val) return true;
复制代码
这种单行语句的好处是可以节约两个字节,提升代码简洁度。但是,这个好处是带刺的,它容易模糊程序结构,让人犯错。举个例子:
if (val)
const bool = true;
func();
复制代码
上面代码看起来是变成下面的样子:
if (val) {
const bool = true;
func();
}
复制代码
然而实际情况是:
if (val) {
const bool = true;
}
func();
复制代码
这种让人以为是在做自己期待的事,其实是在做另一件事的程序是非常不靠谱且有时难以理解。因此,为严谨起见,最好不要使用单行语句,而是使用代码块。
11. 保留字
Javascript中的保留字可以被用作对象字面量的键值,但必须被引号括起来。同时,它们只能用括号表示法,而不能用点表示法。
let obj;
obj = { case: 1 }; // 错误
obj = { 'case': 1 }; // 正确
obj.case = 2; // 错误
obj['case'] = 2; // 正确
复制代码
建议,最好不要使用任何保留字。
12. parseInt
parseInt(string, radix) 函数可解析一个字符串,并返回一个整数。它在遇到非数字时停止解析,但不报错,所以在使用它时,务必小心。
console.log(parseInt('10', 10) === parseInt('10w', 10)); // 10
console.log(parseInt('w10', 10)); // NaN
复制代码
parseInt(string, radix) 函数中的第二参数(可选),表示要解析的数字的基数。它指定,parseInt在解析字符串时,应该使用那种进制。由于在字符串以’0’为开始时,旧的浏览器默认使用八进制基数,而 ECMAScript 5 默认使用十进制的基数。而在八进制中,8 和 9 都不是数字,当我们用parseInt(’08’) 或 parseInt(’09’)解析时间和日期时,得的结果就会是 0。因此,强烈建议,始终提供基数。
关于parseInt的具体用法,大家可以点击详细介绍。
13. eval 和 with 语句
对于 eval 和 with 语句,前者在使用后容易被人篡改运行恶意代码从而被攻击,后者可能会成为混淆错误和兼容性问题的根源。因此,千万不要在代码去应用它们。但是,必要的了解还是需要的。