深入理解javascript闭包
闭包在javascript中是一个非常重要但又难以掌握的概念。已经学习和使用javascript一年半之久,还是完全不理解闭包是什么。今天开始认真理一下。
闭包使得函数可以继续访问定义时的词法作用域。
在学习js闭包之前,首先需要了解一个概念,词法作用域
。
一、 词法作用域
作用域分为词法作用域(大多数编程语言所采用的)和动态作用域(Bash脚本,Perl)。
1. 词法阶段
大部分标准语言编译器的第一个工作阶段,叫做词法化(也加单词化)。词法化的过程会对源代码的字符进行检查,如果是有状态的解析过程,还会赋予单词语义。
2. 词法作用域气泡
简单的说,词法作用域就是定义在词法阶段的作用域。换句话说,词法作用域是由你在写代码时将变量和快作用域写在哪里决定的,因此词法分析器在处理代码时会保持作用域不变。
二、作用域闭包
在javascript中,闭包无处不在。闭包是基于词法作用域书写代码时所产生的自然结果。
闭包的使用和创建在代码中随处可见,我们缺少的是一双识别闭包的眼睛和一颗想要拥抱闭包的心。你可能会蓦然发现,你的代码中到处都是闭包。
当函数记住并访问所在的词法作用域时,就产生了闭包。即使函数是在当前词法作用域之外执行。
下面这段代码清晰的展示了闭包:
示例1:将内部函数作为结果return出来
function foo() {
let a = 2;
function bar() {
console.log(a);
}
return bar;
}
let baz = foo();
baz(); // 2 --->闭包产生的效果
在上述代码中我们把foo()
这个函数的执行结果,也就是bar
这个函数直接赋值给baz
, 然后调用baz()
,实际上只是通过不同的标识符引用调用了内部的函数bar()
。在这个例子中,bar()
可以在自己定义的词法作用域以外的地方执行。
正常情况下,执行完foo(),通常我们认为foo()的整个内部作用域都会被销毁。因为引擎有垃圾回收器,会自动释放不再使用的内存空间。虽然看上去foo()的内容不会再被使用。
但是,在这里,由于闭包,事实上内部作用域依然存在。因为bar()本身还在使用这个内部作用域。
因为bar()声明位置的原因,它拥有涵盖foo()内部作用域的闭包,使得该作用域能够一直存活,以供bar()以后在之后的任何时间都可以使用。
示例2:直接将函数传递到所在的词法作用域外
function foo() {
var a = 2;
function baz() {
console.log( a ); // 2
}
bar( baz );
}
function bar(fn) {
fn(); //这就是闭包!在这里baz()已经被传递到了定义时所在的词法作用域以外。
}
示例3:将函数赋值给全局变量,将内部函数传递到所在的词法作用域外。
var fn;
function foo() {
var a = 2;
function baz() {
console.log( a );
}
fn = baz; // 将baz 分配给全局变量
}
function bar() {
fn(); //这就是闭包!
}
foo();
bar(); // 2
综上,无论使用何种方法,只要将内部函数传递到所在的词法作用域外,它都会保持对原始定义作用域的引用,无论在何处执行都会使用这个闭包。
四、循环和闭包
for (var i=1; i<=5; i++) {
setTimeout( function timer() {
console.log( i );
}, 1000 );
}
上述代码每隔1s输出一个6,一共输出5个6。
我们试图假设循环中的每个迭代在运行时都会给自己“捕获”一个i 的副本。但是 根据作用域的工作原理,实际情况是尽管循环中的五个函数是在各个迭代中分别定义的, 但是它们都被封闭在一个共享的全局作用域中,因此实际上只有一个i。
五、模块和闭包
六、无处不在的闭包
1.for 循环
function wait(message) {
setTimeout( function timer() {
console.log( message );
}, 1000 );
}
wait( "Hello, closure!" );
2.jQuery
function setupBot(name, selector) {
$( selector ).click( function activator() {
console.log( "Activating: " + name ); //闭包
} );
}
setupBot( "Closure Bot 1", "#bot_1" );
setupBot( "Closure Bot 2", "#bot_2" );
本质上,无论何时何地,如果将函数(访问各自的词法作用域)当成一个第一级的值类型,到处传递,那就会有闭包在这些函数中的应用。因为作为值类型时,它拥有和普通变量一样的作用域。
事实上,只要使用了定时器
、事件监听器
、Ajax请求
、跨窗口通信
、Web Worker
或者其他任何的异步(或者同步)任务中,只要使用了回调函数
,实际上就是在使用闭包。
参考:《你不知道的JavaScript(上卷)》
- 人工智能将取代人类?危机亦或是新的机遇
- 大数据驱动的未来网络:体系架构与应用场景(下)网络架构与场景详解
- 冷静点,NVIDIA 禁止 Geforce 进数据中心想限制的并不是深度学习
- 智能机器人崛起背后的中国力量
- 企业微服务架构转型-实施步骤
- Andrew Ng机器学习课程笔记--week2(多元线性回归&正规公式)
- 科技第六感:黑客控制你的车!不信?其实很简单
- python多版本的pip共存问题解决办法
- C++ 对vector进行排序
- 小程序优化36计
- 神经网络权重初始化问题
- Andrew Ng机器学习课程笔记--week11(图像识别&总结划重点)
- 市民近期到南沙有望体验到无人驾驶技术
- Andrew Ng机器学习课程笔记--week10(优化梯度下降)
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- Oracle私网mtu滚动修改实施方案
- 如何使用GOLDENGATE构建数据库的审计表之一
- [java][Servlet]Servlet 简介-Servlet 到 Spring MVC 的简化之路-Servlet/Tomcat/ Spring 之间的关系
- oracle19c安装脚本.
- GOLDENGATE EXTRACT在DATABASE SWITCHOVER后表现以及处理方案
- JAVA中的23种设计模式(GOF)
- 机器学习第3天:预测汽车的燃油效率
- Oracle GoldenGate 19 Microservices安装、配置以及简单数据同步
- 最小覆盖子串
- 通配符匹配
- LaTeX多行注释
- [886]mysql查询以某个字符开头
- 不同路径问题
- 信号量及其应用
- 岛屿问题