闭包
暂时性死区
function bar1() {
console.log(b) // bar is not defined
let b = 'bar'
console.log(b) // bar
}
let b = 'bar'之前的区域为死区,在死区内访问变量b是会报错,而在死区外可以访问
函数参数的死区
我们在此处讲arg1的默认值设置为第二个参数agr2
function foo(arg1 = arg2, arg2){
console.log(`${arg1} ${arg2}`)
}
foo('arg1','arg2') // arg1 arg2
在上面的foo函数中,如果没有传入第一个参数,则会使用第二个参数作为第一个参数
function foo(arg1 = arg2, arg2){
console.log(`${arg1} ${arg2}`)
}
foo('arg3') // arg3 undefined
但是当第一个参数为默认值时,执行arg1 = arg2会被当做暂时性死区处理
function foo(arg1 = arg2, arg2){
console.log(`${arg1} ${arg2}`)
}
foo(undefined,'arg2') // Uncaught ReferenceError: Cannot access 'arg2' before initialization
注意:null和undefined是不一样的
function foo(arg1 = arg2, arg2){
console.log(`${arg1} ${arg2}`)
}
foo(null,'arg2') // null arg2
执行上下文和调用堆栈
代码执行的两个阶段
- 代码预编译阶段
- 代码执行阶段
预编译阶段是前置阶段,这一阶段会由编译器将JavaScript代码编译成可执行的代码。这里的预编译是JavaScript中的独特概念,虽然JavaScript是解释型语言,编译一行,执行一行。但是在代码执行钱,JavaScript引擎确实会做一些“预先准备工作”
执行阶段的主要任务是执行代码逻辑,执行上下文在这个阶段会全部创建完成
对于预编译过程中的一些细节,我们应该注意:
- 在预编译阶段进行变量声明
- 在预编译阶段对变量进行提升,但是值为undefined
- 在预编译阶段对所有非表达式的函数声明进行提升
function bar(){
console.log('1')
}
var bar = function(){
console.log('2')
}
bar() // 2
调换代码的顺序
var bar = function(){
console.log('2')
}
function bar(){
console.log('1')
}
bar() // 2
预编译阶段:
- 对变量bar进行声明,但是不会赋值
- 函数bar被创建并赋值
代码执行阶段:变量bar被赋值
调用栈
在执行一个函数时,如果这个函数又调用了零外一个函数,而这“另外一个函数”又调用了另外一个函数,这样便形成了一系列的调用堆栈。
function foo1(){
foo2()
}
function foo2(){
foo3()
}
function foo3(){
foo4()
}
function foo4(){
console.log('foo4')
}
具体过程:foo1先入栈,紧接着foo1调用foo2,foo2再入栈,以此类推,直到foo4执行完,然后foo4先出栈,foo3再出栈,接着foo2,最后foo1
注意:正常来讲,再函数执行完毕并出栈时,函数内部的局部变量再下一个垃圾回收节点回备回收,该函数对应得执行上下文将会被销毁。也就是我们再外界无法访问函数内定义的变量的原因。
闭包
function numGenerator() {
let num = 1
num++
return () => {
console.log(num)
}
}
var getNum = numGenerator()
getNum() // 2
我们知道在正常情况下是无法访问函数内部变量的,函数执行后,上下文被销毁。但是在函数(外部)中,我们如果返回另外一个函数,且这个返回的函数使用了外层的变量,那么外界便能够通过这个返回的函数获取原函数(外层)内部的变量值
注意:返回的函数必须是匿名函数或者在外层函数编写返回的函数
function a(){
let num = 1
return b
}
function b() {
console.log(num)
}
const temp = a()
temp() // > Uncaught ReferenceError: num is not defined
function a(){
let num = 1
function b() {
console.log(++num)
}
return b
}
const temp = a()
temp() // 2
例题
const foo = ()=> {
var arr = []
var i
for(i = 0; i < 10; i++){
arr[i] = function(){
console.log(i)
}
}
return arr[0]
}
foo()() // 10
数组中的每一个元素都是一个函数
function(){
console.log(i)
}
因为定义i时使用的是var,没有块作用域,所以在函数体内i = 10,所以返回10
const foo = () => {
var a = 2
function innerFoo() {
console.log(a)
}
fn = innerFoo
}
const bar = () => {
fn()
}
foo()
bar() // 2
var fn = null
const foo = () => {
var a = 2
function innerFoo() {
console.log(c)
console.log(a)
}
fn = innerFoo
}
const bar = () => {
var c = 100
fn()
}
foo()
bar() // ReferenceError: c is not defined
变量c不在其作用域链上,c只是bar的内部变量,通过将innerFoo赋值给全局变量fn,fn函数查找变量过程:
- 查找
innerFoo函数内部 - 查找
foo函数内部 - 查找全局变量