【JS脚丫系列】重温闭包

闭包概念解释:

闭包(也叫词法闭包或者函数闭包)。数组

在一个函数parent内声明另外一个函数child,造成了嵌套。函数child使用了函数parent的参数或变量,那么就造成了闭包。浏览器

闭包(closure)是能够访问外部函数做用域中的变量或参数的函数。闭包

此时,包裹的函数称为外部函数。内部的称为内部函数或闭包函数。(zyx456自定义:或称为父函数和子函数)。app

闭包wiki函数

JS采用词法做用域(lexical scoping),函数的执行依赖于函数做用域,这个做用域是在函数定义时决定的,而不是函数调用时决定的。性能

词法做用域:词法做用域也叫静态做用域,是指做用域在词法解析阶段就已经肯定了,不会改变。
动态做用域:是指做用域在运行时才能肯定。this

参看下面的例子,引自杨志的回答code

var foo=1;

function static(){
    alert(foo);
}

!function(){
    var foo=2;
    
    static();
}();

在js中,会弹出1而非2,由于static的scope在建立时,记录的foo是1。
若是js是动态做用域,那么他应该弹出2

zyx456:识别闭包,在词法分析阶段已经肯定了。对象

当外部函数运行的时候,一个闭包就造成了,他由内部函数的代码以及任何内部函数中指向外部函数局部变量的引用组成。生命周期

注意事项

01,闭包函数做用域中,使用的外部函数变量不会被马上销毁回收,因此会占用更多的内存。过分使用闭包会致使性能降低。建议在很是有必要的时候才使用闭包。

02,同一个闭包函数,所访问的外部函数的变量是同一个变量。

03,若是把闭包函数,赋值给不一样的变量,那么不一样的变量指向的是不一样的闭包函数,所使用的外部函数变量是不一样的。

04,闭包函数分为定义时,和运行时。只有运行时,才会访问外部函数的变量。

05,在for循环的闭包函数,只有在运行时,才在做用域中寻找变量。for循环会先运行完毕,此时,闭包函数并无运行。

06,若是在for循环中,使用闭包的自执行函数。那么闭包会使用for循环的变量i(0-*,假设i从0开始)。

07,一个函数里,能够有多个闭包。

匿名自执行函数,能够封装私有变量。不会污染全局做用域。匿名函数中定义的任何变量,都会在执行结束时被销毁。

eval+with(仅了解)

在评论中贺师俊还提到,eval 和 with能够产生动态做用域的效果:

好比 with(o) { console.log(x) } 这个x实际有多是 o.x 。因此这就不是静态(词法)做用域了。

var x = 0;
void function (code) {
    eval(code);
    console.log(x)
}('var x=1')

不过注意eval在strict模式下被限制了,再也不能造成动态做用域了。

为何闭包函数能够访问外部函数的变量?

由于闭包函数的做用域链包含了外部函数的做用域。

如何建立闭包?

在一个函数类内建立另一个函数。内部函数使用了外部函数的变量,就造成了闭包。

普通函数的内部函数是闭包函数么?

zyx456:不是。


函数第一次被调用时,会发生什么?

当函数第一次被调用时,会建立一个执行环境(execution context)和相应的做用域链,并把做用域链赋值给一个内部属性(即[[Scope]])。

而后,使用this、arguments和其余参数来初始化函数的活动对象(activation object)。

在做用域链中,内部函数的活动对象处于第一位,外部函数的活动对象始终处于第二位,外部函数的外部函数的活动对象处于第三位,……直至做为做用域链终点的全局执行环境。

在函数执行过程当中,读取和写入变量的值,都须要在做用域链中查找变量。

每次调用JS函数,会为之建立一个新的对象来保存全部的局部变量(函数定义的变量,函数参数。),把这个对象添加到做用域链中。函数体内部的变量都保存在函数做用域内。

咱们将做用域链看作一个对象列表,而不是一个栈。

(zyx456:栈是一种线性表,仅容许在表的一端进行插入和删除操做。)

当函数返回的时候,就从做用域链中将这个绑定变量的对象删除。

若是这个函数不存在嵌套的函数,也没有其余引用指向这个绑定变量的对象,它就会被当作垃圾回收掉。

(zyx456:这个操做由浏览器自动完成)。

若是这个函数有嵌套的函数,每一个嵌套的函数都各自对应一个做用域链。

这时:

  • 内部函数,被做为返回值返回,或存储在某处属性中,就是会有一个外部引用指向这个它,那么它就不会被当作垃圾回收,而且它所使用外部变量所在的对象也不会被当作垃圾回收。只有内部函数被销毁后,外部函数的活动对象才会被销毁。

在函数中访问一个变量时,就会从做用域链中查找变量。

通常来说,当函数执行完毕后,局部活动对象就会被销毁,内存中仅保存全局做用域(全局执行环境的环境对象)。

可是,闭包的状况又有所不一样。闭包函数的做用域链上有外部函数的做用域链。因此闭包函数能够访问外部函数的变量。

