《你不知道的JavaScript》 作用域闭包

一、什么是闭包

function foo() {
    var a = 2;
   //函数bar( )的词法作用域能访问函数foo( )的内部作用域。将bar( )函数当做值传递。
   //bar( )在foo( )内声明,它拥有涵盖foo( )内部作用域的闭包,使得该作用域能一直存活,供bar( ) 在之后任何时间引用。bar( )本身在使用foo( )的内部作用域,因此foo执行后不会被销毁。
    function bar(){
        console.log( a );
    }
    return bar;
}

//bar( )可以正常运行,而且是在自己的词法作用域以外执行。
var baz = foo();

//foo( )执行后,其返回值bar()函数赋值给变量baz,并调用baz( ),实际上是调用了内部的函数bar( )。
baz();

bar( )依然持有对该作用域的引用,这个引用叫作闭包

无论通过任何手段将内部函数传递到所在词法作用域以外的,它都会有对原始定义作用域的引用,无论在何处执行这个函数都会产生闭包。

function foo() {
    var a = 2;

    function baz() {
        console.log( a ); // 2
    }

    bar( baz );
}

function bar(fn) {
    fn(); // 闭包
}
var fn;

function foo() {
    var a = 2;

    function baz() {
        console.log( a );
    }
    fn = baz();
}

function bar() {
    fn(); // 闭包
}

foo();
bar();

将内部函数timer传递给setTImeout,timer涵盖wait作用域的闭包,因此还保有对message的引用。wait执行1000毫秒后,它的内部作用域不会消失,timer函数还保有wait作用域的闭包。

function wait(message) {
    for(var i = 0; i <= 5; i++){
    setTimeout( function timer() {
        console.log(i);
    }, i*1000);
    }
}

如果将(访问它们各自词法作用域的)函数当做第一级的值类型并到处传递,就能看到闭包在这些函数的应用。在定时器、事件监听器、Ajax请求、跨窗口通信、Web Workers或任何其他的异步(同步)

任务中,只要使用了回调函数,就是在使用闭包。

function setup(name, selector) {
    $( selector ).click( function activator( ) {
        console.log( "Activating: " + name );
    });
}

setupBot( "Closure Bot 1", "#bot_1" );
setupBot( "Closure Bot 2", "#bot_2" );

函数IIFE并不是在本身的词法作用域以外执行,在它定义时所在的作用域执行。a是通过普通的词法作用域查找而非闭包被发现的。尽管IEFF本身不少观察闭包的恰当例子,但它的确创建了闭包,

并且也是最常用来创建可以被封闭起来的闭包的工具。

var a = 2;
(function IIFE( ) {
    console.log( a );
}());

循环和闭包

for(var i = 0; i <= 5; i++){
    setTimeout( function() {
        console.log(i);
    }, i*1000);
}

// 每秒一次输出5个6

延迟函数的回调会在循环结束后才执行。当定时器运行时即使每个迭代中执行的是setTimeout(..., 0),所有的回调函数依然在循环结束后执行。

根据作用域原理,尽管循环的五个函数在各个迭代分别定义,但它们都被在全局作用域,实际只有一个i。

需要更多的闭包作用域,特别是在循环的过程中每次迭代都需要闭包作用域。

//这个例子不能实现
for (var i = 0; i <= 5; i++){
     (function( ) {
          setTimeout( function() {
               console.log(i);
          }, i*1000);
     }())
}
//每个延迟函数都会在IIFE在每次迭代中创建的作用域封闭
//但这里的是空的作用域

它需要有自己的变量,用来在每个迭代中存储 i。

for(var i = 0; i <= 5; i++){
    (function(){
        var j = i;
        setTimeout( function() {
            console.log(j);
        }, j*1000);
    }())
}

稍加改进

for(var i = 0; i <= 5; i++){
    (function(j){
        setTimeout( function() {
            console.log(j);
        }, j*1000);
    }(i))
}

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

使用块作用域

for(var i = 0; i <= 5; i++){
    let j = i;  //闭包的块作用域
    setTimeout( function timer() {
        console.log(j);
    }, j*1000);
}

