作用域
没有块作用域
因为你可能已经注意到上一个观点,javascript中没有块作用域的概念,只有函数作用域。可以试试下面的代码:
for(var i=0; i<10; i++) { console.log(i); } var i; console.log(i); // 10
当i被定义在for循环中,退出循环后它人被保留在这个作用域内,所以最后调用console.log输出了10。这里有一个JSLint警告来让你避免这个问题:强制将所有的变量定义在函数的开头。 我们有可能通过写一个立即执行的function来创建一个作用域:
(function (){ for(var i=0; i<10; i++) { console.log(i); } }()); var i; console.log(i); // undefined
当你在内部函数之前声明一个变量,然后在函数里重声明这个变量,那将会出现一个奇怪的问题,示例代码如下:
var x = 3; (function (){ console.log(x + 2); // 5 x = 0; //No var declaration }());
但是,如果你在内部函数中重新声明x变量,会出现一个奇怪的问题:
var x = 3; (function (){ console.log(x + 2); //NaN - x is not defined var x = 0; //var declaration }());
这是因为在函数中x变量被重新定义了,这说明了翻译程序将var表达式移动到了函数顶部了,最终就变成这样执行了:
var x = 3; (function (){ var x; console.log(x + 2); //NaN - x is not defined x = 0; }());
这个实在是太有意义了!
全局变量
Javascript 有一个全局作用域,在为你的代码创建命名空间时一定要小心谨慎。全局变量会给你的应用增加一些性能问题,因为当你访问它们时,运行时不得不通过每一个作用域来建立知道找到它们为止。他们会因你的有意或者无意而被访问或者修改,这将导致另外一个更加严重的问题 - 跨站点脚本攻击。如果一个不怀好意的家伙在你的页面上找出了如何执行那些代码的方法,那么他们就可以通过修改全局变量非常容易地扰乱你的应用。缺乏经验的开发者在无意中会不断的将变量添加到全局作用域中,通过本文,将会告诉大家这样会发生什么意外的事情。
我曾经看到过下面的代码,它将尝试声明两个值相等的局部变量:
var a = b = 3;
这样非常正确的得到了a=3和b=3,但是a在局部作用域中而b在全局作用域中,”b=3“将会被先执行,全局操作的结果,3,再被分配给局部变量a。
下面的代码声明了两个值为3的变量,这样能达到预期的效果:
var a = 3,
b = a;
”this“和内部函数
”this“关键字通常指当前正在执行的函数所在的对象,然而,如果函数并没有在对象上被调用,比如在内部函数中,”this“就被设置为全局对象(window),如下代码:
var obj = { doSomething: function () { var a = "bob"; console.log(this); // 当前执行的对象 (function () { console.log(this); // window - "this" is reset console.log(a); // "bob" - still in scope }()); } }; obj.doSomething();
杂项
数据不存在:”null“和”undefined“
有两种对象状态来表明数据不存在:null和undefined。这会让那些从其他编程语言比如C#转过来的程序员变得相当混乱。也许你会期望下面的代码返回true:
var a; a === null; //false a === undefined; //true
“a” 实际上是undefined的(尽管你用双等号==来与null比较会得出true的结果,但这只是表面上看起来正确的另一个错误)。
如果你想检查一个变量是否真的存在值,那你不能用双等号==去判断,要用下面的方法:
if(a !== null && a !== undefined) { ... }
哈,你也许会说,既然null和undefined都是false,那么你可以这样去做:
if(a) {
...
}
当然,0是false,空字符串也是。那么如果这其中一个是a的正确的值的话,你就要用前者了。那种比较短小的比较方式,适合于比较objects, arrays, 和booleans类型。
重定义undefined
非常正确,你可以重定义undefined,因为它不是一个保留字:
undefined = "surprise!";
但是,你要通过给undefined变量分配一个值或者使用”void“操作符来取回值(否则这是相当没用的)。
undefined = void 0;
这就是为什么jquery脚本库的第一行要这样写了:
(function ( window, undefined ) { ... // jQuery library! }(window));
这个函数被调用时是传入一个参数的,同时确保了第二个参数”undefined“实际上是undefined的。
顺便说一下,你不能重定义null - 但是你可以重定义NaN,Infinity和带构造函数的内置类型。可以这样尝试一下:
Array = function (){ alert("hello!"); } var a = new Array();
当然,你可以在任何地方用文字语法声明Array。
可选的分号
Javascript代码中分号是可选的,所以初学者写代码就简单多了。但是很不幸的是如果忽略了分号并不会给任何人带来方便。结果是当解释器遇到错误时,必须追溯并尝试去猜测因为哪些分号漏写导致的问题。
这里有一个经典的例子:
return
{
a: "hello"
};
上面的代码并不会返回一个对象,而是返回了undefined - 但是也没有错误抛出。其实是因为分号自动加到了return语句后面,其他的代码都是非常正确的,但是就是什么都不执行,这就证明了在 javascript中,左花括号应该紧跟这一行而不该换行,这不只是一个编程风格的问题。下面的代码才会正确返回一个属性为a的对象:
return {
a: "hello"
};
NaN
NaN的类型是...Number
typeof NaN === "number" //true
另外NaN和任何东西比较都是false:
NaN === NaN; // false
因为NaN之间是不能比较的,唯一判断一个数字是否为NaN的方法是调用isNaN方法。
从另一个方面可以说明,我们也可以用函数isFinite,当其中一个操作数为NaN或者InFinity时返回false。
arguments对象
在一个函数中,我们可以引用arguments对象来遍历传入的参数列表,第一个比较怪异的地方是这个对象并不是Array,而是一个类似 Array的对象(有一个length属性,其值在0-length-1之间)。为了将其转换成array,我们可以array的splice函数来创建 其对应的array数组:
(function(){ console.log(arguments instanceof Array); // false var argsArray = Array.prototype.slice.call(arguments); console.log(argsArray instanceof Array); // true }());
第二个比较怪异的地方是当一个函数的签名中有显式arguments参数时,它们是可以被重新分配的并且arguments对象也会被改变。这就表明了arguments对象指向了变量本身。你不能利用arguments对象来给出它们的初始值:
(function(a){ alert(arguments[0]); //1 a = 2; alert(arguments[0]); //2 }(1));