使用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