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
-
-