作用域闭包

简介: 作用域闭包

说明

《你不知道的JavaScript》学习笔记。



定义

当函数可以记住并访问所在的词法作用域时,就产生了闭包,即使函数是在当前词法作用域之外执行。



例子:

function foo() {
    var a = 2;
    function bar() {
        console.log( a );
    }
    return bar;
}
var baz = foo();
baz(); //2 (这就是闭包的效果。)


本质上无论何时何地,如果将函数(访问它们各自的词法作用域)当作第一级的值类型并到处传递,你就会看到闭包在这些函数中的应用。在定时器、事件监听器、Ajax  请求、跨窗口通信、Web Workers 或者任何其他的异步(或者同步)任务中,只要使用了回调函数,实际上就是在使用闭包!



循环和闭包

for (var i=1; i<=5; i++) {
    setTimeout( function timer() {
        console.log( i ); // 每秒一次的频率输出五次 6。
    }, i*1000 );
}



对上面的例子进行改造:

for (var i=1; i<=5; i++) {
    (function(j) {
        setTimeout( function timer() {
            console.log( j ); // 1 , 2 , 3 , 4 , 5
        }, j*1000 );
    })( i );
}


在迭代内使用 IIFE 会为每个迭代都生成一个新的作用域, 使得延迟函数的回调可以将新的作用域封闭在每个迭代内部, 每个迭代中都会含有一个具有正确值的变量供我们访问。



重返块作用域

上面的例子使用 IIFE 在每次迭代时都创建一个新的作用域。


换句话说, 每次迭代我们都需要一个块作用域。

前面的学习我们了解到 let 声明, 可以用来劫持块作用域, 并且在这个块作用域中声明一个变量。

for (let i=1; i<=5; i++) {
    setTimeout( function timer() {
        console.log( i );
    }, i*1000 );
}



模块

模块有两个主要特征:


(1)、为创建内部作用域而调用了一个包装函数

(2)、包装函数的返回值必须至少包括一个对内部函数的引用, 这样就会创建涵盖整个包装函数内部作用域的闭包。

先看一个例子:

function CoolModule() {
    var something = "cool";
    var another = [1, 2, 3];
    function doSomething() {
        console.log( something );
    }
    function doAnother() {
        console.log( another.join( " ! " ) );
    }
    return {
        doSomething: doSomething,
        doAnother: doAnother
    };
}
var foo = CoolModule();
foo.doSomething(); // cool
foo.doAnother(); // 1 ! 2 ! 3



这个模式在JavaScript中被称为模块。


代码分析:


1、CoolModule() 只是一个函数, 必须要通过调用它来创建一个模块实例。 如果不执行外部函数, 内部作用域和闭包都无法被创建。


2、CoolModule() 返回一个用对象字面量语法 { key: value, ... } 来表示的对象。 这个返回的对象中含有对内部函数而不是内部数据变量的引用。 我们保持内部数据变量是隐藏且私有的状态。 可以将这个对象类型的返回值看作本质上是模块的公共 API。


3、doSomething() 和 doAnother() 函数具有涵盖模块实例内部作用域的闭包( 通过调用 CoolModule() 实现)。


现代的模块机制

例子:

// 1、公共 API 的函数
var MyModules = (function Manager() {
    var modules = {};
    function define(name, deps, impl) {
        for (var i=0; i<deps.length; i++) {
            deps[i] = modules[deps[i]];
        }
        modules[name] = impl.apply( impl, deps ); // 核心代码
    }
    function get(name) {
        return modules[name];
    }
    return {
        define: define,
        get: get
    };
})();
// 2、使用公共 API 来定义bar模块:
MyModules.define( "bar", [], function() {
    function hello(who) {
        return "Let me introduce: " + who;
    }
    return {
        hello: hello
    };
} );
// 3、"foo" 接受 "bar" 的示例作为依赖参数, 并能相应地使用它。
MyModules.define( "foo", ["bar"], function(bar) {
    var hungry = "hippo";
    function awesome() {
        console.log( bar.hello( hungry ).toUpperCase() );
    }
    return {
        awesome: awesome
    };
} );
var bar = MyModules.get( "bar" );
var foo = MyModules.get( "foo" );
console.log(
    bar.hello( "hippo" )
); // Let me introduce: hippo
foo.awesome(); // LET ME INTRODUCE: HIPPO


