在看书的过程中,发现了很多之前不知道的点,感觉明白了这些点以后,对代码的运行有了更深刻的体会,故做此笔记,以温故并巩固。(持续更新...)
一、for循环中的变量
for(var i = 0; i < 10; i++){
setTimeout(()=>{
console.log(i);
},i*1000);
}
for(let i = 0; i < 10; i++){
setTimeout(()=>{
console.log(i);
},i*1000);
}
这两段代码的运行结果,想必大家都是知道的,毕竟这是一道流行了好多年的喜闻乐见的前端笔试题;那我们就来看下书中对这个结果的解释:
第一个循环,会console出10个10,首先解释下10是怎么来的,这个循环的终止条件是 i 不再小于 10; 故当 i = 10 的时候,循环终止;再来看,为什么是10个10,
而不是我们预期的1~10?这是因为延迟函数(setTimeout)的回调会在循环结束时才执行。
这里引申出一个更深入的问题,代码中到底有什么缺陷导致它的行为同语义所暗示的不一致呢?
缺陷是我们试图假设循环中的每个迭代在运行时都会给自己捕获一个i的副本。但是根据作用域的工作原理,实际情况是尽管循环中的函数是在各个迭代中分别定义的,
但是它们被封闭在一个共享的全局作用域中,因此实际只有一个i;
第二个循环,会按照我么的预期,每秒输出一次 i 值;两者的不同之处就是循环中 i 值的定义,一个是 var ; 一个是 let;那 我们就看下书中对 let 的解释吧:
let声明可以用来劫持块作用域,并且在这个块作用域中声明一个变量。for 循环头部的 let 声明还会有一个特殊的行为,这个行为指出变量在循环过程中不止被声明一次,
每次迭代都会声明。随后每个迭代都会使用上一个迭代结束时的值来初始化这个变量。
二、模块的一些概念
首先看一个简单的模块代码:
function coolModule(){ var something = "cool"; var another = [1,2,3]; function doSomething(){ console.log(something); } function doAnother(){ console.log(another.join(",")); } return { doSomething: doSomething, doAnother: doAnother } } var cool = coolModule(); cool.doSomething(); cool.doAnother();
首先,coolModule只是一个函数,必须要通过调用它来创建一个模块实例。如果不执行外部函数,内部作用域和闭包都无法创建。
其次,coolModule()返回一个用对象字面量语法{key:value,...}来表示的对象。这个返回的对象中含有对内部函数而不是内部数据变量的引用。
我们保持内部数据变量是隐藏且私有的状态。可以将这个对象类型的返回值看作本质上是模块公共的API。
以上两点即为一个模块模式需要具备的两个必要条件。
如果我们希望创建的模块为单例模式,只需加一个立即执行即可:
var cool = (function coolModule(){ var something = "cool"; var another = [1,2,3]; function doSomething(){ console.log(something); } function doAnother(){ console.log(another.join(",")); } return { doSomething: doSomething, doAnother: doAnother } })(); cool.doSomething(); cool.doAnother();
下面我们再来看下一个简单的模块机制:
var MyModules = (function(){ var modules = {}; //模块列表 function define(name, deps, impl){ for(var i = 0; i < deps.length; i++){ //把依赖模块从modules中取出来; deps[i] = modules[deps[i]]; } //把当前模块存入模块列表,deps当参数传入 modules[name] = impl.apply(impl, deps); } function get(name){ return modules[name]; } return { define: define, get: get } })(); //加一个立即执行函数,使MyModules成为单例 //定义一个name为bar的模块,且没有任何依赖 MyModules.define('bar', [], function(){ function hello(who){ return "let me introduce: " + who; }
//暴露出模块中的hello方法 return { hello: hello } }); //定义一个name为foo的模块,该模块依赖于bar模块; MyModules.define('foo', ['bar'], function(bar){
//bar模块被当做参数传入,这时候,通过bar. 的方式,可以拿到bar模块中暴露出去的方法和属性,如:hello方法 var hungry = 'hippo'; function awesome(){ console.log(bar.hello(hungry).toUpperCase()); }
//暴露出awesome方法 return { awesome: awesome } }); var bar = MyModules.get('bar'); var foo = MyModules.get('foo'); var str = bar.hello('hippo'); console.log(str); foo.awesome();
以上就是一个简单的模块化封装。
下面我们来总结下ES6模块化的一些特点:
1、ES6的模块API是静态的,因此,在编译期会检查导入模块的API成员的引用是否真实存在,不存在会报错。
2、ES6的模块也没有“行内”格式,必须被定义在独立的文件中。
3、模块加载命令import来加载一个模块时
,不会去执行模块,而是只生成一个动态的只读引用。等到真的需要用到时,再到模块里面去取值。
因此,ES6模块是动态引用,并且不会缓存值,模块里面的变量绑定其所在的模块。
4、如果希望import可以执行所加载的文件or模块,可以这样写: import “模块名or文件路径”;
5、由于import
是静态执行,所以不能使用表达式和变量,这些只有在运行时才能得到结果的语法结构。
三、关于this(下期分享)