🌟 执行上下文与执行上下文栈
1. 变量提升与函数提升
变量提升:通过var关键字定义(声明)的变量,在定义语句之前就可以访问到,只不过其值是undefined
函数提升:通过function声明的函数,在之前就可以直接调用,值是函数定义(对象)。
var a = 3;
function fn(){
console.log(a);
var a = 4;
};
fn(); //undefined
//使用var关键字变量提升了
//相当于 function fn(){
var a;
console.log(a);
a=4;
};
//在函数作用域本身含有a,则不会去全局作用域查找
//变量提升
console.log(b); //undefined
var b = 3;
//函数提升
fun2(); //可调用
function fun2(){
console.log("fn2()");
};
fn3();//不能 这里使用了var关键词,所以重点是变量提升,而不是函数提升
var fn3 = function(){
console.log(3);
};
*函数提升必须使用函数声明的方式
2. 执行上下文
(1)代码分类(位置):全局代码;函数(局部)代码
(2)全局执行上下文
在执行全局代码前,1⃣️ 将window确立为全局执行上下文;2⃣️ 对全局数据进行预处理:var定义的全局变量--->undefined,添加为window的属性;function声明的全局函数--->赋值(fun),添加为window的方法;this ---> 赋值为window;3⃣️ 开始执行全局代码
(3)函数执行上下文
在调用函数时,准备执行函数体之前,创建对应的函数执行上下文对象(虚拟的,存在于栈中)
对局部数据进行预处理:1⃣️ 行参变量--->赋值为实参---> 添加为执行上下文的属性;2⃣️ aruguments ---> 赋值(实参列表),添加为执行上下文的属性;3⃣️ var定义的全局变量--->undefined,添加为执行上下文 的属性;function声明的全局函数--->赋值(fun),添加为执行上下文的方法;this ---> 赋值为调用函数的对象; 4⃣️ 开始执行函数体代码
函数执行完毕后,函数内部所有的变量都会释放
function fn(a1){
//可以访问到的:
console.log(a1);//2
console.log(a2); //undefined
a3(); //a3()
console.log(this); //window
console.log(arguments); //类数组,伪数组(2,3)
var a2 = 3;
function a3(){
console.log(a3);
};
};
fn(2, 3);
3. 执行上下文栈
*函数调用的时候才会产生函数执行上下文,定义的时候不会产生,上下文产生的次数是函数执行的次数+1(window)
//进入全局上下文
var a =10;
var bar = function(x){
var b=5;
fn(x+b); // 进入fn函数执行上下文
};
var fn = function(y){
var c = 5;
console.log(a+c+y);
};
bar(10); //进入bar函数执行上下文
//一共产生三个上下文对象
在全局代码执行前,JS引擎就会创建一个栈来存储管理所有的执行上下文对象;在全局执行上下文(window)确定后,将其添加到栈中(压栈);在函数执行上下文创建后,将其添加到栈中(压栈);在当前函数执行完后,将栈顶的对象移出(出栈);当所有代码执行完毕后,栈中只剩下window。
*在上面的永远先执行,后进先出
4. 面试题
//1. 依次输出什么?
//2.整个过程中产生了几个执行上下文?
//一共产生了五个执行上下文,window和f1-f4,只是f4产生了但并未执行console语句
console.log("gb:"+i); //gb: undefined
var i=1;
foo(1);
function foo(i){
if(i == 4){
return;
}
console.log("fb:"+i); //fb:1 fb:2 fb:3
foo(i+1);//递归函数:自己在内部调用自己
console.log("fe:"+i); // fe:3 fe:2 fe:1
};
console.log("ge:"+i); //ge:1
//测试题1:
function a(){};
var a;
console.log(typeof a; //function
//测试题2:
if(!(b in window)){
var b =1;
};
console.log(b); //undefined,变量b在window中被声明提前
//测试题3:
var c =1;
function c(c){
console.log(c);
};
c(2);//c is not a function
▶️ 函数提升优先级高于变量提升,且不会被同名变量声明覆盖,但是会被变量赋值后覆盖。而且存在同名函数与同名变量时,优先执行函数。
🌟 作用域与作用域链
1. 作用域
作用域就是一块“地盘”,一个代码段所在的区域;它是静态的(相对于上写文对象而言),在编写代码时就确定了。
分类:全局作用域;函数作用域;块作用域
作用:隔离变量,不同作用域下同名变量不会有冲突
var a = 10,b = 20;
function fn(x){
var a = 100,c = 300;
console.log("fn",a,b,c,x);
function bar(x){
var a = 1000, d = 400;
console.log("bar",a,b,c,d,x);
};
bar(100); //bar 1000 20 300 400 100
bar(200);//bar 1000 20 300 400 200};
fn(10); //fn 100 20 300 10
//三个作用域,定义的函数两个+window
2. 作用域和执行上下文的区别
区别1:
全局作用域之外,每个函数都会创建自己的作用域,作用域在函数定义时就已经确定了;
全局执行上下文环境是在全局作用域确定之后,JS代码执行之前创建;
函数执行上下文环境是在调用函数时,函数体代码执行之前创建。
区别2:
作用域是静态的,只要函数定义好了就一直存在,且不会再变化;
上下文环境是动态的,调用函数时创建,函数调用结束时上下文环境就会被自动释放。
联系:
执行上下文环境(对象)是从属于所在的作用域,全局上下文环境---> 全局作用域,函数上下文环境--->函数作用域
3. 作用域链
嵌套的作用域产生的由内向外的链
//寻找变量的时候其实就是沿着作用域链由内而外寻找
var a = 1;
function fn1(){
var b = 2;
function f2(){
var c = 3;
console.log(c);
console.log(b);
console.log(a);
console.log(d); //d is not defined
};
fn2();
};
fn1();
4. 面试题
//测试题1:
var x = 10;
function fn(){
console.log(x);
};
function show(f){
var x = 20;
f();
};
show(fn); //10
//作用域之间的关系在函数定义时就已经确定好了,不会再改变,所以fn访问不到show中的变量值
//测试题2:
var fn = function(){
console.log(fn);
};
fn(); //输出fn整个函数
var obj = {
fn2: function(){
console.log(fn2);} //console.log(this.fn2);可以
};
obj.fn2(); //fn2 is not defined,因为函数作用域时函数的大括号内,函数作用域未找到,则去全局作用域继续查找,没有找到则返回not defined