笔记-JS 作用域
全局作用域和函数作用域
在ES6之前,javascript只有两种作用域,全局作用域和函数作用域。
var scope="global";
function foo(){
var scope="local";
console.log(scope);
}
foo(); //输出结果:local
console.log(scope); //输出结果:global
如果变量在函数内没有声明,即没有使用var关键词,该变量也为全局变量。
//此处可调用carName变量
function myFunction(){
carName="Volvo";
//此处可调用carName变量
}
函数参数只在函数内起作用,是局部变量。
Javasrcipt变量生命周期在它声明时初始化,局部变量在函数执行完毕后销毁,全局变量在页面关闭后销毁。
块级作用域
ES6之前,Javascript并不存在块级作用域。如下代码所示,变量scope被定义在for循环语句的代码块之内,在循环之外引用scope变量由于缺少块级作用域,变量被扩散到全局作用域中,因此可以被正常输出。
for(var scope=0;scope<5;scope++){}
console.log(scope); //输出结果:5
缺少块级作用域不仅会导致代码块执行完毕后变量不会被回收,并且由于定义在代码块中的变量被扩散到上一层作用域中,还会导致一种称为变量声明提升的副作用。
if(!("a" in window)){var a=1;}
console.log(a); //输出结果:undefined
//???为什么不是a=1,a=1变量声明提升至全局中,所以不应该输出a=1吗
ES6引入了let命令来改变缺少块级作用域导致混乱的问题。let命令与var命令不同的是,let声明的变量只存在于其所在的代码块中,如下:
function foo(){
let scope="function";
if(true){
let scope="block";
console.log(scope); //输出结果:block
}
console.log(scope); //输出结果:function
}
ES6之前,在多人协作的项目中,为避免由于定义相同的变量名而产生的异常,通常使用匿名函数划分各自的作用域。而let定义的变量只作用于各自的作用域中,不会发生重复定义同名变量导致被覆盖的错误。同时,使用let命令定义的变量,并不会产生声明前置的效果,即变量必须在声明之后才能使用,未声明前的引用会报错,称之为"暂时性死区"。
typeof a;
var a=1; //undefined
typeof b;
let b=1; //ReferenceError
作用域链
作用域链是由当前作用环境与上层执行环境的一系列变量对象组成的,它保证了当前执行环境对符合权限的变量和函数的有效访问。
var a=20;
function test(){
var b=a+10;
function innerTest(){
var c=10;
return b+c;
}
return innerTest();
}
test();
在上面的例子中,先后创建了全局函数test和函数innerTest的执行上下文,假设他们的变量对象分别为VO(globe),VO(test)和VO(innertest),则innerTest作用域链同时包含这三个变量对象。
//innnerTest的执行上下文
innerTestEC={
VO:{...}, //变量对象
scopeChain:[VO(innerTest),VO(test),VO(globel)], //作用域链
this:{}
}
可以用一个数组来表示作用域链的有序性。数组的第一项scopeChain[0]为作用域的最前端,数组的最后一项为作用域链的最末端。因此所有的作用域链的末端都是全局变量对象。
var a=1;
function fn(){
var b=2;
//当前作用域没有定义的变量,即"自由变量"
console,log(a); //1
console.log(b); //2
}
fn();
var a=1;
function fn1(){
var b=2;
function fn2(){
var c=3;
console.log(a); //1
console.log(b); //2
console.log(c); //3
}
fn2();
}
fn1();