part1-02笔记

Part 1 · JavaScript 深度剖析

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

#### 任务一:ECMAScript 新特性

1. 课程介绍

- ECMAScirpt 与 JavaScript

- ECMAScript 的发展过程

- ECMAScript 2015 的新特性

2. ECMAScript 概述

- 通常被看作JavaScript的标准化规范,实际上JavaScript是ECMAScript的扩展语言

+ ECMAScript 只提供了最基本的语法。JavaScript实现了这种标准,并进行了扩展

- JavaScript @ Web

+ ECMAScript

+ Web APIS: BOM DOM

- JavaScript @ Node.js

+ ECMAScript

+ Node APIs: fs net etc.

3. ES2015概述(ES6)

- 部分开发者喜欢用ES6泛指ES5.1 后所有的新标准

- async 函数是2017才出现的

- 四大类

+ 解决原有语法上的一些问题或者不足 (let,count 提供的块级作用域)

+ 对原有语法进行增强(解构,展开,参数默认值,模版字符串)

+ 全新的对象、全新的方法、全新的功能 (Promsie)

+ 全新的数据结构和数据类型(Symbol,Set)

4. ES2015 准备工作

- Nodemon 修改完代码后自动执行代码

+ yarn add nodemon --dev

+ yarn nodemon 执行文件名

5. ES2015 let 与块级作用域

- 作用域 - 某个成员能够起作用的范围

+ 在ES2015前,ES只有两种作用域:全局作用域和函数作用域

+ 新增 块级作用域({} 括号包裹的范围)

+ 以前块没有独立的作用域

- 声明不提升

```js

let i = '33' // 互不影响,说明 for有有两个作用域,一个是自身的作用域,一个是{}里的块级作用域

for (let i = 0; i < 3 ; i++) {

let i = 'foo'

console.log(i)

}

console.log(i)

// 拆解

let i = 0

if (i < 3) {

let i = 'foo'

console.log(i)

}

i++

// ...

```

6. ES2015 const 常量/恒量 只读特性

- 声明同时要赋值,不能重新指向一个新的内存地址

- 最佳实践:不用var,主用const,配合let

7. 数组的解构

- 根据位置提取

- const [a= 1, b, c] = [1, 2, 3] 可以设置默认值

- const [a, ...] = [1, 2, 3]

8. 对象的解构

- 根据属性名提取

- const {name: newName= '默认值'} = {name: 'kkk'}

9. 模版字符串

- ${} 插值表达式

10. 带标签的模版字符串

- const str = console.log`hello word` tag 标签函数 返回数组 内容分割的结果(插值分割)

```js

const name = 'tom'

function Mytag(strings, name) {

return strings[0] + name.toLowerCase() + strings[1]

}

let result = Mytag`我的名字是:${name}`

console.log(result)

```

10. 字符串的扩展方法

- includes()

- startsWith()

- endsWith()

12. 参数默认值

- 只有当实参传递undefined 才会采用

- 函数有多个形参,带有默认值的形参要在最后

13. 剩余参数

- 剩余操作符 function(...args) 只能出现在最后,且只能使用一次

14. 展开数组

- 展开运算符 ... apply

15. 箭头函数

- 简化了回调函数,增加了可读性

16. 箭头函数与this

- 不会改变this指向

- setTimeout的回调最终会放在全局作用域上执行

17. 对象子面量的增强

- 计算属性名:可以在声明使用动态的key

18. Object.assign

- 将多个源对象中的属性复制到一个目标对象中,有相同的属性,前者覆盖后者

- 应用场景

- 给对象赋默认值

```js

const source1 = {

name: 'aa'

}

const target = {

a: 66

}

const result = Object.assign(target, source1)

console.log(result === target) // true

```

19. Object.js

- === 运算符没有办法区分+0 和-0

- NaN === NaN 不想等 NaN表示非数字,有无限可能,两个无限可能不想等

- Object.js 可以正确处理前两种===特殊情况

20. Proxy

- Object.defineProperty 可以监视对象属性的读写过程。以前靠这个实现数据双向绑定

- Proxy 专门为对象设置访问代理器的

```js

const person = {

name: 'aaa',

age: 0

}

const personProxy = new Proxy(person, {

get(target, property) {

return target in target ? target[property] : 'defaule'

},

set(target, property, value) {

if (property === 'age') {

if (!Number.isInteger(value)) {

throw new TypeError(`${value} is not an int`)

}

}

target[property] = value

}

})

personProxy.age =10

console.log(personProxy)

```

21. Proxy 对比 definProperty

- definProperty 只能监控属性的读写

- Proxy 能够监视到更多对象操作,如对对象方法的调用。delete操作

- ![](../img/1622068363841.jpg)

- Proxy 更好的支持数组对象的监视

+ 以前监视数组方法的调用是重写数组的操作方法

