9-javascript优化 闭包 底层(01-02-06 )

243 阅读5分钟

预解析优点

  • 跳过未被使用的代码
  • 不生成AST,创建无变量引用和声明的scopes
  • 依据规范抛出特定错误
  • 解析速度更快

全量解析

  • 解析被使用的代码
  • 生成AST
  • 构建具体scopes信息,变量引用、声明等
  • 抛出所有语法错误
// 声明时未调用,因此会被认为是不被执行的代码进行预解析
function foo() {
  console.log('foo1)
}

// 声明时未调用,因此会被认为是不被执行的代码,进行预解析
function fn() {}

// 函数立即执行,只进一次全量解析
(function bar() {
})()

// 执行foo。,那么需要重新对foo函数进行全量解析,此时foo函数被解析了两次 foo()
foo()

堆栈

基本信息

  • 基本数据类型是按值进行操作
  • 基本数据类型值是存放在 栈区的
  • 无论我们当前看到的栈内存,还是后续引用数据类型会使用的堆内存都属于计算机内存
  • GO(全局对象)
var obj1 = { x: 100 }
var obj2 = obj1
obj1.y = obj1 = { x: 200 }
console.log(obj1.y)
console.log(obj2)
---
undefined 
{ x:100,y: {x:200}}

函数创建

  • 可以将函数名称看做是变量,存放在 VO 当中 ,同时它的值就是当前函数对应的内存地址
  • 函数本身也是一个对象,创建时会有一个内存地址,空间内存放的就是函数体代码(字符串形式的)

函数执行

  • 函数执行时会形成一个全新私有上下文,它里面有一个AO 用于管理这个上下文当中的变量

步骤:

  • 01 作用域链 <当前执行上下文, 上级作用域所在的执行上下文>
  • 02 确定 this
  • 03 初始化 arguments (对象)
  • 04 形参赋值:它就相当于是变量声明,然后将声明的变量放置于 AO
  • 05 变量提升
  • 06 代码执行

闭包

  • 闭包: 是一种机制:
    1. 保护:当前上下文当中的变量与其它的上下文中变量互不干扰
    2. 保存:当前上下文中的数据(堆内存)被当前上下文以外的上下文中的变量所引用,这个数据就保存下来了
  • 闭包:
    1. 函数调用形成了一个全新的私有上下文,在函数调用之后当前上下文不被释放就是闭包(临时不被释放)
var a = 1
function foo() {
  var b = 2
  return function (c) {
    console.log(c + b++)
  }
}

var f = foo()
f(5) // 7 之前b=2 之后b=3
f(10) // 13 之前b=3 之后b=4
  1. 浏览器都自由垃圾回收(内存管理)
  2. 栈空间 堆空间
  3. 堆:当前堆内存如果不被占用,就能被释放掉,如果我们确定后续不再使用这个内存的数据,也可以自己主动置空,然后浏览器就会对其进行回收。
  4. 栈:当前上下文是否由内容,被其他上下文的变量所占用,如果有则无法释放(闭包)
let a = 10
function foo(a) {
  return function (b) {
    console.log(b + (++a))
  }
}

let fn = foo(10)
fn(5) // 16 a 10->11
foo(6)(7)// 14 a 6->7 调用后会被释放
fn(20)// 32 a 11->12
console.log(a) 10 全局

循环添加事件

// 基础
for (var i = 0; i < aButtons.length; i++) {
  aButtons[i].onclick = function () {
    console.log(`当前索引值为${i}`)
  }
}
点击三个按钮,都会打出 3
---
处理这个有三种方式
 * 闭包
 * 自定义属性
 * 事件委托
---
// 闭包
for (var i = 0; i < aButtons.length; i++) {
  (function (i) {
    aButtons[i].onclick = function () {
      console.log(`当前索引值为${i}`)
    }
  })(i)
}
// 闭包
for (var i = 0; i < aButtons.length; i++) {
  aButtons[i].onclick = (function (i) {
    return function () {
      console.log(`当前索引值为${i}`)
    }
  })(i)
}
// es6的let
for (let i = 0; i < aButtons.length; i++) {
  aButtons[i].onclick = function () {
    console.log(`当前索引值为${i}`)
  }
}
// 
for (var i = 0; i < aButtons.length; i++) {
  aButtons[i].myIndex = i
  aButtons[i].onclick = function () {
    console.log(`当前索引值为${this.myIndex}`)
  }
}
// 事件委托
document.body.onclick = function (ev) {
  var target = ev.target,
    targetDom = target.tagName
  if (targetDom === 'BUTTON') {
    var index = target.getAttribute('index')
    console.log(`当前点击的是第 ${index} 个`)
  }
}

防抖和节流

为什么需要防抖和节流:

在一些高频率事件触发的场景下我们不希望对应的事件处理函数多次执行

场景:

  • 滚动事件
  • 输入的模糊匹配
  • 轮播图切换
  • 点击操作
  • ....
  • 浏览器默认情况下都会有自己的监听事件间隔( 4~6ms),如果检测到多次事件的监听执行,那么就 会造成不必要的资源浪费

前置的场景: 界面上有一个按钮,我们可以连续多次点击

  • 防抖:对于这个高频的操作来说,我们只希望识别一次点击,可以人为是第一次或者是最后一次
  • 节流:对于高频操作,我们可以自己来设置频率,让本来会执行很多次的事件触发,按着我们定义的频率减少触发的次数

防抖函数实现

<button id="btn">点击</button>
 var oBtn = document.getElementById('btn')
    /** 
     * handle 最终需要执行的事件监听
     * wait 事件触发之后多久开始执行
     * immediate 控制执行第一次还是最后一次,false 执行最后一次
    */
    function myDebounce(handle, wait, immediate) {

      // 参数类型判断及默认值处理
      if (typeof handle !== 'function') throw new Error('handle must be an function')
      if (typeof wait === 'undefined') wait = 300
      if (typeof wait === 'boolean') {
        immediate = wait
        wait = 300
      }
      if (typeof immediate !== 'boolean') immediate = false

      // 所谓的防抖效果我们想要实现的就是有一个 ”人“ 可以管理 handle 的执行次数
      // 如果我们想要执行最后一次,那就意味着无论我们当前点击了多少次,前面的N-1次都无用
      let timer = null
      return function proxy(...args) {
        let self = this,
          init = immediate && !timer
        clearTimeout(timer)
        timer = setTimeout(() => {
          timer = null
          !immediate ? handle.call(self, ...args) : null
        }, wait)

        // 如果当前传递进来的是 true 就表示我们需要立即执行
        // 如果想要实现只在第一次执行,那么可以添加上 timer 为 null 做为判断
        // 因为只要 timer 为 Null 就意味着没有第二次....点击
        init ? handle.call(self, ...args) : null
      }
    }

    // 定义事件执行函数
    function btnClick(ev) {
      console.log('点击了1111', this, ev)
    }

    // 当我们执行了按钮点击之后就会执行...返回的 proxy
    oBtn.onclick = myDebounce(btnClick, 200, false)

节流函数实现

    // 节流:我们这里的节流指的就是在自定义的一段时间内让事件进行触发

    function myThrottle(handle, wait) {
      if (typeof handle !== 'function') throw new Error('handle must be an function')
      if (typeof wait === 'undefined') wait = 400

      let previous = 0  // 定义变量记录上一次执行时的时间 
      let timer = null  // 用它来管理定时器

      return function proxy(...args) {
        let now = new Date() // 定义变量记录当前次执行的时刻时间点
        let self = this
        let interval = wait - (now - previous)

        if (interval <= 0) {
          // 此时就说明是一个非高频次操作,可以执行 handle 
          clearTimeout(timer)
          timer = null
          handle.call(self, ...args)
          previous = new Date()
        } else if (!timer) {
          // 当我们发现当前系统中有一个定时器了,就意味着我们不需要再开启定时器
          // 此时就说明这次的操作发生在了我们定义的频次时间范围内,那就不应该执行 handle
          // 这个时候我们就可以自定义一个定时器,让 handle 在 interval 之后去执行 
          timer = setTimeout(() => {
            clearTimeout(timer) // 这个操作只是将系统中的定时器清除了,但是 timer 中的值还在
            timer = null
            handle.call(self, ...args)
            previous = new Date()
          }, interval)
        }
      }

    }

    // 定义滚动事件监听
    function scrollFn() {
      console.log('滚动了')
    }

    // window.onscroll = scrollFn
    window.onscroll = myThrottle(scrollFn, 600)

TypeScriptJavaScript
JavaScript 的超集用于解决大型项目的代码复杂性一种脚本语言,用于创建动态网页。
可以在编译期间发现并纠正错误作为一种解释型语言,只能在运行时发现错误
强类型,支持静态和动态类型弱类型,没有静态类型选项
最终被编译成 JavaScript 代码,使浏览器可以理解可以直接在浏览器中使用
支持模块、泛型和接口不支持模块,泛型或接口
支持 ES3,ES4,ES5 和 ES6 等不支持编译其他 ES3,ES4,ES5 或 ES6 功能
社区的支持仍在增长,而且还不是很大大量的社区支持以及大量文档和解决问题的支持