zoukankan      html  css  js  c++  java
  • JavaScript基础–闭包

    JavaScript基础–闭包

    理解闭包的概念对于学习JavaScript至关重要,很多新手(包括我)开始学习闭包时,都会感觉似懂非懂,之前看了一些资料,整理了闭包的一篇博客,若有疏忽与错误,希望大家多多给意见。

    概述

    理解闭包的概念前,建议大家先回想一下JS作用域的相关知识,如果有疑问的同学,可以参考:JavaScript基础–作用域。闭包的定义如下:

    Closure is when a function is able to remember and access its lexical scope even when that function is executing outside its lexical scope.

    意译出来就是:当函数在其词法作用域外执行时,依然可以访问其词法作用域里的变量。这里的“词法作用域”,就是我们通常理解的作用域。

    我们先来看个例子 
    eg1

    function foo() {
        var a = 2;
    
        function bar() {
            console.log( a ); 
        }
    
        bar();
    }
    
    foo(); //--> 2

    上述例子中,在调用函数bar()时,变量a的值是取自函数foo的作用域,也就是函数bar()的上层作用域,从闭包的概念来说,这个例子基本属于一个闭包。为什么说是“基本”,因为实际上a也是属于函数bar()的作用域链上的变量,我们更多称之为嵌套作用域。我们再看一个例子: 
    eg2

    function foo() {
        var a = 2;
    
        function bar() {
            console.log( a );
        }
    
        return bar;
    }
    
    var test = foo();
    
    test(); // --> 2 
    

     

    eg2的例子也许更能体现闭包的概念:我们在定义函数foo()时,返回的是一个函数;var test = foo();将函数的引用赋值给test,然后在执行test();语句时,我们发现a的值依然能够取到,我们称bar()为一个闭包。

    闭包的原理:编译器在执行var test = foo();时,会标识其为一个闭包,垃圾回收器在回收内存时,就会保留闭包的作用域链。所以运行test()时,就可以访问到闭包所定义的词法作用域了。

    函数setTimeout()

    其实我们自己在写JS代码时,经常用到闭包,只是我们没有意识到,比如setTimeout(): 
    eg3

    function wait(message) {
        setTimeout( function timer(){
            console.log( message );
        }, 1000 );
    }
    
    wait( "Hello, closure!" );

    相信同学们或多或少的用到过setTimeout(),在eg3中我们细心注意,就可以发现我们定义在wait()中的匿名函数是延迟运行的,但它依然可以访问到变量message。对照闭包的概念,是不是就明白了。同样我们在定义很多异步的函数时,都用到了闭包。是不是发现闭包其实我们时时刻刻都在用。

    循环中的闭包

    大家还是先来看一个例子 
    eg4

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

    大家觉得eg4中会输入什么?是1,2,3,4,5吗?如果你把代码赋值到浏览器console面板中,也许会让你失望,代码输出结果为6,6,6,6,6;很多同学觉得每一个i不是单独运行的吗?输出怎么都是6。

    分析这个例子前,我们脑子中要有一个概念:JS应用的是函数作用域,而不是块级作用域。反映到eg4中,就是循环中利用的i是公有的。所以在执行timer()时,i已经变为了6。

    如果应用JS的IIFE(立即执行函数),输出结果还是5个6吗?比如: 
    eg5

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

    我们可以测试一下,结果还是6,6,6,6,6。或者有人认为如果把延迟时间缩小的足够短,结果是不是就可以正常了?实际结果也许会让你失望。我们就算把延迟时间设置为0,结果还是一样的,这是因为for执行效率天生就比setTimeout()高,setTimeout()再怎么缩短延迟时间,也赶不上for

    为了达到我们的期望的结果,解决的办法就是将每次循环的i引入到time()的作用域中,如: 
    eg6

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

    或者是: 
    eg7

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

    扩展:在ES6中引入了let关键字,而引入的目的就是为了在JS中实现块级作用域,所以eg5中代码还可以修改为: 
    eg8

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

    或者 
    eg9

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

    闭包的应用–模块(module)

    模块是应用闭包的典型例子,我们先来看一个例子: 
    eg10

    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

    在eg10中CoolModule是一个函数,返回值为一个对象;那么foo在调用doSomethingdoAnother时,就产生了闭包。这是module中最简单的利用闭包的例子,接下来我们来看一个怎么解决module依赖的例子。 
    eg11 
    定义依赖模块的实现

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

    我们分析一下以上的代码。首先定义了一个空对象module,其次定义了函数define,其中三个参数:name,定义模块的名称;deps,定义模块的依赖项;impl,定义模块的实现方法。

     
    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

    当然ES6中也引入了module,使得调用更加方便,直接看例子吧 
    eg12 
    bar.js

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

    foo.js

    // import only `hello()` from the "bar" module
    import hello from "bar";
    
    var hungry = "hippo";
    
    function awesome() {
        console.log(
            hello( hungry ).toUpperCase()
        );
    }
    
    export awesome;
    

      

    // import the entire "foo" and "bar" modules
    module foo from "foo";
    module bar from "bar";
    
    console.log(
        bar.hello( "rhino" )
    ); // Let me introduce: rhino
    
    foo.awesome(); // LET ME INTRODUCE: HIPPO

    注意在eg12中调用module有两种方式,分别是importmodule,前者调用的是接口,而后者调用的是模块,用法也有些许不同,前者是直接接口本身hello(hungry),而后者则是调用模块中的方法bar.hello("rhino")

    参考文献
  • 相关阅读:
    Ubuntu 修改 ssh 登录后的欢迎信息
    Hbase的配置和安装
    python起的 simpleHTTPServer服务传输文件
    hadoop修改MR的提交的代码程序的副本数
    Fair Scheduler 队列设置经验总结
    调度系统任务创建---创建一个JoinTrigger的依赖任务(五)
    调度系统任务创建---创建一个MultiJob的任务(四)
    调度系统任务创建---创建一个有上下游依赖的任务(三)
    调度系统任务创建---创建一个简单调度任务(二)
    SSM框架新特性关于用Java配置类完全代替XML
  • 原文地址:https://www.cnblogs.com/fengzheqi/p/5132134.html
Copyright © 2011-2022 走看看