JavaScript之作用域与闭包

作用域(Scope)

作用域定义了变量在代码中的可见性和访问性。 在JavaScript中,作用域可以分为全局作用域和局部作用域(函数作用域和块级作用域)。

在ES5中,一般只有两种作用域类型:

  1. 全局作用域(Global Scope):在代码中,位于任何函数外部的变量被称为全局变量,它们在整个代码中都是可见和访问的。

  2. 局部作用域(Local Scope):在函数内部声明的变量拥有函数范围的作用域,称为局部变量。这意味着它们只能在函数内部访问,函数外部无法访问。

    • 函数作用域:在函数内部声明的变量只能在该函数内部访问。函数参数也属于函数作用域。

    • 块级作用域:在ES6新增了一种「块级作用域」,其中引入了letconst关键字,可以在代码块(例如if语句、for循环等)内部创建块级作用域,使得变量在块内部有效,块外部无法访问。

// 全局作用域
var globalVar = 'I am a global variable';

function outerFunction() {
    // 函数作用域
    var outerVar = 'I am an outer variable';
    
    function innerFunction() {
        // 函数作用域内的嵌套函数
        var innerVar = 'I am an inner variable';
        console.log(innerVar); // 输出: "I am an inner variable"
        console.log(outerVar); // 输出: "I am an outer variable"
        console.log(globalVar); // 输出: "I am a global variable"
    }
    
    innerFunction();
    
    // 在函数作用域中访问变量
    console.log(outerVar); // 输出: "I am an outer variable"
    console.log(globalVar); // 输出: "I am a global variable"
}

outerFunction();

// 在全局作用域中访问变量
console.log(globalVar); // 输出: "I am a global variable"
// console.log(outerVar); // 这行会导致错误,outerVar 在全局作用域中不可见
// console.log(innerVar); // 这行也会导致错误,innerVar 在全局作用域中不可见

词法作用域(Lexical Scope)

词法作用域是指变量的作用域由代码的结构在编写时静态决定的特性。换句话说,它是根据代码中变量声明的位置来确定变量在哪个作用域中可见和可访问。

var outerVar = 'I am from outer scope';

function outerFunction() {
    var innerVar = 'I am from inner scope';

    function innerFunction() {
        console.log(outerVar); // 可以访问外部作用域中的 outerVar
        console.log(innerVar); // 可以访问外部作用域中的 innerVar
    }

    innerFunction();
}

outerFunction();

作用域链(Scope Chain)

作用域链是一种结构,用于在运行时决定在哪个作用域中查找变量。当代码中访问一个变量时,JavaScript 引擎会首先在当前作用域中查找,如果找不到,它就会向上一级作用域继续查找,直到找到变量或到达全局作用域为止。

闭包(Closure)

闭包是一个函数以及其在创建时能够访问的词法作用域的组合。 简单来说,闭包允许函数在其外部声明的作用域之外被调用,但仍然能够访问其外部作用域的变量。也就是说,一个函数和对其周围(词法环境)的引用捆绑在一起(或者说函数被引用包围),这样一个组合就是闭包

严格来讲,所以JavaScript函数都是闭包

闭包 = 函数 + 外层作用域

闭包的常见使用场景包括:

  1. 数据封装:通过闭包,可以创建私有变量,限制对变量的直接访问,从而实现数据的封装和保护。

  2. 延迟执行:通过将函数放入闭包中,可以在稍后的时间点执行该函数,并保留执行时的上下文信息。

  3. 循环中的问题:在循环中创建闭包时,需要注意循环变量的作用域问题,否则可能导致意外的结果。

function outerFunction() {
    var outerVariable = 'I am from outer function';
    
    function innerFunction() {
        console.log(outerVariable); // 内部函数可以访问外部函数的变量
    }
    
    return innerFunction;
}

var closureFunction = outerFunction();
closureFunction(); // 输出: "I am from outer function"
高频闭包面试题
理解闭包的概念和用途

问题: 什么是闭包?请举一个实际的例子来说明闭包的用途。

解答: 闭包是指一个函数能够记住并访问其词法作用域(外部函数)中的变量,即使在该外部函数执行完毕后。闭包允许在一个函数内部创建并返回另一个函数,后者可以访问前者作用域中的变量。

循环中的闭包问题

问题: 下面的代码输出什么?为什么?

var arr = []
for(var i=0;i<3;i++){
    arr[i] = function(){
        console.log(i)
    } 
}
arr[0]()  // 3
arr[1]()  // 3
arr[2]()  // 3
// 这里在执行的时候i已经变成了3

// 使用闭包解决
var arr = []
for(var i=0;i<3;i++){
    arr[i] = (function(i){
        return function(){
            console.log(i)
        } 
    })(i)
    
}
arr[0]()  // 0
arr[1]()  // 1
arr[2]()  // 2

第一段段代码会输出三个数字 3,因为在循环中使用了闭包。当循环结束后,i 的值已经变成了 3,因此在每个定时器执行时,它都会打印出 3

为了解决这个问题,可以使用块级作用域和立即执行函数表达式来创建一个新的作用域,以保留每次迭代的 i 值。

问题: 闭包是否可能导致内存泄漏?如果是,如何避免?

解答: 闭包可能导致内存泄漏。如果闭包中引用了外部函数中不再需要的变量,这些变量将不会被垃圾回收,从而导致内存泄漏。

为避免内存泄漏,应该确保在不再需要闭包时,解除对外部变量的引用。例如,可以在函数执行完毕后手动解除闭包引用:

function createClosure() {
    var data = 'Sensitive data';
    
    return function() {
        console.log(data);
        // 当不再需要闭包时,手动解除引用
        data = null;
    };
}

var closure = createClosure();
closure();
// 此时,data 变量已经被解除引用,有助于垃圾回收

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ikkkp

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值