JS基础总结 - 作用域和闭包

1. 作用域

作用域(Scope)指的是代码中定义变量的区域或上下文,它决定了代码中哪些部分可以访问哪些变量。JavaScript 中的作用域主要有两种:全局作用域和局部作用域。

1.1 全局作用域

在最外层定义的变量具有全局作用域,可以在任何地方访问。

var globalVar = "I am a global variable";

function globalScopeTest() {
    console.log(globalVar); // "I am a global variable"
}

globalScopeTest();
console.log(globalVar); // "I am a global variable"

1.2 局部作用域(函数作用域)

在函数内部定义的变量具有局部作用域,只能在函数内部访问。

function localScopeTest() {
    var localVar = "I am a local variable";
    console.log(localVar); // "I am a local variable"
}

localScopeTest();
console.log(localVar); // ReferenceError: localVar is not defined

1.3 块级作用域(ES6新增)

ES6 引入了 let 和 const 关键字,这些关键字定义的变量具有块级作用域,只在定义它们的块中有效。

{
    let blockVar = "I am a block-scoped variable";
    console.log(blockVar); // "I am a block-scoped variable"
}

console.log(blockVar); // ReferenceError: blockVar is not defined

2. 闭包

2.1 什么是闭包?

闭包(Closure)是指在创建它的环境中捕获并保存外部变量的函数。闭包使得函数可以访问和操作其外部的变量,即使这个函数在其词法作用域之外被调用。

简单的说:闭包是返回函数内部变量(自由变量)的函数。(不仅返回函数,而且返回了该函数的运行环境)

ps: 自由变量
一个变量在当前作用域,没有被定义,到外层作用域中被找到。
所有的自由变量的查找,是在函数定义的地方,向上级作用域查找。不是在执行的地方!!!

2.2 基本用法:

  • 函数作为返回值
  • 函数作为参数
function createCounter() {
    let count = 0;

    return function() {
        count++;
        console.log(count);
    };
}

const counter = createCounter();
counter(); // 1
counter(); // 2
counter(); // 3

2.3 实际应用场景

1. 数据隐藏和封装:闭包可以用于模拟私有变量,使得一些数据只能通过特定的函数访问。

function Person(name) {
    let _name = name; // 私有变量

    this.getName = function() {
        return _name;
    };

    this.setName = function(newName) {
        _name = newName;
    };
}

const person = new Person("Alice");
console.log(person.getName()); // Alice
person.setName("Bob");
console.log(person.getName()); // Bob

2. 工厂函数:工厂函数是一种设计模式,通过函数返回新对象。这种方法通常用于避免重复创建对象时的冗余代码,同时保持代码的可维护性和灵活性。

// 简单的计数器工厂函数
function createCounter() {
    let count = 0; // 私有变量

    return {
        increment: function() {
            count++;
            return count;
        },
        decrement: function() {
            count--;
            return count;
        },
        getCount: function() {
            return count;
        }
    };
}

let counter1 = createCounter();
console.log(counter1.increment()); // 1
console.log(counter1.increment()); // 2
console.log(counter1.decrement()); // 1

let counter2 = createCounter();
console.log(counter2.increment()); // 1
console.log(counter2.getCount()); // 1

console.log(counter1.getCount()); // 1 (counter1 和 counter2 的状态是独立的)

3. 回调函数:在异步编程中,闭包经常用于保持某些数据的状态。

function asyncOperation(message, callback) {
    setTimeout(() => {
        console.log(message);
        callback();
    }, 1000);
}

asyncOperation("Hello, World!", function() {
    console.log("Callback executed!");
});

2.3 闭包可能导致的内存泄漏

如果不当使用闭包,也可能导致内存泄漏。要理解如何防止闭包导致的内存泄漏,首先需要理解什么是闭包,以及闭包可能导致内存泄漏的原因。

由于闭包持有对外部函数作用域的引用,因此如果闭包函数存在且未被释放,它会阻止外部函数中的变量被垃圾回收。这会导致内存泄漏,尤其是在长时间运行的程序或在处理大量数据时更为明显。

防止闭包导致内存泄漏的方法

  1. 避免不必要的闭包: 只在确实需要闭包功能时使用闭包,避免在可以使用普通函数或局部变量的地方使用闭包。

  2. 及时清理闭包: 在不再需要使用闭包时,手动解除对闭包的引用,帮助垃圾回收机制回收内存。例如,在JavaScript中,可以将闭包变量设为null

  3. 使用IIFE(立即执行函数表达式): IIFE创建的变量在函数执行完毕后不会被继续引用,从而减少内存泄漏的风险。

  4. 避免在全局对象上创建闭包: 尽量避免在全局对象(如window)上创建闭包,这样会使闭包的生命周期与应用程序的生命周期相同,从而增加内存泄漏的风险。

  5. 使用WeakMap: 在JavaScript中,可以使用WeakMap来存储闭包引用。WeakMap允许对键的弱引用,从而不会阻止垃圾回收。

示例代码

以下是一些示例代码,演示如何使用上述方法防止闭包导致的内存泄漏。

示例1:避免不必要的闭包
function createCounter() {
    let count = 0;
    return function increment() {
        count++;
        return count;
    };
}

let counter = createCounter();
console.log(counter()); // 1
console.log(counter()); // 2
counter = null; // 清理闭包,防止内存泄漏
示例2:使用IIFE
(function() {
    let count = 0;
    function increment() {
        count++;
        console.log(count);
    }
    increment();
    increment();
})(); // 1 2
// 立即执行函数表达式执行完毕,`count`和`increment`不会被继续引用
示例3:使用WeakMap
const wm = new WeakMap();
(function() {
    let obj = {};
    function createClosure() {
        let count = 0;
        return function increment() {
            count++;
            return count;
        };
    }
    wm.set(obj, createClosure());
})();
// 由于WeakMap的使用,闭包可以被回收,不会导致内存泄漏

通过理解闭包的工作机制并采用适当的方法,可以有效防止闭包导致的内存泄漏问题。

总结

  • 作用域:决定了变量和函数的可访问范围。JavaScript 有全局作用域、局部作用域和块级作用域。
  • 闭包:是一个函数能够访问其词法作用域中的变量,即使这个函数在其词法作用域之外被调用。闭包用于数据隐藏、回调函数和模块模式等场景。

理解和运用 JavaScript 的作用域和闭包,可以写出更健壮、灵活和可维护的代码。

posted @ 2021-03-06 23:30  Better-HTQ  阅读(2)  评论(0编辑  收藏  举报