+ Proxy是以非侵入的方式监管了对象的读写

22. Reflect 统一的对象操作API

- Reflect 属于一个静态类

- 不能通过new构建实例

- Reflect 内部封装了一系列对对象的底层操作

- Reflect 成员方法就是Proxy处理对象的默认实现

- 统一提供了一套用于操作对象的API

+ 如果 对象的 'name' in obj, delete obj['age'], Object.keys(obj)

+ 一会操作符,一会方法

```js

const person = {

name: 'aaa',

age: 0

}

const proxy = new Proxy(ob, {

get (target, property) {

return Reflect.get(target, property)

}

})

console.log(personProxy.name)

```

23. Promise

- 一种更优的异步编程解决方案

- 解决了传统异步编程中回调函数嵌套过深的问题

24. class 类

- 在此都是通过原型对象实现类

25. 静态方法 static

- 实例方法 vs 静态方法

- 以前通过构造函数添加(函数是对象),ES2015通过关键字static添加静态成员

- 不会去指向某一个实例对象,而是当前的类型

26. 类的继承 extends

- super() 或者 super.属性 访问父类方法的构造函数和属性方法

27. Set数据结构

- 不允许值重复,数组去重 [... new Set(array)]

28. Map

- 以前对象的key只能是字符串

- Map用来映射两个任意类型数据的关系,可以用任意类型的数据作为键

29. Symbol

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

- 对象类型的key可以是字符串或者symbol

- 可以创建私有属性,外部拿不到symbol类型 key(唯一),没有办法创建一个一样的symbol

- 最主要的作用就是为对象添加第一无二的属性名

- Symbol BigInt(存放更长的数字)(ES2019) 8中数据类型

30. Symbol 补充

- 可以通过 Symbol.for('foo') 得到一个相同的Symbol

+ 这个方法内部维护的是一个字符串和Symbol之间的关系

- for in 和 Object.keys 拿不到 Symbol类型的健,JSON序列化 Symbol 也会被忽略

- Object.getOwnPropertySymbols 获取

31. for ... of 循环

- 作为遍历所有数据结构的统一方法

- 可以通过 break 终止循环。forEach不行

32. 可迭代接口

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

- ES2015提供了iterable接口

- 实现iterable接口就是for...of 的前提

```js

const set = new Set(['foo', 'fbb'])

const iterator = set[Symbol.iterator]()

console.log(iterator.next())

console.log(iterator.next())

console.log(iterator.next())

console.log(iterator.next())

```

33. 实现可迭代接口

```js

const obj = {

[Symbol.iterator]: function() {

let index = 0

const self = this

return {

next: function() {

const result = {

value: self.store[index],

done: index >= self.store.length

}

index++

return result

}

}

}

}

```

34. 迭代器模式

- 提供统一的迭代接口,让外部不用关注数据内部结构

```js

const todos = {

left: ['吃饭', '睡觉', '喝酒'],

right: [1, 2, '好的'],

[Symbol.iterator]: function () {

const all = [...this.left, ...this.right]

let index =0

return {

next: function() {

return {

value: all[index],

done: index++ >= all.length

}

}

}

},

[Symbol.iterator]: * function () { // 通过生成器

const all = [...this.left, ...this.right]

for (let item of all) {

yeild item

}

}

}

for (let item of todos) {

console.log(item)

}

```

35. 生成器 Generator

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

36. 生成器应用

37. ES Modules 语言层面的模块化标准

38. ES2016 概述

- 数组 includes 可以查找NaN

- 指数运行符 2**10

39. ES2017

- Ojbect.values 返回所有值

- Object.entries 返回对象的键值对数组 new Map(Object.entries(obj))

- Object.getOwnPropertyDescriptors 返回对象属性的描述信息

- String.prototype.padStart String.prototype.padEnd 给字符串开始或结尾填充字符,知道达到指定长度为止

- 定义函数或者调用函数是可以多加一个逗号,方便编码 function foo(a,b,)

- Async/Await

Part 1 · JavaScript 深度剖析

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

#### 任务二:TypeScript 语言

1. 概述

- 解决JavaScript类型系统的问题

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

- 探讨JavaScript自有类型系统的问题

- 内容概要

+ 强类型与弱类型

+ 静态类型与动态类型

+ JavaScript自有类型系统的问题

+ Flow静态类型检查方案

+ TypeScript 语言规范与基本应用

2. 强类型与弱类型

- 类型安全

- 语言层面限制函数的实参类型与形参类型相同

- 弱类型语言层面不会限制实参的类型

- 强类型有更强的类型约束,二弱类型中几乎没有什么约束

- 强类型语言中不允许有任意形势的隐式类型转换

- 弱类型允许任意形势的类型转换

- JavaScript的类型错误都是通过运行时的逻辑判断抛出的,而不是在编译阶段抛出

