深入理解JavaScript函数

本文详细介绍了JavaScript中函数的声明、表达式和作用域,包括函数提升、变量提升以及词法环境。通过示例解析了函数声明与函数表达式在执行上下文和词法环境中的不同表现,强调了静态作用域的特点,并探讨了立即执行函数的情况。
摘要由CSDN通过智能技术生成

1. JavaScript定义函数的方式

1.函数声明
2.函数表达式
3.立即执行函数
4.new Funcion(arg1,arg2…,argn,body)创建函数。

2. 函数声明

首先看一个例子:

var a = 2;
function foo() {
    console.log(a); // 2
}

function bar() {
    var a = 3;
    foo();
}
bar();

我们用词法作用域来表示:

GlobalExecutionContext {
	ThisBingding: <Global Object>
	
	LexicalEnvironment {
		EnvironmentRecord {
			Type: "Object",
			foo:<function>
			bar:<function>
		}
		outer: null
	}
	
	VariableEnvironment {
		EnvironmentRecord {
			Type: "Object",
			a:undefined
		}
		outer: <null>
	}
}

FooExecutionContext {
	ThisBinding: <Global Object>

	LexicalEnvironment {
		EnvironmentRecord {
			Type: "Declarative"
		}
		outer: <GlobalLexicalEnvironment>
	}

	VariableEnvironment {
		EnvironmentRecord {
			Type: "Declarative",
		}
		outer: <GlobalLexicalEnvironment>
	}
}

BarExecutionContext {
	ThisBinding: <Global Object>

	LexicalEnvironment {
		EnvironmentRecord {
			Type: "Declarative"
		}
		outer: <GlobalLexicalEnvironment>
	}

	VariableEnvironment {
		EnvironmentRecord {
			Type: "Declarative"
			a: undefined
		}
		outer: <GlobalLexicalEnvironment>
	}
}

在文章深入理解JavaScript执行上下文和词法环境提到过JavaScript是静态作用域,词法环境是由代码结构决定的,开发把代码写成什么样,词法环境就是怎么样,跟方法在哪里调用没有关系。

函数foo bar都是函数声明,函数声明在创建词法环境的时候,会被初始化,这就是我们所说的函数提升

全局变量var a=2是变量声明,变量声明在创建词法环境的时候,会被初始化为undefined,所以上图中的a=undefined,这就是我们所说的变量提升。函数bar中的变量a也类似,只不过它被初始化在bar词法作用域里。

当执行完第一行代码var a=2,给全局变量a赋值;当执行函数bar()里代码var a=3,给bar中的变量赋值;当执行函数foo()里代码console.log(a)输出a的值。

这时候发现在fooFunctionEnviroment词法环境里没有变量a,就会到它的上一层词法环境去找,函数的scope里记录了它上一层词法环境,foo函数的上一层词法环境是GlobalEnvironment全局词法环境,所以输出的是2而不是3。

3.函数表达式

foo(); //TypeError: foo is not a function
bar(); //TypeError: bar is not a function

console.log(foo); // undefined
console.log(bar); // undefined

var a = 2;

var foo = function () {
    console.log(a);
    console.log(b);

}

var bar = function _bar() {
    var a = 3;
    var b =4
    foo();
}

我们可以看到不管是匿名函数表达式还是命名函数表达式,foo 和bar这两个变量有提升初始化为undefined,但是函数体并没有函数提升。

我们把代码改一下:

var a = 2;

var foo = function () {
    console.log(a); // 2
    console.log(b); // Uncaught ReferenceError: b is not defined

}

var bar = function _bar() {
    var a = 3;
    var b =4
    foo();
}

bar();

运行上面代码,a输出的是全局变量a的值,全局变量里没有b,就报了ReferenceError。

我们继续用词法环境来表示一下:

GlobalExecutionContext {
	ThisBingding: <Global Object>
	
	LexicalEnvironment {
		EnvironmentRecord {
			Type: "Object"
		}
		outer: null
	}
	
	VariableEnvironment {
		EnvironmentRecord {
			Type: "Object",
			a:undefined
			foo:undefined
			bar:undefined
		}
		outer: <null>
	}
}

当执行完第一行代码var a=2,给全局变量a赋值,此时的执行上下文和词法环境如下:

GlobalExecutionContext {
	ThisBingding: <Global Object>
	
	LexicalEnvironment {
		EnvironmentRecord {
			Type: "Object"
		}
		outer: null
	}
	
	VariableEnvironment {
		EnvironmentRecord {
			Type: "Object",
			a:2
			foo:undefined
			bar:undefined
		}
		outer: <null>
	}
}

