js 【详解】闭包

简介: js 【详解】闭包

闭包的概念

如果一个函数访问了此函数的父级及父级以上的作用域变量,那么这个函数就是一个闭包。

所以以下写法都是闭包

    var a = 1;
  // 匿名的立即执行函数,因访问了全局变量a,所以也是一个闭包
    (function test (){
    alert(a);
  })()

本质上,JS中的每个函数都是一个闭包,因为每个函数都可以访问全局变量。

关于JS中的作用域,可以参考博客:

https://blog.csdn.net/weixin_41192489/article/details/124277123

实现闭包最常见的方式就是函数嵌套(并不是形成闭包的唯一方式!)

function a() {
    var i = '初始值';
    i = i + "—_执行a"
    // 此处的函数b访问了父级函数a中的局部变量i,成为了一个闭包
    function b() {
        i = i + "_执行b"
        console.log(i)
    }
    return b;
}
var c = a(); // 此时 i 的值为 :初始值—_执行a
c()          // 此时 i 的值为 :初始值—_执行a_执行b
c()          // 此时 i 的值为 :初始值—_执行a_执行b_执行b

闭包的执行过程

以上方代码为例:

  1. 将函数a赋值给全局变量c时,a会执行一次,局部变量 i 的值变为初始值—_执行a,最终返回函数b,此时全局变量c的值为闭包函数b的引用。

此时函数a虽然已执行完,但因为内部包含闭包函数b,所以函数 a 的执行期上下文会继续保留在内存中,不会被销毁,所以局部变量 i 仍是初始值—_执行a


执行期上下文:当函数执行时,会创建一个执行期上下文的内部对象。每调用一次函数,就会创建一个新的上下文对象,他们之间是相互独立的。当函数执行完毕,它所产生的执行期上下文会被销毁

  1. 第一次执行 c() 时,闭包函数b第一次执行,局部变量 i 的值变为初始值—_执行a_执行b
  2. 第二次执行 c() 时,闭包函数b第二次执行,局部变量 i 的值变为初始值—_执行a_执行b_执行b

闭包的图解

var a = "global variable";
var F = function () {
  var b = "local variable";
  var N = function () {
    var c = "inner local";
    return b;
  };
 return N;
};
var d = F()
d()

  • 全局作用域 G 中有:
    —— 函数 F
    —— 全局变量 a
    —— 全局变量 d (存有对闭包函数 N 的引用)
  • 函数 F 中有: (返回闭包函数N)
    —— 函数 F 作用域中的局部变量 b
    —— 闭包函数 N
  • 闭包函数 N 中有: (返回局部变量b)
    —— 函数 N 作用域中的局部变量 c

闭包的特点

1.被闭包函数访问的父级及以上的函数的局部变量(如范例中的局部变量 i )会一直存在于内存中,不会被JS的垃圾回收机制回收。

2.闭包函数实现了对其他函数内部变量的访问。(函数内部的变量对外是无法访问的,闭包通过这种变通的方法,实现了访问。)

Javascript的垃圾回收机制

  • 如果一个对象不再被引用,那么这个对象就会被GC回收。
  • 如果两个对象互相引用,而不再被第三者所引用,那么这两个对象都会被回收。

闭包的用途

  1. 访问函数内部的变量
  2. 让变量始终保持在内存中

闭包的应用场景

模拟面向对象的代码风格

比如模拟两人对话

function person(name) {
    function say(content) {
        console.log(name + ':' + content)
    }
    return say
}

a = person("张三")
b = person("李四")
a("在干啥?")
b("没干啥。")
a("出去玩吗?")
b("去哪啊?")

控制台打印结果为:

张三:在干啥?
李四:没干啥。
张三:出去玩吗?
李四:去哪啊?

使setTimeout支持传参

通过闭包实现setTimeout第一个函数传参(默认不支持传参)

function func(param){
    return function(){
        alert(param)
    }
}
var f1 = func(1);
setTimeout(f1,1000);

封装私有变量