- 强类型类型错误会在语言层面抛出

- 变量类型允许随时改变的特点,不是强弱类型的差异

3. 类型系统 静态类型与动态类型

- 类型检查

- 静态类型:一个变量声明时它的类型就是明确的,声明过后,类型就不允许修改

- 动态类型:在运行阶段才能够明确变量类型,变量的类型也可以随时发生变化

- 强类型不一定是静态类型

- ![](../img/1622255770949.jpg)

4. JavaScirpt 类型系统特征

- 弱类型且动态类型

- 灵活多变的表象就是丢失了类型系统的可靠性

- 早前的JavaScript应用简单

- JavaScript是脚本语言,没有编译环节

- 大规模应用下,这种优势就变成了短板

5. 弱类型的问题

- 运行阶段才能发现代码的一些异常

- 类型不明确,造成函数功能发生改变,如数字加法变成字符串拼接

- 对对象索引器的一种错误的用法 const obj = {}; obj[true] = 100; console.log(obj['true']);

- 君子约定存在隐患,强制要求才有保障

6. 强类型的优势

- 错误可以更早的暴露

- 代码更智能,编码更准确(弱类型编辑器没有办法作出很好的智能提示;提示对应类型的属性)

- 重构更牢靠

- 在代码层面可以减少很多不必要的类型判断

7. FLow概述

- JavaScript的类型检查器 (2014 Facebook)

- 在开发阶段通过类型注解进行检查

- 类型注解

- 不强制要求给每一个变量添加注解

```js

function sum (a:number, b:number) {

return a + b

}

```

8. FLow 快速上手

- yarn add flow-bin --dev

- 需要在效验的文件开始加上 // @flow

- 关闭vs的语法效验 Code 》 Preferences》Settings 》 javascript validate

- yarn flow init 初始化flow

- yarn flow 启动

- yarn flow stop 停止

9. Flow 移除注解

- yarn add flow-remove-types --dev

- yarn flow-remove-types . -d dist

- 或者babel解决

+ yarn add @babel/core @babel/cli @babel/preset-flow --dev

+ 添加 .babelrc {'presets': ['@babel/preset-flow'] }

+ yarn babel m2 -d dist

10. Flow 开发工具插件

- Flow language Support

11. Flow 类型推断

- 可以根据代码中使用情况推断参数类型。 funciotn square(n) { return n * n };square(100)

12. Flow 类型注解

- let num: number = 2

- function foo(): number { return 1 } 没有返回值就是undefined,也会报语法错误,没有返回值可以把返回值类型标记为void

13. 原始类型

- const a: string = 'foobar'

- const b: number = Infinity //NaN

- const c: boolean = false

- const d: nulll = null

- const e: void = undefined

- const f: symbol = Symbol()

14. 数组类型

- const arr1: Array = [1, 2, 3]

- const arr2: number[] = [1, 2, 3]

- const foo: [sring, number] = ['f', 2] // 元组 当函数中返回多个值的时候用到

15. 对象类型

- const obj1: { foo?: string, bar: numver} = {foo: 'st', bar: 100} // 带问号表示可选属性

- cosnt obj3: {[sring]: string} = {} //可以添加任意个数的键,但是键和值的类型必须是字符串

16. Flow 函数类型

- 参数和返回值注解

- function foo (fn: (string, number) => volid) { fn('string', 100) }

17. Flow 特殊类型

- 字面量类型,限定值必须是某一个

+ const a: 'f' = 'f'

- 联合类似,或类型

+ const type: 'success' | 'warning' | 'danger' = 'success'

- type StirngOrNumver = string | number // 用tag给类似做别名

+ const b: StirngOrNumver = 'str'

- maybel类型(有可能)

+ const gender: ?number = undefined

18. Mixed 与 Any

- mixed 类型表示可以接收所有类型

- Any 也可以接收所有类型

- Any 是弱类型(兼容以前老代码), mixed是强类型(类型安全,使用类型有隐患,会进行提示)

19. Flow 类型小结

> https://www.saltycrane.com/cheat-sheets/flow-type/latest/ 类型手册

20. Flow 运行环境API

- 内置对象

+ 如浏览器的dom对象

21. TypeScript概述

- JavaScript的超集 (supserset)

- TypeScript = JavaScript + 类型系统 + ES6+ 编译=》 JavaScript

- 任何一种JavaScript运行环境都支持

- 比起flow功能更强大,生态也更健全,更完善

- Angular / Vue.js 3.0

- TypeScript 前端领域中的第二语言

- TypeScript 属于 渐进式

- 缺点

+ 语言本身多了很多概念

+ 项目前期,TypeScript 会增加一些成本

22. TypeScript 快速上手

- yarn init --yes // 初始化一个package.json

- yarn add typescript --dev

