如何编写高效的js代码(一)

使用use strict

虽然js一直很流行,但是一直到97年才有了标准化,官方成为ECMAScript(以下简称为ES),然而到今天仍然有很多不同版本的javascript实现。

ES3即ECMAScript第三版与1999年最终定稿,成为被广泛接受的js版本。之后的下一个版本ES5直到2009年才发布,它将一些以前未成为规范的一些特性纳入到了标准当中,然而ES5并没有被浏览器普遍支持

由于历史的原因及众多js的实现版本,很难知道每个平台都有实现哪些特性,或者新增了哪些未成为标准的特性,开发人员更无从指定浏览器去用什么版本的js实现来执行代码,因此开发人员必须很小心的去写兼容性代码。

因此ES5引入了strict模式来考虑版本兼容的问题,通过在代码开始处加入"use strict"来使代码运行在严格模式下,顾名思义严格模式对js的实现有非常严格的规范要求,而不支持这个字符串的浏览器会把他仅仅当作一个为操作的字符串,执行后马上丢弃掉

在函数中首行也可以加入此关键字来是函数处于严格模式执行

functionf(x) {  
"use strict";
// ...
}

有了这个严格模式才可以在严格模式下写出兼容旧版本浏览器的js代码,如果不加这个严格模式,很容易写出在ES5环境下运行有问题的代码

functionf(x) {  
"use strict";
var arguments = []; // error: redefinition of arguments严格模式下这里不允许重新定义arguments  
// ...
}

上述代码在严格模式下会报错,但是在不支持ES5的环境中则会正常执行,而如果你把上述代码部署出去的话,在某些支持ES的环境下就会报错,因此我们应该在任何时候在遵循ES5的环境下测试严格模式

然后有一个陷阱需要注意:"use strict"指令只有写在脚本头部或者函数内头部才会被识别,如果你有合并多个js文件的时候需要注意

// file1.js
"use strict";
functionf() {  
// ...
}
// ...

另外一个文件file2

// file2.js
// no strict-mode directive
functiong() {  
vararguments = [];  
// ...
}
// ...

如何我们合并的顺序是file1在前面则会执行严格模式检查

// file1.js
"use strict";
functionf() {  
// ...
}
// ...
// file2.js
// no strict-mode directive
functionf() {  
vararguments = []; // error: redefinition of arguments  
// ...
}
// ...

但如果是file2文件在前面则不会运行严格模式检查

// file2.js
// no strict-mode directive
functiong() {  
vararguments = [];  
// ...
}
// ...
// file1.js
"use strict";
functionf() { // 不处于严格模式  
// ...
}
// ...

要解决这个问题,在一个项目中你应该遵循js代码全部设置严格模式或者全部都不设置,但如果想写出足够健壮的代码并且能在任何情况下的合并都能使用,显然全部设置或者全部都不设置都不是一个好的方法

比较好的一个实践是把任何你写的函数包括起来,处于严格模式下,并立即执行

// 严格模式
(function() {
// file1.js
"use strict";
functionf() {  
// ...
}
// ...
})();
(function() {
// file2.js
// 非严格模式
functionf() {  
vararguments = [];  
// ...
}
// ...
})();

这样便让你的代码有了最大的兼容性,不管它处于什么环境下,不管它以后是否被他人引用或者合并

小心隐式转换

3+ true;// 4  

对于静态类型检查的语言,因为有严格的类型系统,int类型和布尔类型是无法做算数运算的,上述代码会在编译阶段就报出错误;而在像js这样的动态类型系统中,true在加法运算时,会被隐式转换为1

js在对于数学运算的操作符时,会自动把操作数转换成能够进行运算的数字

2+ 3; // 5  
"hello"+ " world";// "hello world"

而对于"+"运算符则有些微妙,因为它技能作为加号运算符来处理数字,也能够重载为字符串连接符,具体如何表现取决于操作数是什么。当数字和字符串想加时,js会优先把它当作字符串连接符

"2"+ 3;// "23"
2+ "3";// "23"

1+ 2+ "3"; // "33" 操作符的顺序从左到右

(1+ 2) + "3"; // "33"
1+ "2"+ 3; // "123"  

这种强制的隐式转换有时候显得非常方便,如你想把用户输入的字符串变成数字时

"17"* 3; // 51
"8"| "1";// 9

但是同时这种强制的隐式转换也会带来陷阱和错误

1+null //1 null在算数运算中被转换为0

1+undefined //NaN undefined会被转换为特殊的双精度数值NaN

对于NaN,js遵循的IEEE双精度标准中规定NaN是不能等于它自己的,因此直接测试一个变量是否为NaN是行不通的

varx = NaN;  
x === NaN; // false  

而isNaN函数又很不靠谱,因为它仍然会把你的操作数隐式转换一次

isNaN(NaN);// true

isNaN("foo"); // true  
isNaN(undefined); // true  
isNaN({}); // true  
isNaN({ valueOf: "foo"}); // true  

好在有一个办法可以测试,因为NaN是js中唯一一个被认为是不等于自身的值

vara = NaN;  
a !== a; // true  
varb = "foo";  
b !== b; // false  
varc = undefined;  
c !== c; // false  
vard = {};  
d !== d; // false  
vare = { valueOf: "foo"};  
e !== e; // false

Object也能被隐式转换为基础类型,最常见的就是转换为字符串.其实是隐式调用了toString()方法

"the Math object: "+ Math; // "the Math object: [object Math]"
"the JSON object: "+ JSON; // "the JSON object: [object JSON]"

Math.toString();// "[object Math]"  
JSON.toString();// "[object JSON]"

同样Object还能通过valueOf方法转换为数字类型,通过控制定义这些方法能够大道转换的目的

"J"+ { toString: function() { return"S"; } }; // "JS"
2* { valueOf: function() { return 3; } }; // 6  

对于"+"操作符,一个object如果同时拥有toString和valueOf方法的话,到底是作为数字运算还是字符串连接符呢?按理来说,应该根据操作数的类型决定,而js的隐式转换使得这不可能发生(因为你没办法知道实际类型),实际上,js会优先选择valueOf。如果你想让object参与到字符串连接中时,实际的效果会和你的期望不一致

varobj = {  
toString: function() {  
return"[object MyObject]";  
},
valueOf: function() {  
return17;  
}
};

"object: "+ obj; // "object: 17"

事实上,valueOf方法是被用来设计成给object表示数值的,对于一般的objects,toString和valueOf方法返回的是一致的结果:一个代表字符串的值或者一个代表同样数值的数字值,因此无论+符号被理解为字符串连接还是算数加号,对结果都没什么影响,而且一般来说,隐式转为字符串要远比数值常见,因此最好避免使用valueOf除非你的object是一个真正数字类型,valueOf方法应该实现toString方法,使得用一个字符来代表数字类型

最后js中只有7种为假的值,false, 0, -0, "", NaN, null, and undefined

作者:shaynegui
喜欢打德州,玩dota,听电音,web前端脑残粉
我的专栏 GitHub