1.ES6新增哪些内容
letconst变量声明- 模板字符串 ``
- 变量的结构赋值
- Set Map数据结构
- Promise
- async await 语法糖
- Symbol 数据类型
- Proxy
- Reflect
- Class 类
- 箭头函数
- 剩余参数
- 函数参数默认值
- 严格相等
- ES2020 BigInt 大整数 基本数据类型
2. let const var 的区别
- 作用域以及变量提升 let const是块级作用域,var是函数作用域
// let const 的块级作用域
{
let l1 = 1;
const s1 = 1
console.log(l1); // 1
console.log(s1); // 1
}
console.log(l1,s1); // l1 is not defined
// var 的作用域影响,var在全局声明的变量,会被挂载到window上
var a = 1;
let b = 1;
const c = 1;
console.log(window.a, a); // 1 , 1
console.log(window.b, b); // undefind , 1
console.log(window.c, c); // undefind , 1
if (true) {
var d = 1;
let e = 1;
const f = 1;
console.log(d, e, f); // 1 , 1 , 1
}
console.log(window.d); //1
console.log(window.e); // undefind
console.log(e); // e is not defind
// var 的函数作用域
var foo = 'aaa'
function change(){
foo = 'ccc'
var foo = 'bbb'
console.log(foo); // bbb
// 函数内的代码相当于 var foo ; foo = 'ccc'; foo = 'bbb';
}
change()
console.log(foo); // aaa var在函数内声明的变量不会影响到全局
- 值的修改以及重复声明
var,let声明的变量的值可以任意改变,const声明的基本数据类型的值不能改变,引用数据类型的值可以修改其属性的值。
var可以重复声明变量,const let 不对变量重复声明
// 暂时性死区
var tmp = 123;
if (true) {
tmp = 456; // Cannot access 'tmp' before initialization 初始化前无法访问当前变量
let tmp;
}
- 变量提升 var创建的变量会被提升到该作用域的最顶部。const,let 不会
console.log(foo) // undefined
var foo = 2
上面代码相当于
var foo
console.log(foo)
foo = 2
- 使用场景
- 一般的let 替换调var
- 对于常数,匿名函数 箭头函数的时候使用const
3. 数值的扩展
3-1 Math对象的一些比较实用的小方法
Math.trunc(): 去除一个数的小数部分Math.sign(): 判断一个数是正数、负数还是零Math.cbrt(): 返回一个数的立方根
3-2 BigInt 数据类型
为了与number区别。BingInt类型的数据必须加后缀n
const a = 123n;
const b = 123;
console.log(Number(a) === Number(b)); // true
console.log(a == b); // true
console.log(a === b); // false
console.log(a === BigInt(b)); // true
console.log(typeof a); // bigint
- bigint不能与普通数值进行混合运算
4. 函数的扩展
4-1 关于函数length属性
函数的length默认情况下返回不具有默认值的参数的个数,当指定了参数的默认值的时候,函数的length属性失真。
const foo = (a, b) => {
console.log(a, b);
};
const bar = (a = 1, b) => {
console.log(a, b);
};
const fn = (a, b = 1) => {
console.log(a, b);
};
console.log(foo.length); // 2
console.log(bar.length); // 0
console.log(fn.length); // 1
4-2 有关函数的作用域
var x = 1;
function f(x, y = x) {
console.log(y);
}
f(2); // 2
// 在这里相当于
// function f( let x ; y = x){}
var x = 1;
function f(y = x) { // let y = x;
console.log(y);
}
f(); // 1
// 这样会报错
var x = 1;
function foo(x = x) { // let x = x; x is not defined
// ...
}
foo(); // ReferenceError: x is not defined
深入一下函数的作用域
var x = 1;
function foo(x,y = function () { x = 2;}) {
var x = 3;
y() // y函数内的x的作用域是新生成的x ,与foo函数内部的x不同一个作用域
console.log(x);
}
foo(); // 3
console.log(x); //1
4-3 箭头函数与普通函数的区别
(1)箭头函数没有自己的this对象。
(2)不可以当作构造函数,也就是说,不可以对箭头函数使用new命令,否则会抛出一个错误。
(3)不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。
(4)不可以使用yield命令,因此箭头函数不能用作 Generator 函数。
箭头函数的作用域: 指向距离最近的父级的作用域
window.s2 = 0
function Timer() {
this.s1 = 0;
this.s2 = 0;
// 箭头函数
setInterval(() => this.s1++, 1000);
// 普通函数
setInterval(function () {
this.s2++;
}, 1000);
}
var timer = new Timer();
setTimeout(() => console.log("s1: ", timer.s1), 3100);
setTimeout(() => console.log("s2: ", timer.s2,window.s2), 3100);
// timer.s1 : 3
// timer.s2 : 0
// window.s2: 3
function foo() {
return () => {
return () => {
return () => {
console.log('id:', this.id); // this 执行foo的this
};
};
};
}
var f = foo.call({id: 1});
var t1 = f.call({id: 2})()(); // id: 1
var t2 = f().call({id: 3})(); // id: 1
由于箭头函数没有自己的this,所以当然也就不能用call()、apply()、bind()这些方法去改变this的指向
globalThis.s = 21;
const obj = {
s: 42,
m: () => console.log(this.s)
};
obj.m() // 21
上面例子中,obj.m()使用箭头函数定义。JavaScript 引擎的处理方法是,先在全局空间生成这个箭头函数,然后赋值给obj.m,这导致箭头函数内部的this指向全局对象,所以obj.m()输出的是全局空间的21,而不是对象内部的42。上面的代码实际上等同于下面的代码。
globalThis.s = 21;
globalThis.m = () => console.log(this.s);
const obj = {
s: 42,
m: globalThis.m
};
obj.m() // 21
5.数组的扩展
5-1 数组扩展运算符常用技巧
// 数组的复制
const arr1 = [1,2,3];
const arr2 = [...arr1];
// 数组的合并
const arr3 = [ ...arr1 , ...arr2]
5-2 Array.from()
// 创建一个指定长度的,内容为0的数组
Array.from({length:3},x=>0); // [0,0,0]
5-3 Array.find()/findIndex()
const temp = [1, 2, -1, 4];
const res = temp.find((cur, index, array) => {
// 相当于高阶函数中的参数 cur 当前值,index索引,array数组
console.log(cur, index, array);
return cur < 0;
});
console.log(res);
// find返回满足条件的第一个值,findIndex返回满足条件的第一个值的索引
5-4 Array.includes()
在以前判断一个值是否在一个数组内的方法是indexOf(),indexOf的缺点:
- 不够语义化,判断一个值是否存在需要判断是否等于-1
- 内部使用严格相等运算符,不能判断
NaN
5-5 数组的拍平 flat() / flatMap()
flat用于嵌套的数组进行拉平,默认拉平一层,参数为一个整形,代表拉平的层数
flatMap 对原嵌套数组的每个元素成员先执行一个函数,然后再进行flat,flatMap只能拍平一层嵌套数组。
两者都会返回一个新的数组,不会影响原来的数组
const temp = [1, [2, [3]]];
console.log(temp.flat()); // [1,2,Array(1)] 默认情况拍平一层
const faTemp = temp.flatMap((x) => [x, x * 2]);
console.log(faTemp); // [1, 2, Array(2), NaN]
6. 对象的扩展
6-1 == & === 的缺点
NaN === NaN // false
NaN == NaN // false
+0 == -0 // true
+0 === -0 // true
从而引出Object.is(),用来判断两个值是否相等。
6-2 Object.assign()
对象的浅拷贝
const target = { a: 1, b: 1 };
const source1 = { b: 2, c: 2 };
const source2 = { c: 3 };
Object.assign(target, source1, source2);
target // {a:1, b:2, c:3}
如果考虑到原型上的属性的话
const clone = (origin)=>{
const originProto = Object.getPrototypeOf(origin)
return Object.assign(Object.create(originProto),origin)
// 浅拷贝
// return Object.assign(origin)
}
7. 运算符的扩展
7-1 链运算符的扩展
// 错误的写法
const firstName = message.body.user.firstName || "default";
// 链运算符
const firrtName = message?.body?.user?.firstName || "default";
// 实用场景
// 1. 表单的验证(对于方法的判断)
if (From.checkVal?.() === false) {
// 这里判断checkVal是否是个函数
// 验证失败;
}
链式调用的注意点:
- 短路机制 (当不满足条件的时候不再往下执行)
- 括号的影响(对括号外没有影响)
- 以下场合实用会报错
// 构造函数
new a?.()
new a?.b()
// 链判断运算符的右侧有模板字符串
a?.`{b}`
a?.b`{c}`
// 链判断运算符的左侧是 super
super?.()
super?.foo
// 链运算符用于赋值运算符左侧
a?.b = c
- 右侧不得有十进制整数
7-2 null 判断运算符
const message = null;
const test = message ?? true;
// test = message || true
console.log(test);
8 Set Map数据结构
8.1 Set 数据结构
set类似于数组,但是成员是唯一的,没有重复的值。
8.1.1 数组的去重
[...new Set(array)]
// 字符串去重
[...new Set('abbbcc')].join('')
8.1.2 Set的属性和方法
属性:
size:返回实例的成员总数.
方法:
add(value): 添加某个值,返回Set结构本身
delete(value): 删除某个值,返回一个布尔值,表示是否删除成功
has(value):返回一个布尔值,判断该值是否为Set的成员
clear():清除所有成员,无返回值
8.1.3 Set的遍历
(1)keys(),values(),entries()
keys(),values()返回的是Set的值。
(2)forEach()
(3)for(let item of [...set])
8.2 weakSet 数据结构
WeakSet 的成员只能是对象,不能是其他类型的值。
WeakSet没有size属性,WeakSet不能遍历,但是WeakSet的方法于Set数据结构的方法相同。
8.3 Map数据结构
8.3.1 定义及初始化
Object 结构提供了“字符串—值”的对应,Map 结构提供了“值—值”的对应
const items = [
[name,'chang']
]
const test = new Map(items)
console.log(test.get(name)); // chang
// 原理
items.forEach((key,value)=>{
map.set(key,value)
})
8.3.2 Map数据类型转换
(1) Map转为数组
const map = new Map([[name,chang]])
const arr = [...map]
(2) 数组转为map
const map = new Map([[name,chang]])
(3) Map转为对象
function strMapToObj(strMap) {
let obj = Object.create(null);
for (let [k,v] of strMap) {
obj[k] = v;
}
return obj;
}
const myMap = new Map()
.set('yes', true)
.set('no', false);
strMapToObj(myMap)
// { yes: true, no: false }
(4) 对象转为Map
let obj = {"a":1, "b":2};
let map = new Map(Object.entries(obj));
8.4 WeakMap 数据结构
WeakMap与Map的区别有两点。
-
WeakMap只接受对象作为键名(null除外),不接受其他类型的值作为键名。 -
WeakMap的键名所指向的对象,不计入垃圾回收机制。(WeakMap键所指的值不会被回收)
9. Proxy代理
Proxy 用于修改某些操作的默认行为。
9.1 对基本对象的拦截
const handler = {
get: function (target, key, receiver) {
// target 目标对象 , key 键 , receiver 接收者
console.log("get", target, key, receiver);
// return target[key];
// 暂时还没能理解这个Reflect
// return Reflect.get(target, key, receiver);
},
set: function (target, key, value) {
console.log("set", target, key, value);
target[key] = value;
},
};
// 一个原生对象
const obj = {
name: "chang",
};
// 给原生对象创建一层代理
const proxy = new Proxy(obj, handler);
// 对代理的取值
console.log(proxy.name);
// 对代理属性的变更
proxy.age = 20;
// 获取代理的新属性
console.log(proxy.age);
9.2 对函数以及构造函数的拦截
const handler = {
get: function (target, key) {
console.log("get:", target, key);
return target[key];
},
apply: function (target, thisBinding, args) {
// 方法调用拦截
console.log("apply", target, thisBinding, args);
return target(...args);
},
construct: function (target, args) {
// target 构造函数, args 参数
console.log("方法构造拦截",target,args);
return new target(...args);
},
};
const sums = (...args) => {
return args.reduce((pre, cur) => {
return pre + cur;
}, 0);
};
const fproxy = new Proxy(sums, handler);
console.log(fproxy(1, 2, 3)); // 触发apply 拦截 6
console.log(fproxy.name); // 触发 get 拦截 sums
// 对构造函数的拦截
// 创建一个地址的构造函数
class address {
constructor(address) {
["province", "city", "county"].forEach((item) => {
this[item] = address[item];
});
}
}
// 建造一个符合address构造函数的参数对象
const chang = {
province: "xx省",
city: "xx市",
county: "xx县",
};
// 对address构造函数设置代理
const address_proxy = new Proxy(address, handler);
// 初始化一个实例
const chang_address = new address_proxy(chang);
console.log('chang',chang_address); // chang address {province: "xx省", city: "xx市", county: "xx县"}
9.3 应用实例
9.3.1 数组负数索引
const createArray = (...args) => {
const handler = {
get: function (target, key) {
const index = Number(key);
if (index < 0) {
return target[target.length + index];
} else {
return target[index];
}
},
};
return new Proxy(args, handler);
};
const arr = createArray(1, 2, 3);
console.log(arr[-2]); //2
console.log(arr[2]); //3
10 Reflect
10.1 Reflect的设计
- 将
Object对象的一些明显属于语言内部的方法(比如Object.defineProperty),放到Reflect对象上。 - 修改某些
Object方法的返回结果。 - 让
Object操作都变成函数行为。 Reflect对象的方法与Proxy对象的方法一一对应,只要是Proxy对象的方法,就能在Reflect对象上找到对应的方法。
自我理解:Reflect就是proxy的简写,Reflect与Proxy对象的方法一一对应。有了Reflect不必再new Proxy(),这样复杂,直接调用Reflect的方法就可以。
10.3 Reflect 的静态方法
Reflect对象一共有 13 个静态方法。
- Reflect.apply(target, thisArg, args)
- Reflect.construct(target, args)
- Reflect.get(target, name, receiver)
- Reflect.set(target, name, value, receiver)
- Reflect.defineProperty(target, name, desc)
- Reflect.deleteProperty(target, name)
- Reflect.has(target, name)
- Reflect.ownKeys(target)
- Reflect.isExtensible(target)
- Reflect.preventExtensions(target)
- Reflect.getOwnPropertyDescriptor(target, name)
- Reflect.getPrototypeOf(target)
- Reflect.setPrototypeOf(target, prototype)
Reflect.apply
const ages = [11, 33, 12, 54, 18, 96];
// 旧写法
const youngest = Math.min.apply(Math, ages);
const oldest = Math.max.apply(Math, ages);
const type = Object.prototype.toString.call(youngest);
// 新写法
const youngest = Reflect.apply(Math.min, Math, ages);
const oldest = Reflect.apply(Math.max, Math, ages);
const type = Reflect.apply(Object.prototype.toString, youngest, []);
10.3.1 Reflect.ownKeys()
var myObject = {
foo: 1,
bar: 2,
[Symbol.for('baz')]: 3,
[Symbol.for('bing')]: 4,
};
// 旧写法
Object.getOwnPropertyNames(myObject)
// ['foo', 'bar']
Object.getOwnPropertySymbols(myObject)
//[Symbol(baz), Symbol(bing)]
// 新写法
Reflect.ownKeys(myObject)
// ['foo', 'bar', Symbol(baz), Symbol(bing)]
11. Promise
11.1 方法
-
Promise.prototype.then()
-
Promise.prototype.catch()
-
Promise.prototype.finally()
-
Promise.all()
将多个Promise实例,包装成一个新的Promise实例。 入参为一个数组,返回一个数组。
const p = Promise.all([p1, p2, p3]);
// 1. 只有p1 p2 p3的状态都变为fulfilled的时候,p的状态才会变为fulfilled.
// 2. 只要其中一个rejected,p的状态就会变为rejected.
- Promise.race()
race:比赛。只要其中的一个实例率先改变状态,那么p的状态就跟着改变。
- Promise.any()
只要其中的一个实例变为fulfilled状态,p的状态就会变为fulfilled状态;
如果所有参数实例都变为rejected状态,包装实例就会编程rejected状态;
12 Iterator
const obj = {
[Symbol.iterator]: function () {
return {
next: function () {
return {
value: 1,
done: true,
};
},
};
},
};
console.log(obj); // {Symbol(Symbol.iterator): ƒ}
const iter = obj[Symbol.iterator]()
console.log(iter.next()); // {value: 1, done: true}
原生具备iterator接口的数据结构如下:
String : 基本类型 String
Array:引用类型 数组
Set: Set
Map:Map
arguments: 普通函数的参数
NodeList:节点列表
typedArray:类型化数组
// TypedArray 关键字需要替换为底部列出的构造函数。
new TypedArray(); // ES2017中新增
new TypedArray(length);
new TypedArray(typedArray);
new TypedArray(object);
// TypedArray 指的是以下的其中之一:
Int8Array();
Uint8Array();
Uint8ClampedArray();
Int16Array();
Uint16Array();
Int32Array();
Uint32Array();
Float32Array();
Float64Array();
12.1 结合generator函数使用
let obj = {
*[Symbol.iterator](){
yield '1'
yield '2'
yield '3'
yield '4'
yield '5'
}
};
for (const item of obj) {
console.log(item);
}
12.2 将对象构造成可遍历的对象
let obj = {
name: "change",
age: 20,
};
function* entire(obj) {
for (let key of Object.keys(obj)) {
yield [key, obj[key]];
}
}
for (const [key, value] of entire(obj)) {
console.log(key, value);
}
12.3 for...in 遍历数组的缺点
- 数组的键名是数字,但是
for...in循环是以字符串作为键名“0”、“1”、“2”等等。 for...in循环不仅遍历数字键名,还会遍历手动添加的其他键,甚至包括原型链上的键。- 某些情况下,
for...in循环会以任意顺序遍历键名。
13. Generator生成器函数
13.1 斐波那契数列
function* fibonacci() {
let [pre, cur] = [0, 1];
for(;;){
yield cur;
[pre, cur] = [cur, pre + cur];
}
}
for (const item of fibonacci()) {
if (item > 100) break;
console.log(item);
}
13.2 Generator 异步处理应用
ES6发布以前的异步处理方法: 回调函数、事件监听、发布/订阅、Promise对象
14. Async
async 其实就是generator的语法糖
async 在 generator函数上的改进:
-
自带执行,与普通函数一样,一行一行的执行
-
更好的语义
-
返回值是Promise,相比于更加方便。Generator返回值是iterator
14.1 延时输出
asyncPrint = async (message, time) => {
await new Promise((resolve, reject) => {
setTimeout(resolve, time);
});
console.log(message);
};
asyncPrint("hello world", 5000);
14.2 使用形式
// 函数声明
async function foo() {}
// 函数表达式
const foo = async function () {};
// 对象的方法
let obj = { async foo() {} };
obj.foo().then(...)
// Class 的方法
class Storage {
constructor() {
this.cachePromise = caches.open('avatars');
}
async getAvatar(name) {
const cache = await this.cachePromise;
return cache.match(`/avatars/${name}.jpg`);
}
}
const storage = new Storage();
storage.getAvatar('jake').then(…);
// 箭头函数
const foo = async () => {};
14.3 await
await后面是一个Promise对象,返回该对象的结果,如果不是Promise对象,就直接返回对应的值。
- 一个休眠的方法
// 休眠的方法
const sleep = (time) => {
return new Promise((resolve) => setTimeout(resolve, time));
};
// 间隔输出
const intervalOutPut = async (time) => {
for (let i = 0; i < 5; i++) {
console.log(i);
await sleep(time)
}
};
intervalOutPut(1000)
14.4 使用的注意点
-
最好把await命令放在try...catch代码块中
-
多个await命令后面的异步操作,如果不存在继发关系,最好让他们同时触发
let [foo, bar] = await Promise.all([getFoo(), getBar()]);
-
await命令只能用在async函数中,在普通函数中会报错
-
async函数可以保留运行堆栈
const a = async () => {
await b();
c();
};
上面代码中,b()运行的时候,a()是暂停执行,上下文环境都保存着。一旦b()或c()报错,错误堆栈将包括a()。
15. Class 类
15.1 一个基本的Class定义
let person = new class {
constructor(name) {
this.name = name;
}
sayName() {
console.log(this.name);
}
}('张三');
person.sayName(); // "张三"
15.2 使用时的注意点
-
严格模式
-
不存在提升
-
name属性
-
Generator 方法
class Foo {
constructor(...args) {
this.args = args;
}
* [Symbol.iterator]() {
for (let arg of this.args) {
yield arg;
}
}
}
for (let x of new Foo('hello', 'world')) {
console.log(x);
}
// hello
// world
- this指向(默认指向类的实例)
15.3 静态属性和方法
加上static关键字,就表示该方法不会呗继承,只能通过类直接调用。
静态方法里的this指向的是类,而不是实例,所以在静态方法里不能使用到类内的属性和方法。
class Foo {
static prop = 1; // 定义一个prop静态属性(本身的属性,不绑定到this上)
static classMethod() {
return "hello";
}
}
Foo.classMethod(); // 'hello'
console.log(Foo.prop); // 1
var foo = new Foo();
console.log(foo.prop); // undefined
foo.classMethod();
// Uncaught TypeError: foo.classMethod is not a function
15.4 私有属性和方法
class Foo {
#message = "message"; // 私有属性
// 公共方法
classMethod() {
this._say(); // 调用私有方法
console.log(this.#message); // 打印私有属性
return "hello";
}
// 私有方法
_say = function () {
console.log("say");
};
}
// Foo._say();
// Foo._say is not a function
// console.log(Foo.#message);
// Uncaught SyntaxError: Private field '#message' must be declared in an enclosing class
// 创建一个类的实例
var foo = new Foo();
// 方法调用
foo.classMethod(); // say \n message
15.5 new.target()
这个属性是可以用确定构造函数是怎么调用的。
function Person(name) {
if (new.target === Person) {
this.name = name;
} else {
throw new Error('必须使用 new 命令生成实例');
}
}
15.6 类的继承以及super关键字
只有在使用super关键字后才可以使用this。super
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
}
class ColorPoint extends Point {
constructor(x, y, color) {
this.color = color; // ReferenceError
super(x, y);
this.color = color; // 正确
}
}