一、ECMAScript 新特性
1、作用域
1、全局作用域
2、函数作用域
3、块级作用域
2、var、let和const的区别
1、let和var用来声明变量,const用来声明常量(变量就是赋值后可以改变它的值,常量就是赋值后就不能改变它的值)
2、const不允许只声明不赋值,一旦声明就必须赋值
3、var是函数作用域,let和const是块级作用域(花括号{}就是块级作用域,函数作用域就是函数里面的内容)
4、var有提升的功能,let和const没有
5、在最外层的作用域,即全局作用域,用var声明的变量,会作为window的一个属性;而用let和const声明的变量或常量,并不会作为window的属性
注:最佳实践:不用var,主用const,配合let
3、数组的解构
const arr = [100, 200, 300]
const [foo, bar, baz] = arr
console.log(foo, bar, baz) //100 200 300
const [, , baz] = arr
console.log(baz) //300
const [foo, ...rest] = arr
console.log(rest) //[200, 300]
const [foo] = arr
console.log(foo) //100
const [foo, bar, baz, more] = arr
console.log(more) //undefined
const [foo, bar, baz = 123] = arr
console.log(baz) //123
4、对象的解构
const obj = { name: 'zce', age: 18 }
const { name } = obj
console.log(name) //zce
//重命名 + 添加默认值
const { name: objName = 'jack' } = obj
console.log(objName) //jack
5、模板字符串
const name = 'tom'
const msg = `hey, ${name}`
console.log(msg) //hey, tom
6、ES2015 字符串的扩展方法
includes() //是否包含
startsWith() //是否以...开头
endsWith() //是否以...结尾
例:
const message = 'Error: foo is not defined.'
console.log(message.startsWith('Error')) //true
console.log(message.endsWith('.')) //true
console.log(message.includes('foo')) //true
7、参数默认值
function foo(enable = true){
console.log(enable)
}
foo(false)
注:设置的默认值只会在调用时没有传递实参或实参传递的是一个undefined时被使用
如果有多个参数,带有默认值的形参一定要放在参数列表的最后
8、箭头函数和普通函数
//普通函数
function f1(){
console.log('我是普通函数');
}
f1()
//箭头函数:相当于匿名函数,如果没有参数,就只写一个 () ,有参数直接写 (参数1, 参数2)
let f2 = () => console.log('ddd') //如果函数里面只有一个表达式,可以省略{}和return
f2()
let f3 = () => { //如果有多个表达式则不能省略{}和return
console.log('我是箭头函数');
}
f3()
9、箭头函数和普通函数的区别
区别1:箭头函数是匿名函数,所以不能作为构造函数,因此也不能是有new
let func = () => console.log('1111')
let newfunc = new func() //会报错 Uncaught TypeError: func is not a constructor
区别2:箭头函数不绑定arguments,取而代之的是用rest参数解决
function f4(a){ // 普通函数
console.log(arguments);
console.log(arguments.length); // length指向传入当前函数参数的长度
}
var a = [2,3,5,6,87]
f4(a) // Arguments [Array(5), callee: ƒ, Symbol(Symbol.iterator): ƒ]
let f5= (a) =>{ // 箭头函数错误示例
console.log(arguments);
}
f5(3,4,5,78,5) // 会报错;Uncaught ReferenceError: arguments is not defined
let f6 = (...a)=>{ //箭头函数正确用法
console.log(a);
}
f6(3,45,6,7,8) // 输出 3,45,6,7,8
区别3:两者的this指向不同
普通函数的this指向的是谁调用该函数就指向谁
箭头函数的this指向的是在你书写代码时候的上下文环境对象的this,如果没有上下文环境对象,那么就指向最外层对象window。
var obj = {
a: 10,
b: () => {
console.log(this.a); // undefined
console.log(this); // Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, frames: Window, …}
},
c: function() {
console.log(this.a); // 10
console.log(this); // {a: 10, b: ƒ, c: ƒ}
}
}
obj.b();
obj.c();
区别4:箭头函数没有原型属性
var a = ()=>{
return 1;
}
function b(){
return 2;
}
console.log(a.prototype); // undefined
console.log(b.prototype); // {constructor: ƒ}
区别5:箭头函数不能当做Generator函数,不能使用yield关键字
10、Proxy 实例的方法
注:proxy用法详细介绍
二、TypeScript 语言
1、编程语言的类型分类
强类型与弱类型(区分维度:类型安全)
强类型有更强的类型约束,而弱类型中几乎没有什么约束
强类型语言不允许随意的隐式类型转换,而弱类型语言则允许任意的数据隐式类型转换
//强类型的优势
1、错误更早暴露
2、代码更智能,编码更准确
3、重构更牢靠
4、减少不必要的类型判断
静态类型与动态类型(区分维度:类型检查)
静态类型:一个变量声明时它的类型就是明确的,而且变量声明过后,它的类型就不允许再修改
动态类型:运行阶段才能够明确变量类型,而且变量的类型随时可以改变
注:动态类型语言中的变量没有类型,变量中存放的值是有类型的(JavaScript是一门标准的动态类型语言)
2、Flow介绍
Flow是JavaScript的类型检查器
功能:可以弥补JavaScript弱类型所带来的弊端
flow类型文档
flow第三方练习手册
3、TypeScript 教程
TypeScript 文档
三、JavaScript 性能优化
1、JavaScript 内存管理
内存:由可读写单元组成,表示一片可操作空间
管理:人为的去操作一片空间的申请、使用和释放
内存管理:开发者主动申请空间、使用空间、释放空间
管理流程:申请-使用-释放
//申请
let obj = {}
//使用
obj.name = 'aaa'
//释放
obj = null
2、GC算法介绍
GC定义与作用
GC 就是垃圾回收机制的简写
GC 可以找到内存中的垃圾、并释放和回收空间
GC 里的垃圾是什么
//程序中不再需要使用的对象
function func() {
name = 'ww'
return `${name} is a coder`
}
func()
//程序中不能再访问到的对象
function func(){
const name = 'ww'
return `${name} is a coder`
}
func()
GC 算法是什么
GC 是一种机制,垃圾回收器完成具体的工作
工作的内容就是查找垃圾释放空间、回收空间
算法就是工作时查找和回收所遵循的规则
常见 GC 算法
引用计数
标记清楚
标记整理
分代回收
3、引用计数算法实现原理
// 核心思想:设置引用数,判断当前引用数是否为0
// 引用计数器
// 引用关系改变时修改引用数字
// 引用数字为0时立即回收
const user1 = {age: 11}
const user2 = {age: 22}
const user3 = {age: 33}
const nameList = [user1.age, user2.age, user3.age}
function fn() {
const num1 = 1
const num2 = 2
}
fn()
//注:fn函数调用执行结束 => 外部全局就不能找到num1、num2 => num1、num2身上的引用计数回到0 => GC工作将num1、num2当做垃圾进行回收
4、引用计数算法优缺点
// 优点
发现垃圾时立即回收
最大限度减少程序暂停
// 缺点
无法回收循环引用的对象
时间开销大
//无法回收循环引用的对象 代码演示
function fn() {
const obj1 = {}
const obj2 = {}
obj1.name = obj2
obj2.name = obj1
return 'qqq'
}
fn()
//注:由于obj1、obj2在作用域内有互相指引关系,所以它们身上的引用计数器的数值并为0,所以GC无法进行回收,从而造成了内存空间的浪费
5、标记整理算法实现原理
标记整理可以看做是标记清除的增强
标记阶段的操作和标记清除一致
清除阶段会先执行整理,移动对象位置
6、标记清除优缺点
// 优点
可以回收循环引用的对象
// 缺点
容易产生碎片化空间,浪费空间
不会立即回收垃圾对象
7、标记整理优缺点
// 优点
减少碎片化空间
// 缺点
不会立即回收垃圾对象
8、V8
V8 简介
V8 是一款主流的JavaScript 执行引擎
V8 速度很快,因为采用即使编译
V8 内部内存设有上限,在64位操作系统下一般不超过1.5GB,在32位操作系统下不超过800MB
V8 中常用 GC 算法
分代回收 (分为新生代、老生代,不同代的对象采用更适合的 GC 算法)
空间复制
标记清除
标记整理
标记增量
V8 内存分配
V8 内存空间一分为二
小空间用于存储新生代对象(32M | 16M)
新生代指的是存活时间较短的对象
From和To是新生代存储区
新生代对象回收实现
回收过程采用复制算法 + 标记整理
新生代内存区分为二个等大小空间
使用空间为 From,空闲空间为 To
活动对象存储于 From 空间
标记整理后将活动对象拷贝至 To
From 与 To 交换空间完成释放
新生代对象回收细节说明
拷贝过程中可能出现晋升
晋升就是将新生代对象移动至老生代
一轮 GC 还存活的新生代需要晋升
To 空间的使用率超过 25% 需要晋升
老年代对象说明
老年代对象存放在右侧老生代区域
64位操作系统1.4G,32位操作系统700M
老年代对象就是指存活时间较长的对象
老年代对象回收实现
主要采用标记清除、标记整理、增量标记算法
首先使用标记清除完成垃圾空间的回收(主要使用的算法)
采用标记整理进行空间优化(新生代晋升时触发)
采用增量标记进行效率优化
标记增量优化垃圾回收流程图
新生代和老生代细节对比
新生代区域垃圾回收使用空间换时间
老生代区域垃圾回收不适合复制算法
V8引擎执行流程
Scanner 是一个扫描器
Parser 是一个解析器
Ignition 是 V8 提供的一个解释器
TurboFan 是 V8 提供的编译器模块
// 预解析优点
跳过未被使用的代码
不生成 AST,创建无变量引用和声明的 scopes
依据规范跑出特定错误
解析速度更快
//全量解析
解析被使用的代码
生成 AST
构建具体 scopes 信息,变量引用、声明等
抛出所有语法错误
//案例
//声明时未调用,因此会被认为是不被执行的代码,进行预解析
function foo() {
console.log('foo')
}
//声明时未调用,因此会被认为是不被执行的代码,进行预解析
function fn() {}
//函数立即执行,只进行一次全量解析
(function bar() {
console.log('bar')
})()
//执行 foo,那么需要重新对 foo 函数进行全量解析,此时 foo 函数被解析了两次
foo()
9、Performance工具介绍
为什么使用Performance
GC 的目的是为了实现内存空间的良性循环
良性循环的基石是合理使用
时刻关注才能确定是否合理
Performance 提供多种监控方式(时刻监控内存)
Performance 使用步骤
打开浏览器输入目标网址
进入开发人员工具面板,选择性能
开启录制功能,访问具体界面
执行用户行为,一段时间后停止录制
分析界面中记录的内存信息
内存问题的外在表现(网络环境正常的情况下)
//1、页面出现延迟加载或经常性暂停
底层可能伴随频繁的垃圾回收出现,原因是程序代码中有一些代码瞬间让内存爆掉了
//2、页面持续性出现糟糕的性能
底层存在内存膨胀,指的是当前界面为了达到最佳的使用速度,会 去申请一定的内存空间,但是所申请的内存空间大小远超过当前设备所能提供的内存大小
//3、页面的性能随时间延长越来越差
内存泄漏
界定内存问题的标准
内存泄漏:内存使用持续升高
内存膨胀:在多数设备上都存在性能问题
频繁垃圾回收:通过内存变化图进行分析
监控内存的几种方式
浏览器任务管理器
Timeline 时序图记录
堆快照查找分离 DOM
判断是否存在频繁的垃圾回收
为什么要判断是否存在频繁的GC(垃圾回收)
GC 工作时应用程序是停止的
频繁且过长的 GC 会导致应用假死
用户使用中感知应用卡顿
如何确定频繁的垃圾回收
Timeline 中频繁的上升下降
任务管理器中数据频繁的增加减少
10、堆栈处理
堆栈准备
JS执行环境
执行环境栈(ECStack,execution context stack)
执行上下文
VO(G),全局变量对象
//案例
var x = 100
var y = x
y = 200
console.log(x)
// 基本数据类型是按值进行操作
//基本数据类型值是存放在 栈区的
//无论我们当前看到的栈内存,还是后续引用数据类型会使用的堆内存都属于计算机内存
//GO(全局对象)
对象堆栈执行详解
//案例1
var obj1 = {x:100}
var obj2 = obj1
obj2['x'] = 200
console.log(obj1.x)
//答案:200
//案例2
var obj1 = {x:100}
var obj2 = obj1
obj1.y = obj1 = {x:200} //首先执行的是obj1.y
console.log(obj1,y) //undefined
console.log(obj2) //{x:100, y:{x:200}}
案例1图解
函数堆栈执行详解
//案例
var arr = ['zce', 'alishi']
fucntion foo(obj){
obj[0] = 'zoe'
obj = ['教育']
obj[1] = ['大前端']
console.log(obj) //['教育', '大前端']
}
foo(arr)
console.log(arr) //['zoe', 'alishi']
//函数创建
可以将函数名称看做是变量,存放在 VO 当中,同时它的值就是当前函数对应的内存地址
函数本身也是一个对象,创建时会有一个内存地址,空间内存放的就是函数体代码(字符串形式的)
//函数执行:函数执行时会形成一个全新私有上下文,它里面有一个 AO 用于管理这个上下文当中的变量
//步骤
作用域链 <当前执行上下文,上级作用域所在的执行上下文>
确定 this 指向
初始化 arguments(对象)
形参赋值:它相当于是变量声明,然后将声明的变量放置于 AO
变量提升
代码执行
闭包堆栈处理
//案例
var a = 1
function foo(){
var b = 2
return function(c){
console.log(c + b++)
}
}
var f = foo()
f(5) //7
f(10) //13
//闭包:是一种机制,简单来说在一个大函数中返回一个小函数,并且返回的小函数被外部所调用,就会形成闭包
//形成闭包后大函数的执行上下文不会被释放,小函数可以获取大函数声明的变量
//函数调用形成了一个全新的私有上下文,在函数调用之后当前上下文不被释放就是闭包(临时不被释放)
//闭包的作用
保护:当前上下文当中的变量与其它的上下文中变量互不干扰
保存:当前上下文中的数据(堆内存)被当前上下文以外的上下文中的变量所引用,这个数据就保存下来
闭包与垃圾回收
浏览器都自有垃圾回收(内存管理,V8为例)
堆空间、栈空间
堆:当前堆内存如果被占用,就不能被释放掉,但是我们如果确认后续不再使用这个内存里的数据,也可以自己主动置空,然后浏览器就会对其进行回收
栈:当前上下文中是否有内容,被其它上下文的变量所占用,如果有则无法释放(闭包)
11、JSBench 介绍
JSBench 是一个在线的可以测试js代码执行效率的网站
11、防抖和节流
为什么需要防抖和节流
在一些高频事件触发的场景下我们不希望对应的事件处理函数多次执行
浏览器默认情况下都会有自己的监听事件间隔(4~6ms),如果检测到多次事件的监听执行,那么就会造成不必要的资源浪费
防抖和节流的概念
前置场景:界面上有一个按钮,我们可以连续多次点击
防抖:对于这个高频的操作来说,我们只希望识别一次点击,可以人为是第一次或者是最后一次
节流:对于高频操作,我们可以自己来设置频率,让本来会执行很多次的事件触发,按着我们定义的频率减少触发的次数
应用场景
滚动事件
输入的模糊匹配
轮播图切换
点击操作
......