//用闭包定义能访问私有函数和私有变量的公有函数。
var counter = (function () {
    var privateCounter = 0; //私有变量
    function change(val) {
        privateCounter += val;
    }
    return {
        increment: function () {
            change(1);
        },
        decrement: function () {
            change(-1);
        },
        value: function () {
            return privateCounter;
        }
    };
})();

console.log(counter.value());//0
counter.increment();
console.log(counter.value());//1
counter.increment();
console.log(counter.value());//2

模拟块作用域

依次点击 4 个 li 标签,结果都弹出 4

解析:onclick绑定的function中没有变量 i,解析引擎会寻找父级作用域,最终找到了全局变量 i,for循环结束时,i 的值已变成了4,所以onclick事件执行时,全都弹出 4。

下面使用闭包来解决这个问题:

var elements = document.getElementsByTagName('li');
var length = elements.length;
for (var i = 0; i < length; i++) {
    elements[i].onclick = function (num) {
        return function () {
            alert(num);
        };
    }(i);
}

通过匿名闭包,把每次的 i 都保存到一个变量中,实现了预期效果。

当然,通过 ES6 的 let 可以轻松解决这个问题:

var elements = document.getElementsByTagName('li');
var length = elements.length;
for (let i = 0; i < length; i++) {
    elements[i].onclick = function () {
        alert(i);
    };
}

实现迭代器

function setup(x) {
  var i = 0;
  return function(){
    return x[i++];
  };
}
var next = setup(['a', 'b', 'c']);

控制台中的执行效果:

> next();
"a"
> next();
"b"
> next();
"c"

闭包的优点

  1. 可以减少全局变量的定义,避免全局变量的污染
  2. 能够读取函数内部的变量
  3. 在内存中维护一个变量,可以用做缓存

闭包的缺点

1)造成内存泄露

闭包会使函数中的变量一直保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。【内存泄露:无用的变量一直在内存中,无法被释放】

解决方法——使用完变量后,手动将它赋值为null;

2)闭包可能在父函数外部,改变父函数内部变量的值。

3)造成性能损失

由于闭包涉及跨作用域的访问,所以会导致性能损失。

解决方法——通过把跨作用域变量存储在局部变量中,然后直接访问局部变量,来减轻对执行速度的影响

闭包的范例

返回匿名闭包

function funA(){
  var a = 10;  // funA的活动对象之中;
  return function(){   //匿名函数的活动对象;
        alert(a);
  }
}
var b = funA();
b();  //10

各自独立的闭包

function outerFn(){
  var i = 0; 
  function innerFn(){
      i++;
      console.log(i);
  }
  return innerFn;
}
var inner = outerFn();  //每次外部函数执行的时候,都会开辟一块内存空间,外部函数的地址不同,都会重新创建一个新的地址
inner();
inner();
inner();
var inner2 = outerFn();
inner2();
inner2();
inner2();   //1 2 3 1 2 3
function fn(){
  var a = 3;
  return function(){
    return  ++a;                                     
  }
}
alert(fn()());  //4
alert(fn()());  //4    

访问全局变量的闭包

var i = 0;
function outerFn(){
  function innnerFn(){
       i++;
       console.log(i);
  }
  return innnerFn;
}
var inner1 = outerFn();
var inner2 = outerFn();
inner1();
inner2();
inner1();
inner2();     //1 2 3 4

写法有点绕的闭包

(function() { 
  var m = 0; 
  function getM() { return m; } 
  function seta(val) { m = val; } 
  window.g = getM; 
  window.f = seta; 
})(); 
f(100);
console.info(g());   //100  闭包找到的是同一地址中父级函数中对应变量最终的值

闭包的链式调用

var add = function (x) {
    var sum = 1;
    var tmp = function (x) {
        console.log('执行tmp')
        sum = sum + x;
        return tmp;
    }
    tmp.toString = function () {
        return sum;
    }
    return tmp;
}
console.log(add(1)(2)(3).toString())

控制台打印结果:

执行tmp
执行tmp
6

add(1) 时执行的是最外面的匿名函数,从(2) 开始,才执行tmp

所以第一个参数无论是几,最终结果都是6

console.log(add(8)(2)(3).toString()) // 最终结果还是 6

留意父函数执行过一次!

