首先,变量对于一个程序来说是一个很重要的角色, 那么问题来了 这些变量存在哪里,程序用到的时候如何找到变量呢?
所以需要一套规则来存储变量方便之后再找到 这套规则就成为作用域.


js是一门编译语言,对于js来说 大部分情况下编译发生在代码执行前的几微妙的时间内,
对于参与到一个程序 : var a = 2; 处理的时候,

  • 引擎 : 从头到尾负责整个js程序的编译和执行过程
  • 编译器: 负责的是语法分析和代码生成
  • 作用域: 负责收集并维护所有声明的变量组成的一系列查询,并实施一套非常严格的规则,最后确定当前执行的代码对于这些变量的访问权限

对于 var a = 2; 来说

  • 首先编译器会先询问作用域是否已经有一个该名称a的变量存在同一个作用域的集合中 ,如果有了 编译器就会忽略否则 则会要求作用域在当前作用域集合中声明一个新的变量 名称为a;
  • 然后编译器生成代码来进行a=2 的复赋值操作, 到了引擎工作的时候了 首先会先在作用域中查找a这个变量,如果有,引擎就会对变量a进行LHS查询(LHS查询是试图找到变量本身然后进行赋值操作(等号左边left) | RHS查询是指非等号左边的查询 即要取到某个变量的指),对a进行赋值操作.如果找不到这个变量就会抛出异常了.

(

  • 这里补充一下引擎的LHS查询和RHS查询 : 例如 var b=a; return a+b 这里的 LHS查询有: b=...
    RHS查询有 =a; a...; b...; 对于变量没有声明的情况下,两个查询的行为是不一样的, 对于 b=a
    时,对b=进行LHS查询 会一直向上作用域查找该变量 如果在最顶层还没有找到 那么就会在最顶层创建一个b变量(非严格模式下) ,
    而对于=a进行RHS查询时,如果在所有作用域中找不到该变量就会抛出ReferenceError异常.

)

了解了js工作机制之后,那么对于 "声明提升" 就会恍然大悟了,
例如:

    foo();
    function foo(){
        console.log(a)  //undefined
        var a= 2; 
    }
    //首先编译器先定义声明foo 和 作用域中的a  然后才会到引擎执行代码进行赋值操作,所以才有了以上的输出, 这种效果即是"声明提升"
    

意味着无论作用域中的声明出现在什么地方,都会在代码执行前(编译阶段)进行首先处理,想象成所有的声明都会被移动到各自作用域的顶端,===>即成为 我们所说的提升.

对于作用域有了深入理解了之后 接下来说一说作用域闭包

  • 网上对于闭包的定义太多了 总的来说 闭包实际上就是:
    无论通过任何手段将内部函数传递到所在的此法作用域以外,它都会持有对原始定义作用域的引用,无论在任何处执行这个函数都会使用闭包,
    所以说平时写的代码中
    很多就是闭包只是自己没有发现而已,本质上无论何时何地,如果将(访问他们各自此法作用域的)函数当做第一级的值类型并到处传递,你就会看到闭包在这些函数中的应用了.
    在定时器,事件监听器,ajax请求,web workers..等任务中只要用了回调函数 其实就是在使用闭包!

    几个闭包的例子:
    1. function foo(){
          var a =2;
          function baz(){
              consolo.log( a ) // 2
          }  
          bar(baz)
      }
      function bar(fn){
          fn();
      }
      foo();         //把内部函数baz 传递给bar 当调用这个内部函数时,它涵盖的foo()内部作用域的闭包就可以观察到了,因为能访问到a.
      
    2. function wait(message){
        setTimeout(function timer(){
            console.log(message)
        },1000)
     }
       wait('hello,closure')
       
    3. function setBot(name,selector){
        $(selector).click(function activator(){
            console.log('activating:' + name)
        })
    }
       setBot('test1','#bot1');
       setBot('test2','#bot2');
       

ok,还有其他的代码模式利用了闭包的作用,表面上看似乎和回调函数无关,这就是 模块

对于模块模式,需要的两个必要条件就是

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

    举个例子(单例模式)
    var foo = (function Moudle(){
           var something = 'oye';
           var other = [1,2,3];
           function dosomething(){
               console.log(something);
           }
           function doother(other){
               console.log(other.join('!'));
           }
           return {
               doSomething : dosomething,
               doother:doother
           }
    })();
    foo.dosomething(); //oye
    foo.doother(); // 1!2!3
    
    //模块也是普通的函数 所以也可以接受参数.

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


    var foo = (function moudel(id){
        function change(){
            //修改公共API
            public.one = two;
        }
        function one(){
            console.log(id);
        }
        function two(){
            console.log(id.toUpperCase());
        }
        var API = {
            change:change,
            identify:one
        }
        return API;
    })('foo moudel')

    foo.identify(); //foo moudel;
    foo.change();
    foo.identify(); // FOO MOUDEL;
    //通过在模块实例的内部保留对公共API对象的内部引用,可以从内部对实例进行修改,包括添加或者删除方法和属性,以及修改值.

对于现代的模块机制
本质上都是将这种模块定义封装进一个友好的API


    //模块管理工具,MyModules
                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
                    }
                })();
                //定义一个模块,data
                MyModules.define("data",[],function(){
                 
                    function getName(name){
                        return '我的名字是'+name;
                    }
                    return {
                        getName:getName
                    }
                });
                //定义一个模块,app
                //该模块依赖data模块
                MyModules.define("app", ["data"], function(data){
                    var newName = 'zhangyu'
                    function run(){
                        console.log(data.getName(newName).toUpperCase());
                    }
                    return {
                        run:run
                    }
                });
                //取出模块
                var data = MyModules.get("data");
                var app = MyModules.get("app");
                console.log(data.getName('zy'))  //我的名字是zy
                app.run();   //我的名字是ZY
            
app和data 模块都是通过一个返回公共API的函数来定义的  app接受data的实例作为依赖参数,并使用.

未来的模块机制
即ES6为模块增加的以及语法支持, 在通过莫魁岸系统进行加载时,ES6会将文件当做独立模块来处理.每个模块都可以导入其他模块或者特定的API成员,同样也可以导出自己的API成员.
(
因为函数的模块并不是一个能被静态识别的模式(编译器无法识别,)他们的API语义只有在运行时才被考虑进来.
相比之下 ES6模块API是静态的,编译器知道这一点 然后在编译器会对模块导出的API或者成员的引用做一次检查是否存在,不存在就会抛出早期的错误 不会等到运行期在动态解析
)


    bar.js
    
        function hello(who){
            return '我是'+who
        }
        export hello;
        
        
    foo.js
    
        import hello from "bar"
        var new = 'marry';
        function newHello(){
            console.log(
                hello(new).toUpperCase(); 
            )
        }
        export awesome;
        
    baz.js
    
        module foo from "foo";
        module bar from "bar";
        console.log(bar.hello('lil')) //我是lil
        foo.newHello();    //我是MARRY;
        
    // import 可以将一个模块的一个或者多个API导入到当前作用域中,并分别绑定在一个变量上. module会将整个模块的API导入并绑定.  export会将当前模块的一个标识符(变量 函数) 导出为公共APII

总结:
当函数可以记住并且能访问所在语法作用域,即使函数实在当前作用域之外执行,这时候就产生了闭包.
对于模块首先必须有外部的封闭函数,该函数必须被至少调用一次,(每次调用都会创建一个新的模块实例)
封闭函数必须返回至少一个内部函数,这样内部函数才能在私有作用域中形成闭包,并且可以访问或者修改私有的状态


2000exists
5 声望2 粉丝