又是 闭包?

1 篇文章 0 订阅

如何看待闭包?

理解方面解释:

​ 函数执行形成一个私有作用域,保护里面的私有变量不受外界的干扰,这种保护机制称为 闭包;


口头方面的(面试说这个):

​ 形成一个不销毁的私有作用域(私有栈内存)就是 闭包;

如:

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'
    }
}

每创建一个块级作用域,都会形成一个 私有的作用域;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值