ECMAScript - 学习笔记 🎬
🧩nvm node.js
包管理工具
🧩nrm npm
镜像管理工具
nrm ls
nrm test [name] // 测试延迟
nrm use [name] // 使用
nrm add [alias] [url] // 添加自己的源
nrm del [name] // 删除源
🧩新的声明方式 let
定义变量
-
let
特性
- 不属于顶层对象的
window
- 全局变量和
var
都可以用window
去指向,这就会导致 污染了全局变量 - 虽然
webpack
打包会规避掉这个问题。
- 全局变量和
- 不允许重复声明
- 不存在变量提升
- 暂时性死区
Demo1:
a = 2
let a
Demo2: 比较隐蔽的
function foo(a = b, b = 2) {
// 上面的第一个默认参数用b时,b还没有被赋值 所以会报错
}
foo()
- 块级作用域
// 因为 var 没法在 {} 中形成一个块,所以最后 i++ 后,循环外可以使用i它是3
for (var i = 0; i < 3; i++) {
console.log('循环内:' + i) // 0 1 2
}
console.log('外:' + i) // 3
注意:块级作用域必须要有
{}
大括号
// 这样就会报错
if (true) let a = 5
知识点 + 面试题:
for (var i = 0; i < 3; i++) {
// 异步
setTimeout(function () {
console.log(i); // 直接输出了i的最后结果:③ 3
})
}
// 解决方案1:
for (var i = 0; i < 3; i++) {
(function(j){
setTimeout(function () {
console.log(j);
})
})
(i) // 调用方法
}
// 解决方案2:(可以通过 babel 查看编译成ES5的语法)
for (let i = 0; i < 3; i++) {
setTimeout(function () {
console.log(i);
})
}
扩展知识:
webpack
打包目录注意static
不会被打包进去webpack
打包时会自动每一行代码拼接\n
所以js
中不需要去刻意加";"
- Babel 是一个 JavaScript 编译器
🧩新的声明方式 const
定义常量
const
特性和let
是一样的特性-
栈内存(stack)、堆内存(heap)
- 栈内存里的存放
变量名 | 值 |
---|---|
num | 123456 |
str | 'abcd' |
obj | 引用地址 |
arr | 引用地址 |
-
栈内存(stack)、堆内存(heap)
- 堆内存里的存放
obj: {name: '三脚猫'} arr: ['ES6', 'ES7', 'ES8']
-
JS API
- ES5 定义常量API:
Object.defineProperty(window, 'NAME', { value: '三脚猫', writable: false // 是否可写的,覆盖 })
- Object.freeze(obj) // 冻结对象:因为是浅层冻结,所以多级需要递归冻结
知识点
const
定义的变量不可赋值,但是对象/数组可以改变,因为对象/数组都存在堆内存,栈内存中只是引用地址不变
Test:
const obj = {
name: '三脚猫'
}
console.log(obj);
obj.englistName = 'Harvey'
console.log(obj);
🧩解构赋值
-
数组解构赋值
- 可设置初始值
let [a = 1] = []
- 可设置初始值
-
对象解构赋值
- 可设置初始值
let {a = 1} = {}
- 起别名
let {name: username} = {name: '三脚猫'}
- 可设置初始值
注意:
- 数组解构时,是通过顺序去解构赋值的
- 对象解构时,是通过
key
值去结构的,改变了key
值顺序是没有影响
-
字符串解构赋值
- 同数组结构写法一样
函数的解构赋值
function foo({name, age}) {
console.log(name, age);
}
foo({name: '三脚猫', age: 18})
- 应用:提取
json
数据
let jsonStr = '{"a": "哈哈", "b": "嘿嘿"}'
let {a, b} = JSON.parse(jsonStr)
console.log(a, b);
🧩数组的遍历方式
ES5 中的数组遍历方式
-
for
循环 -
forEach
forEach(function (item, key, arrSelf) {})- 不支持
break
- 不支持
continue
- 不支持
-
map()
返回新的Array -
filter()
返回符合条件的元素数组 -
some()
返回boolean,判断是否有符合的元素 -
every()
返回boolean,判断每个元素都要符合条件,不然返回false
-
reduce()
接收一个函数作为累加器- 4个参数
prev curr index arrSelf
- reduce第二个值是初始值,没有设置初始值,则将数组第一个元素作为初始值
- reduce 循环找出最大值 Demo:
let arr = [1, 2, 3] let max = arr.reduce(function(previous, current, index, array) { let maxValue = Math.max(previous, current); return maxValue; // 每次都返回该次循环比对的最大值,下一次循环用来比较用 }) console.log(max);
- reduce 循环去重 Demo:
let max = arr.reduce(function(prev, curr) { // indexOf 如果要检索的字符串值没有出现,则该方法返回 -1 prev.indexOf(curr) == -1 && prev.push(curr) return prev }, []) console.log(max);
- 4个参数
- for in 循环
- 会循环数组原型下定义的元素
Array.prototype
原型方法 - Demo:
// 这里用原型定义了一个方法,会被 for in 给循环出来 Array.prototype.foo = function() { console.log('foo'); } let arr = [1, 2, 3] for (let index in arr) { console.log(index, arr[index]); }
- 会循环数组原型下定义的元素
ES6 中的数组遍历方式
-
find()
返回第一个通过测试的元素- 未找到元素时,返回
undefined
- 未找到元素时,返回
-
findIndex()
返回的值为该通过第一个元素的索引- 未找到元素时,索引值返回是 -1
-
for of
- 三种写法Demo:
for (let item of arr.values()) { // 不加 values() 默认也是 value console.log(item); // 拿到每个value } for (let index of arr.keys()) { // keys() 是一个方法 console.log(index); // 拿到每个 key } for (let [index, item] of arr.entries()) { console.log(index, item); // 拿到每个键值对 }
- 三种写法Demo:
🧩ES6新的特性 - 数组的扩展
-
类数组/伪数组
- 也有长度但是不能使用数组的方法
- 比如:
let divs = document.getElementByTagName('div') let divs2 = document.getElementByClassName('div_class') console.log(divs, divs2); // 这都是伪数组 let divs3 = document.quertSelectorAll('xxx') console.log(divs3); // 获取的是 NodeList 节点,但也是 伪数组 // 检测是否是数组 instanceof console.log(divs3 instanceof Array); divs3.push(123); // 通过报错检测
-
Array.from()
把伪数组,转成真正意义上的数组-
ES5
用slice
把伪数组转化为真数组let arr = Array.prototype.slice.call(divs3) // 它会返回一个新的数组 console.log(arr);
-
-
Array.of()
初始化数组ES5: let arr = new Array(1, 2) // 初始化构造数组 let arr = new Array(3) // 注意为一个值的时候 3 代表的是数组长度并不是值 // 用 Array.of() 解决一个参数的问题 let arr2 = Array.of(3); // [3]
-
copyWithin()
替换数组的元素- 它接受三个参数
- target (必需):从该位置开始替换数据
- start (可选):从该位置开始读取数据,默认为 0 。如果为负值,表示倒数
- end (可选):到该位置前停止读取数据,默认等于数组长度。如果为负值,表示倒数
let arr = ["哈哈", "啦啦", "哎哎", "点点", "嘿嘿", "讷讷"] // 0 1 2 3 4 5 // arr.copyWithin(1, 3, 5); // 把 1 2替换到 3 4 // arr.copyWithin(0, -2); // 把 4 5 替换到 0 1 // arr.copyWithin(1, -2); // 把 4 5 替换到 1 2 arr.copyWithin(4, 1); // 把 1 2 替换到 4 5 console.log(arr);
-
fill()
替换填充- 它接受三个参数
- 准备替换的值, start从哪里开始, end到哪里结束(不包含结束的下标)
let arr = ["哈哈", "啦啦", "哎哎", "点点", "嘿嘿", "讷讷"] arr.fill('---', 2, 4); console.log(arr); // ["哈哈", "啦啦", "---", "---", "嘿嘿", "讷讷"] // 如果传一个参数,将默认为全部替换掉 arr.fill('---');
-
includes()
-
ES5
写法arr.indexOf()
检查目标数组中是否包含并返回下标,没有返回 -1 -
indexOf()
不能检测数组下的NaN
-
includes()
直接返回bool
值,并且可以判断NaNarr.includes(NaN)
-
注意:
在
js
当中console.log(NaN == NaN) // false
,所以想判断数组中是否存在就用includes()
🧩ES6新的特性 - 函数的参数
参数的默认值
function test(a, b = 2) {}
-
与解构赋值结合
- Demo:
function ajax(url, {body = '', method = 'GET', headers = {}} = {}) { console.log(method); // POST } ajax('http://', {method: 'POST'})
- Demo:
-
length 属性
-
length
是返回没有指定默认值的方法 Demo:function foo(x, y, z = 3) { console.log(x, y); } // length function console.log(foo.length); // 2
-
-
作用域 (方法括号的作用域)
- 面试思考题 第一个例子 Demo:
let x = 1 function foo(x, y = x) { // 受 (x, y = x) 括号的作用域的影响该值是 2 console.log(y); // 2 } foo(2);
- 面试思考题 第二个例子 Demo:
let x = 1; function foo(y = x) { let x = 2; // 根据作用域链,括号内的参数 x 会向外作用域找 x 定义值所以是 1 console.log(y); // 1 } foo();
- 面试思考题 第一个例子 Demo:
-
函数的
name
属性- 匿名函数用
.name
方法会获取该值 =>anonymous
- Demo:
function foo(){} console.log(foo.name);
- 匿名函数用
-
函数的
bind()
方法,改变this
指向-
bind
方法会创建一个函数,该函数的this指向了传入的第一个参数,当bind()的参数为空时,this指向全局对象。如浏览器中的window -
bind().name
方法会获取该值 =>bound
- Demo:
function foo() { console.log(this.name); // 三脚猫 } let f = foo.bind({name: '三脚猫'}); f();
-
扩展知识:JavaScript 中 call()、apply()、bind() 的用法,
JS中call, apply, bind 的区别 - 知乎
🧩扩展运算符 与 rest参数
-
扩展运算符
- 把数组或者类数组展开成用逗号隔开的值
function foo(a, b, c) { console.log(a, b, c); } let arr = [1, 2, 3] foo(...arr) // 打散字符串 let str = 'abcd' var arr = [...str] // ["a", "b", "c", "d"]
- 数组合并 ES5 实现
let arr1 = [1, 2, 3] let arr2 = [4, 5, 6] Array.prototype.push.apply(arr1, arr2)
- 数组合并 ES6 实现
arr1.push(...arr2)
- 把数组或者类数组展开成用逗号隔开的值
-
rest参数 (rest剩余的意思)
- 把逗号隔开的值组合成一个数组
- 通过
arguments
实现累加Demo:// arguments function foo(x, y, z) { console.log(arguments); // 伪数组 let sum = 0 // ES5 中转化伪数组 // Array.prototype.forEach.call(arguments, function(item) { // sum += item // }) // ES6 中转化伪数组 Array.from(arguments).forEach(function (item) { sum += item }) return sum } console.log(foo(1, 2)); // 3 console.log(foo(1, 2, 3)); // 6
- 通过
rest
实现累加Demo:function foo(...args) { let sum = 0 args.forEach(function(item) { sum += item }); return sum } console.log(foo(1, 2)); // 3 console.log(foo(1, 2, 3)); // 6
- 扩展写法:
// 1. 函数 形参的使用 function foo(x, ...args) { console.log(x); console.log(args); } foo(1, 2, 3) // 2. 结合 解构的使用: let [x, ...y] = [1, 2, 3] console.log(x); // 1 console.log(y); // [2, 3]
🧩ES6 箭头函数
-
写法Demo:
-
=>
箭头左边是参数,右边是函数体let sum = function (x, y) { return x + y } // 由上面演变成 ↓↓↓ // => 箭头左边是参数,右边是函数体 let sum = (x, y) => { return x + y }
- 简写
// 如果只有一行直接简写,默认有一个 return x + y let sum4 = (x, y) => x + y // 只有一个参数 参数括号 也可以省略 let x = x => x console.log(x(123)); // 123
-
-
箭头函数和普通函数的差别
-
this
指向定义时所在的对象,而不是调用时所在的对象- 其实在箭头函数里,并没有
this
对象,其实它的this
是继承了外面上下文的this
<button id='btn'>点我<button>
let oBtn = document.querySelector('#btn') oBtn.addEventListener('click', function() { console.log(this); // <button id="btn">点我</button> // window.setTimeout(function() { // console.log(this); // window对象 // }, 1000) // 1. bind 改变 this 指向 setTimeout(function() { console.log(this); // <button id="btn">点我</button> }.bind(this), 1000) // 2. 箭头函数改变 this 执行 setTimeout(() => { console.log(this); // <button id="btn">点我</button> }, 1000) })
- 其实在箭头函数里,并没有
- 不可以当做构造函数
/** * 放在 webpack 里不会报错,会转成了 ES5 执行了 * 正常报错 People is not a constructor */ let People = (name, age) => { this.name = name this.age = age } let p1 = new People('三脚猫', 18) console.log(p1);
- 不可以使用
arguments
对象
// 放在 webpack 里不会报错,会转成了 ES5 执行了,应该使用 rest参数代替 let args = () => { console.log(arguments); } args(1, 8)
-
🧩ES6 对象的扩展
-
ES6 属性简洁表示法
let name = '三脚猫' let age = 18 let obj = { name, age } console.log(obj); // {name: "三脚猫", age: 18}
-
属性名表达式
let name = '三脚猫' let age = 18 let s = 'school' let obj = { name, age, [s]: 'Harvard' // 属性名表达式 study() { console.log('我叫 ' + this.name); // 我叫 三脚猫 } } console.log(obj); obj.study() // 我叫 三脚猫
注意:对象中不要用箭头函数定义方法,应该用
ES6
给出的方法定义,this
会获取不到属性值
-
Object.is()
Object.is(NaN, NaN) // true Object.is(obj1, obj2) // false 因为是判断的对象的栈内存的地址是否一样
-
扩展运算符 与
Object.assign()
//assign
用于合并对象let x = { a: 3, b: 4, } let y = {...x} // 扩展运算符合并对象方式 let z = { a: 888 } Object.assign(z, x) // 合并对象,并且后面的会覆盖前面的属性值
-
in
数组就是检测下标,如果是对象就是检测 是否包含某个属性y = {a: 1} console.log('a' in y); // true 是否包含某个属性 // 检测数组 let arr = [1, 2, 3] console.log(3 in arr); // false 找的是数组的 3 下标所以没有就 false
-
对象的遍历方式
let obj = { name: '三脚猫', age: 18 }
-
for in
方式for(let key in obj) { console.log(key, obj[key]); }
-
Object.keys()
方式Object.keys(obj).forEach(key => { console.log(key, obj[key]); })
-
Object.getOwnPropertyNames()
ES5 方式Object.getOwnPropertyNames(obj).forEach(key => { console.log(key, obj[key]); })
-
Reflect.ownKeys()
ES6方法Reflect.ownKeys(obj).forEach(key => { console.log(key, obj[key]); })
-
🧩深拷贝与浅拷贝
-
Object.assign()
是浅拷贝 (只有一层)let target = { a: 1, b: { c: 888, d: 999 // 被覆盖了 } } let source = { a: 1, b: { c: 3 } } let res = Object.assign(target, source) console.log(res); // 结果等同于source
-
深拷贝和浅拷贝的比较 (栈内存、堆内存的影响)
- 深拷贝: b没有受影响
let a = 2 let b = a a = 10 console.log(b); // 2
- 浅拷贝: obj都被变化了
let obj1 = { name: '三脚猫', age: 18 } let obj2 = obj1 // 指向了同一块内存地址 obj1.age = 0 console.log(obj1); console.log(obj2);
- 深拷贝: b没有受影响
-
解决深拷贝的问题,复制同一个对象的方式方法
-
parse()
和stringify()
实现let obj1 = { name:'三脚猫', test: { 'age': 18 } } let str = JSON.stringify(obj1) let obj2 = JSON.parse(str) obj1.test.age = 88 console.log('obj1', obj1); // age: 88 console.log('obj2', obj2); // age: 18
-
递归实现 (每一个属性赋值给新的对象/数组)
// - 封装检查类型方法 let checkType = data => Object.prototype.toString.call(data).slice(8, -1) // - 封装递归 let deepClone = target => { console.log('target', target); let targetType = checkType(target) let result // 定义返回值 if (targetType === 'Object') { result = {} }else if(targetType === 'Array') { result = [] } else { return target // 返回当前值 } for (let key in target) { let value = target[key] let valueType = checkType(value) if (valueType === 'Object' || valueType === 'Array') { result[key] = deepClone(value); // 递归 } else { result[key] = value } } return result } // - 定义一个对象 let obj1 = { name: '三脚猫', sex: 'boy', study: { game: false, es6: true } }; // - 调用递归 let obj2 = deepClone(obj1) obj1.study.es6 = '加油' console.log(obj1); // es6: 加油 console.log(obj2); // es6: true
-
知识点:
- 检查数据类型不要用
typeof()
当检查数组和对象时,都返回的是Object
- 用对象原型
toString().call()
检查更准确js判断数据类型.slice(start, end)
字符串/数组的截取函数
🧩ES5 中的类与继承
注意:首字母大写更容易看出是类
function People()
-
方法体本身也是 构造函数
// 类 function People(name, age) { console.log(this); // 指向实例化对象 this.name = name this.age = age People.count++ // 使用静态属性 } People.count = 0 // 定义静态属性 // 通过 new 关键字实例化 let p1 = new People('三脚猫', 18) console.log(p1); let p2 = new People('三脚狗', 20) console.log(p2);
-
不要把方法定义在类里,应该定义在类的原型上
function People(name, age) { this.name = name this.age = age } // 定义方法 People.prototype.showName = function () { console.log('名字 ' + this.name); } p1.showName() // 名字 三脚 猫 p2.showName() // 名字 三脚 狗
-
静态属性,定义在类外部,可以直接用类名去访问
// js 自带的静态方法 Math.max() // max 是静态方法 // 自定义静态 People.count = 0 // 自定义静态属性 // 定义静态方法 People.getCount = function() { console.log('当前共有 ' + People.count + '次调用'); }
-
ES5 中实现类的继承 (组合式继承)
// 定义一个 动物 父类 function Animal(name) { this.name = name } // 给父类定义一个方法 Animal.prototype.showName = function () { console.log('名字叫:' + this.name); } // 创建 狗 的子类,准备继承父类 function Dog(name, color) { // 改变 this 的指向 Animal.call(this, name) // 第二个参数使用父类的成员属性 name 值,所以要传递进去 this.color = color } // 修改子类原型 // console.log(Dog.prototype); // 先查看一下 Dog 的原型 Dog.prototype = new Animal() // console.log(Dog.prototype); // 改变后在查看一下 Dog.prototype.constructor = Dog // console.log(Dog.prototype); let dog = new Dog('豆豆', '白灰') // 第一个参数使用的是父类的name console.log(dog); // 调用父类方法 dog.showName() // 直接调用会报错,需要修改原型
🧩 ES6 中的类与继承
- ES6 类的继承只是语法糖,让语法写起来很舒服,实际还是编译成了ES5
关键字:
class
、extends
、constructor
、super
、static
、get / set
- 实现一个类继承 Demo:
// 定义一个类
class People {
constructor(name, age) {
this.name = name
this.age = age
this._sex = null
}
showName() {
console.log('名字:' + this.name);
}
// 父类定义静态方法
static getHello() {
return 'Hello';
}
// 定义 get & set
get sex() {
if (this._sex === 1) {
return '女士'
} else if (this._sex === 0) {
return '男士'
} else {
return '未知'
}
}
set sex(val) {
this._sex = val
}
}
// 定义父类的静态属性 (ES5语法)
People.world = 'World'
let str = People.getHello() + ' ' + People.world
console.log(str); // Hello World
// 实例
let _people = new People('三脚猫', 18);
console.log(_people);
_people.showName()
// 定义子类
class Coder extends People {
constructor(name, age, company) {
super(name, age) // 继承父类的成员属性
this.company = company
}
showCompany() {
console.log('公司是:' + this.company);
}
}
// 实例子类
let _coder = new Coder('Harvey', 19, 'tri-footed cat')
console.log(_coder);
_coder.showCompany()
_coder.showName() // 调用父类方法
// 设置 父类 set 方法
_people.sex = 0
console.log(_people.sex);
知识点: ES6类中,定义静态属性要和ES5语法一样。
🧩ES6 - Symbol (森宝�) 独一无二的不能重复的字符串
-
一种新的原始数据类型 (注意它不是一个对象)
一共加上Symbol
有7种原始数据类型- undefined
- null
- bool
- string
- number
- object (Array 也是 object)
- symbol
-
它的特性&声明方式:
-
是独一无二的
let s1 = Symbol() let s2 = Symbol() console.log(s1 == s2); // false
-
可以把参数传进去用于描述
Symbol
let s = Symbol('foo') console.log(s); // Symbol(foo)
-
如果参数是一个对象的话
Symbol
会自动调用toString()
方法const obj = {name: '三脚猫'} let s = Symbol(obj) console.log(s); // Symbol([object object])
-
Symbol
的方法-
.description
获取描述let s = Symbol(foo) console.log(s.description); // foo
-
Symbol.for()
相当于定义在全局的环境中let s1 = Symbol.for('foo') let s2 = Symbol.for('foo') // 回去全局找有没有foo的声明,如果有就相同的用 console.log(s1 == s2); // true // 全局的就算在{}作用域下也相等 function getSymbol() { return Symbol.for('foo') } let s3 = getSymbol() console.log(s2 == s3); // true
-
Symbol.keyFor()
返回一个已经登记过的keyconst s1 = Symbol('foo') console.log(Symbol.keyFor(s1)); // undefined const s2 = Symbol.for('foo') console.log(Symbol.keyFor(s2)); // foo
-
-
-
应用场景
-
解决对象中
key
值的重复冲突问题const stu1 = Symbol('三脚猫') const stu2 = Symbol('三脚猫') const grade = { [stu1]: {address: 'yyy'} [stu2]: {address: 'zzz'} } // 会有两个三脚猫对应两个对象 // Symbol(三脚猫): {address: 'yyy'} Symbol(三脚猫): {address: 'zzz'} console.log(grade); // 想要获取这种重复的key方法: console.log(grade[stu1]); // {address: 'yyy'} console.log(grade[stu2]); // {address: 'zzz'}
-
在一定程度上 可以作为私有属性定义
const sym = Symbol('imooc') class User { constructor() { this.name = name this[sym] = '三脚猫' // 定义一个变量属性 } getName() { return this.name + this[sym] } } const user = new User('Harvey') // for in 方式取不到 sym值 for(let key in user) { console.log(key); // 只有 name属性 } // for of 方式也取不到 sym值 for(let key of Object.keys(user)) { console.log(key); // 只有 name属性 } // for of getOwnPropertySymbols() 只能取到 sym值 for(let key of Object.getOwnPropertySymbols(user)) { console.log(key); // 只有 sym值 } // Reflect.ownKeys() 都可以取到 for(let key of Reflect.ownKeys(user)) { console.log(key); // name 和 sym 都可以取到 }
-
消除魔术字符串
const styleType = { blue: Symbol(), // '蓝色' 或者 '蓝' 已经不重要了 red: Symbol(), // '红色' 或者 '红' } function getColor(color) { switch (color) { case styleType.blue: return 'this is blue' case styleType.red: default: return 'this is red' } } let c = getColor(styleType.blue); console.log(c);
-
🧩ES6 - Set (一种新的数据结构)
-
特点(没有重复的值)
- 相对于数组来说,数组里有重复的值。
set
没有重复的值
let s = new Set([1, 2, 3, 2]) console.log(s) // Set{1, 2, 3} 没有重复的值
- 相对于数组来说,数组里有重复的值。
-
一种新的数据结构
-
key
和value
是一样的值
-
-
常用方法 (并且支持链式操作)
-
.add()
添加值
let s = new Set([1, 2, 3]) s.add('string') s.add('AAA').add(123) // 链式操作
-
.delete()
删除值
s.delete('2')
-
.clear()
清空所有值
s.clear()
-
.has()
判断当前是否存在某个值
s.has(1) // true
-
.size()
相当于数组的length
获取元素个数
s.size(s) // 3
-
-
遍历
-
forEach()
遍历
s.forEach(item => console.log(item))
-
for of
三种方法都支持.keys() .values() .entries()
// .entries() 同时获取key和value for (let item of s.keys()) { // .values() set里key值等于value值 console.log(item) }
-
-
应用场景
- 数组去重
let arr = [1, 2, 3, 4, 3, 2] let s = new Set(arr) console.log(s) // [1, 2, 3, 4]
- 合并去重
let arr1 = [3, 4, 3, 1] let arr2 = [4, 3, 2] let s = new Set([...arr1, ...arr2]) // 扩展运算符 合并 console.log(s) // [1, 2, 3, 4] console.log([...s]) // 把set 转化为数组 console.log(Array.from(s)) // 把set 转化为数组
- 获取交集
let arr1 = [3, 4, 3, 1] let arr2 = [4, 3, 2] let s1 = new Set(arr1) let s2 = new Set(arr2) // 循环 arr1 然后用 filter过滤 .has()判断 arr2 满足条件返回 let result = new Set(arr1.filter(item => s2.has(item))) console.log(result) // Set {3, 4} 输出交集重复值
- 获取差集
// 用 非 方式 let result = new Set(arr1.filter(item => !s2.has(item))) console.log(result) // Set {1, 2} 输出差集
-
WeakSet (
WeakSet
和Set
什么区别?)-
WeakSet
只能存储对象 - 不可以遍历 (垃圾回收机制)
- 它是弱引用,对象销毁时它定义的对象也会销毁
let ws = new WeakSet() ws.add({name: '三脚猫'}) ws.delete({name: '三脚猫'}) // 存在引用地址 栈内存问题 删除不掉 // 正确定义方式: const obj = {name: '三脚猫'} ws.add(obj) ws.delete(obj)
- 垃圾回收机制
每次引用变量
obj
引用次数都会 +1,后果是不清零会一直占用内存,多了的话会内存泄漏如果是
WeakSet
它是弱引用,当对象销毁时,它定义的对象也会跟着销毁,避免内存泄漏 -
注意:
WeakSet
删除对象时,也会存在栈内存引用地址问题,所以需要提前声明出对象赋给一个变量名
🧩ES6 - Map (一种新的数据结构)
-
常用方法、方式
-
.set()
设置值
// 设置键值对形式: let map = new Map() map.set('name', 'es') map.set('age', 18) // 0: {"name" => "es"} // 1: {"age" => 18} console.log(map); // set一个就是一个值 // 设置对象是key值 let m = new Map() let obj = { name: '三脚猫' } m.set(obj, 'es') // Map(1) {{…} => "es"} {key:{name: "三脚猫"}, value: 'es'} console.log(m); // key 是对象, value 是 es
-
.get()
获取值
console.log( m.get(obj) ); // es
-
.delete()
删除值
m.delete(obj) // 返回bool值 console.log(m);
- 数组定义方式:第一个元素当做
key
第二个元素是value
let map = new Map([ ['name', '三脚猫'] // ['name', '三脚猫', 'Harvey'], // 只是两个值 key=>value,第三个值不生效 ['age', 18] ]) // 0: {"name" => "三脚猫"} // 1: {"age" => 18} console.log(map); console.log(map.size); // 2 获取元素个数 console.log(map.has('name')); // true 检测是否存在key值 console.log(map.get('age')); // 18 获取对应值 map.set('name', 'Harvey') // 会把原有值 三脚猫 替换 console.log(map); map.delete('name') // 删除键值name console.log(map);
-
-
遍历
-
forEach
遍历 参数是先value 后 key
map.forEach((value, key) => console.log(value, key))
-
for of
遍历 参数是先key 后 value
// 同时也是支持 map.keys() .values() .entries() 三个方法 for (const [key, value] of map) { console.log(value, key); }
-
-
应用场景
- 可以用来判断对象中否存在某个key值,不需要像
Object
一样去循环判断,直接.has()
let obj = { name: '三脚猫' } let m = new Map(obj); console.log(m);
- 获取对象中的键值对个数,也直接用
.size()
- 可以用来判断对象中否存在某个key值,不需要像
补充:
Object
两种判断key
值方式
Object.keys()
然后如果是深判断就需要递归比较麻烦- 直接用
Object
的hasOwnProperty()
方法返回一个布尔值,判断对象是否包含特定的自身(非继承)属性
- WeakMap
- 键名只支持 引用数据 类型 (Array、Object)
- 不支持
.clear()
方法 - 不支持 遍历
- 因为不可遍历 所以也不支持
.size()
方法 - 也是弱引用,垃圾回收机制,不管使用多少次 使用次数都是 1次,销毁后全部销毁,防止内存泄漏
let wm = new WeakMap() wm.set([1], 2) // 两个参数都只能是一位 wm.set({name: '三脚猫'}, 'es') console.log(wm); // 可以获取DOM 相应存一些 info数据 let wm = new WeakMap() let elem = document.getElementsByTagName('h1') // 通过 `.get()` 获取到对应存储的信息 console.log(elem); console.log(wm.get(elem));
🧩字符串的扩展
-
[了解] 字符串的
Unicode
表示法 (ES6 中加强了 unicode字符支持)-
\uxxx
:\u
代表这是一个 unicode,xxx
代表 码点,码点的范围是:0000~ffff
- 在ES6中超出的码点,比如 𠮷 ,用
{}
去写:\u{20BB7}
A 在ES6 之前: \u0041 ES6 之后:\u{41} -> A
- 由于
ES6
的改进,可以用多种方式去表示出一个字符
比如字母说: z // 1. 非特殊字符表示: console.log('\z' === 'z'); // true // 2. webpack打包后,严格模式下不支持,需要拿到浏览器下查看 // 如果 \ 后面对应的是 三个8进制的数 表示出一个字符 console.log('\172' === 'z'); // true // 3. \x + 两位 16进制数 也可以表示 7A 也表示unicode的码点 console.log('\x7A' === 'z'); // true // 4. unicode 表示法: console.log('\u007A' === 'z'); // true // es6 写法: console.log('\u{7A}' === 'z'); // true
-
-
字符串的遍历器接口
-
for of
后面的值是可遍历的就都可以循环遍历
for (let item of '三脚猫') { console.log(item); }
-
-
模板字符串
用 `` 反引号包裹,用
${}
解析变量应用场景
// 换行字符串 const str1 = ` <ul> <li>123</li> <ul> ` // 字符串拼接 let name = '三脚猫' const str2 = `我的名字是${name},我今年要冲鸭~`
- 嵌套模板 实现
class
的替换
// 定义一个判断屏幕宽度函数 const isLargeScreen = () => { return true } let class1 = 'icon' // ES6 之前实现 class1 += isLargeScreen() ? ' icon-big' : ' icon-small' console.log(class1); // 用 ES6 的模板字符串实现 let class2 = `icon icon-${isLargeScreen() ? 'big' : 'small'}` console.log(class2);
- 高阶用法: 带标签的模板字符串
const foo = (a, b, c, d) => { console.log(a); console.log(b); console.log(c); console.log(d); } // foo(1, 2, 3, 4) const name = '三脚猫' const age = 18 // 这种可以直接调用方法参数 第一个参数是 字符串没被替换的部分 // b 输出 三脚猫, c 输出 18, d 输出 undefined (因为缺少一个变量) foo`这是${name},他的年龄是${age}岁`
-
String.fromCodePoint()
输出Unicode
字符- ES5 中写法
// ES5 中输出码点字符串 - 弊端是有些超出了码点范围 console.log(String.fromCharCode(0x20BB7)); // ES5 // 所以必须用 ES6 方法输出 console.log(String.fromCodePoint(0x20BB7)); // ES6
-
String.prototype.includes()
是否存在某个值 返回bool
值- ES5 中写法
// 返回字符串是否存在于某一字符串 const str = '三脚猫' console.log(str.indexOf('猫')); // 2 下标,不存在是 -1 // ES6 console.log(str.includes('猫')); // true
-
String.prototype.startsWith()
是否以什么开头 返回bool
值console.log(str.startsWith('三'));
-
String.prototype.endsWith()
是否以什么结尾 返回bool
值console.log(str.endsWith('猫'));
-
String.prototype.repeat()
重复N次字符串- 在 ES5 中要重复一个字符串只能循环
- 在 ES6 中可以用repeat方法
const str = '三脚猫' const newStr = str.repeat(10) // 重复10次 console.log(newStr);
🧩ES6 中的正则表达式的扩展
-
ES5 中的三个修饰符
- i 忽略大小写
- m 多行匹配
- g 全局匹配
-
ES6 中添加的修饰符
- y 修饰符,粘连修饰符
const str = 'aaa_aa_a' const reg1 = /a+/g // 每次匹配剩余的 const reg2 = /a+/y // 剩余的第一个开始匹配 console.log(reg1.exec(str)); // aaa console.log(reg2.exec(str)); // aaa console.log(reg1.exec(str)); // aa 匹配剩余的 console.log(reg2.exec(str)); // null 因为第二轮匹配的是 _aa 所以开头不是a console.log(reg1.exec(str));
- u 修饰符,unicode (会把超出范围的码点当做一个字符处理,更精准准确)
const str = '\uD842\uDFB7' // 表示一个字符 console.log(/^\uD842/.test(str)); // es5 true ,以为es5中看成了一个字符 console.log(/^\uD842/u.test(str)); // es6 false, 只有在es6中会看做一个字符从而匹配不到 // . 匹配除了换行符以外的任意字符,但是如果码点超出,也会匹配不到,就必须加 u 修饰符 console.log(/^.$/.test(str)); console.log(/^.$/u.test(str)); // 如果要识别码点字符,还是要加 u 的 console.log(/\u{61}/.test('a')); // false console.log(/\u{61}/u.test('a')); // true // 再举例一种 码点超出范围的 , 如果要匹配两次 console.log(/𠮷{2}/.test('𠮷𠮷'), '𠮷𠮷'); // false console.log(/𠮷{2}/u.test('𠮷𠮷'), '𠮷𠮷'); // true
🧩数值的扩展
-
进制转换 十进制转二进制 二进制转十进制
- ES5 中
// ES5 十进制转二进制 const a = 5 console.log(a.toString(2)); // 把 a 转化为 二进制,输出 101 // ES5 二进制转十进制 const b = 101 console.log(parseInt(b, 2)); // 把 b 当做二进制去识别,输出 5
- ES6 中用 0B表示二进制 0O表示八进制
// const test = 0101 // 在ES5中严格模式下,进制不允许前缀用0表示 const a = 0B0101 // 带 0B 则可以识别二进制 console.log(a); // 5
-
Number.isFinite()
Infinity 无限的 isFinite 判断一个值是否是有限的console.log(Number.isFinite(5)); // true 比如 5 / 0 也是无限的 Infinity console.log(Number.isFinite('三脚猫')); // false
-
Number.isNaN()
NaN: not a numberisNaN
判断一个数是不是 NaNconsole.log(Number.isNaN('a' / 5)); // true
-
Number.parseInt()
转化为整数console.log(Number.parseInt(5.5));
-
Number.parseFloat()
转化为浮点数console.log(Number.parseFloat(5.00));
-
Number.isInteger()
判断是否是 intconsole.log(Number.isInteger(18), 'isInteger'); // true console.log(Number.isInteger(5.5), 'isInteger'); // false
-
0.1 + 0.2 === 0.3 ??? 数字精度缺失问题
- IEEE 754 双精度标准 存储
- 在 ES 中整数的最大值是 2的53次方,最小值是 负2的53次方
const max = Math.pow(2, 53) console.log(max); // 9007199254740992 console.log(max + 1); // 9007199254740992 已经是最大值了 +1 也和上面一样 console.log(Number.MAX_SAFE_INTEGER); // 9007199254740991
-
Number.MAX_SAFE_INTEGER
最大的安全整数 max safe integer -
Number.MIN_SAFE_INTEGER
最小的安全证书 min safe integer -
Number.isSafeInteger()
判断是否是安全的整数
-
Math新增方法:
-
Math.trunc()
和Nubmer.parseInt()
的区别?console.log(Math.trunc(5.5)); // 5 console.log(Math.trunc(true)); // 1 bool值区别 console.log(Number.parseInt(5.5)); // 5 console.log(Number.parseInt(true)); // NaN bool值区别
-
Math新增方法:
Math.sign()
判断参数是正数 还是 负数 或者是 0console.log(Math.sign(5)); // 1 console.log(Math.sign(-5)); // -1 console.log(Math.sign(0)); // 0 console.log(Math.sign(true)); // 1 console.log(Math.sign(NaN)); // NaN
-
Math下新增方法:
Math.cbrt()
就算数值的立方根console.log(Math.cbrt(8)); // 2 console.log(Math.cbrt('三脚猫')); // NaN 不能转化的都是NaN
-
🧩ES6 新特性 - Proxy (代理) 中式发音:普若C
- ES5 代理方式:拦截对象的Api
Object.defineProperty()
let obj = {}
let newVal = ''
// 第一个参数目标拦截对象 第二个参数拦截的值 第三个闭包内置get和set钩子函数(Hook)
Object.defineProperty(obj, 'name', {
// 钩子函数 get
get() {
return newVal
},
// 钩子函数 set
set(val) {
// this.name = val // 这样会死循环,set设置完又set
newVal = val
}
})
console.log(obj.name); // ''
obj.name = 'es'
console.log(obj.name); // es
- ES6 代理方式 Proxy
let arr = [7, 8, 9] // 第一个参数是我们要包装的对象 // 第二个参数是代理的配置 arr = new Proxy(arr, { // get 获取数组中的值,如果没有返回error get(target, prop) { // in 数组就是检测下标,如果是对象就是检测 是否包含某个属性 return prop in target ? target[prop] : 'error' } }) console.log(arr[1]); // 8 console.log(arr[3]); // error
-
应用场景 (获取字典值)
-
get
方法
let dict = { 'hello': '你好', 'world': '世界' } dict = new Proxy(dict, { get(target, prop) { // in 数组就是检测下标,如果是对象就是检测 是否包含某个属性 return prop in target ? target[prop] : prop } }) console.log(dict['hello']); // 你好 console.log(dict['三脚猫']); // 三脚猫
-
set
方法 需要返回一个bool
值
let arr = [1, 2] arr = new Proxy(arr, { // target目标数组对象,prop当前目标属性值, val要设置的值 set(target, prop, val) { if (Number.isInteger(val)) { target[prop] = val return true } return false } }) arr.push(5) console.log(arr[2]) // 5
-
has
方法 判断当前key是否在对象里,需要返回一个bool
值
let range = { start: 1, end: 5 } // 判断值是否在这个对象的区间内 range = new Proxy(range, { has(target, prop) { return prop >= target.start && prop <= target.end } }) console.log(5 in range);
-
ownKeys
循环遍历时拦截
let userinfo = { username: '三脚猫', age: 18, _password: '******' } userinfo = new Proxy(userinfo, { ownKeys(target) { // 循环目标 return Object.keys(target).filter( key => !key.startsWith('_') ) } }) // 开始循环key for (let key in userinfo) { console.log(key); // username age 没有 _password } // 对象方法也会被拦截 console.log(Object.keys(userinfo)); // username age 没有 _password
-
-
集合
Proxy
代理操作写一个Demo
:要求是:对于下划线的字段 不允许 获取、设置、删除let user = { name: '三脚猫', age: 18, _password: '***' } user = new Proxy(user, { get(target, prop) { if (prop.startsWith('_')) { throw new Error('不可访问') } else { return target[prop] } }, set(target, prop, val) { if (prop.startsWith('_')) { throw new Error('不可设置') } else { target[prop] = val return true } }, deleteProperty(target, prop) { if (prop.startsWith('_')) { throw new Error('不可删除') } else { delete target[prop] return true } }, ownKeys(target) { return Object.keys(target).filter( key => !key.startsWith('_') ) } }) // get 操作 // console.log(user.name); // console.log(user._password); // 抛出异常 不可获取 // set 操作 // user._password = 123; // 抛出异常 不可设置 // console.log(user); // delete 操作 // console.log(delete user.age); // console.log(delete user._password); // 抛出异常 不可删除 // console.log(user); // 拦截循环 // for (let key in user) { // console.log(key); // 没有 _password 字段 // }
-
Proxy 拦截函数的调用
apply
let sum = (...args) => { let num = 0 args.forEach(item => num += item) return num } // 用 Proxy 拦截 sum = new Proxy(sum, { // target就是函数本身, ctx上下文, args传入的值 apply(target, ctx, args) { return target(...args) * 2 } }) console.log(sum(1, 2)); // 输出6 说明走了Proxy乘以2了
-
Proxy 拦截
new
一个函数类construct
let User = class { constructor(name) { this.name = name } } User = new Proxy(User, { // 目标对象,类的参数, 新目标 construct(target, args, newTarget) { console.log('construct'); return new target(...args) } }) console.log(new User('三脚猫'));
-
扩展知识:
在
Vue2.0
里实现双向数据绑定的是Object.defineProperty
这样的ES5
去实现的
在Vue3.0
里实现双向数据绑定的是用的Proxy
这种拦截器
🧩ES6 中另外一个对象 Reflect
(单词本意映射的意思)
下面几项是说明:Reflect
存在的目的
-
将
Object
属于语言内部的方法放到Reflect
上- 就是
Object.defineProperty()
==Reflect.defineProperty()
使用是一样使用的 - 目的就是以前把方法都定义到了
Object
上了,没有分离出去,有了Reflect
后面会细化分离出对象方法
- 就是
-
修改某些
Obejct
方法的返回结果,让其变得更合理- 比如定义一些不允许设置的方式时,会抛出异常 比如:
// 如果 a 不能被代理的话,就只能用 try catch 捕获异常 try{ Object.defineProperty('a', {}) } catch (e) { // ... }
- 现在的新方法就可以用
Reflect
写,因为会返回bool
值
// 会返回一个 boolean值 if (Reflect.defineProperty('a', {})) { // ... }
-
让
Object
操作变成函数行为- 比如说以前的写法,判断方法是否存在在某个对象下
console.log('assign' in Object); // true
- 用
Reflect
新写法:
// 第一个参数是目标, 第二个参数是说 下面有没有这个方法 console.log(Reflect.has(Object, 'assign')); // true
-
Reflect
对象的方法与Proxy
对象的方法一一对应- 比如说改了上面的
Demo
例子
let user = { name: '三脚猫', age: 18, _password: '***' } user = new Proxy(user, { get(target, prop) { if (prop.startsWith('_')) { throw new Error('不可访问') } else { // return target[prop] // ======== 变形成 Reflect 一一对应这个Object ======= return Reflect.get(target, prop) // 目标对象,prop要获取的值 } }, set(target, prop, val) { if (prop.startsWith('_')) { throw new Error('不可设置') } else { // target[prop] = val // ======== 变形成 Reflect 一一对应这个Object ======= // target要给哪个对象设置,prop要设置的key,val要设置的值 Reflect.set(target, prop, val) return true } }, deleteProperty(target, prop) { if (prop.startsWith('_')) { throw new Error('不可删除') } else { // delete target[prop] // ======== 变形成 Reflect 一一对应这个Object ======= Reflect.deleteProperty(target, prop) // 删除目标下的prop这个属性 return true } }, ownKeys(target) { // return Object.keys(target).filter( key => !key.startsWith('_') ) // ======== 变形成 Reflect 一一对应这个Object ======= return Reflect.ownKeys(target).filter( key => !key.startsWith('_') ) } })
- 变形
apply
let sum = (...args) => { let num = 0 args.forEach(item => num += item) return num } // 用 Proxy 拦截 sum = new Proxy(sum, { // target就是函数本身, ctx上下文, args传入的值 apply(target, ctx, args) { // return target(...args) * 2 // ======== 变形成 Reflect 一一对应这个Object ======= return Reflect.apply(target, target, [...args]) } }) console.log(sum(1, 2)); // 输出6 说明走了Proxy乘以2了
- 比如说改了上面的
🧩异步操作的前置知识
JS是单线程的
-
同步任务 和 异步任务
[图片上传失败...(image-c9006c-1600260968705)]- 面试一个小知识点,这里的0毫秒最低最低是4毫秒执行
setTimeout(()=>{ console.log('Time'); }, 0) // 这里的0毫秒最低最低是4毫秒执行
-
Ajax
原理 全称(async javascript and xml)- 面试题 什么是
Ajax
原理? === 用原生实现一个Ajax
function ajax(url, successCallback, failCallback) { // 1. 创建 `XMLHttpRequest` 对象 (IE7 以后才支持这个对象) var xmlHttp // 判断如果window下面有这个对象,判断 IE7 之后 if (window.XMLHttpRequest) { xmlHttp = new XMLHttpRequest() } else { // 兼容 IE7 之前 xmlHttp = new ActiveXObject('Microsoft.XMLHTTP') } // 2. 发送请求 (请求方式,地址,true为async异步 false是同步) xmlHttp.open('GET', url, true) xmlHttp.send() // 3. 服务端响应 xmlHttp.onreadystatechange = function () { // 这里会出现三次打印分别是 readyState 2载入完成 -> 3交互 -> 4完成 if (xmlHttp.readyState === 4 && xmlHttp.status === 200) { // 把接收值 转化为 一个json对象 var obj = JSON.parse(xmlHttp.responseText) successCallback && successCallback(obj) } else if(xmlHttp.readyState === 4 && xmlHttp.status === 404) { // xmlHttp.readyState 响应完成都是4 failCallback && failCallback(xmlHttp.statusText) // 这里会有错误信息 } } } // 调用方法 var url = 'http://musicapi.xiecheng.live/personalized' ajax(url, res => { console.log(res); })
- 面试题 什么是
-
Callback Hell (回调深渊/回调地狱) 用
ES6 - Promise
很好的解决- 当第二个请求结果依赖第一个请求结果时... 就以此类推多级
Ajax
嵌套 - 比如说 三级联动,省市县的分类请求
ajax(url, res => { console.log(res); ajax(url, res => { console.log(res); ajax(url, res => { console.log(res); }) }) })
- 当第二个请求结果依赖第一个请求结果时... 就以此类推多级
🧩ES6 Promise (重要知识)
- 主要是对异步操作的 状态管理 (异步状态管理)
- resolve (瑞子骚)
// Promise 状态管理
console.log(1);
let p = new Promise((resolve, reject) => {
// 在Promise中是同步操作直接输出
console.log(2);
var status = true
if (status) {
resolve() // 异步调用成功回调,最后执行
} else {
reject() // 执行失败后回调
}
})
console.log(3);
// .then(音译:然后) 第一个参数必填,第二个可以不写
p.then( res => {
console.log('成功');
}, () => {
console.log('失败');
})
[图片上传失败...(image-51e78f-1600260968705)]
-
Promise 一共有三种状态
-
new
的时候是pending
进行中 - 如果成功调用
resolve
状态是fulfilled
已成功 - 如果失败调用
reject
状态是rejected
已失败 - 状态时不可逆的,一旦成功或失败是不可改变的,只有进行中是可以改变的
-
-
用
Promise
解决 回调深渊/回调地狱- 未封装前
// 未封装前 new Promise((resolve, reject) => { ajax('static/a.json', res => { console.log(res); resolve() }) }).then(res => { console.log('我是AAAA'); // 当A成功后调用B return new Promise((resolve, reject) => { // 要把Promise return 回去 ajax('static/b.json', res => { console.log(res); resolve() }) }) }).then(res => { // 这里是链式调用 console.log('我是BBBB'); // 当B成功后调用C // 链式调用一定要把当前Promise return 回去 return new Promise((resolve, reject) => { ajax('static/c.json', res => { console.log(res); resolve() }) }) }).then(res => { console.log('我是CCCC'); })
- 简化封装:
// 封装 Promise function getPromise(url){ return new Promise((resolve, reject) => { ajax(url, res => { console.log(res); resolve(res) }, err => { reject(err) }) }) } // 调用函数 getPromise('static/a.json').then(res => { console.log('我是A'); return getPromise('static/b.json') }, err => { console.log(err); // 可以接收到Ajax的err错误,但是后面的还会执行 }).then(res => { console.log('我是B'); return getPromise('static/c.json') }).then(res => { console.log('我是最后一个C'); }).catch(err => { console.log('统一捕获所有请求失败'); })
🧩ES6 Promise 静态方法
.then() .catch()
都是Promise
的实例方法,下面的都是静态方法-
Promise.resolve()
和Promise.reject()
- Demo
// 不需要实例只是返回字符村 let p1 = Promise.resolve('success') p1.then(res => { console.log(res); }) // 不需要实例只是捕获错误返回字符串 let p2 = Promise.reject('fail') p2.catch(res => { console.log(res); })
- 应用场景:当有些时候并没有也不需要实例只是单纯的返回字符串/bool
// 定义一个方法 function foo(flag){ if (flag) { return new Promise(resolve => { // 异步操作 resolve('成功') }) } else { // 错误的时候只返回一个字符串,不需要实例就用到了静态方法 return Promise.reject('失败') // 调用静态 } } // 调用方法 foo(false).then(res => { console.log(res); // 成功 }).catch(res => { console.log(res); // 失败 })
-
Promise.all([])
所有Promise执行完后再去做一些事情Do something ~
- 应用场景:比如上传到服务器三张图片,异步操作后最后全部上传后要提示
图片全部上传完成
let p1 = new Promise((resolve, reject) => { setTimeout(() => { console.log('这是第 1 张图片上传中'); resolve('1成功') }, 1000); }) let p2 = new Promise((resolve, reject) => { setTimeout(() => { console.log('这是第 2 张图片上传中'); // resolve('2成功') reject('2失败') // 当有一个失败的话,就会直接进入失败 }, 2000); }) let p3 = new Promise((resolve, reject) => { setTimeout(() => { console.log('这是第 3 张图片上传中'); resolve('3成功') }, 3000); }) // 调用静态方法 all Promise.all([p1, p2, p3]).then(res => { console.log(res); }, err => { console.log(err); })
- 应用场景:上传图片简写
const imgArr = ['1.jpg', '2.jpg', '3.jpg'] let promiseArr = [] imgArr.forEach(item => { promiseArr.push(new Promise((resolve, reject) => { // 图片上传操作 resolve() })) }) // 调用静态 Promise.all(promiseArr).then(res => { // 插入数据库操作 console.log('图片全部上传完成'); })
- 应用场景:比如上传到服务器三张图片,异步操作后最后全部上传后要提示
-
Promise.race()
(瑞思) 只要一个完成就立马执行.then()
报告完成- 应用场景:图片加载超时
// 定义一个异步上传图片 function getImg(){ return new Promise((resolve, reject) => { let img = new Image() img.onload = function () { resolve(img) } img.src = 'http://www.xxx.com/xx.png' // img.src = 'https://www.imooc.com/static/img/index/logo.png' }) } // 定义一个异步定时器(超时器) function timeOut(){ return new Promise((resolve, reject) => { setTimeout(() => { reject() }, 2000); }) } // 调用Promise的 race 静态方法 Promise.race([getImg(), timeOut()]).then(res => { console.log(res, '未超时,上传成功'); }, err => { console.log(err, '图片上传超时,请重试'); })
- 应用场景:图片加载超时
🧩ES6 Generator 也叫 生成器函数 (另一种异步解决方案) 摘呢瑞特
-
关键词
* yield(艾尔的) .next()
- 使用Demo
function* foo() { for (let $i = 0; $i < 3; $i++) { yield $i } } // 错误写法: // console.log(foo().next()); // {value: 0, done: false} // console.log(foo().next()); // {value: 0, done: false} // 正确写法: let f = foo(); console.log(f.next()); // {value: 0, done: false} console.log(f.next()); // {value: 1, done: false} console.log(f.next()); // {value: 2, done: false} console.log(f.next()); // {value: undefined, done: true}
- 使用Demo
-
特点
- 不能作为构造函数去使用
Generator
-
yield
只能在Generator
里使用,比如会报错:// 下面的方法会报错,因为 yield 在 forEach 闭包里使用了 function* gen(args) { args.forEach(item => { yield item + 1 }) }
- 不能作为构造函数去使用
-
深入复杂用法
- Demo
function* gen(x) { let y = 2 * (yield (x + 1)) let z = yield (y / 3) return x + y + z } // 调用 let g = gen(5) console.log(g.next()); // {value: 6, done: false} // 12 表示的是上一条 yield的返回值,所以相当于 2 * (12) console.log(g.next(12)); // y=24 -> yield (24 / 3) 输出 {value: 8, done: false} // 13 表示的是: // let z = yield (y / 3) -> let z = yield 13 所以 z=13 console.log(g.next(13)); // 最终输出:y=24 + z=13 + x=5 = 42 输出 {value: 42, done: true}
- 应用场景:实现一个 7 的倍数,类似 说7喝酒的游戏,说7或7的倍数 就罚喝酒
function* count(x = 1){ while (true) { if (x % 7 === 0) { yield x } x++ } } // 调用 let n = count() console.log(n.next().value); // 7 console.log(n.next().value); // 14 console.log(n.next().value); // 21 console.log(n.next().value); // 28
- 举一反三 - 应用场景2:实现一个自增ID
function* addId() { let id = 0; while (true) { yield (id + 1) id++ } } // 调用 let i = addId(); console.log(i.next().value); // 1 console.log(i.next().value); // 2 console.log(i.next().value); // 3
- Demo
-
用
Generator
解决 回调深渊/回调地狱function ajax(url, success, error){ let xmlHttp if (window.XMLHttpRequest) { xmlHttp = new XMLHttpRequest() } else { // 麦克若 扫福特 xmlHttp = new ActiceXObject('Microsoft.XMLHTTP') } // 发送请求 xmlHttp.open('Get', url, true) xmlHttp.send() // 服务端响应 xmlHttp.onreadystatechange = function () { if (xmlHttp.readyState == 4 && xmlHttp.status == 200) { // 把数值转化为对象 let obj = JSON.parse(xmlHttp.responseText); success(obj) } else if(xmlHttp.readyState == 4 && xmlHttp.status != 200) { error(xmlHttp.statusText) } } } // 调用 Ajax function request(url) { ajax(url, res => { genData.next(res) }) } // 定义 Generator function* gen(){ let res1 = yield request('static/a.json'); let res2 = yield request('static/b.json') let res3 = yield request('static/c.json') console.log(res1); console.log(res2); console.log(res3); } // 注意调用 Genarator 一定要赋值给变量去调用,不然堆内存会每次都不一样 let genData = gen(); genData.next();
🧩迭代器 Iterator
A特瑞特
懵逼术语三连:
1.是一种接口机制,为各种不同的数据结构提供统一访问的机制
2.主要供for...of
消费
3.一句话:不支持遍历的数组结构变成可遍历
可迭代协议:要具备有
Symbol.iterator
迭代器协议:必须符合这个格式 :
-
return的{ next(){ return {value, done} } }
- 必须返回对象
- 实现 next 并返回对象
- 返回对象并有 value, done的key
-
封装一个实现一个 iterator 方法
// 定义一个 iterator 方法 function makeIterator(arr) { // 定义一个循环索引值 let nextIndex = 0 // 1. 要实现返回一个对象 return { // 2. 要实现一个 next() 方法,并且方法里要返回一个对象 next() { // 返回的对象必须有两个参数:value done // if (nextIndex < arr.length) { // // 继续循环 // return { // value: arr[nextIndex++], // 先使用取值,后++运算 // done: false // 表示还未完成 // } // } else { // return { // value: undefined, // done: true // 表示循环完毕 // } // } // 上面的判断可以 简写为: return nextIndex < arr.length ? { value: arr[nextIndex++], done: false // 表示还未完成 } : { value: undefined, done: true // 表示循环完毕 } } } } // 一定要赋值给变量,再去 .next() 调用,栈内存引用地址问题 let it = makeIterator(['a', 'b', 'c']) console.log(it.next()); // {value: "a", done: false} console.log(it.next()); // {value: "b", done: false} console.log(it.next()); // {value: "c", done: false} console.log(it.next()); // {value: undefined, done: true}
-
原生具备
iterator
接口的数据结构- Array
- Map
- Set
- String
- TypedArray (作用:对于底层二进制描述用)
- 函数的
arguments
对象 (arguments是类数组) - NodeList对象 (也是类数组)
-
Array 数组的
iterator
方法举例let arr = [1, 2, 3] console.log(arr); // 查看是否有 Symbol.iterator 这个方法 // 取出这个方法 就可以像 迭代器/Generator 一样遍历 let it = arr[Symbol.iterator]() console.log(it.next()); // {value: 1, done: false} console.log(it.next()); // {value: 2, done: false} console.log(it.next()); // {value: 3, done: false} console.log(it.next()); // {value: undefined, done: true}
-
Map
new Map()
的iterator
方法举例let map = new Map() map.set('name', 'es') map.set('age', 18) // 有 Symbol(Symbol.iterator) 方法,可遍历 console.log(map); // 0: {"name" => "es"} let it = map[Symbol.iterator]() console.log(it.next()); // {value: Array(2), done: false} 因为map是键值对 console.log(it.next()); // {value: Array(2), done: false} 所以是数组 console.log(it.next()); // {value: undefined, done: true}
-
循环遍历一种不可遍历的对象
-
应用场景:当所有页面都使用该对象时,不用每次都遍历出每一个键值,直接迭代器封装好。当然如果只需要一次,是可以用
.
去找每个对象依次循环值// 循环遍历一种不可遍历的对象 course 扣赛 课程 let course = { allCourse: { frontend: ['ES', '小程序', 'Vue', 'React'], backend: ['Java', 'Python', 'PHP'], webapp: ['Android', 'ios'] } } // 可迭代协议:要具备有 Symbol.iterator // 迭代器协议:必须符合这个格式 return的{ next(){ return {value, done} } } // 1. 必须返回对象 // 2. 实现 next 并返回对象 // 3. 返回对象并有 value, done的key // 开始改变 course[Symbol.iterator] = function () { let allCourse = this.allCourse let keys = Reflect.ownKeys(allCourse) // 获取到所有的 key let values = [] return { next() { // 判断values是否为空 为空就执行真区间 if (!values.length) { // 如果keys值都被踢出到0后 就不再踢出 if (keys.length) { // 把当前 key 值放入 values values = allCourse[keys[0]] keys.shift() // 从前面踢出 } } return { done: !values.length, // 这里 false 表示没有循环完成 value: values.shift() } } } } // 循环遍历 for (const item of course) { console.log(item); // ES 小程序 Vue React Java Python PHP Android ios }
-
用
Generator
的写法写 循环遍历一种不可遍历的对象- 因为
Generator
自带next()
方法,并且自带done、value
返回格式,所以写起来迭代器跟方便
let course = { allCourse: { frontend: ['ES', '小程序', 'Vue', 'React'], backend: ['Java', 'Python', 'PHP'], webapp: ['Android', 'ios'] } } // Generator 自带next()方法,并且自带done、value返回格式 course[Symbol.iterator] = function* () { let allCourse = this.allCourse; // 获取key值下的所有内容 // 获取所有 key let keys = Reflect.ownKeys(allCourse); // ["frontend", "backend", "webapp"] // 定义返回数据的数组 let values = [] // 无限循环 直到完成输出 while (true) { if (!values.length) { // 判断还有 key 可以循环 if (keys.length) { // console.log(keys[0]); // frontend values = allCourse[keys[0]] // 获取第一组 ['ES', '小程序', 'Vue', 'React'] keys.shift() yield values.shift() // 每次输出1个元素 } else { return false } } else { // 当一组没有被取完就一直走这个区间 console.log(values, 'values有值'); yield values.shift() // 小程序 ...Vue ...React } } } // 循环遍历 for (const item of course) { console.log(item); // ES 小程序 Vue React Java Python PHP Android ios }
- 因为
-
[图片上传失败...(image-70b3dc-1600260968705)]
注意:因为当前没有数据没有
Symbol.iterator
这个方法,就是无法遍历的意思。只要有该方法,遵循
iterator
协议的都可遍历。
🧩ES6 Module
-
优势:
- 插件模块化
- 封装函数模块,提高重复使用率
- 解决重名,解决模块化依赖关系 按顺序加载
- 因为是模块,里面的变量也不会挂在在
window
上,也解决全局变量污染的问题
-
导入:import、别名、类
// 导出 const a = 5 class People { constructor(name){ this.name = name } showName() { console.log(this.name); } } export { a, sum, People } // ======== module分割线 ======== // import { a as aa, // 起别名 People } from './module.js' console.log(aa); // 5 let p = new People('三脚猫') p.showName() // 三脚猫
-
export defult
const a = 5 // export default a // 错误写法,会报错 // export default const a = 5 export default a export c = '我是C' // ======== module分割线 ======== // // 如果是 export default导出,import的时候名字随便起,因为只有一个默认值 import xxx, {c} from './module.js' console.log(xxx, c); // 5 我是C
-
*
导入多个const a = 5 const b = 'string' export default{ a, b, } // ======== module分割线 ======== // // 1. 对象访问 import mod from './module.js' console.log(mod); // a: 5, b: "string"} 打来出来是一个对象 // 2. Module 类 import * as mod2 from './module.js' console.log(mod2); // Module {__esModule: true, …}... ... // 这里要加 default 访问 console.log(mod2.default.a); // 5
🧩ES7 ECMAScript7 (2016)
- 数组扩展
-
Array.prototype.includes
(searchElement, fromIndex) -
includes VS indexOf
什么情况下用includes
什么情况下用indexOf
- 当需要判断
NaN
时只能用includes
- 当需要判断值是否存在并且需要返回下标用
indexOf
- 当需要判断
// includes 会返回 -> boolean型 const arr = ['es6', 'es7', 'es8'] console.log(arr.includes('es7')); // true console.log(arr.includes('es7', 1)); // true console.log(arr.includes('es7', 2)); // false console.log(arr.includes('es7', -2)); // true // 检查一个数组类型,得出结论 只能判断基础数据类型,不能判断引用数据类型 const arr2 = ['es6', ['es7', 'es8'], 'es9'] console.log(arr2.includes(['es7', 'es8']), 'arr2'); // false console.log(arr2.includes('es7'), 'arr2'); // false console.log(arr2.indexOf(['es7', 'es8'])); // -1 // NaN 特殊值 const arr3 = ['es6', 'es7', NaN, 2] console.log(arr3.includes(NaN), 'NaN'); // true console.log(arr3.indexOf(NaN), 'NaN'); // -1 // 测试严格检查 console.log(arr3.includes('2'), '测试严格类型检查'); // false console.log(arr3.indexOf('2'), '测试严格类型检查'); // -1
-
🧩ES7 新特性 数值扩展->幂运算符(指数运算符)
-
运算标识符
**
等同于Math.pow()
console.log(Math.pow(2, 10)); // 1024 console.log(2 ** 10); // 1024
-
2的10次方 不用函数自己封装:
function pow(x, y){ let res = 1 for (let i = 0; i < y; i++) { res *= x } return res } console.log(pow(2, 10)); // 1024
🧩ES8 ECMAScript8 (2017) Async Await(重要知识)
-
Async Await
是Generator
的语法糖- 基本用法:
// 定义一个异步操作 function timeOut() { return new Promise((resolve, reject) => { setTimeout(() => { console.log(1, 'timeOut'); // TODO 如果不写 resolve 就不打印输出2 resolve(888) }, 1000); }) } async function foo() { let res = await timeOut() console.log(res); // 888 console.log(2); } foo() // 先输出 1 然后输出 888 然后输出 2
-
用
Async Await
方式解决 回调深渊// 用 Module 引入之前封装的 ajax import ajax from './ajax' // 封装接口调用函数 function request(url) { return new Promise(resolve => { ajax(url, res => { resolve(res) // 返回请求的值 }) }) } // 获取数据 async function getData() { let res1 = await request('static/a.json') console.log(res1); // {a: "我是A"} let res2 = await request('static/b.json') console.log(res2); // {b: "我是B"} let res3 = await request('static/c.json') console.log(res3); // {c: "我是C"} // let res3 = await request('static/c.json', res3...) // 可以加参数 request(url, res) } // 调用获取数据方法 getData()
🧩ES8 对象的扩展
-
Object.values()
const obj = { name: '三脚猫', age: 18, course: 'es8' } // 以前的方式获取所有 value 值 const res = Object.keys(obj).map(key => obj[key]) console.log(res); // ["三脚猫", 18, "es8"] // ES8 获取 console.log(Object.values(obj)); // ["三脚猫", 18, "es8"]
-
Object.entries()
let obj2 = Object.entries(obj); console.log(Object.entries(189195)); for (const [key, val] of obj2) { // name: 三脚猫 // age: 18 // course: es8 console.log(`${key}: ${val}`); } // 转换数组 并没有太大的意义 const arr = ['a', 'b', 'c']; console.log(Object.entries(arr));
🧩ES8 对象属性描述符
-
Object.getOwnPropertyDescriptors()
value
对象的值writable
(外特保) 表示对象属性是否可以改enumerable
(N牛若宝) 是否可以通过fo in
循环出来configurable
(config若宝) 是否可以用delete
运算符删除掉-
Object.getOwnPropertyDescriptors()
使用Demo:获取对象属性const obj = { name: '三脚猫', course: 'es' } let desc = Object.getOwnPropertyDescriptors(obj); console.log(desc); // value: "三脚猫" ...writable: true ...configurable: true ...enumerable: true
-
给对象设置属性
const obj2 = {} // 第一个参数传入对象 第二个要给这个对象设置什么属性 Object.defineProperty(obj2, 'name', { value: '三脚猫猫', writable: false, // name 不可被修改 configurable: false, // 不能删除 name 属性 enumerable: false, // 不能循环 name }) Object.defineProperty(obj2, 'age', { value: 18, writable: false, // age 不可被修改 configurable: false, // 不能删除 age 属性 enumerable: true, // 能循环 age }) obj2.name = '嗷嗷嗷' // 赋值不成功 delete obj2.name // 删除不成功 console.log(obj2); console.log('开始循环 ====='); for (const item in obj2) { console.log(item); // age 因为 name 设置了不允许循环 }
-
扩展知识:获取对象下的指定属性的描述
const obj3 = { name: '三脚猫', age: 18 } console.log(Object.getOwnPropertyDescriptor(obj3, 'age'));
🧩ES8 (ES2017) 字符串扩展
-
String.prototype.padStart()
在开始的地方填充const str = '三脚猫' // 从开头填充8位,以什么字符串去填充 console.log(str.padStart(8, '1234567')) // 12345三脚猫 console.log(str.padEnd(8, 'x')) // 三脚猫xxxxx // 第二个参数是可选的,如果不写会以 空格 去填充 console.log(str.padStart(10)) // 三脚猫
-
padStart()
应用场景// 应用场景1: yyyy-mm-dd 2020-04-01 里面的月份和天数 前面需要填充0 const now = new Date() // 实例化当前日期 const year = now.getFullYear() // 获取年 const month = now.getMonth() + 1 // 获取月份 注意这里打印是 0~11 需要+1 const day = now.getDate() // 获取天数 console.log(`${year}-${month}-${day}`); // 2020-8-27 // 补全月份和日期: let month2 = month.toString().padStart(2, 0) let day2 = day.toString().padStart(2, 0) console.log(`${year}-${month2}-${day2}`); // 2020-08-27 // 应用场景2: 手机号 *******6789 const tel = '18511116789' const newTel = tel.slice(-4).padStart(tel.length, '*') console.log(newTel); // *******6789
-
-
String.prototype.padEnd()
在结束的地方填充-
padStart()
应用场景// 应用场景: 后端传输的时间戳m为单位,前端需要补全为13位 console.log(new Date().getTime()) // 1598537583239 (13位 ms为单位的时间戳) let time = 1598537678 // 模拟后端传输 (2020-08-27 22:14:38) let newTime = time.toString().padEnd(13, 0) // (2020-08-27 22:14:38:000) console.log(time, newTime); // 1598537678 "1598537678000"
-
🧩ES8 新特性 尾逗号 Trailing commas
- 允许函数参数列表使用尾逗号
- 一般就是方便后续继续添加参数,git记录少一步冲突
- 在编译的ES5的时候,还是会去掉逗号的
- Demo:
function foo(a, b, c,) { console.log(a, b, c,); } // foo( // 4, // 5, // 6, //) foo(4, 5, 6,) // 4 5 6
🧩ES9 ECMAscript9 (ES2018)
-
异步迭代
for-await-of
异步迭代Symbol.asyncIterator
-
同步迭代:
const arr = ['es6', 'es7', 'es8', 'es9'] arr[Symbol.iterator] = function () { let nextIndex = 0 return { next() { return nextIndex < arr.length ? { // 后++的所以 从0开始 value: arr[nextIndex++], done: false } : { value: undefined, done: true } } } } for (let item of arr) { console.log(item); }
-
异步迭代
Symbol.asyncIterator
// 封装异步管理 function getPromise(time) { return new Promise((resolve, reject) => { setTimeout(function() { // resolve(time) 改进: resolve({ value: time, done: false }) }, time); }) } const arr2 = [getPromise(1000), getPromise(2000), getPromise(3000)] // 异步迭代 Symbol.asyncIterator arr2[Symbol.asyncIterator] = () => { let nextIndex = 0 return { next() { return nextIndex < arr2.length ? arr2[nextIndex++] : Promise.resolve({ value: undefined, done: true }) } } } // ES9 新特性 for await of async function test() { for await (let item of arr2) { console.log(item); // 1000 2000 3000 是异步等待几秒后加载出来 } } test()
🧩ES9 (2018) 正则表达式扩展
ES5里的修饰符:
g i m
ES6:y u s
-
dotAll (dot 点的意思) 就是修饰符
s
// 普通的.匹配 let reg = /./ console.log(reg.test('x')); // true console.log(reg.test('\n')); // false console.log(reg.test('\r')); // false console.log(reg.test('\u{2028}')); // false // 加上 dotAll 修饰符 s 匹配 let reg2 = /./s console.log(reg2.test('x')); // true console.log(reg2.test('\n')); // true console.log(reg2.test('\r')); // true console.log(reg2.test('\u{2028}')); // true
-
具名组匹配
(?<name>)
// 不使用之前这样写 const reg3 = /(\d{4})-(\d{2})-(\d{2})/ let res = reg3.exec('2020-08-31') console.log(res); // groups: undefined console.log(res[1]); // 2020 // 使用具名组匹配 const reg4 = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/ let res2 = reg4.exec('2020-08-31') // let year = res2.groups.year // let month = res2.groups.month // let day = res2.groups.day // 上面的用 解构赋值 简写: let {year, month, day} = res2.groups console.log(year, month, day); // 2020 08 31
-
ES9 后行断言
(?<=ecma)、(?<!ecma)
(相对应ES5的先行断言 (?=script)
是之前就有的)// - 先行断言:先匹配前面的值,后面的值是否是断言值 const str = 'ecmascript' console.log(str.match(/ecma(?=script)/)); // ["ecma", index: 0, input: "ecmascript", groups: undefined] // - 后行断言:后面先确定下来,匹配前面的值 console.log(str.match(/(?<=ecma)script/)); // ["script", index: 4, input: "ecmascript", groups: undefined] console.log(str.match(/(?<!ecma)script/)); // 这样写是不等于ecma的
🧩ES9 对象扩展 (用...扩展对象)
- Rest & Spread
// 复习之前数组合并
const arr1 = [1, 2, 3]
const arr2 = [4, 5, 6]
const arr3 = [...arr1, ...arr2]
console.log(arr3);
// 用来 - 克隆对象
let obj1 = {
name: '三脚猫',
age: 18,
money: {'RMB': 5, 'USD': 3}
}
let obj2 = {...obj1}
// console.log(obj2);
obj2.school = 'Harvard'
obj1.money.RMB = 88
console.log(obj1);
console.log(obj2); // 浅拷贝 money: {RMB: 88, USD: 3}
// 用来 - 合并对象
let obj3 = {
sex: '男'
}
let obj4 = {...obj1, ...obj3}
console.log(obj4, 'obj4');
// 用来 - 剩余运算符
let obj5 = {
name: '三脚猫',
age: 18,
school: 'Harvard',
sex: 2
}
let {name, age, ...rest} = obj5
console.log(name); // 三脚猫
console.log(age); // 18
console.log(rest); // {school: "Harvard", sex: 2}
🧩ES9 Promise扩展 finally()
-
Promise.prototype.finally()
(finally 烦呢类 最终的意思)-
Demo:
new Promise((resolve, reject) => { // resolve('success') reject('fail') }).then(res => { console.log(res); }).catch(err => { console.log(err); }).finally(() => { console.log('finally'); }) // fail finally
应用场景: 加载完异步后隐藏 加载中的
loading
块
-
🧩ES9 字符串扩展(特性)
- 放松了模板字符串转义序列的语法限制
// - 只有在标签模板字符串中放松了 const foo = arg => { console.log(arg); } foo`\u{61} and \u{62}` // ["a and b", raw: Array(1)] // - 模板字符串中还是未改变 foo`\u{61} and \unicode`; // 不报错,放松了 let str = `\u{61} and \u{62}` // 会报错
🧩ES10 ECMAScript10 (2019) 对象扩展
-
Object.fromEntries()
反转entries
格式// ES8 中 Object.entries() 把对象的每一个值转化为一个数组 const obj = { name: '三脚猫', course: 'es' } let entries = Object.entries(obj) console.log(entries); // [["name","三脚猫"],["course","es"]] // ES10 Object.fromEntries() 反转上面的entries格式 let fromEntries = Object.fromEntries(entries); console.log(fromEntries); // {name: "三脚猫", course: "es"}
-
应用场景 map -> 转换为对象
const map = new Map() map.set('name', 'Harvey') map.set('study', 'ES6') console.log(map); // Map(2) {"name" => "Harvey", "study" => "ES6"} let fromEntries2 = Object.fromEntries(map); console.log(fromEntries2); // {name: "Harvey", study: "ES6"}
-
应用场景2 找出80分以上的 (科目 和 分数)
const course = { math: 80, english: 85, chinese: 90 } console.log(Object.entries(course)); // [["math",80],["english",85],["chinese",90]] // ([key, val]) === item 结构方法获取到 val 值 const res = Object.entries(course).filter(([key, val]) => { return val > 80 }) console.log(Object.fromEntries(res)); // {english: 85, chinese: 90}
🧩ES10 (2019) 字符串扩展 去掉字符串前后空格
-
String.prototype.trimStart()
去掉字符串前面的空格 -
String.prototype.trimEnd()
去掉字符串后面的空格 - 以前只能用正则表达式的方式:
str.replace(/^\s+/g, '')
// 去掉字符串的空格 const str = ' 三脚猫 1 '; console.log(str); // 以前只能用正则表达式的方式: console.log(str.replace(/^\s+/g, '')); // 去掉前面的空格 console.log(str.replace(/\s+$/g, '')); // 去掉后面的空格 // ES10 // 去掉前面的空格 console.log(str.trimStart()); console.log(str.trimLeft()); // 去掉后面的空格 console.log(str.trimEnd()); console.log(str.trimRight()); // 去掉前后的空格 console.log(str.trim());
🧩ES10 (2019) 数组的扩展
-
Array.prototype.flat()
扁平化数组(拍平)// 多维数组 扁平化(拍平) const arr = [1, 2, 3, [4, 5, 6, [7, 8, 9, [10, 11, 12]]]] console.log(arr.flat()); // [1, 2, 3, 4, 5, 6, Array(4)] console.log(arr.flat().flat()); // [1, 2, 3, 4, 5, 6, 7, 8, 9, Array(3)] console.log(arr.flat().flat().flat()); // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12] // - 深度传参 传比3大的都行 console.log(arr.flat(3)); // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12] // - 无限的扁平化 Infinity console.log(arr.flat(Infinity)); // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
-
Array.prototype.flatMap()
循环拍平数组// 比如想 循环+1 const arr2 = [1, 2, 3, 4, 5] // const res = arr2.map(x => x + 1) // const res = arr2.map(x => [x + 1]).flat() // 同上 +1 的用法 const res = arr2.flatMap(x => [x + 1]) // 相当于结合了上面的两种方法 flat + map console.log(res); // [2, 3, 4, 5, 6]
🧩ES10 (2019) 修订Function.prototype.toString()
新特性
- 返回源代码中的实际文本片段
// 修订`Function.prototype.toString()` function foo(){ // 这是ES10 console.log('三脚猫'); } // 以前调用 toString 只返回函数的主体,不会返回注释、空格等 console.log(foo.toString()); // 下面是打印出来的值: // function foo() { // // 这是ES10 // console.log('三脚猫'); // }
🧩ES10 (2019) 可选的 Catch Binding
-
省略
catch
绑定的参数和括号// 封装一个方法,验证是否是 json 格式的数据 const validJSON = json => { // 以前的 try catch 格式: // try{ // JSON.parse(json) // } catch(e){ // console.log(e); // } // ES10 的格式,可以省略掉 catch 的后面括号内容 try{ JSON.parse(json) return true; } catch { return false; } } // 定义一个 json 字符串 const json = '{"name":"三脚猫", "course":"es"}' let res = validJSON(json) console.log(res); // true
-
ES10 的格式,可以省略掉 catch 的后面括号内容
try{ JSON.parse(json) return true; } catch { return false; }
🧩ES10 (2019) JSON扩展 新特性
-
JSON superset
JSON 超集// JSON 超集 (以前的规定JSON只是ES的一个子集,早期ES不支持 行分隔符(\u2028)/段分隔符(\u2029)) eval('var str="三脚猫";\u2029 function foo(){return str;}') console.log(foo());
-
JSON.stringify()
增强能力-
JSON.stringify()
是有一定的解析范围的,是从0xD800~0xDfff
,ES10
弥补了这个范围 - 比如当下的 emoji 表情
\uD83D\uDE0E
多字节的一个字符,代表一个表情,所以它超出了范围// JSON.stringify() 是有一定的解析范围的,是从 0xD800~0xDfff // 比如当下的 emoji 表情 \uD83D\uDE0E 多字节的一个字符,代表一个表情,所以它超出了范围 let emoji = JSON.stringify('\uD83D\uDE0E') // emoji 表情 console.log(emoji); let test = JSON.stringify('\uD83D') // "\ud83d" 一半,它什么都不代表 console.log(test);
-
🧩ES10 (2019) Symbol 扩展
-
Symbol.prototype.description
在ES10
才被纳入标准,以前也可以用的const s = Symbol('三脚猫') console.log(s); // Symbol(三脚猫) console.log(s.description); // 三脚猫 // description是只读属性,不能写,不能赋值 s.description = 'es' console.log(s.description, '重写'); // 三脚猫 重写 // 未定义时 const s2 = Symbol() console.log(s2.description); // undefined
🧩ES11 (2020)
-
全局模式捕获:
String.prototype.matchAll()
-
举例:
.exec()
const str = ` <html> <body> <div>第一个div</div> <p>这是p</p> <div>第二个div</div> <span>这是span</span> </body> </html> ` // exec 可以设置一个正则表达式,可以获取具名组 // 封装一个正则方法 function selectDiv(regExp, str) { let matches = [] // 匹配多个后要返回的数组 while(true) { console.log(regExp.lastIndex); // 正则的底层原理:正则的索引下标 const match = regExp.exec(str) if (match == null) { break } matches.push(match.groups.div) // 具名组匹配 } return matches } // 调用 const regExp = /<div>(?<div>.*)<\/div>/g // 如果不加 g 会死循环,因为每次都在第一个div循环 const res = selectDiv(regExp, str) console.log(res); // ["第一个div", "第二个div"]
-
举例:
.match()
// match 获取所有的匹配,多余的匹配了 <div> 标签 console.log(str.match(regExp)); // ["<div>第一个div</div>", "<div>第二个div</div>"]
-
举例:
.replace()
// replace function selectDiv2(regExp, str) { let matches = [] str.replace(regExp, (all, first) => { console.log(first); // 第一个div ... 第二个div matches.push(first) }) return matches } const res2 = selectDiv2(regExp, str) console.log(res2); // ["第一个div", "第二个div"]
-
举例:
.matchAll()
// matchAll 注意:正则的参数必须加 g 修饰符 function selectDiv3(regExp, str) { let matches = [] for (let item of str.matchAll(regExp)) { matches.push(item[1]) } return matches } const res3 = selectDiv3(regExp, str) console.log(res3); // ["第一个div", "第二个div"]
-
扩展知识:正则的底层原理
lastIndex
属性,正则的索引下标,寻找字符串时,会从上次的下标开始找
🧩ES11 (2020) Dynamic import() (带奶麦克)
-
动态导入 (按需导入)
- 大多数用于首屏 也就是用户第一次加载的时候会动态加载需要的模块
-
Vue
里的 路由懒加载 就是这样
-
使用方式:
const Foo = () => import('./Foo.vue')
🧩ES11 (2020) BigInt 新纳入的标准(新的原始数据类型)
-
新的原始数据类型:
BigInt
// 整型的取值范围是:2 ** 53 **是ES7语法 幂运算符 const max = 2 ** 53 console.log(max); // 9007199254740992 // MAX_SAFE_INTEGER 是表示最大值的常量 console.log(Number.MAX_SAFE_INTEGER); // 9007199254740991 这个会少一个数 console.log(max === max + 1); // true 超出了最大值怎么比较都是 true
-
如果想用比int值大
- 第一种定义 bigInt 的方式:加 n
const bigInt = 9007199254740993n console.log(bigInt); // 9007199254740993n 加一个 n 就可以输出出比最大值还大的 console.log(typeof bigInt); // bigint // 在数值后面加一个 n 数值不会变化,只是类型变化了 console.log(1n == 1); // true console.log(1n === 1); // false 就相当于是 bigint === number
- 第二种定义
BigInt()
方式
const bigInt2 = BigInt(9007199254740993n) console.log(bigInt2); // 9007199254740993n // 测试是否会相加 const num = bigInt + bigInt2 console.log(num); // 18014398509481986n // 如果不想要 n ,只能用字符串的形式存储大的数值 console.log(num.toString()); // 18014398509481986
扩展知识:不想要后面跟
n
就只能用字符串的形式存储,用.toString()
转化
🧩ES11 (2020) Promise扩展
-
Promise.allSettled()
扩展的静态方法 赛头儿的 (allSettled 稳定的固定的) -
allSettled() VS all()
Promise.allSettled([ Promise.resolve({ code: 200, data: [1, 2, 3] }), Promise.reject({ code: 500, data: [1, 2, 3] }), Promise.resolve({ code: 200, data: [1, 2, 3] }) ]).then(res => { // 原静态方法 .all 的调用后必须全成功 // [ // {"code":200,"data":[1,2,3]}, // {"code":200,"data":[1,2,3]}, // {"code":200,"data":[1,2,3]} // ] console.log(res); console.log(JSON.stringify(res)); console.log('成功'); // 换成静态方法 .allSettled 后 // [ // {"status":"fulfilled","value":{"code":200,"data":[1,2,3]}}, // {"status":"rejected","reason":{"code":500,"data":[1,2,3]}}, // {"status":"fulfilled","value":{"code":200,"data":[1,2,3]}} // ] let success = res.filter(item => item.status == 'fulfilled') // [ // {"status":"fulfilled","value":{"code":200,"data":[1,2,3]}}, // {"status":"fulfilled","value":{"code":200,"data":[1,2,3]}} // ] console.log(success); console.log(JSON.stringify(success)); }).catch(err => { // 如果有错误直接 全部走错误区间了 console.log(err); // {"code":500,"data":[1,2,3]} console.log(JSON.stringify(err)); console.log('失败'); })
🧩ES11 (2020) 引入了新的对象 globalThis
-
提供了一个标准的方式,去获取不同环境下的全局镜像
- 写法:
console.log(globalThis); // Window {parent: Window, opener: null, top: Window, length: 0, frames: Window, …}...
- 写法:
-
js
在不同环境下获取全局对象是不一样的:- node 的全局对象叫 global
- web端 的全局对象叫 global windows === self
-
ES11
globalThis
之前,想获取任何下面的全局对象,需要判断const getGlobal = () => { // 判断当前是不是有全局对象 if (typeof self !== 'undefined') { return self } if (typeof window !== 'undefined') { return window } if (typeof global !== 'undefined') { return global } // 如果都没有 抛出错误 throw new Error('无法找到全局对象') } const global = getGlobal() console.log(global); // Window {parent: Window, opener: null, top: Window, length: 0, frames: Window, …}... // ES11 写法 console.log(globalThis); // 同上相同结果
🧩ES11 (2020) 新特性 可选链?.
(Optional chaining)
-
可选链 (就是对象下面的属性.属性.属性,并且每次都要判断下面的属性是否存在)
const user = { address: { street: 'xx街道', getNum() { return '88号' } } } // 获取上面的属性时,需要每次都判断 const street = user && user.address && user.address.street console.log(street); // 细节 方法名 先不加括号是判断方法是否存在 const num = user && user.address && user.address.getNum && user.address.getNum() console.log(num);
-
ES11 可选链
const street2 = user ?. address ?. street console.log(street2); // 如果没有是 undefined const num2 = user ?. address ?. getNum ?. () console.log(num2);
🧩ES11 (2020) 新特性 ??
Nullish coalescing Operator
空值合并运算符 (有些时候需要给某些值设置默认值)
-
大多数,这种情况用来判断的是 null、undefined 如果值就是 0 或 false,也会走默认值,是错误的
const b = false const a = b || 5 // 如果 b 没有值,就默认给 5 console.log(a); // 5
-
用空值运算符解决
const c = false // 不管是 ''、false、0、'false'、'null' 都不会走默认值 const d = c ?? 888 // 只有是 null、undefined 是默认 888 console.log(d); // false
🧩常用数组/对象方法
方法名 | 用途 | 参数/说明 |
---|---|---|
数组 | ||
.splice(startIndex, replaceNum, arge+) |
替换/插入 数组元素 |
startIndex 开始的下标位置replaceNum 替换的元素,如果是插入该值设为0 arge N个元素 |
.slice(start, end) |
剪切数组 | ... |
.flat(Infinity) |
扁平化数组 |
Infinity 无限的扁平化 |
.push() |
入栈 | 尾部新增 |
.pop() |
出栈 | 尾部取出 |
.unshift() |
头部新增 | 在数组头部添加新元素 |
.shift() |
头部取出 | 取出数组头部元素 |