闭包函数必须返回(return)么,return这个闭包函数?

zyx456:没必要要返回,只要使用外部函数的变量便可。

代码:

function fn1() {
    var a = 1;
    function fn2() {
        console.log(a);
    }
    fn2();
}
fn1();

若是用不一样的变量引用函数中的闭包函数,那么是不一样的闭包变量。

简单的例子:

function outter(){
    var x = 0;
    return function(){
        return x++;
    }
}
var a = outter();
console.log(a());
console.log(a());
var b = outter();
console.log(b());
console.log(b());

运行结果为:
0
1
0
1

闭包的用途:

能够建立私有变量。

由于只有闭包函数能够访问外部函数的变量。

由于在闭包内部保持了对外部活动对象的访问,但外部的变量却没法直接访问内部,避免了全局污染;

function setMoyu(){
    var name = "moyu";
    return function(newValue){
        name=newValue;
        console.log(name);
        
    }
}

var setValue = setMoyu();
setValue("world");//world
/*zyx456:这时name是私有属性了,只能经过闭包函数设置它*/

闭包的缺点?

  1. 可能致使内存占用过多,由于闭包携带了自身的函数做用域。
  2. 闭包只能取得外部函数中的最后一个值。

做用域:

变量声明若是不使用 var 关键字,那么它就是一个全局变量,即使它在函数内定义。

变量生命周期

全局变量的做用域是全局性的,即在整个JS程序中,全局变量到处都在。

而在函数内部声明的变量,只在函数内部起做用。

这些变量是局部变量,做用域是局部性的;

函数的参数也是局部性的,只在函数内部起做用。

在JS中,全部函数都能访问它们上一层的做用域。

例子:

function compare(value1, value2){
    if (value1 < value2){
        return -1;
    } else if (value1 > value2){
        return 1;
    } else {
        return 0;
    }
}

var result = compare(5, 10);

内存泄漏

因为IE 的JS对象和DOM对象使用不一样的垃圾收集方式,所以闭包在IE中会致使内存泄漏的问题,也就是没法销毁驻留在内存中的元素。

事件绑定种的匿名函数也是闭包函数。

若是闭包的做用域链中保存着一个HTML元素,那么就意味着该元素将没法被销毁。

function box() {
    var oDiv = document.getElementById('oDiv'); //oDiv 用完以后一直驻留在内存
    oDiv.onclick = function () {
        alert(oDiv.innerHTML); //这里用oDiv 致使内存泄漏
    };
}
box();

那么在最后应该将oDiv 解除引用来避免内存泄漏。

function box() {
    var oDiv = document.getElementById('oDiv');
    var text = oDiv.innerHTML;
    oDiv.onclick = function () {
        alert(text);
    };
    oDiv = null; //解除引用
}

PS:若是并无使用解除引用,那么须要等到浏览器关闭才得以释放。

闭包和this和arguments

闭包函数中的this问题

对于某个函数来讲,若是函数在全局环境中,this指向window。若是在对象中,就指向这个对象。

而对象中的闭包函数,this指向window。由于闭包并不属于这个对象的属性或方法。

var user = 'The Window';
var obj = {
    user : 'The Object',
    getUserFunction : function () {
        return function () { //闭包不属于obj,里面的this 指向window
            return this.user;
        };
    }
};
alert(obj.getUserFunction()()); //The window
//能够强制指向某个对象
alert(obj.getUserFunction().call(obj)); //The Object
//也能够从上一个做用域中获得对象
getUserFunction : function () {
    var that = this; //从对象的方法里得对象
    return function () {
        return that.user;
    };
}

例子:

var self = this; // 将this保存至一个变量中,以便嵌套的函数可以访问它

绑定arguments的问题与之相似。

arguments并非一个关键字,但在调用每一个函数时都会自动声明它,因为闭包具备本身所绑定的arguments,所以闭包内没法直接访问外部函数的参数数组,除非外部函数将参数数组保存到另一个变量中:

var outerArguments = arguments;  //保存起来以便嵌套的函数能使用它

在经过call()或apply()改变函数执行环境的状况下,this就会指向其余对象。

例子:

var scope = "global scope";             // 全局变量
function checkscope() {
        var scope = "local scope";      // 局部变量
        function f() { return scope; }  // 在做用域中返回这个值
        return f();
}
checkscope()                            // => "local scope"

checkscope()函数声明了一个局部变量,并定义了一个函数f(),函数f()返回了这个变量的值,最后将函数f()的执行结果返回。

你应当很是清楚为何调用checkscope()会返回"local scope"。如今咱们对这段代码作一点改动。

var scope = "global scope";             // 全局变量
function checkscope() {
        var scope = "local scope";      // 局部变量
        function f() { return scope; }  // 在做用域中返回这个值
        return f;
}
checkscope()()                          // 返回值是什么?//local scope
相关文章
相关标签/搜索