全局命名空间污染与 IIFE
总是将代码包裹成一个 IIFE(Immediately-Invoked Function Expression),用以创建独立隔绝的定义域。这一举措可防止全局命名空间被污染。
IIFE 还可确保你的代码不会轻易被其它全局命名空间里的代码所修改(i.e. 第三方库,window 引用,被覆盖的未定义的关键字等等)。
不推荐
var x = 10, y = 100; // Declaring variables in the global scope is resulting in global scope pollution. All variables declared like this // will be stored in the window object. This is very unclean and needs to be avoided. console.log(window.x + ' ' + window.y);
推荐
/ We declare a IIFE and pass parameters into the function that we will use from the global space (function(log, w, undefined){ 'use strict'; var x = 10, y = 100; // Will output 'true true' log((w.x === undefined) + ' ' + (w.y === undefined)); }(window.console.log, window));
IIFE(立即执行的函数表达式)
无论何时,想要创建一个新的封闭的定义域,那就用 IIFE。它不仅避免了干扰,也使得内存在执行完后立即释放。
所有脚本文件建议都从 IIFE 开始。
立即执行的函数表达式的执行括号应该写在外包括号内。虽然写在内还是写在外都是有效的,但写在内使得整个表达式看起来更像一个整体,因此推荐这么做。
不推荐
[javascript]view plaincopy (function(){})();
推荐
[javascript]view plaincopy (function(){}());
so,用下列写法来格式化你的 IIFE 代码:
[javascript]view plaincopy (function(){ 'use strict'; // Code goes here }());
如果你想引用全局变量或者是外层 IIFE 的变量,可以通过下列方式传参:
[javascript]view plaincopy (function($, w, d){ 'use strict'; $(function() { w.alert(d.querySelectorAll('div').length); }); }(jQuery, window, document));
严格模式
ECMAScript 5 严格模式可在整个脚本或独个方法内被激活。它对应不同的 JavaScript 语境会做更加严格的错误检查。严格模式也确保了 javascript 代码更加的健壮,运行的也更加快速。
严格模式会阻止使用在未来很可能被引入的预留关键字。
你应该在你的脚本中启用严格模式,最好是在独立的 IIFE 中应用它。避免在你的脚本第一行使用它而导致你的所有脚本都启动了严格模式,这有可能会引发一些第三方类库的问题。
不推荐
[javascript]view plaincopy // Script starts here 'use strict'; (function(){ // Your code starts here }());
推荐
[javascript]view plaincopy (function(){ 'use strict'; // Your code starts here }());
变量声明
总是使用
var
来声明变量。如不指定 var,变量将被隐式地声明为全局变量,这将对变量难以控制。如果没有声明,变量处于什么定义域就变得不清(可以是在 Document 或 Window 中,也可以很容易地进入本地定义域)。所以,请总是使用 var 来声明变量。
采用严格模式带来的好处是,当你手误输入错误的变量名时,它可以通过报错信息来帮助你定位错误出处。
不推荐
[javascript]view plaincopy x = 10; y = 100;
推荐
[javascript]view plaincopy var x = 10, y = 100;
理解 JavaScript 的定义域和定义域提升
在 JavaScript 中变量和方法定义会自动提升到执行之前。JavaScript 只有 function 级的定义域,而无其他很多编程语言中的块定义域,所以使得你在某一 function 内的某语句和循环体中定义了一个变量,此变量可作用于整个 function 内,而不仅仅是在此语句或循环体中,因为它们的声明被 JavaScript 自动提升了。
我们通过例子来看清楚这到底是怎么一回事:
原 function
【javascript】view plaincopy (function(log){ 'use strict'; var a = 10; for(var i = 0; i < a; i++) { var b = i * i; log(b); } if(a === 10) { var f = function() { log(a); }; f(); } function x() { log('Mr. X!'); } x(); }(window.console.log));
被 JS 提升过后
(function(log) { 'use strict'; // All variables used in the closure will be hoisted to the top of the function var a, i, b, f; // All functions in the closure will be hoisted to the top function x() { log('Mr. X!'); } a = 10; for (i = 0; i < a; i++) { b = i * i; log(b); } if (a === 10) { // Function assignments will only result in hoisted variables but the function body will not be hoisted // Only by using a real function declaration the whole function will be hoisted with its body f = function() { log(a); }; f(); } x(); }(window.console.log));
根据以上提升过程,你是否可理解以下代码?
有效代码
(function(log) { 'use strict'; var a = 10; i = 5; x(); for (var i; i < a; i++) { log(b); var b = i * i; } if (a === 10) { f = function() { log(a); }; f(); var f; } function x() { log('Mr. X!'); } }(window.console.log));
正如你所看到的这段令人充满困惑与误解的代码导致了出人意料的结果。只有良好的声明习惯,也就是下一章节我们要提到的声明规则,才能尽可能的避免这类错误风险。
提升声明
为避免上一章节所述的变量和方法定义被自动提升造成误解,把风险降到最低,我们应该手动地显示地去声明变量与方法。也就是说,所有的变量以及方法,应当定义在 function 内的首行。
只用一个
var
关键字声明,多个变量用逗号隔开。
不推荐
[javascript]view plaincopy (function(log){ 'use strict'; var a = 10; var b = 10; for(var i = 0; i < 10; i++) { var c = a * b * i; } function f() { } var d = 100; var x = function() { return d * d; }; log(x()); }(window.console.log));
推荐
[javascript]view plaincopy (function(log){ 'use strict'; var a = 10, b = 10, i, c, d, x; function f() { } for(i = 0; i < 10; i++) { c = a * b * i; } d = 100; x = function() { return d * d; }; log(x()); }(window.console.log));
把赋值尽量写在变量申明中。
不推荐
[javascript]view plaincopy
- var a,
- b,
- c;
- a = 10;
- b = 10;
- c = 100;
推荐
[javascript]view plaincopy
- var a = 10,
- b = 10,
- c = 100;
总是使用带类型判断的比较判断
总是使用
===
精确的比较操作符,避免在判断的过程中,由 JavaScript 的强制类型转换所造成的困扰。
如果你使用
===
操作符,那比较的双方必须是同一类型为前提的条件下才会有效。
在只使用
==
的情况下,JavaScript 所带来的强制类型转换使得判断结果跟踪变得复杂,下面的例子可以看出这样的结果有多怪了:
[javascript]view plaincopy (function(log){ 'use strict'; log('0' == 0); // true log('' == false); // true log('1' == true); // true log(null == undefined); // true var x = { valueOf: function() { return 'X'; } }; log(x == 'X'); }(window.console.log));
明智地使用真假判断
当我们在一个 if 条件语句中使用变量或表达式时,会做真假判断。
if(a == true)
是不同于
if(a)
的。后者的判断比较特殊,我们称其为真假判断。这种判断会通过特殊的操作将其转换为 true 或 false,下列表达式统统返回 false:
false
,
0
,
undefined
,
null
,
NaN
,
''
(空字符串).
这种真假判断在我们只求结果而不关心过程的情况下,非常的有帮助。
以下示例展示了真假判断是如何工作的:
[javascript]view plaincopy (function(log){ 'use strict'; function logTruthyFalsy(expr) { if(expr) { log('truthy'); } else { log('falsy'); } } logTruthyFalsy(true); // truthy logTruthyFalsy(1); // truthy logTruthyFalsy({}); // truthy logTruthyFalsy([]); // truthy logTruthyFalsy('0'); // truthy logTruthyFalsy(false); // falsy logTruthyFalsy(0); // falsy logTruthyFalsy(undefined); // falsy logTruthyFalsy(null); // falsy logTruthyFalsy(NaN); // falsy logTruthyFalsy(''); // falsy }(window.console.log));
变量赋值时的逻辑操作
逻辑操作符
||
和
&&
也可被用来返回布尔值。如果操作对象为非布尔对象,那每个表达式将会被自左向右地做真假判断。基于此操作,最终总有一个表达式被返回回来。这在变量赋值时,是可以用来简化你的代码的。
不推荐
[javascript]view plaincopy
- if(!x) {
- if(!y) {
- x = 1;
- } else {
- x = y;
- }
- }
推荐
[javascript]view plaincopy
- x = x || y || 1;
[javascript]view plaincopy
- 这一小技巧经常用来给方法设定默认的参数。
[javascript]view plaincopy
- (function(log){
- 'use strict';
- function multiply(a, b) {
- a = a || 1;
- b = b || 1;
- log('Result ' + a * b);
- }
- multiply(); // Result 1
- multiply(10); // Result 10
- multiply(3, NaN); // Result 3
- multiply(9, 5); // Result 45
- }(window.console.log));
分号
总是使用分号,因为隐式的代码嵌套会引发难以察觉的问题。当然我们更要从根本上来杜绝这些问题[1] 。以下几个示例展示了缺少分号的危害:
[javascript]view plaincopy // 1. MyClass.prototype.myMethod = function() { return 42; } // No semicolon here. (function() { // Some initialization code wrapped in a function to create a scope for locals. })(); var x = { 'i': 1, 'j': 2 } // No semicolon here. // 2. Trying to do one thing on Internet Explorer and another on Firefox. // I know you'd never write code like this, but throw me a bone. [ffVersion, ieVersion][isIE](); var THINGS_TO_EAT = [apples, oysters, sprayOnCheese] // No semicolon here. // 3. conditional execution a la bash -1 == resultOfOperation() || die();
So what happens?
- JavaScript 错误 —— 首先返回 42 的那个 function 被第二个 function 当中参数传入调用,接着数字 42 也被“调用”而导致出错。
- 八成你会得到 ‘no such property in undefined’ 的错误提示,因为在真实环境中的调用是这个样子:
x[ffVersion, ieVersion][isIE]()
- .
die
- 总是被调用。因为数组减 1 的结果是
NaN
- ,它不等于任何东西(无论
resultOfOperation
- 是否返回
NaN
- )。所以最终的结果是
die()
- 执行完所获得值将赋给
THINGS_TO_EAT
- .
Why?
JavaScript 中语句要以分号结束,否则它将会继续执行下去,不管换不换行。以上的每一个示例中,函数声明或对象或数组,都变成了在一句语句体内。要知道闭合圆括号并不代表语句结束,JavaScript 不会终结语句,除非它的下一个 token 是一个中缀符[2] 或者是圆括号操作符。
这真是让人大吃一惊,所以乖乖地给语句末加上分号吧。
澄清:分号与函数
分号需要用在表达式的结尾,而并非函数声明的结尾。区分它们最好的例子是:
[javascript]view plaincopy
- var foo = function() {
- return true;
- }; // semicolon here.
- function foo() {
- return true;
- } // no semicolon here.
嵌套函数
嵌套函数是非常有用的,比如用在持续创建和隐藏辅助函数的任务中。你可以非常自由随意地使用它们。
语句块内的函数声明
切勿在语句块内声明函数,在 ECMAScript 5 的严格模式下,这是不合法的。函数声明应该在定义域的顶层。但在语句块内可将函数申明转化为函数表达式赋值给变量。
不推荐
[javascript]view plaincopy
- if (x) {
- function foo() {}
- }
推荐
[javascript]view plaincopy
- if (x) {
- var foo = function() {};
- }