作用域链、立即执行函数、闭包

版权声明:本文为博主原创文章,未经博主允许不得转载 https://blog.csdn.net/weixin_42472040/article/details/85221710

目录

一、作用域链详解

作用域

作用域链

执行期上下文

代码示例

示例图解

二、闭包

闭包的概念

闭包的作用及好处

闭包的缺点

闭包的应用

代码示例

三、立即执行函数

定义

特点

形式


一、作用域链详解

作用域

作用域是变量或函数生效的区域。[[scope]]是函数的作用域,其中存储着作用域链。[[scope]]是函数的一个隐式属性,不能被用户主动访问,仅供Javascript引擎存取。

作用域链

函数的[[scope]]属性中存储着不同执行期上下文构成的集合,这个集合呈链式链接,被称为作用域链。函数定义后[[scope]]中存储其所在环境的作用域,预编译会将新生成的AO置于作用域链顶端。每一个函数都有一条作用域链,函数中变量的查询是从作用域链的顶端开始依次向下查找,直至最后也没找到则返回undefined。

函数的链式作用域导致内部函数可以调用外部变量,外部不能调用内部变量。

执行期上下文

预编译产生的AO或GO就是执行期上下文。AO是函数的执行期上下文,GO是全局的执行期上下文。每次预编译产生的AO都是独一无二的,它定义了函数执行时的内部环境,在函数执行完毕后被立刻销毁,之后函数回归到刚定义时的状态,等待下次执行。

代码示例

//GO{
//  this: window,
//  window: (object),
//  document: (object),
//  a: function a(){ ... },
//  glob: 100
//}
function a(){  
    var a = 123;    
    function b(){} 
    b();             //因为b在a中定义,所以b会随a的执行期上下文一起被销毁
}
var glob = 100;
a();               

//函数a,b不同时期(定义时、执行时)的作用域链:

//a defined:a.[[scope]] ---> 0: GO{}      

//a doing  :a.[[scope]] ---> 0: aAO{}         
//                            1: GO{}                                       

//b defined:b.[[scope]] ---> 0: aAO{}     由于a执行产生了b定义 ---> b继承a的作用域
//                            1: GO{}      
   
//b doing  :b.[[scope]] ---> 0: bAO{}     函数预编译时把自己的AO放在作用域链的顶端
//                            1: aAO{} 
//                            2: GO{}        

//a,b作用域链中的所有的aAO指的是同一个,所有的bAO指的是同一个,所有的GO指的是同一个
//由作用域链可以看出 ---> 内部的函数可以调用外部的变量,外部的不能调用内部的变量
//a执行时,查找变量顺序:aAO ---> GO
//b执行时,查找变量顺序:bAO ---> aAO ---> GO       

示例图解

a函数定义时作用域链:

a函数执行时作用域链:

 

二、闭包

闭包的概念

把内部函数保存到外部就会形成闭包,闭包可以理解成一个函数,即保存到外部的内部函数。

闭包的作用及好处

闭包是函数内部和外部连接的桥梁,它继承了外部函数的作用域。通过闭包可以读取函数内部的变量,可以让函数中的变量值始终存储在内存里。使用闭包可以避免全局变量的污染。

闭包的缺点

闭包使外层函数作用域链不释放,使用不当易造成内存泄漏。内存泄漏就是内存占用,可以把内存理解成手中的沙子,泄漏的多剩下的就少了。

function a(){  
    function b(){
        console.log(aaa);
    }
    var aaa = 234;
    return b;  
}
var glob = 100;
var demo = a();  //即使函数a执行完后被销毁了,但因其作用域链被闭包函数继承,所以它的作用域链不会被释放
//GO{
//    glob: 100,
//    demo: undefined,
//    a: function a(){ ... }
//}
//aAO{
//    aaa: 234,
//    b: function b(){ ... }
//}
//bAO{
//   
//}
demo();   //demo doing: demo.[[scope]] ---> 0: bAO{}
          //                                1: aAO{}
          //                                2: GO{}    
< 234

闭包的应用

1.实现公有变量,如函数累加器

function add(){
    var count = 0; //共有变量,属于函数add的AO
    function demo(){
           count++;
           console.log(count);
    }
    return demo;
}
var counter = add(); 
counter();          
counter();
< 1
< 2
function test(){
    var num = 100;
    function a(){
        num ++;
        console.log(a);
    }
    function b(){
        num --;
        console.log(b);
    }
    return [a,b];
}
var myArr = test();
myArr[0]();
myArr[1]();

2.可以做缓存(存储结构)

function eater(){
    var food = "";
    var obj = {
        eat:function(){
             console.log("i am eating" + food);
             food = "";
        },
        push : function (myFood){
              food = myFood;
        }
    }
    return obj;
}
var eater1 = eater();   //闭包会导致eater的作用域链无法释放
eater1.push("banana");
eater1.eat();

