目录
一、 变量提升与函数提升
为什么要进行变量提升和函数提升
JS引擎在读取js代码的过程中,分为两步。第一个步骤是整个js代码的解析读取,第二个步骤是执行。在JS代码执行之前,浏览器的解析器在遇到 var 变量名 和function 整个函数 提升到当前作用域的最前面。
在ES6出来之前,JS并没有块级作用域这一说,只有全局作用域和局部作用域。变量提升指的是使用var声明的变量提升到他所在的作用域的最顶端。
1. 变量声明提升
* 通过var定义(声明)的变量, 在定义语句之前就可以访问到
* 值: undefined
2. 函数声明提升
* 通过function声明的函数, 在定义之前就可以直接调用
* 值: 函数定义(对象)
3. 问题: 变量提升和函数提升是如何产生的?
1.1 变量提升
console.log(a) //undefined
var a='我是谁'
console.log(a) //'我是谁'
它的过程就相当于
var a;
console.log(a);
a='我是谁'
console.log(a)
1.2 函数提升
函数提升只针对具名函数(用function 声明的函数),而对于赋值的匿名函数,并不会存在函数提升。
console.log(a); // f a()
console.log(b); //undefined
function a(){
console.log('hello')
}
var b = function(){
console.log('world')
}
它的过程就相当于:
var a = function (){
console.log('hello')
}
var b;
console.log(a);
console.log(b);
1.3 变量提升与函数提升的优先级
函数提升优先级高于变量提升,且不会被同名变量声明覆盖,但是会被变量赋值后覆盖。而且存在同名函数与同名变量时,优先执行函数。
下面这道题真的很有水准:
console.log(a);
console.log(a()); //1
var a = 1;
function a(){
console.log(1);
}
console.log(a); //1
a = 3
console.log(a()) //Uncaught TypeError: a is not a function
它的过程就相当于:
var a = function (){ //声明一个变量a指向function函数
console.log(1)
}
var a;
console.log(a) // 1 a优先执行函数
console.log(a());
a = 1 //这里变量赋值后覆盖同名函数,a此时是变量且值为1
console.log(a) // 1
a = 3
console.log(a()) // 3
二、执行上下文
1. 代码分类(位置)
* 全局代码
* 函数(局部)代码
2. 全局执行上下文
* 在执行全局代码前将window确定为全局执行上下文
* 对全局数据进行预处理
* var定义的全局变量==>undefined, 添加为window的属性
* function声明的全局函数==>赋值(fun), 添加为window的方法
* this==>赋值(window)
* 开始执行全局代码
3. 函数执行上下文
* 在调用函数, 准备执行函数体之前, 创建对应的函数执行上下文对象(虚拟的, 存在于栈中)
* 对局部数据进行预处理
* 形参变量==>赋值(实参)==>添加为执行上下文的属性
* arguments==>赋值(实参列表), 添加为执行上下文的属性
* var定义的局部变量==>undefined, 添加为执行上下文的属性
* function声明的函数 ==>赋值(fun), 添加为执行上下文的方法
* this==>赋值(调用函数的对象)
* 开始执行函数体代码
console.log(a1, window.a1)
window.a2()
console.log(this)
var a1 = 3
function a2() {
console.log('a2()')
}
console.log(a1)
//函数执行上下文
function fn(a1) {
console.log(a1);// 2
console.log(a2);// undefined
a3() // a3()执行了
console.log(this) // window 因为调用函数的对象为window
console.log(arguments);//伪数组 Arguments(2) [2, 3, callee: ƒ, Symbol(Symbol.iterator): ƒ]
var a2 = 3
function a3() {
console.log('a3()执行了');
}
}
fn(2, 3) //调用函数的对象为window
三、执行上下文栈
1. 在全局代码执行前, JS引擎就会创建一个栈来存储管理所有的执行上下文对象
2. 在全局执行上下文(window)确定后, 将其添加到栈中(压栈)
3. 在函数执行上下文创建后, 将其添加到栈中(压栈)
4. 在当前函数执行完后,将栈顶的对象移除(出栈)
5. 当所有的代码执行完后, 栈中只剩下window
练习题1:
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) // 30
练习题2:
console.log('gb:' + i);
var i = 1
foo(1)
function foo(i) {
if (i == 4) {
return
}
console.log('gb:' + i);
foo(i + 1);// 递归调用: 在函数内部调用自己
console.log('fe:' + i);
}
console.log('ge:' + i);
输出结果:
四、习题
习题1:
function a() { }
var a
console.log(typeof a) // 'function'
// 浏览器一上来就会先把函数预编译了,也就是函数提升定义a为函数,然后a又被赋值成了undefined但是不会覆盖之前的函数,所以a是函数了
习题2:
function a() { }
var a = 1
console.log(typeof a) // 'Number'
// 浏览器一上来就会先把函数预编译了,也就是函数提升定义a为函数,然后a又被赋值成了1覆盖了之前的函数,所以a是Number类型
习题3:
if (!(b in window)) {
var b = 1
}
console.log(b) // undefined
习题4:
/*
浏览器一上来就会先把函数预编译了,也就是函数提升定义c为函数,然后c又被赋值成了1,所以再调用c时c已经不是函数了,所以报错
函数的提升比变量优先级高,但是如果变量赋值,函数就会被覆盖
*/
var c = 1
function c(c) {
console.log(c)
}
console.log(c); // 1
c(2) // 报错