前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Javascript 闭包

Javascript 闭包

原创
作者头像
超级大帅比
修改2021-09-13 10:08:29
3970
修改2021-09-13 10:08:29
举报
文章被收录于专栏:长路漫漫长路漫漫

1 闭包定义

一个函数和对其周围状态(lexical environment,词法环境)的引用捆绑在一起(或者说函数被引用包围),这样的组合就是闭包(closure)。也就是说,闭包让你可以在一个内层函数中访问到其外层函数的作用域。在 JavaScript 中,每当创建一个函数,闭包就会在函数创建的同时被创建出来。

代码语言:txt
复制
// 示例1
function makeFunc() {
    // name是个局部变量,在makeFunc的函数作用域中
    var name = "Mozilla";
    function displayName() {
        // displayName函数使用了自己函数作用域以外的变量
        alert(name);
    }
    return displayName;
}

var myFunc = makeFunc();
myFunc();

// 示例2
function makeAdder(x) {
  // x是makeAdder函数的入参,在makeAdder的函数作用域中,被一个匿名函数使用了
  return function(y) {
    return x + y;
  };
}

var add5 = makeAdder(5);
var add10 = makeAdder(10);

console.log(add5(2));  // 7
console.log(add10(2)); // 12

类似于面向对象编程。在面向对象编程中,对象允许我们将某些数据(对象的属性)与一个或者多个方法相关联。

2 使用场景

2.1 回调函数

代码语言:txt
复制
// 示例1
// 在页面上添加一些可以调整字号的按钮。
function makeSizer(size) {
  return function() {
    document.body.style.fontSize = size + 'px';
  };
}

var size12 = makeSizer(12);
var size14 = makeSizer(14);
var size16 = makeSizer(16);

document.getElementById('size-12').onclick = size12;
document.getElementById('size-14').onclick = size14;
document.getElementById('size-16').onclick = size16;

2.2 封装私有变量、私有函数

代码语言:txt
复制
var makeCounter = function() {
  // 私有变量
  var privateCounter = 0;
  // 私有函数
  function changeBy(val) {
    privateCounter += val;
  }
  return {
    increment: function() {
      changeBy(1);
    },
    decrement: function() {
      changeBy(-1);
    },
    value: function() {
      return privateCounter;
    }
  }
};
/* makeCounter执行一次会生成新的局部变量privateCounter,由于有闭包,makeCounter执行完后         
 * privateCounter不会释放,每个闭包都是引用自己词法作用域内的变量privateCounter
 */
var Counter1 = makeCounter();
var Counter2 = makeCounter();
console.log(Counter1.value()); /* logs 0 */
Counter1.increment();
Counter1.increment();
console.log(Counter1.value()); /* logs 2 */
Counter1.decrement();
console.log(Counter1.value()); /* logs 1 */
console.log(Counter2.value()); /* logs 0 */

2.3 防抖与节流(闭包的典型应用)

代码语言:txt
复制
// 防抖
function debounce (fn, delay=500) {
    let timer = null;    
    return function() {
        if(timer) {
            clearTimeout(timer);
        }
        timer = setTimerout(() => {
                fn.apply(this, arguments);
                timer = null;
            }, delay);
        
    }    
}

// 节流
function throttle (fn, delay=500) {
    let timer = null;    
    return function() {
        if(timer) {
            return;
        }
        timer = setTimerout(() => {
                fn.apply(this, arguments);
                timer = null;
            }, delay);

    }    
}

3 与闭包经常一起使用的语法 IIFE(立即调用函数表达式)

3.1 IIFE定义

IIFE( 立即调用函数表达式)是一个在定义时就会立即执行的 JavaScript 函数。

写法: (函数声明)(函数参数)

3.2 IIFE特性

当函数变成立即执行的函数表达式时,表达式中的变量不能从外部访问。

代码语言:txt
复制
(function () {
    var name = "Barry";
})();
// 无法从外部访问变量 name
name // 抛出错误:"Uncaught ReferenceError: name is not defined"

将 IIFE 分配给一个变量,不是存储 IIFE 本身,而是存储 IIFE 执行后返回的结果

代码语言:txt
复制
var result = (function () {
    var name = "Barry";
    return name;
})();
// IIFE 执行后返回的结果:
result; // "Barry"

4 使用闭包时的常见错误

4.1循环中创建闭包

代码语言:txt
复制
function showHelp(help) {
  document.getElementById('help').innerHTML = help;
}

function setupHelp() {
  var helpText = [
      {'id': 'email', 'help': 'Your e-mail address'},
      {'id': 'name', 'help': 'Your full name'},
      {'id': 'age', 'help': 'Your age (you must be over 16)'}
    ];

  for (var i = 0; i < helpText.length; i++) {
    // var变量只有全局作用域及函数作用域,这里每次循环使用的是同一个item 
    var item = helpText[i];
    document.getElementById(item.id).onfocus = function() {
      showHelp(item.help);
    }
  }
}

赋值给 onfocus 的是闭包。这些闭包是由他们的函数定义和在 setupHelp 作用域中捕获的环境所组成的。这三个闭包在循环中被创建,但他们共享了同一个词法作用域,在这个作用域中存在一个变量item。三次循环后,变量对象item(被三个闭包所共享)已经指向了helpText的最后一项。

解决办法:

1.使用更多闭包

代码语言:txt
复制
// 其余地方不变,这里只修改了循环部分
for (var i = 0; i < helpText.length; i++) {
    // 三次循环生成了三个匿名函数,每个onfocus的回调绑定了一个各自的词法作用域
    (function() {
       var item = helpText[i];
       document.getElementById(item.id).onfocus = function() {
         showHelp(item.help);
       }
    })(); // 马上把当前循环项的item与事件回调相关联起来

2.使用es5中的let

代码语言:txt
复制
// 其余地方不变,这里只修改了循环部分
for (var i = 0; i < helpText.length; i++) {
    // let具有块级作用域,循环三次生成了三个
    let item = helpText[i];
    document.getElementById(item.id).onfocus = function() {
      showHelp(item.help);
    }
  }

5 性能考量

如果不是某些特定任务需要使用闭包,在其它函数中创建函数是不明智的,因为闭包在处理速度和内存消耗方面对脚本性能具有负面影响。应该尽量少使用闭包。

代码语言:txt
复制
// 这个构造函数每次调用MyObject都会生成新的getName、getMessage方法
function MyObject(name, message) {
  this.name = name.toString();
  this.message = message.toString();
  this.getName = function() {
    return this.name;
  };

  this.getMessage = function() {
    return this.message;
  };
}

// 推荐改为
function MyObject(name, message) {
  this.name = name.toString();
  this.message = message.toString();
}
MyObject.prototype.getName = function() {
  return this.name;
};
MyObject.prototype.getMessage = function() {
  return this.message;
};

参考

代码语言:txt
复制
[MDN](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Closures)

[MDN](https://developer.mozilla.org/zh-CN/docs/Glossary/IIFE)

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1 闭包定义
  • 2 使用场景
    • 2.1 回调函数
      • 2.2 封装私有变量、私有函数
        • 2.3 防抖与节流(闭包的典型应用)
        • 3 与闭包经常一起使用的语法 IIFE(立即调用函数表达式)
          • 3.1 IIFE定义
            • 3.2 IIFE特性
            • 4 使用闭包时的常见错误
              • 4.1循环中创建闭包
              • 5 性能考量
              • 参考
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档