一、变量提升
JavaScript 代码执行过程中,JavaScript 引擎把var
变量的声明部分和函数的声明部分提升到代码开头的行为
具体表现形式:
对于变量的声明:
// 使用var声明变量和赋值
var name = 'gelx'
在js中,这一行给name赋值为’gelx’可以看成:
var name = undefined
name = 'gelx'
对于函数的声明:
// 函数声明会有两种方式
// 1.完整的函数声明
function sayName() {
console.log('name')
}
// 2.带变量的声明
var sayName = function() {
console.log('name')
}
在js中对于函数的声明,会被视为:
// 1.第一种情况不变,依旧为函数声明
function sayName(){
console.log('name')
}
// 2.会被视为声明、赋值两部分
var sayName = undefined
sayName = function() {
console.log('name')
}
二、执行流程
JavaScript 代码在运行时会经历两部分:编译阶段,执行阶段
2.1 编译阶段
编译阶段分为:
- 变量提升
- 执行部分代码
2.1.1 执行部分代码
js代码在经过编译后,会生成两部分内容:执行上下文和可执行代码
2.1.2执行上下文
执行上下文是 JavaScript 执行一段代码时的运行环境,确定该函数在执行期间用到的诸如 this、变量、对象以及函数等
2.1.3可执行代码
当js检测到代码中存在声明赋值、函数调用时,会记录下这些代码,最终变成可执行的代码
2.2执行阶段
JavaScript开始根据可执行代码,按照代码顺序一行一行进行执行
二、调用栈
在了解调用栈前,先了解了一下js创建执行上下文的情况:
- 当 JavaScript 执行全局代码的时候,会编译全局代码并创建全局执行上下文,而且在整个页面的生存周期内,全局执行上下文只有一份
- 当调用一个函数的时候,函数体内的代码会被编译,并创建函数执行上下文,一般情况下,函数执行结束之后,创建的函数执行上下文会被销毁
- 当使用 eval 函数的时候,eval 的代码也会被编译,并创建执行上下文
调用栈是 JavaScript 引擎追踪函数执行的一个机制
- 在js代码执行的时候,首先创建、执行全局上下文(压入栈中)
- 每调用一个函数,JavaScript 引擎会为其创建执行上下文,并把该执行上下文压入调用栈,然后 JavaScript 引擎开始执行函数代码
- 如果在一个函数 A 中调用了另外一个函数 B,那么 JavaScript 引擎会为 B 函数创建执行上下文,并将 B 函数的执行上下文压入栈顶
- 当前函数执行完毕后,JavaScript 引擎会将该函数的执行上下文弹出栈
当分配的调用栈空间被占满时,会引发“堆栈溢出”问题
三、块级作用域
3.1 作用域
作用域是指在程序中定义变量的区域,该位置决定了变量的生命周期。作用域就是变量与函数的可访问范围,作用域控制着变量和函数的可见性和生命周期
全局作用域:对象在代码中的任何地方都能访问,其生命周期伴随着页面的生命周期
函数作用域:在函数内部定义的变量或者函数,并且定义的变量或者函数只能在函数内部被访问。函数执行结束之后,函数内部定义的变量会被销毁
块级作用域:是使用一对大括号包裹的一段代码,比如函数、判断语句、循环语句,甚至单独的一个{}都可以被看作是一个块级作用域
//if 块
if(1){}
//while 块
while(1){}
// 函数块
function foo(){
//for 循环块
for(let i = 0; i<100; i++){}
// 单独一个块
{}
变量提升带来的问题:
- 变量容易在不被察觉的情况下被覆盖掉
- 本应销毁的变量没有被销毁
3.2 ES6解决的问题
ES6通过引入let
、const
声明引入了块级作用域
let:声明一个属性,在块级作用域中生效,且可以随意修改参数,初始化时不需要赋值
const:声明一个属性,在块级作用域中生效,不可以修改参数,初始化时必须要赋值
在编译并创建执行上下文时,遇到let
、const
变量声明时,这部分的变量将会被存放在上下文中的词法变量中,与var
声明的变量环境区分开
3.3 暂时性死区
let name = 'gx'
{
console.log(name); //报错
let name = 'gelx'
}
使用let
、const
时,由于不会产生变量提升,如果在声明前使用该参数时,该属性查找不到,就会报错
四、作用域&闭包
4.1 作用域链
在每个执行上下文的变量环境中,都包含了一个外部引用,用来指向外部的执行上下文
当一段代码使用了一个变量时,JavaScript首先会在“当前的执行上下文”中查找变量,如果在当前的变量环境中没有查找到,那么 JavaScript会继续在 当前执行上下文中的外部引用 所指向的执行上下文中查找
JavaScript引擎在全局执行上下文中查找变量的链条就称为作用域链。
4.2 词法作用域
词法作用域是指作用域是由代码中函数声明的位置决定的,所以词法作用域是静态的作用域,通过它就能够预测代码在执行过程中如何查找标识符。
词法作用域是代码阶段就决定好的,和函数是怎么调用的没有关系
4.3 闭包
JavaScript 中,内部函数总是可以访问其外部函数中声明的变量,当通过调用一个外部函数返回一个内部函数后,即使该外部函数已经执行结束了,但是内部函数引用外部函数的变量依然保存在内存中,我们就把这些变量的集合称为闭包。
4.3.1 回收闭包
- 如果该闭包会一直使用,那么它可以作为全局变量而存在;
- 如果使用频率不高,而且占用内存又比较大的话,那就尽量让它成为一个局部变量。
五、this
this 与执行上下文绑定,因此this也会有全局执行上下文中的 this、函数中的 this 和 eval 中的 this
5.1 全局执行上下文中的this
全局执行上下文中的 this 指向 window 对象
5.2 函数执行上下文中的this
在默认情况下调用一个函数,其执行上下文中的 this 指向 window 对象
在使用对象来调用其内部的一个方法,该方法的 this 指向对象本身
通过 new 关键字构建一个新对象,构造函数中的 this 指向就是新对象
5.2.1 new CreateObj对象的过程
- 首先创建了一个空对象 tempObj;
- 接着调用 CreateObj.call 方法,并将 tempObj 作为 call 方法的参数,这样当 CreateObj 的执行上下文创建时,它的 this 就指向了 tempObj 对象;
- 然后执行 CreateObj 函数,此时的 CreateObj 函数执行上下文中的 this 指向了 tempObj 对象;
- 最后返回 tempObj 对象。 ```javascript var tempObj = {}
CreateObj.call(tempObj)
return tempObj
``
可以通过
call、
apply、
bind`修改this的指向
5.3 this的缺陷
- 嵌套函数中的 this 不会从外层函数中继承
- 普通函数中的 this 默认指向全局对象 window
弥补方案:
- 声明一个
self
变量存储this,再利用变量的作用域机制传递给嵌套函数 使用ES6的箭头函数,因为箭头函数没有自己的执行上下文,所以它会继承调用函数中的 this
使用严格模式,严格模式下的函数执行上下文中的 this 值是 undefined