当执行foo = function(){},bar = function _bar(){}的时候,会将变量foo赋值匿名函数,将变量bar赋值_bar函数。

当执行bar(),会给bar的函数表达式新创建一个执行上下文和词法环境,此时的执行上下文和词法环境如下:

GlobalExecutionContext {
	ThisBingding: <Global Object>
	
	LexicalEnvironment {
		EnvironmentRecord {
			Type: "Object"
		}
		outer: null
	}
	
	VariableEnvironment {
		EnvironmentRecord {
			Type: "Object",
			a:2
			foo:<function>
			bar:<function>
		}
		outer: <null>
	}
}

_BarExecutionContext {
	ThisBinding: <Global Object>

	LexicalEnvironment {
		EnvironmentRecord {
			Type: "Declarative"
		}
		outer: <GlobalLexicalEnvironment>
	}

	VariableEnvironment {
		EnvironmentRecord {
			Type: "Declarative"
			a:3
			b:4
		}
		outer: <GlobalLexicalEnvironment>
	}
}

当执行foo(),会给foo的函数表达式新创建一个执行上下文和词法环境,此时的执行上下文和词法环境如下:

GlobalExecutionContext {
	ThisBingding: <Global Object>
	
	LexicalEnvironment {
		EnvironmentRecord {
			Type: "Object"
		}
		outer: null
	}
	
	VariableEnvironment {
		EnvironmentRecord {
			Type: "Object",
			a:2
			foo:<function>
			bar:<function>
		}
		outer: <null>
	}
}

BarExecutionContext {
	ThisBinding: <Global Object>

	LexicalEnvironment {
		EnvironmentRecord {
			Type: "Declarative"
		}
		outer: <GlobalLexicalEnvironment>
	}

	VariableEnvironment {
		EnvironmentRecord {
			Type: "Declarative"
			a:3
			b:4
		}
		outer: <GlobalLexicalEnvironment>
	}
}

FooExecutionContext {
	ThisBinding: <Global Object>

	LexicalEnvironment {
		EnvironmentRecord {
			Type: "Declarative"
		}
		outer: <GlobalLexicalEnvironment>
	}

	VariableEnvironment {
		EnvironmentRecord {
			Type: "Declarative"
		}
		outer: <GlobalLexicalEnvironment>
	}
}

这时候发现在fooFunctionEnviroment词法环境里没有变量a和b,就会到它的上一层词法环境去找,函数的scope里记录了它上一层词法环境,foo函数的上一层词法环境是GlobalEnvironment全局词法环境,所以输出的是2和ReferenceError。

我们再把上面的代码改一下:

var a = 2;

var bar = function _bar() {
    var a = 3;
    var b =4
    var foo = function () {
        console.log(a); // 3
        console.log(b); // 4
    
    }
    foo();
}

bar();

从上面的例子可以看出,对于函数表达式,它们的函数体不会函数提升,函数的初始化发生在代码执行的时候,函数表达式的词法环境还是由代码结构决定的,开发把代码写成什么样,词法环境就是怎么样,跟函数在哪里调用没有关系。

4.立即执行函数

立即执行函数和函数表达式是一样的,不会函数提升,函数的初始化发生在代码执行的时候,词法环境还是由代码结构决定的。

5. new Funcion(arg1,arg2…,argn,body)

用new Function(arg1,arg2,…,argn,body) 创建函数的过程有和上面函数表达式类似,不同地方在于,创建函数使用的scope是直接使用全局词法环境(glbal enviroment),而不管当前运行上下文,一律取全局词法环境(glbal enviroment)。

6.思考题

console.log(foo);

function foo(){
    console.log("foo");
}

var foo = 1;

在这里会打印函数,而不是 undefined。

这是因为在进入执行上下文的时候,首先会处理函数声明,其次会处理变量声明,如果变量名称已经跟已经声明的形式参数或函数相同,则变量声明不会干扰已经存在的这类属性。

7.总结

  • 函数声明,会有变量提升,函数初始化是发生在函数创建时运行上下文的词法环境里。
  • 函数表达式/匿名函数/立即执行函数,没有变量提升,函数初始化是发生在代码执行的时候。
  • 函数的词法环境中的scope,是用来记录上一层的词法环境。
  • 如果函数有形参,那么这些形参都属于函数的词法环境。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值