一 作用域链
1.在理解什么事作用域链之前我们首先要了解几个概念:变量、变量的声明、变量声明提前
变量:变量包括两种,普通变量和函数变量。
普通变量:凡是用var标识的都是普通变量
var x=1;
var object={};
var getA=function(){}; //以上三种均是普通变量,但是这三个等式都具有赋值操作。所以,要分清楚声明和赋值。声明是指 var x; 赋值是指 x=1;
函数变量:函数变量特指的是下面的这种,fun就是一个函数变量。
function fun(){} ;// 这是指函数变量. 函数变量一般也说成函数声明。
为了便于理解,我们在这里将将分开讨论普通变量的声明和函数变量的声明
普通变量:声明是在函数第一行代码执行之前就已经完成,而赋值是在函数执行时期才开始赋值。所以,声明总是存在于赋值之前。
而且,普通变量的声明时期总是等于undefined.所以如果在赋值之前就调用普通变量的话就会是undefined;
alert(x);
var x=3;//undefined
函数变量:函数声明也是提前到在函数第一行代码之前,所以如果在一段代码中先调用函数,然后再写出函数,也是可以被正确执行的,因为函数声明会提前。
f(5);
function f(n){alert(n)}//5
函数的作用域,也就是函数的执行环境,所以函数作用域内肯定保存着函数内部声明的所有的变量。
所以函数的变量的来源无非三个方面:A:函数的参数
B:函数内部的变量
C:函数外部的作用域的变量
举个栗子:
var x = 1;
function add(num){
var y = 2;
return x+y+num;
}
console.log(add(5));//console 7
在函数调用的时候函数的作用域才存在,调用之前开始创建函数的作用域(执行环境)
创建步骤:a.函数形参的声明
b.函数内部变量的声明
c.普通变量的声明(执行函数外部的变量的声明)
d.函数内部this指针赋值
..........函数内部代码开始执行
这也解释了为什么声明提前,在函数开始执行之前,需要声明所有函数作用域内变量将其储存在一个“变量对象”里
关于变量的声明这里有几点需要注意:
ps1.函数的形参在声明时已经指定了值
function show(num){
return(num);
}
show(5);//5
ps2.第二步函数变量的声明,会覆盖以前声明过得同名变量
function add(num) {
var num = 10;
alert(num);
}
add(1);//出现10 而不是1
ps.3第三步中普通变量的声明不会覆盖以前声明过得同名变量
var num = 10;
function add(num) {
alert(num);
}
add(1)//1
会覆盖和不会覆盖的原因解释:作用域链!
3 作用域链的组成
在js的函数是允许嵌套的比如
var a = 1;
function A(d){
var b = 2;
function B(){
var c=3;
return a+b+c+d;//这里可以访问abcd
}
return B()+a+b+d;//这里可以访问abd
}
//这里只能访问a
alert(A(4));
当执行函数B的时候,会从函数内部开始搜寻变量abcd,只找到c然后向包围他的函数A搜寻再向全局环境搜寻直到找到标示符
如果执行的是函数A则作用域是A内的变量和全局变量这是A的执行环境,将A的执行环境里面的变量保存在一个变量对象里面,作用域链的头从A的变量开始尾巴是全局变量,在这个过程中开始匹配标示符知道全部找到,如果都找不到,抛出错误,在这里也解释了为何会有同名变量的覆盖
二 闭包
关于闭包好像不是那么的容易理解,现在的我翻译几个外国网站上的例子来加深自己的理解:
闭包(与镶嵌函数有关一个函数内还定义了另个函数,其中的函数一般是第一类型函数)是一种支持第一函数的方法,可以这样表达,,当他被宣布为第一函数的时候,他被分配给一个变量,或者是
作为结果返回到上一级的函数,这个第一函数它能够访问他的作用域范围(作用域链向上延伸知道全局变量)内的变量。现在,有一个变量!它里面存储的是作为第一函数的那个函数
只要这个变量被执行,那么这个第一函数能访问到的其他变量就会一直存在,即便是他所在的函数已经执行完毕(通常来说,某个变量只存在他所在的函数被执行阶段
,执行完毕后被内存释放)内存不会释放。所以如果你调用闭包函数(存储第一函数的变量)那么有可能改变了上一级的变量,然后再执行闭包函数是访问到的上一级的
变量已经是改变后的变量值。
举个例子:
function sayHello2(name){
var text='hello'+name;
var say=function(){console.log(text);}
return say;
}
var say2=sayHello2('bob');
say2();
也可以换种写法同样还是这个函数
var say2=undefined;
function sayHello2(name){
var text="hello"+name;
say=function(){console.log(text);}
}
sayHello2('Wei');
say();
闭包函数因为作用域内的变量无法释放会产生一些有意思的现象:
举个例子:
function A(){
var a=10;
var B=function(b){
return console.log( b+a++);
}
return B;
}
var C=A();
C(10);//20
C(10);//21
C(10);//22
var a=10;
var B=function(b){
return console.log( b+a++);
}
return B;
}
var C=A();
C(10);//20
C(10);//21
C(10);//22
下面来看另个类似的函数:
var B=function(b){
var a=10;
return console.log( b+a++);
}
var a=10;
return console.log( b+a++);
}
B(10);//20
B(10);//20
B(10);//20
思考一下为什么会出现上面两种情况:理解的重点在于变量的存活时间,第一个a变量因为闭包函数的存在内存无法释放所以每次调用闭包函数a的值都会加1,而下次再调用闭包函数
访问的a的值是加1后的,下面的情况是:函数执行完毕后内存被释放销毁,再次调用函数时重新将变量a放入栈中,所以值是没有变化的