理论是自信的基础, 结合理论的实践才能让我们走的更远.
前两个系列, 我记录了闭包的学习, 如何利用闭包解决实际问题了? 其实, 很多东西你我都知道, 不是一蹴而就的, 不是你今天学了就会了, 还需要多次练习, 反复练习. 相信终究一天你我会运用自如.
下面就通过 3 个小例子, 来运用闭包解决实际问题吧.
9.1 循环, setTimeout 与闭包
在面试题中常常会遇到一个与循环, 闭包有关的问题, 如下:
- // 利用闭包的知识修改这段代码, 让代码的执行结果为隔秒输出 1,2,3,4,5
- for (var i = 1; i<=5; i++) {
- setTimeout(timer=()=>{
- console.log(i)
- },i*1000)
- }
首先来分析一下如果直接运行这个例子会输出什么结果.(涉及到定时器线程的相关知识还需要自行学习)
前面我们已经知道, for 循环的大括号并不会形成自己的作用域, 因此这个时候肯定是没有闭包产生的, 而 i 值作为全局的一个变量, 会随着循环的过程递增. 因此循环结束之后, i 变成了 6.
而每一个循环中, setTimerout 的第二个参数访问的都是当前的 i 值, 因此第二个 i 值分别是 1,2,3,4,5. 而第一个参数 timer 函数中虽然访问的是同一个 i 值, 但是由于延迟的原因, 当 timer 函数被 setTimerout 运行时, 循环已经结束, 即 i 已经变为 6 了.
因此这段代码执行结果是隔秒输出 6.
而我们想要的是隔秒输出 1,2,3,4,5, 因此需要借助闭包的特性, 将每一个 i 值都用一个闭包保护起来. 每一轮循环, 都把当前的 i 值保存在一个闭包中, 当 setTimerout 中定义的操作执行时, 访问对应闭包即可.
这个时候我们回想一下闭包形成的条件, 简单来说, 就是一个函数中定义了一个子函数, 子函数内部访问了函数的变量对象. 因此我们只需要创建一个这样的环境即可.
- for (var i = 1; i<=5; i++) {
- (function(i) {
- setTimeout(timer=()=>{ console.log(i)
- },i*1000) })(i)
- }
定义一个匿名函数, 称作 A, 并将其当作闭包环境. 而 timer 函数则作为 A 的内部函数, 当 A 执行时, 只需访问 A 的变量对象即可. 因此将 i 值作为参数传入, 这样也就满足了闭包的条件, 并将 i 值保存在了 A 中.
同样的道理也可以在 timer 函数里做文章. 还有更多方法, 这里就不一一赘述.
9.2 单例模式与闭包
好, 我继续我的笔记.
在 javascript 中有许多解决特定问题的编码思维(设计模式, Alloy Team 出的那本个人觉得很棒), 例如工厂模式, 发布订阅模式, 装饰者模式, 单例模式等. 其中, 单例模式是早期开发最常用的模式之一, 而它的实现, 与闭包戚戚相关.
所谓单例模式, 就是只有一个实例.
1. 最简单的单例模式
对象字面量的方法就是最简单的单例模式, 我们可以将属性与方法依次放在字面量里.
- var person = {
- name: 'pan',
- age: '18',
- getName: function() {
- return this.name
- },
- getAge: function() {
- return this.age
- }
- }
但是这样的单例模式有一个严重的问题, 即它的属性可以被外不修改. 因此在许多场景中, 这样的写法并不符合我们的需求, 我们更期望对象能够有自己的私有方法与属性.
PS: 个人觉得不论学习什么, 都应该回顾它的历史, 以便更利于当下的学习. 理论性知识发展到目前, 不是偶然, 总有那么一个阶段性的过渡. 司徒正美在他的作品javascript 框架设计前言中就说到了这么一个事 "当初, 我阅读 jQuery 源码, 最初看的是 1.4.3 版本, 看得一头雾水, 一气之下, 从最初的 1.0 版本开始看. 看完所有版本, 了解其迭代过程, 才明白".
2. 有私有方法 / 属性的单例模式
通过前面所学的知识我们很容易就能想到, 想要一个对象拥有自己私有的方法属性, 那么只需要创建一个单独的作用域将对象与外界隔离起来就行了. 这里我们借助匿名函数自执行的方式即可.
- var person = (function() {
- var name = 'pan';
- var age = 18;
- return{
- function getName() { return name
- };
- function getAge() {
- return age
- } }
- })();
- person.getName();
私有变量的好处在于, 外界对于私有变量能够进行什么样的操作是可以控制的. 我们可以提供一个 getName 方法让外界可以访问名字, 也可以额外提供一个 setName 方法, 来修改它的名字. 对外提供什么样的能力, 完全由我们自己决定.
现在我们正走在模块化的道路上....
3. 调用时才初始化的单例模式
有的时候 (使用频次较少) 我们希望自己的实例仅仅只是在调用的时候才被初始化, 而不像上面两个例子那样, 即使没有调用 person,person 的实例在函数自执行的时候就返回了.
那么我们就需要在上面例子的基础上做一点小小的改动了.
- var person = (function(){
- // 定义一个变量, 用来保存实例
- var instance = null;
- var name = 'pan';
- var age = 18;
- // 初始化方法
- function init() {
- return{
- getName: function() { return name;},
- getAge: function() { return age;}
- }
- }
- return {
- getInstance: function() {
- if(!instance){
- instance = init()
- }
- return instance
- }
- }
- })();
- // 只在使用时获取实例
- var p1 = person.getInstance();
在这个例子中, 我们对匿名函数中定义了一个 instance 变量用来保存实例. 在 getInstance 方法中判断了是否对他进行重新赋值. 由于这个判断的存在, 因此变量 instance 仅仅只在第一次调用 getInstance 方法时赋值了.
在写一个系列, 我将持续更新我闭包相关应用. 感谢阳波大神.
这些都是我以往的学习笔记. 如果您看到此笔记, 希望您能指出我的错误. 有这么一个群, 里面的小伙伴互相监督, 坚持每天输出自己的学习心得, 不输出就出局. 希望您能加入, 我们一起终身学习. 欢迎添加我的个人微信号: Pan1005919589
来源: https://juejin.im/post/5b20e657f265da6e053ad5ee