ES 新特性与 TypeScript、JS 性能优化

ES 新特性与 TypeScript、JS 性能优化

ES2015

1、Proxy

Proxy 对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)

注:可以理解为一个门卫,获取和修改都需要经过它

使用:

const person = {}
cont personProxy = new Proxy(person, {
    get(target, property) {
        return property in target ? target[property] : 'default'
    },
    set() {}
})
复制代码

场景应用:vue3属性代理使用Proxy

2、Proxy对比defineProperty

Proxy相对于defineProperty更加强大,例如:

  • proxy监听全对象,而defineProperty监听具体属性,需要指定
  • defineProperty无法监听到属性新增和删除,而proxy可以
  • defineProperty无法监听数组的变化
  • proxy以非侵入式方式监听,而defineProperty不是

3、Reflect

Reflect 是一个内置的对象,它提供拦截 JavaScript 操作的方法。Reflect不是一个函数对象,因此它是不可构造的。

  • 提供统一的对象操作Api
  • 不能通过new,是静态方法类
  • 具有13个方法(原本14个,淘汰一个)
Reflect.apply(target, thisArgument, argumentsList) // 对一个函数进行调用操作,同时可以传入一个数组作为调用参数。和 Function.prototype.apply() 功能类似。

Reflect.construct(target, argumentsList[, newTarget]) //对构造函数进行 new 操作,相当于执行 new target(...args)。

Reflect.defineProperty(target, propertyKey, attributes) //和 Object.defineProperty() 类似。如果设置成功就会返回 true

Reflect.deleteProperty(target, propertyKey) // 作为函数的delete操作符,相当于执行 delete target[name]。

Reflect.get(target, propertyKey[, receiver]) // 获取对象身上某个属性的值,类似于 target[name]。

Reflect.getOwnPropertyDescriptor(target, propertyKey) // 类似于 Object.getOwnPropertyDescriptor()。如果对象中存在该属性,则返回对应的属性描述符,  否则返回 undefined.

Reflect.getPrototypeOf(target) // 类似于 Object.getPrototypeOf()。

Reflect.has(target, propertyKey) // 判断一个对象是否存在某个属性,和 in 运算符 的功能完全相同。

Reflect.isExtensible(target) // 类似于 Object.isExtensible().

Reflect.ownKeys(target) // 返回一个包含所有自身属性(不包含继承属性)的数组。(类似于 Object.keys(), 但不会受enumerable影响).

Reflect.preventExtensions(target) // 类似于 Object.preventExtensions()。返回一个Boolean。

Reflect.set(target, propertyKey, value[, receiver]) // 将值分配给属性的函数。返回一个Boolean,如果更新成功,则返回true。

Reflect.setPrototypeOf(target, prototype) // 设置对象原型的函数. 返回一个 Boolean, 如果更新成功,则返回true。
复制代码

4、Symbol(符号)

  • Symbol是新增的一种原数数据类型
  • 主要作用是为对象添加一个独一无二的标识符(可以理解为key)
  • 用Symbol模拟实现对象私有成员,避免、解决冲突
  • 数据类型:number、string、function、bigInt、symbol、boolean、undefined、object
  • 特殊类型:null,JS 存在的一个悠久 Bug。在 JS 的最初版本中使用的是 32 位系统,为了性能考虑使用低位存储变量的类型信息,000 开头代表是对象然而 null 表示为全零,所以将它错误的判断为 object

5、类的继承 extends

  • 在之前实现继承是通过原型的方式实现,现在可以通过class的extend可实现,内部实现上还是函数

  • 由于class本质还是一个function 因此它就会拥有一个prototype属性,当new一个class时,会把class的porototype属性赋值给这个新对象的 proto属性

  • prototype是函数的一个属性,它是一个指针。对于构造函数来说,prototype是作为构造函数的属性。prototype也可以是一个对象,prototype是对象实例的原型对象。所以prototype即是属性,又是对象。在new一个对象时,可以理解为一个对象没有ptototype属性,所以把ptototype给一个对象的 proto

6、迭代器模式

  • 提供统一的可迭代接口
  • 语法层面上实现了此种模式

