重新了解 javascript

最近学了很多东西,发现 js 的很多内容只是凭借着我的 主观意识,或者说 感觉。所以这需要系统地学习 以及整理一下

1、变量提升

首先要明确的一点是,在 es6 出现以前,js 只有四种作用域 ,

  1. 全局作用域
  2. 函数作用域
  3. eval 作用域
  4. with 作用域 (在 vue 模板语法的编译之中,就广泛地使用到了 这个),也正是通过这个,在 模板语法中才能 不适用 this 直接读取到 对应的数据

后面三种 不谈,就谈谈 前面两个

console.log(name)  // function name
console.log(age)   // undefined

function name() {}
var name = 'dong'
var age = 24

可以看到,在这种情况下,就出现了 变量提升

  1. 在很多文章中,都提到了 变量提升是为了解决 函数 调用的问题,但是 var 又是怎么回事呢?
  2. 首先  js 在执行之前,会先进行一遍 编译
  3. 编译的过程中,并不会执行代码
  4. 像上面的代码所示,会 解析到 name ,那么会 将 name 保存的环境变量中,但是 当前是一个函数,有 较高的优先级,会覆盖掉 var 的undefined 定义,函数体 也被放到了 环境变量中
  5. 第二个 var name 就如 第四点 提到的,优先级 比函数低,所以内部的存储 依旧是 function
  6. 到了 age ,则是将 age 放到环境变量中,并且默认赋值 为 undefined
  7. 然后开始执行,输入如代码中所示
  8. 但是有一点是需要注意的,那就是 function 定义不能穿透 if 等语句,如下代码所示,就是 类似于 var ,变量提升了,但是却不能对其进行赋值
console.log(f) // undefined
if (true) {
  function f () {}
}

2、let 和 const

正是因为 var 的变量提升以及 缺少 作用域 ,所以出现了 let 和const

  1. let 和 const 在 { } 中是存在作用域的,哪怕 那只是一个 空的 { }
  2. 在 上一点 提到的 编译过程之中,其实 let 和 const 在 执行之前也被 编译到了,那么 这个是如何做到 暂时性死区 和 作用域的呢?
  3. 在 编译到 let 和 const 之后,对应的值 就会被 放入 词法环境(相当于单独的环境变量)中,而不是 通常所说的 环境变量中,然后 引擎 在 执行的过程之中,会 特别 注意这一点,禁止 代码在 定义之前 进行访问
  4. 所以下面代码的执行结果 就很清晰了
  5. 当 为 var 的时候,由于 没有作用域的影响,var 变量提升了,变成了 全局作用域,5 个 i 指向了 同一个
  6. 当 为 let 的时候,由于 作用域的 影响,每一个 {} 都是一个单独的作用域,所以 出现了 5 个 不同 的 i
  7. 但是 在 for 循环中,实际上 出现了 两种作用域,一种似 for 循环本身的作用域,还有一种 是 { } 包裹的作用域
  8. 很多文章 都说 let 和 const 没有出现变量提升,但是在我看来,其实已经做了提升,但是 禁止你使用,这个就称作 暂时性死区,究其原因,就是 我 第四大点 提到的 ,js 先进行了一遍解析,之后再执行

for (let i = 0; i < 3; i ++) {
  let i = 'hhh'
  console.log(i)
}
// hhh
// hhh
// hhh

3、作用域链

讲作用域链,就肯定会谈到 闭包,以及 闭包的原理了

  1. 闭包的定义,只和 词法作用域 有关,而和在哪里执行无关
  2. 也就是说,一个函数的闭包,只和 你在哪里定义了 这个函数有关系
  3. 然后 引用 第二大点 的 let 和 const, 一个 作用域会先 从 词法环境 里面找,然后 再到 环境变量中找,一层一层得找下来,直到 匹配 或者 到全局环境 中发现未定义为止
  4. 这里又要讲到函数 调用栈的 概念了,也就是 先进后出 的 关系
  5. 在上文中提到的编译阶段,遇到函数的时候,会快速地 对这个函数 做一次 词法扫描,然后 将 函数中 用到的值保存到 堆空间中
  6. 所以 下面 输出 的是 outer 也是一目了然的事情了

 

3、this

由于 闭包的缺陷(不能 手动 指定 作用域 到底是什么 ),js 又引入了 this 的概念

  1. 严格模式下,函数调用的时候,内部指向的是 undefined ,而在非严格模式下,指向的是 window
  2. 嵌套函数中的 this 不会继承 外层函数的  this,这里就引出了 () => {}, 就像 下图 Vue 中的 mounted 一样,内部定义一个函数,结果 this 指向的是 undefined。。。这里其实就和 第一点说的一样,内部调用的时候,是 undefined
  3. 各种操作 this 的方法中,this 的指向 是有 权重的,总的来说,是 obj.fn < fn.bind(obj) / apply / call < new fn()
  4. 要注意的是,事件绑定(指向 当前 dom),以及 setTimeout 中的this 指向(指向 window)

4、那么 js 是怎么被解析的呢?

这里面就涉及到了 v8引擎的底层实现,我肯定是不知道的,只讲讲表面

  1. 正如上文所讲的,V8引擎 在执行之前 会编译一遍代码,
  2. 编译代码的时候,会将 你的代码 分成 一个又一个的 token,也就是 最小的不可分割的部分,例如 var 、 = 、 name 等等
  3. 将 token 转化为 AST 抽象语法树, 闭包、变量提升 就是在这个阶段完成的(大名鼎鼎的 babel、ESlint 就是 基于 AST 语法树  来进行 编译的)
  4. 如果 成功便成 AST ,那么自然相安无事,如果 变不成,那么就是 你写的代码有问题,出现了语法错误,这个时候就会抛出一个错误
  5. 但是 AST 依旧处于高层,所以 V8 会将 语法树 编译成为 字节码
  6. 事实上 字节码 依旧不能直接在 计算机中运行,而是需要 解释器 便成 机器码 才行
  7. 那么 为什么 不直接转为 机器码呢?
  8. 因为 机器码 占用的内存实在是太多,在 移动端的场景之中,很快就会 把内存给吃掉
  9. 然后 在执行的 过程之中,V8 引擎 发现有一段代码执行了多次,那么 就会把这段代码 单独提出来,转化成 字节码,保存进内存中
  10. 这样 你会发现,一段 js 代码 会越来越快,浏览器占用的 内存也会越来越高

5、把抽象的理论转到可视化

前端就是可视化的直接呈现着,那么有没有办法把上面的那些东西可视化呢?

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <script>
        function n() {
            var na = 'inner';
            return function () {
                debugger
                console.log(na)
            }
        }
        var fn = n()
        fn()

    </script>
</body>
</html>

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值