- yarn tsc p1.ts // 编译转换es的新特性,检查异常,移除代码注解

23. TypeScript 配置文件

- yarn tsc --init // tsconfig.json

- yarn tsc // 配置完tsconfig.json后,可以通过整个命令执行编译整个项目(source放在src下,out放在dist文件夹下)

24. TypeScript 原始类型

- 非严格模式下,string,number,boolean 都可以为空 null (配置文件配置)

- void 只能存放 NaN 或者 undefined,严格模式下只能后者

25. 标准库声明

- 内置对象类型

+ 如果配置文件 target配置的是es5,那么不能使用symbol,或者其它es6新增的对象,因为ts使用的标准库是以es5特性为基础的

+ 解决方案有两个:把target配置为es2015,或者是在 lib 数组中加入 es2015 (这个时候console不能使用了,因为它是浏览器环境提供的,lib本来默认配置了,现在被覆盖了,需要在lib中加入 DOM,这里的DOM是BOM和DOM的集合)

+ "lib": ["ES2015", "DOM]

- 标准库就是内置对象所对应的声明

26. TypeScript 中文错误消息

- 会根据编辑器的默认语言显示消息

- yarn tsc --locale zh-CN // 命令行异常输出语言

- 可以在vscode设置中找到 typescript local 中进行设置,那么vscode报出的错误消息也是中文的

27. Typescript 作用域问题

- 用立即执行函数 IEEF

- 使用 export 变成模块作用域

+ export {} 并不是导出一个空对象

28. TypeScript Object 类型

- cosnt foo: object = function () {} // [] {}// 并不单指对象,而是除了原始类型的其它类型 能接受对象,数组,函数

- const obj: {foo: number} = {foo: 123 } // 对象字面量方式

29. 数组类型

- cosnt arr1: Array = [1, 2, 3]

- const arr2: number[] = [1, 2, 3]

30. 元组类型

- const tuple: [number, string]} = [20, 'tt']

- 和 Object.entries类似 返回一个键值数组

31. 枚举类型

- const enum PostStatus { // 在前面加const是常量枚举,使用枚举的地方都会被替换掉

Draft = 0, // 如果不指定,那么从0开始累加。如果指定了第一个,后续累加。字符串枚举每个都要给值

Unpublished = 1,

published = 2

}

- 编译会会变成一个双向键值对,可以通过值访问键,也可以通过键访问值

```js

var PostStatus;

(function (PostStatus) {

PostStatus[PostStatus["Draft"] = 0] = "Draft";

PostStatus[PostStatus["Unpublished"] = 1] = "Unpublished";

PostStatus[PostStatus["published"] = 2] = "published";

})(PostStatus || (PostStatus = {}));

```

32. TypeScript 函数类型

- 可选参数用奥出现在必须参数后面,因为如果可选参数在前面,没有传参的化,那么函数拿到的参数就不准确

33. TypeScript 任意类型

- any 可以接受任意类型的值,不会有类型检查,没有语法上的报错

35. TypeScript 隐式类型推断

- let age = 18; let foo;foo = 100; foo = 'string' // 前者只能是number类型,后者可以赋任何值

36. 类型断言

- 断言的两种方式,明确告诉TypeScript 值的类型

+ const nums = [110, 130]; const res = nums.find(n => n > 0);const num1 = res as number

+ num2 = res // JSX 下不能使用,会和和其标签产生冲突

36. 接口 interface

- 只说为有结构的数据做类型约束的,编译后无痕迹

- 约束对象的结构,一个对象实现了一个接口,就必须拥有约束接口中的所有成员

```js

interface Post {

title: string // 可以加逗号或者分号,默认加了分号

content?: string // 可选成员

readonly summary: string //只读成员 初始化后就不能修改了

}

```

37. 接口补充

- 动态类型成员

```js

interface Cache { [prop: string]: string }

```

38. 类的基本使用

- 描述一类具体事物的抽象特征

- TypeScript 增强了class 的相关语法

- 需要在calss明确声明属性,不能单纯的在 constructor 动态添加,使用前先声明

```js

class Person {

private name: string // 表示私有属性,只能在类内部访问

public age: number // 默认的访问修饰符

protected readonly gender: boolean // 只能在子类中访问, readonly 只读属性,在声明时或者构造函数中赋值,赋值完成后不能修改

private constructor(name: string, age: number) { // 构造函数默认访问修身符也是 public,如果定义为private在外部不能实例化,不能被继承,通过静态方法创建类型对象

this.name = name

this.age = age

this.gender = true

}

}

```

39. TypeScript 类的访问修饰符

40. 类的只读属性

41. TypeScript 类与接口

- 手机和座机都能打电话,因为它们实现类相同的协议:接口

- 接口和父类的区别:接口只定义行为,没有实现,而父类可能有实现

- 一个接口只实现一个能力,多个接口约束

