JS学习梳理(一)作用域和闭包
- 编译特点:实时编译、执行。性能保证:JIT、延迟编译、重编译
- 编译构成
- 引擎
- 编译器
- 作用域
- 执行顺序:编译阶段 -> 执行阶段
- 编译阶段:函数声明和变量声明都会被提升
- 编译阶段:函数提升先于变量
- 编译阶段:同名函数或变量会被覆盖
- 欺骗词法(非严格模式)
- eval('str'),性能低
- with,未找到匹配属性时,易自动创建成全局变量
- 函数作用域和块作用域
- 立即执行函数
(function foo() {})();
- let(ES6),块作用域
{ console.log( bar ); // ReferenceError! let bar = 2; }
function process(data) { // 在这里做点有趣的事情 } // 在这个块中定义的内容可以销毁了! { let someReallyBigData = { .. }; process( someReallyBigData ); } var btn = document.getElementById( "my_button" ); btn.addEventListener( "click", function click(evt){ console.log("button clicked"); }, /*capturingPhase=*/false );
- const(ES6),块作用域,被修改后会报错
- 立即执行函数
- 闭包
//我们对这段代码行为的预期是分别输出数字1~5,每秒一次,每次一个。但实际上,这段代码在运行时会以每秒一次的频率输出五次6
for (var i=1; i<=5; i++) { setTimeout( function timer() { console.log(i); }, i*1000 ); }
//修改之后,通过立即执行函数来创建作用域。注意i,j之间的值传递。
for (var i=1; i<=5; i++) {
(function(j) {
setTimeout( function timer() {
console.log(j);
}, j*1000 );
})(i);
}//for 循环头部的let 声明还会有一个特殊的行为。这个行为指出变量在循环过程中不止被声明一次,每次迭代都会声明。随后的每个迭代都会使用上一个迭代结束时的值来初始化这个变量。
for (let i=1; i<=5; i++) {
setTimeout( function timer() {
console.log( i );
}, i*1000 );
} - 模块
- 最常见的实现模块模式的方法通常被称为模块暴露
- ES6 的模块没有“行内”格式,必须被定义在独立的文件中(一个文件一个模块)
- 动态作用域
- JavaScript 并不具有动态作用域,它只有词法作用域,但是this 机制某种程度上很像动态作用域
- 主要区别:词法作用域是在写代码或者说定义时确定的,而动态作用域是在运行时确定的。(this 也是!)词法作用域关注函数在何处声明,而动态作用域关注函数从何处调用。
- 其他
- Google 维护着一个名为Traceur 的项目,用来将ES6 代码转换成兼容ES6 之前的环境。
- “胖箭头”
var obj = { count: 0, cool: function coolFn() { if (this.count < 1) { setTimeout(() => { // 胖箭头语法 this.count++; console.log("awesome?"); }, 100); } } }; obj.cool(); // awesome?
- bind
var obj = { count: 0, cool: function coolFn() { if (this.count < 1) { setTimeout(function timer() { this.count++; // this 是安全的 // 因为bind(..) console.log("more awesome"); }.bind(this), 100); // look, bind()! } } }; obj.cool(); // more awesome