for循环的let声明有一个特殊行为,变量在循环过程不止声明一次,每次迭代都会声。随后的每次迭代都会用上一个迭代结束时的值来初始化这个变量。

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

模块

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

这种模式被成为模块,最常见的实现模块方式被成为模块暴露,这里的是其变体。

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

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

这个对象类型的返回值最终被赋值给外部的变量foo,然后就可以通过它来访问API的属性方法。

  • 从模块返回实际的对象不少必须的,可以返回一个内部函数,如jQuery。

模块模式需要具备两个必要条件。

  1. 必须有外部的封闭函数,该函数必须至少被调用一次(每次调用都会创建一个新的模块实例)。
  2. 封闭函数必须返回至少一个内部函数,这样内部函数才能在私有作用域中形成闭包,并且可以访问或修改私有的状态。

当只需一个实例时,可以实现单例模式:

var foo  = (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
    };
}());

foo.doSomething(); //cool
foo.doAnother(); // 1 ! 2 ! 3

模块也是普通的函数,因此可以接受参数:

function coolModule(id) {
    function identify() {
        console.log( id );
    }

    return {
        identify: identify
    };
}

var foo1 = coolModule( "foo 1" );
foo1.identify();  // "foo 1"

模块模式另一个简单但强大的用法是命名将要作为公共API返回的对象:

var foo = (function coolModule(id){
    function change(){
        publicAPI.identify = identify2;
    }

    function identify1() {
        console.log( id );
    }

    function identify2() {
        console.log( id.toUpperCase() );
    }

    var publicAPI = {
        change: change,
        identify: identify1
    }

    return publicAPI;
}("foo module"));

foo.identify();  //  foo module
foo.change();
foo.identify();  //  FOO MODULE

现代的模块机制

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
    }
}());

这段代码的核心是 modules[name] = impl.apply( impl, deps)。为了模块的定义引入了包装函数,并将返回值(模块API)存储在一个根据名字来管理的模块列表中

//定义模块
MyModules.define( "bar", [], function() {
    function hello(who) {
        return "Let me introduce: " + who;
    }

    return {
        hello: hello
    };
});

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" ));
foo.awesome();

foo和bar模块都是通过一个返回公共API的函数来定义的。“foo”甚至接受“bar”的实例作为依赖参数,并能相应地使用它。

时间: 2024-08-14 00:26:58

《你不知道的JavaScript》 作用域闭包的相关文章

JavaScript作用域闭包(你不知道的JavaScript)

JavaScript闭包,是JS开发工程师必须深入了解的知识.3月份自己曾撰写博客<JavaScript闭包>,博客中只是简单阐述了闭包的工作过程和列举了几个示例,并没有去刨根问底,将其弄明白! 现在随着对JavaScript更深入的了解,也刚读完<你不知道的JavaScript(上卷)>这本书,所以乘机整理下,从底层和原理上去刨一下. JavaScript并不具有动态作用域,它只有词法作用域.词法作用域是在写代码或者说定义时确定的,而动态作用域是在运行时确定的.了解闭包前,首先我

javascript 作用域 闭包 对象 原理和示例分析(上)

                                                                                             阅读.理解.思考.实践,再实践.再思考....  深圳小地瓜献上 javascript高级特性包含:作用域.闭包.对象 -----------------------------------------------作用域-----------------------------------------------

JavaScript作用域闭包简述

作用域 技术一般水平有限,有什么错的地方,望大家指正. 作用域就是变量起作用的范围.作用域包括全局作用域,函数作用域以块级作用域,ES6中的let和const可以形成块级作用域. 除了块级作用域,在函数外面声明的变量可以在任何一个地方被访问到,这些变量的作用域都是全局作用域,全局作用域中的变量可以再任何一个地方使用: var a = "zt"; function fn1(){ console.log(a); } function fn2(){ console.log(a); } fn1

《你不知道的JavaScript》整理(一)——作用域、提升与闭包

