JS作用域&作用域链
变量
变量分为全局变量和局部变量,全局变量就是指该变量的作用域为当前文档,也就是说全局变量在当前文档的所有所有JavaScript脚本都可以访问到,都是有定义的。换言之就是说全局变量能够被访问的区域就是全局变量作用域。
<script type="text/javascript">
//定义了一个全局变量。那么这个变量在当前html页面的任何的JS脚本部分都可以访问到。
var v = 20;
alert(v); //弹出:20
</script>
<script type="text/javascript">
//因为v是全局变量,所以这里仍然可以访问到。
alert(v); //弹出:20
1 <script type="text/javascript"> 2 alert(a); 3 var a = 20; 4 </script>
这段代码并不会报错,而是输出undefined。
因为这是声明提前引起的。所有的全局变量的声明都会提前到JavaScript的前段声明,而且会早有其他一切代码。但是声明提前了,但是对于全局变量的位置不会提前,赋值的位置仍然是原位置。
<script type="text/javascript">
var a; //声明提前
alert(a);
a = 20; //赋值仍然在原来的位置
</script>
而局部变量指的是在函数内部声明的变量,其中函数的形参变量也是局部变量。局部变量的作用域是该局部变量所在的整个函数内部,出了这个函数就不能访问了。
局部变量的声明也会提前到所在函数的最顶端。
1 <script type="text/javascript"> 2 function f(){ 3 alert(v); // 弹出:undefined 4 var v = "abc"; // 声明局部变量。局部变量也会声明提前到函数的最顶端。 5 alert(v); // 弹出:abc 6 } 7 alert(v); //报错。因为变量v没有定义。 方法 f 的外部是不能访问方法内部的局部变量 v 的。 8 </script>
分解得
1 <script type="text/javascript"> 2 function f(){ 3 var v //声明提升 4 alert(v) 5 v = "abc"; 6 alert(v); 7 } 8 alert(v); 9 </script>
全局和局部变量重名
当在同一个作用域内定义了名字相同的变量和方法的话,会根据前置顺序产生覆盖
1 var fn = 3; 2 function fn(){} 3 4 console.log(fn); // 3
分解相当于
1 var fn 2 function fn() {}//覆盖上面的 3 fn = 3//重新复制 4 console.log(fn)//变量和函数会提升到最前面
函数执行有命名冲突的时候,可以认为在还是内部一开始有隐藏的声明变量这个操作
1 function fn(fn){ 2 console.log(fn);//同样给了fn(10) 3 4 var fn = 3; 5 console.log(fn);//3 6 } 7 8 fn(10); //10 3
全局变量下可以通过window访问
1 var m = 10; 2 function f(){ 3 var m = 20; 4 alert(window.m); //访问同名的全局变量。其实这个时候相当于在访问window这个对象的属性。 5 } 6 f();
JavaScript中没有块级作用域,这是与其他强类型的语言不同,JavaScript的作用域是靠函数来形成的,也就是说一个函数内定义的变量函数外不可以访问
var m = 5; if(m == 5){ var n = 10; } console.log(n); //10
在web浏览器中,全局执行环境是最外围的一个执行环境,该环境也被认为是window对象,因此全局执行环境的变量对象就是window对象。而对每个函数而言,都有自己的执行环境,也就是该函数的内部。每当执行一个函数,就会进入该函数的执行环境。
而作用域链是与执行环境相关的。在JavaScript中,“一切皆对象”,而函数的这个对象有一个内部属性[[scope]],该内部属性指向了该函数的作用域链,而作用域链中存储了每个执行环境相关的变量对象。

1 function sum(num1, num2) { 2 var sum = num1 + num2; 3 function inner(a) { 4 5 } 6 return sum; 7 } 8 var sum = sum(3, 4); 9 console.log(sum)//7
以上是在创建(或声明)一个函数时会创建一个作用域链,而当函数被调用时,也就是进入到一个新的执行环境的时候,此时这个执行环境也就会有一个新的变量对象被创建,而这个对象就会被存储在该函数的[[scope]]属性所指向的作用域链中,而之前的对象就被压在了新的变量对象的下边,这个可以类比栈,新的变量对象就放在了栈的最顶端,给最顶端的序号为0,向下以此类推。
当以后我们需要查找变量的时候,就总是会沿着这个作用域链的顶端(序号0/栈顶)开始查找,一直到作用域链(栈底)的末端,直到找到为止。也就是说顶端的活动对象可以访问到其后面的参数。
1 var a = 1 2 function fn1(){ 3 function fn2(){ 4 console.log(a)//他的上一级函数变量里有 a=2 5 } 6 function fn3(){ 7 var a = 4 8 fn2() 9 } 10 var a = 2 11 return fn3 12 } 13 var fn = fn1() 14 fn() //输出多少
var a = 1 function fn1(){ function fn3(){ var a = 4 fn2() } var a = 2 return fn3 } function fn2(){ console.log(a)//他的上一级是全局变量 a=1 } var fn = fn1() fn() //输出多少//1
1 var a = 1 2 function fn1(){ 3 4 function fn3(){ 5 function fn2(){ 6 console.log(a)//fn3 a=4但是对自身没有印象但对内嵌函数有影响。 7 } 8 var a 9 10 fn2() 11 a = 4 12 } 13 var a = 2 14 return fn3 15 } 16 var fn = fn1() 17 fn() //输出多少
- 函数在执行的过程中,先从自己内部找变量
- 如果找不到,再从创建当前函数所在的作用域去找, 以此往上
- 注意找的是变量的当前的状态
补充函数表达式不会提前声明
1 sayName('world'); 2 sayAge(10); 3 function sayName(name){ 4 console.log('hello ', name); 5 } 6 var sayAge = function(age){ 7 console.log(age); 8 };
分解应该是如下
1 sayName('world'); 2 sayAge(10); 3 var sayAge; 4 function sayName(name) { 5 console.log('hello ', name); 6 } 7 sayAge = function (age) { 8 console.log(age); 9 };

浙公网安备 33010602011771号