文 / 景朝霞
来源公号 / 朝霞的光影笔记
ID / zhaoxiajingjing
图 / 自己画
❥❥❥❥点个赞,让我知道你来过~❥❥❥❥
前情提要:
- 题目 | let和var的区别(一、二)
- 图解 | let和var的区别(一、二)
- 题目 | 带VAR和不带VAR的区别
- 图解 | 带VAR和不带VAR的区别
- 总结 | LET和VAR区别(三、四)
- 图解 | 作用域和作用域链
- 练习题 | 作用域和作用域链
- 图解 | 理解闭包
来~大声朗读3遍 or 写一遍吧!
函数执行会形成一个全新的私有作用域,保护里面的变量不受外界干扰,这种保护机制就称为闭包
var i = 0;
function A(){
var i = 10;
function x(){
console.log(i);
}
return x;
}
var y = A();
y();
function B(){
var i = 20;
y();
i++;
}
B();
△全图在文末~
(1)浏览器会开辟一个栈内存作为全局执行上下文/全局作用域。
(2)在全局执行上下文中有VO(G)
变量对象用于当前作用域下变量存储和值存储。
(3)在代码执行之前,会有变量提升:找var/function。var:只会提前声明变量名;function:提前声明+定义。
如:
var i;
带var的只提前声明
function A(){...}
引用数据类型:
- 浏览器开辟一块16进制地址的堆内存
- 把函数体中的代码以代码字符串格式进行存储
- 并把16进制地址赋值给变量名
A
- 函数在哪创建,在执行时需要查找的上级作用域就是哪里
【PS:在哪里出生,他爹妈就在哪里,它的老家就在哪里~~我滴老家就在这个屯儿~我是这个屯里土生土长的人儿】
var y;
带var的只提前声明
(4)在代码自上而下执行时,对于已经进行变量提升的用删除线表示。
i = 0;
已经声明过变量i
,此处进行等号赋值:把变量i
与值0进行关联。
function A(){…}已经声明+定义了**
y = A();
**已经提前声明了变量y,此处进行赋值
**y = A();
**执行这句代码的意思:
return xxx;
,则把return
后面的东西赋值给变量yreturn
,则函数的返回值为undefined
赋值给变量y(5)执行函数A,会开辟一个全新的栈内存/执行上下文/作用域,并把函数的返回值赋值给变量y
函数执行会形成一个全新的私有作用域,保护里面的变量不受外界干扰,这种保护机制就是闭包
- 当前作用域中this指向的是window
- 作用域链
scopeChain:
- 在函数里面有
AO(A)
活动对象进行:变量存储+值存储- 形参赋值&变量提升&代码执行
- 函数A有返回值
return x;
,即:把函数x的堆内存地址BF0返回去,赋值给变量y
函数A执行完后,该栈内存/执行上下文/作用域EC(A),不销毁:当前作用域内有东西被外界占用了。
此作用域不销毁,里面的变量i和函数x会被保存下来。
(6)函数y执行,即:浏览器开辟一个全新的栈内存/执行上下文/私有作用域EC(y)#1(假设:标记为1号),把BF0堆内存的函数代码字符串拿出来执行
函数执行会形成一个全新的私有作用域,保护里面的变量不受外界干扰,这种保护机制就是闭包
console.log(i);
打印输出变量i的值,该函数没有返回值,没有东西被外界占用,执行完后,被销毁。
在当前作用域EC(y)#1下找变量i,没有找到。会沿着作用域链向上级作用域查找,如果有则使用。如果没有找到,会继续向上级作用域查找…一直找到全局作用域EC(G)的变量对象VO(G),如果找到变量i,则使用;如果在全局作用域都没有找到变量i,那么就会报错了。这种查找机制是作用域链查找。
(7)函数B已经在变量提升阶段:声明+定义。
(8)函数B执行,即:浏览器开辟一个全新的栈内存/执行上下文/私有作用域,把AF1堆内存中的代码字符串拿出来执行
函数执行会形成一个全新的私有作用域,保护里面的变量不受外界干扰,这种保护机制就是闭包
var i = 20;
在此作用域下声明的变量i,不会受到EC(A)作用域和EC(G)全局作用域中变量i的干扰,这里操作变量i都是该作用域下的私有变量。
y();
函数y在当前作用域EC(B)下没有声明,那么找到上级作用域EC(G),这里存储函数y的地址为:BF0。浏览器会开一个全新的栈内存/执行上下文/私有作用域EC(y)#2(假设标记为2号),把BF0堆内存地址的代码字符串拿出来执行:
函数执行会形成一个全新的私有作用域,保护里面的变量不受外界干扰,这种保护机制就是闭包
console.log(i);
通过作用域链查找机制,找到上级作用域的活动对象中AO(A)中有变量i,输出:10
EC(A)这个私有作用域里面有东西被外界占用,没有被销毁,会把其中的变量保存起来
i++;
在当前作用域下能找到变量i,那么,它操作的就是自己的。变量i的值变为21。
在VO(G)全局的变量对象和没有被销毁的AO(A)活动对象中保存的变量i是不会受到它的干扰。
函数在哪里创建,哪里就是它的上级作用域。跟函数在哪执行没有半毛钱关系。
函数创建——形成上级作用域
函数执行——判断this
函数执行时,浏览器会开辟一个全新的栈内存/执行上下文/私有作用域,保护它里面的变量不受外界干扰,这种保护机制就是闭包。
遇到变量,如果当前作用域下没有,那么顺着作用域链向上级作用域查找,如果有则使用,如果没有找到则继续查找上级作用域…一直找到全局作用域为止,这种查找机制就是作用域链查找机制。
函数执行结束后,如果有内容被外界占用,则不会被销毁。
下载一个jQuery
$ npm init -y
$ npm install jquery
找到文件:\zhaoxiajingjing\node_modules\jquery\dist\jquery.js
把代码都收起来查看
(function(global, factory){
// ... something code
})(typeof window !== "undefined" ? window : this, function(window, noGLobal){
// ... something code
});
再简化:
// 自执行匿名函数
(function([arg1 [, arg2]]){})([params1], [params2]);
(function(){})();
函数执行会形成一个全新的私有作用域,保护里面的变量不受外界干扰,这种保护机制就是闭包。
项目开发时,我们会定义很多方法来实现需求。当引入jQuery类库时,里面有大量的方法,为防止它与我们写的方法冲突,需要使用闭包保护起来,jQuery类库的方法不受外界的干扰。
使用闭包作用:保护
自执行匿名函数:
(function(){})()
~function(){}()
+function(){}()
-function(){}()
!function(){}()
需求:tab选项卡,切换按钮与卡片的内容对应。
【PS:此处省略…样式代码】
<div id="tabBox">
<ul id="navBox">
<li class="active">公号li>
<li>读书li>
<li>运动li>
ul>
<div class="active">
<p>公号:朝霞的光影笔记p>
<p>ID:zhaoxiajingjingp>
div>
<div>《JavaScript 高级程序设计(第三版)》div>
<div>举个铁~~div>
div>
var tabBox = document.querySelector('#tabBox'), // tab盒子
navList = document.querySelectorAll('#navBox li'), // 导航按钮集合
divList = tabBox.querySelectorAll('div'); // 卡片集合
// 实现选项卡切换
var changeTab = function (index) {
// index:当前被点击的LI的索引
for (var i = 0; i < navList.length; i++) {
navList[i].className = '';
divList[i].className = '';
}
navList[index].className = 'active';
divList[index].className = 'active';
};
// 循环给每一个LI绑定点击事件
for (var i = 0; i < navList.length; i++) {
navList[i].onclick = function () {
// 此时的i是循环结束后的结果,i=>3
changeTab(i);
}
}
在浏览器控制台打印dir(navList)
可以查看节点集合
△上述代码的简单图解
我们想要点击时候,i是分别是0、1、2,利用闭包解决(解决方案很多种,以后再汇总):
函数执行会形成一个全新的私有作用域,保护里面的变量不受外界干扰,这种保护机制就是闭包
for (var i = 0; i < navList.length; i++) {
~ function (n) {
navList[n].onclick = function () {
changeTab(n);
}
}(i);
}
△第一种闭包写法
△第一个闭包写法的图解
使用闭包作用:保存
把选项卡的索引通过闭包保存起来,在用户触发点击事件时再去查找对应的变量。
for (var i = 0; i < navList.length; i++) {
navList[i].onclick = (function (n) {
return function () {
changeTab(n);
}
})(i);
}
△第二种闭包写法,图解自己去画吧~皮卡丘
很多人认为,只有形成不销毁的栈内存才是闭包,或者简单的说是一个函数返回一个函数就是闭包。
但,函数执行会形成一个全新的私有作用域,保护里面的变量不受外界干扰,这种保护机制就是闭包。从函数保护自己的私有变量不受外界干扰那一刻,闭包就形成了。
【PS:男友力爆棚,有木有闭包就是函数的男友力技能,有木有】
那么,使用闭包的两个好处/作用:保护和保存。
在阅读jQuery的源码时发现,它使用了自执行匿名函数把自己代码使用闭包包裹起来,不受外界方法的干扰,这样在自己开发过程中命名的方法不会和jQuery里面定义的方法冲突,利用的就是闭包的保护作用。
在tab选项卡这个需求里面,同样可以使用闭包形成的不销毁作用域/栈内存把变量存储起来,用户再点击时触发的方法通过作用域链查找到对应的索引变量,从而实现选项卡的正确切换,这个利用的是闭包的保存作用。
堆栈内存的销毁方式?
▽大图来啦今天的内容很长,但不难,仔细消化消化吧画起来~冲鸭!