刚刚开始学习javascript时,其实并没有过多的看过相关方面的书籍,只是稍微了解了其语法和如何定义函数、变量,凭借着自己之前写过java和 c++的经验,开始了我的前端之路。写一些小的项目还基本OK,可是有时候总是踩坑,还半天爬不出来。后来,有空 看了看JavaScrip高级程序设计,才发现其实js跟之前的c++和java还是有很多区别的,很多时候踩坑就是因为不清楚js跟这些语言本质上的区别。在此,我记录下一些看书时候觉得比较有用,但是可能被广大小白同学忽略的问题,希望对大家有帮助。对于有时间的初学朋友,还是推荐能好好的看看《JavaScrip高级程序设计》,必定会有很多的收获。
一、变量的作用域
1.1无块级作用域
与大多数语言相同的是,在全局环境中的变量存在与整个程序运行中,在局部作用域中的变量离开其作用域之后,被销毁。不同之处在javascript没有块级作用域。在c或java中,在花括号中定义的变量,离开这对花括号之后,便被销毁。可是,于 javascript来讲,在花括号中定义的变量的作用域并不仅仅存在与花括号内。如下:
if (true) { var hi = "hi"; } console.log(hi);
在c或java中,hi在if语句中定义,出了if的作用域就被销毁,在if之外访问hi会引发程序错误,但是,因为js没有块级作用域,所以在if中定义的变量在if语句之外的作用域还 是有效的。所以上述代码在js中是正确的。类似的容易搞错的还有在for循环中定义的变量。如下:
for (var i=1;i<10;i++) { doSomething(i); } console.log(i);
在for的条件中初始化的变量在for循环之外还是存在的。
1.2 变量作用域范围
通过上述例子,可能有很会好奇在花括号中定义的变量的作用域又到哪里呢?难道在全局范围内有效么?
关于这个问题就需要讲到js中的执行环境这个概念。执行环境规定了变量或者函数有权访问的哪些数据。每个执行环境中有一个对应的变量对象,变量对象保存了该环境中定义的变量和函数。
全局执行环境是最外层的执行环境。在web浏览器中,全局执行环境的对象是window对象。也就是说所有在全局中定义的函数和变量都属window对象的属性和方法。执行环境在改环境中的所有代码执行后,其中的变量和函数被全部销毁(全局执行环境直到应用程序退出才被销毁)。
每个函数都有自己的执行环境。当函数执行完后,其执行环境被销毁,控制权返回给之前的执行环境。所以,定义的变量的作用域是离定义这个变量最近的函数的执行环境中。如果定义这个变量是在全局范围内,那变量的作用域是全局范围。
for (var i=1;i<10;i++) { if(true) { if(true) { var hi = "hi" } } } console.log(hi); doSomthing();function doSomting () { if (true) { for (var i=1;i<10;i++) { var hello = "hello"; } } console.log(hello); }
上述代码中,无论hi是在多少个内层花括号中定义的,它在离它最近的执行环境中都是有效的(这里是全局执行环境)。而hello在doSometihng函数中定义,所以,hello在doSomething中都是有效的。
1.3 作用域链
当代码在一个环境中执行时,会创建一个作用域链,作用域链就是为了执行环境能有序地访问变量和函数。作用域链最开始是当前代码所在环境中的变量对象。下一个变量对象是包含当前环境的外部环境,就这样一直包含到全局执行环境。所以作用域链的最后一个对象总是全局执行环境中的变量对象。
在访问变量时,就是从作用域的最前端开始,逐级寻找变量的定义。如下:
var hi = "hi"; function sayA () { var hello = "hello"; function sayB() { var world = "world"; // 可以访问hello ,hi,world console.log(hi + "," + hello + ' ' + world); } // 可以访问hello ,hi,但是不能访问world console.log(hi + "," + hello); }
通过这个小例子,我们可以看出在js中,即使没有通过参数将外部变量传递给函数,但是通过作用域链还是可以访问外部函数中定义的变量。但是外部函数不能访问内部函数定义的变量。因为外部函数的作用域链中不能包含内部函数的变量对象。所以任何函数都能访问全局环境中的变量和函数同时,如果一个变量名在多处被定义,则根据作用域链访问顺序,采用第一个访问到的变量名的定义。 如下:
var a = 9; sayA(); function sayA () { var a = 7; sayB(); function sayB() { console.log(a); } }
打印出 :7
因为sayB先访问自己的变量对象,没有a的定义,再访问sayA的变量对象,找到了a的定义,就不会再继续找了。