JavaScript执行上下文、作用域、this难点总结
一、执行上下文
当函数执行(调用)时,会创建一个称为执行上下文的内部对象。一个执行上下文定义了一个函数执行时的环境。
因为不同的调用可能有不同的参数。因此执行上下文是在执行的时候定义(产生)的。
当javascript代码文件被浏览器载入后,默认最先进入的是一个全局的执行上下文。当在全局上下文中调用执行一个函数时,程序流就进入该被调用函数内,此时引擎就会为该函数创建一个新的执行上下文,并且将其压入到执行栈顶部。一旦执行完毕,该执行上下文就会从执行栈顶部弹出(这个上下文环境以及其中的数据都会被销毁),并且将控制权交给即将进入的执行上下文。这样,执行栈中的执行上下文就会被依次执行并且弹出,直到回到全局的执行上下文。处于活动状态的执行上下文环境只有一个,即是执行栈顶部的那个。
函数每次执行时对应的执行上下文都是独一无二的,所以多次调用同一个函数会创建多个执行上下文。
二、作用域
作用域只是一个“地盘”,函数会创建自己的作用域。作用域在函数定义时就已经确定了。作用域只是用于划分你在这个作用域里定义的变量的有效范围,出了这个作用域就无效。
作用域链
函数在定义的时候(而不是调用的时候)就已经确定了函数体内部自由变量的作用域。
比如a,在fn作用域使用,但是并没有在fn作用域定义。
let a = 100
function fn() {
let b = 20
function bar() {
console.log(a + b)
}
return bar
}
let x = fn(), b = 200
x()
那么a的值要如何得到的呢?这就引出了作用域链。
bar要取得a的值,就要到创建bar这个函数的作用域中取值(bar定义在fn中,它的作用域就是fn),在fn没有找到a,就到创建fn这个函数的作用域中取值(这里是全局作用域),于是找到了a。这就是作用域链。
三、上下文和作用域的区别
每个函数的调用都有与之相关的作用域和上下文。作用域基于函数,上下文基于对象。作用域与函数每次调用时变量的访问有关(可以通过作用域链查找),且每次调用都是独立的。上下文则是当前的执行环境,即调用当前可执行代码所在的对象。
同一个作用域下,对同一个函数的不同调用会产生不同的执行上下文环境,继而产生不同的变量的值,所以,作用域中变量的值是在执行过程中确定的,而作用域是在函数创建时就确定的。
如果要查找一个作用域下某个变量的值,就需要找到这个作用域对应的执行上下文环境,再在其中找到变量的值。
四、this关键字
上下文中this关键字的值,表示调用当前可执行代码的对象的引用。它通常取决于一个函数如何被调用。
(1)当函数作为对象的方法被调用时,this 指向调用方法的对象。
(2)当通过 new 操作符创建一个对象的实例,以这种方式来调用时,this 指向新创建的实例。
(3)当调用一个未绑定函数,this 默认指向全局上下文或者浏览器中的window对象。然而如果函数在严格模式下被执行(“use strict”),this 默认指向 undefined。
(4)箭头函数中的this
this在箭头函数中,可以看做一个普通变量。即通过作用域链向上查找
如果需要通过对象本身来调用的函数,就用普通方式定义(这样调用时this明确指向该对象),如果不是就一律使用箭头函数来定义,这样this的值就跟随函数所在的作用域中那个this。
如下例箭头函数所在的作用域就是outerDiameter函数。而outerDiameter被调用时this是circle,因此innerDiameter this的值也是circle。
var circle = {
radius: 10,
outerDiameter() { // 普通定义
var innerDiameter = () => { // 使用箭头函数
console.log(2 * this.radius);
};
innerDiameter();
}
};
circle.outerDiameter(); // 打印20