JavaScript - 闭包

69 阅读3分钟

1、前置概念

  • JavaScript - 词法作用域( 最核心概念
    • 词法作用域是指函数和变量的作用域是在代码编写时确定的,而不是在运行时确定
    • 定义函数时生效的变量作用域,不是调用函数时生效的变量作用域

2、对闭包的理解

  • 你不知道的JavaScript

    • 使得函数可以 继续访问 定义时的词法作用域
  • Javascript 忍者秘籍2

    • 允许 函数访问并操作函数 "外部" 的变量的函数
  • Javascript 高级程序设计4

    • 有权访问 另一个函数作用域中的变量的函数
  • JavaScript 权威指南7

    • JavaScript使用词法作用域:这意味着函数执行时使用的是定义函数时生效的变量作用域,而不是调用函数时生效的变量作用域。为了实现词法作用域,JavaScript函数对象的内部状态不仅要包括函数代码,还要包括对函数定义所在作用域的引用。这种函数对象与作用域组合起来解析函数变量的机制,在计算机科学文献中被称作闭包。
    • 严格来讲,所有JavaScript函数都是闭包。但由于多数函数调用与函数定义都在同一作用域内,所以闭包的存在无关紧要。闭包真正值得关注的时候,是定义函数与调用函数的作用域不同的时候。最常见的情形是一个函数返回了在它内部定义的嵌套函数
  • MDN

    • 定义:函数和对其周围状态的引用,捆绑在一起构成是闭包
    • 能力:也就是说,闭包让你可以在一个内层函数中访问到其外层函数的作用域
    • 在 JavaScript 中,每当创建一个函数,闭包就会在函数创建的同时被创建出来

3、闭包的应用

基本使用

function Add3(){
    var a = 10;
    return function(){
        a++;
        return a;
    };
};

var cc =  Add3();
cc(); // 11
cc(); // 12
cc(); // 13

for循环中闭包的使用

function outer() {
    var result = [];
    for (var i = 0;i<10; i++){
        // 外面包一层自执行函数,保留了当前循环的 i 值
        result[i] = function (num) {
            // 当执行内层函数的时候,为当前循环时的值
             return function() {
                 // 使用闭包,每一个num值都是不一样的
                 console.info(num);    
             }
        }(i) // 外套一层函数
    }
    return result
}


outer()[6]()

获取商品的价格区间

// 使用箭头函数
var between = (a, b)=> v =>{
    return a>=v.price && b<=v.price;
}

let lessons = [
    {title:"html",click:3,price:3},
    {title:"html",click:34,price:34},
    {title:"html",click:1,price:1}
];
console.log( lessons.filter(between(10,40)))

4、解决this的历史遗留问题

// 利用闭包、箭头函数、bind 等,解决上述问题
let user = {
    name:"张三",
    getName(){
        let that = this;
        return function(){
            // return this.name; 指向 window
            return that.name; // 指向 "张三"
        }
    }
}
user.getName()() // 张三

5、防止闭包内存泄露的策略

5.1 及时清理事件监听器和定时器

function setup() {
    const element = document.getElementById('my-element');
    const handler = () => {
        console.log('Clicked!');
    };

    element.addEventListener('click', handler);

    return () => {
        // 清理事件监听器
        element.removeEventListener('click', handler);
    };
}

5.2 避免全局变量或模块作用域中的闭包

// 不推荐
let globalClosure;

function setupGlobalClosure() {
    const localVariable = 'data';
    globalClosure = () => {
        console.log(localVariable);
    };
}

// 推荐
function setupLocalClosure() {
    const localVariable = 'data';
    return () => {
        console.log(localVariable);
    };
}

const localClosure = setupLocalClosure();