谈起做用域链,咱们就不得不从做用域开始谈起。由于所谓的做用域链就是由多个做用域组成的。那么, 什么是做用域呢?javascript
每个函数在执行的时候都有着其特有的执行环境,ECMAScript标准规定,在javascript中只有函数才拥有做用域。换句话,也就是说,JS中不存在块级做用域。好比下面这样:html
function getA() { if (false) { var a = 1; } console.log(a); //undefined } getA(); function getB() { console.log(b); } getB(); // ReferenceError: b is not defined
上面的两段代码,区别在于 :getA()函数中,有变量a的声明,而getB()函数中没有变量b的声明。java
另外还有一点,关于做用域中的声明提早。闭包
在上面的getA()函数中,或许你还存在着疑惑,为何a="undefined"呢,具体缘由就是由于做用域中的声明提早:因此getA()函数和下面的写法是等价的:函数
function getA(){ var a; if(false){ a=1 }; console.log(a); }
既然提到变量的声明提早,那么只须要搞清楚三个问题便可:post
1.什么是变量this
2.什么是变量声明spa
3.声明提早到何时。指针
什么是变量?code
变量包括两种,普通变量和函数变量。
var x=1; var object={}; var getA=function(){}; //以上三种均是普通变量,可是这三个等式都具备赋值操做。因此,要分清楚声明和赋值。声明是指 var x; 赋值是指 x=1;
function fun(){} ;// 这是指函数变量. 函数变量通常也说成函数声明。
相似下面这样,不是函数声明,而是函数表达式
var getA=function(){} //这是函数表达式 var getA=function fun(){}; //这也是函数表达式,不存在函数声明。关于函数声明和函数表达式的区别,详情见javascript系列---函数篇第二部分
什么是变量声明?
变量有普通变量和函数变量,因此变量的声明就有普通变量声明和函数变量声明。
var x=1; //声明+赋值 var object={}; //声明+赋值
上面的两个变量执行的时候老是这样的
var x = undefined; //声明 var object = undefined; //声明 x = 1; //赋值 object = {}; //赋值
关于声明和赋值,请注意,声明是在函数第一行代码执行以前就已经完成,而赋值是在函数执行时期才开始赋值。因此,声明老是存在于赋值以前。并且,普通变量的声明时期老是等于undefined.
function getA(){}; //函数声明
声明提早到何时?
全部变量的声明,在函数内部第一行代码开始执行的时候就已经完成。-----声明的顺序见1.2做用域的组成
函数的做用域,也就是函数的执行环境,因此函数做用域内确定保存着函数内部声明的全部的变量。
一个函数在执行时所用到的变量无外乎来源于下面三种:
1.函数的参数----来源于函数内部的做用域
2.在函数内部声明的变量(普通变量和函数变量)----也来源于函数内部做用域
3.来源于函数的外部做用域的变量,放在1.3中讲。
好比下面这样:
var x = 1; function add(num) () { var y = 1; return x + num + y; //x来源于外部做用域,num来源于参数(参数也属于内部做用域),y来源于内部做用域。 }
那么一个函数的做用域究竟是什么呢?
在一个函数被调用的时候,函数的做用域才会存在。此时,在函数尚未开始执行的时候,开始建立函数的做用域:
函数做用域的建立步骤:
1. 函数形参的声明。
2.函数变量的声明
3.普通变量的声明。
4.函数内部的this指针赋值
......函数内部代码开始执行!
因此,在这里也解释了,为何说函数被调用时,声明提早,在建立函数做用域的时候就会先声明各类变量。
关于变量的声明,这里有几点须要强调
1.函数形参在声明的时候已经指定其形参的值。
function add(num) { var num; console.log(num); //1 } add(1);
2.在第二步函数变量的生命中,函数变量会覆盖之前声明过的同名声明。
function add(num1, fun2) { function fun2() { var x = 2; } console.log(typeof num1); //function console.log(fun2.toString()) //functon fun2(){ var x=2;} } add(function () { }, function () { var x = 1 });
3. 在第三步中,普通变量的声明,不会覆盖之前的同名参数
function add(fun,num) { var fun,num; console.log(typeof fun) //function console.log(num); //1 } add(function(){},1);
在全部的声明结束后,函数才开始执行代码!!!
在JS中,函数的能够容许嵌套的。即,在一个函数的内部声明另外一个函数
相似这样:
function A(){ var a=1; function B(){ //在A函数内部,声明了函数B,这就是所谓的函数嵌套。 var b=2; } }
对于A来讲,A函数在执行的时候,会建立其A函数的做用域, 那么函数B在建立的时候,会引用A的做用域,相似下面这样
函数B在执行的时候,其做用域相似于下面这样:
从上面的两幅图中能够看出,函数B在执行的时候,是会引用函数A的做用域的。因此,像这种函数做用域的嵌套就组成了所谓的函数做用域链。当在自身做用域内找不到该变量的时候,会沿着做用域链逐步向上查找,若在全局做用域内部仍找不到该变量,则会抛出异常。
闭包的概念:有权访问另外一个做用域的函数。
这句话就告诉咱们,第一,闭包是一个函数。第二,闭包是一个可以访问另外一个函数做用域。
那么,相似下面这样,
function A(){ var a=1; function B(){ //闭包函数,函数b可以访问函数a的做用域。因此,像相似这么样的函数,咱们就称为闭包 } }
因此,建立闭包的方式就是在一个函数的内部,建立另一个函数。那么,当外部函数被调用的时候,内部函数也就随着建立,这样就造成了闭包。好比下面。
var fun = undefined; function a() { var a = 1; fun = function () { } }
其实,理解什么是闭包并不难,难的是闭包很容易引发各类各样的问题。
看下面的这道例题:
var funB, funC; (function() { var a = 1; funB = function () { a = a + 1; console.log(a); } funC = function () { a = a + 1; console.log(a); } }()); funB(); //2 funC(); //3.
对于 funB和funC两个闭包函数,不管是哪一个函数在运行的时候,都会改变匿名函数中变量a的值,这种状况就会污染了a变量。
两个函数的在运行的时候做用域以下图:
这这幅图中,变量a能够被函数funB和funC改变,就至关于外部做用域链上的变量对内部做用域来讲都是静态的变量,这样,就很容易形成变量的污染。还有一道最经典的关于闭包的例题:
var array = [ ]; for (var i = 0; i < 10; i++) { var fun = function () { console.log(i); } array.push(fun); } var index = array.length; while (index > 0) { array[--index](); } //输出结果 全是10;
想这种相似问题产生的根源就在于,没有注意到外部做用域链上的全部变量均是静态的。
因此,为了解决这种变量的污染问题---而引入的闭包的另一种使用方式。
那种它是如何解决这种变量污染的呢? 思想就是: 既然外部做用域链上的变量时静态的,那么将外部做用域链上的变量拷贝到内部做用域不就能够啦!! 具体怎么拷贝,固然是经过函数传参的形式啊。
以第一道例题为例:
var funB,funC; (function () { var a = 1; (function () { funB = function () { a = a + 1; console.log(a); } }(a)); (function (a) { funC = function () { a = a + 1; console.log(a); } }(a)); }()); funB()||funC(); //输出结果全是2 另外也没有改变做用域链上a的值。
在函数执行时,内存的结构如图所示:
由图中内存结构示意图可见,为了解决闭包的这种变量污染的问题,而加了一层函数嵌套(经过匿名函数自执行),这种方式延长了闭包函数的做用域链。
内存泄露其实严格来讲,就是内存溢出了,所谓的内存溢出,当时就是内存空间不够用了啊。
那么,闭包为何会引发内存泄露呢?
var fun = undefined; function A() { var a = 1; fun = function () { } }
看上面的例题,只要函数fun存在,那么函数A中的变量a就会一直存在。也就是说,函数A的做用域一直得不到释放,函数A的做用域链也不能获得释放。若是,做用域链上没有不少的变量,这种牺牲还无关紧要,可是若是牵扯到DOM操做呢?
var element = document.getElementById('myButton'); (function () { var myDiv = document.getElementById('myDiv') element.onclick = function () { //处理程序 } }())
像这样,变量myDiv若是是一个占用内存很大的DOM....若是持续这么下去,内存空间岂不是一直得不到释放。长此以往,变引发了内存泄露(也是就内存空间不足)。