```js

interface Eat {

eat (food: string): void

}

class Parson implements Eat //,其它接口{

eat(food: string) {

console.log(food)

}

}

```

42. 抽象类

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

```js

abstract class Animal {

eat(fond: string): void {

console.log(`呼噜噜的吃 ${fond}`)

}

abstract run (dustance: number) : void

}

class Dog extends Animal {

run(dustance: number) {

}

}

```

43. 泛型

- 不能确定类型的情况下使用

```js

function createArray (length: number, value: T): T[] {

const arr = Array(length).fill(value)

return arr

}

const res = createArray(3, 'foo')

```

44. 类型声明

- 在声明是没有声明类型,在使用前声明,为了解决一些依赖模块没有声明的问题

- 如果没有声明模块,就可以通过declare声明语句解决

```js

import { camelBase } from 'lodash' // 也可以通过安装lodash对应的声明模块的方式解决

declare function camelBase(name:string): void {

}

const res2 = camelBase('aaa')

```

Part 1 · JavaScript 深度剖析

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

#### 任务三:JavaScript 性能优化

1. 课程概述

- 本阶段的核心是JavaScript语言的优化

+ 内存管理

+ 垃圾回收与常见GC算法

+ V8引擎的垃圾回收

+ Performance 工具

+ 代码优化实例

2. 内存管理

- 介绍

+ 内存:由可读写单元组成,表示一片可操作空间

+ 管理:人为的去操作一片空间的申请、使用和释放

+ 内存管理:开发者主动申请空间、使用空间、释放空间

+ 管理流程: 申请-使用-释放

3. JavaScript 中的垃圾回收

- Javascript中内存管理是自动的

- 对象不再被引用时是垃圾

- 对象不能从根上访问到时是垃圾

- JavaScript中的可达对象

+ 可以访问到的对象就是可达对象(引用、作用域链)

+ 可达的标准就是从根出发是否能够被找到

+ JavaScript 中的根就可以理解为是全局变量对象

4. GC定义与作用

- GC就是垃圾回收机制的简写

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

- GC里的垃圾是什么

+ 程序中不再需要使用的对象

+ 程序中不能在访问到的对象

- GC 算法是什么

+ GC是一种机制,垃圾回收器完成具体的工作

+ 工作的内容就是查找垃圾释放空间、回收空间

+ 算法就是工作是查找和回收所遵循的规则

- 常见的GC算法

+ 引用计数 可以通过一个数字来判断当前的对象是不是一个垃圾

+ 标记清除 可以在工作时给到那些活动对象添加上一个标记,来判断它是否是垃圾

+ 标记整理 和标记清除很类似,但是在回收时会做一些不一样的事情

+ 分代回收 V8中会用到

5. 引用计数算法实现原理

- 核心思想:设置引用数,判断当前引用数是否为0

- 引用计数器

- 引用关系改变是修改引用数字

- 引用数字为0时立即回收

6. 引用计数算法优点

- 优点

- 发现垃圾时立即回收

- 最大限度减少程序暂停

+ 程序执行是内存有占满的情况,发现垃圾立即回收能降低内存占用率

- 缺点

- 无法回收循环引用的对象

- 时间开销大 需要维护一个数值的变化,数值的修改需要时间,如果内存里有很多对象需要修改,那消耗的时间就更多

7. 标记清除算法实现原理

- 核心思想:分标记和清除两个阶段完成

- 遍历所有对象找标记活动对象(可达对象)

- 遍历所有对象清除没有标记的对象 (会抹除第一次下的标记)

- 回收相应的空间,放在空闲列表中,方便后续程序使用

8. 标记清除算法优缺点

- 可以解决对象循环引用的情况

- 会造成空间碎片化的问题

+ 因为回收的到空闲列表中的空间不连续,如果新的需要空间如果匹配那直接使用没有问题,但是多了或者少了那就不太适合使用了

- ![](../img/1622379748160.jpg)

9. 标记整理算法实现原理

- 标记整理可以看做是标记清除的增强

- 标记阶段的操作和标记清除一致

- 清除阶段会先执行整理,移动活动对象位置,让它们在地址上产生连续

- 标记》整理》回收

10. 常见GC算法总结

- 引用计数

+ 可以及时回收垃圾对象

+ 减少程序卡顿时间

+ 无法回收循环引用对象

+ 资源消耗较大

- 标记清除

+ 可以回收循环引用的对象

+ 容易产生碎片化空间,浪费空间

+ 不会立即回收垃圾对象(清除的时候程序是停止工作的)

- 标记整理清除

+ 可以解决空间碎片化的问题

+ 不会立即回收垃圾对象

- 标记清除算法的垃圾空间被回收到空闲列表中之后,会以内存碎片的形式存在,所以会导致内存空间碎片化。那么引用计数的话发现垃圾是立即回收也就是说引用数值为0,它是一直监听着对象的引用数组变化的,不存在会有一个空闲列表的区域。