最近在读一本进阶的JavaScript的书<你不知道的JavaScript(上卷)>,里面分析了很多基础性的概念. 可以更全面深入的理解JavaScript深层面的知识点. 一.函数作用域 1)函数作用域 就是作用域在一个“Function”里,属于这个函数的全部变量都可以在整个函数的范围内使用及复用. function foo(a) { var b = 2; function bar() { // ... } var c = 3; } bar(); // 失败 console.log( a,

你不知道的JavaScript(作用域和闭包)

作用域和闭包 ?作用域 引擎:从头到尾负责整个JavaScript的编译及执行过程. 编译器:负责语法分析及代码生成等. 作用域:负责收集并维护由所有声明的标识符(变量)组成的一系列查询,并实施一套非常严格的规则,确定当前执行的代码对这些标识符的访问权限. 作用域是一套规则,用于确定在何处以及如何查找变量(标识符). 如果查找的目的是对变量进行赋值,那么就会使用LHS查询: 如果目的是获取变量的值,就会使用RHS查询. ?词法作用域 无论函数在哪里被调用,也无论它如何被调用,它的词法作用域都只由

你不知道的Javascript(上卷)读书笔记之一 ---- 作用域

你不知道的Javascript(上卷)这本书在我看来是一本还不错的书籍,这本书用比较简洁的语言来描述Js的那些"坑",在这里写一些博客记录一下笔记以便消化吸收. 1 编译原理 在此书中,开始便提出:Javascript是一门编译型语言,我一开始以为这是国内翻译的锅,翻译的不够准确,后来我还专门去github看了,作者确实是将Js描述为一门编译型语言(compiled language).然而我认为作者更想表达的是Js也拥有和Java一般的特定的编译过程.而不是传统得认为只是单纯的进行&

JavaScript作用域、上下文环境、函数对象的定义与调用、匿名函数的定义与调用、闭包

提到闭包总给人很高深的感觉,网上的例子也数不胜数.但是我发现相当一部分并不容易理解.根据我的观察,是因为这些例子把标题中提到的概念糅杂在了一起,往往越看越糊涂.所以我希望化整为零,拆成简单例子来解释. 1.先看作用域: JavaScript作用域只有两种--全局作用域和函数内作用域,没有代码块作用域.示例: function loop(){ for(var i=0;i<5;i++){ //doSomething; } alert(i); } loop(); //执行函数结果为5. 尽管变量i已经

举例详细说明javascript作用域、闭包原理以及性能问题(转)

这可能是每一个jser都曾经为之头疼的却又非常经典的问题,关系到内存,关系到闭包,关系到javascript运行机制.关系到功能,关系到性能. 文章内容主要参考自<High Performance JavaScript>,这本书对javascript性能方面确实讲的比较深入,大家有空都可以尝试着阅读一下,我这里有中英电子版,需要的话QQ317665171或者QQ邮箱联系. 复习,笔记,更深入的理解. 欢迎拍砖指正. 作用域: 下面我们先搞明白这样几个概念: 函数对象的[[scope]]属性.S

《你不知道的JavaScript》系列分享专栏

<你不知道的JavaScript>系列分享专栏 你不知道的JavaScript"系列就是要让不求甚解的JavaScript开发者迎难而上,深入语言内部,弄清楚JavaScript每一个零部件的用途 <你不知道的JavaScript>已整理成PDF文档,点击可直接下载至本地查阅https://www.webfalse.com/read/205515.html 文章 你不知道的JavaScript--Item3 隐式强制转换 你不知道的JavaScript--Item4 基本

JavaScript作用域链

JavaScript作用域链 之前写过一篇JavaScript 闭包究竟是什么的文章理解闭包,觉得写得很清晰,可以简单理解闭包产生原因,但看评论都在说了解了作用域链和活动对象才能真正理解闭包,起初不以为然,后来在跟公司同事交流的时候发现作用域和执行环境确实很重要,又很基础,对理解JavaScript闭包很有帮助,所以在写一篇对作用域和执行环境的理解. 作用域 作用域就是变量和函数的可访问范围,控制着变量和函数的可见性与生命周期,在JavaScript中变量的作用域有全局作用域和局部作用域. 单纯