7、生成器

  • 在方法前增加一个*号,则代表一个生成器函数
  • 执行成功后返回的是一个对象
  • 需要通过next方法进行继续执行
  • 内部通过yield暂停
  • 其次yield后的值会作为本次next的结果返回
  • 同时会返回一个done的状态
  • 应用场景:发号器

8、es2016

  • includes方法:检测是否存在,且兼容NaN
  • 指数运算符: **,如 2 ** 10

9、es2017

  • object.values方法
  • object.entry方法,key、valye数组的形式拿到对象的内容
  • padStart,padEnd,如数字前补0

Typescript

概述

  • ts是基于js之上的编程语言,解决js类型系统不足之处。
  • 通过ts可提高代码的可靠程度
  • ts是js的一个超集
  • js是动态类型,ts是静态类型
  • ts基于js添加了不少特性

ts作用域问题

  • 变量默认在全局作用域,不允许重复
  • 默认使用export {} 变成模块导出

ts类型

  • Object 类型:函数、数组、对象
  • Array 类型:Array, number[]
  • 元祖类型:tuple types,可以理解为明确的类型、明确的长度的数据类型
  • 枚举类型:enum,数字的是自动增加,字符串必须给定值,最终会变成双向的键值对
  • 函数类型:
    函数声明
        1、形参类型限制
        2、返回值类型
    函数表达式
        箭头函数式标明
复制代码
  • 任意类型:接受任意类型:any,属于动态类型
  • 隐式类型推断:未声明类型,则会进行自动类型推断
  • 类型断言:无法推断时,as关键词,尖括号,尖括号会和jsx冲突,建议使用as

接口

  • 一种约定,一种规范
  • interface进行定义 接口
  • 形式为:key:类型
  • 约定对象的结构,且必须实现接口定义的内容
  • 可选成员:增加问好
  • 只读成员:增加readonly,赋值后就不许再改了
  • 任意成员:[x: types]: types

  • 类用来描述一类具体事务的抽象特征
  • 类的访问修饰符:private,public(默认的),proteted(受保护的)
  • proteted:只允许在子类中使用
  • private:只允许类的内部使用
  • 只读属性:readonly,需要跟在修饰符后面
  • 一个接口最好只限制一种能力,一个类可以实现多个接口,使用implements关键字,多个接口使用逗号分隔

性能优化

  • 内存管理
  • 垃圾回收与常见的GC算法
  • V8引擎的垃圾回收
  • performance工具

内存管理

  • 内存:由可读写单元组成,表示一片可操作性的空间
  • 管理:人为的去操作一片空间的申请、使用和释放
  • 内存管理:开发者主动申请空间、使用空间、释放空间
  • js并没有api可以直接开辟空间,而是使用变量声明、创建函数、创建类等方式,通过浏览器来开辟空间
  • 使用空间时注意,避免不当操作造成空间泄露

垃圾回收

  • js中内存管理是自动的
  • 对象不再被引用时即是垃圾
  • 对象不能从根上访问到时是垃圾

可达对象

  • 可以访问到的对象就是可达对象
  • 可达的标准就是从根出发是否能够被找到(从根上,到作用域链查找等)
  • 根:可以理解为全局变量对象

GC算法介绍

  • GC:就是垃圾回收机制的简写
  • GC 可以找到内存中的垃圾、并释放和回收空间
  • GC 里的垃圾是什么?
  • 程序中不在需要使用的对象
  • 程序中不能再访问到的对象
  • GC 是一种机制,垃圾回收器完成具体的工作,而工作内容就是查找垃圾并释放和回收空间;算法就是工作时查找和回收所遵循的规则
  • 常用的算法:引用计数、标记清除、标记整理、分代回收

GC:引用计数

  • 核心思想:设置引用数,判断当前引用数是否为0,当为0的时候进行回收
  • 实现原理:
  • 维护一个引用计数器,当有变量引用时,计数器加1,不再引用时计数器减1,当计数器为0时,GC回收,释放空间
  • 引用计数器:因为计数器存在,导致和其他机制有所区别

优缺点

  • 当发现为0时,立即回收
  • 减少 程序卡顿时间
  • 因为时刻监测内存,当快满的时候就回收,因此内存不会慢
  • 但是因需要维护计数器的变化,时间开销会相比其他机制大
  • 同时,如果两个对象互相引用,因为互相不为0,因此无法删除

