ES6-ES13新特性
# let/const是否存在作用域提升
let/const
定义的变量在执行上下文的词法环境创建出来的时候变量事实上已经被创建了,只是这个变量是不能被访问的
作用域提升的定义:在声明变量的作用域中,如果这个变量可以在声明之前被访问,那么可以称之为作用域提升
let/const
定义的变量虽然被创建出来了,但是不能被访问,因此不能称之为作用域提示
总结:let/const
没有进行作用域提升,但是会在解析阶段创建出来
# 块级作用域
在ES5之前,只有全局和函数会形成自己的作用域,var
声明的变量在非严格模式下是没有块级作用域的
从ES6开始,使用let/const/function/class
声明的变量是有块级作用域的,
虽然函数拥有块级作用域,但是在外面依然可以访问,这是因为引擎会对函数的声明进行特殊的处理,允许像var定义的变量一样允许作用域提升
{
let foo = 'foo'
function bar(){
console.log('bar')
}
class Person{}
}
console.log(foo) // 不可以访问
bar() // 可以访问
var newPerson = new Person() // 不可以访问
2
3
4
5
6
7
8
9
10
11
12
# 模板字符串
# 标签模板字符串
function foo(...args) {
console.log(args);
}
// 普通JavaScript函数调用
foo("Hello World"); // ['Hello World']
// 使用标签模板字符串
const name = "zs";
const age = 18;
foo`Hello ${name} Wolrd ${age}`; // [Array(3), 'zs', 18]
2
3
4
5
6
7
8
9
如果使用标签模板字符串,并且在调用的时候插入其它变量
- 模板字符串被拆分了
- 第一个元素是数组,是被模块字符串拆分的字符串组合
- 后面的元素是一个个模块字符串传入的内容
# 函数的默认参数
function foo(arg1, arg2){
// 默认值写法判断,方法一
arg1 = arg1 ? arg1 || '默认值'
// 方法二
arg1 = arg1 || '默认值'
}
2
3
4
5
6
以上两种默认值的处理方式都是不严谨的,如果说函数传递的参数为0
,""(空字符串)
,false
这样的参数时都会重新赋值为默认值
修改为严谨的写法
function foo(arg1 = '方案三的默认值', arg2){
// 方法一 三元运算符
arg1 = (arg1 === undefined || arg1 === null)?"默认值":arg1
// 方法二 ES6之后新增语法 ??
arg1 = arg1 ?? '默认值'
// 方法三,直接在参数上面赋值默认值
// 方案三不会对null进行判断
}
2
3
4
5
6
7
8
注意
- 有默认参数的函数形参尽量写到后面;
- 有默认参数的形参是不会计算在length之内的(并且后面的所有的参数都不会计算在length之内)
- 剩余参数也放到最后(默认参数放在剩余参数前面)
function foo(arg1, arg1 = '默认参数', ...arg3){
}
2
3
对象可通过展开运算符实现浅拷贝,修改新的对象不会影响原本的值
const obj = {
name: 'zs',
age: 18,
height: 180
}
const info2 = {...obj}
2
3
4
5
6
# Symbol的基本使用
Symbol
是ES6中新增的一个基本类型,翻译为符号
# 为什么需要Symbol
- 在ES6之前,对象的属性名都是字符串形式,那么很容易造成属性名的冲突
- 比如原来有一个对象,我们希望在其中添加一个新的属性和值,但是我们在不确定它原来内部有什么内容的情况下,很容易造成冲突,从而覆盖掉它内部的某个属性
- 比如我们前面在讲apply、call、bind实现时,我们有给其中添加一个fn属性,那么如果它内部原来已经有了fn属性了呢
- 比如开发中我们使用混入,那么混入中出现了同名的属性,必然有一个会被覆盖掉
Symbol用来生成一个独一无二的值,Symbol值是通过Symbol函数
来生成的,生成后可以作为属性名。
Symbol即使多次创建值,结果也是不相同的
const s1 = Symbol()
const obj = {
[s1]: 'aaa'
}
2
3
4
可以在创建Symbol值的时候传入一个描述description:这个是ES2019(ES10)新增的特性
# Symbol作为属性名
const s1 = Symbol()
const s2 = Symbol()
// 写法一 定义字面量对象直接使用
const obj = {
[s1]:'aaa',
[s2]:'bbb'
}
// 写法二 属性名赋值
const obj2 = {}
obj2[s1] = 'abc'
obj2[s2] = 'cba'
// 写法三
Object.defineProperty(obj, s1, {
value: 'abc'
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 获取Symbol对应的key
console.log(Object.keys(obj)) // 获取不到Symbol定义的key
console.log(Object.getOwnPropertySymbols(obj)) // 获取Symbol定义的key
const symbolKeys = Object.getOwnPropertySymbols(obj)
for(const key of symbolKeys){
console.log(obj[key])
}
2
3
4
5
6
7
8
# description
Symbol的目的是为了创建一个独一无二的值,那么如果我们现在就是想创建相同的Symbol应该怎么来做呢
- 使用Symbol.for方法
- 通过Symbol.keyFor方法来获取对应的key
const s1 = Symbol("ccc");
console.log(s1.description); // cccc
const s2 = Symbol(s1.description);
console.log(s1 === s2); // false
const s5 = Symbol.for(s1.description)
const s6 = Symbol.for(s1.description)
console.log(s5 === s6) // true
console.log(Symbol.keyFor(s5)) // ccc
2
3
4
5
6
7
8
# Set使用
ES6之前,存储数据的结构主要有两种:数组、对象
在ES6中新增了另外两种数据结构:Set
、Map
,以及它们的另外形式WeakSet
、WeakMap
Set是一个新增的数据结构,可以用来保存数据,类似于数组,但是和数组的区别是元素不可以重复。 创建Set需要通过Set构造函数
Set一个常用的功能就是给数组去重
const set1 = new Set();
set1.add(10);
set1.add(12);
set1.add(14);
set1.add(16);
console.log(set1);
const set2 = new Set([11, 15, 18, 11]);
console.log(set2);
const arr = [10, 20, 10, 44, 78, 44];
const set3 = new Set(arr);
const newArr1 = [...set3];
const newArr2 = Array.from(set3);
console.log(newArr1, newArr2);
console.log(set1.delete(12));
console.log(set1);
console.log(set1.has(10));
// set1.clear()
console.log(set1);
set1.forEach((s) => console.log(s));
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# Set 的常见方法
# Set的常见属性
- size: 返回Set中元素的个数
# Set中常用的方法
- add(value): 添加某个元素返回Set对象本身
- delete(value): 从Set中删除和这个值相等的元素,返回boolean类型
- has(value): 判断set中是否存在某个元素,返回boolean类型
- clear(): 清空set中的所有元素,无返回值
- forEach(clallback, [, thisArg]): 通过forEach遍历set
set是支持for...of...
遍历的
# WeakSet使用
WeakSet
的数据结构和Set类似,也是内部不能重复的数据结构
和Set的区别
- WeakSet中只能存放对象类型的,不能存放基本类型数据
- WeakSet 对元素的引用时弱引用,如果没有其它引用对某个对象进行引用,那么GC可以对该对象进行回收
# WeakSet的常见方法
- add(value): 添加某个元素,返回weakset本身
- delete(value): 从weakset中删除和这个值相等的元素,返回boolean类型
- has(value): 判断weakset中是否存在某个应用,返回boolean类型
# 注意:WeakSet不能遍历
- 因为WeakSet只是对对象的弱引用,如果遍历获取其中的元素,那么有可能造成对象不能正常销毁
- 所以存储到WeakSet中的对象是没有办法获取的
# WeakSet的使用
WeakSet如果运用到具体的业务场景中不好举出对应的实例
const pwset = new WeakSet()
class Person{
constructor(){
pwset.add(this)
}
running(){
if(!pwset.has(this)){
throw new Error('不能通过其他对象调用')
}
console.log('running', this)
}
}
2
3
4
5
6
7
8
9
10
11
12
# Map的基本使用
新增Map数据结构用于存储映射关系
之前存储映射关系可以使用对象,那么Map和对象有什么区别
- 对象存储映射关系只能使用字符串,Symbol作为属性名‘
- 如果某些情况下需要使用对象作为key,这个时候会自动将对象转换为字符串
[object object]
作为key,不同的对象转换出来的字符串都是一样的。这种情况下就需要使用Map结构来存储映射关系,可以将对象类型作为key
const obj1 = { name: "zs" };
const obj2 = { age: 18 };
const map = new Map();
map.set(obj1, "abc");
map.set(obj2, "cba");
console.log(obj1);
console.log(map.get(obj2));
// 支持forEach遍历,遍历输出的结果是map中对应的value
map.forEach((item) => console.log(item));
// 支持for...of...遍历,遍历的每一项是由 key和value组成的数组
for (const item of map) {
// 解构获取key和value
const [key, value] = item;
console.log(key, value);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# Map常用的方法
常见属性
- size: 返回Map中元素的个数
常见方法
- set(key, value): 在Map中添加key, value。并且返回整个Map对象
- get(key): 根据key获取Map中的value
- has(key): 判断是否包括某一个key, 返回Boolean类型
- delete(key): 根据key删除一个键值对,返回Boolean类型
- clear(): 清空所有的元素
- forEach(callback,[,thisArg]): 通过forEach遍历Map
Map也是支持for...of...
遍历的
# WeakMap
WeakMap是和Map数据结构相似的键值对存储形式
和Map的区别
- WeakMap的key只能使用对象,不接受其它类型作为key
- WeakMap的key对想引用对象的引用是弱引用,如果没有其它引用引用这个对象,那么GC可以回收这个对象
WeakMap常见的方法有四个
- set(key, value): 在Map中添加key, value,并返回整个Map对象
- get(key): 根据key获取Map中的value
- has(key): 判断是否包括某一个key,返回Boolean类型
- delete(key): 根据key删除一个键值对,返回Boolean类型
# WeakMap的应用
WeakMap是不能遍历的,没有forEach
方法,也不支持通过for...of...
的方式进行遍历
const targetMap = new WeakMap();
function getDep(target, key) {
let depsMap = targetMap.get(target);
if (!depsMap) {
depsMap = new Map();
targetMap.set(target, depsMap);
}
let dep = depsMap.get(key);
if (!dep) {
dep = new Dep();
depsMap.set(key, dep);
}
return dep;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
# ES7内容
# Array includes
在ES7中通过includes
来判断一个数组中是否包含一个指定元素,返回true/false
# 指数运算符
ES7之前计算数字的乘方需要通过Math.pow
方法来完成
在ES7中,增加**
对数字来计算乘方
const result1 = Math.pow(3, 3)
const result2 = 3 ** 3
2
# ES8内容
# Object.values
之前通过Object.keys
来获取一个对象所有的key
在ES8中提供Object.values
来获取所有的value值
const obj = {
name: "zs",
age: 18,
height: 180,
};
console.log(Object.keys(obj)); // ['name', 'age', 'height']
console.log(Object.values(obj)); // ['zs', 18, 180]
2
3
4
5
6
7
# Object.entries
通过Object.entries
可以获取到一个数组,数组中存放可枚举属性的键值对数组。可以针对对象、数组、字符串进行操作
const obj = {
name: "zs",
age: 18,
height: 180,
};
// 对象
console.log(Object.entries(obj)); // [['name', 'zs'],['age', 18], ['height', 180]]
for (const entry of Object.entries(obj)) {
const [key, value] = entry;
console.log(key, value);
}
// 数组
console.log(Object.entries(["abc", "cba", "nba"])); // [['0', 'abc'],['1', 'cba'],['2', 'nba']]
// 字符串
console.log(Object.entries("abc")); // [['0', 'a'],['1', 'b'],['2', 'c']]
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# String Padding
某些字符串需要对其进行前后的补充,实现格式化效果,ES8中新增padStart
和padEnd
方法来分别对字符串的首尾进行填充
let hours = "12";
let minutes = "5";
let seconds = "6";
console.log(
hours.padStart(2, "0"),
minutes.padStart(2, "0"),
seconds.padStart(2, "0")
); // 12 05 06
// 在字符串前面补0,总共2位,如果已经是两位数就不补0
let carNum = "133467907723144123";
carNum = carNum.slice(-4).padStart(carNum.length, "*");
console.log(carNum); // **************4123
2
3
4
5
6
7
8
9
10
11
12
# Trailing Commas 尾部追加
在ES8中允许在函数定义和调用时多加一个逗号
function foo(a, b,){
console.log(a, b)
}
foo(10, 20, )
2
3
4
# Object Descriptors
# Object.getOwnPropertyDescriptions
const obj = {
name: "zs",
age: 20,
height: 180,
};
console.log(Object.getOwnPropertyDescriptors(obj));
2
3
4
5
6
# async, await
# ES9内容
# Async iterators
# Object spread operators-- 展开运算符
# Promise finally
# ES10内容
# flat flatMap
flat()
方法会按照一个可指定的深度递归遍历数组,并将所有的元素与遍历到的子数组中的元素合并为一个新的数组返回
const nums = [10, 20, [5, 8], [[2,3],[9,22]], 100];
const newNums1 = nums.flat(1)
const newNums2 = nums.flat(2)
console.log(newNums1) // [10, 20, 5, 8, [2, 3], [9, 22], 100]
console.log(newNums2) // [10, 20, 5, 8, 2, 3, 9, 22, 100]
2
3
4
5
flatMap()
方法首先使用映射函数映射每个元素,然后将结果压缩成一个新的数组
注意
- flatMap是先进行map操作再做flat操作
- flatMap中flat的深度是1
const messages = ["Hello World aaa", "Hello ZS", "hello d"];
// 1. 通过for循环获取单数的字符串
/* const newInfos = [];
for (const item of messages) {
const infos = item.split(" ");
for (const info of infos) {
newInfos.push(info);
}
}
console.log(newInfos); */
// 2. 先进行map再使用flat
/* const newMessages = messages.map((item) => item.split(" "));
console.log(newMessages);
const finalMessages = newMessages.flat(1);
console.log(finalMessages); */
// 3. 直接使用flatMap
const finalMessages = messages.flatMap((item) => item.split(" "));
console.log(finalMessages);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# Object fromEntries
之前可以通过Object.entries
将对象转换成entries,那么如何将一个entries转换成对象呢。这里ES10提供了Object.fromEntries
来实现
const obj = {
name: "zs",
age: 20,
height: 180,
};
const entries = Object.entries(obj);
console.log(entries);
const info = Object.fromEntries(entries);
console.log(info);
2
3
4
5
6
7
8
9
const paramsString = "name=zs&age=20&height=180";
const searchParams = new URLSearchParams(paramsString);
console.log(searchParams);
for (const param of searchParams) {
console.log(param);
}
const searchObj = Object.fromEntries(searchParams);
console.log(searchObj);
2
3
4
5
6
7
8
# trimStart trimEnd
去除字符串首尾的空格,可以通过trim()
方法,如果需要单独去除前面或者后面的空格使用trimStart
和trimEnd
# Symbol
# ES11部分
# Bigint
在之前的JavaScript中,不能正确的表示过大的数字
即大于MAX_SAFE_INTEGER
的数值,不能正确表示
const maxInt = Number.MAX_SAFE_INTEGER;
console.log(maxInt); // 9007199254740991
console.log(maxInt + 1); // 9007199254740992
console.log(maxInt + 2); // 9007199254740992
2
3
4
在ES11中引入新的数据类型BigInt,用于表示大的整数
- BigInt的表示方法是在数值后面加上n
const bigInt = 9007199254740991n;
console.log(bigInt + 1n); // 9007199254740992n
console.log(bigInt + 2n); // 9007199254740993n
2
3
并且表示的颜色也不一致
# 空值合并操作符
const foo = "";
const result1 = foo || "默认值";
const result2 = foo ?? "默认值";
console.log(result1); // 默认值
console.log(result2); // ""
2
3
4
5
# 可选链
可选链是ES11中新增加的一个特性,主要是在代码中进行null
和undefined
判断时更加简洁
const obj = {
friend: {
bestFriend: {
name: "gd",
},
playing() {
console.log("playing");
},
},
};
if (obj.friend && obj.friend.bestFriend) {
console.log(obj.friend.bestFriend.name);
}
// 可选链的方式
console.log(obj.friend?.bestFriend?.name);
obj?.friend?.playing?.();
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# Global this
获取JavaScript环境的全局对象在不同环境下获取的方式是不一样的
- 浏览器中通过
this
,window
来获取 - Node中通过
global
来获取
在ES11中获取全局对象进行了统一的规范:globalThis
console.log(globalThis)
console.log(this)
console.log(global)
2
3
# for...in...标准化
在ES11之前虽然很多浏览器是支持使用for...in...
来遍历对象类型的,但是没有被ECMA标准化
在ES11中对其进行了标准化for...in
用于遍历对象的key
const obj = {
name: "zs",
age: 20,
height: 180,
};
for (const key in obj) {
console.log(key);
}
2
3
4
5
6
7
8
# Dynamic Import
# Promise.allSettled
# import meta
# ES12内容
# FinalizationRegistry
FinalizationRegistry
对象可以在对象被垃圾回收时请求一个回调
FinalizationRegistry
提供了这样一种方法,当一个在注册表中注册的对象被回收时,请求再某个时间点上调用一个清理回调。(清理回调有时被称为finalizer
)
可以通过调用register
方法注册任何想要清理回调的对象,传入该对象和所含的值
let obj = { name: "zs" };
const registry = new FinalizationRegistry((value) => {
console.log("对象被销毁了", value);
});
registry.register(obj, "obj");
obj = null;
2
3
4
5
6
# WeakRefs
如果默认将一个对象赋值给另外一个引用,那么这个引用是一个强引用,如果希望是一个弱引用可以使用WeakRef
let obj = {name: 'zs'}
let info = new WeakRef(obj)
const info = { name: "zs", age: 20 };
let info1 = new WeakRef(info);
// 获取弱引用对象的属性
const info1Ref = info1.deref();
console.log(info1Ref);
2
3
4
5
6
7
8
# 逻辑操作
// 逻辑或
let message = "";
// message = message || 'hello world'
message ||= "Hello World";
console.log(message);
// 逻辑与操作
let obj = {
name: "zs",
};
// obj = obj && obj.foo();
obj &&= obj.name;
console.log(obj);
// 逻辑空运算符
let foo = null
foo ??= "默认值"
console.log(foo)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# Numeric Separator
数字分隔符,例如100_0000_0000
# String.replaceAll
# ES13内容
# method.at()
字符串,数组的method.at()
方法是作为ES13中的新特性加入的
// 数组
const names = ["abc", "cba", "nba"];
console.log(names.at(1)); // cba
console.log(names.at(-1)); // nba
// 字符串
const str = "Hello World";
console.log(str.at(1)); // e
console.log(str.at(-1)); // d
2
3
4
5
6
7
8
# Object.hasOwn(obj, propKey)
Object中新增了一个静态方法(类方法):hasOwn(obj, propKey)。该方法用于判断一个对象中是否有某个自己的属性
和Object.prototype.hasOwnProperty
的区别
- 防止对象内部有重写
hasOwnProperty
- 对于隐式原型指向null的对象,
hasOwnProperty
无法进行判断
const obj = {
name: "why",
age: 20,
hasOwnProperty: function () {
return false;
},
};
let info = Object.create(null);
info.name = "zs";
// console.log(info.hasOwnProperty("name")); // 报错
console.log(Object.hasOwn(info, "name")); // true
2
3
4
5
6
7
8
9
10
11
# New members of classes
在ES13中新增了定义class类中成员字段(field)的其它方式
- Instance public fields
- Static public fields
- Instance private fields
- static private fields
- static block
class Person {
address = "中国";
static totalCount = "70亿";
// 只能类内部访问
// 对象的私有属性 在之前默认 _sex为私有属性
#sex = "male";
static #maleCount = "10亿";
constructor(name, age) {
this.name = name;
this.age = age;
}
static {
console.log("static block execution");
}
printInfo() {
console.log(this.address, this.#sex, Person.#maleCount);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18