JavaScript闭包

网络算
• 阅读 1769
本文将带你用正确姿势看待JavaScript闭包。
在 JavaScript 中闭包描述的是 function 中 外层作用域的变量 被内层作用域 引用的场景,闭包的结构为 内层作用域 保存了 外层作用域的变量。

要理解闭包,首先要知道 JS词法作用域 是如何工作的。

JS词法作用域(lexical scoping)

来看这段代码:

let name = 'John';

function greeting() { 
    let message = 'Hi';
    console.log(message + ' '+ name);
}

变量 name 是全局变量。它可以在任何地方调用,包括在 greeting 函数内部。

变量 message 是局部变量,只能在 greeting 函数内部调用。

如果你尝试从 greeting() 外部访问 message 变量,会抛出一个错误:

ReferenceError: message is not defined

比较有意思的是 函数内部的作用域是可以嵌套的,如下:

function greeting() {
    let message = 'Hi';

    function sayHi() {
        console.log(message);
    }

    sayHi();
}

greeting();
// Hi

greeting() 函数 创建了一个局部变量 message 和一个局部函数 sayHi()。

sayHi() 是 greeting() 的一个内部方法,只能在 greeting() 内部访问。sayHi() 可以访问 greeting() 的 message 变量。在 greeting() 内部调用了 sayHi(),打印出了变量 message 的值。

JavaScript闭包(closures)

来修改一下greeting:

function greeting() {
    let message = 'Hi';

    function sayHi() {
        console.log(message);
    }

    return sayHi;
}
let hi = greeting();
hi(); // 仍然可以获取到message的值

这次我们不是在 greeting() 执行 sayHi(),而是在 greeting() 被调用时把 sayHi 作为结果返回。

在 greeting() 函数外部,声明了一个变量 hi,它是 sayHi() 函数的索引。

这时,我们通过这个索引来执行 sayHi() 函数,可以得到和之前一样的结果。

通常情况下,一个局部变量只会在函数执行的时候存在,函数执行完成,会被垃圾回收机制回收。

有意思的是,上边的这种写法当我们执行 hi(),message 变量是会一直存在的。这就是闭包的作用,换句话说上面的这种形式就是闭包。

其他示例

下面的例子阐述了闭包更加实用的情况:

function greeting(message) {
   return function(name){
        return message + ' ' + name;
   }
}
let sayHi = greeting('Hi');
let sayHello = greeting('Hello');

console.log(sayHi('John')); // Hi John
console.log(sayHello('John')); // Hello John

greeting() 接收一个参数(message),返回了一个函数接收 一个参数(name)。

greeting 返回的匿名函数 把 message 和 name 做了拼接。

这时 greeting() 表现的行为像 工厂模式。使用它创建了 sayHi() 和 sayHello() 函数,它们都维护了各自的 message ”Hi“ 和 ”Hello“。

sayHi() 和 sayHello() 都是闭包。它们共用了同一个函数体,但是保存了不同的作用域。

防抖和节流

在面试的时候,经常会有面试官让你手写一个防抖,节流函数,其实用到的就是闭包。

如果有兴趣可以 查看一下这篇文章 《防抖和节流实例讲解》

好处和问题

闭包的优势

闭包可以在自己的作用域保存变量的状态,不会污染全局变量。因为如果有很多开发者开发同一个项目,可能会导致全局变量的冲突。

闭包可能导致的问题

闭包的优势可能会成为严重的问题,因为闭包中的变量无法被GC回收,尤其是在循环中使用闭包:

function outer() {
  const potentiallyHugeArray = [];

  return function inner() {
    potentiallyHugeArray.push('Hello'); // function inner is closed over the potentiallyHugeArray variable
    console.log('Hello');
  };
};
const sayHello = outer(); // contains definition of the function inner

function repeat(fn, num) {
  for (let i = 0; i < num; i++){
    fn();
  }
}
repeat(sayHello, 10); // each sayHello call pushes another 'Hello' to the potentiallyHugeArray 
 
// now imagine repeat(sayHello, 100000)

在这个例子中,potentiallyHugeArray 会随着循环的次数增加而无限增大而导致内存泄漏(Memory Leaks)。

总结

闭包既有优势,也会导致问题。只有理解了它的原理,才能让它发挥正确的作用。

文章首发于 IICOOM-个人博客 《JavaScript闭包》

