前端开发核心知识进阶 —— 闭包

闭包

暂时性死区

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

注意:nullundefined是不一样的

function foo(arg1 = arg2, arg2){
    console.log(`${arg1} ${arg2}`)
}
foo(null,'arg2') // null arg2

执行上下文和调用堆栈

代码执行的两个阶段

  1. 代码预编译阶段
  2. 代码执行阶段

预编译阶段是前置阶段,这一阶段会由编译器将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

预编译阶段:

  1. 对变量bar进行声明,但是不会赋值
  2. 函数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赋值给全局变量fnfn函数查找变量过程:

  1. 查找innerFoo函数内部
  2. 查找foo函数内部
  3. 查找全局变量

版权声明:本文为qq_56303170原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。