Javascript作用域原理
理解 JavaScript 作用域和作用域链
作用域
作用域就是对变量和函数可访问的范围,作用域控制着函数和变量的可见性和生命周期
全局作用域
在代码中,任何地方都能访问到的的对象具有全局作用域。以下三种情况具有全局作用域。
最外层函数和在最外层定义的变量具有全局作用域。var name = "cindy"; //全局变量 function func(){ //全局 var anotherName = "sam"; alert(anotherName); }
所有未定义直接赋值的变量自动声明为拥有全局性。
var name = "cindy"; //全局 function func(){ //全局 var anotherName = "sam"; name1 = "jack"; //全局 alert(name1); }
所有windows对象都拥有全局作用域。
局部作用域
与全局作用域相反,局部作用域一般只在固定的代码段中可以访问到,常见的就是定义在函数内部的变量。
作用域链
当代码在一个环境中执行时,会创建变量对象的一个作用域链(scope chain),作用域链的用途,是保证对执行环境有权访问的所有变量和函数的有序访问。
作用域链是由一个一个变量对象链接起来的一个链,整个作用域链构成了当前执行环境中变量和函数可访问的范围,即作用域。由于变量对象是按一定顺序链接在一起的,所以就达到了对所有可访问变量、函数有序访问的效果。那么它们是按怎样的顺序链接成作用域链的呢?这就要说到活动对象。
当函数运行时就会为其创建一个活动对象,其中包含形参和函数特殊的arguments对象。活动对象之后会做为函数执行环境的变量对象来使用。
作用域链的前端,始终都是当前执行的代码所在环境的变量对象。但如果这个环境是函数,则将其活动对象作为变量对象,放在其作用域链的前端。作用域链中的下一个变量对象来自包含环境,而再下一个变量对象来自下一个包含环境……这样一直延续到全局执行环境。全局执行环境的变量对象始终是作用域链中的最后一个变量对象。
为什么执行环境是函数会有这样特殊的规定呢?
JavaScript中的函数运行在它们被定义的作用域里,而不是它们被执行的作用域里
在JavaScript中,一切都是对象,对象具有通过代码访问属性和一系列仅供JavaScript引擎访问的内部属性。其中一个内部属性就是[scope],这个属性包含了函数被创建的作用域中对象的集合,这个集合也称作为作用域链(scope chain);
函数被定义时,会将定义时刻的scope chain 链接到这个函数对象的[scope]属性上。
函数被调用时,就会创建一个活动对象(activation object),活动对象最开始只包含一个变量,即arguments对象(这个对象在全局环境中不存在的),然后对于函数的每一个形参,都命名为该活动对象的命名属性,然后将这个活动对象做为此时的作用域链最前端,并将这个函数的[[scope]]属性加入到作用域链中.
var name1 = "ben";
function func(){
console.log(name1);
var name1 = "john";
console.log(name1);
}
func();
结果是
undefined
john
有人可能觉得会是
ben
john
在函数func被创建时,它的作用域链中会填入一个全局对象,该全局对象包含了所有全局变量。
执行此func函数时
在这里将调用参数赋值给形参数,对于缺少的调用参数,赋值为undefined。所以name1为undefined.所以在真正执行时,按照作用域链的排列来查询.
在函数执行过程中,每遇到一个变量,都会经历一次标识符解析过程以决定从哪里获取和存储数据。该过程从作用域链头部,也就是从活动对象开始搜索,查找同名的标识符,如果找到了就使用这个标识符对应的变量,如果没找到继续搜索作用域链中的下一个对象,如果搜索完所有对象都未找到,则认为该标识符未定义。函数执行过程中,每个标识符都要经历这样的搜索过程。
在执行console.log(name1);
标识符解析,从最开始的活动对象查询到的为表中的undefined,在执行console.log(name1);
标识符解析,将mark赋值给name1;所以结果是mark.
注意到, 因为函数对象的[[scope]]属性是在定义一个函数的时候决定的, 而非调用的时候。
如这个例子:
var name = "mark";
function intro(){
console.log(name);
}
function changeName(){
var name = "martin";
intro();
}
changeName();
结果是
mark