- 核心就是,一个立即清除,一个存一堆之后再清除,这种存一堆再清除的就很难保证地址还是连续可用的

11. 认识V8

- V8 是一款主流的JavaScript 执行引擎

- V8采用即时编译,直接翻译成机器码执行,很多以前的执行引擎要把源代码先转成字节码后才去执行

- V8设置有内存上线 64位操作系统不超过1.5g 32位操作系统不超过800M

+ 本身是为了浏览器而制造的,现有的大小对于网页应用来说够用了

+ 再有就是它本身的垃圾回收机制也决定了它这么设置是合理的

+ 官方做过测试,当内存达到1.5个G的时候,如果采用增量标记的算法去进行回收,只需要50毫秒。采用非增量标记的算法去回收,需要1s。

+ 继续扩大空间的话,时间就超过里1s,对用户替换不友好

12. V8 垃圾回收策略

- 基础的原始数据都是程序语言本身来控制的,这里的回收指的是存放在堆里的对象数据

- 采用分代回收的思想

- 内存分为新生代、老生代

+ V8 内存空间

- 新生代对象储存 》采用具体的GC算法

- 老生代对象储存 》采用具体的算法

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

- V8 中常用GC 算法

+ 分代回收

+ 空间复制

+ 标记清除

+ 标记整理

+ 标记增量

13. V8 如何回收新生代对象

- V8 内存空间一分为二

- 小空间用于储存新生代对象 (32M|16M)

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

- 新生代对象回收实现

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

+ 新生代内存取费二个等大小空间

+ 使用空间为From,空闲空间为To

+ 活动对象储存于From空间

+ 标记整理后将活动对象拷贝至To

+ From 与 To 交换空间完全释放

- 回收细节说明

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

+ 晋升就是将新生代对象移动至老生代

+ 一轮GC还存活的新生代需要晋升

+ To 空间的使用率超过25% 也需要晋升 (因为要和from空间交换,如果使用率太高就容易不够用)

14. V8如何回收老生代对象

- 老生代对象存放在右侧老生代区域

- 64 位操作系统1.4G,32操作系统700M

- 老生代对象就是指存活时间较长的对象

- 老年代对象回收实现

+ 主要采用标记清除、标记整理、增量标记算法

+ 首先使用标记清除完成垃圾空间的回收

+ 采用标记整理进行空间优化 当准备把新生代储存区往老生代储存区移动的时候,而老生代储存区又不够用的时候,对碎片空间进行整理回收

+ 采用增量标记进行效率优化

- 细节对比

+ 新生代区域垃圾回收使用空间换时间

+ 老生代区域垃圾回收不适合复制算法,因为占用的空间比较大,一分为二就产生里很大的浪费

- 标记增量如何优化垃圾回收

+ 垃圾回收时,程序是停滞的,遍历对象进行标记时间比较长,而标记增量是分开操作的,也就是每执行一段程序,就标记一次。让程序执行和垃圾回收交替执行。

- ![](../img/1622392827604.jpg)

15. V8 垃圾回收总结

- V8 是一款主流的JavaScript执行引擎

- 内存设置上线

- 采用基于分代回收思想实现垃圾回收

- 内存分为新生代和老生代

- 垃圾回收常见的GC算法

16. Performance 工具介绍

- GC的目的是为了实现内存空间的良性循环

- 良性循环的基石是合理使用

- 时刻关注才能确定是否合理

- Performance 提供多种监控方式

- Performance 使用步骤

+ 打开浏览器输入目标地址

+ 进入开发人员工具面板,选择性能

+ 开启录制功能,访问具体界面

+ 执行用户行为,一段时间后停止录制

+ 分析界面中记录的内存信息

17. 内存问题的体现

- 内存问题的外在表现

+ 页面出现延迟加载或者经常性暂停

+ 页面持续性出现糟糕的性能

+ 页面的性能随着时间延长越来越差

18. 监控内存的几种方式

- 界定内存问题的标准

+ 内存泄漏:内存使用持续升高

+ 内存膨胀: 在多数设备上都存在性能问题

+ 频繁垃圾回收:通过内存变化图进行分析

- 监控内存的几种方式

+ 浏览器任务管理器

+ Timeline 时序图记录

+ 堆快照查找分离DOM

+ 判断是否存在频繁的垃圾回收

19. 任务管理器监控内存

- 内存:查看DOM内存消耗情况

- JavaScript 内存:查看js内存使用情况,判断是否存在问题,不好定位问题

20. Timeline 记录内存

21. 堆快照查找分离DOM

- 什么是分离DOM

+ 界面元素存活在DOM树上

+ 垃圾对象时的DOM节点

+ 分离状态的DOM节点

