什么是调用栈
- js在执行过程中,创建一个全局上下文,把全局上下文压入到栈中
- 当执行到函数的地方,就会又创建一个该函数的执行上下文
- 当函数执行完毕的话,该函数的执行上下文就会出栈,
- 在执行过程中,一直持续把函数执行上下文进栈出栈操作
- 当全局代码执行完毕了,把全局执行上下文出栈,到此,程序执行完毕
function func1() {
}
function func(b, c) {
func1()
}
func()
上述代码的执行栈结构如下,(在func1函数内打断点查看该call stack),最下面的anonymous是全局执行上下文,当执行的过程中,不断地把已经执行完毕的函数执行上下文出栈,最后栈为空,程序执行完毕
作用域与作用域链
作用域就是程序中变量和函数的可访问范围,作用域有三类
- 全局作用域 任何代码都能够访问
- 函数作用域 只能够在函数内部访问
- 块级作用域 在作用域块中可以使用
注意: 对象不是一个作用域,只是一个数据结构
var myname = " 外部 "
function showName(){
console.log(myname);
if(0){
var myname = "内部 "
}
console.log(myname);
}
showName()
// 打印出两个undefined
分析一下上面的代码
- var 声明的变量会被提升到当前作用域的最顶端,赋值不会被提升,所以,两个打印都是访问的函数内部的myname,所以打印了undefined
作用域链
首先看一段代码,尝试猜一下输出
function bar() {
console.log(name)
}
function foo() {
var name = " 内部 "
bar()
}
var name = " 外部 "
foo()
第一眼想法按照认真分析了,输出的应该是内部,按照这个变量的寻找顺序,打印的必定是'内部',结果却... 下面来逐步分析,先来看看作用域链是什么
作用域链:每个执行上下文的变量环境中都包含一个引用,用来指向外部的执行上下文,例如在上面的例子中寻找name,在bar函数中没有找到,当前外部引用所指向的函数上下文来寻找,这个寻找变量的链条,就叫做,作用域链
按照作用域链的逻辑,为什么还是寻找到name为外部的变量,不应该是内部吗?
这是因为作用域链是由词法环境决定的,要知道变量的作用域链的查找顺序,还得知道词法作用域
词法作用域: 是作用域是由代码中函数声明的位置来决定的,所以词法作用域是静态的作用域,取决于代码结构中的位置,并不是调用栈中的相对顺序
上述例子中,bar是定义在全局的,所以,根据词法作用域,bar 和 foo 的上层执行上下文都是全局执行上下文,所以作用域链都是foo -> global,所以问题迎刃而解了
修改上面的代码如下,把 bar 函数的声明放在 foo 函数内部
function bar() {
console.log(name)
}
function foo() {
var name = " 内部 "
function bar() {
console.log(name)
}
bar()
}
var name = " 外部 "
foo()
修改过后,函数的作用域链的查找顺序是,bar --> foo --> global,这样的查找顺序,至此,有点理解了。
闭包
先看一段代码:
function foo() {
var myName = " test1 "
let test1 = 1
const test2 = 2
var innerBar = {
getName: function () {
let test = 'testGetName'
console.log(test1)
return myName
},
setName: function (newName) {
let test = 'testGetName'
myName = newName
}
}
return innerBar
}
let global = 'global';
var bar = foo()
bar.setName(" test2 ")
bar.getName()
console.log(bar.getName())
根据词法作用域的规则,foo 内部的 getName, setName 可以访问 foo 中的变量,当 foo 函数执行完毕之后,foo 的执行上下文出栈,但是因为内部的两个函数仍然引用着 foo 中定义的变量,这些变量就是闭包
所以闭包 是,内部函数总能够访问外部函数声明的变量,即使外部函数执行完毕,内部函数引用外部函数的变量仍然被保存在内存中,这些变量的集合就是闭包,这些变量包含在 foo 函数中,就说是 foo 的闭包。 闭包在浏览器中的closure中。
可以看到的是闭包只有myName, test1 两个遍历被包含在闭包中,内部函数不用的变量是不会驻留在内存中的,不会包含在闭包里面
最后一段代码,理解作用域链和闭包
var bar = {
myName:"time.geekbang.com",
printName: function () {
console.log(myName)
}
}
function foo() {
let myName = " test1 "
return bar.printName
}
let myName = " test "
let _printName = foo()
_printName() // test
bar.printName() // test
// 两个都打印test,
// 对于第一个:因为返回的printName函数是在函数外部定义的,在bar内部定义,所以按照词法规则,他会从printName定义时所在的作用域开始因为只有一个全局作用域,所以直接找到的全局下的myName,注意,对象不算是一个作用域,只是一个数据结构,作用域链寻找的规则是依照词法作用域的
// 对于第二个:同第一个,解释了对象不是一个作用域,除了函数的执行上下文,就是全局上下文了,没有经过闭包的操作
var bar = {
myName:"time.geekbang.com",
printName: function () {
console.log(myName)
}
}
function foo() {
let myName = " test1 "
return function () {
console.log(myName)
}
}
let myName = " test "
let _printName = foo()
_printName() // test1
bar.printName() // test
// 做了一些修改,直接把foo的返回函数写在函数里面,这样的话,返回函数当查找自身上下文不存在变量时,往上层寻找,就 找到了foo的闭包,接着是global
至此,这些是目前对于这些问题的理解,可能在过几天又会有新的认识,不断学习,不断纠正和更新自己的理解,就是慢慢的进步。加油 !!!