详解JavaScript中的作用域链与闭包
作者:大眼睛图图 发布时间:2024-04-22 13:07:00
作用域链
首先来看看这段代码:
var a = '喜羊羊';
function A(){
console.log(a);
a = '美羊羊';
function B(){
console.log(a);
}
B();
}
A();
在这里毫无疑问结果肯定是我们想到的先打印喜羊羊,再打印美羊羊。因为作用域链嘛,如果当前层没找到,那么就去当前层的上一级找。
那么再看这道
function bar() {
console.log(myName)
}
function foo() {
var myName = "极客邦"
bar()
}
var myName = "极客时间"
foo()
是不是感觉是打印极客邦?如果是的话,那么恭喜你,掉坑里了。(还不赶快爬起来,补一补作用域链的知识)。
为什么打印不是极客邦而是极客时间呢?
既然问题出现在了对作用域链的理解上,那么就再回到作用域链的定义上吧。
其实在每个执行上下文的变量环境中,都包含了一个外部引用,用来指向外部的执行上下文,我们把这个外部引用称为 outer。
比如上面那段代码在查找 myName
变量时,如果在当前的变量环境中没有查找到,那么 JavaScript 引擎会继续在 outer 所指向的执行上下文中查找
为了直观理解,你可以看下面这张图:
看到这张图我猜你又纳闷了,为什么bar
函数创建的执行上下文中的outer会指向全局??
哈哈哈,这里就要涉及到了词法作用域了
词法作用域
词法作用域就是指作用域是由代码中函数声明的位置来决定的,所以词法作用域是静态的作用域,通过它就能够预测代码在执行过程中如何查找标识符。
这么讲可能不太好理解,你可以看下面这张图:
从图中可以看出,词法作用域就是根据代码的位置来决定的,其中 main
函数包含了 bar
函数,bar
函数中包含了 foo
函数,因为 JavaScript 作用域链是由词法作用域决定的,所以整个词法作用域链的顺序是:foo 函数作用域—>bar 函数作用域—>main 函数作用域—> 全局作用域。
明白了词法作用域,那么我们再回到刚刚的问题。
为什么bar函数创建的执行上下文中的outer会指向全局
这是因为根据词法作用域,而词法作用域又是根据代码的位置,而bar函数代码的位置就是包裹在全局下,而喜羊羊那个例子中的B函数是在A函数的环境下,所以会造成它们的词法作用域链不同,也就导致函数作用域链不同了。
所以我们才有那句话词法作用域是代码编译阶段就决定好的,和函数是怎么调用的没有关系。
也就是只和代码位置有关,和函数直接如何调用没关系
闭包
老生常谈的问题,这次再从一个更深入的角度来理解一下。
看下面这段代码:
function foo() {
var myName = "极客时间"
let test1 = 1
const test2 = 2
var innerBar = {
getName:function(){
console.log(test1)
return myName
},
setName:function(newName){
myName = newName
}
}
return innerBar
}
var bar = foo()
bar.setName("极客邦")
bar.getName()
console.log(bar.getName())
这段代码乍一看没有什么问题,但是这里有一个细节很多人会忽视。
在foo()执行完将返回值给bar
时,这里foo函数会从调用栈中弹出,变量都会被回收。既然变量都被回收了,那么bar.setName()
这些调用方法从何而来??
foo执行完后的情况可以参考下图:
从上图可以看出,foo
函数执行完成之后,其执行上下文从栈顶弹出了,但是由于返回的 setName
和 getName
方法中使用了 foo 函数内部的变量 myName
和 test1
,所以这两个变量依然保存在内存中。这像极了 setName
和 getName
方法背的一个专属背包,无论在哪里调用了 setName
和 getName
方法,它们都会背着这个foo
函数的专属背包。
之所以是专属背包,是因为除了 setName
和 getName
函数之外,其他任何地方都是无法访问该背包的,我们就可以把这个背包称为 foo 函数的闭包。
来源:https://juejin.cn/post/7163893183804342308
猜你喜欢
- 什么是浅克隆、深克隆浅克隆:直接将存储在栈中的值赋值给对应变量,如果是基本数据类型,则直接赋值对应的值,如果是引用类型,则赋值的是地址。深克
- 常有人因为页面的面积问题,想在一个窄小的地方,显示一条条的信息,顺序往上滚动,在经典的BBS里,有一个随机上滚动的JS,好些人用不了,现在蛋
- function AutomateExcel() { try { //Start Excel and get Application obj
- 成员运算符Python 提供了两个成员运算符来检查或验证值的成员资格。它测试序列中的成员资格,例如字符串、列表或元组。 in 运算
- 1、get方式:如何为爬虫添加ip代理,设置Request header(请求头)import urllib import urllib.r
- 先前我们讲的都是“线性结构”,他的特征就是“一个节点最多有一个”前驱“和一个”后继“。那么我们今天讲的树会是怎样的呢?我们可以对”线性结构“
- 本文实例讲述了Python设计模式之命令模式原理与用法。分享给大家供大家参考,具体如下:命令模式(Command Pattern):将请求封
- 这个周忙的就像打仗一样,感觉有点被别人牵着鼻子走了,每天都是早出晚归,干不完的活儿,有时候感觉DBA这碗饭真的不好
- 有的时候需要对python程序内存占用进行监控,这个时候可以用到psutil库,Anaconda中是自带的,如果import出错,可以用pi
- 目录一、IDEA如何连接数据库第一种方法:直接在方法体中增加连接信息方法二:二、方法代码的实现1.快递员增加快递2.快递员删除快递用数据库编
- 阅读上一篇:网马解密大讲堂——网马解密中级篇(Eval篇) 一.Document.write 函数简介:在Microsoft JScript
- 协同开发时本地测试昨天的文章中提到了Go如何优雅的进行本地测试,今天分享一下:在多人协同开发中,如果大家都进行本地测试可能会出现的问题。最大
- 官网: https://matplotlib.org一、版本# 01 matplotlib安装情况 import matplotlib ma
- 本文实例讲述了Python基于回溯法解决01背包问题。分享给大家供大家参考,具体如下:同样的01背包问题,前面采用动态规划的方法,现在用回溯
- Pycharm Python Console用法Pycharm的下方工具栏中有两个窗口:Python Console和Terminal(如下
- 今天开始学习python,首先环境安装1.在https://www.python.org/downloads/下载python2.X或者3.
- 1、基本原理访问网站扫码登录页,网站给浏览器返回一个二维码和一个唯一标志KEY浏览器开启定时轮询服务器,确认KEY对应的扫码结果用户使用ap
- 本文实例讲述了PHP数据库表操作的封装类及用法。分享给大家供大家参考,具体如下:数据库表结构:CREATE TABLE `test_user
- 一、Celery介绍和基本使用 Celery 是一个 基于python开发的分布式异步消息任务队列,通过它可以轻松的实现任务的异步处理, 如
- 本文实例讲述了Python排序搜索基本算法之希尔排序。分享给大家供大家参考,具体如下:希尔排序是插入排序的扩展,通过允许非相邻的元素进行交换