一、ECAMScript新特性
1.let、const
1)块级作用域
2)不能进行变量提升
const定义常量,一开始就要有初始值,不能修改
const obj = {}
obj.name = ‘hhh’
上述并没有修改obj的内存地址,所以是可以修改对象中的属性的。
2.数组的解构
3.对象的解构
4.模板字符串
- 可以直接换行
- 可以插入值
- 通过 \ 转译
const str = `name is \`hahha\``
// 输出
name is `hahha`
5.对象字面量的增强
// 对象的增强
const name = 5
const obj = {
a: 5,
name, // 同名缩写
foo() {}, // 相当于foo: function() {},这是缩写形式
[name]: 'yang', // 计算属性名[]会把里面的值计算出来
}
console.log(obj)
6.Proxy
代理,就是实现对 对象 的一个“拦截操作”
Proxy和Object.defineProperty的对比:
1)Proxy代理功能远比Object.defineProperty强大,Object只能拦截对象中的某个属性,而proxy能够对整个对象进行拦截
2)Object.defineProperty只能进行简单的set, get操作,Proxy不仅可以读取和设置,还可以拦截 in ,delete等操作。
3)Objec.defineProperty做递归处理只能一开始做递归处理,而Proxy在调用的时候就可以直接做递归处理,在性能上Proxy更优秀。
4)对象上设置新属性时,Proxy监听得到,Object.defineProperty监听不到
5)数组增删修改时,Proxy监听得到变化,Object.defineProperty监听不到
7.Reflect
为对象专门设计的api,主要是替代OBJECT
1)Object上的内部方法,例如Object.defineProperty ,都放在Reflect上
2)修改某些Object方法的返回结果,让其变得合理
3)提供了统一的api操作对象,比如has,ownKeys
4)Reflect的静态方法和Proxy上的是对应的,一共13个
8.Set、WeakSet、Map、WeakMap
8.1Set
一种新数据类型,跟数组类似,每一项的值是不重复的。
基本属性:size、constructor
有四个操作方法,add、has、delete、clear
有四个遍历方法,keys、values,entries、forEach
8.2WeakSet
与Set的主要区别是,它的每一项只接受对象,并且不可遍历
主要是WeakSet中的对象不计入垃圾回收机制。
因此只有 has、get、delete方法,没有size和forEach以及遍历方法
8.3 Map
javascript的对象,与普通对象的区别就是值->值,Map对象中键不仅限于字符串
基本属性: size
操作方法: has、set、get、delete、clear
遍历方法:keys、values、entries、forEach
8.4WeakMap
WeakMap与Map对象最大的区别是,WeakMap中的键只能是对象(Null除外),同样WeakMap中键 不计入垃圾回收机制,所以也不能有遍历方法和size属性,因此WeakMap只有四个方法可用
get、set、has、delete
9.Symbol
新增的一种基本类型,是唯一的。
- Symbol 作为属性名,遍历对象的时候,该属性不会出现在for…in、for…of循环中,也不会被Object.keys()、Object.getOwnPropertyNames()、JSON.stringify()返回。
- 可以使用Symbol.for()方法查找之前创建的symbol值,for方法是如果有就返回之前的symbol,如果没找到就重新创建一个symbol
而 如果调用Symbol(‘foo’)100次,则创建了100个symbol值 - 对象的Symbol.iterator属性指向该对象默认的遍历器方法
10.for…of… 方法
所有含有迭代器接口(即含有Symbol.iterator的方法)的都可以使用for…of…方法,但是一个普通object对象不能使用 此方法,会报错
es相关提案
es相关提案
已完成的提案
vscode 通过jsDoc增加智能提示,可以参考如下文档:https://www.jb51.net/article/163666.htm
二、Typescript
强类型的优势:
- 错误能更早暴露
- 代码更智能,编码更准确
- 重构更牢靠
- 减少不必要的类型推断。
Typescirpt定义
typescript是javascript的超集,是一个类型检测系统,能够在编译阶段进行代码类型检测
es6语法错误处理
- 在tsconfig.json中配置lib
类 class
1) private、protected、public关键字
private只允许成员在当前类中访问,外部不能访问
protected只允许在成员在当前类和子类中访问,外部不能访问
publick公共,所有地方都可以访问
2) 只读属性readonly
只能在初始值或者构造函数中初始化,只能选其一
类的实现接口
类的实现接口就是一种契约,它可以强制一个类去符合某种契约。
interface Eat {
eat(food: string): void
}
interface Run {
run(distance: number): void
}
class Person implements Eat, Run {
eat(food: string) {
console.log('eat')
}
run(distance: number){
console.log('run')
}
}
抽象类
关键字abstract
- 抽象类只能被继承不能被实例化
- 抽象方法,就是在抽象类中的方法前加上abstract关键字,在子类中一定要实现,否则就报错
// 抽象类
abstract class Animal {
eat(food: string){
console.log(`eat->${food}`)
}
// 抽象方法
abstract run(distance: number): void
}
class Cat extends Animal {
run(distance: number): void {
throw new Error("Method not implemented.")
}
}
let cat = new Cat()
cat.run(100)
三、性能优化
3.1 内存管理介绍
内存: 由可读单元组成,表示一片可操作的空间。
管理: 人为的去操作一片空间的申请、使用和释放
内存管理: 开发人员主动申请空间、使用空间和释放空间。
管理流程: 申请、使用、释放。
3.2 JavaScript中的垃圾回收
3.2.1javascript中的垃圾
- Javascript中内存管理是自动
- 对象不再被引用时是垃圾
- 对象不能从根上访问到时是垃圾
3.2.2 javascript中的可达对象
- 可访问到的对象就是可达对象(引用、作用域链)
- 可达对象的标准就是从根出发是否能被找到。
- Javascript中的根就可以理解为是全局变量对象
3.2.3 GC算法
GC的定义和介绍:
- GC就是垃圾回收机制的简写
- GC可以找到内存中的垃圾、并释放和回收空间
GC里的垃圾是什么:
GC算法是什么
- GC是一种机制,垃圾回收器完成具体的工作
- 工作内容就是查找垃圾、释放空间、回收空间
- 算法就是工作时查找和回收所遵循的规则
常用GC算法
引用计数、标记清除、标记整理、分代回收
1)引用计数算法
核心思想: 设置引用数,当引用关系发生改变时,修改计数,如果引用数为0就立马进行回收。
优点:
- 发现垃圾立即回收
- 最大限度减少程序占满。(当内存占满时,引用计数算法会立即找那些引用数为0的对象,进行回收,从而保证内存不会永远被占满)
缺点
- 无法回收循环引用的对象
/* 循环引用 */
function fn() {
const obj1 = {}
const obj2 = {}
obj1.name = obj2
obj2.name = obj1
}
fn()
- 时间开销大(因为它时刻要保持对象的计数)
2)标记清除算法
核心思想: 分标记和清除两个阶段完成
- 遍历所有对象标记活动对象
- 遍历所有对象清除所有没有被标记的对象,并清除活动对象的标记
- 回收相应的空间
优点:
- 相对引用计数方式,解决了循环对象的问题。
缺点:
- 容易产生碎片化空间,浪费空间
- 不会立即回收垃圾对象
B可达对象,A/C没有被引用的对象
当A、C被标记清除算法释放后,会被放到空闲链表,但是A的大小是2个,C的大小是1个,这样地址的不连续性,导致后面如果申请内存地址时只能申请2个或者1个,如果是1.5个则无法满足。
3)标记整理算法
核心思想: 标记整理算法可以看做是标记整理算法的增强
- 标记阶段的操作和标记清除算法一致
- 清除阶段会先进行整理,移动对象位置, 使得释放的空间连续
优点:
减少碎片化空间
缺点
不能立即回收垃圾对象
4)分代回收算法
3.2 V8引擎
3.1.1 认识v8
- v8是一款主流的javascript执行引擎
- v8是即时编译的,速度很快
- v8设有内存上限,64位系统一般不超过1.5g,32位系统一般不超过800M
3.1.2 V8垃圾回收策略
- 采用分代回收的思想
- 内存分为新生代、老生代
- 根据不同代的内存区采用不同的GC算法
v8中常见GC算法
- 分代回收
- 空间复制
- 标记清除
- 标记整理
- 标记增量
v8内存分配
- v8内存空间一分为二
- 小空间用存储新生代对象(64位 32M | 32位 16M)
- 新生代指的的是存活时间比较短的对象,比如一个函数内的对象,执行完不用的
左侧两个为新生代对象内存区域
v8中对新生代对象回收实现
- 回收过程中采用采用复制算法+标记整理
- 新生代内存也会被分为两个等大小空间
- 使用空间是From,空闲空间是To
- 活动对象存储于From空间
- 标记整理后将From空间活动对象拷贝至To
- From与To交换空间完成释放
回收细节:
- From拷贝到To过程中可能出现晋升,晋升就是将新生代对象移动至老生代
- 出现晋升的触发点:
1)一轮GC还存活的新生代需要晋升
2)To空间的使用率超过25%
v8中对老年代对象回收实现
老年代对象的说明:
- 老年代对象存放在右侧老生代区域
- 内存大小,64位操作系统1.4g,32位操作系统700M
- 老年代对象指的就是存活时间比较长的对象
实现:
- 主要采用标记清除、标记整理、增量标记算法
- 首先采用标记清除方法完成垃圾空间的回收
- 采用标记整理进行空间优化,当新生代对象晋升到老生代区域,并且老生代空闲空间不满足新生代晋升对象的空间时,这个时候就会进行老生代空间的标记整理,整理碎片化空间给新生代晋升对象使用。
- 采用增量标记效率优化,
垃圾回收,会阻塞程序的执行,标记增量算法,就是将之前一口气执行的垃圾回收操作,分段执行,大大提升效率
3.1.3 Performance的使用步骤
performance主要是监听浏览器内存,进行内存性能优化
内存问题的外在表现
- 页面出现延迟加载或经常性暂停(垃圾回收)
- 页面持续性出现糟糕的性能(内存膨胀)
- 页面的性能随时间延长越来越差(内存泄漏)
界定内存问题的标注
- 内存泄漏:内存使用持续升高
- 内存膨胀(为了使代码运行最优突然申请使用大量内存):如果在多数设备上都存在性能问题就代表是代码的问题,否则就是设备的问题
- 频繁垃圾回收:通过内存变化图进行分析
监控内存的几种方式
浏览器任务管理器
TimeLine时序图记录
可以观察js内存的在哪个时间节点突然出现问题,一般,正常的是有上有下。如果一直向上就有可能是内存泄漏了,然后定位在那个时间节点就可一乐
堆快照查找分离DOM
界面元素存活在DOM树上
垃圾对象的DOM节点:从当前DOM树上脱离了,并且没有被引用
分离的DOM节点:从当前的DOM树上脱离了,但是被引用着(实际上就是内存泄漏)
测试代码
<!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>
<div>
<button id="btn">点击</button>
</div>
<script>
var tmpFile
function fn() {
var ul = document.createElement('ul')
for(var i =0; i< 10; i++) {
var li = document.createElement('li')
ul.append(li)
}
tmpFile = ul
tmpFile = null
}
document.getElementById('btn').addEventListener('click', fn)
</script>
</body>
</html>
步骤:开始之前点一次,操作之后点一次
搜索detach,也就是分离的意思,搜到了就代表有分离DOM
点击按钮再拍一次快照
清空就把分离的变量设置为null就行了
判断是否存在频繁的垃圾回收
GC工作,程序就会暂停,如果频繁的进行GC操作,就会出现以下现象
1)应用假死
2)用户感知上应用卡顿
如何确定:
1)TimeLine中频繁的上升下降
2)任务管理器中数据频繁的增加减小
如果出现这种情况就要定位到哪个时间节点出现这种问题。
3.1.4 代码优化
https://jsperf.com/ 停止维护了
另外一个网站JSBench https://jsbench.me/
慎用全局变量
// 优化前
var i, str = ''
for (i = 0; i < 1000; i++) {
str += i
}
// 优化后
for (let i = 0; i < 1000; i++) {
let str = ''
str += i
}
将使用中无法避免的全局变量缓存到局部变量中
// 优化前
function getBtn() {
let oBtn1 = document.getElementById('btn1')
let oBtn3 = document.getElementById('btn3')
let oBtn5 = document.getElementById('btn5')
let oBtn7 = document.getElementById('btn7')
let oBtn9 = document.getElementById('btn9')
}
// 优化后
function getBtn2() {
let obj = document
let oBtn1 = obj.getElementById('btn1')
let oBtn3 = obj.getElementById('btn3')
let oBtn5 = obj.getElementById('btn5')
let oBtn7 = obj.getElementById('btn7')
let oBtn9 = obj.getElementById('btn9')
}
通过原型对象添加附加方法
在原型对象上新增实例对象需要的方法
// 优化前
var fn1 = function() {
this.foo = function() {
console.log(11111)
}
}
let f1 = new fn1()
// 优化后
var fn2 = function() {}
fn2.prototype.foo = function() {
console.log(11111)
}
let f2 = new fn2()
避开闭包陷阱
避免属性访问方法使用
// 优化前
function Person() {
this.name = 'icoder'
this.age = 18
this.getAge = function() {
return this.age
}
}
const p1 = new Person()
const a = p1.getAge()
// 优化后
function Person() {
this.name = 'icoder'
this.age = 18
}
const p2 = new Person()
const b = p2.age
for循环优化
// 优化前
var arrList = []
arrList[10000] = 'icoder'
for (var i = 0; i < arrList.length; i++) {
console.log(arrList[i])
}
// 优化后
for (var i = arrList.length; i; i--) {
console.log(arrList[i])
}
采用最优的循环方式
forEach > for > for in
文档碎片优化节点添加
// 优化前
for (var i = 0; i < 10; i++) {
var oP = document.createElement('p')
oP.innerHTML = i
document.body.appendChild(oP)
}
// 优化后
const fragEle = document.createDocumentFragment()
for (var i = 0; i < 10; i++) {
var oP = document.createElement('p')
oP.innerHTML = i
fragEle.appendChild(oP)
}
document.body.appendChild(fragEle)
克隆优化节点操作
// 优化前
for (var i = 0; i < 3; i++) {
var oP = document.createElement('p')
oP.innerHTML = i
document.body.appendChild(oP)
}
// 优化后
var oldP = document.getElementById('box1')
for (var i = 0; i < 3; i++) {
var newP = oldP.cloneNode(false)
newP.innerHTML = i
document.body.appendChild(newP)
}
字面量替换new Object()
// 优化前
var a1 = new Array(3)
a1[0] = 1
a1[1] = 2
a1[2] = 3
// 优化后
var a = [1, 2, 3]
减少if判断层级
如果有大量的if…else…判断,可以提前把不满足的条件return掉
// 优化前
function doSomething (part, charpter) {
const parts = ['ES2016', '工程化', 'Vue', 'React', 'Node']
if (part) {
if (parts.includes(part)) {
console.log('属于当前课程')
if (charpter > 5) {
console.log('您需要提供VIP身份')
}
}
} else {
console.log('请确认模块信息')
}
}
// 优化后
function doSomething (part, chapter) {
const parts = ['ES2016', '工程化', 'Vue', 'React', 'Node']
if (!part) {
console.log('请确认模块信息')
return
}
if (!parts.includes(part)) return
console.log('属于当前课程')
if(chapter > 5){
console.log('您需要提供VIP身份')
}
}
doSomething('ES2016', 6)
减少作用域链查找层级
// 优化前
var name = 'zs'
function fn() {
name = 'ls'
function children(age) {
console.log(name, age)
}
children()
}
// 优化后
var name = 'zs'
function fn() {
let name = 'ls'
function children(age) {
console.log(name, age)
}
children()
}
fn()
减少数据读取次数
字面量与构造式
字面量 形式 比 new 构造式 快些
对象: {} 形式快与 new Object() 形式,因为new Object相当于调用函数,而字面量形式直接是在开辟了一个使用内存
减少循环体活动
- 尽量把不变的值提取到循环体外,缓存出来。
- 不考虑执行顺序的话,用while循环会比for循环更快些
减少声明及与语句数
减少内存空间的开辟。根据具体需求和维护成本上来判定如何优化
惰性函数与性能
惰性函数的优化程度并没普通的快,所有要考虑使用场景
采用事件绑定
利用addEventLisener事件委托 捕获或者冒泡来执行事件