1.变量提升和函数提升
通过var声明的变量可以变量提升,提升的值为undefined
通过函数声明的定义的函数可以函数提升,提升的值是整个函数,而通过函数表达式定义的函数不能函数提升,以为其本质是变量,提升的结果是undefined,所以声明前调用时出错。
栗1:–变量提升(举例子来说明变量提升更清晰简洁)
var a = 3
function fn() {
console.log(a)
var a =4
}
fn() // undefined
//函数fn相当于
function fn() {
var a // 此时a值为undefined
console.log(a) // 由于作用域的原因,它自身有a变量,所以不会再向上(到全局中)去找该变量
a =4
}
栗2:–函数提升
fn2() // fn2 --函数提升
fn3() // 报错
function fn2() {
console.log( 'fn2')
}
var fn3 = function () {
console.log( 'fn2')
}
Q:变量提升和函数提升是如何产生的?
2.执行上下文(并不真实存在)
2.1 代码分类(按位置来分)
全局代码
局部(函数)代码
2.2 全局执行上下文
在执行全局代码之前,将window作为全局上下文,JS引擎会做以下三步准备(预处理):
a。 将全局中使用var定义的的变量放到window中,作为window的属性,并赋值undefined ——变量提升的原因
b。 将全局中使用函数声明定义的函数,整个函数放到window中,作为window的方法——函数提升的原因
(变量提升在函数提升前面执行,所以有函数名和变量名冲突时,函数提升会覆盖变量提升)
c。 指定全局的this ——此时值为window
(注意:例如,c = 2这个语句在执行全局代码前,window(全局执行上下文)中并不会包含c,而是等到执行到该语句时c才存在)
栗子:
console.log(a1) // undefined ——调用前window中已经存在变量a1
// 相当于console.log(window.a1)
a2() // a2 ——调用前window中已经存在函数a2
// window.a2()
console.log(this) // window ——此时全局环境的this为window
var a1 = 3
function a2() {
console.log('a2')
}
2.3 函数执行上下文(并不真实存在)
函数调用前,提前生成函数执行向下文,JS引擎会做以下准备(预处理):
a。 将函数中的使用var定义的变量放到函数执行上下文中,作为执行上下文的属性,并赋值undefined
b。 将由实参传给形参的整个arguments对象放到执行上下文中,作为执行上下文的属性
(变量提升在函数提升前面执行,所以有函数名和变量名冲突时,函数提升会覆盖变量提升)
c。 将函数内部使用函数声明定义(使用function定义)的函数,整个放到执行上下文中,作为执行上下文的方法
d。 指定该函数的this值,该值指向本函数的调用者
做好以上准备后才开始执行函数
栗子:
function fn(a1) {
console.log(a1) // 1
console.log(a2) // 3
a3() // I am a3
console.log(arguments) // (1,2,3)-伪数组
console.log(this) // window
var a2 = 3
function a3() {
console.log('I am a3')
}
}
fn(1,2,3) // 1 3 I am a3 (1,2,3,...)-伪数组 window
3.执行上下文栈(栈–后进先出)
(执行上下文栈——管理和保存所有的执行上下文
)
全局函数执行时,js引擎首先将window放到栈底 ——>
每调用一个函数,都会把函数推入栈内 ——>
函数执行结束就从栈顶弹出(释放函数所占的内存)(永远是栈顶那个的函数在执行) ——>
所有函数执行结束后,栈中最终只剩下window
var a = 10
var fn = function (x) {
var b = 5
foo(x + b) // 函数中可以访问全局中的属性和方法
}
var foo = function (y) { // foo()是全局变量(函数)
var c =5
console.log(a + c + y)
}
fn(10) // 30
// 以上例子产生三个执行上下文:全局上下文(永远在栈底)、fn执行上下文、foo执行上下文
// 执行上下文栈结构变化:1.(栈底)window ==>2.(栈底)window --> fn() ==>3.(栈底)window--> fn()--> foo(栈顶) ==>4. (栈底)window --> fn() ==> 5.(栈底)window
4.执行上下文和执行上下文栈相关面试题
题目1:
输出结果的顺序?-- undefined 1 2 3 3 2 1 1
整个过程产生的执行上下文一共有几个?5个–window、foo(1)、foo(2)、foo(3)、foo(4)
console.log(i) // undefined
var i = 1
foo(1)
function foo(i) {
if (i === 4) { // 递归终止条件
return
}
console.log(i)
foo(i + 1) // 递归调用
console.log(i)
}
console.log(i) // 1
分析:
foo(1)
function foo(i) {
if (i === 4) { // 递归终止条件
return
}
console.log(i)
foo(i + 1) // 递归调用
console.log(i)
}
// 相当于
foo(1){
if (i === 4) { // 递归终止条件
return
}
console.log(1) // 1
foo (2){
if (i === 4) { // 递归终止条件
return
}
console.log(2) // 2
foo(3){
if (i === 4) { // 递归终止条件
return
}
console.log(3) // 3
foo(4){
if (i === 4) { // 递归终止条件
return
}
}
}
console.log(3)//3
}
console.log(2) //2
}
console.log(1) // 1
题目2:
function a() {
}
var a
console.log(typeof a)
// function ——先执行变量提升,再执行函数提升,函数提升把变量提升覆盖了
题目3:
if (!(b in window)){
var b = 1
}
console.log(b) // undefined ——调用b时,window中没有b,所以if语句都没有被执行
题目4:
var c = 1
function c(c) {
console.log(c)
}
c(2) //报错
分析:
//执行过程相当于
var c // 变量提升
function c() { // 函数提升,在执行时不会在执行函数这个代码块
console.log(c)
}
c = 1 // 正式执行时,c并不是函数
c(2) // 执行时出错,因为c不是函数