关于 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
来源: http://www.bubuko.com/infodetail-2690710.html