一、变量提升 & 函数提升
1. 概念
-
js引擎在js代码正式执行之前,会做一些 预解析 的工作
-
找关键字:
var
、function
-
找到
var
以后将var
后边的变量提前声明,但是不赋值 :var a;
-
找到
function
以后,定义对应的函数,也就是说,函数在预解析的时候已经定义完毕 -
预解析:全局预解析,局部预解析
2. 证明
2.1、实例
我们可以使用 断电调试的方法,来证明变量 & 函数 是否提升
如以下代码:
console.log('程序开始') // 断点打在这里
fun() // 1. 测试下面的函数是否提升
var b = 345 // 2. 测试b这个变量是否提升
function fun() {
var a = 123
console.log('fun()')
}
fun2() // 3. 测试这种方法的于 function fun2 的有什么不同
var fun2 = function() {
console.log('fun2()')
}
既然断点打在第一行,使用 debug 调试,那么程序就会执行到第一行停下,这样我们就可以知道,我们定义的fun、b、fun2
是否有值
2.2、证明 变量提升
当只执行了第一行,那么,说明:var b = 345
并没有执行,那么,b
这个变量,是否定义了呢
如图所示,即使没有执行var b = 345
,变量b
也是定义好了的,预加载不赋值,所以值为undefined
即代码可以变为:
// 预加载
var b;
//第一行:
console.log('程序开始')
2.3、证明函数提升
那么,相同的,function
也会被预加载,定义相应的函数
可以看到,即使还没执行到对应的代码,fun
也是定义好了函数的
即代码可以变成
// 预加载
var b;
function fun() {}
//第一行:
console.log('程序开始')
2.4、证明两种函数定义的不同
那么
function foo (){}
var foo = function() {}
这两种有什么不同呢?
可以很清晰的看到,第一种的开头是function
,第二种开头是var
,也就是说:
function foo (){}
----> 这属于函数提升var foo = function() {}
—> 这属于 变量提升
在预解析时,第一种foo
有函数值,第二种foo
值为undefined
所以代码可以变为:
// 预加载
var b;
function fun() {};
var fun2;
//第一行:
console.log('程序开始')
3. 面试题
3.1、第一道
/* 输出什么? */
var a = 4;
function fn() {
console.log(a)
var a = 5
}
fn()
答案: undefined
解析:
他要看输出什么,全部代码只有函数内才有输出,那么,这就是考 局部预解析,所以:
var a = 4; // 1. 这个没用 function fn() { // 预解析: 有一个 var a; console.log(a) // 开始执行:先找局部变量,a为undefined,那么输出undefined var a = 5 } fn() // 2. 进入函数内,开始预解析
3.2、第二道
/* 输出什么? */
console.log(a1)
a2()
var a1 = 3
function a2() {
console.log(a1)
}
答案:undefined、undefined
解析:
// 1. 开始执行前,预解析: var a1; function a2(){} console.log(a1) // 2. 预解析有定义:undefined a2() // 3. 进入函数 var a1 = 3 function a2() { // 4. 预解析,没有 console.log(a1) // 5. 先找自身变量,没用;找全局变量,有一个a1:undefined }
二、执行上下文
1. 概念
执行上下文,代表了代码执行的环境,有:执行环境、变量对象、this、作用域链
流程:
-
js引擎在js代码正式执行之前,会先创建一个执行环境
-
进入环境后,会创建一个变量对象,该对象用于收集(预解析):
- 变量
- 函数
- 函数的参数
- this
-
确认
this
的执行 -
创建作用域链
执行上下文是动态创建的
尤其是针对函数,每调用一次,就会创建一次执行上下文
2. 面试题
2.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)
答案:
global begin:undefined foo() begin: 1 foo() begin: 2 foo() begin: 3 foo() end: 3 foo() end: 2 foo() end: 1 global end:1 创建了 5 次 执行上下文
解析:
执行上下文使用的是 栈 的存储方式,先进后出
那么,流程如下:
第一次,执行全局上下文
global
,压入栈
执行上下文对象 输出 说明 global global begin:undefined 全局上下文 第二次,执行 i 为 1 的局部上下文,压入栈
执行上下文对象 输出 说明 local(i = 1) foo() begin: 1 i 为 1的局部上下文 global global begin:undefined 全局上下文 第三次,执行 i 为 2 的局部上下文,压入栈,一直到 i = 4,此时有5个执行上下文
执行上下文对象 输出 说明 local(i = 4) i 为 4的局部上下文 local(i = 3) foo() begin: 3 i 为 3的局部上下文 local(i = 2) foo() begin: 2 i 为 2的局部上下文 local(i = 1) foo() begin: 1 i 为 1的局部上下文 global global begin:undefined 全局上下文 i = 4 的时候,退出,出栈,执行 i = 3 的剩余代码
执行上下文对象 输出 第二次输出 说明 local(i = 3) foo() begin: 3 foo() end: 3 i 为 3的局部上下文 local(i = 2) foo() begin: 2 i 为 2的局部上下文 local(i = 1) foo() begin: 1 i 为 1的局部上下文 global global begin:undefined 全局上下文 然后依次出栈,依次执行剩余代码
所以,输出了:
// 进栈后输出 global begin:undefined foo() begin: 1 foo() begin: 2 foo() begin: 3 // 先进后出 // 出栈后输出 foo() end: 3 foo() end: 2 foo() end: 1 global end:1
2.2、第二道
if(! (b in window)) {
var b = 1
}
console.log(b)
答案: undefined
2.3、第三道
var c = 1
function c(c) {
console.log(c)
var c = 3
}
c(2)
答案: 报错,c is not function