JS编程学习笔记

111 阅读15分钟

JS编程

ECMAScript(ES)

JS的语言本身

通常看作JS的标准化规范

实际上JS是ECMAScript的扩展语言

ECMAScript只提供了最基本的语法。

JS

  • Web环境

    • ECMAScript

    • WebApis

      • BOM
      • DOM
  • Node环境

    • ECMAScript

    • NodeApis

      • fs
      • net
      • etc.

ES2015

  • 又称ES6

    • 特指ECMAScript2015。

    • 用于泛指所有的新标准。

      • “使用ES6的async和await”
  • 相比于ES5.1的变化比较大

  • 自此,标准命名规则发生变化。

  • 新特性

    • let与块级作用域

      • 作用域:某个成员能够起作用的范围。

      • 在这之前存在两种作用域

        • 全局作用域
        • 函数作用域
      • 增加了块级作用域

        • 用花括号包起来的范围
        • 块级作用域定义的变量外部不能访问。
    • const

      • 声明衡量或常量

      • 在let基础上多了一个只读的特性。

        • 变量一旦声明后不允许修改
    • 数组解构

      • const array = ['时解之','三漂亮'] const [ sjz, spl ] = array
    • 对象解构

      • const obj = { name: '时解之', age: 31 }
      • const { name } = obj
    • 模板字符串字面量

      • 支持多行输入
      • 支持插入JS变量或表达式
      • 实例: const name = '时解之' console.log(I'm ${name})
    • 模板字符串标签函数

    • 字符串的扩展方法

      • startsWith
      • endsWith
      • includes
    • 参数默认值

    • 剩余参数

    • 展开数组

      • console.log(...['时解之','三漂亮'])
    • 箭头函数

      • const fun = n => n+1

      • 箭头函数与this

        • 箭头函数不会改变this指向。
    • 对象字面量增强

      • const bar = 515 const obj = { foo: 666, bar, add () { return this.bar + 1 }, [Math.random()]: '计算属性名' }
    • 对象扩展方法

      • Object.assign

        • 可将多个源对象的属性复制到一个目标对象中。
      • Object.is

        • +0 === -0 : true Object.is(+0,-0) : false
        • NaN === NaN : false Object.is(NaN,NaN) : true
    • Proxy

      • new Proxy(targetObj, handlerObj)

      • vs. Object.defineProperty()

        • defineProperty只能监视属性的读写

        • Proxy能够监视到更多对象操作

          • delete
          • 对象中方法调用
          • etc.
        • Proxy是以非侵入式的方式监管了对象的读写

    • Reflect

      • 统一的对象操作API
      • Reflect内部封装了一系列对对象的底层操作。
      • Reflect成员方法就是Proxy处理对象的默认实现。
      • 统一提供了一套用于操作对象的API
    • Promise

      • 一种更优的异步编程解决方案
      • 解决了传统异步编程中回调函数嵌套过深的问题。
    • class类

      • 实例方法

      • 静态方法

        • ES2015中新增添加静态成员的static关键词。
        • 方法前➕static
      • 类的继承

        • extends
        • 用super表示父类实例
    • Set

      • add

        • 可以链式调用
      • delete

      • clear

      • has

    • Map

      • set
      • get
      • has
      • delete
      • clear
    • Symbol

      • 一种全新的原始数据类型

      • 表示一个独一无二的值

      • Symbol() === Symbol() : false

      • 可以用来真正实现私有对象成员

      • 最主要的作用就是为对象添加独一无二的属性名。

      • Symbol.for('sjz') === Symbol('sjz') : true

      • Symbol.for(true) === Symbol.for('true') : true

      • 内置常量

        • Symbol.iterator
        • Symbol.hasInstance
        • Symbol.toStringTag
      • 通过Object.getOwnPropertySymbols(obj)获取Symbol属性

    • for...of循环

      • 遍历所有数据结构的统一方式

      • 循环体中可以直接使用break终止循环

      • 目前支持情况

        • 数组
        • Set
        • Map
        • 不支持迭代对象
    • 可迭代接口

      • ES中能够表示有结构的数据类型越来越多。

      • 为了给各种各样的数据结构提供统一的遍历方式。

      • 是可以使用for...of的前提

      • 可迭代对象

        • 内部必须要有一个返回迭代器的Symbol.iterator属性方法

          • 返回的iterator对象

            • 包含next方法

              • 返回带value和done的IterationResult对象。
    • 迭代器模式

    • 生成器Generator

      • 避免异步编程中回调嵌套过深

      • 提供更好的异步编程解决方案

      • 函数体中配合yield关键字

      • 惰性执行

      • 应用

        • 发号器
        • 实现iterator方法
      • 由function * 定义

    • ES Modules

      • 语言层面的模块化标准

ES2016

  • 数组实例对象的includes方法

    • 传统使用indexOf查找数组中是否存在某个元素,不存在返回-1。缺点是不能查找NaN。
    • 可以查找NaN。
  • 指数运算符

    • 2 ** 10

ES2017

  • Object.values

  • Object.entries

  • Object.getOwnPropertyDescriptors

  • String.prototype

    • padStart
    • padEnd
  • 在函数参数中添加尾部逗号

  • async/await

TypeScript语言

解决JS类型系统的问题

可以大大提高代码的可靠程度

JS自有类型系统的问题

类型安全

  • 强类型

    • 语言层面限制函数的实参类型必须与形参类型相同。
    • 有更强的类型约束。
    • 不允许任意的隐式类型转换。
    • 语言的语法层面做限制
  • 弱类型

    • 语言层面不会限制实参的类型。
    • 几乎没有什么约束。
    • 允许任意的数据隐式类型转换。

类型检查

  • 静态类型

    • 一个变量声明时它的类型就是明确的。
    • 变量声明过后,它的类型就不允许再修改。
  • 动态类型

    • 运行阶段才能明确变量类型
    • 而且变量类型可以随时发生变化。
    • 也可以理解为变量是没有类型的。 而变量中存放的值是有类型的。

JS的类型系统特征

  • 弱类型

    • 存在问题

      • 某些类型异常需要等到运行时才能被发现。
      • 类型不明确会造成函数功能发生改变。
      • 造成对对象索引器错误的用法。
    • 强类型优势

      • 可以在运行前提前消灭一大部分类型异常问题。
      • 错误更早暴露
      • 代码更加智能,编码更准确。
      • 重构更加牢靠。
      • 减少不必要的类型判断。
  • 动态类型

  • 缺失了类型系统的可靠性。

  • JS没有编译环节

Flow

  • JS的类型检查器

  • 2014年由Facebook推出。

  • 采用类型注解声明数据类型

  • 并不要求给所有的变量都添加类型注解。

  • 相对于Typescript, Flow只是一个小工具。

  • 安装: yarn add flow-bin

  • 使用: // @flow

  • 初始化flow配置文件: yarn flow init

  • 通过编译 移除 类型注解

    • yarn add flow-remove-types --dev

      • yarn flow-remove-types src -d dist
    • yarn add @babel/core @babel/cli @babel/preset-flow --dev

      • 添加.babelrc

        • 内容: { "presets": ["@babel/preset-flow"] }
      • yarn babel src -d dist

  • Flow开发工具插件

    • VSCode extensions: Flow Language Support

      • 保存后才会检测类型错误。
  • 类型推断

  • 类型注解

    • 函数参数

    • 函数返回值

    • 变量

    • 类型

      • 原始类型

        • :string

        • :number

          • NaN
          • Infinity
        • :boolean

          • false
          • true
        • :null

          • null
        • :void

          • undefined
        • :symbol

          • new Symbol()
      • T: number | string | boolean | null | void | symbol

      • 数组类型

        • :Array

        • :T[]

        • :[T, T, ...]

          • 元组
      • 对象类型

        • :{ key?: T, key2: T, ... ,keyn: T}
        • :{ [T]: T }
      • 函数类型

        • : (T, T, ..., T) => T
      • 特殊类型

        • 字面量类型

          • : 'foo'
        • 联合字面量类型

          • :'foo' | 'bar' | 'sjz'
        • 联合类型

          • : T | T
        • type关键字声明联合类型

          • type StringOrNumber = string | number
          • 使用: :StringOrNumber
        • Maybe类型

          • :?T
        • Mixed 与 Any

          • : mixed

            • 所有类型的联合类型
            • 强类型
            • 使用时需要明确具体类型后再使用。
            • 通过typeof进行明确。
          • : any

            • 弱类型
            • 为了兼容以前的一些历史代码。
  • 运行环境API

    • 内置对象

      • 浏览器环境API
      • Node环境API

TypeScript

  • JS的超集

    • JS
    • 类型系统
    • ES6+
  • 最终会被编译为JS

  • 任何一种JS运行环境都支持

  • 功能更为强大,生态也更健全、更完善。

  • 使用示例

    • Angular
    • Vue.js 3.0
  • 前端领悟中的第2语言。

  • 缺点

    • 语言本身多了很多概念。
    • 项目初期,TypeScript会增加一些成本。
  • 优点

    • TypeScript属于☞渐进式
  • 快速上手

    • 初始化package.json文件: yarn init --yes

    • 安装: yarn add typescript --dev

    • 新建.ts扩展名文件编写代码。

    • 编译: yarn tsc filename.ts

      • 检查代码中的类型类型使用异常。
      • 移除掉类型注解中的一些扩展语法
      • 自动转换ES的新特性。
  • 配置文件

    • 生成: yarn tsc --init
    • 编译整个项目时配置才会生效
  • 类型注解

    • 原始类型

      • : string

        • 不同于Flow, 非严格模式下这里允许为空
      • : number

        • 不同于Flow, 非严格模式下这里允许为空
      • : boolean

        • 不同于Flow, 非严格模式下这里允许为空
        • true
        • false
      • : void

        • null
        • undefined
      • : null

      • : undefined

      • : symbol

        • new Symbol()
    • 标准库声明

      • 标准库就是内置对象所对应的声明。
    • 中文错误消息

      • 设置: Preferences ⥤ Settings⥤ 搜索 typescript locale⥤选择 zh-CN
    • 作用域问题

      • 解决1: 立即执行函数
      • 解决2: export {}
    • Object类型

      • 并不单指普通的对象类型。

      • 泛指所有的非原始类型。

        • 对象
        • 数组
        • 函数
      • : object

      • : { key: T, key2: T,..., keyn: T }

        • 对象属性不能多也不能少。
        • 不建议使用。建议用接口方式定义
    • 数组类型

      • :Array
      • :T[]
    • 元组类型

      • :[T, T, ...]
    • 枚举类型

      • 声明: enum EnumName { enum1 = 1, enum2 = 2 }

      • 使用: EnumName.enum1

      • 会入侵到运行时的代码。

      • 常量枚举

        • 定义: const enum EnumName { enum1: 1, enum2 }
        • 使用: EnumName.enum1
    • 函数类型

      • 函数声明

        • 示例: function func1 (a: T = 'T类型值' , b?: T,...rest: T[]): T { return 'T类型值' }
      • 函数表达式

        • : (a: T, b: T,..., n:T) ⥤ T
    • 任意类型

      • : any
    • 隐式类型推断

      • 示例: let age = 31
      • 无法推断出类型时,会用any
    • 类型断言

      • as T

        • 易与JSX语法混淆。
    • 接口

      • 用来约定对象的结构
      • 定义: interface IName { field1: T field2: T selectableField?: T readonly readOnlyField: T [dynamicField: T]: T }
      • 描述一类事物的抽象特征

      • 用来描述一类对象的抽象成员

      • ES6以前,函数+原型 模拟实现类。

      • ES6开始有专门的类class

      • TypeScript增强了class的相关语法。

      • 定义: class ClassName { field: T = '初始值' field2: T constructor (field:T, field2: T) { this.field2 = field2 } }

      • 访问修饰符

        • 定义: class ClassName { protected readonly field: T = '初始值' private privateField2: T constructor (field:T, field2: T) { this.privateField2 = field2 } static staticMethod (field1: T, field2: T) { return new ClassName(field1, field2) } }
        • 默认 public
        • private
        • protected
        • static
    • 类与接口

      • 接口就是一种协议
      • 类通过implements实现接口
    • 抽象类

      • 可以包含一些具体的实现

      • 不可以被实例化

      • 定义: abstract class

      • 只能够通过extends关键字被继承

      • 类体中可以通过abstract关键字定义抽象方法

        • 没有方法体
        • 只有方法签名
    • 泛型

      • 在定义函数,接口或类的时候没有指定类型。而是等到我们使用时再指定具体类型的特征。
      • 作用: 极大程度的复用代码。
      • 示例: function genericsFn (arg1: T, arg2: T): T { return arg1 + arg2 }
    • 类型声明

      • 主要针对第三方模块

      • 示例: lodash

        • yarn add @types/lodash --dev
      • declare function camelCase(input: string): string

      • .d.ts文件即为类型声明文件。

      • 有的模块自身自带类型声明模块,不用再单独安装或单独声明。

JS性能优化

JS内存管理

  • JS中内存管理是自动的

  • 内存

    • 由可读写单元组成,表示一片可操作空间
  • 管理

    • 人为的去操作一片空间的申请、使用和释放。
    • 管理流程:申请»使用»释放。
  • 开发者主动申请空间、使用空间、释放空间。

  • 申请空间: let obj = {}

  • 使用空间: obj.name = '时解之'

  • 释放空间: obj = null

JS中的垃圾回收

  • JS中的垃圾回收就是找到垃圾对象,然后进行释放和回收。

  • JS中的垃圾

    • 对象不再被引用时变成垃圾
    • 对象不能从根上访问到时变成垃圾
  • JS中的可达对象

    • 可以访问到的对象就是可达对象(引用、作用域链)
    • 可达的标准就是从根出发是否能够被找到。
    • JS中的根就可以理解为全局变量对象。

GC

  • GC是垃圾回收机制的简写

  • GC可以找到内存中的垃圾、并释放和回收内存空间

  • GC中的垃圾

    • 程序中不再需要使用的对象
    • 程序中不能再访问到的对象
  • GC算法

    • GC算法是什么?

      • GC是一种机制,垃圾回收器完成具体的工作
      • 工作的内容就是查找垃圾、释放空间、回收空间
      • 算法就是工作时查找和回收所遵循的规则。
    • 引用计数

      • 实现原理

        • 核心思路:设置引用数,判断当前引用数是否0。
        • 引用计数器
        • 引用关系改变时修改引用数字。
        • 引用数字为0的时立即回收。
      • 优点

        • 发现垃圾时立即回收
        • 最大限度减少程序暂停
      • 缺点

        • 无法回收循环引用的对象
        • 资源消耗较大
    • 标记清除

      • 实现原理

        • 核心思想:分标记和清除2个阶段完成。
        • 遍历所有对象标记所有可达对象为活动对象。
        • 遍历所有对象清除没有标记的对象,同时清除原来的标记。
        • 回收相应的内存空间
      • 优点

        • 可以回收循环引用的对象
      • 缺点

        • 容易产生碎片化空间,浪费空间。
        • 不会立即回收垃圾对象。
    • 标记整理

      • 实现原理

        • 标记整理可以看做是标记清除的增强。
        • 标记阶段的操作和标记清除一致。
        • 清除阶段会先执行整理,移动对象位置。
      • 优点

        • 减少碎片化空间
      • 缺点

        • 不会立即回收垃圾对象

V8

  • V8是一款主流的JS执行引擎

  • V8采用即时编译

  • V8内存设限(64位操作系统不超过1.5G,32位操作系统不超过800M。)

  • V8垃圾回收策略

    • 采用分代回收思想
  • V8内存空间

    • 新生代对象存储

      • V8内存空间一分为二
      • 小空间用于存储新生代对象(64操作系统是32M,32位操作系统是32M)
      • 新生代指的是存活时间较短的对象
    • 老生代对象存储

      • 老年代对象存放在右侧老生代区域。
      • 内存限制:64位操作系统1.4G,32位操作系统700M。
      • 老年代对象就是指存活时间较长的对象。
  • V8常用GC算法

    • 分代回收

      • 针对不同对象采用不同算法

      • 内存分为新生代、老年代

      • 实现原理

        • 新生代

          • 回收过程采用复制算法+标记整理。

          • 新生代内存区分为2个等大小空间即From、To。

          • 使用空间为From,空闲空间为To。

          • 活动对象存储于From空间。

          • 标记整理后将活动对象拷贝至To空间。

          • From与To交换空间完成释放。

          • 回收细节说明

            • 拷贝过程中可能出现晋升

              • 一轮GC后还存活的新生代需要晋升。
              • To空间的使用率超过了25%
            • 晋升就是将新生代对象移动至老生代。

        • 老年代

          • 主要采用标记清除、标记整理、增量标记算法。
          • 首先使用标记清除完成垃圾空间的回收。
          • 采用标记整理进行空间优化。
          • 采用增量标记进行效率优化。
        • 新生代、老年代垃圾回收细节对比

          • 新生代垃圾回收使用空间关时间。
          • 老年代区域垃圾回收不适合复制算法
    • 空间复制

    • 标记清除

    • 标记增强

    • 标记增量

      • 程序执行与垃圾回收交替执行。

Performance工具

  • 为什么使用Performance?

    • GC的目的是为了实现内存空间的良性循环。
    • 良性循环的基石是合理使用内存。
    • 时刻关注内存变化才能确定是否合理。
    • Performance提供多种监控方式。
    • 通过Performance时刻监控内存。
  • 内存问题的体现

    • 页面出现延迟加载或经常性暂停。
    • 页面持续性出现糟糕的性能。
    • 页面的性能随时间延长越来越差。
  • 监控内存的几种方式

    • 界定内存泄露的标准

      • 内存泄露:内存使用持续身高。
      • 内存膨胀:在多数设备上存在性能问题。
      • 频繁垃圾回收:通过内存变化图进行分析。
    • 浏览器任务管理器

      • Shift+Esc调出任务管理器

      • 观察两列值

        • 原生DOM内存
        • Javascript内存
    • Timeline时序图记录

    • 堆快照查找分离DOM

      • 什么是分离DOM

        • 界面元素存活在DOM树上。
        • 垃圾对象时的DOM节点。
        • 分离状态的DOM节点。
    • 判断是否存在频繁的垃圾回收

      • 为什么确定频繁垃圾回收

        • GC工作时应用程序是停止的
        • 频繁且过长的GC会导致应用假死。
        • 用户使用中感知应用卡顿。
      • Timeline中频繁的上升下降。

      • 任务管理器中数据频繁的增加减少。

代码优化介绍

  • 如何精准测试JS性能?

    • 本质上就是采集大量的执行样本进行数学统计和分析。
    • 使用基于Benchmark.js的JsPerf完成
  • 慎用全局变量

    • 为什么要慎用?

      • 全局变量定义在全局上下文,是所有作用域链的顶端。
      • 全局执行上下文一直存在于上下文执行栈,直到程序退出。
      • 如果某个局部作用域出现了同名变量则会遮蔽或污染全局。
  • 缓存全局变量

    • 将使用中无法避免的全局变量缓存到局部
  • 通过原型新增方法

    • 在原型对象上新增实例对象需要的方法。
  • 避开闭包陷阱

    • 闭包特点

      • 外部具有指向内部的引用
      • 在“外”部作用域访问“内”部作用域的数据。
      • 闭包是一种强大的语法
      • 闭包使用不当很容易出现内存泄露。
      • 不要为了闭包而闭包。
  • 避免属性访问方法的使用

    • JS中的面向对象

      • JS不需要属性的访问,所有属性都是外部可见的。
      • 使用属性访问方法只会增加一层重定义,没有访问的控制力。
  • for循环优化

    • 缓存循环数组长度后再使用
  • 选择最优的循环方法

    • 选Array之forEach
  • 文档碎片优化节点添加

    • 节点的添加操作必然会有回流和重绘。
  • 克隆优化节点操作

  • 直接量替换Object操作

  • JSBench

    • 堆栈中的JS执行过程

      • 堆:函数声明、对象、数组
      • 栈:创建执行上下文、字面量、局部变量
    • 减少判断层级

      • 提前判断无效条件并在无效时直接返回
    • 减少作用域链查找层级

    • 减少数据读取次数

    • 字面量与构造式

    • 减少循环体活动

    • 减少声明及语句数

    • 采用事件绑定

      • 采用事件委托

        • ul vs li