JS作用域、作用链及this使用原理是什么


这篇文章主要介绍了JS作用域、作用链及this使用原理是什么的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇JS作用域、作用链及this使用原理是什么文章都会有所收获,下面我们一起来看看吧。变量提升:JavaScript代码执行过程中 JavaScript引擎把变量的声明部分和函数的声明部分提升到代码开头的行为 (变量提升后以undefined设为默认值)JavaScript代码的执行流程:有些人认为 变量提升就是将声明部分提升到了最前面的位置 其实这种说法是错的 因为变量和函数声明在代码中的位置是不会变的 之所以会变量提升是因为在编译阶段被JavaScript引擎放入内存中(换句话来说 js代码在执行前会先被JavaScript引擎编译 然后才会进入执行阶段)流程大致如下图那么编译阶段究竟是如何做到变量提升的呢 接下来我们一起来看看 我们还是以上面的那段代码作为例子执行图如下可以看到 结果编译后 会在生成执行上下文和可执行代码两部分内容执行上下文:JavaScript代码执行时的运行环境(比如调用一个函数 就会进入这个函数的执行上下文 确定函数执行期间的this、变量、对象等)在执行上下文中包含着变量环境(Viriable Environment)以及词法环境(Lexicol Environment) 变量环境保存着变量提升的内容 例如上面的myName 以及callName那既然变量环境保存着这些变量提升 那变量环境对象时怎么生成的呢 我们还是用上面的代码来举例子第一、三行不是变量声明 JavaScript引擎不做任何处理第二行 发现了function定义的函数 将函数定义储存在堆中 并在变量环境中创建一个callName的属性 然后将该属性指向堆中函数的位置第四行 发现var定义 于是在变量环境中创建一个personName的属性 并使用undefined初始化经过上面的步骤后 变量环境对象就生成了 现在已经有了执行上下文和可执行代码了 接下来就是代码执行阶段了总所周知 js执行代码是按照顺序一行一行从上往下执行的 接下来还是使用上面的例子来分析执行到callName()是 JavaScript引擎便在变量环境中寻找该函数 由于变量环境中存在该函数的引用 于是引擎变开始执行该函数 并输出"callName Done!"接下来执行到console.log(personName); 引擎在变量环境中找到personName变量 但是这时候它的值是undefined 于是输出undefined接下来执行到了var personName = 'james'这一行 在变量环境中找到personName 并将其值改成james以上便是一段代码的编译和执行流程了 相信看到这里你对JavaScript引擎是如何执行代码的应该有了更深的了解Q:如果代码中出现了相同的变量或者函数怎么办?A:首先是编译阶段 如果遇到同名变量或者函数 在变量环境中后面的同名变量或者函数会将之前的覆盖掉 所以最后只会剩下一个定义你在日常开发中有没有遇到过这样的报错根据报错我们可以知道是出现了栈溢出的问题 那什么是栈溢出呢?为什么会栈溢出呢?Q1:什么是栈呢?A1:一种后进先出的数据结构队列Q2:什么是调用栈?A2:代码中通常会有很多函数 也有函数中调用另一个函数的情况 调用栈就是用来管理调用关系的一种数据结构当我们在函数中调用另一个函数(如调用自身的递归)然后处理不当的话 就很容易产生栈溢出 比如下面这段代码既然知道了什么是调用栈和栈溢出 那代码执行过程中调用栈又是如何工作的呢?我们用下面这个例子来举例可以看到 我们在findOneDetail中调用了findName函数 那么调用栈是怎么变化的第一步:创建全局上下文 并将其压入栈底接下来开始执行personName = 'james'的操作 将变量环境中的personName设置为james第二步:执行findOneDetail函数 这个时候JavaScript会为其创建一个执行上下文 最后将其函数的执行上下文压入栈中接下来执行完tel = ‘110'后 将变量环境中的tel设置为110第三步:当执行detail = findName()时 会为findName创建执行上下文并压入栈中接下来执行完findName函数后 将其执行上下文弹出调用栈 接下来再弹出findOneDetail的执行上下文以及全局执行上下文 至此整个JavaScript的执行流程结束所以调用栈是JavaScript引擎追踪函数执行的一个机制 当一次有多个函数被调用时 通过调用栈就能追踪到哪个函数正在被执行以及各函数之间的调用关系点击source并打上断点刷新后就可以再Call Stack查到调用栈的信息(也可以通过代码中输入console.track()查看)当我们在写递归的时候 很容易发生栈溢出 可以通过尾调用优化来避免栈溢出作用域是指在程序中定义变量的区域,该位置决定了变量的生命周期。通俗地理解,作用域就是变量与函数的可访问范围,即作用域控制着变量和函数的可见性和生命周期我们都知道 使用var会产生变量提升 而变量提升会引发很多问题 比如变量覆盖 本应被销毁的变量依旧存在等等问题 而ES6引入了let 和const两种声明方式 让js有了块免费云主机域名级作用域 那let和const时如何实现块级作用域的呢 其实很简单 原来还是从理解执行上下文开始我们都知道 JavaScript引擎在编译阶段 会将使用var定义的变量以及function定义的函数声明在对应的执行上下文中的变量环境中创建对应的属性 当时我们发现执行上下文中还有一个词法环境对象没有用到 其实 词法环境对象便是关键之处 我们还是通过举例子来说明一下第一步:执行并创建上下文函数内部通过var声明的变量 在编译阶段全都被存放到变量环境里面了通过let声明的变量 在编译阶段会被存放到词法环境(Lexical Environment)中在函数的作用域内部 通过let声明的变量并没有被存放到词法环境中接下来 第二步继续执行代码 当执行到代码块里面时 变量环境中a的值已经被设置成了1 词法环境中b的值已经被设置成了2这时候函数的执行上下文就如下图所示:可以看到 当进入函数的作用域块是 作用域块中通过let声明的变量 会被放到词法环境中的一个单独的区域中 这个区域并不邮箱作用域块外面的变量 (比如声明了b = undefined 但是不影响外面的b = 2)其实 在词法作用域内部 维护了一个小型的栈结构 栈底是函数最外层的变量 进入一个作用域块后 便会将过海作用域内部耳朵变量压到栈顶 当作用域执行完之后 就会弹出(通过letconst声明的变量)当作用域块执行完之后 其内部定义的变量就会从词法作用域的栈顶弹出块级作用域就是通过词法环境的栈结构来实现的 而变量提升是通过变量环境来实现 通过这两者的结合 JavaScript引擎也就同时支持了变量提升和块级作用域了。在开始作用域链和闭包的学习之前 我们先来看下这部分代码在每个执行上下文的变量环境中 都包含了一个外部引用 用来执行外部的执行上下文 称之为outer当代码使用一个变量时 会先从当前执行上下文中寻找该变量 如果找不到 就会向outer指向的执行上下文查找可以看到callNamefindName的outer都是指向全局上下文的 所以当在callName中找不到personName的时候 会去全局找 而不是调用callNamefindName中找 所以输出的是curry而不是james作用域链是由词法作用域决定的词法作用域就是指作用域是由代码中函数声明的位置来决定的 所以词法作用域是静态的作用域 通过它就能够预测代码在执行过程中如何查找表示符所以词法作用域是代码阶段就决定好的 和函数怎么调用的没有关系我们来看下下面这个例子我们知道 如果是let或者const定义的 就会储存在词法环境中 所以寻找也是从该执行上下文的词法环境找 如果找不到 就去变量环境 还是找不到则去outer指向的执行上下文寻找 如下图JavaScript 中 根据词法作用域的规则 内部函数总是可以访问其外部函数中声明的变量 当通过调用一个外部函数返回一个内部函数后 即使该外部函数已经执行结束了 但是内部函数引用外部函数的变量依然保存在内存中 我们就把这些变量的集合称为闭包举个例子首先我们看看当执行到 foo 函数内部的return innerBar这行代码时调用栈的情况 你可以参考下图:从上面的代码可以看出 innerBar 是一个对象 包含了 getNamesetName的两个方法 这两个方法都是内部定义的 且都引用了函数内部的变量根据词法作用域的规则 getNamesetName总是可以访问到外部函数foo中的变量 所以当foo执行结束时 getNamesetName依然可以以后使用变量myNametest 如下图所示可以看出 虽然foo从栈顶弹出 但是变量依然存在内存中 这个时候 除了setNamegetName 其他任何地方都不能访问到这两个变量 所以形成了闭包那如何使用这些闭包呢 可以通过bar来使用 当调用了bar.seyName时 如下图可以使用chrome的Clourse查看闭包情况通常 如果引用闭包的函数是一个全局变量 那么闭包会一直存在直到页面关闭 但如果这个闭包以后不再使用的话 就会造成内存泄漏如果引用闭包的函数是各局部变量 等函数销毁后 在下次JavaScript引擎执行垃圾回收的时候 判断闭包这块内容不再被使用了 就会回收所以在使用闭包的时候 请记住一个原则:如果该闭包一直使用 可以作为全局变量而存在 如果使用频率不高且占内存 考虑改成局部变量相信大家都有被this折磨的时候 而this确实也是比较难理解和令人头疼的问题 接下来我将从执行上下文的角度来分析JavaScript中的this 这里先抛出结论:this是和执行上下文绑定的 每个执行上下文都有一个this接下来 我将带大家一起理清全局执行上下文的this和函数执行上下文的this、全局执行上下文的this和作用域链的最底端一样 都是指向window对象我们通过一个例子来看一下默认情况下调用一个函数 其执行上下文的this也是指向window对象那如何改变执行上下文的this值呢 可以通过apply call 和bind实现 这里讲下如何使用call来改变可以看到这里this的指向已经改变了使用对象来调用其内部方法 该方法的this指向对象本身的这个时候我们如果讲对象赋给另一个全局变量 this又会怎样变化呢在全局环境中调用一个函数 函数内部的this指向全局变量window通过一个对象调用内部的方法 该方法的this指向对象本身当使用new关键字构建好了一个新的对象 构造函数的this其实就是对象本身在默认情况下调用一个函数 其指向上下文的this默认就是指向全局对象window关于“JS作用域、作用链及this使用原理是什么”这篇文章的内容就介绍到这里,感谢各位的阅读!相信大家对“JS作用域、作用链及this使用原理是什么”知识都有一定的了解,大家如果还想学习更多知识,欢迎关注百云主机行业资讯频道。

相关推荐: Go中的数值类型有哪些

这篇“Go中的数值类型有哪些”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“Go中的数值类型有哪些”文章吧。 Go中数值类型有3种:1、整数类型,…

免责声明:本站发布的图片视频文字,以转载和分享为主,文章观点不代表本站立场,本站不承担相关法律责任;如果涉及侵权请联系邮箱:360163164@qq.com举报,并提供相关证据,经查实将立刻删除涉嫌侵权内容。

(0)
打赏 微信扫一扫 微信扫一扫
上一篇 03/28 09:55
下一篇 03/28 09:56

相关推荐