只有理解了JavaScript执行上下文,才能更好的理解JavaScript语言本身:变量提升、作用域、闭包等。
变量提升指的是:JS代码在执行过程中,JS引擎会把变量的声明部分和函数的声明部分提升到代码开头的行为,变量提升后,会给变量设置默认值,这个值就是我们熟悉的undefined。
JavaScript是先编译后执行,在编译阶段变量的声明和函数的声明提升到代码开头,被JS引擎放入到内存中去了。
输入一段代码,经过JS引擎编译后,会生成两部分内容:执行上下文和可执行代码。 执行上下文是JavaScript执行一段代码的运行环境:比如调用一个函数,就会进入这个函数的执行上下文,确定函数在执行期间的诸如this、变量、对象以及函数等。 要谨记在执行上下文中存在一个变量环境(Variable Enviroment)和词法环境的对象,变量环境对象中保存了变量提升的内容。
JS引擎执行可执行代码,按照顺序一行一行执行。执行过程中遇到一些变量或者函数就去变量变量环境中查找。
JavaScript代码执行过程中,需要先做变量提升,这是因为代码执行前需要先编译,编译阶段JS引擎会将变量和函数的存放到变量环境中去,变量默认值为undefined,执行阶段,JS引擎会从变量环境中查找变量和函数,若在编译阶段,存在两个相同函数,会被第二个覆盖掉。 核心是要清楚JavaScript的执行机制:先编译后执行。
学习完执行上下文,本章学习调用栈。 学习调用栈我们可以了解JavaScript引擎背后的工作原理、有调试JavaScript代码的能力。
调用栈是用来管理函数调用关系的一种数据结构–这个函数调用另外一个函数。
通过几行代码来看函数调用
let a =1;
function add(){
let b=2;
return a + b
}
add()
通常把管理执行上下文的栈成为执行上下文栈,又称调用栈,调用栈是引擎追踪函数执行的一个机制。 可以通过查看浏览器的call stack或者在函数中输出console.trace()来查看调用栈。
function runStack(n){
if(n===0) return 100;
return runStack(n-2)
}
// 修改后
function runStack(n){
if(n===0) return 100;
return setTimeout(function(){runStack(n-2)},0)
}
块级作用域就是通过词法环境的栈结构来实现的,而变量提升是通过变量环境来实现,通过两者结合,JavaScript引擎也就同时支持了变量提升和块级作用域。
理解作用域链是理解闭包的基础,简单总结下作用域链,然后通过作用域链来理解什么是闭包。
其实在每个执行上下文变量环境中,都包含了一个外部引用,用来指向外部的执行上下文,我们把这个外部引用称为outer。 上句话明白之后,我们接着分析。 当一段代码中使用了一个变量,首先在"当前的执行上下文"的词法环境中查找该变量,若没有找到,继续在变量环境中去查找该变量。 如何它依旧没有找到,那么JavaScript引擎就会继续在outer所指向的执行上下文中查找。 我们就把这个查找变量过程的链条称为作用域链。
到这里,还需要解决的一个问题是,foo函数中调用bar函数,为什么bar函数内部的外部引用执行的是全局执行上下文,而不是foo函数的执行上下文呢?
词法作用域是指作用域是由代码中函数声明的位置来决定的,所有词法作用域是静态的作用域,通过它能够预测代码在执行过程中如何查找标识符。 词法作用域是代码编译阶段就决定好的,和函数是怎么调用的没有关系,即之和函数声明的位置有关系。
了解了作用域链,接着我们就要来聊聊闭包了。 确切的说,了解了变量环境、词法环境和作用域链等概念,来理解闭包就容易多了。 先看段代码:
function foo() {
var myName = "内部变量"
let test1 = 1
const test2 = 2
var innerBar = {
getName:function(){
console.log(test1)
return myName
},
setName:function(newName){
myName = newName
}
}
return innerBar
}
var bar = foo()
bar.setName("外部传入")
bar.getName()
console.log(bar.getName())
分析上面代码,调用栈是这么进行的: 首先,全局执行上下文压入栈底,其中的变量环境为bar == undefined,词法环境为空,其作用域链包含的外部引用outer为空。 接着,执行foo函数的时候,形成了foo函数的执行上下文,它的变量环境为 myName=‘内部变量’,innerBar为函数,词法环境为test1为1,test2为2,其作用域链包含的外部outer(通过函数的定义位置)为全局作用域。 然后foo函数执行完毕之后,一般情况下我们分析内部的变量环境和词法环境就会清空,但是由于foo函数返回的innerBar还在使用者test1和myName这两个变量,所以虽然foo函数的执行上下文从栈顶弹出,但是用到的两个变量还是依旧保存在了内存中,而这两个变量除了setName和getName这两个方法可以访问,其他无论什么情况都访问不了,这个时候我们称:foo函数为闭包。
在JavaScript中, 根据词法作用域的规则,内部函数总是可以访问其外部函数中声明的变量, 当通过调用一个外部函数返回一个内部函数后, 即使外部函数已经执行结束了,但内部函数中引用外部函数的变量依然保存在内存中, 我们就把这些变量的集合称为闭包。 比如外部函数是foo,那么这些变量的集合就称为foo函数的闭包。
之所以需要关注闭包的回收,是因为如果闭包使用不正确,会很容易造成内存泄漏。 通常,如果引用闭包的函数是一个全局变量,那么变量会一直存在页面直到页面关闭,但如果这个闭包以后不再使用的话,就会造成内存泄漏。 如果引用闭包的是一个局部变量,那么当函数销毁时,在下次JavaScript引擎执行垃圾回收时,会判断闭包这块内容是否已经不再使用,若不再使用,那额就回收这块内存。 最后,记住一个原则:如果该闭包一直使用,那么它可以作为全局变量而存在,如果使用频率不高且占用内存较大,那么尽量让它称为一个局部变量。
作用域链和this是两套不同的系统,它们之间没有太多的联系。 在前文中,我们提到了执行上下文包含了:变量环境、词法环境、外部环境outer,这一节加上我们要分析的this,也就是四个部分。
this是和执行上下文进行绑定的,而我们已经知道了执行上下文包括全局执行上下文、函数执行上下文以及eval执行上下文,那么对应的this也只要这三种,撇去这个eval中的this,我们重点关注全局执行上下文中的this和函数执行上下文中的this。
全局执行上下文中的this,通过控制台打印的方式验证,我们知道这个this就是window。 然后,重点分析的就是函数中的this了,从下面最简单代码开始分析。
function foo(){
console.log(this)
}
foo()
打印结果为this,也就是:“默认情况下调用一个函数,函数内部的执行上下文中的this也是指向Window对象” 那么,能不能设置函数内执行上下文中this的指向呢?That must be!
应用:找出一个数组const arr = [1,10,7]中最大的数:Math.max.call(null,…arr) or Math.max.apply(null,arr)
let myObj = {
name:'liugezhou',
showThis:function(){
console.log(this)
}
}
myObj.showThis()
以上代码输出的为myObj对象,通过这个小例子,我们可以得出结论: 使用对象调用内部的一个方法,该方法的this是指向对象本身的。
function CreateObj(){
this.name='liugezhou'
}
let myObj = new CreateObj()
这几行代码的理解,可以让我们对new一个对象内部过程到底发生了什么,产生深刻的理解。 首先,new的时候呢,创建了一个临时 空对象, 然后,调用CreateObj的call方法,将临时对象作为参数传入 接着,执行CreateObj方法的时候,内部的this就是这个临时对象 最后返回这个临时对象,也就得到了我们的myObj。 ¸
也就是说在一个对象中定义个方法,输出的this为这个对象,然后这个对象中的方法继续定义个方法,输出的this执行的就是window,因此在解决this执行的时候,可以在该对象内部的方法中定义一个变量that指向this,然后这个对象中方法的方法输出的that就是该对象。 当然,我们现在的做法一般是在该对象内部的方法中的方法使用ES6的箭头函数,这样就不用再去定义一个中间变量了。 也就告诉我们箭头函数不会创建自己的执行上下文,箭头函数中的this取决于他的外部函数。
这个原理我们已经清楚,这也是一个设计缺陷,在我们编写代码的时候,并不希望函数中的this是指向全局的,因此会容易造成误操作,,像我们前面学的,通过call方法来改变this指向。
扫码关注腾讯云开发者
领取腾讯云代金券
Copyright © 2013 - 2025 Tencent Cloud. All Rights Reserved. 腾讯云 版权所有
深圳市腾讯计算机系统有限公司 ICP备案/许可证号:粤B2-20090059 深公网安备号 44030502008569
腾讯云计算(北京)有限责任公司 京ICP证150476号 | 京ICP备11018762号 | 京公网安备号11010802020287
Copyright © 2013 - 2025 Tencent Cloud.
All Rights Reserved. 腾讯云 版权所有