变量提升:JavaScript代码是按顺序执行的吗?
-
变量提升(Hoisting)
所谓的变量提升,是指在 JavaScript 代码执行过程中,JavaScript 引擎把变量的声明部分和函数的声明部分提升到代码开头的“行为”。变量被提升后,会给变量设置默认值 undefined。 -
JavaScript 代码的执行流程
- 编译阶段
输入一段代码,经过编译后,会生成两部分内容:执行上下文(Execution context)和可执行代码,执行上下文是 JavaScript 执行一段代码时的运行环境。 - 执行阶段
JavaScript 引擎开始执行“可执行代码”,按照顺序一行一行地执行。
- 编译阶段
-
代码中出现相同的变量或者函数怎么办?
一段代码如果定义了两个相同名字的函数,那么最终生效的是最后一个函数。 -
总结
JavaScript 代码执行过程中,需要先做变量提升,而之所以需要实现变量提升,是因为 JavaScript 代码在执行之前需要先编译。
在编译阶段,变量和函数会被存放到变量环境中,变量的默认值会被设置为 undefined;在代码执行阶段,JavaScript 引擎会从变量环境中去查找自定义的变量和函数。
如果在编译阶段,存在两个相同的函数,那么最终存放在变量环境中的是最后定义的那个,这是因为后定义的会覆盖掉之前定义的。
调用栈:为什么JavaScript代码会出现栈溢出?
-
哪些情况下代码才算是“一段”代码,才会在执行之前就进行编译并创建执行上下文?
- 当 JavaScript 执行全局代码的时候,会编译全局代码并创建全局执行上下文,而且在整个页面的生存周期内,全局执行上下文只有一份。
- 当调用一个函数的时候,函数体内的代码会被编译,并创建函数执行上下文,一般情况下,函数执行结束之后,创建的函数执行上下文会被销毁。
- 当使用 eval 函数的时候,eval 的代码也会被编译,并创建执行上下文。
-
什么是栈?
栈就是类似于一端被堵住的单行线,车子类似于栈中的元素,栈中的元素满足后进先
出的特点。 -
什么是 JavaScript 的调用栈?
在执行上下文创建好后,JavaScript 引擎会将执行上下文压入栈中,通常把这种用来管理执行上下文的栈称为执行上下文栈,又称调用栈。
如果在一个函数 A 中调用了另外一个函数 B,那么 JavaScript 引擎会为 B 函数创建执行上下文,并将 B 函数的执行上下文压入栈顶。当前函数执行完毕后,JavaScript 引擎会将该函数的执行上下文弹出栈。 -
栈溢出(Stack Overflow)
调用栈是一种用来管理执行上下文的数据结构,符合后进先出的规则。调用栈是有大小的,当入栈的执行上下文超过一定数目,JavaScript 引擎就会报错,我们把这种错误叫做栈溢出。
块级作用域:var缺陷以及为什么要引入 let 和 const?
正是由于 JavaScript 存在变量提升这种特性,从而导致了很多与直觉不符的代码,这也是 JavaScript 的一个重要设计缺陷。
-
作用域(scope)
作用域是指在程序中定义变量的区域,该位置决定了变量的生命周期。通俗地理解,作用域就是变量与函数的可访问范围,即作用域控制着变量和函数的可见性和生命周期。 -
变量提升所带来的问题
- 变量容易在不被察觉的情况下被覆盖掉
- 本应销毁的变量没有被销毁
-
ES6 是如何解决变量提升带来的缺陷
引入了 let 和 const 关键字,作用块内声明的变量不影响块外面的变量。 -
JavaScript 是如何支持块级作用域的
块级作用域就是通过词法环境的栈结构来实现的,而变量提升是通过变量环境来实现,通过这两者的结合,JavaScript 引擎也就同时支持了变量提升和块级作用域了。
作用域链和闭包 :代码中出现相同的变量,JavaScript引擎是如何选择的?
- 作用域链
bar 函数和 foo 函数的 outer 都是指向全局上下文的,这也就意味着如果在 bar 函数或者 foo 函数中使用了外部变量,那么 JavaScript 引擎会去全局执行上下文中查找。
我们把通过作用域查找变量的链条称为作用域链;作用域链是通过词法作用域来确定的,而词法作用域反映了代码的结构。
-
词法作用域
词法作用域就是指作用域是由代码中函数声明的位置来决定的,所以词法作用域是静态的作用域,通过它就能够预测代码在执行过程中如何查找标识符。
词法作用域是代码阶段就决定好的,和函数是怎么调用的没有关系。 -
块级作用域中的变量查找
首先是在 bar 函数的执行上下文中查找,但因为 bar 函数的执行上下文中没有定义 test 变量,所以根据词法作用域的规则,下一步就在 bar 函数的外部作用域中查找,也就是全局作用域。
- 闭包(closure)
在 JavaScript 中,根据词法作用域的规则,内部函数总是可以访问其外部函数中声明的变量,当通过调用一个外部函数返回一个内部函数后,即使该外部函数已经执行结束了,但是内部函数引用外部函数的变量依然保存在内存中,我们就把这些变量的集合称为闭包。比如外部函数是 foo,那么这些变量的集合就称为 foo 函数的闭包。
- 闭包使用原则
如果该闭包会一直使用,那么它可以作为全局变量而存在;但如果使用频率不高,而且占用内存又比较大的话,那就尽量让它成为一个局部变量。
this:从JavaScript执行上下文的视角讲清楚this
this 是和执行上下文绑定的,也就是说每个执行上下文中都有一个 this。
执行上下文主要分为三种——全局执行上下文、函数执行上下文和 eval 执行上下文,所以对应的 this 也只有这三种——全局执行上下文中的 this、函数中的 this 和 eval 中的 this。
-
全局执行上下文中的 this
全局执行上下文中的 this 是指向 window 对象的。这也是 this 和作用域链的唯一交点,作用域链的最底端包含了 window 对象,全局执行上下文中的 this 也是指向 window 对象。 -
函数执行上下文中的 this
-
通过函数的 call、bind 和 apply 方法设置
this 由参数决定。 -
通过对象调用方法设置
在全局环境中调用一个函数,函数内部的 this 指向的是全局变量 window。
通过一个对象来调用其内部的一个方法,该方法的执行上下文中的 this 指向对象本身。 -
通过构造函数中设置
首先 new 关键字会创建一个空的对象,然后会自动调用一个函数 apply 方法,将 this 指向这个空对象,这样的话函数内部的 this 就会被这个空的对象替代。
(1)若无 return 语句,则默认返回 this 即构造函数的实例。
(2)若有 return 语句,如果 return 了一个基本数据类型,则最终返回 this
(3)若有 return 语句,如果 return 了一个对象,则最终返回这个对象
-
-
this 的设计缺陷以及应对方案
-
嵌套函数中的 this 不会从外层函数中继承
var myObj = { name: " 极客时间 ", showThis: function () { console.log(this) // myObj function bar() { console.log(this) } bar() // Window } } myObj.showThis()
解决方案:
第一种是把 this 保存为一个 self 变量,再利用变量的作用域机制传递给嵌套函数。
第二种是继续使用 this,但是要把嵌套函数改为箭头函数,因为箭头函数没有自己的执行上下文,所以它会继承调用函数中的 this。 -
普通函数中的 this 默认指向全局对象 window
在严格模式下,默认执行一个函数,其函数的执行上下文中的 this 值是 undefined
-
网友评论