参考资料:
ES6这些就够了
ECMAScript 6 入门
我们都知道在ES6以前,var关键字声明变量。无论声明在何处,都会被视为声明在函数的最顶部(不在函数内即在全局作用域的最顶部)。这就是函数变量提升。例如:
function aa() {
if(bool) {
var test = 'hello man'
} else {
console.log(test)
}
}
以上的代码实际上是:
function aa() {
var test // 变量提升
if(bool) {
test = 'hello man'
} else {
//此处访问test 值为undefined
console.log(test)
}
//此处访问test 值为undefined
}
所以不用关心bool是否为true or false。实际上,无论如何test都会被创建声明。
而在ES6中,我们通常用let和const来声明,let表示变量、const表示常量。let和const都是块级作用域。怎么理解这个块级作用域?
在一个函数内部
在一个代码块内部
说白了 {}大括号内的代码块即为let 和 const的作用域。
注意:
function aa() {
if(bool) {
let test = 'hello man'
} else {
//test 在此处访问不到
console.log(test)
}
}
let的作用域是在它所在当前代码块,但不会被提升到当前函数的最顶部。
注意:let
不允许在相同作用域内,重复声明同一个变量。因此,不能在函数内部重新声明参数。
var funcs = []
for (var i = 0; i < 10; i++) {
funcs.push(function() { console.log(i) })
}
funcs.forEach(function(func) {
func()
})
这样的面试题是大家常见,很多同学一看就知道输出 10 十次。
但是如果我们想依次输出0到9呢?两种解决方法。直接上代码。
// ES5告诉我们可以利用闭包解决这个问题
var funcs = []
for (var i = 0; i < 10; i++) {
func.push((function(value) {
return function() {
console.log(value)
}
}(i)))
}
// es6
for (let i = 0; i < 10; i++) {
func.push(function() {
console.log(i)
})
}
达到相同的效果,es6简洁的解决方案是不是更让你心动!!!
var a = 123;
if(true){
a = 456;
let a = 5;
console.log(a);
}
尽管a是全局变量,但是在代码块里let了a,这个代码块便形成封闭作用域,全局变量a也会失效,这时a = 456是在let a之前操作,这时的a是未定义的,所以a = 456这个操作会报错,形成暂时性死区。
暂时性死区(temporal dead zone,简称 TDZ)的本质就是,只要一进入当前作用域,所要使用的变量就已经存在了,但是不可获取,只有等到声明变量的那一行代码出现,才可以获取和使用该变量。
const name = 'lux'
name = 'joe' //再次赋值此时会报错
const作用域和let相同,声明的常量也不提升,也存在暂时性死区,也不可重复声明;
const实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址不得改动。所以,const定义一个对象后,对象虽不能再定义,但是依然可以改变对象中的属性。如果希望对象中的属性也不可被更改,就用对象冻结方法Object.freeze()
。
const obj = {
"name": "666"
};
Object.freeze(obj);
// 常规模式时,下面一行不起作用;
// 严格模式时,该行会报错
obj.name = 'hi';
// console.log(obj);
模板字符串可以将表达式嵌入字符串中进行拼接,使用${}
来界定。大括号内部可以放入任意的 JavaScript 表达式,可以进行运算,以及引用对象属性。模板字符串之中还能调用函数。
//es5
var name = 'lux'
console.log('hello' + name)
//es6
const name = 'lux'
console.log(`hello ${name}`) //hello lux
function fn() {
return "Hello World";
}
`foo ${fn()} bar`
// foo Hello World bar
toString
方法。在ES5时我们通过反斜杠(\)来做多行字符串或者字符串一行行拼接。ES6反引号(``)直接搞定。
// es5
var msg = "Hi \
man!
"
// es6
const template = `
hello world
`
如果使用模板字符串表示多行字符串,所有的空格和缩进都会被保留在输出之中。如果你不想要这个换行,可以使用trim
方法消除它。
模板字符串可以紧跟在一个函数名后面,该函数将被调用来处理这个模板字符串。这被称为“标签模板”功能(tagged template)。
alert`123`
// 等同于
alert(123)
let a = 5;
let b = 10;
tag`Hello ${ a + b } world ${ a * b }`;
// 等同于
tag(['Hello ', ' world ', ''], 15, 50);
function tag(stringArr, ...values){
// ...
}
标签模板其实不是模板,而是函数调用的一种特殊形式。“标签”指的就是函数,紧跟在后面的模板字符串就是它的参数。
但是,如果模板字符里面有变量,就不是简单的调用了,而是会将模板字符串先处理成多个参数,再调用函数。
let str = 'hahay'
console.log(str.includes('y',3)) // true
console.log(str.startsWith('y')) // false
console.log(str.endsWith('y')) // true
使用第二个参数n时,endsWith的行为与其他两个方法有所不同。它针对前n个字符,而其他两个方法针对从第n个位置直到字符串结束。
repeat
方法返回一个新字符串,表示将原字符串重复n
次。
let s = 'he'
console.log(s.repeat(3)) // 'hehehe'
'na'.repeat('na') // ""
'na'.repeat('3') // "nanana"
如果某个字符串不够指定长度,可使用padStart()
在头部或padEnd()
在尾部补全。
'x'.padStart(5, 'ab') // 'ababx'
'x'.padEnd(5, 'ab') // 'xabab'
padStart
和padEnd
一共接受两个参数,第一个参数用来指定字符串的最小长度,第二个参数是用来补全的字符串。在ES5我们给函数定义参数默认值是怎么样?
function action(num) {
num = num || 200
//当传入num时,num为传入的值
//当没传入参数时,num即有了默认值200
return num
}
但细心观察的同学们肯定会发现,num传入为0的时候就是false, 此时num = 200
与我们的实际要的效果明显不一样。
ES6为参数提供了默认值。在定义函数时便初始化了这个参数,以便在参数没有被传递进去时使用。
function action(num = 200) {
console.log(num)
}
action() //200
action(300) //300
如果非尾部的参数设置默认值,实际上这个参数是没法省略的。
function f(x = 1, y) {
return [x, y];
}
f() // [1, undefined]
f(2) // [2, undefined])
f(, 1) // 报错
f(undefined, 1) // [1, 1]
ES6 引入 rest 参数(形式为...变量名
),用于获取函数的多余参数,这样就不需要使用arguments
对象了。rest 参数搭配的变量是一个数组,该变量将多余的参数放入数组中。
function add(...values) {
let sum = 0;
for (var val of values) {
sum += val;
}
return sum;
}
add(2, 5, 3) // 10
注意,rest 参数之后不能再有其他参数(即只能是最后一个参数),否则会报错。
箭头函数最直观的三个特点:
[1,2,3].map( x => x + 1 )
//等同于:
[1,2,3].map((function(x){
return x + 1
}).bind(this))
{}
。var people = name => 'hello' + name
//参数name就没有括号
var people = (name, age) => {
const fullName = 'h' + name
return fullName
}
//如果缺少()或者{}就会报错
ES5我们对于对象都是以键值对的形式书写,是有可能出现键值对重名的。例如:
function people(name, age) {
return {
name: name,
age: age
};
}
键值对重名,ES6可以简写如下:
function people(name, age) {
return {
name,
age
};
}
ES6 同样改进了为对象字面量方法赋值的语法。ES5为对象添加方法:
const people = {
name: 'lux',
getName: function() {
console.log(this.name)
}
}
ES6通过省略冒号与 function 关键字,将这个语法变得更简洁
const people = {
name: 'lux',
getName () {
console.log(this.name)
}
}
ES6 允许字面量定义对象时,用把表达式放在方括号内的方法作为对象的属性名。表达式还可以用于定义方法名。
let propKey = 'foo';
let obj = {
[propKey]: true,
['a' + 'bc']: 123
};
注意,属性名表达式与简洁表示法,不能同时使用,会报错。
注意,属性名表达式如果是一个对象,默认情况下会自动将对象转为字符串[object Object]
,这一点要特别小心。
const keyA = {a: 1};
const keyB = {b: 2};
const myObject = {
[keyA]: 'valueA',
[keyB]: 'valueB'
};
myObject // Object {[object Object]: "valueB"}
上面代码中,[keyA]
和[keyB]
得到的都是[object Object]
,所以[keyB]
会把[keyA]
覆盖掉,而myObject
最后只有一个[object Object]
属性。
ES6 对象提供了Object.assign()
这个方法来实现浅复制。Object.assign()
可以把任意多个源对象自身可枚举的属性拷贝给目标对象,然后返回目标对象。第一参数即为目标对象。在实际项目中,我们为了不改变源对象。一般会把目标对象传为{}
。
const obj = Object.assign({}, objA, objB)
Object.assign([1, 2, 3], [4, 5])
// [4, 5, 3]
// Object.assign把数组视为属性名为 0、1、2 的对象,因此源数组的 0 号属性4覆盖了目标数组的 0 号属性1。
注意:
Object.assign
会直接返回该参数。如果该参数不是对象,则会先转成对象,然后返回。undefined
和null
无法转成对象,所以如果它们作为首参数,就会报错。undefined
和null
不在首参数,就不会报错。其他类型的值(即数值、字符串和布尔值)不在首参数,也不会报错。但是,除了字符串会以数组形式,拷贝入目标对象,其他值都不会产生效果。Object.assign
只能进行值的复制,如果要复制的值是一个取值函数,那么将求值后再复制。Object.assign()
只能克隆原始对象自身的值,不能克隆它继承的值。如果想要保持继承链,可以采用下面的代码。
function clone(origin) {
let originProto = Object.getPrototypeOf(origin);
return Object.assign(Object.create(originProto), origin);
}
数组和对象是JS中最常用也是最重要表示形式。为了简化提取信息,ES6新增了解构(Destructuring),这是将一个数据结构分解为更小的部分的过程。
ES5我们提取对象中的信息形式如下:
const people = {
name: 'lux',
age: 20
}
const name = people.name
const age = people.age
console.log(name + ' --- ' + age)
是不是觉得很熟悉,没错,在ES6之前我们就是这样获取对象信息的,一个一个获取。现在,解构能让我们从对象或者数组里取出数据存为变量,例如
//对象
const people = {
name: 'lux',
age: 20
}
const { name, age } = people
console.log(`${name} --- ${age}`)
//数组
const color = ['red', 'blue']
const [first, second] = color
console.log(first) //'red'
console.log(second) //'blue'
基本用法
let [a,b,c] = [1,2,3];
console.log(a); // 1
let [a,[b,c],d] = [1,[3],5];
console.log(b); // 3
let [,,c] = ['isA','isB','isC'];
console.log(c); // isC
let [a] = 1; // 报错,=号两边模式必须相同
let { a } = { b: 'bbb', a: 'aaa' };
console.log(a); // aaa
// 如果解构不成功,变量的值就等于`undefined`。
let { a } = { b: "bbb", c: "ccc" };
console.log(a); // undefined
默认值
let [a = 'a'] = []; // 设置默认值
console.log(a); // a
let [a,b = 'isB'] = ['a']; // b设置默认值
console.log(a); // a
console.log(b); // isB
// 只有当一个数组成员严格等于`undefined`,默认值才会生效。
let [a = 'aaa',b] = [undefined,'bbb'];
console.log(a); // aaa
console.log(b); // bbb
对象的解构赋值
数组的元素是按次序排列的,变量的取值由它的位置决定;而对象的属性没有次序,变量必须与属性同名。
对象的解构赋值的内部机制,是先找到同名属性,然后再赋给对应的变量。真正被赋值的是后者,而不是前者。
// 变量名与属性名一致
let { b} = { c: 'ccc', b: 'bbb'};
console.log(b); // bbb
// 变量名与属性名不一致
let { b: a} = { c: 'ccc', b: 'bbb'};
console.log(a); // bbb
let obj = { first: 'hello', second: 'world'};
let { first: f, second: s } = obj;
console.log(f); // hello
console.log(s); // world
const node = {
loc: {
start: {
line: 1,
column: 5
}
}
};
let { loc, loc: { start }, loc: { start: { line }} } = node;
line // 1
loc // Object {start: Object}
start // Object {line: 1, column: 5}
字符串的解构赋值
let [a,b,c,d,e] = 'hello';
console.log(a); // h
console.log(b); // e
console.log(c); // l
console.log(d); // l
console.log(e); // o
属性的解构赋值
// length属性的解构赋值
let { length: len } = 'hello word';
console.log(len); // 10
// 其他属性的解构赋值
let { toString: s } = 123;
console.log(isNaN(s)); // true
解构赋值的规则是,只要等号右边的值不是对象或数组,就先将其转为对象。由于undefined
和null
无法转为对象,所以对它们进行解构赋值,都会报错。
let { prop: x } = undefined; // TypeError
let { prop: y } = null; // TypeError
函数参数的解构赋值
function add([x,y]){
return x + y;
}
console.log( add([1,2]) ); // 3
function move({ x = 0, y = 0} = {}){
return { x, y };
}
console.log( move() ); // { x: 0, y: 0 }
console.log( move({ x: 5, y: 9}) ); // { x: 5, y: 9 }
console.log( move({ y:6 }) ); // { x: 0, y: 6 }
console.log( move({}) ); // { x: 0, y: 0 }
典型用法
// 1.变换x,y值
let x = 3, y = 5;
[ x, y ] = [ y, x ];
console.log(x); // 5
console.log(y); // 3
// 2.从函数返回多个值
function example(){
return [1, 2, 3];
}
let [a, b, c] = example();
console.log(a); // 1
console.log(b); // 2
console.log(c); // 3
// 3.函数参数的定义
// 参数是一组有次序的值
function f([x, y, z]){...}
f([1, 2, 3]);
// 参数是一组无次序的值
function f({x, y, z}){...}
f({y: 1, x: 2, z: 3});
// 4.解构赋值提取json数据
let jsonData = {
id: 42,
status: "OK",
data: [867, 5309]
};
let { id, status, data: number } = jsonData;
console.log(id, status, number);
// 42, "OK", [867, 5309]
// 5.集合遍历
let map = new Map();
map.set('first', 'hello');
map.set('second', 'world');
for(let [key, val] of map){
console.log(key + ' is ' + val); // first is hello second is world
}
// 获取键名
for (let [key] of map) {
// ...
}
// 获取键值
for (let [,value] of map) {
// ...
}
// 6.输入模块的指定方法
const { SourceMapConsumer, SourceNode } = require("source-map");
展开运算符可用于组装对象或者数组。
//数组
const color = ['red', 'yellow']
const colorful = [...color, 'green', 'pink']
console.log(colorful) //[red, yellow, green, pink]
//对象
const alp = { fist: 'a', second: 'b'}
const alphabets = { ...alp, third: 'c' }
console.log(alphabets) //{ "fist": "a", "second": "b", "third": "c"
}
有时候我们想获取数组或者对象除了前几项或者除了某几项的其他项。
//数组
const number = [1,2,3,4,5]
const [first, ...rest] = number
console.log(rest) //2,3,4,5
//对象
const user = {
username: 'lux',
gender: 'female',
age: 19,
address: 'peking'
}
const { username, ...rest } = user
console.log(rest) //{"address": "peking", "age": 19, "gender": "female"}
对于 Object 而言,还可以用于组合成新的 Object 。(ES2017 stage-2 proposal) 当然如果有重复的属性名,右边覆盖左边。
const first = {
a: 1,
b: 2,
c: 6,
}
const second = {
c: 3,
d: 4
}
const total = { ...first, ...second }
console.log(total) // { a: 1, b: 2, c: 3, d: 4 }
Set就是没有重复的值的数组。
//创建Set
const s = new Set();
[2, 3, 5, 4, 5, 2, 2].forEach(x => s.add(x));
for (let i of s) {
console.log(i);
}
// 2 3 5 4
//创建Map
const m = new Map();
const o = {p: 'Hello World'};
m.set(o, 'content')
m.get(o) // "content"
m.has(o) // true
m.delete(o) // true
m.has(o) // false
Set 函数可以接受一个数组(或者具有 iterable 接口的其他数据结构)作为参数,用来初始化。
Map 也可以接受一个数组(任何具有 Iterator 接口、且每个成员都是一个双元素的数组的数据结构)作为参数。该数组的成员是一个个表示键值对的数组。
const set = new Set([1, 2, 3, 4, 4]);
const map = new Map([
['name', '张三']
]);
map.size // 1
map.has('name') // true
map.get('name') // "张三"
Set和Map都具有属性size
,用于返回Set
或Map
实例的成员总数。
Set操作方法:
add(value)
:添加某个值,返回 Set 结构本身。delete(value)
:删除某个值,返回一个布尔值,表示删除是否成功。has(value)
:返回一个布尔值,表示该值是否为Set
的成员。clear()
:清除所有成员,没有返回值。向 Set 加入值的时候,不会发生类型转换,所以5
和"5"
是两个不同的值。Set 内部判断两个值是否不同,使用的算法叫做“Same-value-zero equality”,它类似于精确相等运算符(===
),主要的区别是NaN
等于自身,而精确相等运算符认为NaN
不等于自身。另外,两个对象总是不相等的。
Map操作方法:
set(key, value)
:设置键名key
对应的键值为value
,然后返回整个 Map 结构。如果key
已经有值,则键值会被更新,否则就新生成该键。get(key)
:读取key
对应的键值,如果找不到key
,返回undefined
。has(key)
:返回一个布尔值,表示某个键是否在当前 Map 对象之中。delete(key)
:删除某个键,返回true
。如果删除失败,返回false
。clear()
:清除所有成员,没有返回值。Map 的键实际上是跟内存地址绑定的,只要内存地址不一样,就视为两个键。这就解决了同名属性碰撞(clash)的问题。
const map = new Map();
map.set(['a'], 555);
map.get(['a']) // undefined
上面代码的set
和get
方法,表面是针对同一个键,但实际上这是两个值,内存地址是不一样的,因此get
方法无法读取该键,返回undefined
。
如果 Map 的键是一个简单类型的值(数字、字符串、布尔值),则只要两个值严格相等,Map 将其视为一个键,比如0
和-0
就是一个键,布尔值true
和字符串true
则是两个不同的键。另外,undefined
和null
也是两个不同的键。虽然NaN
不严格相等于自身,但 Map 将其视为同一个键。
Set和Map的遍历方法基本一致:
keys()
:返回键名的遍历器values()
:返回键值的遍历器entries()
:返回键值对的遍历器forEach()
:使用回调函数遍历每个成员注意:
Set
和Map
的遍历顺序就是插入顺序。Set
的key就是value。values
方法。entries
方法。Array.from
方法可以将 Set 结构转为数组。
const items = new Set([1, 2, 3, 4, 5]);
const array = Array.from(items);
使用 Set 可以很容易地实现并集(Union)、交集(Intersect)和差集(Difference)。
let a = new Set([1, 2, 3]);
let b = new Set([4, 3, 2]);
// 并集
let union = new Set([...a, ...b]);
// Set {1, 2, 3, 4}
// 交集
let intersect = new Set([...a].filter(x => b.has(x)));
// set {2, 3}
// 差集
let difference = new Set([...a].filter(x => !b.has(x)));
// Set {1}
import导入模块、export导出模块。
// example.js
var firstName = 'Michael';
var lastName = 'Jackson';
var year = 1958;
export {
firstName,
lastName,
//使用as关键字重命名
year as birthday
};
// 部分导出
export class App extend Component {};
// 导出默认, 有且只有一个默认
export default class { ... }
export
可以输出变量、函数、类(class)。export
输出的变量就是本来的名字,但是可以使用as
关键字重命名。export
命令规定的是对外的接口,必须与模块内部的变量建立一一对应关系。export
语句输出的接口,与其对应的值是动态绑定关系,即通过该接口,可以取到模块内部实时的值。export
命令可以出现在模块的任何位置,只要处于模块顶层就可以。如果处于块级作用域内,就会报错。export default
命令其实只是输出一个叫做default
的变量,所以它后面不能跟变量声明语句,但可以直接将一个值写在export default
之后,相当于赋值给default
变量。//导入部分
import {name, age} from './example'
//有一种特殊情况,即允许你将整个模块当作单一对象进行导入
//该模块的所有导出都会作为对象的属性存在
import * as example from "./example.js"
console.log(example.name)
console.log(example.age)
console.log(example.getName())
//导入模块的默认输出,可以随意命名
import people from './example'
import
命令接受一对大括号,里面指定要从其他模块导入的变量名。大括号里面的变量名,必须与被导入模块对外接口的名称相同。import
命令要使用as
关键字,将输入的变量重命名。import
命令输入的变量都是只读的,因为它的本质是输入接口。也就是说,不允许在加载模块的脚本里面,改写接口。import
后面的from
指定模块文件的位置,可以是相对路径,也可以是绝对路径,.js
后缀可以省略。如果只是模块名,不带有路径,那么必须有配置文件,告诉 JavaScript 引擎该模块的位置。import
命令具有提升效果,会提升到整个模块的头部,首先执行。如果处于块级作用域内,就会报错。import
是静态执行,所以不能使用表达式和变量,这些只有在运行时才能得到结果的语法结构。import
语句会执行所加载的模块,如果多次重复执行同一句import
语句,那么只会执行一次,而不会执行多次。也就是说,import
语句是 Singleton 模式。如果在一个模块之中,先输入后输出同一个模块,import
语句可以与export
语句写在一起。
export { foo, bar } from 'my_module';
// 可以简单理解为
import { foo, bar } from 'my_module';
export { foo, bar };
// 整体输出
// export *命令会忽略my_module模块的default方法
export * from 'my_module';
// 默认接口的写法
export { default } from 'foo';
导入的时候有没有大括号的区别总结:
当用export default people
导出时,就用 import people
导入(不带大括号)
一个文件里,有且只能有一个export default
。但可以有多个export
。
当用export name
时,就用import { name }
导入(记得带上大括号)
当一个文件里,既有一个export default people
, 又有多个export name
或者 export age
时,导入就用 import people, { name, age }
当一个文件里出现n多个 export
导出很多模块,导入时除了一个一个导入,也可以用import * as example
在promise之前代码过多的回调或者嵌套,可读性差、耦合度高、扩展性低。通过Promise机制,扁平化的代码机构,大大提高了代码可读性;用同步编程的方式来编写异步代码,保存线性的代码逻辑,极大的降低了代码耦合性而提高了程序的可扩展性。
说白了就是用同步的方式去写异步代码。
发起异步请求
fetch('/api/todos')
.then(res => res.json())
.then(data => ({ data }))
.catch(err => ({ err }));
一篇关于面试题的很有意思。
setTimeout(function() {
console.log(1)
}, 0);
new Promise(function executor(resolve) {
console.log(2);
for( var i=0 ; i<10000 ; i++ ) {
i == 9999 && resolve();
}
console.log(3);
}).then(function() {
console.log(4);
});
console.log(5);
Excuse me?这个前端面试在搞事!
当然以上promise的知识点,这个只是冰山一角。需要更多地去学习应用。
生成器(generator)是能返回一个迭代器的函数。生成器函数也是一种函数,最直观的表现就是比普通的function多了个星号*,在其函数体内可以使用yield关键字,有意思的是函数会在每个yield后暂停。
这里生活中有一个比较形象的例子。咱们到银行办理业务时候都得向大厅的机器取一张排队号。你拿到你的排队号,机器并不会自动为你再出下一张票。也就是说取票机“暂停”住了,直到下一个人再次唤起才会继续吐票。
OK。说说迭代器。当你调用一个generator时,它将返回一个迭代器对象。这个迭代器对象拥有一个叫做next的方法来帮助你重启generator函数并得到下一个值。next方法不仅返回值,它返回的对象具有两个属性:done和value。value是你获得的值,done用来表明你的generator是否已经停止提供值。继续用刚刚取票的例子,每张排队号就是这里的value,打印票的纸是否用完就这是这里的done。
// 生成器
function *createIterator() {
yield 1;
yield 2;
yield 3;
}
// 生成器能像正规函数那样被调用,但会返回一个迭代器
let iterator = createIterator();
console.log(iterator.next().value); // 1
console.log(iterator.next().value); // 2
console.log(iterator.next().value); // 3
那生成器和迭代器又有什么用处呢?
围绕着生成器的许多兴奋点都与异步编程直接相关。异步调用对于我们来说是很困难的事,我们的函数并不会等待异步调用完再执行,你可能会想到用回调函数,(当然还有其他方案比如Promise比如Async/await)。
生成器可以让我们的代码进行等待。就不用嵌套的回调函数。使用generator可以确保当异步调用在我们的generator函数运行一下行代码之前完成时暂停函数的执行。
那么问题来了,咱们也不能手动一直调用next()方法,你需要一个能够调用生成器并启动迭代器的方法。就像这样子的
function run(taskDef) { //taskDef即一个生成器函数
// 创建迭代器,让它在别处可用
let task = taskDef();
// 启动任务
let result = task.next();
// 递归使用函数来保持对 next() 的调用
function step() {
// 如果还有更多要做的
if (!result.done) {
result = task.next();
step();
}
}
// 开始处理过程
step();
}
生成器与迭代器最有趣、最令人激动的方面,或许就是可创建外观清晰的异步操作代码。你不必到处使用回调函数,而是可以建立貌似同步的代码,但实际上却使用 yield 来等待异步操作结束。
有一个 Generator 函数,依次读取两个文件。
const fs = require('fs');
const readFile = function (fileName) {
return new Promise(function (resolve, reject) {
fs.readFile(fileName, function(error, data) {
if (error) return reject(error);
resolve(data);
});
});
};
const gen = function* () {
const f1 = yield readFile('/etc/fstab');
const f2 = yield readFile('/etc/shells');
console.log(f1.toString());
console.log(f2.toString());
};
写成async
函数,就是下面这样。
const asyncReadFile = async function () {
const f1 = await readFile('/etc/fstab');
const f2 = await readFile('/etc/shells');
console.log(f1.toString());
console.log(f2.toString());
};
async
函数返回的 Promise 对象,必须等到内部所有await
命令后面的 Promise 对象执行完,才会发生状态改变,除非遇到return
语句或者抛出错误。也就是说,只有async
函数内部的异步操作执行完,才会执行then
方法指定的回调函数。
let methodName = 'getPosition';
//定义类
class Point {
// 没有写constructor构造函数的话,会添加默认的constructor构造函数
constructor(x, y) {
this.x = x;
this.y = y;
}
// 可以通过表达式设置属性名和函数名
[methodName]() {
// ...
}
// #x是一个私有属性,它的读写都通过get #x()和set #x()来完成。
#xValue = 0;
// 实例属性
myProp = 42;
// 静态属性
static myStaticProp = 42;
// 使用get和set关键字,对某个属性设置存值函数和取值函数
get prop() {
return 'getter';
}
set prop(value) {
console.log('setter: '+value);
}
// Generator函数
* [Symbol.iterator]() {
yield x;
}
}
// 静态方法
static classMethod() {
return 'hello';
}
toString() {
return '(' + this.x + ', ' + this.y + ')';
}
}
let inst = new Point();
inst.prop = 123;
// setter: 123
inst.prop
// 'getter'
Point.classMethod() // 'hello'
class ColorPoint extends Point {
constructor(x, y, color) {
super(x, y); // 调用父类的constructor(x, y)
this.color = color;
}
toString() {
return this.color + ' ' + super.toString(); // 调用父类的toString()
}
}
new
调用,否则会报错。get
和set
关键字,对某个属性设置存值函数和取值函数,拦截该属性的存取行为。this
关键字,这个this
指的是类,而不是实例。super
对象上调用的。constructor
方法中调用super
方法,否则新建实例时会报错。这是因为子类没有自己的this
对象,而是继承父类的this
对象,然后对其进行加工。如果不调用super
方法,子类就得不到this
对象。