首先,变量对于一个程序来说是一个很重要的角色, 那么问题来了 这些变量存在哪里,程序用到的时候如何找到变量呢?
所以需要一套规则来存储变量方便之后再找到 这套规则就成为作用域.
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,还有其他的代码模式利用了闭包的作用,表面上看似乎和回调函数无关,这就是 模块
对于模块模式,需要的两个必要条件就是
- 必须有外部的封闭函数,该函数必须被至少调用一次,(每次调用都会创建一个新的模块实例)
- 封闭函数必须返回至少一个内部函数,这样内部函数才能在私有作用域中形成闭包,并且可以访问或者修改私有的状态!
举个例子(单例模式)
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
总结:
当函数可以记住并且能访问所在语法作用域,即使函数实在当前作用域之外执行,这时候就产生了闭包.
对于模块首先必须有外部的封闭函数,该函数必须被至少调用一次,(每次调用都会创建一个新的模块实例)
封闭函数必须返回至少一个内部函数,这样内部函数才能在私有作用域中形成闭包,并且可以访问或者修改私有的状态
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。