标记清除

  • 核心思想:分两个阶段,第一个阶段将活动对象进行标记,第二个标记将未标记的清除掉,同时消除之前的标记
  • 从根查找,沿着作用域链只要能找到的,就为可达对象,否则为不可达,后续会被GC进行清除

优缺点

  • 可以解决引用计数时无法解决的互相引用问题
  • 互相引用时,在作用域执行完毕后从根递归查找已无法可达,因此会被清除
  • 缺点1:因清除掉后空间地址不连续,造成空间浪费
  • 缺点2:碎片化,不会立即清除

标记整理

标记阶段和标记清除机制一直,不同点在于,在执行完毕第一阶段后,在执行第二阶段标记清除之前,会先进行一次空间整理,移动对象的位置,使可达的活动对象空间地址是连续的,之后再进行清除操作

  • 减少碎片化

  • 不会立即回收垃圾对象

V8

  • V8是一款主流的js执行引擎,chrome平台已在使用

  • V8 采用即时编译

  • 内存设限

    64为系统中 1.5g

    32为系统中 800M

    原因就是:当内存使用到1.5g的时候,使用增量标记的方式需要使用500毫秒。使用其他方式需要1s,从用户体验角度来说,设定此上线,且使用增量标记较合理

V8垃圾回收策略

  • 采用分代回收思想,分为新生代、老生代,针对不同对象采用不同算法

  • 先划分,按照新生代、老生代的模式分代回收

  • 其次进行空间复制

  • 标记清除

  • 标记整理

  • 标记增量

新生代回收
  • 新生代空间被分为等分两部分

  • 在64为系统中是32M单个

  • 在32位系统中是16M单个

  • 新生代指的是存活时间较短的对象

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

  • 等分的两个空间划分为:使用空间为From,空间空间为To
  • 如果声明变量时,开辟空间则会在From空间中存储,为活动对象,此时To空间为空闲状态,未使用
  • 当From空间使用到一定比例时将会出发GC操作,顺序是先进行标记整理,然后将活动对象拷贝至TO空间。拷贝完毕后From区域被进行释放,TO编程了使用状态。进行了交换

回收细节:From到To空间的拷贝存在晋升(晋升就是将新生代对象移动到老生代空间内)。而这个晋升的标准分为:

  • 1、一轮GC后还存活的新生代需要晋升。
  • 2、TO空间的使用率超过25%,将全部拷贝至老生代(因为From和To需要交换,保证空间足够)
老生代回收
  • 老生代对象存储在老生代区域
  • 老生代的空间大小限制为:64位操作系统是1.4g,32位操作系统是700M
  • 老生代对象就是指存活时间较长的对象
  • 老生代的回收主要采用标记清除、标记整理、增量标记算法
  • 首先:使用标记清除完成垃圾回见的回收,速度较快
  • 标记整理需要根据条件触发:当新生代空间向老生代空间晋升,但空间无法满足时,会先进行比较整理进行空间优化,
  • 最后采用增量标记进行效率优化

和新生代的细节对比

  • 新生代使用空间换时间
  • 老生代不适合复制算法,原因:大,时间长

标记增量:将垃圾回收和程序执行分割交替执行,时间短,用户感知弱,体验好

V8执行流程

  • scanner-扫描器,将纯文本的代码进行扫描。做词法分析,得到词法单元,最小的一个单元
  • parser-解析器,语法分析,将词法分析的数据进行转化为ast树
  • preparser,预解析,全量解析

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

全量解析:解析被使用的代码,生成AST,构建具体scopes信息,变量引用、声明等,抛出所有语法错误

示例

// 声明时未调用,因此会被认为是不被 执行的代码,运行预解析
function foo() {
    console.log('foo')
}

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

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

// 执行foo 那么需要重新对foo函数进行全量解析,此时foo函数被解析了两次
foo()
复制代码
  • lgnition 是V8提供的一个解释器

去把AST转化成字节码

  • turBoFan 是编译器模块

之前数据转换成机器码(汇编代码)

函数防抖与节流

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享