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


    JavaScript闭包。是JS开发project师必须深入了解的知识。

    3月份自己曾撰写博客《JavaScript闭包》。博客中仅仅是简单阐述了闭包的工作过程和列举了几个演示样例,并没有去刨根问底。将其弄明确。

    如今随着对JavaScript更深入的了解,也刚读完《你不知道的JavaScript(上卷)》这本书,所以乘机整理下。从底层和原理上去刨一下。

    JavaScript并不具有动态作用域,它仅仅有词法作用域。

    词法作用域是在写代码或者说定义时确定的。而动态作用域是在运行时确定的。

    了解闭包前。首先我们得知道什么是词法作用域(作用域是由书写代码时函数声明的位置来决定的)。


    一、何为闭包

    演示样例1:

    function foo(){
    	var a = 2;
    	function bar(){
    		console.log(a);
    	}
    	return bar;
    }
    var baz = foo();
    baz(); //2
    在foo()运行后,通常觉得垃圾回收机制会将foo()的整个内部作用域都被销毁;而闭包能够阻止这样事情发生,让其内部作用域依旧存在。

    由于bar()处于foo()内部。它拥有涵盖foo()作用域的闭包,使得该作用域能够一直存活,以供bar()在之后不论什么时间进行引用。



    bar()依旧持有对该作用域的引用,而这个引用就叫作闭包
    简言之:当函数能够记住并訪问所在的词法作用域,即使函数是在当前词法作用域之外运行,这时就产生了闭包。

    演示样例2:

    不管使用何种方式对函数类型的值进行传递,当函数在别处被调用时都能够观察到闭包。
    function foo(){
    	var a = 2;
    	function baz(){
    		console.log(a);
    	}
    	bar(baz);
    }
    
    function bar(fn){
    	fn();	// 这就是闭包
    }

    演示样例3:

    将一个内部函数(timer)传递给setTimeout。timer具有涵盖wait()作用域的闭包,保有对变量message的引用。


    wait()运行1000毫秒后,它的作用域并不会消失,timer依旧保有wait()作用域的闭包。


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

    演示样例4:

    下述activator()具有涵盖setupBot()作用域的闭包。
    function setupBot(name, selector){
    	$(selector).click(function activator(){
    		console.log("Activating: "+ name);
    	});
    }
    setupBot("Closure Bot 1", "#bot_1");
    setupBot("Closure Bot 2", "#bot_2");

    二、循环和闭包

    for(var i=1; i<=5; i++){
    	setTimeout(function timer(){
    		console.log(i);
    	}, i*1000);
    }
    // 期望:每秒一次的频率输出1~5
    // 结果:每秒一次的频率输出五次6
    先解释一下:“i*1000”,5个定时分别在1s、2s、3s、4s、5s后运行,并非1s、3s、6s、10s、15s。

    也就是频率为1s,不是每次间隔添加1s。假设去掉i写成“1000”,会在for运行完1s后直接输出五次6。

    回调函数在循环结束后才被运行,因此输出的是循环终止条件是i值。其实。当定时器运行时即使每一个迭代中运行的是setTimeout(..., 0)。全部的回调函数依旧是在循环结束后才被运行。


    依据作用域的工作原理,虽然五个函数是在各个迭代中分别定义的。可是它们都被封闭在一个共享的全局作用域中,因此实际上仅仅有一个i。


    解决方式1:

    for(var i=0; i<=5; i++){
    	(function(j){
    		setTimeout(function timer(){
    			console.log(j);
    		}, j*1000 );
    	})(i);
    }
    // 结果:每秒一次的频率输出1~5
    每一个迭代都生成一个新的作用域,使得延迟函数的回调能够将新的作用封闭在每一个迭代内部,每一个迭代中都会含有一个具有正确值的变量供我们訪问。

    解决方式2(ES6):

    for(var i=0; i<=5; i++){
    	let j = i;
    	setTimeout(function timer(){
    		console.log(j);
    	}, j*1000 );
    }
    // 结果:每秒一次的频率输出1~5


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

    三、模块

    模块须要具备两个必要条件:

    (1)必须有外部的封闭函数,该函数必须至少被调用一次(每次调用都会创建一个新的模块实例)。

    (2)封闭函数必须返回至少一个内部函数,这样内部函数才干在私有作用域中形成闭包,而且能够訪问或者改动私有的状态。

    典型的模块化:

    function CoolMoudle(){
    	var something = "cool";
    	var doSomething = function(){
    		console.log(something);
    	}
    	return{
    		doSomething: doSomething
    	};
    }
    var foo = CoolMoudle();	//假设不运行外部函数CoolMoudle(),内部作用域和闭包都无法创建
    foo.doSomething();	//cool

    单例模式:

    var foo = (function CoolModule(id){
    	function change(){
    		// 改动公共API
    		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

  • 相关阅读:
    JS 判断一个字符串是否包含在一个数组中
    CSS溢出文本省略(text-overflow)
    C++ const
    Neural Network Virtual Machine
    RFCN配置参数
    推荐系统实战(1)
    决策树
    神经网络之全连接层详解
    [专题论文阅读]【分布式DNN训练系统】 FireCaffe
    不见了的一块钱
  • 原文地址:https://www.cnblogs.com/llguanli/p/8379410.html
Copyright © 2020-2023  润新知