function love1(){
     var num = 223;
     var me1 = function() {
           console.log(num);
     }
     num++;
     return me1;
}
var loveme1 = love1();
loveme1();   //输出224

打印每次链式调用的上一次传参

function fun(n,o) {
    console.log(o);
    return {
         fun:function(m) {
               return fun(m,n);
         }
    };
}
var a = fun(0);  //undefined
a.fun(1);  //0  
a.fun(2);  //0  
a.fun(3);  //0  
var b = fun(0).fun(1).fun(2).fun(3);   //undefined  0  1  2
var c = fun(0).fun(1);  
c.fun(2);  
c.fun(3);  //undefined  0  1  1

目录
相关文章
|
1月前
|
自然语言处理 JavaScript 前端开发
深入理解JavaScript中的闭包:原理与实战
【10月更文挑战第12天】深入理解JavaScript中的闭包:原理与实战
|
21天前
|
JavaScript 前端开发
js 闭包的优点和缺点
【10月更文挑战第27天】JavaScript闭包是一把双刃剑,在合理使用的情况下,它可以带来很多好处,如实现数据封装、记忆功能和模块化等;但如果不注意其缺点,如内存泄漏、变量共享和性能开销等问题,可能会导致代码出现难以调试的错误和性能问题。因此,在使用闭包时,需要谨慎权衡其优缺点,根据具体的应用场景合理地运用闭包。
106 58
|
21天前
|
缓存 JavaScript 前端开发
js 闭包
【10月更文挑战第27天】JavaScript闭包是一种强大的特性,它可以用于实现数据隐藏、记忆和缓存等功能,但在使用时也需要注意内存泄漏和变量共享等问题,以确保代码的质量和性能。
36 7
|
24天前
|
自然语言处理 JavaScript 前端开发
JavaScript闭包:解锁编程潜能,释放你的创造力
【10月更文挑战第25天】本文深入探讨了JavaScript中的闭包,包括其基本概念、创建方法和实践应用。闭包允许函数访问其定义时的作用域链,常用于数据封装、函数柯里化和模块化编程。文章还提供了闭包的最佳实践,帮助读者更好地理解和使用这一强大特性。
15 2
|
1月前
|
设计模式 JavaScript 前端开发
探索JavaScript中的闭包:从基础概念到实际应用
在本文中,我们将深入探讨JavaScript中的一个重要概念——闭包。闭包是一种强大的编程工具,它允许函数记住并访问其所在作用域的变量,即使该函数在其作用域之外被调用。通过详细解析闭包的定义、创建方法以及实际应用场景,本文旨在帮助读者不仅理解闭包的理论概念,还能在实际开发中灵活运用这一技巧。
|
1月前
|
缓存 JavaScript 前端开发
深入了解JavaScript的闭包:概念与应用
【10月更文挑战第8天】深入了解JavaScript的闭包:概念与应用
|
1月前
|
自然语言处理 JavaScript 前端开发
Javascript中的闭包encloure
【10月更文挑战第1天】闭包是 JavaScript 中一种重要的概念,指函数能够访问其定义时的作用域内的变量,即使该函数在其词法作用域之外执行。闭包由函数及其词法环境组成。作用域链和词法作用域是闭包的核心原理。闭包常用于数据隐藏和封装,如模块模式;在异步操作中也广泛应用,如定时器和事件处理。然而,闭包也可能导致内存泄漏和变量共享问题,需谨慎使用。
|
1月前
|
自然语言处理 JavaScript 前端开发
深入理解JavaScript中的闭包:原理、应用与代码演示
【10月更文挑战第12天】深入理解JavaScript中的闭包:原理、应用与代码演示
|
1月前
|
自然语言处理 JavaScript 前端开发
深入理解JavaScript闭包:原理与应用
【10月更文挑战第11天】深入理解JavaScript闭包:原理与应用
20 0
|
2月前
|
JSON JavaScript 前端开发
JavaScript第五天(函数,this,严格模式,高阶函数,闭包,递归,正则,ES6)高级
JavaScript第五天(函数,this,严格模式,高阶函数,闭包,递归,正则,ES6)高级