JavaScript语法之词法作用域

关于js基本的包括词法作用域和模块基础的总结。
 
1 编译器---作用域---引擎
 
编译器负责分析及代码生成,作用域负责维护好所有的标识符(变量)组成的一系列查询,引擎负责按照作用域规定的规则执行代码。
 
所以,作用域相当于中介,先是编译器编译,作用域维护,然后引擎按照作用域来执行。所以虽然js是解释型语言,但实际上仍然是先编译再执行。
 
引擎在执行时采取LHS查询和RHS查询。按我理解,LHS查询就是查询“容器”,即装载数据的变量。RHS查询的是变量值本身。
 
 
2 遍历嵌套作用域链的规则
 
引擎从当前知道执行作用域开始查找变量,如果找不到就向上一级查找。当抵达最外层的全局作用域时,无论是否找到,查找过程都会停止。
 
 
3 js遵循的是词法作用域。
 
一个函数的作用域取决于其定义时所处的定义域,而与其在哪里如何被调用无关。(函数申明提升,那也是在它定义时所处的作用域中提升,所以仍在同一个作用域中,没有影响)
 
为避免函数污染所在的作用域(比如说一个函数只需要运行一次,之后就不需要了),那么可以用(function foo(){})(),即IIFE
 
除了函数,还可以用{let a =1;}即let和大括号来显式创建块作用域。其中,let的作用就是让let 的变量只能在它所处的这个块中使用,块以外的作用域用不了。
 
为变量显式声明块作用域,即把某些变量放进{}里,用let声明,并对变量进行本地绑定,这样当这个变量被使用完毕后,数据会被垃圾回收。什么意思呢?就是说把一个数据主动放入块作用域中,即外面不能用,这样垃圾回收机制就知道,这个数据一旦被用完,块作用域外面的作用域就再也用不到(也用不了)它了,所以可以直接被垃圾回收。这样减少内存占用。
 
 
4 关于提升
 
包括变量和函数在内的所有声明都会在编译阶段被处理,即声明提升。而赋值或其他运行逻辑会留在原地,等待执行阶段。之前已经知道,js是先编译后执行,所以可以知道是先有声明,后有赋值。
函数声明和变量声明都会被提升,但是函数声明会首先被提升,即被提升到各自作用域最顶部,超过变量声明。
 
 
5 闭包
 
由于js是遵循词法作用域的,所以函数在别的某个地方(不在其所在的作用域中)被调用(以将函数本身return出去的方式或者别的无论什么方式),而执行的时候是遵循其作用域的,这样就形成了闭包。
不好理解的地方是,函数整个被当做值类型并导出传递,无论传递到哪里去调用,由于遵循词法作用域,运行时还是依据定义该函数的位子所在的定义域。或者可以理解为,function aa(){.....} 这整个函数最开始定义在哪里,就遵循那里的作用域。
当函数可以记住并访问所在的词法作用域,即使函数是在当前词法作用域之外执行,这时就产生了闭包。
只要使用了回调函数,实际上就是在使用闭包。(原因是这个函数整个在某个异步参数中被定义,而在别的线程中被调用,那么运行时所依据的当然是定义时的位置所处的作用域链)
 
for (let i=1; i<=5; i++) {
setTimeout( function timer() {
console.log( i );
}, i*1000 );
}
这是闭包和块作用域的联手。闭包指的是setTimeout异步,块作用域是指用let。在for循环里调用let即说明每次的i都是重新声明的。
 
其实,闭包就是函数定义在作用域A上,而在作用域A外的某个作用域调用。
 
 
6 关于模块
 
模块就是利用了闭包。按我理解,就是一个函数,里面先写好内部函数和变量,即闭包,然后将函数return出来。每次先调用外层函数,获得里面的函数,在外面调用。
 
模块模式必须具备两个条件:1 必须有外部的封闭函数,该函数必须至少被调用一次(每次调用都会创建一个新的模块实例)2 封闭函数必须返回至少一个内部函数,这样内部函数才能在私有作用域中形成闭包,并且可以访问或者修改私有变量。
 
模块的例子:
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);
        console.log(modules);
    }
 
    function get(name){
        return modules[name];
    }
 
    return {
        define:define,
        get:get
    }
})();
 
首先,这是一个叫做Manager的模块创造器,立即调用后将API接口暴露出来赋值给MyModules,这样就只执行一次,即为单例模式。
这个Manager模块里有一个modules,这是放各个模块的对象。define即包装函数用来定义新的模块。传入3个参数,分别是name,即新模块的名字;deps,即该模块所需要依赖的其他模块(注意,只是该新模块定义时所需要的模块而不是所有模块,用数组的方式传入,数组里面的每一项是所需的依赖模块的名字);impl,即新模块的定义。
define函数先进行一个遍历,目的是将deps中的模块从modules中取出,放入deps中。
modules[name] = impl.apply(impl,deps);意思是将impl的运行上下文绑定到自己的函数里面,传入依赖deps并运行,将结果储存在以传入的name命名的modules中。
(注,这里用apply函数,我个人认为是利用了apply函数的一个很方便的特性,那就是可以将数组直接拆成一个个元素传入函数)
接下来是调用:
 
MyModules.define('bar',[],function(){
    function hello(who){
        return 'nihao '+ who;
    }
    return {
        hello:hello
    }
});
 
MyModules.define('bye',[],function(){
    function bye(who){
        return 'bye '+who;
    }
    return {
        bye:bye
    }
});
 
MyModules.define('foo',['bye','bar'],function(){
    var who= 'Mike';
    var args = arguments;
    function awesome(){
        console.log(args[0].bye(who)); //用apply运行函数时可以用arguments来获取参数,这样可不必列出所有形参
        console.log(args[1].hello(who));
    }
    return {
        awesome:awesome
    }
});
 
var foo = MyModules.get('foo');
foo.awesome();// bye Mike   nihao Mike
 
 
欢迎互相交流,互相学习。前端开发QQ群:711357426

猜你喜欢

转载自www.cnblogs.com/emptylee/p/9332805.html