前置:
执行上下文
执行上下文EC(exec context) 就是当前代码的执行环境、作用域,和作用域链相辅相成。
ec = variable object变量对象 + 作用域链 sc + this
vo是一个底层概念,前端拿不到。
假定存在这个代码
function fn(a,b){ return a+b }
let r = fn(1,2)
todo 下面的描述需要整理
在这段代码中:
- fn是全局变量,在全局上下文中
- 调用fn。
- 创建vo的sc,
- 会创建一个ao,里面有arguments/a/b,作为sc上的第一个对象
- this/fn/r 是第二个对象
作用域和作用域链
内部能访问外部,外部不能访问内部,这就是作用域。
内部使用了一个属性,内部找不到就一层层向上查找,最终找到或者报错。这就是作用域链。
如果使用 try/catch的catch eval with 会修改作用域。工作中用不到,忽略即可。
变量提升
console.log(a) // undefined var a 提升
var a =1
fn(1) //1 function提升
function fn(v){}
js在执行之前,会先预编译,把即将执行的变量和函数声明都拿出来:变量先给undefined,函数先声明等待使用。预编译完了才开始执行。
ES6增加了let和const,产生了块级作用域,作用域有了新的概念。由此,有了一个 暂时性死区Temporal Dead Zone 的概念:
在函数开头,到相关变量声明语句所在行,这个范围内不能使用let和const声明的变量。
function bar1(){
console.log(aa) // tdz
let aa='fff'
console.log(aa)
}
闭包
在案例中,A函数执行返回新的函数B。
- B通过作用域,可以访问自身变量、A的变量。
- A函数执行,得到函数B,函数B继续执行,依然可以访问应该被销毁的A函数内容
- 这也意味着A的活动对象没有被销毁,因为B还引用着。
- 因为闭包会保留函数作用域,会占内存
function F1(){
var a =1
return function(){
console.log(a)
}
}
var f1 = F1()
var a = 200
f1()
闭包有两个应用场景:
- 函数作为返回值
- 函数作为参数传递
给大家开个眼
function a(){
let a = 1
window.b = function(){
console.log(a)
}
}
a()
b()
注意,很多人误认为闭包就是,函数嵌套函数,然后返回函数,这是有误解。闭包的作用,让我们间接访问函数内部的变量。
用闭包解决问题
for(var i =0;i<=5;i++){
setTimeout(function timer(){console.log(i)},i*1000)
}
运行会返回一堆6,很明显异步函数执行的太晚了。第一种用闭包来解决,或者用let
for (var i = 1; i <= 5; i++) {
;(function(j) {
setTimeout(function timer() {
console.log(j)
}, j * 1000)
})(i)
}
-1 参考
- 参考 《前端开发核心知识进阶》
- 高级程序设计