ECMAScript 是JavaScript 所基于的脚本语言;
开局一张图
ES6
一、字面量增强的写法
var nickname = "网上冲浪不靠岸"
var age = 18
var obj = {
// 1.property shorthand(属性的简写)
nickname,
age,
// 2.method shorthand(方法的简写)
foo: function() {
console.log(this)
},
bar() {
console.log(this)
},
baz: () => {
console.log(this)
},
// 3.computed property name(计算属性名)
[nickname + 123]: '哈哈哈'
}
// obj.baz()
// obj.bar()
// obj.foo()
// obj[nickname + 123] = "哈哈哈"
console.log(obj)
二、数组的解构
var names = ['ace', 'sabot', 'luffy'];
// var namea = names[0],
// nameb = names[1],
// namec = names[2];
// console.log(namea, nameb, namec); //ace sabot luffy
// 1. 对数组的解构: []
var [item1, item2, item3] = names;
console.log(item1, item2, item3); //ace sabot luffy
// 2. 解构最后一个;
var [, , namez] = names;
console.log(namez); //luffy
// 3. 解构第一个剩余放入新数组;
var [namex, ...newNames] = names;
console.log(namex, newNames); //ace [ 'sabot', 'luffy' ]
// 4. 解构的默认值
var [itema, itemb, itemc, itemy = 'long'] = names;
console.log(itemy); //long
三、对象的解构
var obj = {
name: "yzh",
age: 18,
height: 1.80
}
var { name, age, height } = obj
console.log(name, age, height) //yzh 18 1.8
var { name, ...newObj } = obj
console.log(name, newObj); //yzh { age: 18, height: 1.8 }
var { age } = obj
console.log(age) //18
var { name: newName } = obj
console.log(newName) //yzh
var { address: newAddress = "深圳市" } = obj
console.log(newAddress) //深圳市
function foo(info) {
console.log(info.name, info.age) //yzh 18
}
foo(obj)
function baz({ name, age }) {
console.log(name, age) //yzh 18
}
baz(obj)
四、let 和 const
let语句声明一个块级作用域的局部变量,并可以初始化为一个值(可选);const类似用 let语句定义的变量。但常量的值是无法(通过重新赋值)改变的,也不能被重新声明;
4.1. let和const基本使用
var nickName = "wangming";
let alias = "biecheng";
// 注意事项一: 通过let/const定义的变量名是不可以重复定义
// SyntaxError: Identifier 'foo' has already been declared
let alias = "biecheng";
// const constant(常量/衡量)
// 注意事项二: const本质上是传递的值不可以修改
const age = 18;
// TypeError: Assignment to constant variable.
age = 20;
// 但是如果传递的是一个引用类型(内存地址), 可以通过引用找到对应的对象, 去修改对象内部的属性, 这个是可以的
const obj = {
name: "yzh"
};
obj.name = 'hzy';
console.log(obj);
4.2. let和const作用域提升
- 作用域提升: 能提前被访问;
- let和const它们是没有作用域提升的
//undefined console.log(msg); var msg = 'hello world'; // ReferenceError: Cannot access 'l_desc' before initialization console.log(l_desc); // ReferenceError: Cannot access 'c_desc' before initialization console.log(c_desc); let l_desc = '这是一段描述!' const c_desc = '这是一段描述!'
4.3. 作用域的理解
- ES5作用域
// 声明对象的字面量 var obj = { address: "shenzhenshi" } // ES5中没有块级作用域 // 块代码(block code) { // 声明一个变量 var address = "shenzhenshi" } // 所以能访问到 console.log(address); //shenzhenshi // 所以在ES5中只有两个东西会形成作用域 // 1.全局作用域 // 2.函数作用域 function foo() { var able = 'able' }; console.log(able); //ReferenceError: able is not defined - ES6块级作用域
// 通过let、const、function、class声明的标识符是具备块级作用域的限制的 { let box = 'box' const title = 'title' function test() { console.log('text function'); } class Person {} } console.log(box); //ReferenceError: box is not defined console.log(title); //ReferenceError: title is not defined //不同的浏览器有不同实现的(大部分浏览器为了兼容以前的代码, 让function是没有块级作用域) test() //text function var p = new Person() //ReferenceError: Person is not defined
let和const 在 if / switch / for 中都会产生块级作用域;
4.4. 块级作用域的应用场景
- 场景:有一组按钮,实现当点击某个按钮时输出点击的是第几个按钮;
- 四个按钮:按钮1 按钮2 按钮3 按钮4
- 获取:
const btnEls = document.getElementsByTagName('button');for (var i = 0; i < btnEls.length; i++) { btnEls[i].onclick = function() { /** * 输出:第4个按钮被点击!(因为函数去上层作用域找i,此时window.i = 4, * 所以点击任意一个按钮都会输出同样的内容); */ console.log('第' + i + '个按钮被点击!'); } }; console.log(i); //4
在全局通过var来声明一个变量,事实上会在window上添加一个属性
- 解决1:以前解决办法是使用匿名立执行函数;
for (var i = 0; i < btnEls.length; i++) { (function(n) { btnEls[n].onclick = function() { /** * 上层作用域是匿名函数; */ console.log('第' + n + '个按钮被点击!'); } })(i); }; console.log(i); //4 - 解决2:使用let在for循环里形成块级作用域;
for (let i = 0; i < btnEls.length; i++) { btnEls[i].onclick = function() { console.log('第' + i + '个按钮被点击!'); } };
五、模板字符串
5.1. 模板字符串的基本使用
- 在ES6之前,如果想要将字符串和一些动态的变量(标识符)拼接到一起,是非常麻烦和丑陋的;
- ES6允许我们使用字符串模板来嵌入JS的变量或者表达式来进行拼接:
- 首先,使用
``符号来编写字符串,称之为模板字符串; - 其次,在模板字符串中,我们可以通过
${expression}来嵌入动态的内容;const name = "yzh"; const age = 18; const height = 1.80; // ES6之前拼接字符串和其他标识符 console.log("my name is " + name + ", age is " + age + ", height is " + height) //my name is yzh, age is 18, height is 1.8 // ES6提供模板字符串 `` const message = `my name is ${name}, age is ${age}, height is ${height}` console.log(message) //my name is yzh, age is 18, height is 1.8 const ageMsg = `age double is ${age * 2}` console.log(ageMsg) //age double is 36 function doubleAge() { return age * 2 } const ageMsg2 = `double age is ${doubleAge()}` console.log(ageMsg2) //double age is 36
- 首先,使用
5.2. 标签模板字符串的使用
function foo(m, n, x) {
console.log(m, n, x)
}
// 函数普通调方式
foo("Hello", "World") //Hello World undefined
// 另外调用函数的方式: 标签模块字符串
foo`` //[ '' ] undefined undefined
foo`Hello World` //[ 'Hello World' ] undefined undefined
// 第一个参数依然是模块字符串中整个字符串, 只是被切成多块,放到了一个数组中
// 第二个参数是模块字符串中, 第一个 ${} 嵌入动态的内容
// 第三个参数是模块字符串中, 第二个 ${} 嵌入动态的内容
const name = "yzh"
const age = 18
foo`Hello${name}Wo${age}rld` //[ 'Hello', 'Wo', 'rld' ] yzh 18
5.3. styled-components库
图:style.js 文件
styled-components 是一个针对 React 的 css in js 类库;
六、函数的默认值
6.1. 写起来很麻烦, 并且代码的阅读性是比较差;
function foo() {
var m = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'aaa';
var n = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'bbb';
console.log(m, n);
}
foo() //aaa bbb
foo('aaa', 'bbb') //aaa bbb
6.2. ES12逻辑或运算符
- 写法有bug:比如给函数传入 0 ,''(空字符串);
function foo(m, n) { m = m || "aaa" n = n || "bbb" console.log(m, n) } foo() //aaa bbb foo(0, "") //aaa bbb
6.3. ES6可以给函数参数提供默认值
//1.
function foo(m = "aaa", n = "bbb") {
console.log(m, n)
}
foo() //aaa bbb
foo(0, "") //0
//2.
function baz(x, y = x + 1) {
console.log(x, y)
}
baz(10) // 10, 11
6.4. 函数的对象参数和默认值以及解构
- 默认值也可以和解构一起来使用:
function printInfo({ name, age } = { name: "yzh", age: 18 }) { console.log(name, age) } printInfo() //yzh 18 printInfo({}) //undefined undefined printInfo({ name: "ace", age: 22 }) //ace 22 // 另外一种写法 function printInfo1({ name = "hzy", age = 18 } = {}) { console.log(name, age) } printInfo1() //hzy 18 printInfo1({}) //hzy 18 printInfo1({ name: 'sabot', age: 20 }) //sabot 20
6.5. 函数的长度(length)
- 默认值会改变函数的length的个数,默认值以及后面的参数都不计算在length之内了;
function baz(x, y, z, q, w) { console.log(x, y, z, q, w) } console.log(baz.length) //5 function baz(x, y, z = 30, q, w) { console.log(x, y, z, q, w) } console.log(baz.length) //2
七、函数的剩余参数
- ES6中引用了rest parameter,可以将不定数量的参数放入到一个数组中:
- 如果最后一个参数是 ... 为前缀的,那么它会将剩余的参数放到该参数中,并且作为一个数组;
- 剩余参数和arguments的区别:
- 剩余参数只包含那些没有对应形参的实参,而 arguments 对象包含了传给函数的所有实参;
- arguments对象不是一个真正的数组,而rest参数是一个真正的数组,可以进行数组的所有操作;
function foo(m, n, ...args) { console.log(m, n) //20 30 console.log(args) //[ 40, 50, 60 ] console.log(arguments) //[Arguments] { '0': 20, '1': 30, '2': 40, '3': 50, '4': 60 } } foo(20, 30, 40, 50, 60)
1)arguments是早期的ECMAScript中为了方便去获取所有的参数提供的一个数据结构,而rest参数 是ES6中提供并且希望以此来替代arguments的;
2)剩余参数必须放到最后一个位置,否则会报错;
function foo(...args) {
console.log(args) //[ 20, 30, 40, 50, 60 ]
}
// 错误示例
// function foo(...args, x, y) {
// console.log(args)
// }
foo(20, 30, 40, 50, 60)
八、箭头函数补充
- 箭头函数表达式的语法比函数表达式更简洁,并且没有自己的this(不绑定this,只会往上层作用域找),arguments;
- 箭头函数是没有显式原型的,所以不能作为构造函数,使用new来创建对象;
const foo = () => { console.log('foo'); }; console.log(foo.prototype); //undefined const f = new foo(); //TypeError: foo is not a constructor
九、展开语法 (Spread syntax)
- 在函数调用/数组构造时,将数组表达式或者string在语法层面展开;
- 在构造字面量对象时, 将对象表达式按key-value的方式展开;
const names = ["ace", "sabot", "luffy"] const name = "yzh" const info = { name: "hzy", age: 18 } // 1.函数调用时 function foo(x, y, z) { console.log(x, y, z) } // foo.apply(null, names) foo(...names) //ace sabot luffy foo(...name) //y z h // 2.构造数组时 const newNames = [...names, ...name] console.log(newNames) //[ 'ace', 'sabot', 'luffy', 'y', 'z', 'h' ] // 3.构建对象字面量时,也可以使用展开运算符,这个是在ES2018(ES9)中添加的新特性; const obj = { ...info, address: "shenzhenshi", ...names } console.log(obj) /* { '0': 'ace', '1': 'sabot', '2': 'luffy', name: 'hzy', age: 18, address: 'shenzhenshi' } */ - 展开运算符其实是一种浅拷贝;
const info = { name: "yzh", friend: { name: "ace" } } const obj = { ...info } console.log(obj) //{ name: 'yzh', friend: { name: 'ace' } } obj.friend.name = "luffy" console.log(info.friend.name) //luffy
十、Symbol数据类型
- Symbol是ES6中新增的一个基本数据类型,翻译为符号;
- 在ES6之前,对象的属性名都是字符串形式,那么很容易造成属性名的冲突;
- 比如原来有一个对象,我们希望在其中添加一个新的属性和值,但是我们在不确定它原来内部有什么内容的情况下,很容易造成冲突,从而覆盖掉它内部的某个属性;
var obj = { name: "yzh", friend: { name: "ace" }, age: 18 }; var newObj = { ...obj, name: 'new yzh'}; console.log(newObj); //{ name: 'new yzh', friend: { name: 'ace' }, age: 18 }
- 比如原来有一个对象,我们希望在其中添加一个新的属性和值,但是我们在不确定它原来内部有什么内容的情况下,很容易造成冲突,从而覆盖掉它内部的某个属性;
- Symbol就是为了解决类似这样的问题,用来生成一个独一无二的值;
- Symbol值是通过Symbol函数来生成的,生成后可以作为属性名;
- 也就是在ES6中,对象的属性名可以使用字符串,也可以使用Symbol值;
10.1. Symbol的基本使用
- Symbol即使多次创建值,它们也是不同的:Symbol函数执行后每次创建出来的值都是独一无二的;
const s1 = Symbol(); const s2 = Symbol(); console.log(s1 === s2); //false
10.2. Symbol作为属性名
前面:
const s1 = Symbol();
const s2 = Symbol();
const obj = {};
- 1)在定义对象字面量时:
const obj = { [s1]: "ace", [s2]: "sabot" } - 2)属性名赋值:
obj[s3] = "luffy" - 3)Object.defineProperty:
const s4 = Symbol() Object.defineProperty(obj, s4, { enumerable: true, configurable: true, writable: true, value: "long" }) //ace sabot luffy long console.log(obj[s1], obj[s2], obj[s3], obj[s4])
注意: 不能通过.语法获取
console.log(obj.s1)
10.3. Object.getOwnPropertySymbols
- 使用Symbol作为key的属性名,在遍历/Object.keys等中是获取不到这些Symbol值;
- 需要Object.getOwnPropertySymbols来获取所有Symbol的key;
Object.getOwnPropertySymbols()方法返回一个给定对象自身的所有 Symbol 属性的数组;console.log(Object.keys(obj)); //[] console.log(Object.getOwnPropertyNames(obj)); //[] console.log(Object.getOwnPropertySymbols(obj)); //[ Symbol(), Symbol(), Symbol(), Symbol() ] const sKeys = Object.getOwnPropertySymbols(obj); for (const sKey of sKeys) { console.log(obj[sKey]) //ace sabot luffy long };
Object.getOwnPropertyNames()方法返回一个由指定对象的所有自身属性的属性名(包括不 可枚举属性但不包括 Symbol 值作为名称的属性)组成的数组;
10.4. 相同值的Symbol
- 使用Symbol.for方法创建相同的Symbol;
- 使用Symbol.keyFor方法来获取Symbol对应的key;
- 语法:
Symbol.for(key)/Symbol.keyFor(symbol)const sa = Symbol.for("aaa") const sb = Symbol.for("aaa") console.log(sa === sb); //true const key = Symbol.keyFor(sa); console.log(key); //aaa const sc = Symbol.for(key); console.log(sa === sc); //true
- 语法:
🍚补充
- 可以在创建Symbol值的时候传入一个描述description:这个是ES2019(ES10)新增的特性;
// ES2019(ES10)中, Symbol还有一个描述(description) const s = Symbol("miaoshu"); console.log(s.description); //miaoshu
在ES6之前,我们存储数据的结构主要有两种:数组、对象;
在ES6中新增了另外两种数据结构:Set、Map,以及它们的另外形式WeakSet、WeakMap;
十一、数据结构Set
Set对象允许你存储任何类型的唯一值,无论是原始值或者是对象引用,但是和数组的区别是元素不能重复;Set对象是值的集合;- 创建Set我们需要通过Set构造函数;
const set = new Set(); console.log(set); //Set(0) {}
- 创建Set我们需要通过Set构造函数;
- Set中存放的元素是不会重复的,Set有一个非常常用的功能就是给数组去重:
const set = new Set([10, 12, 8, 10, 12, 6]); console.log(set); //Set(4) { 10, 12, 8, 6 } //对数组去重: const arr = [5, 8, 6, 5, 2, 4, 6, 8]; const arrSet = new Set(arr); console.log(arrSet); //Set(5) { 5, 8, 6, 2, 4 } const newArr = Array.from(arrSet); console.log(newArr); //[ 5, 8, 6, 2, 4 ] const newArr2 = [...arrSet]; console.log(newArr2); //[ 5, 8, 6, 2, 4 ]
11.1. Set常见的属性
- size:返回Set中元素的个数;
const arr = [5, 8, 6, 5, 2, 4, 6, 8]; const arrSet = new Set(arr); console.log(arrSet.size); //5
11.2. Set常用的方法
前面:
const set = new Set();
- add(value)方法:添加某个元素,返回Set对象本身;
set.add('ace'); set.add('sabot'); set.add('luffy'); set.add('yzh'); const res = set.add(1); console.log(res); //Set(5) { 'ace', 'sabot', 'luffy', 'yzh', 1 } console.log(set); //Set(5) { 'ace', 'sabot', 'luffy', 'yzh', 1 } - delete(value):从set中删除和这个值相等的元素,返回boolean类型;
const dSet = set.delete(1); console.log(dSet); //true console.log(set); //Set(4) { 'ace', 'sabot', 'luffy', 'yzh' } - has(value):判断set中是否存在某个元素,返回boolean类型;
const hSet = set.has(1); const hSet1 = set.has('yzh'); console.log(hSet); //false console.log(hSet1); //true console.log(set); //Set(4) { 'ace', 'sabot', 'luffy', 'yzh' } - clear():清空set中所有的元素,没有返回值;
set.clear(); console.log(set); //Set(0) {} - forEach():通过forEach遍历Map;
set.forEach(item => { console.log(item); }); //Set也是支持for of遍历的; for (const item of set) { console.log(item, 'for of'); };
十二、数据结构WeakSet
- 和Set类似的一个数据结构,也是内部元素不能重复的数据结构;
- 和Set的区别:
- 1)WeakSet中只能存放对象类型,不能存放基本数据类型;
- 2)WeakSet对元素的引用是弱引用:如果没有其它引用对某个对象进行引用,那么GC(垃圾回收机制)可以对该对象进行回收;
const wSet = new WeakSet(); //TypeError: Invalid value used in weak set wSet.add(12)
12.1. WeakSet常用的方法
前面:
const wSet = new WeakSet();
const obj = {
name: 'yzh',
age: 18
};
const info = {
address: '深圳市'
};
const desc = {
hobby: 'game'
};
- add(value):添加某个元素,返回WeakSet对象本身;
wSet.add(obj); wSet.add(info); wSet.add(desc); console.log(wSet); - delete(value):从WeakSet中删除和这个值相等的元素,返回boolean类型;
const res = wSet.delete(desc); console.log(res); //true - has(value):判断WeakSet中是否存在某个元素,返回boolean类型;
console.log('has', wSet.has(desc)); //has false console.log('has2', wSet.has(info)); //has2 true
12.2. WeakSet应用场景
- WeakSet没有size属性,所以不支持forEach、for of遍历;
- WeakSet只是对对象的弱引用,如果我们遍历获取到其中的元素,那么有可能造成对象不能正常的销毁;
- 应用场景:
const personSet = new WeakSet(); class Person { constructor() { personSet.add(this) } running() { if (!personSet.has(this)) { throw new Error('不能通过非构造方法出来的对象调用running方法'); }; console.log('runing', this); } }; let p = new Person(); p.running(); // Error: 不能通过非构造方法出来的对象调用running方法 // p.running.call({name: 'yzh'});
十三、数据结构Map
Map对象保存键值对,并且能够记住键的原始插入顺序。任何值(对象或者基本类型)都可以作为一个键或一个值;- JS里对象中的key是不可以使用对象的,否则会转为字符串('[object Object]')对象,我们可能希望通过其他类型作为key,比如对象,这个时候就可以使用Map;
前面:
const obj = {
name: 'yzh'
};
const obj2 = {
name: 'ace'
};
const map = new Map([[obj, 'luffy'], [obj2, 'ace'], [2, 'sabo']]);
console.log(map); //Map(3) {{ name: 'yzh' } => 'luffy', { name: 'ace' } => 'ace', 2 => 'sabo'}
13.1. Map的常见的属性
- size:返回Map中元素的个数;
console.log(map.size); //3
13.2. Map的常用的方法
-
set(key, value):在Map中添加key、value,并且返回整个Map对象;
map.set('t-bag', '111'); console.log(map); /* Map(4) { { name: 'yzh' } => 'luffy', { name: 'ace' } => 'ace', 2 => 'sabo', 't-bag' => '111' } */ -
get(key):根据key获取Map中的value;
console.log(map.get('t-bag')); //'111' -
delete(key):根据key删除一个键值对,返回Boolean类型;
map2.delete('t-bag'); console.log(map2); /* Map(3) { { name: 'yzh' } => 'luffy', { name: 'ace' } => 'ace', 2 => 'sabo' } */ -
has(key):判断是否包括某一个key,返回Boolean类型;
console.log(map.has('t-bag')); //false console.log(map.has(obj)); //true -
clear():清空所有的元素;
// map.clear(); // console.log(map); //Map(0) {} -
forEach():通过forEach遍历Map;
map.forEach((item, key) => { console.log(item, key, 'foreach'); }); /* luffy { name: 'yzh' } foreach ace { name: 'ace' } foreach sabo 2 foreach */ -
keys():返回一个引用的迭代器对象。它包含按照顺序插入
Map对象中每个元素的 key 值;const map1 = new Map(); map1.set('0', 'foo'); map1.set(1, 'bar'); const iterator1 = map1.keys(); console.log(iterator1.next().value); //'0' console.log(iterator1.next().value); //1 -
values():方法返回一个新的迭代器对象。它包含按顺序插入
Map对象中每个元素的value值;const map1 = new Map(); map1.set('0', 'foo'); map1.set(1, 'bar'); const iterator1 = map1.values(); console.log(iterator1.next().value); //"foo" console.log(iterator1.next().value); //"bar" -
entries():方法返回一个新的迭代器对象。其中包含
Map对象中按插入顺序排列的每个元素的[key, value]对;const map1 = new Map(); map1.set('0', 'foo'); map1.set(1, 'bar'); const iterator1 = map1.entries(); console.log(iterator1.next().value); //["0", "foo"] console.log(iterator1.next().value); //[1, "bar"] -
也可以使用 for...of 方法迭代 Map
const myMap = new Map(); myMap.set(0, 'zero'); myMap.set(1, 'one'); for (const item of myMap) { console.log(`${item[0]} = ${item[1]}`); } for (const [key, value] of myMap) { console.log(`${key} = ${value}`); } // 0 = zero // 1 = one for (const key of myMap.keys()) { console.log(key); } // 0 // 1 for (const value of myMap.values()) { console.log(value); } // zero // one for (const [key, value] of myMap.entries()) { console.log(`${key} = ${value}`); } // 0 = zero // 1 = one
十四、数据结构WeakMap
- 与Map区别:
- 1.WeakMap中的key只能使用对象类型,不接受其它的类型作为key;
- 2.WeakMap的key对对象的引用是弱引用,如果没有其它引用对这个对象进行强引用,那么GC可以对该对象进行回收;
- 没有clear()方法,没有遍历;
const wMap = new WeakMap(); //TypeError: Invalid value used as weak map key wMap.set('str', 'hello weakMap'); //TypeError: Invalid value used as weak map key wMap.set(1, 'hello yzh');
14.1. WeakMap常用的方法
- set(key, value):在Map中添加key、value,并且返回整个Map对象;
- get(key):根据key获取Map中的value;
- has(key):判断是否包括某一个key,返回Boolean类型;
- delete(key):根据key删除一个键值对,返回Boolean类型;
const obj = {name: "obj"}; const wMap = new WeakMap(); wMap.set(obj, "111obj"); console.log(wMap); console.log(wMap.get(obj)); //111obj console.log(wMap.has(obj)); //true // console.log(wMap.delete(obj)); //true // console.log(wMap);
14.2. WeakMap的应用
// 响应式中的WeakMap
const obj1 = {
name: 'obj1',
age: 18
};
function obj1NameFn1() {
console.log('Name-obj1NameFn1');
};
function obj1NameFn2() {
console.log('Name-obj1NameFn2');
};
function obj1AgeFn1() {
console.log('Age-obj1AgeFn1');
};
function obj1AgeFn2() {
console.log('Age-obj1AgeFn2');
};
const obj2 = {
name: 'obj2',
age: 20
};
function obj2NameFn1() {
console.log('obj2NameFn1');
};
function obj2NameFn2() {
console.log('obj2NameFn2');
};
function obj2AgeFn1() {
console.log('obj2AgeFn1');
};
function obj2AgeFn2() {
console.log('obj2AgeFn2');
};
// 1.创建WeakMap
const wMap = new WeakMap();
// 2.收集依赖结构
// 2.1.对obj1收集的数据结构;
const obj1Map = new Map();
obj1Map.set("name", [obj1NameFn1, obj1NameFn2]);
obj1Map.set("age", [obj1AgeFn1, obj1AgeFn2]);
wMap.set(obj1, obj1Map);
// 2.2.对obj2收集的数据结构;
const obj2Map = new Map();
obj2Map.set("name", [obj2NameFn1, obj2NameFn2]);
wMap.set(obj2, obj2Map);
//console.log(wMap);
// 3.如果obj1.name发生了改变;
obj1.name = "yzh1";
const targetMap = wMap.get(obj1);
const fns = targetMap.get('name');
fns.forEach(fn => fn());
十五、Proxy
15.1. 监听对象的操作
- 监听一个对象中的属性被获取或设置的过程;
const obj = { name: 'yzh', age: 18 }; Object.keys(obj).forEach(key => { let value = obj[key]; Object.defineProperty(obj, key, { get: function() { console.log(`监听到obj对象的${key}属性被访问了`); return value; }, set: function(newVal) { console.log(`监听到obj对象的${key}属性被设置了`); value = newVal; } }) }); console.log(obj.name); //监听到obj对象的name属性被访问了 yzh console.log(obj.age); //监听到obj对象的age属性被访问了 18 obj.name = 'ace'; //监听到obj对象的name属性被设置了 obj.age = 22; //监听到obj对象的age属性被设置了 //存储数据描述符并没有监听拦截到 obj.height = 1.80;
这样做的缺点:
1)首先,Object.defineProperty设计的初衷,不是为了去监听拦截一个对象中所有的属性的; 在定义某些属性的时候,初衷其实是定义普通的属性,但是后面我们强行将它变成了数据属性描述符;
2)其次,如果我们想监听更加丰富的操作,比如新增属性、删除属性,那么Object.defineProperty是无能 为力的;
3)所以,存储数据描述符设计的初衷并不是为了去监听一个完整的对象;
15.2. Proxy基本使用
- Proxy 对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等);
- 也就是说,如果要监听一个对象的相关操作,那么可以先创建一个代理对象(Proxy对象);
- 然后对该对象的所有操作,都通过代理对象来完成,代理对象可以监听我们想要对原对象进行哪些操作;
- 语法:
const p = new Proxy(target, handler) - 参数:
target:需要侦听的对象;要使用Proxy包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理);
handler:处理对象;一个通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理p的行为;
1)Proxy的set和get捕获器
- 侦听某些具体的操作,在handler中添加对应的捕捉器;
- set和get分别对应的是函数类型;
- set函数有四个参数:
- target:目标对象(侦听的对象);
- property:将被设置的属性key;
- value:新属性值;
- receiver:调用的代理对象;
- get函数有三个参数:
- target:目标对象(侦听的对象);
- property:被获取的属性key;
- receiver:调用的代理对象;
- set函数有四个参数:
- 监听一个对象中的属性被获取或设置的过程;
const obj = { name: 'yzh', age: 18 }; const objProxy = new Proxy(obj, { //获取值时的捕捉器; get: function(target, key) { console.log(`监听到对象的${key}属性被get了`, target); return target[key]; }, //设置值时的捕捉器; set: function(target, key, newVal) { console.log(`监听到对象的${key}属性被set了`, target); target[key] = newVal; }, // 监听in的捕获器 has: function(target, key) { console.log(`监听到对象的${key}属性in操作`, target); return key in target; }, //监听delete的捕获器 deleteProperty: function(target, key) { console.log(`监听到对象的${key}属性delete操作`, target); delete target[key]; } }); console.log(objProxy.name); //监听到对象的name属性被get了 yzh console.log(objProxy.age); //监听到对象的age属性被get了 18 objProxy.name = 'ace'; //监听到对象的name属性被set了 objProxy.age = 22; //监听到对象的age属性被set了 console.log('name' in objProxy); //监听到对象的name属性in操作 true delete objProxy.name; //监听到对象的name属性delete操作 console.log(obj); //{ age: 22 }
2)Proxy所有捕获器
handler对象是一个容纳一批特定属性的占位符对象。它包含有Proxy的各个捕获器;- 所有的捕捉器是可选的。如果没有定义某个捕捉器,那么就会保留源对象的默认行为;
3)Proxy的construct和apply
- proxy对函数对象的监听:
function foo(a, b) { this.num = a; this.num2 = b; }; const fooProxy = new Proxy(foo, { apply: function(target, thisArg, argArray) { console.log('函数apply的监听', thisArg, argArray); return target.apply(thisArg, argArray); }, construct: function(target, argArray, newTarget) { console.log('函数new的监听', argArray, newTarget); return new target(...argArray); } }); //函数apply的监听 { name: 'haha' } [ 'ace', 'sabo' ] fooProxy.apply({name: 'haha'}, ['ace', 'sabo']); //函数new的监听 [ '123', '321' ] [Function: foo] const fn = new fooProxy('123', '321'); console.log(fn.num); //123 console.log(fn.num2); //321
十六、Reflect
16.1. Reflect的作用
- Reflect也是ES6新增的一个API,它是一个对象,字面的意思是反射。
- 它主要提供了很多操作JavaScript对象的方法,有点像Object中操作对象的方法;
- 比如Reflect.getPrototypeOf(target) 类似于 Object.getPrototypeOf();
- 比如Reflect.defineProperty(target, propertyKey, attributes) 类似Object.defineProperty();
- 为什么还需要有Reflect这样的新增对象呢?
- 这是因为在早期的ECMA规范中没有考虑到这种对 对象本身 的操作如何设计会更加规范,所以将这些API放到了Object上面;
- 但是Object作为一个构造函数,这些操作实际上放到它身上并不合适;
- 另外还包含一些类似于 in、delete操作符,让JS看起来是会有一些奇怪的;
- 所以在ES6中新增了Reflect,让我们这些操作都集中到了Reflect对象上;
16.2. Reflect的静态方法
- Reflect.getPrototypeOf(target)
- 类似于 Object.getPrototypeOf();
- Reflect.setPrototypeOf(target, prototype)
- 设置对象原型的函数. 返回一个 Boolean, 如果更新成功,则返回true;
- Reflect.isExtensible(target)
- 类似于 Object.isExtensible();
- Reflect.preventExtensions(target)
- 类似于 Object.preventExtensions()。返回一个Boolean;
- Reflect.getOwnPropertyDescriptor(target, propertyKey)
- 类似于 Object.getOwnPropertyDescriptor()。如果对象中存在该属性,则返回对应的属性描述符, 否则返回 undefined;
- Reflect.defineProperty(target, propertyKey, attributes)
- 和 Object.defineProperty() 类似。如果设置成功就会返回 true;
- Reflect.ownKeys(target)
- 返回一个包含所有自身属性(不包含继承属性)的数组。(类似于Object.keys(), 但不会受enumerable影响);
- Reflect.has(target, propertyKey)
- 判断一个对象是否存在某个属性,和 in 运算符 的功能完全相同;
- Reflect.get(target, propertyKey[, receiver])
- 获取对象身上某个属性的值,类似于 target[name];
- Reflect.set(target, propertyKey, value[, receiver])
- 将值分配给属性的函数。返回一个Boolean,如果更新成功,则返回true;
- Reflect.deleteProperty(target, propertyKey)
- 作为函数的delete操作符,相当于执行 delete target[name];
- Reflect.apply(target, thisArgument, argumentsList)
- 对一个函数进行调用操作,同时可以传入一个数组作为调用参数。和Function.prototype.apply() 功能类似;
- Reflect.construct(target, argumentsList[, newTarget])
- 对构造函数进行 new 操作,相当于执行 new target(...args);
16.3. Reflect的使用
const obj = {
name: 'yzh',
age: 18
};
const objProxy = new Proxy(obj, {
//获取值时的捕捉器;
get: function(target, key) {
console.log(`监听到对象的${key}属性被get了`, target);
return Reflect.get(target, key);
},
//设置值时的捕捉器;
set: function(target, key, newVal) {
console.log(`监听到对象的${key}属性被set了`, target);
Reflect.set(target, key, newVal);
},
// 监听in的捕获器
has: function(target, key) {
console.log(`监听到对象的${key}属性in操作`, target);
return Reflect.has(target, key);
},
//监听delete的捕获器
deleteProperty: function(target, key) {
console.log(`监听到对象的${key}属性delete操作`, target);
Reflect.deleteProperty(target, key);
}
});
console.log(objProxy.name);
console.log(objProxy.age);
objProxy.name = 'ace';
objProxy.age = 22;
// console.log(obj);
console.log('name' in objProxy);
delete objProxy.name;
// console.log(obj);
16.4. Receiver的作用
- 如果源对象(obj)有setter、getter的访问器属性,那么可以通过receiver来改变里面的this;
const obj = { _name: "yzh", get name() { return this._name }, set name(newValue) { this._name = newValue } }; const objProxy = new Proxy(obj, { get: function(target, key, receiver) { // receiver是创建出来的代理对象 console.log("get方法被访问--------", key, receiver); console.log(receiver === objProxy); return Reflect.get(target, key, receiver); }, set: function(target, key, newValue, receiver) { console.log("set方法被访问--------", key); Reflect.set(target, key, newValue, receiver); } }); console.log(objProxy.name) objProxy.name = "ace"; console.log(objProxy.name)
16.5. Reflect的construct
- 语法:Reflect.construct(target, argumentsList, newTarget);
- 参数:
- target:被运行的目标构造函数;
- argumentsList:类数组,目标构造函数调用时的参数;
- newTarget:作为新创建对象的原型对象的
constructor属性。默认值为target;
Reflect.construct()方法的行为有点像new操作符构造函数,相当于运行new target(...args);function Student(name, age) { this.name = name; this.age = age; }; function Teacher() { }; // 执行Student函数中的内容, 但是创建出来对象是Teacher对象 const teacher = Reflect.construct(Student, ["yzh", 18], Teacher); console.log(teacher); //Teacher { name: 'yzh', age: 18 } console.log(teacher.__proto__ === Teacher.prototype); //true
十七、Generator(生成器函数)
十八、Iterator(迭代器对象)
ES7
一、Array.prototype.includes()
- ES7之前,如果我们想判断一个数组中是否包含某个元素,需要通过 indexOf 获取结果,并且判断是否为 -1;
- ES7中可以通过
includes()方法用来判断一个数组是否包含一个指定的值,根据情况,如果包含则返回true,否则返回false; - 语法:includes(searchElement) / includes(searchElement, fromIndex);
- searchElement:需要查找的元素值;
- fromIndex:从
fromIndex索引处开始查找searchElement。如果为负值,则按升序从array.length + fromIndex的索引开始搜(从末尾开始往前跳fromIndex的绝对值个索引,然后往后搜寻)。默认为 0;const names = ['ace', 'sabo', NaN, 'luffy', 'long', 'yzh']; console.log(names.indexOf(NaN)); //-1 console.log(names.includes(NaN)); //true //末尾开始往前跳 `fromIndex` 的绝对值个索引,然后往后搜寻 console.log(names.includes(NaN, -3)); //false if (names.indexOf("yzh") !== -1) { console.log("包含abc元素") } //包含abc元素 if (names.includes("yzh", 2)) { console.log("包含abc元素") } //包含abc元素 if (names.indexOf(NaN) !== -1) { console.log('indexOf: ', "包含NaN") } if (names.includes(NaN)) { console.log('includes: ', "包含NaN") } //includes: 包含NaN
二、指数(乘方)的运算方法
- ES7之前,计算数字的乘方需要通过 Math.pow 方法来完成;
Math.pow()函数返回基数(base)的指数(exponent)次幂,即base^exponent;
- ES7中,增加了 ** 运算符,可以对数字来计算乘方:
const res1 = Math.pow(3, 3); console.log(res1); //27 //es7 const res2 = 3 ** 3; console.log(res2); //27 console.log((2 ** 3) ** 2); //64
ES8
一、Object values
- 之前我们可以通过 Object.keys 获取一个对象所有的key,在ES8中提供了 Object.values 来获取所有的value值;
Object.values()方法返回一个给定对象自身的所有可枚举属性值的数组,值的顺序与使用for...in循环的顺序相同(区别在于 for-in 循环枚举原型链中的属性)const obj = { name: 'yzh', age: 18 }; console.log(Object.values(obj)); //[ 'yzh', 18 ] console.log(Object.values(['ace', 'sabo'])); //[ 'ace', 'sabo' ] console.log(Object.values('string')); //[ 's', 't', 'r', 'i', 'n', 'g' ]
二、Object entries
Object.entries()方法返回一个给定对象自身可枚举属性的键值对数组;const obj = { name: 'yzh', age: 18 }; console.log(Object.entries(obj)); //[ [ 'name', 'yzh' ], [ 'age', 18 ] ] const objEnts = Object.entries(obj); // objEnts.forEach(item => { // console.log(item[0], item[1]); // }); objEnts.forEach(([key, value]) => { console.log(key, value); }); console.log(Object.entries(['ace', 'sabo'])); //[ [ '0', 'ace' ], [ '1', 'sabo' ] ] console.log(Object.entries('abc')); //[ [ '0', 'a' ], [ '1', 'b' ], [ '2', 'c' ] ]
三、字符串填充(String padding)
padStart()方法用另一个字符串填充当前字符串(如果需要的话,会重复多次),以便产生的字符串达到给定的长度。从当前字符串的左侧开始填充;- 语法:padStart(targetLength) / padStart(targetLength, padString)
targetLength当前字符串需要填充到的目标长度。如果这个数值小于当前字符串的长度,则返回当前字符串本身;padString填充字符串。如果字符串太长,使填充后的字符串长度超过了目标长度,则只保留最左侧的部分,其他部分会被截断;'abc'.padStart(10); // " abc" 'abc'.padStart(10, "foo"); // "foofoofabc" 'abc'.padStart(6,"123465"); // "123abc" 'abc'.padStart(8, "0"); // "00000abc" 'abc'.padStart(1); // "abc"
padEnd()方法会用一个字符串填充当前字符串(如果需要的话则重复填充),返回填充后达到指定长度的字符串。从当前字符串的末尾(右侧)开始填充;- 语法:和padStart()一样;
'abc'.padEnd(10); // "abc " 'abc'.padEnd(10, "foo"); // "abcfoofoof" 'abc'.padEnd(6, "123456"); // "abc123" 'abc'.padEnd(1); // "abc" - 应用场景:
const message = 'Hello'; const newMsg = message.padStart(9, 'yzh ').padEnd(15 ,' World'); console.log(newMsg) //yzh Hello World; console.log(newMsg.length) //15; //身份证、银行卡的前面位数进行隐藏 const cardNumber = '681815233646548897'; const lastCardNumber = cardNumber.slice(-4); const finalCardNumber = lastCardNumber.padStart(cardNumber.length, '*'); console.log(finalCardNumber); //**************8897
四、尾随逗号(Trailing Commas)
- 在ES8中,我们允许在函数定义和调用时多加一个逗号:
function foo(a, b,) { console.log(a, b); }; foo(1, 2,)
无聊的知识又增加了!
五、对象属性描述符(Object Descriptors)
- ES8中增加了一个
Object.getOwnPropertyDescriptors()方法用来获取一个对象的所有自身属性的描述符;const obj = { name: 'yzh', age: 18 } console.log(Object.getOwnPropertyDescriptors(obj)); /* { name: { value: 'yzh', writable: true, enumerable: true, configurable: true }, age: { value: 18, writable: true, enumerable: true, configurable: true } } */
六、aysnc的function使用
ES9
一、迭代器(Async iterators)
二、对象展开运算符
三、promise finally
ES10
一、flat、flatMap
flat:
flat()方法会按照一个可指定的深度递归遍历数组,并将所有元素与遍历到的子数组中的元素合并为一个新数组返回;- 语法:flat() / flat(depth)
depth(可选)指定要提取嵌套数组的结构深度,默认值为 1;
- 返回值:一个包含数组与子数组中所有元素的新数组;
var arr1 = [1, 2, [3, 4]]; console.log(arr1.flat()); // [1, 2, 3, 4] var arr2 = [1, 2, [3, 4, [5, 6]]]; console.log(arr2.flat()); // [1, 2, 3, 4, [5, 6]] var arr3 = [1, 2, [3, 4, [5, 6]]]; console.log(arr3.flat(2)); // [1, 2, 3, 4, 5, 6] //使用 Infinity,可展开任意深度的嵌套数组 var arr4 = [1, 2, [3, 4, [5, 6, [7, 8, [9, 10]]]]]; console.log(arr4.flat(Infinity)); // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] //flat()方法会移除数组中的空项 var arr5 = [1, 2, , 4, 5]; console.log(arr5.flat()); // [1, 2, 4, 5]
flatMap:
flatMap()方法首先使用映射函数映射每个元素,然后将结果压缩成一个新数组。它与map()连着深度值为 1 的flat()几乎相同,但flatMap通常在合并成一种方法的效率稍微高一些;- 1)flatMap是先进行map操作,再做flat的操作;
- 2)flatMap中的flat相当于深度为1;
- 语法:flatMap(callbackFn) / flatMap(callbackFn, thisArg)
- 参数:
callbackFn可以生成一个新数组的函数,可以传入三个参数:currentValue当前正在数组中处理的元素;index可选的。数组中正在处理的当前元素的索引;array可选的。被调用的map数组;
thisArg可选。执行callbackFn函数时 使用的this值;// 箭头函数 flatMap((currentValue) => { } ) flatMap((currentValue, index) => { } ) flatMap((currentValue, index, array) => { } ) // 回调函数 flatMap(callbackFn) flatMap(callbackFn, thisArg) // 行内回调函数 flatMap(function(currentValue) { }) flatMap(function(currentValue, index) { }) flatMap(function(currentValue, index, array){ }) flatMap(function(currentValue, index, array) { }, thisArg)
- flatMap的应用场景
const message = ['Hello FlatMap', 'hello yzh', 'my name is yzh']; const mapMsg = message.map(item => item.split(' ')); console.log(mapMsg); //[[ 'Hello', 'FlatMap' ], [ 'hello', 'yzh' ], [ 'my', 'name', 'is', 'yzh' ]] const mapFMsg = mapMsg.flat(); console.log(mapFMsg); //['Hello', 'FlatMap', 'hello', 'yzh', 'my', 'name', 'is', 'yzh'] // 对每一项split之后的数组自动做一个降维 const words = message.flatMap(item => { return item.split(' ') }); console.log(words); //['Hello', 'FlatMap', 'hello', 'yzh', 'my', 'name', 'is', 'yzh']
二、Object.fromEntries
Object.fromEntries()方法把键值对列表转换为一个对象;- 语法:Object.fromEntries(iterable);
- 参数:
iterable类似Array、Map或者其它实现了可迭代协议的可迭代对象;const entries = new Map([ ['foo', 'bar'], ['baz', 42] ]); const obj = Object.fromEntries(entries); console.log(obj); //{ foo: "bar", baz: 42 } - 场景1:在ES8可以通过 Object.entries 将一个对象转换成 entries 数组,现在有一个entries了,要把它转换成对象;
const obj = { name: 'yzh', age: 18, height: 1.88 }; const objEnts = Object.entries(obj); console.log(objEnts); // [ [ 'name', 'yzh' ], [ 'age', 18 ], [ 'height', 1.88 ] ]- 1)循环
const newObj = {}; for (const [key, value] of objEnts) { newObj[key] = value; }; console.log(newObj); //{ name: 'yzh', age: 18, height: 1.88 } - 2)API
const newObj = Object.fromEntries(objEnts); console.log(newObj); //{ name: 'yzh', age: 18, height: 1.88 }
- 1)循环
- 场景2
const queryString = 'name=yzh&age=18&height=1.88'; const queryParams = new URLSearchParams(queryString); // console.log(queryParams); //{ 'name' => 'yzh', 'age' => '18', 'height' => '1.88' } for (const param of queryParams) { // console.log(param); /** * [ 'name', 'yzh' ] * [ 'age', '18' ] * [ 'height', '1.88' ] */ }; const paramsObj = Object.fromEntries(queryParams); console.log(paramsObj); //{ name: 'yzh', age: '18', height: '1.88' }
三、trimStart和trimEnd
trimStart()方法会删除字符串开头的空白字符。trimLeft()是此方法的别名;const msg = ' Hello world! '; console.log(msg); //' Hello world! '; console.log(msg.trimStart()); //'Hello world! ';
String.prototype.trimLeft.name === "trimStart";
trimEnd()方法会删除字符串末尾的空白字符。trimRight()是这个方法的别名;const msg = ' Hello world! '; console.log(msg); //' Hello world! '; console.log(msg.trimEnd()); //' Hello world!';
String.prototype.trimRight.name === "trimEnd";
补充:
trim()方法从字符串的两端清除空格,返回一个新的字符串,而不修改原始字符串。此空格是指所有的空白字符(空格、tab、不换行空格等);const msg = ' Hello world! '; console.log(msg); //' Hello world! '; console.log(msg.trim()); //'Hello world!';
四、Symbol description
五、try...catch
ES11
一、BigInt
- ES11之前 MAX_SAFE_INTEGER,大于这个数就不一定是安全的、准确的;
const maxInt = Number.MAX_SAFE_INTEGER; console.log(maxInt); // 9007199254740991 大于这个数表示的可能是不正确的; console.log(maxInt + 1); //9007199254740992 console.log(maxInt + 2); //9007199254740992 - ES11中,引入了新的数据类型BigInt,用于表示大的整数;
- BitInt的表示方法是在数值的后面加上n
const bigInt = 900719925474099100n; console.log(bigInt + 10n); //900719925474099110n const num = 100; console.log(bigInt + BigInt(num)); //900719925474099200n const smallNum = Number(bigInt); console.log(smallNum); //900719925474099100
- BitInt的表示方法是在数值的后面加上n
二、空值合并运算符
- 逻辑运算符,当左侧的操作数为
null或者undefined时,返回其右侧操作数,否则返回左侧操作数// ?? 只有值为undefined或null才赋值运算符后面 // const kname = 0; // const kname = ''; // const kname = null; const kname = undefined; const newName = kname ?? 'default value'; console.log(newName); //default value // 逻辑或缺陷 0或者空字符串 // const age = 0; const age = ''; const newAge = age || 'd v'; // console.log(newAge); //d v console.log(newAge); //d v
三、Optional Chaining(可选链)
- 可选链运算符(
?.)允许读取位于连接对象链深处的属性的值,而不必明确验证链中的每个引用是否有效; - 在引用为空 (
null或者undefined) 的情况下不会引起错误,该表达式短路返回值是undefined。与函数调用一起使用时,如果给定的函数不存在,则返回undefined;const data = { name: 'yzh', cat: { name: '年年' } }; const dogName = data.dog?.name; console.log(dogName) //undefined console.log(data.foo?.()) //undefined - 场景:
const info = { name: "yzh", friend: { girlFriend: { name: "hzy" } } } //1. //console.log(info.friend.girlFriend.name) if (info && info.friend && info.friend.girlFriend) { console.log(info.friend.girlFriend.name) } //2. console.log(info.friend?.girlFriend?.name)
让我们的代码在进行null和undefined判断时更加清晰和简洁;
四、Global This
- 在之前我们希望获取JavaScript环境的全局对象,不同的环境获取的方式是不一样的
- 在浏览器中可以通过this、window来获取;
- 在Node中我们需要通过global来获取;
- 在ES11中对获取全局对象进行了统一的规范:globalThis
//浏览器上 console.log(this); console.log(window); //Node中 console.log(global); console.log(globalThis);
五、for..in标准化
- 在ES11之前,虽然很多浏览器支持for...in来遍历对象类型,但是并没有被ECMA标准化;
- 在ES11中,对其进行了标准化,for...in是用于遍历对象的key的:
const info = { name: 'yzh', age: 18, height: 1.8 }; for (const key in info) { console.log(key); //name age height }
六、Promise.allSettled
七、Dynamic Import(动态引入)
- 关键字 import 可以像调用函数一样来动态的导入模块;
// index.js // 默认先加载foo.js模块 拿变量,在执行后面代码; // import { name, age, foo } from './foo.js' // console.log(name) // import函数返回的结果是一个Promise // 不阻塞后面代码运行; import("./foo.js").then(res => { console.log("res:", res.name) }) // foo.js const name = "yzh" const age = 18 const foo = "foo value" export { name, age, foo }
八、import meta
import.meta是一个给 JavaScript 模块暴露特定上下文的元数据属性的对象;- 它包含了这个模块的信息,比如说这个模块的 URL;
// index.html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <script src="./main.js" type="module"></script> </body> </html> // main.js // meta属性本身也是一个对象: { url: "当前模块所在的路径" } console.log(import.meta)
- 它包含了这个模块的信息,比如说这个模块的 URL;
ES12
一、FinalizationRegistry
- FinalizationRegistry 对象可以让你在对象被垃圾回收时请求一个回调:
- FinalizationRegistry 提供了这样的一种方法:当一个在注册表中注册的对象被回收时,请求在某个时间点上调用一个清理回调;
- 你可以通过调用register方法,注册任何你想要清理回调的对象,传入该对象和所含的值;
/** * FinalizationRegistry类 * 监听对象的销毁 */ const finalizaReg = new FinalizationRegistry(value => { console.log('注册在finalizaReg的对象,被销毁了!', value); }); let obj = { name: '1zh' }; let info = { name: 'ace' }; finalizaReg.register(obj, 'obj'); finalizaReg.register(info, 'info'); obj = null; info = null;
二、WeakRefs
- 如果我们默认将一个对象赋值给另外一个引用,那么这个引用是一个强引用:
- 如果我们希望是一个弱引用的话,可以使用WeakRef;
// ES12: WeakRef类; // WeakRef.prototype.deref: // > 如果原对象没有销毁, 那么可以获取到原对象; // > 如果原对象已经销毁, 那么获取到的是undefined; const finalRegistry = new FinalizationRegistry((value) => { console.log("注册在finalRegistry的对象, 某一个被销毁", value); }); let obj = { name: "1zh" }; //let info = obj; //强引用,obj不会被垃圾回收机制回收 let info = new WeakRef(obj); //弱引用 finalRegistry.register(obj, "obj"); obj = null; setTimeout(() => { //console.log(info..name); //强引用 1zh console.log(info.deref()?.name); //弱引用 undefined }, 10000); //确保走了finalRegistry,再获取info的信息
- 如果我们希望是一个弱引用的话,可以使用WeakRef;
三、逻辑赋值运算符
- 逻辑或赋值;
let message = ''; message ||= 'default value'; // 等同于 // message = message || 'defaut value'; console.log(message); //defalut value - 逻辑与赋值;
let obj = { name: '1zh' }; obj &&= obj.name; // 等同于 // obj = obj && obj.name; console.log(obj); //1zh - 逻辑空赋值;
let msg = undefined; msg ??= 'd v'; // 等同于 // msg = msg ?? 'd v'; console.log(msg); //d v
四、Numeric Separator(数字分隔符)
_不会改变数字的真实值,只是为了方便代码阅读;let num = 1_000_000; console.log(num); //1000000
注意事项:
1)不能连着两个下划线(如100__000)
2)不能出现在数字末尾(如100_)
3)不能出现在以0开头的数字0后面(如0_1)
五、Promise.any
六、String.prototype.replaceAll()
replaceAll(pattern, replacement)方法返回一个新字符串,新字符串所有满足pattern的部分都已被replacement替换;pattern可以是一个字符串或一个RegExp;replacement可以是一个字符串或一个在每次匹配被调用的函数;let msg = '这是一段很长很长的描述x所讲的内容是....'; //这是一段很长很长的描述,所讲的内容是.... console.log(msg.replaceAll('x', ',')); let p = 'aaabbbccc'; const regex = /b/g; console.log(p.replaceAll(regex, '.')); //aaa...ccc
当使用一个
regex时,必须设置全局(“g”)标志, 否则报错 TypeError:“使用非全局RegExp参数调用了replaceAll”;错误示例如:
const regex = /b/; p.replaceAll(regex, '.');