ES6
ES6
前置知识
关于ES6
ECMAScript 6(以下简称 ES6)是 JavaScript 语言的标准,在 2015 年 6 月发布。它的目标,是使得 JavaScript 语言可以用来编写复杂的大型应用程序。
ES6带来的新特性包括 let 和 const 命令、变量的解构赋值、字符串扩展、函数扩展、对象扩展、数组扩展、运算符扩展、Promise对象、Class、Class 继承等。
关于Nodejs
Nodejs诞生于2009年,主攻服务器方向,使得利用 JavaScript 也可以完成服务器代码的编写。
Babel转码器
Babel 是一个广泛使用的 ES6 转码器,可以将 ES6 代码转为 ES5 代码,从而在老版本的浏览器执行。这意味着,你可以用 ES6 的方式编写程序,又不用担心现有环境是否支持。
安装
npm install --save-dev @babel/core
配置
新建一个.babelrc
文件:
{
"presets": [],
"plugins": []
}
设置转码规则
presets字段设定转码规则,官方提供以下的规则集,你可以根据需要安装:
npm install --save-dev @babel/preset-env
把规则加入配置文件
{
"presets": [
"@babel/env"
],
"plugins": []
}
转码到指定文件
安装命令行转码工具:
npm install --save-dev @babel/cli
转码:
npx babel example.js -o compiled.js
npx babel src -d lib
效果
转换箭头函数:
// example.js
Input.map(item => item+1)
// compiled-example.js
"use strict";
Input.map(function (item) {
return item + 1;
});
let命令
ES6 新增了let命令,用来声明变量。它的用法类似于var,但是所声明的变量,只在let命令所在的代码块内有效。
{
let itbaizhan = 10;
var sxt = 1;
}
itbaizhan // ReferenceError: itbaizhan is not defined.
sxt // 1
let不能变量提升、不能重复声明。var 命令会发生“变量提升”现象,即变量可以在声明之前使用,值为undefined 。
const命令
定义常量,赋值后就不能再改,同样只在块级作用域有效、不能提升、不能重复声明。
解构赋值
对于数组
let [a, b, c, [d]] = [1, 2, 3, [4]];
let [a, b] = [10];
// b的值是undefined
const [first, , third] = ['foo', 'bar', 'baz'];
// 可以忽略某个元素
数组的元素是按次序排列的,变量的取值由它的位置决定;而对象的属性没有次序,变量必须与属性同名,才能取到正确的值:
let {sex,age,name} = {name:"iwen",age:20};
sex // undefined
属性提取
提取出现有对象的方法,赋值到某个变量:
let { random,floor } = Math;
// 等价于:
let random = Math.random;
let floor = Math.floor;
let { log } = console;
// 等价于:
let log = console.log;
如果右侧是一个对象(如 Math
或 console
),左侧是一个对象字面量模式,其中的属性名与右侧对象的属性名匹配。系统会查找右侧对象中与左侧模式中同名的属性,将找到的属性值赋给左侧对应的变量。
如果需要提取的属性是被嵌套的:
let { nested: { prop } } = obj;
先从 obj
对象中查找 nested
属性,然后从 nested
对象中查找 prop
属性,再将 prop
属性的值赋给同名的变量 prop
。
同样,这种方式还可以重命名变量:
let { random: rnd, floor: fl } = Math;
// 等价于:
let rnd = Math.random;
let fl = Math.floor;
// 例如:
let {length: len} = 'hello';
len // 5
指定默认值
var {x = 3} = {};
// 等价于:
var x = (typeof ({}).x !== 'undefined') ? ({}).x : 3;
尝试从空对象 {}
中解构出 x
属性,如果 x
属性不存在(如本例中的空对象)或者值为 undefined
,则给变量 x
赋予默认值 3
。
但是这与直接写var x=3
有什么区别?看如下例子:
function createUser({ username = '匿名', age = 18, vip = false } = {}) {
console.log(`用户名: ${username}, 年龄: ${age}, VIP: ${vip}`);
}
createUser({ username: "张三", age: 25 });
// 用户名: 张三, 年龄: 25, VIP: false
createUser({});
// 用户名: 匿名, 年龄: 18, VIP: false
createUser();
// 用户名: 匿名, 年龄: 18, VIP: false
createUser接收一个配置对象作参数,如果不传东西就取默认的对象值。
字符串扩展
使用unicode
"\u0061" // "a"
字符串遍历
for (let i of 'itbaizhan') {
console.log(i);
}
模板串
let url = "www.itbaizhan.com"
let h1 = "<a href='"+ url +"'>itbaizhan</a>"
let h2 = `<a href='${url}'>itbaizhan</a>`
新增方法
子串检查
let s = 'Hello world!';
s.startsWith('world', 6) // true
s.endsWith('Hello', 5) // true
s.includes('Hello', 6) // false
重复
s.repeat(3)
// "Hello world!Hello world!Hello world!"
自动补全
'x'.padStart(5, 'ab') // 'ababx'
'x'.padStart(4, 'ab') // 'abax'
'x'.padEnd(5, 'ab') // 'xabab'
'x'.padEnd(4, 'ab') // 'xaba'
去掉空格
const s = ' itbaizhan ';
s.trim() // "itbaizhan"
s.trimStart() // "itbaizhan "
s.trimEnd() // " itbaizhan"
返回指定下标
const str = 'hello';
str.at(1) // "e"
str.at(-1) // "o"
数值扩展
Number.isFinite(15); // true
Number.isFinite(0.8); // true
Number.isFinite(Infinity); // false
Number.isFinite(-Infinity); // false
Number.isNaN(NaN) // true
Number.isNaN(15) // false
Number.isInteger(25) // true
Number.isInteger(25.1) // false
Number.parseInt('12.34') // 12
Number.parseFloat('123.45#') // 123.45
扩展运算符
...
可以把迭代对象展开为单个元素:
console.log(1, ...[2, 3, 4], 5)
// 1 2 3 4 5
[...document.querySelectorAll('div')]
// [<div>, <div>, <div>]
用于数组合并或转换:
const arr1 = [1, 2];
const arr2 = [3, 4];
const combined = [...arr1, ...arr2]; // [1, 2, 3, 4]
const chars = [...'hello']; // ['h', 'e', 'l', 'l', 'o']
让函数可以接收列表:
const nums = [1, 2, 3];
Math.max(...nums);
// 等价于 Math.max(1, 2, 3)
function logArgs(...args) {
args.forEach(arg => console.log(arg));
}
用于数组或对象解构:
const [first, ...rest] = [1, 2, 3, 4];
console.log(first); // 1
console.log(rest); // [2, 3, 4]
const { a, ...others } = { a: 1, b: 2, c: 3 };
console.log(a); // 1
console.log(others); // { b: 2, c: 3 }
常用数组新增方法
类数组转换
常见三类类数组:arguments、元素集合、类似数组对象。Array.from
可以把它们转换为真正的数组。
arguments
function add() {
let collect = Array.from(arguments);
collect.push(40);
console.log(collect);
}
add(10,20,30)
元素集合
let divs = document.querySelectorAll('div');
console.log(Array.from(divs));
类似数组对象
let arrayLike = {
'0': 'a',
'1': 'b',
'2': 'c',
length: 3
};
let arr = Array.from(arrayLike);
console.log(arr);
值到数组转换
把一组值转换为数组:
Array.of(3, 11, 8) // [3,11,8]
copyWithin
数组实例的copyWithin()
方法,在当前数组内部,将指定位置的成员复制到其他位置(会覆盖原有成员),然后返回当前数组。也就是说,使用这个方法,会修改当前数组:
Array.prototype.copyWithin(target, start = 0, end = this.length)
三个参数: target(必需):从该位置开始替换数据;start(可选):从该位置开始读取数据,默认为 0;end(可选):到该位置前停止读取数据,默认等于数组长度。
[1, 2, 3, 4, 5].copyWithin(0, 3) // [4, 5, 3, 4, 5]
[1, 2, 3, 4, 5].copyWithin(0, 3, 4) // [4, 2, 3, 4, 5]
find和findIndex
数组实例的 find 方法,用于找出第一个符合条件的数组成员。它的参数是一个回调函数,所有数组成员依次执行该回调函数,直到找出第一个返回值为true的成员,然后返回该成员。如果没有符合条件的成员,则返回 undefined:
[1, 5, 10, 15].find(function(value, index, arr) {
return value > 9;
}) // 10
findIndex方法几乎一样,只是如果所有成员都不符合条件,则返回-1。这两个方法都可以发现NaN,弥补了数组indexOf方法的不足:
[NaN].indexOf(NaN)
// -1
[NaN].findIndex(function(value, index, arr) {
if(Number.isNaN(value)) {
console.log(index); // 0
}
})
fill
使用给定值,填充一个数组,还可以接受第二个和第三个参数,用于指定填充的起始位置和结束位置:
new Array(3).fill(7)
// [7, 7, 7]
['a', 'b', 'c'].fill(7)
// [7, 7, 7]
['a', 'b', 'c'].fill(7, 1, 2)
// ['a', 7, 'c']
entries、keys、values
用于遍历数组 for...of
循环进行遍历,唯一的区别是 keys()
是对键名的遍历、values()
是对键值的遍历, entries()
是对键值对的遍历。
for (let index of ['a', 'b'].keys()) {
console.log(index);
}
// 0
// 1
for (let elem of ['a', 'b'].values()) {
console.log(elem);
}
// 'a'
// 'b'
for (let [index, elem] of ['a', 'b'].entries()) {
console.log(index, elem);
}
// 0 "a"
// 1 "b"
includes
返回一个布尔值,表示某个数组是否包含给定的值,第二个参数表示搜索的起始位置,默认为0:
[1, 2, 3].includes(2) // true
[1, 2, 3].includes(4) // false
[1, 2, NaN].includes(NaN) // true
[1, 2, 3].includes(3, 3) // false
flat
数组的成员有时还是数组, flat() 用于将嵌套的数组“拉平”,变成一维的数组。该方法返回一个新数组,对原数据没有影响, flat() 默认只会“拉平”一层,如果想要“拉平”多层的嵌套数组,可以将 flat() 方法的参数写成一个整数,表示想要拉平的层数,默认为1:
[1, 2, [3, [4, 5]]].flat()
// [1, 2, 3, [4, 5]]
[1, 2, [3, [4, 5]]].flat(2)
// [1, 2, 3, 4, 5]
对象扩展
简写变量与函数
const o = {
method() {
return "Hello!";
}
};
// 等价于
const o = {
method: function() {
return "Hello!";
}
};
属性名可以是表达式
let propKey = 'itbaizhan';
let obj = {
[propKey]: true,
['a' + 'bc']: 123
};
扩展运算符
...
提取对象z
中所有可枚举属性展平并放入新对象,输出来和z
一样。
let z = { a: 3, b: 4 };
let n = { ...z };
console.log(n);
新增对象方法
同值相等
ES5 比较两个值是否相等,只有两个运算符:相等运算符()和严格相等运算符(=)。它们都有缺点,前者会自动转换数据类型,后者的NaN不等于自身,以及+0等于-0。JavaScript 缺乏一种运算,在所有环境中,只要两个值是一样的,它们就应该相等。
Object.is()
的用法:
Object.is('foo', 'foo')
// true
Object.is({}, {})
// false,对象是通过引用比较的,而不是通过值比较,每次对象字面量 {} 都会创建一个全新的对象。两个独立的对象永远不会相等,即使它们的内容完全相同。
+0 === -0 //true
NaN === NaN // false
Object.is(+0, -0) // false
Object.is(NaN, NaN) // true
对象合并
assign方法:
const target = { a: 1 };
const source1 = { b: 2 };
const source2 = { c: 3 };
Object.assign(target, source1, source2);
target // {a:1, b:2, c:3}
如果有同名属性,会被替换:
const target = { a: "hello" }
const source = { a: "world" }
console.log(Object.assign(target, source));
处理数组时,要注意相同的下标也会被替换,因为它会把数组当做对象处理:
Object.assign([1, 2, 3], [4, 5])
// [4, 5, 3],0、1下标被替换为新值
函数扩展
参数默认值
ES6 之前,不能直接为函数的参数指定默认值,只能采用变通的方法,ES6 允许为函数的参数设置默认值,即直接写在参数定义的后面:
// es5:
function log(info) {
info = info || 'World';
console.log(info);
}
// es6:
function log(info = 'world') {
console.log(info);
}
函数的默认值惰性求值,每次都要现场求值:
let x = 99;
function add(p = x + 1) { // 这一步并不会直接让默认值变成100
console.log(p);
}
add() // 100
x = 100;
// 重新求值
add() // 101
带有默认值的函数,如果想省略默认值,传参时必须严格对位:
function add(x = 1, y) {
console.log(x+y);
}
add(10) // NaN,传一个参数默认指向第一个
length属性
指定了默认值以后,函数的 length(本来的功能是获取参数个数)属性,将返回没有指定默认值的参数个数。也就是说,指定了默认值后,length 属性将失真:
function fn1(x) {}
console.log(fn1.length); // 1
function fn2(x=1) {}
console.log(fn2.length); // 0
应用
如果想要指定某一个参数不可省略,可以把它的默认值设为抛出异常:
function missingParameter() {
throw new Error('Missing parameter');
}
function add(x = missingParameter()) {
console.log(x);
}
add() // Missing parameter
箭头函数
单条语句的代码块部分:
var add = (x,y) => x+y;
// 等同于
var add = function (x,y) {
return x+y;
};
var add = () => 100;
// 等同于
var add = function () {
return 100;
};
如果箭头函数的代码块部分多于一条语句,就要使用大括号将它们括起来,并且使用 return 语句返回:
var add = (x,y) => {
var z = 10;
return x+y+z
};
注意,由于大括号被解释为代码块,所以如果箭头函数直接返回一个对象,必须在对象外面加上括号,否则会报错:
var add = (x,y) => ({x:10,y:20});
箭头函数中this的指向
箭头函数没有this,如果用了就指向外层:
var name = "fuufhjn"
var user = {
name: 'chromatic vizier',
getName() {
setTimeout(() => {
console.log(this.name);
})
}
}
user.getName(); // chromatic vizier
rest参数
ES6 引入 rest 参数(形式为 ...变量名 ),用于获取函数的多余参数,这样就不需要使用 arguments 对象了。rest 参数搭配的变量是一个数组,该变量将多余的参数放入数组中:
function add(...values) {
let sum = 0;
for (var val of values) { // values,即所有参数,被变为了一个数组
sum += val;
}
return sum;
}
add(2, 5, 3) // 10
function add(a, ...b,c) {} // 报错,rest参数只能作为最后一个参数,后面不能有其它参数
function add(a, ...b) {}
console.log(add.length); // 1,length不统计rest参数
严格模式
未声明变量赋值、删除不可删除的属性、意外创建全局变量、使用未来可能成为关键字的标识符、eval
中声明的变量泄漏到外部作用域、重复属性名和参数名、this
的隐式转换、八进制字面量都会报错。
function doSomething(a, b) {
'use strict';
// code
}
ES2016 做了一点修改,规定只要函数参数使用了默认值、解构赋值、或者扩展运算符,那么函数内部就不能显式设定为严格模式,否则会报错:
function doSomething(a, b = a) {
'use strict';
// code
}
函数名属性
函数的name方法返回函数名:
function foo() {}
foo.name // "foo"
Symbol类型
ES5 的对象属性名都是字符串,这容易造成属性名的冲突:
const user = {
id: 123,
name: '张三',
// 第三方库可能已经有一个toString方法
toString() {
return `User[${this.id}]`;
}
};
user.toString = function() {
return `${this.name} (ID: ${this.id})`;
};
console.log(user.toString()); // 输出覆盖后的toString
ES6 引入了一种新的原始数据类型 Symbol ,表示独一无二的值。它属于 JavaScript 语言的数据类型之一:
// 创建唯一的Symbol作为属性名
const toStringSymbol = Symbol('toString');
// 第三方库对象
const user = {
id: 123,
name: '张三',
// 原始toString方法
toString() {
return `User[${this.id}]`;
}
};
// 用Symbol添加toString方法
user[toStringSymbol] = function() {
return `${this.name} (ID: ${this.id})`;
};
// 现在可以同时访问两种toString
console.log(user.toString()); // "User[123]" (原始方法)
console.log(user[toStringSymbol]()); // "张三 (ID: 123)" (新添加的方法)
注意
Symbol 函数前不能使用 new 命令,否则会报错。这是因为生成的 Symbol 是一个原始类型的值,不是对象。也就是说,由于 Symbol 值不是对象,所以不能添加属性。基本上,它是一种类似于字符串的数据类型。
Symbol 函数的参数只是表示对当前 Symbol 值的描述,因此相同参数的 Symbol 函数的返回值是不相等的。
let s1 = Symbol();
let s2 = Symbol();
s1 === s2 // false
let s1 = Symbol('itbaizhan');
let s2 = Symbol('itbaizhan');
s1 === s2 // false
如果想让两个Symbol相等(复制一份),应该用for属性:
let s1 = Symbol.for('itbaizhan');
let s2 = Symbol.for('itbaizhan');
console.log(s1 === s2); // true
可以使用description属性获取描述:
const sym = Symbol('itbaizhan');
sym.description // "itbaizhan"
Symbol 作为属性名,遍历对象的时候,该属性不会出现在for...in、for...of 循环中,也不会被 Object.keys() 返回。有一个 Object.getOwnPropertySymbols() 方法,可以获取指定对象的所有 Symbol 属性名。该方法返回一个数组,成员是当前对象的所有用作属性名的 Symbol 值:
let mySymbol = Symbol("age");
var user = {
name:"iwen",
[mySymbol]:20
}
for(let item in user) {
console.log(item); // name
}
const objectSymbols =
Object.getOwnPropertySymbols(user);
console.log(objectSymbols); // [Symbol(age)]
Proxy修改默认行为
Proxy 用于修改某些操作的默认行为,等同于在语言层面做出修改,所以属于一种“元编程”(meta programming),即对编程语言进行编程。可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。 Proxy 这个词的原意是代理,用在这里表示由它来“代理”某些操作,可以译为“代理器”:
// 1. 创建目标对象
const target = {
name: '张三',
age: 25
};
// 2. 创建 Proxy 代理对象
const handler = {
get(target, property) {
console.log(`正在读取属性: ${property}`);
return target[property];
}
};
const proxy = new Proxy(target, handler);
// 3. 使用代理对象
console.log(proxy.name);
// 输出:
// 正在读取属性: name
// 张三
console.log(proxy.age);
// 输出:
// 正在读取属性: age
// 25
其中,target
- 要代理的原始对象,handler
定义拦截行为的对象。这里的 get
方法拦截所有属性读取操作proxy
生成的代理对象,操作它会触发 handler 中的拦截器。
Reflect
检测一个对象是否存在特定属性:
Reflect.has(duck, 'color');
// true
Reflect.has(duck, 'haircut');
// false
返回对象自身属性:
const duck = {
name: 'Maurice',
color: 'white',
greeting: function () {
console.log(`Quaaaack! My name is ${this.name}`);
}
}
console.log(Reflect.ownKeys(duck));
// (3) ['name', 'color', 'greeting']
为对象添加新属性:
const duck = {
name: 'Maurice',
color: 'white',
greeting: function () {
console.log(`Quaaaack! My name is ${this.name}`);
}
}
Reflect.set(duck, 'eyes', 'black');
console.log(duck);
与Proxy配合实现拦截对象属性的设置操作,并在设置成功后打印日志:
// 1. 创建原始对象
var user = {};
// 2. 创建Proxy代理对象
var obj = new Proxy(user, {
// set拦截器,在属性被设置时调用
set: function (target, name, value) {
// 使用Reflect.set执行默认的设置行为
var success = Reflect.set(target, name, value);
// 如果设置成功,打印日志
if (success) {
console.log('property ' + name + ' on ' + target + ' set to ' + value);
}
// 返回是否设置成功
return success;
}
});
// 3. 使用代理对象
obj.name = "iwen";
// property name on [object Object] set to iwen
console.log(obj.name);
// iwen
同步和异步
同步,就是调用某个东西时,调用方得等待这个调用返回结果才能继续往后执行。
异步,和同步相反调用方不会理解得到结果,而是在调用发出后调用者可以继续执行后续操作,被调用者通过状态来通知调用者,或者通过回调函数来处理这个调用。
一个异步任务调度的例子:
var n = 0;
setTimeout(function () {
n++;
console.log(n);
}, 1000); // 设置一个1秒后执行的回调函数
console.log(n);
主线程同步代码执行完毕后,开始等待,大约1秒后,定时器回调被放入任务队列,事件循环从队列中取出该回调执行。
也就是说,主线程中setTimeout会被跳过,直接去打印n。主线程执行完才会开始依次执行回调。所以这段代码先输出0再输出1。
Promise对象
Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。
Promise是一个容器,保存着未来才会结束的事件(通常是异步操作)的结果。从Promise对象可以获得异步操作的消息。有了Promise 对象,就可以将异步操作以同步操作的流程表达出来, 避免了层层嵌套的回调函数。
const promise = new Promise((resolve, reject) => {
// Some code
if(async_success) {
resolve(value);
} else {
reject(error);
}
});
Promise 的构造函数接受一个函数作为参数,该函数的两个参数分别是 resolve 和 reject 。它们是两个函数,由 JavaScript 引擎提供,不用自己部署。Promise实例生成以后,可以用 then 方法分别指定状态的回调函数:
promise.then((value) {
// success
}, function(error) {
// failure
});
对比没有Promise时,使用多层回调的写法:
function asyncOperation(callback) {
// Some code
if(async_success) {
callback(null, value); // 第一个参数是错误对象,null表示没有错误
} else {
callback(error); // 有错误时传递错误对象
}
}
// 使用回调函数
asyncOperation((err, result) => {
if (err) {
// 处理错误
console.error('Error:', err);
} else {
// 处理成功结果
console.log('Success:', result);
}
});
如果有多种错误需要处理,会形成回调地狱:
asyncOperation1((err1, result1) => {
if (err1) { /* 处理错误 */ }
asyncOperation2(result1, (err2, result2) => {
if (err2) { /* 处理错误 */ }
asyncOperation3(result2, (err3, result3) => {
if (err3) { /* 处理错误 */ }
// 最终处理
});
});
});
但是使用Promise可以用then写得更清晰:
asyncOperation1()
.then(result1 => asyncOperation2(result1))
.then(result2 => asyncOperation3(result2))
.then(result3 => {
// 最终处理
})
.catch(err => {
// 统一错误处理
});
Promise方法
then
then 方法是定义在原型对象 Promise.prototype 上的。它的作用是为 Promise 实例添加状态改变时的回调函数。then 方法返回的是一个新的 Promise 实例。因此可以采用链式写法,即then方法后面再调用另一个 then 方法:
getJSON("/posts.json").then(function(json) {
return json.post;
}).then(function(post) {
// ...
}, function(err) {
console.log("rejected: ", err);
});
getJSON调完会返回一个Promise对象,如果成功,触发then。then里面有一个回调函数,接收解析后的json。如果失败,会跳过所有then,找到最近的错误处理,这里是第二个then的第二个参数function(err) {console.log("rejected: ", err);}
。
关于回调函数的参数来源
getJSON
返回一个Promise 对象,而不是直接的 JSON 数据。但Promise 对象在 resolved(成功)时,会将其解析值(resolved value)传递给下一个 .then
的回调函数。Promise 在 resolved 时返回的数据类型完全取决于 resolve(value)
中传入的 value
,它可以是任何合法的 JavaScript 值,具体看实现时是怎么写的。
catch、finally
catch
是.then(null, rejection)
或.then(undefined, rejection)
的别名,用于指定发生错误时的回调函数;finally
用于指定不管 Promise 对象最后状态如何,都会执行的操作::
getJSON("http://iwenwiki.com/api/blueberrypai/getChengpinDetails.php").then(
data => {
console.log(data)
throw new Error('test');
}, error => {
console.log("error"+error);
}).catch(err => {
console.log("catch"+err);
}).finally(() => {
console.log("will be executed any way.")
})
all
Promise.all()
方法用于将多个 Promise 实例,包装成一个新的 Promise 实例 优点就是多个promise都有结果,才会触发 then 或者 catch:
Promise.all([promise1,promise2]).then(data => {
console.log(data);
}).catch(error => {
console.log(error);
})
Generator函数
写法
Generator 函数是 ES6 提供的一种异步编程解决方案,语法行为与传统函数完全不同,语法上,首先可以把它理解成,Generator 函数是一个状态机,封装了多个内部状态。
形式上,Generator 函数是一个普通函数,但是有两个特征。一是,function 关键字与函数名之间有一个星号;二是,函数体内部使用yield表达式,定义不同的内部状态。
function* helloWorldGenerator() {
yield 'hello';
yield 'world';
return 'ending';
}
var hw = helloWorldGenerator();
console.log(hw.next());
// { value: 'hello', done: false }
console.log(hw.next());
// { value: 'world', done: false }
console.log(hw.next());
// { value: 'ending', done: true }
console.log(hw.next());
// { value: undefined, done: true }
每次用next,都会执行到下一个yield处。如果到了return还继续next,返回undefined。
可以用for…of来遍历状态:
function* foo() {
yield 1;
yield 2;
yield 3;
yield 4;
yield 5;
return 6;
}
for (let v of foo()) {
console.log(v);
}
// 1 2 3 4 5
// 这种方式不会遍历到return值
应用
function ajax(url) {
$.getJSON(url, (data) => {
info.next(data)
})
}
function* getInfo() {
const ids = yield ajax("http://localhost/generator/list.php");
const name = yield ajax("http://iwenwiki.com/api/generator/id.php?id="+ids[0]);
const infos = yield ajax("http://iwenwiki.com/api/generator/name.php?name="+name.name)
console.log(infos);
}
var info = getInfo();
info.next();
首次调用 info.next()
,生成器开始执行,遇到第一个 yield
,调用 ajax()
发送请求,生成器暂停。
请求成功后,回调函数执行 info.next(data)
,将data
传给 yield
,赋值给 ids
……以此类推直到最终的yield
。
Async函数
async
函数是generator
函数的语法糖。
function ajax(url) {
return new Promise(function(resolve,reject) {
$.getJSON(url, function(result) {
resolve(result);
}, function(error) {
reject(error);
})
})
}
async function getInfo() {
let ids = await ajax("http://iwenwiki.com/api/generator/list.php");
let names = await ajax("http://iwenwiki.com/api/generator/id.php?id="+ids[0]);
let infos = await ajax("http://iwenwiki.com/api/generator/name.php?name=" + names.name);
console.log(infos);
}
getInfo();
内部用了await
,函数上就必须加async。await会等待后面的异步操作的返回值。
Fetch API
概念
是 XMLHttpRequest 的升级版,用于在 JavaScript 脚本里面发出 HTTP 请求。fetch() 的功能与 XMLHttpRequest 基本相同,但有三个主要的差异:
- fetch() 使用 Promise,不使用回调函数,简化了写法,写起来更简洁;
- fetch() 采用模块化设计,API 分散在多个对象上(Response 对象、Request 对象、Headers 对象),更合理;
- fetch() 通过数据流(Stream 对象)处理数据,可以分块读取,有利于提高网站性能表现,减少内存占用,对于请求大文件或者网速慢的场景相当有用。XMLHTTPRequest 对象不支持数据流,所有的数据必须放在缓存里,不支持分块读取,必须等待全部拿到后,再一次性吐出来。
用法
fetch() 接受一个 URL 字符串作为参数,默认向该网址发出 GET 请求,返回一个 Promise 对象:
fetch(url).then(...).catch(...)
常用的完整写法(包含常用参数):
fetch("http://iwenwiki.com/api/blueberrypai/login.php", {
method: "POST",
headers: {
"Content-type": "application/x-www-form-urlencoded; charset=UTF-8",
},
body:
"[email protected]&password=iwen123&verification_code=crfvw"
}).then(res => {
return res.json()
}).then(data => {
console.log(data);
})
关于Class
之前生成实例对象的传统方法是通过构造函数:
function Point(x, y) {
this.x = x;
this.y = y;
}
Point.prototype.toString = function () {
return '(' + this.x + ', ' + this.y + ')';
};
var p = new Point(1, 2);
ES6 提供了更接近传统语言的写法,引入了 Class 这个概念,作为对象的模板。通过class关键字,可以定义类。ES6 的 class 可以看作只是一个语法糖,它的绝大部分功能,ES5 都可以做到,新的 class 写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已:
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
toString() {
return '(' + this.x + ', ' + this.y + ')';
}
}
constructor方法
constructor() 方法是类的默认方法,通过 new 命令生成对象实例时,自动调用该方法。一个类必须有一个空的 constructor() 方法,如果没有显式定义, constructor() 方法会被默认添加。这部分和c++是一样的:
class Point {
}
// 等同于
class Point {
constructor() {}
}
生成实例
必须用new
:
var point = new Point(2, 3);
取值getter与存值setter
类内部定义的get
与set
可以触发默认的类名.
的写法:
class Person {
constructor(name) {
this.name = name;
}
get n() {
return this.name
}
set n(value) {
this.name = value;
}
}
var p = new Person("iwen")
console.log(p.n);
p.n = "ime"
console.log(p.n);
注意
类和模块的内部,默认就是严格模式,所以不需要使用 use strict 指定运行模式;
类不存在变量提升(在定义前使用);
本质上,ES6 的类只是 ES5 的构造函数的一层包装,所以函数的许多特性都被Class继承,包括 name 属性。
Class属性和方法
一般属性和方法
class People {
constructor(name,age) {
this.name = name;
this.age = age;
}
say() {
console.log(this.name,this.age);
}
}
var p = new People("iwen",20);
p.say()
console.log(p.name,p.age);
静态方法
属于类本身,而不属于某个对象:
class Person {
static classMethod() {
console.log("Hello");
}
}
静态方法内的this
关键字指向的是类本身而不是实例。
静态属性
静态属性指的是 Class 本身的属性,即 Class.propName
。
在实例属性前加一个static。
私有的方法与属性
在方法前面添加下划线("_")代表就是静态方法(这只是一种约束,不是语法规范):
class People {
// 公有方法
say() {
return this._hi()
}
// 私有方法
_hi() {
return "hi"
}
}
也可以用#表示:
class Counter {
#count = 0;
increment() {
this.#count++;
}
#sum() {
console.log(this.#count++);
}
}
Class的继承
extends、super等的用法都和java一样。
关于Module
ES6 模块是通过 export 命令显式指定输出的代码,再通过 import 命令输入:
export var Hello = "hello" // hello.js文件
import { Hello } from "./hello.js" // index.js文件
export命令
通过export导出的东西可以被别的文件通过import获取:
export var firstName = 'sxt';
export var lastName = 'itbaizhan';
export function add(x, y) {
return x + y;
};
export 命令规定的是对外的接口,必须与模块内部的变量建立一一对应关系:
// 报错
export 1;
// 报错
var m = 1;
export m;
// 写法一
export var m = 1;
// 写法二
var m = 1;
export { m };
// 写法三
var m = 1;
export {m as n};
import命令
import { firstName, lastName, year } from './profile.js';
import { value as val } from './value.js';
// 可用星号指定一个对象,所有输出值都加载在这个对象上面
import * as circle from './circle';
export default命令
使用 import 命令的时候,用户需要知道所要加载的变量名或函数名,否则无法加载。但是,用户肯定希望快速上手,未必愿意阅读文档。
为了给用户提供方便,让他们不用阅读文档就能加载模块,就要用到export default 命令,为模块指定默认输出。
// export-default.js
export default function () {
console.log('foo');
}
// import-default.js
import customName from './export-default';
customName(); // 'foo'