点赞
收藏
评论区
推荐文章
Dax Dax
4年前
JS核心原理理解闭包
前置概念在正式看闭包之前,我们先来学习一下前置知识,那就是JS中的作用域,我们知道,在ES5之中,作用域分为两种:全局作用域和函数作用域,随着ES6的到来,新增了块级作用域,想更好的理解闭包,那么搞清楚作用域是首要条件全局作用域我们知道,对于变量而言,我们一般会分成两类:全局变量和局部变量,一般定义在最外围环境的为全局变量,定义在函数当中的为局部变量,在we
Karen110 Karen110
3年前
一篇文章带你了解JavaScript作用域
在JavaScript中,对象和函数也是变量。在JavaScript中,作用域是你可以访问的变量、对象和函数的集合。JavaScript有函数作用域:这个作用域在函数内变化。一、本地JavaScript变量一个变量声明在JavaScript函数内部,成为函数的局部变量。局部变量有局部作用域:它们只能在函数中访问。JS://codeherecann
Jacquelyn38 Jacquelyn38
4年前
你不可不知的JS面试题(第三期)
1、什么是闭包?如图所示,闭包就是一个定义在函数内部的函数,其作用是将函数内部和函数外部连接起来。大家知道,作用域的问题,就是在函数内部定义的变量称为局部变量,外部取不到值。下面我们通过代码来更加详细地看一下:function A()       let x  1;       return function B()           c
菜园前端 菜园前端
2年前
一篇文章教会你什么是闭包
原文链接:什么是闭包?闭包的概念并不复杂,但是它的定义比较绕(就像平时经常用到它,却又说不出来是什么)。可以在一个作用域中调用函数的内部函数并访问到该函数中的作用域的成员,这就是闭包。给一个建议,网上闭包的概念可以搜出来一大堆,但是你真的了解它吗?你有去调
菜园前端 菜园前端
2年前
为你解惑JS作用域和作用域链知识
原文链接:变量作用域一个变量的作用域(scope)是程序源代码中定义这个变量的区域。全局变量拥有全局作用域,在JavaScript代码中的任何地方都是可以访问的。然而在函数内声明的变量只能在函数体内访问,它们是局部变量,作用域是局部性的。函数参数也是局部变
Bill78 Bill78
4年前
Python 中的闭包
闭包定义:如果在一个内部函数里,对在外部作用于(但不是在全局作用域)的变量进行引用,那么内部函数就被认为是闭包Python中的闭包原文出处:田小计划(http://www.cnblogs.com/wilber2013/p/4658894.html)闭包(closure)是函数式编程的重要的语法结构
Wesley13 Wesley13
3年前
JS 闭包(内存溢出与内存泄漏)(垃圾回收机制)
1.有关闭包定义闭包是指有权访问另一个函数作用域中变量的函数,创建闭包的最常见的方式就是在一个函数内创建另一个函数,通过另一个函数访问这个函数的局部变量闭包的特性:函数内再嵌套函数内部函数可以引用外层的参数和变量参数和变量不会被垃圾回收机制回收
Stella981 Stella981
3年前
JavaScript易错知识点整理
前言本文是我学习JavaScript过程中收集与整理的一些易错知识点,将分别从变量作用域,类型比较,this指向,函数参数,闭包问题及对象拷贝与赋值这6个方面进行由浅入深的介绍和讲解,其中也涉及了一些ES6的知识点。JavaScript知识点1.变量作用域vara1;functio
Easter79 Easter79
3年前
Swift讲解专题八——闭包
Swift讲解专题八——闭包一、引言      Swift中的闭包是有一定功能的代码块,这十分类似于ObjectiveC中的block语法。Swift中的闭包语法风格十分简洁,其作用和函数的作用相似。二、从一个系统函数看闭包      Swift标准函数库中提供了一个sort排序函数,对
Stella981 Stella981
3年前
JavaScript函数——闭包
闭包概念只有函数内部的子函数才能读取局部变量,所以闭包可以理解成“定义在一个函数内部的函数“。在本质上,闭包是将函数内部和函数外部连接起来的桥梁例子functionouter(){varlocalVal30;returnlocalVal;}
Easter79 Easter79
3年前
Swift3.0 闭包(blcok)的全面介绍及使用
闭包是自包含的函数代码块,可以在代码中被传递和使用。Swift中的闭包与C和ObjectiveC中的代码块(blocks)以及其他一些编程语言中的匿名函数比较相似。闭包可以捕获和存储其所在上下文中任意常量和变量的引用。被称为包裹常量和变量。Swift会为你管理在捕获过程中涉及到的所有内存操作。闭包表达式语法有如下的一般形式:
网络算
网络算
Lv1
不知何岁月,得与尔同归?
文章
4
粉丝
0
获赞
0