特点: 为函数定义引入包装函数, 并保证它的返回值和模块的 API 保持一致。


换句话说, 模块就是模块, 即使在它们外层加上一个友好的包装工具也不会发生任何变化。



未来的模块机制


ES6 中为模块增加了一级语法支持。 但通过模块系统进行加载时, ES6 会将文件当作独立的模块来处理。 每个模块都可以导入其他模块或特定的 API 成员, 同样也可以导出自己的API 成员。



ES6 的模块没有“行内” 格式, 必须被定义在独立的文件中(一个文件一个模块)。

1、bar.js 模块

function hello(who) {
    return "Let me introduce: " + who;
}
export hello;


2、foo.js 模块

// 从 "bar" 模块导入 hello()
import hello from "bar";
var hungry = "hippo";
function awesome() {
    console.log(
        hello( hungry ).toUpperCase()
    );
}
export awesome;



// 导入完整的 "foo" 和 "bar" 模块
module foo from "foo";
module bar from "bar";
console.log(
    bar.hello( "rhino" )
); // Let me introduce: rhino
foo.awesome(); // LET ME INTRODUCE: HIPPO


模块文件中的内容会被当作好像包含在作用域闭包中一样来处理, 就和前面介绍的函数闭包模块一样。






目录
相关文章
|
14天前
|
存储 缓存 JavaScript
哪些情况适合使用块级作用域,哪些情况适合使用函数作用域?
【10月更文挑战第29天】块级作用域和函数作用域在不同的场景下各有优势,合理地选择和运用这两种作用域可以使JavaScript代码更加清晰、高效和易于维护。在实际开发中,需要根据具体的业务需求、代码结构和编程模式来决定使用哪种作用域,或者在适当的情况下结合使用两者,以达到最佳的编程效果。
|
14天前
|
JavaScript 前端开发
块级作用域和函数作用域有什么区别?
【10月更文挑战第29天】块级作用域和函数作用域在JavaScript中各有特点和用途。块级作用域提供了更精细的变量控制,有助于避免变量提升和意外的全局变量污染等问题;而函数作用域则在函数封装和模块化编程等方面有着重要的应用。在实际开发中,需要根据具体的需求和场景合理地选择使用哪种作用域来声明变量和组织代码。
|
2月前
|
Java
作用域
作用域
19 2
|
2月前
C 作用域详解
在 C 语言中,作用域决定了变量和函数的可见性和生命周期,包括块作用域、函数作用域、文件作用域和全局作用域。块作用域内的变量仅在块内有效,函数作用域内的变量在整个函数内有效,文件作用域内的全局变量和函数在整个文件内有效,而全局作用域内的变量和函数在整个程序运行期间有效。作用域的优先级遵循局部变量优先的原则,局部变量会遮蔽同名的全局变量。变量的生命周期分为局部变量(函数调用时创建和销毁)、全局变量(程序开始时创建和结束时销毁)以及静态变量(整个程序期间有效)。理解作用域有助于避免命名冲突和错误,提高代码的可读性和可维护性。
|
6月前
|
自然语言处理 JavaScript 前端开发
深入理解作用域、作用域链和闭包
在 JavaScript 中,作用域是指变量在代码中可访问的范围。理解 JavaScript 的作用域和作用域链对于编写高质量的代码至关重要。本文将详细介绍 JavaScript 中的词法作用域、作用域链和闭包的概念,并探讨它们在实际开发中的应用场景。
|
设计模式 自然语言处理 JavaScript
一篇文章帮你真正理解javascsript作用域闭包
一篇文章帮你真正理解javascsript作用域闭包
85 0
|
存储 JavaScript 前端开发
深入理解作用域和闭包(上)
深入理解作用域和闭包(上)
深入理解作用域和闭包(上)
|
存储 缓存 JavaScript
深入理解作用域和闭包(下)
深入理解作用域和闭包(下)
深入理解作用域和闭包(下)
|
自然语言处理 JavaScript 前端开发
作用域是什么
作用域是什么
121 0
|
存储 自然语言处理 JavaScript
作用域相关的知识点:闭包、执行上下文、LHS/RHS、词法作用域
作用域相关的知识点:闭包、执行上下文、LHS/RHS、词法作用域
133 0