文章目录
1. let 关键字
let
关键字用来声明变量,使用 let
声明的变量有几个特点:
-
不允许重复声明
允许在程序的任何位置使用var
重新声明 JavaScript 变量:var x = 10; // 现在,x 为 10 var x = 6; // 现在,x 为 6
在相同的作用域,或在相同的块中,通过
let
重新声明一个var
变量是不允许的:var x = 10; // 允许 let x = 6; // 不允许 { var x = 10; // 允许 let x = 6; // 不允许 }
在相同的作用域,或在相同的块中,通过
let
重新声明一个let
变量是不允许的:let x = 10; // 允许 let x = 6; // 不允许 { let x = 10; // 允许 let x = 6; // 不允许 }
在相同的作用域,或在相同的块中,通过
var
重新声明一个let
变量是不允许的:let x = 10; // 允许 var x = 6; // 不允许 { let x = 10; // 允许 var x = 6; // 不允许 }
在不同的作用域或块中,通过
let
重新声明变量是允许的:let x = 6; // 允许 { let x = 7; // 允许 } { let x = 8; // 允许 }
使用
var
关键字重新声明变量会带来问题。在块中重新声明变量也将重新声明块外的变量:var x = 10; // 此处 x 为 10 { var x = 6; // 此处 x 为 6 } // 此处 x 为 6
使用
let
关键字重新声明变量可以解决这个问题。在块中重新声明变量不会重新声明块外的变量:var x = 10; // 此处 x 为 10 { let x = 6; // 此处 x 为 6 } // 此处 x 为 10
-
块级作用域
通过var
关键词声明的变量没有块作用域。在块{}
内声明的变量可以从块之外进行访问。{ var x = 10; } // 此处可以使用 x
可以使用
let
关键词声明拥有块作用域的变量。在块{}
内声明的变量无法从块外访问:{ let x = 10; } // 此处不可以使用 x
循环作用域
在循环中使用var
:var i = 7; for (var i = 0; i < 10; i++) { // 一些语句 } // 此处,i 为 10
在循环中使用
let
:let i = 7; for (let i = 0; i < 10; i++) { // 一些语句 } // 此处 i 为 7
-
不存在变量提升
通过var
声明的变量会提升到顶端。您可以在声明变量之前就使用它:// 在此处,您可以使用 carName console.log(carName); var carName;
通过
let
定义的变量不会被提升到顶端。在声明let
变量之前就使用它会导致ReferenceError
。变量从块的开头一直处于“暂时死区”,直到声明为止:// 在此处,您不可以使用 carName let carName;
-
不影响作用域链
{ let str = '孙悟空'; function fn() { console.log(str); } fn(); }
应用场景:以后声明变量使用 let 不会错
2. const 关键字
const
关键字用来声明常量,const
声明有以下特点:
-
声明必须赋初始值
const PI = 3.14159265359;
-
标识符一般为大写
-
不允许重复声明
在程序中的任何位置都允许重新声明 JavaScriptvar
变量:var x = 2; // 允许 var x = 3; // 允许 x = 4; // 允许
在同一作用域或块中,不允许将已有的
var
或let
变量重新声明或重新赋值给const
:var x = 2; // 允许 const x = 2; // 不允许 { let x = 2; // 允许 const x = 2; // 不允许 }
在同一作用域或块中,为已有的
const
变量重新声明声明或赋值是不允许的:const x = 2; // 允许 const x = 3; // 不允许 x = 3; // 不允许 var x = 3; // 不允许 let x = 3; // 不允许 { const x = 2; // 允许 const x = 3; // 不允许 x = 3; // 不允许 var x = 3; // 不允许 let x = 3; // 不允许 }
在另外的作用域或块中重新声明
const
是允许的:const x = 2; // 允许 { const x = 3; // 允许 } { const x = 4; // 允许 }
-
值不允许修改
关键字const
有一定的误导性。它没有定义常量值。它定义了对值的常量引用。
因此,我们不能更改常量原始值:const PI = 3.141592653589793; PI = 3.14; // 会出错 PI = PI + 10; // 也会出错
但我们可以更改常量对象的属性:
// 您可以创建 const 对象: const car = {type:"porsche", model:"911", color:"Black"}; // 您可以更改属性: car.color = "White"; // 您可以添加属性: car.owner = "Bill";
但是您无法重新为常量对象赋值:
const car = {type:"porsche", model:"911", color:"Black"}; car = {type:"Volvo", model:"XC60", color:"White"}; // ERROR
我们也可以更改常量数组的元素:
// 您可以创建常量数组: const cars = ["Audi", "BMW", "porsche"]; // 您可以更改元素: cars[0] = "Honda"; // 您可以添加元素: cars.push("Volvo");
但是您无法重新为常量数组赋值:
const cars = ["Audi", "BMW", "porsche"]; cars = ["Honda", "Toyota", "Volvo"]; // ERROR
注意: 对象属性修改和数组元素变化不会出发 const 错误
-
块作用域
在块作用域内使用const
声明的变量与let
变量相似。在本例中,x
在块中声明,不同于在块之外声明的x
:var x = 10; // 此处,x 为 10 { const x = 6; // 此处,x 为 6 } // 此处,x 为 10
-
不存在变量提升
通过var
声明的变量会提升到顶端。您可以在声明变量之前就使用它:// 在此处,您可以使用 carName console.log(carName); var carName;
通过
const
定义的变量不会被提升到顶端。const
变量不能在声明之前使用:carName = "Volvo"; // 您不可以在此处使用 carName const carName = "Volvo";
应用场景:声明对象类型使用 const
,非对象类型声明选择 let
3. 解构赋值
ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构赋值。
- 数组解构赋值
数组必须按照顺序解构// 常规用法 const F4 = ['小沈阳','刘能','赵四','宋小宝']; let [xiao, liu, zhao, song] = F4; console.log(xiao); // 小沈阳 console.log(liu); // 刘能 // 可设置默认值,当对应的值为undefined时才会使用默认值 let [a, b, c, d, xie = '谢大脚'] = F4; console.log(xie); // 谢大脚 // 可以使用rest操作符来捕获剩余项(必须是最后一个解构项) let [d, e, ...f] = F4; console.log(d); // 小沈阳 console.log(e); // 刘能 console.log(f); // ['赵四','宋小宝'] // 不完全解构 const [g,,h] = F4; console.log(g); // 小沈阳 console.log(h); // 赵四
- 对象解构赋值
// 常规用法 const sun = { name: '孙悟空', age: '500', fName:null, daYaoGuai: function () { console.log("我可以打妖怪"); } }; let { name, age, daYaoGuai } = sun; console.log(name); // 孙悟空 console.log(age); // 500 console.log(daYaoGuai); // 这个方法 daYaoGuai(); // 我可以打妖怪 // 取别名 let { name: ming, age, daYaoGuai } = sun; console.log(ming); // 孙悟空 // 设置默认值,当定义的变量在对象中不存在时,其默认值才会生效 let { name, fName: '齐天大圣', age, daYaoGuai } = sun; console.log(fName); // 齐天大圣 // 可将剩余属性赋值给一个变量(必须是最后一个解构项) let { name, fName: '齐天大圣', ...b } = sun; console.log(name); // 孙悟空 console.log(fName); // 齐天大圣 console.log(b); // { age: '500',daYaoGuai:这个方法... }
- 复杂解构
let sun = { name: '孙悟空', age: 500, brothers: ['猪八戒', '沙僧', '白龙马', '唐僧'], history: [ { name: '白骨精' }, { name: '黑熊精' }, { name: '牛魔王' } ] }; let { brothers: [one, two, three], history: [first, second, {name}] } = sun; console.log(one); // 猪八戒 console.log(two); // 沙僧 console.log(first); // { name: '白骨精' } console.log(second); // { name: '黑熊精' } console.log(name); // 牛魔王 // 如果想要继续使用 brothers 或 history,只需要在解构时多写一个就可以了 let { brothers, brothers: [one, two, three], history, history: [first, second, {name}] } = sun; console.log(brothers); // ['猪八戒', '沙僧', '白龙马', '唐僧'] console.log(history); // [ { name: '白骨精' }, { name: '黑熊精' }, { name: '牛魔王' } ]
注意:频繁使用对象方法、数组元素,就可以使用解构赋值形式
4. 模板字符串
模板字符串是增强版的字符串,用反引号``来标识,他可以用来定义单行字符串,也可以定义多行字符串,或者在字符串中嵌入变量。
- 字符串中可以出现换行符
- 可以使用
${ xxx }
形式输出变量
// bad
const field = "this is a" + example;
// good
const field = `this is a ${example}`;
注意:当遇到字符串与变量拼接的情况使用模板字符串
5. 函数默认参数
ES6 允许给函数参数赋值初始值
- 形参初始值 具有默认值的参数, 一般位置要靠后(潜规则)
- 可与解构赋值结合
function add(a, b, c = 10) {
return a + b + c;
}
let result = add(1, 2);
console.log(result); // 13
6. 简化对象写法
ES6 允许在大括号里面,直接写入变量和函数,作为对象的属性和方法。书写更加简洁。
let name = '孙悟空';
let daYaoGuai = function () {
console.log('我可以打妖怪!');
}
const sun = {
name,
change,
doing() {
console.log("我要去西天取经");
}
}
7. 箭头函数
ES6中引入了箭头函数,用来简化函数的定义(更简洁更优雅):
let fn = (a,b) => {
return a + b;
}
let result = fn(1, 2);
console.log(result); // 3
-
如果形参只有一个,则小括号可以省略
let add = n => { return n + n; } console.log(add(9)); // 18
-
函数体如果只有一条语句,则花括号可以省略,函数的返回值为该条语句的执行结果
let pow = n => n * n; console.log(pow(8)); // 64
-
箭头函数 this 指向声明时所在作用域下 this 的值
箭头函数不会创建自己的this
, 所以没有自己的this
,它只会在自己作用域的上一层继承this
。所以箭头函数中this
的指向在它在创建时已经确定了,之后不会改变。 -
不可作为构造函数
构造函数new
操作符的执行步骤如下:- 创建一个对象
- 将构造函数的作用域赋给新对象(也就是将对象的
__proto__
属性指向构造函数的prototype
属性) - 指向构造函数中的代码,构造函数中的
this
指向该对象(也就是为这个对象添加属性和方法) - 返回新的对象
实际上第三步就是将函数中的this指向该对象,又由于箭头函数时没有自己的this的,并且this指向外层的执行环境,不能改变指向,所以不能当做构造函数使用。
8. rest 参数
ES6 引入 rest
参数,用于获取函数的实参,用来代替 arguments
// ES5 获取实参的方式
function fn1() {
console.log(arguments);
}
fn1(1, 2, 3, 4, 5);
// rest 参数
function fn2(a, b, ...args) {
console.log(a, b, args);
}
fn2(100, 200, 1, 2, 3, 4, 5, 6);
注意:rest
参数非常适合不定个数参数函数的场景
9. spread 扩展运算符
扩展运算符:(...xxx
)就像是rest
参数的逆运算,将一个数组转为用逗号分隔的参数序列,对数组进行解包。
扩展运算放到一个数组/对象前面,就变成了展开运算符。
可替代数组concat
/对象的 assign
方法,将一个数组/对象展开拆封,合并到一个数组/对象,属于浅拷贝的一种。
var arr1 = [1, 2, 3];
var arr2 = [...arr1, 4, 5, 6];
console.log(arr2); // [1, 2, 3, 4, 5, 6]
将数组转化为用逗号分隔的参数序列:
function test(a,b,c){
console.log(a); // 1
console.log(b); // 2
console.log(c); // 3
}
var arr = [1, 2, 3];
test(...arr);
10. Symbol
ES6 引入了一种新的原始数据类型 Symbol
,表示独一无二的值。
它是JavaScript 语言的第七种数据类型,是一种类似于字符串的数据类型。
特点:
Symbol
的值是唯一的,用来解决命名冲突的问题Symbol
值不能与其他数据进行运算Symbol
定义的对象属性不能使用for…in
循环遍历,但是可以使用Reflect.ownKeys
来获取对象的所有键名
let s1 = Symbol();
console.log(typeof s1); // "symbol"
let s2 = Symbol('hello');
let s3 = Symbol('hello');
console.log(s2 === s3); // false
基于以上特性,Symbol
属性类型比较适合用于两类场景中:常量值和对象属性。
-
基本用法
符号需要使用Symbol()
函数初始化。let sym = Symbol(); // 因为符号本身是原始类型,所以typeof操作符对符号返回symbol console.log(typeof sym); // symbol
调用
Symbol()
函数时,也可以传入一个字符串参数作为对符号的描述,将来可以通过这个字符串来调试代码。但是,这个字符串参数与符号定义或标识完全无关:// Symbol的值是唯一的,不会出现相同值的常量 let s1 = Symbol(); let s2 = Symbol(); console.log(s1 == s2); // false // 可以传入一个字符串参数作为对符号的描述 let s3 = Symbol('foo'); let s4 = Symbol('foo'); console.log(s3 == s4); // false
符号没有字面量语法。 按照规范,只要创建
Symbol()
实例并将其用作对象的新属性,就可以保证它不会覆盖已有的对象属性,无论是符号属性还是字符串属性。let s1 = Symbol(); console.log(s1); // Symbol() let s2 = Symbol('foo'); console.log(s2); // Symbol(foo)
Symbol()
函数不能与new
关键字一起作为构造函数使用。
这样做是为了避免创建符号包装对象,像使用Boolean
、String
或Number
那样,它们都支持构造函数且可用于初始化包含原始值的包装对象。let b = new Boolean(); console.log(typeof b); // "object" let str = new String(); console.log(typeof str); // "object" let n = new Number(); console.log(typeof n); // "object" let sym = new Symbol(); // 报错,TypeError console.log(sym);
如果想使用符号包装对象,可以借用
Object()
函数:let sym1 = Symbol(); let sym2 = Object(mySymbol); console.log(typeof sym2); // "object"
-
使用全局符号注册表
如果运行时的不同部分需要共享和重用符号实例,那么可以用一个字符串作为键,在全局符号注册表中创建并重用符号。
Symbol.for()
方法:let fooSymbol = Symbol.for('foo'); console.log(typeof fooSymbol); // symbol
Symbol.for()
对每个字符串键都执行幂等操作。第一次使用某个字符串调用时,它会检查全局运行时注册表,发现不存在对应的符号,于是就会生成一个新符号实例并添加到注册表中。
后续使用相同字符串的调用同样会检查注册表,发现存在与该字符串对应的符号,然后就会返回该符号实例。// 创建新符号 let sym1 = Symbol.for('foo'); // 重用已有符号 let sym2 = Symbol.for('foo'); console.log(sym1 === sym2); // true
采用相同符号,在全局注册表中定义的符号跟使用
Symbol()
定义的符号也不同:// 使用Symbol()定义 let sym1 = Symbol('foo'); // 使用全局注册表定义 let sym2 = Symbol.for('foo'); console.log(sym1 === sym2); // false
全局注册表中的符号必须使用字符串键来创建,因此作为参数传给
Symbol.for()
的任何值都会被转换为字符串。注册表中使用的键同时也会被用作符号描述。
let emptySymbol = Symbol.for(); console.log(emptySymbol); // Symbol(undefined)
使用
Symbol.keyFor()
来查询全局注册表,这个方法接收符号,返回该全局符号对应的字符串键。如果查询的不是全局符号,则返回undefined。// 创建全局符号 let s1 = Symbol.for('foo'); console.log(Symbol.keyFor(s1)); // foo // 创建普通符号 let s2 = Symbol('bar'); console.log(Symbol.keyFor(s2)); // undefined
如果传给
Symbol.keyFor()
的不是符号,则该方法抛出TypeError。Symbol.keyFor(123); // TypeError: 123 is not a symbol
-
使用符号作为属性
凡是可以使用字符串或数值作为属性的地方,都可以使用符号。
包括对象字面量属性和Object.defineProperty(obj, prop, descriptor)
/Object.defineProperties()
定义的属性。
对象字面量只能在计算属性语法中使用符号作为属性。let s1 = Symbol('abc'), s2 = Symbol('def'), s3 = Symbol('ghi'), s4 = Symbol('jkl'); let o = { // [属性],会对属性进行读取,并且转换成字符串。[s1]是读取了Symbol的字符串键'abc' [s1]: 'abc val' }; // 或 o[s1] = 'abc val'; console.log(o); // { [Symbol(abc)]: 'abc val' } Object.defineProperty(o, s2, { value: 'def val' }); console.log(o); // {Symbol(abc): abc val, Symbol(def): def val} Object.defineProperties(o, { [s3]: { value: 'ghi val' }, [s4]: { value: 'jkl val' } }); console.log(o); // {Symbol(abc): abc val, Symbol(def): def val, Symbol(ghi): ghi val, Symbol(jkl): jkl val}
let s1 = Symbol('abc'), s2 = Symbol('def'); let o = { [s1]: 'abc val', [s2]: 'def val', ghi: 'ghi val', jkl: 'jkl val' }; // Object.getOwnPropertySymbols()返回对象实例的符号属性数组 console.log(Object.getOwnPropertySymbols(o)); // [ Symbol(abc), Symbol(def) ] // Object.getOwnPropertyNames()返回对象实例的常规属性数组 console.log(Object.getOwnPropertyNames(o)); // [ 'ghi', 'jkl' ] // Object.getOwnPropertyDescriptors()会返回同时包含常规和符号属性描述符的对象 console.log(Object.getOwnPropertyDescriptors(o)); // Reflect.ownKeys()会返回两种类型的键 console.log(Reflect.ownKeys(o)); // [ 'ghi', 'jkl', Symbol(abc), Symbol(def) ]
注意:
Object.getOwnPropertyNames()
和Object.getOwnProperty-Symbols()
两个方法的返回值彼此互斥。因为符号属性是对内存中符号的一个引用,所以直接创建并用作属性的符号不会丢失。
但是,如果没有显式地保存对这些属性的引用,那么必须遍历对象的所有符号属性才能找到相应的属性键。
let o = { [Symbol('ab')]: 'ab val', [Symbol('cd')]: 'cd val' }; console.log(o); // { [Symbol(ab)]: 'ab val', [Symbol(cd)]: 'cd val' } let sym = Object.getOwnPropertySymbols(o).find((Symbol) => Symbol.toString().match(/ab/)); console.log(stm); // Symbol(ab)
-
.Symbol 内置值
除了定义自己使用的Symbol
值以外,ES6 还提供了 11 个内置的Symbol
值,指向语言内部使用的方法。可以称这些方法为魔术方法,因为它们会在特定的场景下自动执行。属性 含义 Symbol.hasInstance
当其他对象使用 instanceof
运算符,判断是否为该对象的实例时,会调用这个方法Symbol.isConcatSpreadable
对象的 Symbol.isConcatSpreadable
属性等于的是一个布尔值,表示该对象用于Array.prototype.concat()
时,是否可以展开。Symbol.species
创建衍生对象时,会使用该属性 Symbol.match
当执行 str.match(myObject)
时,如果该属性存在,会调用它,返回该方法的返回值。Symbol.replace
当该对象被 str.replace(myObject)
方法调用时,会返回该方法的返回值。Symbol.search
当该对象被 str.search (myObject)
方法调用时,会返回该方法的返回值。Symbol.split
当该对象被 str.split(myObject)
方法调用时,会返回该方法的返回值。Symbol.iterator
对象进行 for...of
循环时,会调用Symbol.iterator
方法,返回该对象的默认遍历器Symbol.toPrimitive
该对象被转为原始类型的值时,会调用这个方法,返回该对象对应的原始类型值。 Symbol.toStringTag
在该对象上面调用 toString
方法时,返回该方法的返回值Symbol.unscopables
该对象指定了使用 with
关键字时,哪些属性会被with
环境排除。
11. 迭代器
遍历器(Iterator
)就是一种机制。它是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署 Iterator
接口,就可以完成遍历操作。
- ES6 创造了一种新的遍历命令
for...of
循环,Iterator
接口主要供for...of
消费 - 原生具备
iterator
接口的数据(可用for...of
遍历)
Array
、Arguments
、Set
、Map
、String
、TypedArray
、NodeList
- 工作原理
a. 创建一个指针对象,指向当前数据结构的起始位置
b. 第一次调用对象的next
方法,指针自动指向数据结构的第一个成员
c. 接下来不断调用next
方法,指针一直往后移动,直到指向最后一个成员
d. 每调用 next 方法返回一个包含value
和done
属性的对象
注: 需要自定义遍历数据的时候,要想到迭代器。
//声明一个数组
const xiYouJi = ['唐僧', '孙悟空', '猪八戒', '沙僧'];
//使用 for...of 遍历数组
for (let v of xiYouJi) {
console.log(v); // 唐僧 孙悟空 猪八戒 沙僧
}
let iterator = xiYouJi[Symbol.iterator]();
//调用对象的next方法
console.log(iterator.next()); // {value: '唐僧', done: false}
console.log(iterator.next()); // {value: '孙悟空', done: false}
console.log(iterator.next()); // {value: '猪八戒', done: false}
console.log(iterator.next()); // {value: '沙僧', done: false}
console.log(iterator.next()); // {value: undefined, done: true}
迭代器自定义遍历对象:
//声明一个对象
let shui_hu = {
name: "水浒",
staff: [
'宋江',
'李逵',
'时迁',
'鲁智深'
],
[Symbol.iterator]() {
//索引变量
let index = 0;
// 存储this
let _this = this;
return {
next: function () {
if (index < _this.staff.length) {
const result = { value: _this.staff[index], done: false };
//下标自增
index++;
//返回结果
return result;
} else {
return { value: undefined, done: true };
}
}
};
}
}
//遍历这个对象
for (let v of shui_hu) {
console.log(v);
}
12. 生成器
生成器函数是 ES6 提供的一种异步编程解决方案,语法行为与传统函数完全不同。
生成器是一个特殊函数,特殊的迭代器(Iterator
)
代码说明:
- (
*
)的位置没有限制 - 生成器函数返回的结果是迭代器对象,调用迭代器对象的
next
方法可以得到yield
语句后的值 yield
相当于函数的暂停标记,也可以认为是函数的分隔符,每调用一次next
方法,执行一段代码next
方法可以传递实参,作为yield
语句的返回值
1. 生成器的迭代
for-of 遍历
function * gen(){
yield '孙悟空';
yield '猪八戒';
yield '沙和尚';
}
//遍历
for(let v of gen()){
console.log(v);
}
// v= 孙悟空
// v= 猪八戒
// v= 沙和尚
2. 生成器执行next
yield
的参数是next
方法的返回值yield
把代码分成N段执行next
方法可以分段执行
function* gen() {
console.log('111')
yield "孙悟空"; // 第1段
console.log('222')
yield "猪八戒" // 第2段
console.log('333')
yield "沙和尚" // 第3段
console.log('444')
} // 第4段
let iterator = gen()
console.log('iterator.next()=', iterator.next())
// 111
// iterator.next()= {value: '孙悟空', done: false}
console.log('iterator.next()=', iterator.next())
// 222
// iterator.next()= {value: '猪八戒', done: false}
console.log('iterator.next()=', iterator.next())
// 333
// iterator.next()= {value: '沙和尚', done: false}
console.log('iterator.next()=', iterator.next())
// 444
// iterator.next()= {value: undefined, done: true}
3. 生成器yield的返回值
next
方法的参数是yield的返回值- 第N个
next
方法的参数是N-1个yield
的返回值
function* gen(arg) {
console.log('arg', arg); // arg AAA
let one = yield 111;
console.log('one', one) // one BBB
let two = yield 222;
console.log('two', two); // two CCC
let three = yield 333;
console.log('three', three); // three DDD
}
//执行获取迭代器对象
let iterator = gen('AAA');
console.log('iterator.next()', iterator.next());
// iterator.next() {value: 111, done: false}
//next方法可以传入实参
console.log("iterator.next('BBB')", iterator.next('BBB'));
// iterator.next('BBB') {value: 222, done: false}
console.log("iterator.next('CCC')", iterator.next('CCC'));
// iterator.next('CCC') {value: 333, done: false}
console.log("iterator.next('DDD')", iterator.next('DDD'));
// iterator.next('DDD') {value: undefined, done: true}
4. 异步任务同步化表达
- 需求: 按顺序获取异步请求的结果
- 顺序: 用户数据 > 订单数据 > 商品数据
- 异步: 使用
settimout
模拟
// 使用回调方式执行: 回调地狱
function getData() {
setTimeout(() => {
console.log('用户数据')
setTimeout(() => {
console.log('订单数据')
setTimeout(() => {
console.log('商品数据')
}, 500)
}, 500);
}, 500)
}
getData()
//模拟获取 用户数据 订单数据 商品数据
function getUsers() {
setTimeout(() => {
let data = '用户数据';
//调用 next 方法, 并且将数据传入
iterator.next(data);
}, 1000);
}
function getOrders() {
setTimeout(() => {
let data = '订单数据';
iterator.next(data);
}, 1000)
}
function getGoods() {
setTimeout(() => {
let data = '商品数据';
iterator.next(data);
}, 1000)
}
function* gen() {
let users = yield getUsers();
let orders = yield getOrders();
let goods = yield getGoods();
}
//调用生成器函数
let iterator = gen();
iterator.next();
13. Promise
感谢阅读,正在努力撰写博客,敬请稍候。
14. Set
ES6 提供了新的数据结构 Set
(集合)。它类似于数组,但成员的值都是唯一的,集合实现了 iterator
接口,所以可以使用『扩展运算符』和『for…of…
』进行遍历。
JavaScript 的 Set
(集合)是一组唯一值的集合。
每个值只能在 Set
中出现一次。
Set
可以容纳任何数据类型的值。
Set
集合的属性和方法:
属性/方法 | 方法 |
---|---|
size | 返回集合的元素个数 |
new Set() | 创建新的 Set |
add() | 增加一个新元素,返回当前集合 |
delete() | 删除元素,返回 boolean 值 |
has() | 检测集合中是否包含某个元素,返回 boolean 值 |
clear() | 清空集合,返回 undefined |
forEach() | 为每个元素调用回调函 |
values() | 返回包含 Set 中所有值的迭代器 |
keys() | 与 values() 相同 |
entries() | 返回迭代器,其中包含 Set 中的 [value,value] 值值对 |
-
如何创建 Set
- 将数组传递给
new Set()
- 创建一个新的
Set
,然后使用add()
方法添加值 - 创建一个新的
Set
,然后使用add()
方法添加变量
- 将数组传递给
-
新的 Set() 方法
将数组传递给新的Set()
构造函数:// 创建 Set const letters = new Set(["a","b","c"]);
创建一个
Set
并添加字面量值:// 创建 Set const letters = new Set(); // 将值添加到 Set letters.add("a"); letters.add("b"); letters.add("c");
创建一个
Set
并添加变量:// 创建变量 const a = "a"; const b = "b"; const c = "c"; // 创建 Set const letters = new Set(); // 向 Set 添加变量 letters.add(a); letters.add(b); letters.add(c);
-
add() 方法
基础用法:// 创建新的 Set const letters = new Set(["a","b","c"]); // 添加新元素 letters.add("d"); letters.add("e");
如果您添加相等的元素,只有第一个会被保存:
// 创建 Set const letters = new Set(); // 向 Set 中添加值 letters.add("a"); letters.add("b"); letters.add("c"); letters.add("c"); letters.add("c");
-
forEach() 方法
// 创建 Set const letters = new Set(["a", "b", "c"]); // 列出所有元素 letters.forEach((item) => { console.log('item', item) })
-
values() 方法
const letters = new Set(["a", "b", "c"]); console.log('letters.values()', letters.values()) // letters.values() => SetIterator {'a', 'b', 'c'} for (const x of letters.values()) { console.log('x', x) } // x a // x b // x c
-
keys() 方法
Set 没有键。
keys() 返回与 values() 相同的结果。
这使得 Set 与 Map 相兼容。const letters = new Set(["a", "b", "c"]); console.log('letters.keys()', letters.keys()) // letters.keys() => SetIterator {'a', 'b', 'c'} for (const x of letters.keys()) { console.log('x', x) } // x a // x b // x c
-
entries() 方法
Set
没有键(key
)。
entries() 方法返回的是 [value,value] 值值对,而不是 [key,value] 键值对。
这使得Set
与Map
兼容:const letters = new Set(["a", "b", "c"]); const iterator = letters.entries(); for (const x of iterator) { console.log('x', x) } // x ['a', 'a'] // x ['b', 'b'] // x ['c', 'c']
-
实际应用
由于集合中元素的唯一性,所以在实际应用中,可以使用Set
来实现数组去重:let arr = [1,2,3,4,5,4,3,2,1]; Array.from(new Set(arr)) // [1, 2, 3, 4, 5]
可以通过
Set
来求两个数组的交集、并集、差集:let arr1 = [1, 2, 3, 4, 5, 4, 3, 2, 1]; let arr2 = [3, 4, 5, 6, 7, 6, 5, 4, 3]; // 交集 let result = [...new Set(arr1)].filter(item => new Set(arr2).has(item)); console.log(result); // [3, 4, 5] // 并集 let union = [...new Set([...arr1, ...arr2])]; console.log(union); // [1, 2, 3, 4, 5, 6, 7] // 差集 let diff1 = [...new Set(arr1)].filter(item => !(new Set(arr2).has(item))); console.log(diff1); // [1, 2] let diff2 = [...new Set(arr2)].filter(item => !(new Set(arr1).has(item))); console.log(diff2); // [6, 7]
用以下方法可以进行数组与集合的相互转化:
// Set集合转化为数组 const arr = [...mySet] const arr = Array.from(mySet) // 数组转化为Set集合 const mySet = new Set(arr)
15. Map
ES6 提供了 Map
数据结构。它类似于对象,也是键值对的集合。但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。Map
也实现了iterator
接口,所以可以使用『扩展运算符』和『for…of…
』进行遍历。
Map
保存键值对,其中键可以是任何数据类型。
Map
会记住键的原始插入顺序。
Map
提供表示映射大小的属性。
Map
的属性和方法:
属性/方法 | 方法 |
---|---|
size | 返回 Map 的元素个数 |
new Map() | 创建新的 Map 对象 |
set() | 增加一个新元素,返回当前 Map |
get() | 返回键名对象的键值 |
has() | 检测 Map 中是否包含某个元素,返回 boolean 值 |
clear() | 清空集合,返回 undefined |
delete() | 删除由某个键指定的 Map 元素 |
forEach() | 为 Map 中的每个键/值对调用回调函数 |
entries() | 返回迭代器对象,其中包含 Map 中的 [key, value] 键值对 |
keys() | 返回迭代器对象,其中包含 Map 中的键 |
values() | 返回迭代器对象,其中包含 Map 中的值 |
-
如何创建 Map
- 将数组传递给
new Map()
- 创建映射并使用
Map.set()
- 将数组传递给
-
new Map()
通过将数组传递给new Map()
构造函数来创建Map
:// 创建一个 Map const fruits = new Map([ ["apples", 500], ["bananas", 300], ["oranges", 200] ]);
-
Map.set()
使用set()
方法将元素添加到Map
中:// 创建一个 Map const fruits = new Map(); // 设置 Map 的值 fruits.set("apples", 500); fruits.set("bananas", 300); fruits.set("oranges", 200);
set()
方法还可用于更改现有的Map
值:const fruits = new Map([ ["apples", 500], ["bananas", 300], ["oranges", 200] ]); fruits.set("apples", 200);
-
Map.get()
get(
) 方法获取Map
中键的值:const fruits = new Map([ ["apples", 500], ["bananas", 300], ["oranges", 200] ]); fruits.get("apples"); // 返回 500
-
Map.size
size
属性返回Map
中元素的数量:const fruits = new Map([ ["apples", 500], ["bananas", 300], ["oranges", 200] ]); fruits.size; // 3
-
Map.delete()
delete()
方法删除Map
元素:const fruits = new Map([ ["apples", 500], ["bananas", 300], ["oranges", 200] ]); // Delete an Element fruits.delete("apples");
-
Map.clear()
clear()
方法从Map
中删除所有元素:const fruits = new Map([ ["apples", 500], ["bananas", 300], ["oranges", 200] ]); // Clear the Map fruits.clear();
-
Map.has()
如果Map
中存在键,则has()
方法返回true
:const fruits = new Map([ ["apples", 500], ["bananas", 300], ["oranges", 200] ]); fruits.has("apples"); // true
-
Map.forEach()
forEach()
方法为Map
中的每个键/值对调用回调:const fruits = new Map([ ["apples", 500], ["bananas", 300], ["oranges", 200], ]); fruits.forEach(function (value, key) { console.log(`${key} - ${value}`); });
-
Map.entries()
entries()
方法返回一个带有 Map 中 [key,values] 的迭代器对象:const fruits = new Map([ ["apples", 500], ["bananas", 300], ["oranges", 200], ]); for (const x of fruits.entries()) { console.log("x", x); }
-
Map.keys()
keys()
方法返回一个迭代器对象,其中包含Map
中的键:const fruits = new Map([ ["apples", 500], ["bananas", 300], ["oranges", 200], ]); for (const x of fruits.keys()) { console.log("x", x); }
-
Map.values()
values()
方法返回一个迭代器对象,其中包含Map
中的值:const fruits = new Map([ ["apples", 500], ["bananas", 300], ["oranges", 200], ]); for (const x of fruits.values()) { console.log("x", x); }
-
将对象用作键
能够将对象用作键是 Map 的一项重要特性。// 创建对象 const apples = {name: 'Apples'}; const bananas = {name: 'Bananas'}; const oranges = {name: 'Oranges'}; // 创建 Map const fruits = new Map(); // 将对象添加到 Map fruits.set(apples, 500); fruits.set(bananas, 300); fruits.set(oranges, 200); fruits.get(apples); // 500 fruits.get("apples"); // undefined
16. class 类
ES6 提供了更接近传统语言的写法,引入了 Class
(类)这个概念,作为对象的模板。通过 class
关键字,可以定义类。基本上,ES6 的 class
可以看作只是一个语法糖,它的绝大部分功能,ES5 都可以做到,新的 class
写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。
-
类的语法
请使用关键字class
创建一个类。
请始终添加一个名为constructor()
的方法:语法
class ClassName { constructor() { ... } }
实例
创建一个名为 “Car” 的类,该类有两个初始属性:“name” 和 “year”:class Car { constructor(name, year) { this.name = name; this.year = year; } }
-
使用类
class Car { constructor(name, year) { this.name = name; this.year = year; } } let myCar1 = new Car("Ford", 2014); let myCar2 = new Car("Audi", 2019);
上面的例子使用 Car 类来创建两个 Car 对象。
在创建新对象时会自动调用构造方法(constructor method)。 -
构造方法
构造方法是一种特殊的方法:- 它必须有确切的名称的 “
constructor
” - 创建新对象时自动执行
- 用于初始化对象属性
- 如果您没有定义构造方法,JavaScript 会添加一个空的构造方法。
- 它必须有确切的名称的 “
-
类方法
创建类方法的语法与对象方法相同。
请使用关键字 class 创建一个类。
请始终添加constructor()
方法。
然后添加任意数量的方法。语法
class ClassName { constructor() { ... } method_1() { ... } method_2() { ... } method_3() { ... } }
实例
创建一个名为 “age” 的类方法,它返回车年:class Car { constructor(name, year) { this.name = name; this.year = year; } age() { let date = new Date(); return date.getFullYear() - this.year; } } let myCar = new Car("Ford", 2014); myCar.age()
您可以向类方法发送参数:
class Car { constructor(name, year) { this.name = name; this.year = year; } age(x) { return x - this.year; } } let date = new Date(); let year = date.getFullYear(); let myCar = new Car("Ford", 2014); myCar.age(year)
-
类继承
如需创建类继承,请使用extends
关键字。
使用类继承创建的类继承了另一个类的所有方法:实例
创建一个名为 “Model” 的类,它将继承 “Car” 类的方法:class Car { constructor(brand) { this.carname = brand; } present() { return 'I have a ' + this.carname; } } class Model extends Car { constructor(brand, mod) { super(brand); this.model = mod; } show() { return this.present() + ', it is a ' + this.model; } } let myCar = new Model("Ford", "Mustang"); console.log(myCar.show()); // I have a Ford, it is a Mustang
super()
方法引用父类。
通过在constructor
方法中调用super()
方法,我们调用了父级的constructor
方法,获得了父级的属性和方法的访问权限。继承对于代码可重用性很有用:在创建新类时重用现有类的属性和方法。 -
Getter 和 Setter
类还允许您使用getter
和setter
。
为您的属性使用getter
和setter
很聪明,特别是如果您想在返回它们之前或在设置它们之前对值做一些特殊的事情。如需在类中添加
getter
和setter
,请使用get
和set
关键字。实例
为 “carname” 属性创建getter
和setter
:class Car { constructor(brand) { this.carname = brand; } get cnam() { return this.carname; } set cnam(x) { this.carname = x; } } let myCar = new Car("Ford"); console.log(myCar.cnam);
注释:即使
getter
是一个方法,当你想要获取属性值时也不要使用括号。
getter
/setter
方法的名称不能与属性名称相同,在本例中为 carname。
许多程序员在属性名称前使用下划线字符_
将getter
/setter
与实际属性分开:实例
您可以使用下划线字符将getter
/setter
与实际属性分开:class Car { constructor(brand) { this._carname = brand; } get carname() { return this._carname; } set carname(x) { this._carname = x; } } let myCar = new Car("Ford"); console.log(myCar.carname);
如需使用
setter
,请使用与设置属性值相同的语法,不带括号:class Car { constructor(brand) { this._carname = brand; } get carname() { return this._carname; } set carname(x) { this._carname = x; } } let myCar = new Car("Ford"); myCar.carname = "Volvo"; console.log(myCar.carname);
-
Static 方法
static
类方法是在类本身上定义的。
您不能在对象上调用static
方法,只能在对象类上调用。实例
class Car { constructor(name) { this.name = name; } static hello() { return "Hello!!"; } } let myCar = new Car("Ford"); // 您可以在 Car 类上调用 'hello()' : console.log(Car.hello()); // 但不能在 Car 对象上调用: // console.log(myCar.hello()); // 此举将引发错误。
如果要在
static
方法中使用 myCar 对象,可以将其作为参数发送:class Car { constructor(name) { this.name = name; } static hello(x) { return "Hello " + x.name; } } let myCar = new Car("Ford"); console.log(Car.hello(myCar));
17. 数值扩展
-
新的数字属性
ES6 将以下属性添加到 Number 对象:EPSILON
是 JavaScript 表示的最小精度MIN_SAFE_INTEGER
代表在 JavaScript中最小的安全的integer
型数字MAX_SAFE_INTEGER
常量表示在 JavaScript 中最大的安全整数
let x = Number.EPSILON; let min = Number.MIN_SAFE_INTEGER; let max = Number.MAX_SAFE_INTEGER; console.log(x); // 2.220446049250313e-16 console.log(min); // -9007199254740991 console.log(max); // 9007199254740991
-
新的数字方法
ES6 为Number
对象添加了 2 个新方法:Number.isInteger()
Number.isSafeInteger()
Number.isInteger() 方法
如果参数是整数,则Number.isInteger()
方法返回 true。Number.isInteger(10); // true Number.isInteger(10.5); // false
Number.isSafeInteger() 方法
安全整数是可以精确表示为双精度数的整数。
如果参数是安全整数,则Number.isSafeInteger(
) 方法返回 true。Number.isSafeInteger(10); // true Number.isSafeInteger(12345678901234567890); // false
安全整数指的是从 -(253 - 1) 到 +(253 - 1) 的所有整数。
这是安全的:9007199254740991。这是不安全的:9007199254740992。 -
新的全局方法
ES6 还增加了 2 个新的全局数字方法:isFinite()
isNaN()
isFinite() 方法
如果参数为Infinity
或NaN
,则全局isFinite()
方法返回false
。否则返回true
:isFinite(10/0); // false isFinite(10/1); // true isFinite(10); // true isFinite(Infinity); // false
isNaN() 方法
如果参数是NaN
,则全局isNaN()
方法返回true
。否则返回false
isNaN("Hello"); // true isNaN(123); // false
-
二进制和八进制
ES6 提供了二进制和八进制数值的新的写法,分别用前缀0b
和0o
表示。let b = 0b1010; let o = 0o777; let d = 100; let x = 0xff; console.log('b', b) // 10 console.log('o', o) // 511 console.log('d', d) // 100 console.log('x', x) // 255
-
Number.parseInt() 与 Number.parseFloat()
ES6 将全局方法parseInt
和parseFloat
,移植到Number
对象上面,使用不变。 -
Math.trunc
用于去除一个数的小数部分,返回整数部分。Math.trunc(3.5) // 3
18. 对象扩展
ES6 新增了一些 Object
对象的方法
-
Object.is()
Object.is()
比较两个值是否严格相等,与『===』行为基本一致,不同的地方在于+0
/-0
与NaN
。console.log(+0 === -0); // true console.log(NaN === NaN); // false console.log(Object.is(+0, -0)); // false console.log(Object.is(NaN, NaN)); // true
语法
Object.is(value1, value2);
Object.is()
方法返回一个布尔类型的值,若满足以下条件则两个值相等:- 都是
undefined
- 都是
null
- 都是
true
或都是false
- 都是字符串,且相同长度、相同字符、相同顺序
- 是相同对象(每个对象有同一个引用)
- 都是数字,且满足以下任意一个
- 都是
0
- 都是
+0
- 都是
-0
- 都非零,且非
NaN
,且是同一个值 - 都是
NaN
- 都是
- 都是
-
Object.assign()
Object.assign()
对象的合并,将源对象的所有可枚举属性,复制到目标对象语法
target
是目标对象,sources
是源对象:Object.assign(target, sources1, sources2, sources3...)
实例
let obj1 = { a: 1 }; let obj2 = { b: 2 }; let res = Object.assign(obj1, obj2); console.log(res); // { a: 1, b: 2 }
如果目标对象和源对象中有相同的属性,那么源对象的属性将覆盖目标对象的属性:
let obj1 = { a: 1, b: 2 }; let obj2 = { a: 2 }; let res = Object.assign(obj1, obj2); console.log(res); // { a: 2, b: 2 }
19. 数组方法
-
reduce()
reduce()
方法对数组中的每个元素执行一个reducer
函数(升序执行),将其结果汇总为单个返回值。其使用语法如下:arr.reduce(callback,[initialValue])
reduce
为数组中的每一个元素依次执行回调函数,不包括数组中被删除或从未被赋值的元素,接受四个参数:初始值(或者上一次回调函数的返回值),当前元素值,当前索引,调用reduce
的数组。
callback
(执行数组中每个值的函数,包含四个参数)previousValue
(上一次调用回调返回的值,或者是提供的初始值(initialValue
))urrentValue
(数组中当前被处理的元素)index
(当前元素在数组中的索引)array
(调用reduce
的数组)
initialValue
(作为第一次调用callback
的第一个参数。)let arr = [1, 2, 3, 4] let sum = arr.reduce((prev, cur, index, arr) => { console.log(prev, cur, index); return prev + cur; }) console.log(arr, sum); // 1 2 1 // 3 3 2 // 6 4 3 // [1, 2, 3, 4] 10
再来加一个初始值看看:
let arr = [1, 2, 3, 4] let sum = arr.reduce((prev, cur, index, arr) => { console.log(prev, cur, index); return prev + cur; }, 5) console.log(arr, sum); // 5 1 0 // 6 2 1 // 8 3 2 // 11 4 3 // [1, 2, 3, 4] 15
通过上面例子,可以得出结论:如果没有提供
initialValue
,reduce
会从索引1的地方开始执行callback
方法,跳过第一个索引。如果提供initialValue
,从索引0开始。
注意:该方法如果添加初始值,就会改变原数组,将这个初始值放在数组的最后一位。 -
filter()
filter()
方法用于过滤数组,满足条件的元素会被返回。它的参数是一个回调函数,所有数组元素依次执行该函数,返回结果为true
的元素会被返回。该方法会返回一个新的数组,不会改变原数组。let arr = [1, 2, 3, 4, 5] arr.filter(item => item > 2) // [3, 4, 5]
可以使用filter()方法来移除数组中的
undefined
、null
、NAN
等值。let arr = [1, undefined, 2, null, 3, false, '', 4, 0] arr.filter(Boolean) //[1, 2, 3, 4]
-
Array.from
Array.from
的设计初衷是快速基于其他对象创建新数组,准确来说就是从一个类似数组的可迭代对象中创建一个新的数组实例。其实,只要一个对象有迭代器,Array.from
就能把它变成一个数组(注意:该方法会返回一个的数组,不会改变原对象)。语法
Array.from
有 3 个参数(第一个参数是必选的,后两个参数都是可选的):- 类似数组的对象,必选。
- 加工函数,新生成的数组会经过该函数的加工再返回。
this
作用域,表示加工函数执行时this
的值。
var obj = {0: 'a', 1: 'b', 2:'c', length: 3}; Array.from(obj, function(value, index){ console.log(value, index, this, arguments.length); return value //必须指定返回值,否则返回 undefined }, obj);
// String Array.from('abc'); // ["a", "b", "c"] // Set Array.from(new Set(['abc', 'def'])); // ["abc", "def"] // Map Array.from(new Map([[1, 'ab'], [2, 'de']])); // [[1, 'ab'], [2, 'de']]
-
fill()
使用fill()
方法可以向一个已有数组中插入全部或部分相同的值,开始索引用于指定开始填充的位置,它是可选的。如果不提供结束索引,则一直填充到数组末尾。如果是负值,则将从负值加上数组的长度而得到的值开始。该方法的语法如下:array.fill(value, start, end)
其参数如下:
value
:必需。填充的值。start
:可选。开始填充位置。end
:可选。停止填充位置 (默认为array.length
)。
const arr = [0, 0, 0, 0, 0]; // 用5填充整个数组 arr.fill(5); console.log(arr); // [5, 5, 5, 5, 5] arr.fill(0); // 重置 // 用5填充索引大于等于3的元素 arr.fill(5, 3); console.log(arr); // [0, 0, 0, 5, 5] arr.fill(0); // 重置 // 用5填充索引大于等于1且小于等于3的元素 arr.fill(5, 3); console.log(arr); // [0, 5, 5, 0, 0] arr.fill(0); // 重置 // 用5填充索引大于等于-1的元素 arr.fill(5, -1); console.log(arr); // [0, 0, 0, 0, 5] arr.fill(0); // 重置
-
find()
find()
方法返回通过测试函数的第一个数组元素的值。
接受三个参数:- 项目值
- 项目索引
- 数组本身
实例
查找(返回)第一个大于 18 的元素(的值):var numbers = [4, 9, 16, 25, 29]; var first = numbers.find(myFunction); function myFunction(value, index, array) { return value > 18; }
-
findIndex()
findIndex()
方法返回通过测试函数的第一个数组元素的索引。
接受三个参数:- 项目值
- 项目索引
- 数组本身
实例
查找(返回)第一个大于 18 的元素(的值):var numbers = [4, 9, 16, 25, 29]; var first = numbers.findIndex(myFunction); function myFunction(value, index, array) { return value > 18; }
20. 模块化
ES6中首次引入模块化开发规范ES Module,让Javascript首次支持原生模块化开发。ES Module把一个文件当作一个模块,每个模块有自己的独立作用域,然后将每个模块组合起来。核心点就是模块的导入与导出。
模块化的优势有以下几点:
- 防止命名冲突
- 代码复用
- 高维护性
- export 导出模块
方式一:分别暴露
方式二:统一暴露// m1 export var first = 'test'; export function func() { return true; }
方式三:默认暴露// m2 var first = 'test'; var second = 'test'; function func() { return true; } export {first, second, func}; // as关键字 // export {first, second as two, func}; // as关键字可以重命名暴露出的变量或方法,经过重命名后同一变量可以多次暴露出去。
// m3 // export default会导出默认输出,即用户不需要知道模块中输出的名字,在导入的时候为其指定任意名字。 export default { first: 'test', second: 'test', func: function () { return true; } } // 注意: 导入默认模块时不需要大括号,导出默认的变量或方法可以有名字,但是对外无效。export default只能使用一次。
- import 导入模块:
方式一:通用的导入方式
方式二:解构赋值形式// 引入 m1.js 模块内容 import * as m1 from "./m1.js"; //引入 m2.js 模块内容 import * as m2 from "./m2.js"; //引入 m3.js import * as m3 from "./m3.js";
方式三:简便形式 针对默认暴露import {first , func} from "./m1.js"; import {first, second as two, func} from "./m2.js"; import {default as m3} from "./m3.js";
import m3 from "./m3.js";