1、let、const
let
、var
、const
之间的变量
声明方式 | 变量提升 | 暂时性死区 | 重复声明 | 初始值 | 作用域 |
---|---|---|---|---|---|
var | 存在 | 不存在 | 允许 | 不需要 | 除块级 |
let | 存在 | 存在 | 不允许 | 不需要 | 块级 |
const | 存在 | 存在 | 不允许 | 需要 | 块级 |
1.1、变量提升
变量提升指的是将变量和函数声明提升到它们所在作用域的顶部
。这意味着在执行代码之前,JavaScript
引擎会先处理变量和函数的声明,使它们在声明之前就可以被访问。
// 变量提升
console.log(a); //undefined
var a = 1;
console.log(b); //ReferenceError: b is not defined
let b = 1;
console.log(c); //ReferenceError: c is not defined
const c = 1;
let
和 const
不能在声明之前被访问是由于它们引入了暂时性死区的概念。在这个区域内,变量虽然已经被提升到作用域的顶部,但在初始化之前访问它们会导致运行时错误。
在声明阶段,let
和 const
声明的变量被提升到作用域的顶部,但它们在初始化之前处于暂时性死区。在这个区域内,尝试访问变量会导致 ReferenceError
,即使在声明点之前有变量名的提升。
1.2、暂时性死区
如果在代码块中存在 let
或 const
命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错。
var tmp = 123;
if (true) {
tmp = 'abc';//报错,ReferenceError: tmp is not defined
let tmp;
}
这段代码的原意是在 if
内定义外部的 tmp
为 'abc'。
但现实是,存在全局变量 tmp
,但是块级作用域内 let
又声明了一个 tmp
变量,导致后者被绑定在这个块级作用域中,所以在 let
声明变量前,对 tmp
赋值就报错了。
1.3、重复声明
指在相同作用域内,重复声明同一个变量。
let
和 const
命令声明的变量不允许重复声明:
function func(){
let a = 10;
const PI = 3.1415;
var a = 1;// 报错,Uncaught SyntaxError: Identifier 'a' has already been declared
var PI = 3;// 报错,Uncaught SyntaxError: Identifier 'PI' has already been declared
}
// 当调用func()时报错,Uncaught SyntaxError: Identifier 'a' has already been declared
function func(){
let a = 10;
const PI = 3.1415;
let a = 1;// 报错,Uncaught SyntaxError: Identifier 'a' has already been declared
const PI = 3;// 报错,Uncaught SyntaxError: Identifier 'PI' has already been declared
}
var 是允许重复定义的,而这又会给我们带来什么麻烦呢?
var i = 10;
for(var i = 0;i < 5;i++){
console.log(i);
}
console.log(i);// 输出 5
对于学习过静态(类型)语言的人知道,这段代码要是替换成 c 语言或其他静态语言,输出的结果应该是 10。然而对于 javascript 来说,这段代码的输出结果是 5。因为 var
命令没有块级作用域,所以 for
循环括号内的变量 i
会覆盖外层 i
,而且 var
允许重复声明,所以这段代码中 i
被声明了两次,i
的最终结果就被 for
循环的 i
给覆盖了。
1.4、初始值
由于 const
声明的是只读的常量,一旦声明,就必须立即初始化,声明之后值不能改变。(注意:不可改变指的是在内存中的地址不可改变)
const PI = 3.1415;
PI = 3;// 报错,Uncaught TypeError: Assignment to constant variable.
//const 的本质: const 定义的变量并非常量,并非不可变,
// 它定义了一个常量引用一个值。
// 不可变的本质是:在内存中的地址是不能改变的
// 使用 const 定义的对象或者数组,其实是可变的。因为内存中地址不变
const b ={}
b.m=123
console.log(b.m) //123
b.m=456
console.log(b.m) //456
b.o={
k:789
}
b.o={
k:125
}
console.log(b.o) //{ k: 125 }
b ={} //Assignment to constant variable. 引用值不能改变
1.5、作用域
在 ES5 中只有全局作用域和函数作用域,没有块级作用域,这带来很多不合理的场景。
第一种场景,内层变量可能会覆盖外层变量。
var tmp = new Date();//处于全局作用域
function f() {
console.log(tmp);//处于函数作用域
if (false) {
var tmp = 'hello world';
}
}
f(); // undefined
上面代码的原意是,if
代码块的外部使用外层的tmp
变量,内部使用内层的tmp
变量。
然而现实是在这段代码中,function
内部的2个 tmp
变量处在同一函数作用域,由于变量提升,导致函数作用域中的 tmp
覆盖全局作用域中的 tmp
,所以,f()输出结果为undefined
。
第二种场景,用来计数的循环变量泄露为全局变量(前面在重复声明中提到的):
var i = 10;
for(var i = 0;i < 5;i++){
console.log(i);
}
console.log(i);// 输出 5
上面代码中,变量i只用来控制循环,但是循环结束后,它并没有消失,泄露成了全局变量。
2、解构赋值
他是一种针对数组或者对象进行模式匹配,然后对其中的变量进行赋值。
在代码书写上简洁且易读,语义更加清晰明了;也方便了复杂对象中数据字段获取。
var person = {
name:'jerry',
age:22
}
const {name ,age } = person
console.log(name) //jerry
console.log(age) //22
console.log(a) //undefined
console.log(b) //undefined
3、symbol
一种新的原始数据类型 Symbol
,表示独一无二的值,最大的用法是用来定义对象的唯一属性名。
Symbol
函数栈不能用 new
命令,因为 Symbol
是原始数据类型,不是对象。可以接受一个字符串作为参数,为新创建的 Symbol
提供描述,用来显示在控制台或者作为字符串的时候使用,便于区分。
let sy = Symbol("KK");
console.log(sy); // Symbol(KK)
typeof(sy); // "symbol"
// 相同参数 Symbol() 返回的值不相等
let sy1 = Symbol("kk");
sy === sy1; // false
3.1、使用场景
作为属性名
由于每一个 Symbol 的值都是不相等的,所以 Symbol 作为对象的属性名,可以保证属性不重名。
写法:
let sy = Symbol("key1");
// 写法1
let syObject = {};
syObject[sy] = "kk";
console.log(syObject); // {Symbol(key1): "kk"}
// 写法2
let syObject = {
[sy]: "kk"
};
console.log(syObject); // {Symbol(key1): "kk"}
// 写法3
let syObject = {};
Object.defineProperty(syObject, sy, {value: "kk"});
console.log(syObject); // {Symbol(key1): "kk"}
Symbol 作为对象属性名时不能用.运算符,要用方括号。因为.运算符后面是字符串,所以取到的是字符串 sy 属性,而不是 Symbol 值 sy 属性。
Symbol 值作为属性名时,该属性是公有属性不是私有属性,可以在类的外部访问。但是不会出现在 for...in 、 for...of 的循环中,也不会被 Object.keys() 、 Object.getOwnPropertyNames() 返回。如果要读取到一个对象的 Symbol 属性,可以通过 Object.getOwnPropertySymbols() 和 Reflect.ownKeys() 取到。
定义常量
使用 Symbol 定义常量,这样就可以保证这一组常量的值都不相等。
const RED = Symbol('Red');
const BLUE = Symbol('Blue');
function getColor(color) {
switch (color) {
case RED:
return 'This is red';
case BLUE:
return 'This is blue';
default:
return 'Unknown color';
}
}
console.log(getColor(RED)); // 'This is red'
私有属性和方法的模拟
使用 Symbol
可以模拟实现私有属性和方法,因为在外部无法访问使用 Symbol
创建的属性。
const privateMethod = Symbol('privateMethod');
class MyClass {
constructor() {
this[privateMethod]();
}
[privateMethod]() {
console.log('This is a private method');
}
}
const instance = new MyClass(); // 输出 'This is a private method'
// instance[privateMethod](); // 无法直接访问私有方法
3.2、Symbol.for()
Symbol.for() 类似单例模式,首先会在全局搜索被登记的 Symbol 中是否有该字符串参数作为名称的 Symbol 值,如果有即返回该 Symbol 值,若没有则新建并返回一个以该字符串参数为名称的 Symbol 值,并登记在全局环境中供搜索。
let yellow = Symbol("Yellow");
let yellow1 = Symbol.for("Yellow");
yellow === yellow1; // false
let yellow2 = Symbol.for("Yellow");
yellow1 === yellow2; // true
3.3、Symbol.keyFor()
Symbol.keyFor() 返回一个已登记的 Symbol 类型值的 key ,用来检测该字符串参数作为名称的 Symbol 值是否已被登记。
let yellow1 = Symbol.for("Yellow");
Symbol.keyFor(yellow1); // "Yellow"
4、Map
Map 对象保存键值对。任何值(对象或者原始值) 都可以作为一个键或一个值。
4.1、Map 和 Object 的区别
- 一个 Object 的键只能是字符串或者 Symbols(使用其他类型的数据会转化成字符串),但一个 Map 的键可以是任意值。
- Map 中的键值是有序的(FIFO 原则),而添加到对象中的键则不是。
- Map 的键值对个数可以从 size 属性获取,而 Object 的键值对个数只能手动计算。
- Object 有原型链,可能会包含从原型链继承来的属性。Map 没有原型链,不包含从原型链继承来的属性。
var myMap = new Map();
var keyString = "a string";
myMap.set(keyString, "和键'a string'关联的值");
myMap.get(keyString); // "和键'a string'关联的值"
myMap.get("a string"); // "和键'a string'关联的值"
// 因为 keyString === 'a string'
4.2、Map 的迭代
for...of
var myMap = new Map();
myMap.set('0key', "zero");
myMap.set('1key', "one");
for (let [key, value] of myMap) {
console.log(key + " = " + value);
}
/**
* 0key = zero
* 1key = one
*/
for (let item of myMap) {
console.log('item', item);
}
/**
* item [ '0key', 'zero' ]
* item [ '1key', 'one' ]
*/
forEach()
var myMap = new Map();
myMap.set('0key', "zero");
myMap.set('1key', "one");
myMap.forEach(function(value, key) {
console.log(key + " = " + value);
})
// 0key = zero
// 1key = one
4.3、Map 对象的操作
Map 与 Array的转换
var kvArray = [["key1", "value1"], ["key2", "value2"]];
// Map 构造函数可以将一个 二维 键值对数组转换成一个 Map 对象
var myMap = new Map(kvArray);
// 使用 Array.from 函数可以将一个 Map 对象转换成一个二维键值对数组
var outArray = Array.from(myMap);
console.log(outArray) //[ [ 'key1', 'value1' ], [ 'key2', 'value2' ] ]
console.log(kvArray) //[ [ 'key1', 'value1' ], [ 'key2', 'value2' ] ]
console.log(myMap) //Map { 'key1' => 'value1', 'key2' => 'value2' }
Map 的克隆
var myMap1 = new Map([["key1", "value1"], ["key2", "value2"]]);
var myMap2 = new Map(myMap1);
console.log(myMap1 === myMap2); // false。 Map 对象构造函数生成实例,迭代出新的对象。
Map 的合并
var first = new Map([[1, 'one'], [2, 'two'], [3, 'three'],]);
var second = new Map([[1, 'uno'], [2, 'dos']]);
// 合并两个 Map 对象时,如果有重复的键值,则后面的会覆盖前面的,对应值即 uno,dos, three
var merged = new Map([...first, ...second]);
console.log(first) //Map { 1 => 'one', 2 => 'two', 3 => 'three' }
console.log(second) //Map { 1 => 'uno', 2 => 'dos' }
console.log(merged) //Map { 1 => 'uno', 2 => 'dos', 3 => 'three' }
4.4、WeakMap
WeakMap结构与Map结构类似,也是用于生成键值对的集合。 不同之处在于:
-
键:
Map
的键可以是任意数据类型,键的引用是强引用,即使键没有其他引用,它们仍然会存在于Map
中。WeakMap
的键必须是对象,而且,键是弱引用,当键对象没有其他引用时,垃圾回收器可以回收它们,这可能导致键值对从WeakMap
中移除。
-
遍历和迭代:
Map
提供了丰富的遍历方法,包括forEach
、entries
、keys
和values
。WeakMap
不提供直接的遍历方法,因为键是弱引用,遍历可能会导致一些不确定的结果。
-
性能影响:
Map
的键是强引用,键值对不会被轻易回收,因此在某些场景下,可能对内存占用有一些影响。WeakMap
的键是弱引用,当键对象没有其他引用时,垃圾回收器可以回收它们,有助于减小内存占用。
5、Set
Set 对象允许你存储任何类型的唯一值,无论是原始值或者是对象引用。
5.1、Set 中的特殊值
Set 对象存储的值总是唯一的,所以需要判断两个值是否恒等。有几个特殊值需要特殊对待:
- +0 与 -0 在存储判断唯一性的时候是恒等的,所以不重复;
- undefined 与 undefined 是恒等的,所以不重复;
- NaN 与 NaN 是不恒等的,但是在 Set 中只能存一个,不重复。
let mySet = new Set();
console.log(mySet.add(1)); // Set {1}
console.log(mySet.add(5)); // Set {1, 5}
console.log(mySet.add(5)); // Set {1, 5} 体现了值的唯一性
console.log(mySet.add("some text")); // Set {1, 5, "some text"} 体现了类型的多样性
var o = {a: 1, b: 2};
console.log(mySet.add(o)); //Set { 1, 5, 'some text', { a: 1, b: 2 } }
console.log(mySet.add({a: 1, b: 2})); //Set { 1, 5, 'some text', { a: 1, b: 2 }, { a: 1, b: 2 } }
// 这里体现了对象之间引用不同不恒等,即使值相同,Set 也能存储
5.2、类型转换
// Array 转 Set
var mySet = new Set(["value1", "value2", "value3"]);
console.log(mySet) // Set { 'value1', 'value2', 'value3' }
// 用...操作符,将 Set 转 Array
var myArray = [...mySet];
console.log(myArray) //[ 'value1', 'value2', 'value3' ]
// String 转 Set
var mySet = new Set('hello');
console.log(mySet) //Set { 'h', 'e', 'l', 'o' }
// 注:Set 中 toString 方法是不能将 Set 转换成 String
5.3、set作用
Set
是 JavaScript 中的一种数据结构,它是一种集合,用于存储唯一的值,即集合中的每个元素都必须是唯一的。Set
的主要作用包括以下几个方面:
-
存储唯一值:
-
Set
中的元素是唯一的,不允许重复值。当你向Set
中添加重复的值时,它会被自动忽略。const uniqueSet = new Set(); uniqueSet.add(1); uniqueSet.add(2); uniqueSet.add(1); // 重复值被忽略 console.log(uniqueSet); // Set { 1, 2 }
-
-
检查值是否存在:
-
通过
has
方法可以轻松检查某个值是否存在于Set
中。const mySet = new Set([1, 2, 3]); console.log(mySet.has(2)); // true console.log(mySet.has(4)); // false
-
-
删除特定值:
-
使用
delete
方法可以从Set
中删除特定的值。const mySet = new Set([1, 2, 3]); mySet.delete(2); console.log(mySet); // Set { 1, 3 }
-
-
迭代集合元素:
-
Set
提供了多种迭代集合元素的方法,包括forEach
、for...of
等。const mySet = new Set([1, 2, 3]); mySet.forEach(value => { console.log(value); }); // 或者使用 for...of 迭代 for (const value of mySet) { console.log(value); }
-
-
获取集合大小:
-
使用
size
属性可以获取Set
的大小,即集合中唯一元素的个数。const mySet = new Set([1, 2, 3]); console.log(mySet.size); // 3
-
Set
的主要优势在于它提供了一种简单且高效的方式来存储唯一值,并且可以方便地进行集合操作,如并集、交集和差集。它对于需要存储不重复值的场景非常有用。
5.4、WeakSet
WeakSet
是 JavaScript 中的一种数据结构,它是 Set
的变体,用于存储对象的弱引用。与 Set
不同的是,WeakSet
中的元素必须是对象,并且这些对象是弱引用的,即它们不会阻止垃圾回收器回收它们,即便这些对象存在于 WeakSet
中。
以下是 WeakSet
的主要特点和用法:
-
只能存储对象:
-
WeakSet
中的元素必须是对象。如果尝试添加非对象值,将引发TypeError
。const weakSet = new WeakSet(); const obj = { key: 'value' }; const nonObj = 'not an object'; weakSet.add(obj); // weakSet.add(nonObj); // TypeError: Invalid value used in weak set
-
-
弱引用:
-
WeakSet
中的对象是弱引用的,这意味着它们不会阻止对象被垃圾回收。如果在其他地方不存在对这些对象的引用,它们可能会被回收,并且相应的键值对会从WeakSet
中自动移除。const weakSet = new WeakSet(); let obj = { key: 'value' }; weakSet.add(obj); console.log(weakSet.has(obj)); // true obj = null; // 对象失去引用 // 在某个时间点,垃圾回收器可能回收了对象,并且从 WeakSet 中移除 console.log(weakSet.has(obj)); // false
-
-
不可迭代:
- 由于
WeakSet
中的元素是弱引用的,它没有提供类似于forEach
或for...of
这样的方法来迭代元素。因此,无法直接获取WeakSet
中的所有元素。
- 由于
-
没有
size
属性:-
与
Set
不同,WeakSet
没有size
属性,也不能直接获取其包含的元素个数。const weakSet = new WeakSet(); console.log(weakSet.size); // undefined
-
WeakSet
主要用于需要存储一组对象,但这些对象的生命周期是由其他部分代码控制的情况。例如,它可以用于存储对象的附加信息,而不会阻止这些对象在其他地方被垃圾回收。需要注意的是,由于 WeakSet
中的对象是弱引用的,不可遍历,因此它具有一些特定的使用场景,并且不适用于所有情况。
Set 与 Map 的异同
Set
和 Map
是 JavaScript 中两种常见的集合类型,它们有一些相似之处,但也有明显的区别。以下是它们的异同点:
相同点:
-
存储唯一值:
Set
和Map
都用于存储唯一的值(或者说键),即集合中的每个元素都必须是唯一的。 -
提供迭代方法: 两者都提供了迭代集合元素的方法,包括
forEach
、for...of
等。
不同点:
- 存储的内容:
-
Set: 存储的是一组唯一的值,不分键值对,每个值在
Set
中都是唯一的。 -
Map: 存储的是键值对,其中键和值可以是任意类型。
-
选择使用 Set
还是 Map
取决于需求。如果只需要存储一组唯一值,而不需要与这些值关联其他信息,可以选择使用 Set
。如果需要键值对存储,或者需要与每个值关联其他信息,那么选择 Map
更合适。
6、Reflect和Proxy
Proxy 可以对目标对象的读取、函数调用等操作进行拦截,然后进行操作处理。它不直接操作对象,而是像代理模式,通过对象的代理对象进行操作,在进行这些操作时,可以添加一些需要的额外操作。
Reflect 可以用于获取目标对象的行为,它与 Object 类似,但是更易读,为操作对象提供了一种更优雅的方式。它的方法与 Proxy 是对应的。
proxy
一个 Proxy 对象由两个部分组成: target 、 handler 。在通过 Proxy 构造函数生成实例对象时,需要提供这两个参数。 target 即目标对象, handler 是一个对象,声明了代理 target 的指定行为。
let target = {
name: 'Tom',
age: 24
}
let handler = {
get: function(target, key) {
console.log('getting '+key);
return target[key]; // 不是target.key
},
set: function(target, key, value) {
console.log('setting '+key);
target[key] = value;
}
}
let proxy = new Proxy(target, handler)
proxy.name // 实际执行 handler.get
proxy.age = 25 // 实际执行 handler.set
// getting name
// setting age
// proxy 是对target的浅拷贝
console.log(target.age) //25
reflect
ES6 中将 Object 的一些明显属于语言内部的方法移植到了 Reflect 对象上(当前某些方法会同时存在于 Object 和 Reflect 对象上),未来的新方法会只部署在 Reflect 对象上。
Reflect.get(target, name, receiver)
查找并返回 target 对象的 name 属性
let exam = {
name: "Tom",
age: 24,
get info(){
return this.name + this.age;
}
}
Reflect.get(exam, 'name'); // "Tom"
// 当 target 对象中存在 name 属性的 getter 方法, getter 方法的 this 会绑定 // receiver
let receiver = {
name: "Jerry",
age: 20
}
Reflect.get(exam, 'info', receiver); // Jerry20
// 当 name 为不存在于 target 对象的属性时,返回 undefined
Reflect.get(exam, 'birth'); // undefined
// 当 target 不是对象时,会报错
Reflect.get(1, 'name'); // TypeError
Reflect.set(target, name, value, receiver)
将 target 的 name 属性设置为 value。返回值为 boolean ,true 表示修改成功,false 表示失败。当 target 为不存在的对象时,会报错。
let exam = {
name: "Tom",
age: 24,
set info(value){
return this.age = value;
}
}
exam.age; // 24
Reflect.set(exam, 'age', 25); // true
exam.age; // 25
// value 为空时会将 name 属性清除
Reflect.set(exam, 'age', ); // true
exam.age; // undefined
// 当 target 对象中存在 name 属性 setter 方法时,setter 方法中的 this 会绑定 // receiver , 所以修改的实际上是 receiver 的属性,
let receiver = {
age: 18
}
Reflect.set(exam, 'info', 1, receiver); // true
receiver.age; // 1
let receiver1 = {
name: 'oppps'
}
Reflect.set(exam, 'info', 1, receiver1);
receiver1.age; // 1
Reflect.has(obj, name)
是 name in obj 指令的函数化,用于查找 name 属性在 obj 对象中是否存在。返回值为 boolean。如果 obj 不是对象则会报错 TypeError。
let exam = {
name: "Tom",
age: 24
}
Reflect.has(exam, 'name'); // true
Reflect.deleteProperty(obj, property)
let exam = {
name: "Tom",
age: 24
}
Reflect.deleteProperty(exam , 'name'); // true
exam // {age: 24}
// property 不存在时,也会返回 true
Reflect.deleteProperty(exam , 'name'); // true
Reflect.ownKeys(target)
用于返回 target 对象的所有属性,等同于 Object.getOwnPropertyNames 与Object.getOwnPropertySymbols 之和。
var exam = {
name: 1,
[Symbol.for('age')]: 4
}
Reflect.ownKeys(exam) // ["name", Symbol(age)]
组合使用
Reflect 对象的方法与 Proxy 对象的方法是一一对应的。所以 Proxy 对象的方法可以通过调用 Reflect 对象的方法获取默认行为,然后进行额外操作。
let exam = {
name: "Tom",
age: 24
}
let handler = {
get: function(target, key){
console.log("getting "+key);
return Reflect.get(target,key);
},
set: function(target, key, value){
console.log("setting "+key+" to "+value)
Reflect.set(target, key, value);
}
}
let proxy = new Proxy(exam, handler)
proxy.name = "Jerry"
proxy.name
// setting name to Jerry
// getting name
// "Jerry"
7、模板字符串
使用大量的“”(双引号)和 + 来拼接才能得到我们需要的模版。模板字符串允许更优雅的方式去拼接字符串,不容易出错
`string text ${expression} string text`
8、对象
Object.assign(target, source_1, ···)
用于将源对象的所有可枚举属性复制到目标对象中。
如果目标对象和源对象有同名属性,或者多个源对象有同名属性,则后面的属性会覆盖前面的属性。
Object.is(value1, value2)
用来比较两个值是否严格相等,与(===)基本类似。
区别:
//一是+0不等于-0
Object.is(+0,-0); //false
+0 === -0 //true
//二是NaN等于本身
Object.is(NaN,NaN); //true
NaN === NaN //false
9、数组
Array.of()
将参数中所有值作为元素形成数组。
console.log(Array.of(1, 2, 3, 4)); // [1, 2, 3, 4]
// 参数值可为不同类型
console.log(Array.of(1, '2', true)); // [1, '2', true]
// 参数为空时返回空数组
console.log(Array.of()); // []
Array.from()
将类数组对象或可迭代对象转化为数组
// 参数为数组,返回与原数组一样的数组
console.log(Array.from([1, 2])); // [1, 2]
// 参数含空位
console.log(Array.from([1, , 3])); // [1, undefined, 3]
10.扩展运算符
扩展运算符(spread)(形如:...),有点像 rest参数的逆运算,将一个数组转为用逗号分隔的参数序列。
var arr = [1,2,3,4,5,6]
console.log(...arr) //1 2 3 4 5 6
应用场景:
- 解构赋值
const [a,b,...c] = [1,2,3,4,5,6]
console.log(a) //1
console.log(b) //2
console.log(c) // [ 3, 4, 5, 6 ]
const {name,age,...f} = {name:'jerry',age:22,some:true,another:'sda'}
console.log(name) //jerry 必须是name
console.log(age) //22 必须是age
console.log(f) //{ some: true, another: 'sda' }
2. 合并数组,对象
var arr1=[1,2,3]
var arr2 = [3,4,5]
var arr = [...arr1,...arr2]
console.log(arr) //[ 1, 2, 3, 3, 4, 5 ]
var obj1 = {
name:'jerry',
age:22
}
var obj2={
school:'hebeigc',
class:3
}
var obj = {...obj1,...obj2}
console.log(obj) //{ name: 'jerry', age: 22, school: 'hebeigc', class: 3 }
3. 帮助完成vuex中函数的映射
export default new Vuex.Store({
state: {
list: [],
inputValue: ''
},
mutations: {
setInput(state, inpVal) {
state.inputValue = inpVal
}
},
actions: {
async getList(context) {
const { data: res } = await axios.get('/list.json')
context.commit('initList', res)
}
},
getters: {
unDone(state) {
const unDoneList = state.list.filter(item => {
return item.done === false
})
return '剩余' + unDoneList.length + '条'
}
})
// 导入函数
import { mapState, mapMutations, mapActions, mapGetters } from 'vuex'
export default {
name: 'app',
data() {
return {}
},
methods: {
...mapMutations(['setInput']),
...mapActions(['getList']),
},
computed: {
...mapState(['inputValue']),
...mapGetters(['unDone'])
}
}
mapMutations函数和mapActions函数将mutations和actions中指定的函数映射为当前组件的methods函数。
mapState函数和mapGetters函数将state中全局数据映射为当前组件的计算属性。
- 浅拷贝
var obj = {
o:{
a:1
},
arr:[1,2]
}
var clonObj = {...obj}
console.log(clonObj) //{ o: { a: 1 }, arr: [ 1, 2 ] }
clonObj.o.a=5;
clonObj.arr[0]=5
console.log(obj) //{ o: { a: 5 }, arr: [ 5, 2 ] }
console.log(clonObj) //{ o: { a: 5 }, arr: [ 5, 2 ] }
11.rest参数
rest参数搭配的变量是一个数组,将函数多余的且未知个数的参数收纳到一个数组中(不限长度)
let {a, b, ...rest} = {a: 10, b: 20, c: 30, d: 40};
// a = 10
// b = 20
// rest = {c: 30, d: 40}
用于解决箭头函数没有 arguments 问题。
function arg(){
console.log(arguments)
console.log(arguments[0]) //1
}
arg(1,2,3,4,5,6) //[Arguments] { '0': 1, '1': 2, '2': 3, '3': 4, '4': 5, '5': 6 }
const args=(...rest)=>{
console.log('argument',arguments) //得不到传入的参数
console.log('rest',rest) //[ 1, 2, 3, 4, 5 ]
}
args(1,2,3,4,5)
arguments和...rest:
function cb(a,b){}
在上述函数中,我们如果想获得除了 a,b,之外的参数,
必须for从2开始循环arguments,或者挨个给传入的参数一个形参
如果用...rest 的话就可以简单:
function cb(a,b,...rest){}
剩下的参数都会在rest数组中
10、箭头函数
(function(){
console.log([
(()=>this.x).bind({x:'in'})(),
(()=>this.x)()
])
}).call({x:'ourter'})
// [ 'ourter', 'ourter' ]
var id = 1
const o = {
id: 2,
m() {
console.log('m--->', this.id)
},
n: () => {
console.log('n--->', this.id)
}
}
o.m() // 2 指向o
o.n() // 1 指向window
let p = o.m
p() // 1 指向window
p = o.n
p() // 1 指向window
var id = 1
const o = {
id: 2,
m() {
console.log('m--->', this.id)
},
n: function () {
return () => {
console.log('n--->', this.id)
}
}
}
o.m() // 2 指向o
o.n()() // 2 指向o
let p = o.m
p() // 1 指向window
p = o.n()
p() // 2 指向o
p = o.n
p()() // 1 指向o
由上总结:箭头函数this取决于定义它时的作用域,别的函数this取决于运行时作用域
箭头函数不会创建自己的this,它只会从自己的作用域链的上一层继承this
11、class 类
12、模块module
13、promise
promise对象是一个构造函数,用于生成Promise实例。对象状态不受外界影响,只有异步操作的结果才能决定当前是哪一种状态。三种状态:pending(进行中)、fulfilled(已成功)、rejected(已失败)。实例生成以后,用then方法分别指定resolved状态和rejected状态的回调函数
promise常用的API
在 JavaScript 中,有四个常用的 Promise 组合方法,它们分别是 Promise.all
、Promise.allSettled
、Promise.any
和 Promise.race
。这些方法用于处理多个 Promise 对象,每个方法都有不同的行为和适用场景。
1. Promise.all
-
语法:
Promise.all(iterable).then( (values) => { // 处理所有 Promise 解决时的值 }, (reason) => { // 处理第一个被拒绝的 Promise 的原因 } );
-
行为:
- 当所有的 Promise 都解决时,返回一个新的 Promise,解决值是包含所有解决值的数组。
- 如果任何一个 Promise 被拒绝,整个
Promise.all
就会被拒绝,且返回第一个被拒绝的 Promise 的原因。
2. Promise.allSettled
-
语法:
Promise.allSettled(iterable).then((results) => { // 处理所有 Promise 解决或拒绝时的结果 });
-
行为:
- 返回一个新的 Promise,解决值是包含所有 Promise 结果的数组。
- 不关心 Promise 是解决还是被拒绝,只要有结果就会放入结果数组中。
3. Promise.any
-
语法:
Promise.any(iterable).then((value) => { // 处理第一个完成的 Promise 的值 });
-
行为:
- 返回一个新的 Promise,解决值是第一个完成的 Promise 的值。
- 只要有一个 Promise 解决,即使其他 Promise 被拒绝,整个
Promise.any
也会解决。
4. Promise.race
-
语法:
Promise.race(iterable).then( (value) => { // 处理第一个完成的 Promise 的值 }, (reason) => { // 处理第一个被拒绝的 Promise 的原因 } );
-
行为:
- 返回一个新的 Promise,解决值或拒绝原因与第一个完成或被拒绝的 Promise 相关。
- 如果第一个完成,整个
Promise.race
解决;如果第一个被拒绝,整个Promise.race
被拒绝。
总结
- 使用
Promise.all
当你希望等待所有 Promise 完成,只有当所有 Promise 解决时才解决。 - 使用
Promise.allSettled
当你希望等待所有 Promise 完成,不管是解决还是被拒绝,都收集结果。 - 使用
Promise.any
当你希望等待第一个 Promise 完成,不关心其他 Promise 是否被拒绝。 - 使用
Promise.race
当你希望等待第一个 Promise 完成或被拒绝,整个race
会与第一个完成或被拒绝的 Promise 相关。
Promise的链式调用
可以在then中继续写Promise对象并返回,然后继续调用then来进行回调操作
先看一段代码:
new Promise((resolve, reject) => {
console.log("log: 外部promise");
resolve();
})
.then(() => {
console.log("log: 外部第一个then");
new Promise((resolve, reject) => {
console.log("log: 内部promise");
resolve();
})
.then(() => {
console.log("log: 内部第一个then");
})
.then(() => {
console.log("log: 内部第二个then");
});
})
.then(() => {
console.log("log: 外部第二个then");
});
// log: 外部promise
// log: 外部第一个then
// log: 内部promise
// log: 内部第一个then
// log: 外部第二个then
// log: 内部第二个then
结论
-
当执行 then 方法时,如果前面的 promise 已经是 resolved 状态,则直接将回调放入微任务队列中
在同步执行 then 方法时,会进行判断:
-
如果前面的 promise 已经是 resolved 状态,则会立即将回调推入微任务队列(但是执行回调还是要等到所有同步任务都结束后)
-
如果前面的 promise 是 pending 状态则会将回调存储在 promise 的内部,一直等到 promise 被 resolve 才将回调推入微任务队列
-
-
当一个 promise 被 resolve 时,会遍历之前通过 then 给这个 promise 注册的所有回调,将它们依次放入微任务队列中
如何理解
通过 then 给这个 promise 注册的所有回调
,考虑以下案例let p = new Promise((resolve, reject) => { setTimeout(resolve, 1000); }); p.then(() => { console.log("log: 外部第一个then"); }); p.then(() => { console.log("log: 外部第二个then"); }); p.then(() => { console.log("log: 外部第三个then"); });
1 秒后变量 p 才会被 resolve,但是在 resolve 前通过 then 方法给它注册了 3 个回调,此时这 3 个回调不会被执行,也不会被放入微任务队列中,它们会被 p 内部储存起来(在手写 promise 时,这些回调会放在 promise 内部保存的数组中),等到 p 被 resolve 后,依次将这 3 个回调推入微任务队列,此时如果没有同步任务就会逐个取出再执行。
-
then必须返回的是promise实例(才能通过resolve将下一个then注册的回调函数推入微任务队列中)
对于 then 方法返回的 promise 它是没有 resolve 函数的,取而代之只要 then 中回调的代码执行完毕并获得同步返回值,这个 then 返回的 promise 就算被 resolve
同步返回值
的意思换句话说,如果 then 中的回调返回了一个 promise,那么 then 返回的 promise 会等待这个 promise 被 resolve 后再 resolvenew Promise((resolve, reject) => { resolve(); }) .then(() => //1.返会promise 3.这个then被resolve new Promise((resolve, reject) => { resolve(); }).then(() => { console.log("log: 内部第一个then"); //2.返回的promise被 resolve }) ) .then(() => { //4.外部第一个thenresolve,将外部第二个then注册的回调函数推入微任务队列 console.log("log: 外部第二个then"); }); // log: 内部第一个then // log: 外部第二个then
-
resolve:
resolve 函数就是在实例化 Promise 时,传入函数的第一个参数
new Promise(resolve => { resolve(); });
它的作用除了将当前的 promise 由 pending 变为 resolved,还会遍历之前通过 then 给这个 promise 注册的所有回调,将它们依次放入微任务队列中,很多人以为是由 then 方法来触发它保存回调,而事实上 then 方法即不会触发回调,也不会将它放到微任务,then 只负责注册回调,由 resolve 将注册的回调放入微任务队列,由事件循环将其取出并执行。
-
回过头看最开始的例子:
new Promise((resolve, reject) => {
console.log("log: 外部promise");
resolve();
})
.then(() => {
console.log("log: 外部第一个then");
new Promise((resolve, reject) => {
console.log("log: 内部promise");
resolve();
})
.then(() => {
console.log("log: 内部第一个then");
})
.then(() => {
console.log("log: 内部第二个then");
});
})
.then(() => {
console.log("log: 外部第二个then");
});
// log: 外部promise
// log: 外部第一个then
// log: 内部promise
// log: 内部第一个then
// log: 外部第二个then
// log: 内部第二个then
-
最开始promise 实例化,
log: 外部promise
-
执行 resolve 函数,最开始promise的状态变成resolved
-
执行外部第一个 then函数,由于前边的promise已经resolved,所以直接将then中的回调函数推入微任务队列
- 主线程:外部第二个then函数
- 微任务:外部第一个then函数
-
执行主线程外部第二个then函数,由于前边的promise(第一个then返回的promise)仍然是pending状态,所以什么都不做。
- 主线程:
- 微任务:外部第一个then函数
-
执行微任务队列中外部第一个then函数,
log: 外部第一个then
,初始化promise,log: 内部promise
,resolve函数执行,内部promise状态变为resolved -
执行内部第一个then函数,由于之前的promise已经resolved,所以直接将内部第一个then函数的回调推入微任务队列
- 主线程:内部第二个then
- 微任务:内部第一个then
-
执行主线程上内部第二个then函数,由于前边的promise(内部第一个then)还处于pending状态,所以什么都不做(注册,不推进,不执行)。
-
此时外部第一个then函数执行完毕,状态变为resolved,将用then(外部第二个then函数)为他注册的回调函数,推入微任务队列。
- 主线程:
- 微任务:内部第一个then函数,外部第二个then函数
-
执行微任务中内部第一个then函数,
log: 内部第一个then
,此时内部第一个then函数执行完毕,状态变为resolved,将他的回调函数(内部第二个then)推入微任务队列- 主线程:
- 微任务:外部第二个then函数,内部第二个then函数
-
之后依次执行,
log: 外部第二个then
,log: 内部第二个then
promise常用方法
function func(){
return new Promise((resolve,reject)=>{
// resolve(data)或者reject(err)
})
}
-
promise.then(成功回调函数,失败回调函数)
func().then( (data) => { //resolve }, (err) => { //reject } )
-
promise.then(成功回调函数).catch(失败回调函数)
func().then( (data) => {//resolved} ).catch( (err) => { // reject} )
-
promise.then(成功回调函数).catch(失败回调函数).finally(成功失败都执行的函数)
func().then( (data) => {//resolved} ).catch( (err) => { // reject} ).finally( (over) => {//all} )
-
promise.all([a1,a2,a3])
Promise.all可以将多个Promise实例包装成一个新的Promise实例。同时,成功和失败的返回值是不同的,成功的时候返回的是一个结果数组,而失败的时候则返回最先被reject失败状态的值。
Promse.all在处理多个异步处理时非常有用,比如说一个页面上需要等两个或多个ajax的数据回来以后才正常显示,在此之前只显示loading图标。
需要特别注意的是,Promise.all获得的成功结果的数组里面的数据顺序和Promise.all接收到的数组顺序是一致的,即p1的结果在前,即便p1的结果获取的比p2要晚。这带来了一个绝大的好处:在前端开发请求数据的过程中,偶尔会遇到发送多个请求并根据请求顺序获取和使用数据的场景,使用Promise.all毫无疑问可以解决这个问题。
let p1 = new Promise((resolve, reject) => { resolve("成功了"); }); let p2 = new Promise((resolve, reject) => { resolve("success"); }); let p3 = Promise.reject("失败"); Promise.all([p1, p2]) .then((result) => { console.log(result); //['成功了', 'success'] }) .catch((error) => { console.log(error); }); Promise.all([p1, p3, p2]) .then((result) => { console.log(result); }) .catch((error) => { console.log(error); // 失败了,打出 '失败' });
-
promise.race([a1,a2,a3])
顾名思义,Promse.race就是赛跑的意思,意思就是说,Promise.race([p1, p2, p3])里面哪个结果获得的快,就返回那个结果,不管结果本身是成功状态还是失败状态。
let p1 = new Promise((resolve, reject) => { setTimeout(() => { resolve("success"); }, 1000); }); let p2 = new Promise((resolve, reject) => { setTimeout(() => { reject("failed"); }, 500); }); Promise.race([p1, p2]) .then((result) => { console.log(result); }) .catch((error) => { console.log('err-',error); // err- failed });
-
promise.resolve()
Promise.resolve('foo') // 等价于 new Promise(resolve => resolve('foo'))
-
promise.reject()
// Promise.reject(reason)方法也会返回一个新的 Promise 实例,该实例的状态为rejected const p = Promise.reject('出错了'); // 等同于 const p = new Promise((resolve, reject) => reject('出错了')) p.then(null, function (s) { console.log(s) // 出错了 });
async/await
async返回什么
先看看看以下代码的输出值:
async function asyncReturn() {
return 'async返回的是什么?'
}
var result = asyncReturn();
console.log(result); //Promise { 'async返回的是什么?' }
从结果中可以看到async
函数返回的是一个promise
对象,如果在函数中 return
一个直接量,async
会把这个直接量通过 Promise.resolve()
封装成 Promise
对象。
如果async
函数没有返回值:
async function testAsync1() {
console.log("hello async");
}
let result1 = testAsync1();
console.log(result1);
await做了什么处理
从字面意思上看await
就是等待,await
等待的是一个表达式,这个表达式的返回值可以是一个promise
对象也可以是其他值。
很多人以为await
会一直等待之后的表达式执行完之后才会继续执行后面的代码,实际上awai
t是一个让出线程的标志
。await
后面的函数会先执行一遍,然后就会跳出整个async函数来执行后面js栈的代码。等本轮事件循环执行完了之后又会跳回到async
函数中等待await
后面表达式的返回值,如果返回值为非promise
则继续执行async
函数后面的代码,否则将返回的promise
放入promise
队列(Promise的Job Queue)。
async/await执行顺序
function testSometing() {
console.log("2-执行testSometing"); //3--> 打印2-执行testSometing
return "5-testSometing"; //4--> 返回值,await让出线程,向下执行
}
async function testAsync() {
console.log("6-执行testAsync"); //11-->打印6-执行testAsync
return Promise.resolve("8-hello async"); //12-->返回值,await让出线程,执行别的代码(promise)
}
async function test() { //整体从调用test开始
console.log("1-test start..."); //1-->打印1-test start...
const v1 = await testSometing(); //2-->执行到这去执行testSometing()
console.log(v1); //9-->打印v1:5-testSometing
const v2 = await testAsync(); //10-->执行testAsync()
console.log(v2); //14-->打印8-hello async
console.log(v1, v2); //15-->打印5-testSometing,8-hello async,结束
}
test();
var promise = new Promise((resolve)=> { //5-->执行promise
console.log("3-promise start.."); //6--> 打印3-promise start..
resolve("7-promise");});//关键点2 //7-->将promise放入promise的队列
promise.then((val)=> console.log(val)); //13-->打印7-promise,返回test()
console.log("4-test end...") //8-->打印4-test end... 本轮事件循环执行结束,跳回到async函数test()中。
加上seTimeout看看结果如何:
async function async1() {
console.log("async1 start");
await async2();
console.log("async1 end");
};
async function async2() {
console.log( 'async2');
};
console.log("script start");
setTimeout(function () {
console.log("settimeout");
},0);
async1();
new Promise(function (resolve) {
console.log("promise1");
resolve();
}).then(function () {
console.log("promise2");
});
console.log('script end');
await 让出线程后边的未执行代码执行完后,在执行微任务。
再看一个例子:
var a = 0
var b = async () => {
a = a + await 10
console.log('2', a) // -> '2' 10
a = (await 10) + a
console.log('3', a) // -> '3' 20
}
b()
a++
console.log('1', a) // -> '1' 1
输出结果:
1 1
2 10
3 20
解析:
- 首先函数
b
先执行,在执行到await 10
之前变量a
还是 0,因为在await
内部实现了generators
,generators
会保留堆栈中东西,所以这时候a = 0
被保存了下来 - 因为
await
是异步操作,遇到await
就会立即返回一个pending
状态的Promise
对象,暂时返回执行代码的控制权,使得函数外的代码得以继续执行,所以会先执行console.log('1', a)
- 这时候同步代码执行完毕,开始执行异步代码,将保存下来的值拿出来使用,这时候
a = 10
- 然后后面就是常规执行代码了