一、js执行顺序
这里需要特别提的是词法环境。
词法环境(即作用域)是js引擎内部用来跟踪标识符和特定变量之间的映射关系。
词法环境主要基于代码嵌套,通过代码嵌套可以实现代码结构的包含关系。
在作用域范围内,每次执行代码时,代码结构都会获得与之关联的词法环境,而我们知道内部代码结构可以访问外部代码结构中定义的变量,外部代码结构不能访问内部代码结构定义的变量,js引擎就是根据词法环境来进行这些变量的跟踪的。
因为需要能够访问到外部代码结构,所以需要跟踪外部词法变量,如果当前环境中无法找到某一个标识符,就会对外部环境进行查找,一旦找到匹配的变量或者在全局环境中依然没有找到匹配的变量而返回错误,就会停止查找。
var ninja = "Muneyoshi"
function skulk(){
var action = "skulking"
function report(){
var intro = "Aha!"
console.log(intro)
console.log(action)
console.log(ninja)
}
report()
}
skulk()
二、js代码为什么会栈溢出
那么接下来我们就来明确下,哪些情况下代码才算是“一段”代码,才会在执行之前就进行编译并创建执行上下文。
一般说来,有这么三种情况:
- 当 JavaScript 执行全局代码的时候,会编译全局代码并创建全局执行上下文,而且在整个页面的生存周期内,全局执行上下文只有一份。
- 当调用一个函数的时候,函数体内的代码会被编译,并创建函数执行上下文,一般情况下,函数执行结束之后,创建的函数执行上下文会被销毁。
- 当使用eval 函数的时候,eval 的代码也会被编译,并创建执行上下文。
调用栈是 JavaScript 引擎追踪函数执行的一个机制,当一次有多个函数被调用时,通过调用栈就能够追踪到哪个函数正在被执行以及各函数之间的调用关系。有两种方法可以查看调用关系:
三、let和const的原理
function foo(){
var a = 1
let b = 2
{
let b = 3
var c = 4
let d = 5
console.log(a)
console.log(b)
}
console.log(b)
console.log(c)
console.log(d)
}
foo()
函数只会在第一次执行的时候被编译,所以编译时变量环境和词法环境最顶层数据已经确定了。当执行到块级作用域的时候,块级作用域中通过let和const申明的变量会被追加到词法环境中,当这个块执行结束之后,追加到词法作用域的内容又会销毁掉。
也就是当作用域块执行结束之后,其内部定义的变量就会从词法环境的栈顶弹出,最终执行上下文
四、作用域链
function bar() {
console.log(myName)
}
function foo() {
var myName = "极客邦"
bar()
}
var myName = "极客时间"
foo()
比如上面那段代码在查找 myName 变量时,如果在当前的变量环境中没有查找到,那么 JavaScript 引擎会继续在 outer 所指向的执行上下文中查找。为了直观理解,你可以看下面这张图:
在开头那段代码中,foo 函数调用了 bar 函数,那为什么 bar 函数的外部引用是全局执行上下文,而不是 foo 函数的执行上下文?
这是因为根据词法作用域,foo 和 bar 的上级作用域都是全局作用域,所以如果 foo 或者 bar 函数使用了一个它们没有定义的变量,那么它们会到全局作用域去查找。也就是说,词法作用域是代码编译阶段就决定好的,和函数是怎么调用的没有关系。
闭包
function foo() {
var myName = "极客时间"
let test1 = 1
const test2 = 2
var innerBar = {
getName:function(){
console.log(test1)
return myName
},
setName:function(newName){
myName = newName
}
}
return innerBar
}
var bar = foo()
bar.setName("极客邦")
bar.getName()
console.log(bar.getName())
根据词法作用域的规则,内部函数 getName 和 setName 总是可以访问它们的外部函数 foo 中的变量,所以当 innerBar 对象返回给全局变量 bar 时,虽然 foo 函数已经执行结束,但是 getName 和 setName 函数依然可以使用 foo 函数中的变量 myName 和 test1。
闭包的回收
通常,如果引用闭包的函数是一个全局变量,那么闭包会一直存在直到页面关闭;但如果这个闭包以后不再使用的话,就会造成内存泄漏。
如果引用闭包的函数是个局部变量,等函数销毁后,在下次 JavaScript 引擎执行垃圾回收时,判断闭包这块内容如果已经不再被使用了,那么 JavaScript 引擎的垃圾回收器就会回收这块内存。
所以在使用闭包的时候,你要尽量注意一个原则:如果该闭包会一直使用,那么它可以作为全局变量而存在;但如果使用频率不高,而且占用内存又比较大的话,那就尽量让它成为一个局部变量。
五、js的this
JavaScript 中的 this 是什么关于 this,我们还是得先从执行上下文说起。
执行上下文中的 this从图中可以看出,this 是和执行上下文绑定的,也就是说每个执行上下文中都有一个 this。执行上下文主要分为三种——全局执行上下文、函数执行上下文和 eval 执行上下文,所以对应的 this 也只有这三种——全局执行上下文中的 this、函数中的 this 和 eval 中的 this。另外,
- 当函数作为对象的方法调用时,函数中的 this 就是该对象;
- 当函数被正常调用时,在严格模式下,this 值是undefined,非严格模式下 this 指向的是全局对象 window;
- 嵌套函数中的 this 不会继承外层函数的 this值。最后,我们还提了一下箭头函数,因为箭头函数没有自己的执行上下文,所以箭头函数的 this 就是它外层函数的 this。