先来看一段代码:
alert(a); //undefined
alert(b); //undefined
alert(c); //报错,Uncaught ReferenceError: c is not defined
alert(d); //报错,Uncaught ReferenceError: c is not defined
var a=1;
if(false){ //按顺序扫描时,当作这一行不存在的,依然把b加入到window中去
var b=2;
}else{
c=3;
}function f() { //f是window对象的一个全局成员,d的话是f的内容,不会出现在window中去
var d=4;
}
我们来分析以下上述代码的输出。
用var定义的变量a,会被添加到词法作用域中去,因此输出为undefined;
用var定义的变量b,也是一样的undefined;
全局变量c,不会被添加到词法作用域中,在执行之前调用时,会报错;
用var定义的变量d,因为d在f中,f在window中,所以d不会在window中,也就是不会被添加到词法作用域中去。
什么是作用域
作用域确定了一个变量,函数,成员在程序中可以被访问到的范围;也可以用来做信息隐藏(如上述代码中的d,就相当于被隐藏起来了)
块作用域
用{}包起来的部分,即为块
for (var i=0; i<3; i++){
}
alert(i);//3
这说明了js中好像没有块级作用域的概念。否则i将只在for循环中生效,而不会被alert访问到。
但是ES6提供的新的变量定义方式let解决了js中块级作用域的问题
for (let i=0; i<3; i++){
}
alert(i);//3
此时报错:Uncaught ReferenceError: i is not defined。即用let声明变量后,被声明的变量只在它的块级作用域内能被访问到。
函数作用域
function f(){
var x;//变量x只在函数内部生效
}
动态作用域
function f(){
alert(x);
}
function f1() {
var x=5;
f();
}
f1();//调用函数f1之后报错:x is not defined.也就是说,函数f1中的f访问不到上一行的x=5;
所以js是没有动态作用域的概念的,本身是静态作用域
静态作用域(也称作词法作用域或者闭包)
仍以上个代码为例:解析器执行代码的时候,会给f创建一个scope,它指向创建这个f时候的词法环境,即f.scope-->f.le,而创建f时候的词法环境是window,也就是说,内部有一个我们看不见的scope指向window
真正执行代码的时候,f会创建自己的词法环境,会和f的作用域scope本身会关联起来。
【这其实也就解释了上段代码为何会报错:调用f的时候,产生scope,去window中找要alert的x,发现window中没有,所以就报错了】
那么我们在全局中增添一个变量即可:
var x=100;
function f(){
alert(x);
}
function f1() {
var x=5;
f();
}
f1(); //此时输出为100;
js的作用域的链条
function f(){ //创建f时,scope==window
//执行时,创建自己的词法环境,简称为f.le;此时f.le--> f.[[scope]]
var x=100;
function g() { //g.scope --> f.le
//g.le --> g.scope
}
g();
}
new Function创建的函数,它的作用域永远指向全局
function f() {
var x=100;
var g=new Function("","alert(x)");
g();
}
f();//此时报错,x is not defined
只有当全局中存在x变量,才能被读取
var x=2
function f() {
var x=100;
var g=new Function("","alert(x)");
g();
}
f();//2
作用域的本质和用途
先在函数本身词法环境中找,再到父函数的此法环境中找,再到window中找。 用途就是可以隐藏变量,减少冲突。
(function () {
var a=5;
var b=6;
function f(){
alert(a);
}
window.f = f;
})();
f();//5
此时的变量a,b就相当于被隐藏起来了。