如何看待闭包?
理解方面解释:
函数执行形成一个私有作用域,保护里面的私有变量不受外界的干扰,这种保护机制称为 闭包;
口头方面的(面试说这个):
形成一个不销毁的私有作用域(私有栈内存)就是 闭包;
如:
functon fn () {
return function() {}
}
var f = fn();
以上可以称为 闭包,也可以称为: “柯里化函数”
如:闭包 => 惰性函数;
var utils = (function () {
return {
xxx
}
})()
闭包的应用:
项目中,为保证 JS 的性能(堆栈内存的性能优化),应该尽可能地减少 闭包的使用(不销毁的堆栈内存是耗性能的)
1,闭包的保护作用:
保护私有变量不受外界干扰;
真实项目中,团队合作开发,应当尽可能的减少全局变量的使用,防止变量的冲突(“全局变量的污染”),我们可以将 自己的代码 封装到一个闭包(立即执行函数)中,让全局变量转换为 私有变量 。
// 小米的代码
(function one() {
xxxxx
})();
// 小苗的代码
(function one() {
xxxxx
})();
但是,我们把代码都包裹在 闭包中,都变成了 私有变量,但我们有时,外部需要我们的方法,该怎么人外部使用我们的方法呢?
1,JQ的方式:
在闭包内,给 window
添加属性,在外部就可以获取到 里面的属性和方法了 。
(function() {
function jQuery () {
xxxxx
}
.....
window.jQuery = window.$ = jQuery
})();
jQuery(); 或 $()
2,Zepto 的方式:
基于 return
的方式,把需要暴露的方法和属性,都用 return
一个对象的方式,暴露出来;
/*
1, 通过 return 暴露方法
2, 赋值给变量
3,变量点的形式拿到值
*/
let Zepto = (function () {
xxxxxxx
return {
xxx: function() {}
......
}
})();
Zepto.xxx()
2,闭包的保存机制:
我们可以看一题,就是使用 for
循环,然后给每个元素添加点击事件:
tab 栏切换,就是点击不同的框,显示不同的内容;
以下是代码:
<body>
<div class="one">
<div class="title">
<div class="a active">1</div>
<div class="b">2</div>
<div class="c">3</div>
</div>
<div class="content">
<div class="aa active">1</div>
<div class="bb">2</div>
<div class="cc">3</div>
</div>
</div>
<script>
let titles = document.querySelector('.title')
let titleItem = titles.querySelectorAll('div')
let content = document.querySelector('.content')
let contentItem = content.querySelectorAll('div')
xxxxx
</script>
</body>
在 xxx 处写这个代码:
for (var i = 0; i < titleItem.length; i++) {
titleItem[i].onclick = function() {
console.log(i)
}
}
给框添加点击事件,页面加载好后,点击框,输出的内容居然是 3,3,3, 3次3,这是怎么回事呢!
由老穆 和 小木马 来给你们来说:
老穆:你可以从浏览器的渲染机制来看;我们的浏览器渲染好页面后,是不是意味着 js 代码也执行好了。
小木马: 对啊,页面加载完成,js 也是加载好的 。
老穆: js运行时,点击事件只绑定,没有执行; 在 js 运行时形成一个 全局作用域 window,第一步就 “变量提升” ,把带 var 和 function 的进行预编译, for 中的 var i,于是就变成了 全局变量,对吧!
小木马: 对对对,别啰嗦,快说输出 i 为什么是 3 的原因;
老穆:不着急,这个 i 为全局变量你懂吗? 如果不懂,建议可以去看看 js的预编译/变量提升 。
小木马: 还可以,继续吧!
老穆: 执行 for 给每个框添加了 点击事件,在我们点击框时,才执行,
点击事件是一个函数,会形成一个 “私有作用域”,在函数中输出 i 的值,很明显 i 不是 函 数私有作用域 的 私有变量,那么就到上级作用域找 i 这个值,找到了在 全局作用域
(window)中的 i(for 循环定义的 i 变为全局的嘛!)
图自己乱画的。
小木马: 去上级作用域找值,这个是不是 作用域链 的知识点!
老穆: 是的,不熟息,可以去学习一下这一点;
我们之前说过, i 已经变成了全局变量,那 for 执行时,进行 ++ 的 i,也是他(全局的i),
那么 for执行完毕后,我们的 i已经被加成了 条件不正确时 的值,也就是我们的 3;
这个时候,我们点击框时输出的 i 是 3 。
小木马: 那那那,我们怎么解决这个问题呢!
老穆: 那么,我们可以使用 闭包的保存机制,把 i 保存到 私有作用域 中,使他变成 私有变量,就不用到 window 中找 i 了。
小木马: 具体的实现方法呢?!
老穆: 创建一个上级作用域,那么函数的上级作用域,就是私有作用域;把 i 存储到这个私有作用域中,变为私有变量
for (var i = 0; i < titleItem.length; i++) {
function fn (index) {
titleItem[i].onclick = function() {
console.log(index)
}
}
fn(i)
}
// 或者
for (var i = 0; i < titleItem.length; i++) {
(function(index) {
titleItem[index].onclick = function() {
console.log(index)
}
})(i)
}
但是他会形参一个不销毁作用域,这样会耗费性能;
小木马: 为什么,这样就可以了呢?
老穆: 你看看,我们的点击事件是在页面加载后执行的,输出的 i 已经累加完成了,
但我们在页面执行时,同时把 i 传递到 私有作用域(函数)中,在 私有作用域 中,
第一步就是 形参赋值,这个形参是我们的 私有变量,在 点击事件 中,
输出的 i 就是 私有变量了,就不用到 上级作用域(我们这里是全局作用域)找 i 了,
私有变量,不受到外部的影响,for 循环,累加的 i 是全局的,和我们 私有作用域中的 i 没有关系;
那么我们点击框时,输出的 i 就是 1,2,3 了。
小木马: 哦哦哦,在执行 for 循环时,也会执行 普通函数 / 自执行函数 (上面2个例子),
把 i 传递进去了,是循环时的 i (i 为 0,1,2 时),传递到 私有作用域 中,就不受外界影响,
这样就厉害呀,这就是 闭包的保存 机制吗?
老穆: 是的,如果还不太明白,这里有个视频,可以去看看,就是有点 长,不过每天一集,看完应该会很了解的 。
那我们有哪些解决方法呢?
1,自定义属性
for (var i = 0; i < titleItem.length; i++) {
titleItem[i].setAttribute('index', i)
titleItem[i].onclick = function() {
// 排他思想
for(var i = 0; i <titleItem.length; i++) {
titleItem[i].className = contentItem[i].className = ''
}
// 获取自定义属性
const index = this.getAttribute('index')
// 根据自定义属性给样式
titleItem[index].className = contentItem[index].className = 'active'
}
}
2,闭包;
function qh (index) {
for(var i = 0; i <titleItem.length; i++) {
// 排他思想
titleItem[i].className = contentItem[i].className = ''
}
// 根据自定义属性给样式
titleItem[index].className = contentItem[index].className = 'active'
}
for (var i = 0; i < titleItem.length; i++) {
(function(index) {
titleItem[index].onclick = function() {
qh(index)
}
})(i);
}
3,使用 ES6 语法 let
for (let i = 0; i < titleItem.length; i++) {
titleItem[i].onclick = function() {
// 排他思想
for(let i = 0; i <titleItem.length; i++) {
titleItem[i].className = contentItem[i].className = ''
}
// 根据自定义属性给样式
titleItem[i].className = contentItem[i].className = 'active'
}
}
每创建一个块级作用域,都会形成一个 私有的作用域;