- Memory > Take snapshot > 搜索 deta

22. 判断是否存在频繁GC

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

+ GC工作时应用程序是停止的

+ 频繁且过长的GC会导致应用假死

+ 用户使用中感知应用卡顿

- 确定频繁的垃圾回收

+ Timeline中频繁的上升下降

+ 任务管理器中数据频繁的增加减小

23. Performance 使用

- Performance 使用流程

- 内存问题的相关分析

- Performance 时序图监控内存变化

- 任务管理器监控内存变化

- 堆快照查找分离DOM

24. V8 引擎工作流程

- ![](../img/1623037024019.jpg)

- Scanner 是一个扫描器

+ 对纯文本的JavaScript进行词法分析,分析成不同的tokens,得到一个词域的单元,可能是一个单个的字符串,也可能是一个字符串,没有办法再分割了

- Parser 是一个解析器

+ 把扫描器得到的tokens转换成抽象的语法树

+ 做语法分析的过程中进行语法效验

- 预解析 preParser 声明未使用,没有在后面进行使用

- 预解析的优点 不是全量解析

+ 跳过未被使用的代码

+ 不生成AST,创建无变量引用和声明的scopes

+ 依据规范抛出特定错误

+ 解析速度更快

- 全量解析 对应执行上下午的创建

+ 解析被使用的代码

+ 生成AST

+ 构建具体scopes 信息,变量引用、声明等

+ 抛出所有语法错误

+ 解析阶段代码未执行

- ![](../img/1623038219962.jpg)

- lgnition 是V8提供的一个解析器 预编译阶段

+ 把生成的抽象语法树AST转成字节码,收集下一阶段编译需要的信息

- TurboFan 是V8 提供的编译器模块

+ 把字节码转换成具体的汇编代码,然后可以开始具体的代码执行

25. 堆栈处理

- JS 执行环境

- 执行环境栈 (ECSstack, execution context stack)

- 执行上下文 EC(G)

- VO(G), 全局变量对象

+ 基本数据类型是按值进行操作

+ 基本数据类型值是存放在 栈区的

+ 无论我们当前看到的栈内存,还是后续引用数据类型会使用的堆内存都属于计算机内存

+ GO:全局对象。它不是VO(G),但是它也是一个对象,因此它也会有一个内存的空间地址,有地址就可以对其访问 JS会在VO(g)当中准备一个变量叫window

- ![](../img/1624003006937.jpg)

26. 引用类型堆栈处理

- ![](../img/1624004610259.jpg)

27. 函数堆栈处理

- 函数创建

+ 可以将函数名称看做是变量,存放在 VO 当中,同时它的值就是当前函数对应的内存地址

+ 函数本身也是一个对象,创建时会有一个内存地址,空间内存放的就是函数体代码(字符串形式的)

- 函数执行

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

+ 步骤:

- 作用域链 当前执行上下文,上级作用域所在的执行上下文

- 确定this

- 初始化 arguments 对象

- 形参赋值:它就相当于变量声明,然后将声明的变量放置于 AO

- 变量提升

- 代码执行

- ![](../img/1624008092255.jpg)

- ![](../img/1624008132709.jpg)

28. 闭包堆栈处理

- 闭包: 是一种机制

+ 保护: 当前上下文当中的变量与其它的上下文中变量互不干扰

+ 保存:当前上下文中的数据 (堆内存)被当前上下文以外的上下文中的变量所引用,这个数据就保存下来

- 闭包

+ 函数调用形成了一个全新的私有上下文,在函数调用之后当前上下文不被释放就是闭包(临时不被释放)

- ![](../img/1624009835304.jpg)

- ![](../img/1624009925430.jpg)

- ![](../img/1624009970271.jpg)

29. 闭包与垃圾回收1

- 立即执行的闭包只是临时不会被回收 如 foo(1)(2)

30. 闭包与垃圾回收2

- 页面没有关闭全局执行上下文不会被释放

- 浏览器都自有垃圾回收(垃圾管理,v8为例)

- 栈空间、堆空间

- 堆:当前堆内存如果被占用,就不能被释放,但是我们确认后续不使用这个内存的数据,也可以自己主动置空,然后浏览器会对其进行回收。

- 栈:当前上下文是否有内容,被其它上下文的变量所占用,如果有则无法释放(闭包)

- fn = null 只是考虑对空间的使用

31. 循环添加事件

```js

按钮1

按钮2

按钮3

var btns = document.querySelectorAll('button')

// let 实现闭包

for (let i = 0; i < btns.length; i++) {

btn[i].onclick = function(i){

console.log(i)

}

}

// 自执行函数实现闭包

for (var i = 0; i < btns.length; i++) {

btn[i].onclick = (function(i){

console.log(i)

})(i)

}

// 自执行函数实现闭包

for (var i = 0; i < btns.length; i++) {

(function(i) {

btn[i].onclick = function(i){

console.log(i)

}

})(i)

}

// 添加自定义属性保存值

for (var i = 0; i < btns.length; i++) {

btn[i].myIndex = i

btn[i].onclick = function(i){

console.log(this.myIndex)

}

}

```

