资讯专栏INFORMATION COLUMN

【JS脚丫系列】重温闭包

MartinDai / 1455人阅读

摘要:内部的称为内部函数或闭包函数。过度使用闭包会导致性能下降。,闭包函数分为定义时,和运行时。循环会先运行完毕,此时,闭包函数并没有运行。闭包只能取得外部函数中的最后一个值。事件绑定种的匿名函数也是闭包函数。而对象中的闭包函数,指向。

闭包概念解释:

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

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

闭包(closure)是可以访问外部函数作用域中的变量或参数的函数。

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

闭包wiki

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

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

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

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是私有属性了,只能通过闭包函数设置它*/
闭包的缺点?

可能导致内存占用过多,因为闭包携带了自身的函数作用域。

闭包只能取得外部函数中的最后一个值。

作用域:

变量声明如果不使用 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

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/106028.html

相关文章

  • JS脚丫系列重温bind

    摘要:和构造函数构造函数可以使用,然后再次创建实例。提供的值被忽略,提供的那些参数仍然会被前置到构造函数调用的前面。在这种情况下,指向全局作用域。现在将作为的方法来调用,传入这些实参用于构造函数。 概念 bind() 方法会返回一个新函数(称为绑定函数),绑定函数与原函数(使用bind()的函数)具有相同的函数体,但是绑定函数有新的this值和参数。 说白了,bind()就是创建一个有着新t...

    MASAILA 评论0 收藏0
  • 重温基础】19.闭包

    摘要:系列目录复习资料资料整理个人整理重温基础篇重温基础对象介绍重温基础对象介绍重温基础介绍重温基础相等性判断本章节复习的是中的关于闭包,这个小哥哥呀,看看。这里随着闭包函数的结束,执行环境销毁,变量回收。 本文是 重温基础 系列文章的第十九篇。今日感受:将混乱的事情找出之间的联系,也是种能力。 系列目录: 【复习资料】ES6/ES7/ES8/ES9资料整理(个人整理) 【重温基础】...

    nanfeiyan 评论0 收藏0
  • 重温基础】4.函数

    摘要:本文是重温基础系列文章的第四篇。系列目录复习资料资料整理个人整理重温基础语法和数据类型重温基础流程控制和错误处理重温基础循环和迭代本章节复习的是中的基础组件之一,函数,用来复用特定执行逻辑。箭头函数不能使用命令,即不能用作函数。 本文是 重温基础 系列文章的第四篇。今日感受:常怀感恩之心,对人对己。 系列目录: 【复习资料】ES6/ES7/ES8/ES9资料整理(个人整理) 【重温基...

    maxmin 评论0 收藏0
  • 重温基础】22.内存管理

    摘要:内存泄露内存泄露概念在计算机科学中,内存泄漏指由于疏忽或错误造成程序未能释放已经不再使用的内存。判断内存泄漏,以字段为准。 本文是 重温基础 系列文章的第二十二篇。 今日感受:优化学习方法。 系列目录: 【复习资料】ES6/ES7/ES8/ES9资料整理(个人整理) 【重温基础】1-14篇 【重温基础】15.JS对象介绍 【重温基础】16.JSON对象介绍 【重温基础】1...

    Pandaaa 评论0 收藏0
  • 【ES6脚丫系列】箭头函数

    摘要:箭头函数本文字符数,阅读时间约分钟左右。箭头函数等于说,只保留了函数的参数和返回。箭头函数体内的,继承的是外层代码块的。所以,不用用箭头函数声明对象的方法。不可以使用命令因此箭头函数不能用作函数。 【01】ES6箭头函数 本文字符数4300+,阅读时间约8分钟左右。 【01】箭头函数 等于说,只保留了函数的参数和返回。省略function和return。 写法: (形参) => {st...

    tinyq 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<