浏览器原理 08 # 块级作用域:var缺陷以及为什么要引入let和const?

简介: 浏览器原理 08 # 块级作用域:var缺陷以及为什么要引入let和const?

说明

浏览器工作原理与实践专栏学习笔记



前言

由于 JavaScript 存在变量提升这种特性,从而导致了很多与直觉不符的代码,这也是 JavaScript 的一个重要设计缺陷。


  • 分析为什么在 JavaScript 中会存在变量提升,以及变量提升所带来的问题
  • 介绍如何通过块级作用域并配合 let 和 const 关键字来修复这种缺陷



作用域(scope)

作用域是指在程序中定义变量的区域,该位置决定了变量的生命周期。通俗地理解,作用域就是变量与函数的可访问范围,即作用域控制着变量和函数的可见性和生命周期。


在 ES6 之前,ES 的作用域只有两种:

  • 全局作用域中的对象在代码中的任何地方都能访问,其生命周期伴随着页面的生命周期。
  • 函数作用域就是在函数内部定义的变量或者函数,并且定义的变量或者函数只能在函数内部被访问。函数执行结束之后,函数内部定义的变量会被销毁。


ES6 之前是不支持块级作用域的

在ES3开始,try /catch 分句结构中也具有块作用域。



块级作用域


块级作用域就是使用一对大括号包裹的一段代码,比如函数、判断语句、循环语句,甚至单独的一个{}都可以被看作是一个块级作用域。

//if块
if(1){}
//while块
while(1){}
//函数块
function foo(){}
//for循环块
for(let i = 0; i<100; i++){}
//单独一个块
{}


变量提升所带来的问题

1. 变量容易在不被察觉的情况下被覆盖掉

var myname = "极客时间"
function showName(){
  console.log(myname);
  if(0){
   var myname = "极客邦"
  }
  console.log(myname);
}
showName()


开始执行 showName 函数时的调用栈

20210321145221688.png


先使用函数执行上下文里面的变量,输出两个undefined


2. 本应销毁的变量没有被销毁

function foo(){
  for (var i = 0; i < 7; i++) {
  }
  console.log(i); 
}
foo()



在创建执行上下文阶段,变量 i 就已经被提升了,所以当 for 循环结束之后,变量 i 并没有被销毁。最后打印出来的是 7。


ES6 是如何解决变量提升带来的缺陷

ES6 引入了 let 和 const 关键字,从而使 JavaScript 也能像其他语言一样拥有了块级作用域。


let 和 const 的用法:

let x = 5
const y = 6
x = 7
y = 9 //报错,const声明的变量不可以修改


ES6 是如何通过块级作用域来解决上面的问题的?

1、存在变量提升的代码:

function varTest() {
  var x = 1;
  if (true) {
    var x = 2;  // 同样的变量!
    console.log(x);  // 2
  }
  console.log(x);  // 2
}



在编译阶段,会生成varTest 函数的执行上下文:

只生成了一个变量 x,函数体内所有对 x 的赋值操作都会直接改变变量环境中的 x 值。


20210321170355650.png


2、把 var 关键字替换为 let 关键字,改造后的代码如下:

function letTest() {
  let x = 1;
  if (true) {
    let x = 2;  // 不同的变量
    console.log(x);  // 2
  }
  console.log(x);  // 1
}


let 关键字是支持块级作用域的,所以在编译阶段,JavaScript 引擎并不会把 if 块中通过 let 声明的变量存放到变量环境中,这也就意味着在 if 块通过 let 声明的关键字,并不会提升到全函数可见。



JavaScript 是如何支持块级作用域的


JavaScript 引擎是通过变量环境实现函数级作用域的,那么 ES6 又是如何在函数级作用域的基础之上,实现对块级作用域的支持呢?


先看一下下面这段代码

function foo(){
    var a = 1
    let b = 2
    {
      let b = 3
      var c = 4
      let d = 5
      console.log(a)
      console.log(b)
    }
    console.log(b) 
    console.log(c)
    console.log(d)
}   
foo()


执行结果


20210321181729703.png


执行流程

第一步是编译并创建执行上下文

foo 函数的执行上下文


2021032118304191.png


  1. 函数内部通过 var 声明的变量,在编译阶段全都被存放到变量环境里面。
  2. 通过 let 声明的变量,在编译阶段会被存放到词法环境(Lexical Environment)中。
  3. 在函数的作用域块内部,通过 let 声明的变量并没有被存放到词法环境中。



第二步继续执行到代码块里面时


20210321184112709.png


在词法环境内部,维护了一个小型栈结构,栈底是函数最外层的变量,进入一个作用域块后,就会把该作用域块内部的变量压到栈顶;当作用域执行完成之后,该作用域的信息就会从栈顶弹出,这就是词法环境的结构。(变量是指通过 let 或者 const 声明的变量。)


当执行到作用域块中的console.log(a)这行代码时,就需要在词法环境和变量环境中查找变量 a 的值了,具体查找方式是:


   沿着词法环境的栈顶向下查询,如果在词法环境中的某个块中查找到了,就直接返回给 JavaScript 引擎


   如果没有查找到,那么继续在变量环境中查找。


查找过程:

20210321185937672.png


当作用域块执行结束之后,其内部定义的变量就会从词法环境的栈顶弹出

20210321191057897.png



块级作用域就是通过词法环境的栈结构来实现的,而变量提升是通过变量环境来实现,通过这两者的结合,JavaScript 引擎也就同时支持了变量提升和块级作用域了














目录
相关文章
|
3月前
|
存储 缓存 前端开发
浏览器缓存工作原理是什么?
浏览器缓存工作原理是什么?
|
Web App开发 消息中间件 监控
浏览器原理 39 # 页面性能工具:如何使用 Performance?
浏览器原理 39 # 页面性能工具:如何使用 Performance?
381 0
浏览器原理 39 # 页面性能工具:如何使用 Performance?
|
2月前
|
Web App开发 编解码 JavaScript
Safari浏览器不支持let声明的解决方式
Safari浏览器不支持let声明的解决方式
18 0
|
3月前
|
存储 安全 前端开发
浏览器跨窗口通信:原理与实践
浏览器跨窗口通信:原理与实践
54 0
|
3月前
|
消息中间件 JavaScript 前端开发
前端秘法进阶篇----这还是我们熟悉的浏览器吗?(浏览器的渲染原理)
前端秘法进阶篇----这还是我们熟悉的浏览器吗?(浏览器的渲染原理)
|
4月前
|
消息中间件 前端开发 Java
【面试题】前端必修-浏览器的渲染原理
【面试题】前端必修-浏览器的渲染原理
|
5月前
|
Web App开发 JavaScript 前端开发
从浏览器原理出发聊聊Chrome插件
本文从浏览器架构演进、插件运行机制、插件基本介绍和一些常见的插件实现思路几个方向聊聊Chrome插件。
778 0
|
8月前
|
安全 算法 网络协议
浏览器基础原理-安全: HTTPS
浏览器基础原理-安全: HTTPS
63 0
|
8月前
|
Web App开发 存储 监控
浏览器基础原理-安全: 渲染进程-安全沙盒
浏览器基础原理-安全: 渲染进程-安全沙盒
39 0
|
8月前
|
安全
浏览器基础原理-安全: CSRF攻击
浏览器基础原理-安全: CSRF攻击
54 0

热门文章

最新文章