32. 底层执行分析

33. 事件委托实现

```js

document.body.onclick = function(e) {

var target = e.target

var targetName = target.nodeName

if (targetName === 'BUTTON') {

var index = target.getAttribute('index')

console.log(index)

}

}

```

34. JSBench 实现

- jsbench.me js性能测试

35. 变量局部化

- 这样可以提高代码执行效率(减少了数据访问时需要查找的路径)

36. 缓存数据

- 对于需要多次使用的数据进行提前保存,后续进行使用

+ 减少声明和语句数 (词法 语法)

+ 缓存数据 (作用域链查找变快)

37. 减少访问层级

38. 防抖和节流

- 为什么血压奥防抖和节流

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

- 场景:

+ 滚动事件

+ 输入的模糊匹配

+ 轮播图切换

+ 点击操作

- 浏览器默认情况下都会有自己的监听事件间隔(4-6ms),如果检测到多次事件的监听执行,就会造成不必要的资源浪费

- 前置场景:界面上一个按钮,进行多次点击

+ 防抖

- 对于这个高频行为来说,只识别一次,可以是第一次也可以是最后一次

+ 节流

- 对于高频操作,自己设置频率,让本来执行很多次的事件触发,按照指定的频率去触发,减少触发次数

39. 防抖实现-1

40. 防抖实现-2

```js

// wait 触发多久之后执行

// immediate 控制执行第一次还是最后一次,false执行最后一次

function myDebounce(handle, wait, immediate) {

if (typeof handle !== 'function') throw new Error('handle must an be function')

if (typeof wait === 'undefined') wait = 300

if (typeof wait === 'boolean') {

immediate = wait

wait = 300

}

if (typeof immediate !== 'boolean') immediate = false

let timer = null

// 防抖就是现实管理handle的执行次数

return function proxy(...args) {

let that = this

let init = immediate && !timer

timer && clearTimeout(timer)

timer = setTimeout(function(){

timer = null

!immediate && handle.apply(that, args)

}, wait)

// 如果当前传递进来的是 true 就表示需要立即执行

// 如果想要实现只在第一次执行,那么可以添加上timer为null做为判断

// 因为只要timer为null 就意味着没有第二次点击

init && handle.apply(that, args)

}

}

document.querySelector('button').onclick = myDebounce(btnClick, true)

function btnClick(e) {

console.log('点击了', e)

}

```

41. 节流函数的实现

- 值的是在自定义的一段时间内让事件触发

+ 假设当前在5ms的时间点执行了一次proxy,我们就可以用这个时间减去上次执行的时间(0),此时就会有一个时间差

+ 前置条件:我们自己定义了一个wait = 500

+ wait - (new - previous)

+ 此时如果上述的计算结果示大于0的,就意味着当前次的操作是一个高频触发,我们想办法让它不要去执行handle,如果这个结果小于等于0,这就意味着当前次不是一个高频触发,那么我们就可以直接执行handle。

+ 此时我们可以在500ms内想办法让所有的高频操作在将来都有一次执行就够了,不需要给每个高频操作都添加一个定时器

```js

function myThrottle(handle, wait) {

if (typeof handle !== 'function') throw new Error('handle must an be function')

if (typeof wait === 'undefined') wait = 400

var previous = 0 // 定义上一次执行的时间

var timer = null

return function proxy(...args) {

let self = this

let now = new Date()

let interval = wait - (now - previous)

if (interval <= 0 ) {

clearTimeout(timer) // 这个操作只是把timer在系统中对应的定时器清除了,但是timer中的值还在

timer = null

handle.apply(self, args)

previous = new Date()

} else if (!timer){

timer = setTimeout(function() {

clearTimeout(timer)

timer = null

handle.apply(self, args)

previous = new Date()

}, interval)

}

}

}

function scrollFn() {

console.log('执行了')

}

window.onscroll = myThrottle(scrollFn, 600)

```

43. 减少判断层级

- 出现多层if-else 嵌套,是否可以通过提前reture

44. 减少循环体活动

45. 字面量与构造式

- 引用类型通过字面量创建比构造式快(通过JSBench测试)

+ new Object() 的时候就好像是调用一个函数,再开辟堆空间储存

+ 而字面量创建对象则直接就开辟一个堆空间储存了

+ 差距不大

- 基础类型通过字面量创建比构造式快,快很多

+ 通过构造式创建的默认是一个字符串对象,调用函数会做更多的事情

+ 通过字面量创建默认是一个字符串,调用字符串方法时,默认先转成字符串对象


 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值