变量提升与函数提升
变量声明提升
通过var定义 (声明)的变量,在定义语句之前就可以访问到,但是值是: undefined eg:
console.log(b)
var b =3;
输出:
undefined
注意:必须是通过var定义的变量,如果一个变量没有通过var定义特就不会进行提升。
函数声明提升
通过function声明的函数,在定义之前就可以直接调用,并且值就是函数所定义的内容(对象)。 eg:
fun()
function fun (){
console.log('我是fun')
}
输出:
我是fun
函数提升必须使用声明的方式定义函数,如果使用变量的方式定义函数就是变量提升了。 eg:
fun()
var fun = function (){
console.log('我是fun')
}
输出:
函数提升和变量提升的先后书顺序
先执行变量提升,再执行函数提升 eg:
function a() {}
var a;
console.log(typeof a);
输出:
function
那么为什么会有声明提升呢?下面就会介绍
代码分类
代码分为全局代码与局部(函数)代码
执行上下文
全局执行上下文
- 在执行全局代码前将
window 确定为全局执行上下文 - 对全局数据进行预处理:
var定义的全局变量==>undefined, 添加为window的属性,即放到全局执行上下文中 function声明的全局函数==>赋值(fun) ,赋值为函数对象(创建了函数对象但是没有执行函数),添加为window的方法,即放到全局执行上下文中 this==>赋值(window) - 开始执行全局代码
执行全局代码的时候会跳过预处理时已经处理过的代码片段(主要是函数) 注意: 预处理不是执行函数,而是执行函数定义,即只创建了函数对象,真正执行函数还要看执行代码时什么时候调用函数。 如果执行过程中遇到 var定义的全局变量、function声明的全局函数、this ,就会去全局执行上下文window 中寻找。 eg:
console.log(a1, window.a1)
a2()
console.log(this)
var a1 = 3
function a2 (){
console.log('a2()')
}
console.log(a1)
输出: 断点调试 16 ==>17 ==>18 ==>19 ==>20 ==>24, 到20行后就直接到24行,因为在预处理的时候就已经创建过函数对象了。 (补充:执行到17行后直接执行18行,这里虽然不显示执行过程,但是函数 a2 仍然是在17行调用的时候执行的,预处理只是为其定义对象。)
eg2:有参数的函数 14行==> 27行==>28行 不显示执行过程,但是函数 fn 是在27行调用的时候执行的
函数执行上下文
- 在调用函数,准备执行函数体之前,创建对应的
函数执行上下文对象 (全局的时候直接使用window即可,无需再创建对象) - 对局部数据进行预处理
形参变量==>赋值(实参)==>添加为执行上下文的属性 arguments==>赋值(实参列表),添加为执行上下文的属性 var定义的局部变量==>undefined,添加为执行上下文的属性 function声明的函数==>赋值(fun),赋值为函数对象(创建了函数对象但是没有执行函数),添加为执行上下文的方法 this==>赋值(调用函数的对象 : window 或其他对象) - 开始执行函数体代码
执行函数体的时候会跳过预处理时已经处理过的代码片段。 如果执行过程中遇到 形参变量、 arguments、var定义的全局变量、function声明的全局函数、 this ,就会去函数执行上下文对象 中寻找。
函数执行上下文对象 实际上是一个虚拟对象,是一个区域,该区域中存放预处理的变乱给、函数等,存放在栈中(局部变量存放在栈中)。 不调用就不会出现函数执行上下文。
局部变量的区域是封闭的,而且函数执行完毕之后局部变量就会消失。
eg:
function fn (a1){
console.log(a1)
console.log(a2)
a3()
console.log(this)
console.log(arguments)
var a2 =3
function a3 (){
console.log("a3()")
}
}
fn(2,3)
输出: 正式由于全局执行上下文和函数执行上下文的存在才会使函数与变量的声明提前。
执行上下文栈
引例
判断下面的代码是否会出错:
var a =10
var bar =function(x){
var b =5
foo(x+b)
}
var foo = function (y){
var c =5
console.log(a+c+y)
}
bar(10)
bar函数在foo声明之前就调用foo,而且foo函数不是用声明的方式定义函数,即不会出现函数提前声明,乍一看是会出错的,但是再仔细看一下。 var bar =function(x), var foo = function (y) 他们是一个赋值语句只不过赋的值是函数,但是赋值不代表函数执行,函数在调用的时候才会执行。也就是说先依次声明了bar、foo变量(函数),到后面执行bar函数的时候,foo函数已经声明好了,自然不会报错。
执行上下文数目
执行上下文包括全局执行上下文和函数执行上下文,每次执行代码就会产生一个全局上下文window,每调用一次函数就会产生一个函数执行上下文。 所以产生的执行上下文数目 = 调用函数的次数 + 1 eg: 上述引例就产生了 3 个执行上下文
执行上下文的管理——执行上下文栈
一个函数代码可能会产生很多个执行上下文,所以我们需要对他们进行管理。 我们采用栈结构进行管理: 执行过程: 1.在全局代码执行前,JS引擎就会创建一个栈来存储管理所有的执行上下文对象 2.在全局执行上下文(window)确定后,将其添加到栈中(压栈) 3.在函数执行上下文创建后,将其添加到栈中(压栈) 4.在当前函数执行完后,将栈顶的对象移除(出栈) 5.当所有的代码执行完后,栈中只剩下window
eg:
var a =10
var bar =function(x){
var b =5
foo(x+b)
}
var foo = function (y){
var c =5
console.log(a+c+y)
}
bar(10)
对应的执行上下文栈就是:
特点
- 采用栈结构,先进后出
- 栈底永远是全局执行上下文环境window,其余的都是函数执行上下文
- 当前正在运行的永远是栈顶的执行上下文
题目
题1
console.log('global begin:'+ i)
var i =1
foo(1)
function foo (i){
if(i==4){
return;
}
console.log('foo() begin:'+i)
foo(i+1)
console.log('foo() end:'+i)
}
console.log('global() end:'+i)
输出什么? 对应的执行上下文栈? 产生了5个执行上下文栈:
题2
function a() {}
var a;
console.log(typeof a);
输出:
function
因为先执行变量提升,再执行函数提升
if(!(b in window)){
var b =1;
}
console.log(b)
输出:
undefined
因为b in window 为真
var c =1
function c (c){
console.log(c)
}
c(2)
输出:
Uncaught TypeError: c is not a function
原因:因为预处理先执行变量提升,再执行函数提升,但是不会为变量赋值,所以上面的代码就相当于
var c
function c (c){
console.log(c)
}
c=1
c(2)
所以自然报错。
问题:与预处理的时候函数是否执行
有两个概念:
|