3.可以实现封装,属性私有化

4.模块化开发,防止污染全局变量

代码示例

function test(){
    var arr = [];
    for(var i = 0;i < 10;i ++){
         arr[i] = function () { //仅仅定义函数,没有执行函数,所以函数引用中的i不会被系统翻译
               console.log(i + " ");      
         }
   }
   return arr;
}
var myArr = test(); //test函数返回数组arr,里面装的是十个函数,形成十个闭包
for(var j = 0; j < 10;j ++){
    myArr[j]();     //闭包函数的作用域链中testAO是同一个
                    //testAO中i = 10,所以十个函数中的i都会被系统翻译成10
}      
< 10 10 10 10 10 10 10 10 10 10 10 10
//使用匿名自执行函数+闭包 ---> 输出0-9
function test(){
    var arr = [];
    for(var i = 0;i < 10;i ++){
         (function(j){ //使用立即函数中转一下,当作中间层,在下面执行函数时,不使用变量i
               arr[j] = function () {         
                     console.log(j + " ");   
               }
         }(i));
   }
   return arr;
}
var myArr = test();                    
for(var j = 0; j < 10;j ++){
    myArr[j]();                   
}                   
//执行结果 0 1 2 3 4 5 6 7 8 9

//立即执行函数执行完后虽然被销毁了,但因其作用域链被闭包函数继承,所以它的作用域链不会被释放
//每一个闭包函数中的j是不一样的     

三、立即执行函数

定义

定义完后立即被执行的函数叫立即执行函数。

特点

立即执行函数没有声明,执行完立即被销毁,适合做初始化工作。销毁的是立即执行函数的引用而不是代码,函数在视觉上客观存在,只是被永久性放弃了 。立即执行函数是Javascript提供的唯一一个能手动销毁函数的方法。立即执行函数除了其独有的特点外其余和普通函数没有任何区别。立即执行函数可以有返回值。  

var num = (function(){ 
     return 1;
}()); 

> num
< 1
//函数执行完后立即释放,并将返回值赋给num

形式

执行符号

立即执行函数不是明文规定的语法,它利用执行符号来起作用。执行符号就是一对小括号,只有表达式才能被执行符号执行。函数声明不是表达式所以不能被执行符号执行,但它可以通过特殊符号(如括号、等号、一元正负、与、或、非)转变为表达式从而被执行。数学运算的小括号优先级高于执行符号。 

> function test(){

  }();
//只有表达式才能被执行符号执行
//语法解析错误,系统认为这是一个非常底端的错误,扫一遍还没执行就报错了

> function test(){

  }
> test()
//成功执行,因为test就和123、"123"(数字表达式,字符串表达式)一样都是表达式
function test(){

}
(1); 
//不报错,系统会把函数和(1)分开解释
//(1)就是个1,加数学运算符括号没影响
function test(){

}

(1); 
(function test(){

}())
> test
//报错,函数被执行符号执行后,函数名被放弃
//所以可以省略函数名
(function(){

}())
//应为数学运算的小括号优先级高于执行符号
//所以可以把执行符号拿到外面
(function(){

})()

立即执行函数标准形式解析 

// ()是数学计算符,它可以使函数变为表达式 ---> (function test(){})

// 省略函数名后 --> (function (){}) 

// 表达式可以被执行符号执行 --> (function (){})() 

// 后面的括号可以被放到里面-->(function(){}()), 这就是立即执行函数最标准的形式

// 这些括号中先执行最外层的括号,只有最外层的括号是数学运算的小括号,其余括号都有语法意义

// 先执行外面的括号会把里面的函数变成表达式,这样就可以被执行符号执行了,执行符号放在最外层括号
// 里面和外面都行

利用特殊符号实现立即执行函数

//第一种:利用括号(官网w3c推荐使用)
(function(){  }())
(function(a,b,c){ }(1,2,3))  //第一个小括号里面可以放形参,第二个放实参

//第二种:利用括号
(function(){ })()

//第三种:利用一元正负
+function(){  }() 
-function(){  }() 
//+、-指的是一元正负而不是加减
//在函数声明的前面加一个一元正号,意思是把函数转换成一个数字,在趋势上它就是一个表达式了

//第四种:利用与、或、非
!function(){  }()       //非是对函数的返回值进行取反操作
true && function(){  }() 
false || function(){  }() 
//与和或前面必须加表达式,要保证能执行到函数部分才行

//第五种:利用等号
var num = function(){  return 1 }();

//以上形式,执行符号中都可以传实参
//函数被执行符号执行后函数名被放弃,所以以上代码都省略了函数名

猜你喜欢

转载自blog.csdn.net/weixin_42472040/article/details/85221710