2017-08-17 1696
个人博客: alex-my.xyz
CSDN: blog.csdn.net/alex_my
由于var全局范围有效,i
最终值为10,所以无论是a[1] ()还是a[2] (),输出的统统都是10。
var a = [];
for (var i = 0; i < 10; i++) {
a[i] = function () {
console.log(i);
};
}
a[6](); // 10
let只在本轮循环中有效,所以每一次循环的i
变量其实都是一个新的变量,所以a1=1,a2=2。
var a = [];
for (let i = 0; i < 10; i++) {
a[i] = function () {
console.log(i);
};
}
a[6](); // 6
for
循环中的循环部分是一个父作用域,循环体是一个子作用域。let i = 'abc'
前添加console.log(i)
则会报错。因为只要快级作用域内存在let
命令,它所声明的变量的绑定了这个区域。在这个区域类,这个变量就是被let
修饰的。在声明之前就是用就会报错。同理于const。
for (let i = 0; i < 3; ++i) {
let i = 'abc';
console.log(i);
}
// 连续输出三个abc
// abc
// abc
// abc
let不允许在同一个作用域内,重复声明同一个变量。同理于const。
let a = 1;
var a = 2; // 报错, a已经定义了
console.log(a);
变量解构成功的示例
let [a, b, c] = [1, 2, 3];
let [a, , c] = [1, 2, 3]; // a=1,c=3
let [a, b, c] = [1]; // a=1,b=undefined,c=undefined
变量解构失败的示例
let [a] = 1;
let [a] = null;
let [a] = {}
变量解构允许有默认值
let [a = 1] = []; // a = 1
let [a = 1] = [null]; // a = null
let [a = 1] = [undefined]; // a = 1
function f() { console.log('function f'); }
let [a = f()] = [1]; // 并不会执行f()
对象解构赋值
let {A, B} = {A: 'a', B: 'b'}; // A = a, B = b
let {A: a, B: b} = {A: 'Aa', B: 'Bb'}; // a = Aa, b = Bb
对象解构允许有默认值
let {A: a = 'aa', B: b = 'bb'} = {}; // a = aa, b = bb
字符串解构
let [a, b, c, d, e, f] = 'hello world'; // a = h, b = e, c = l,...
let {length : len} = 'hello world'; // len = 11, 字符串会被转为一个对象,对象中有length属性。
用途
提取JSON数据
let jsonData = {
id: 1,
age: 20
};
let {id, age} = jsonData;
遍历Map
var map = new Map();
map.set('first', 'hello');
map.set('second', 'world');
for (let [key, value] of map) {
console.log(`key ${key} => value ${value}`);
}
for (let [key] of map) {
console.log(`key ${key}`);
}
for (let [, value] of map) {
console.log(`value ${value}`);
}
遍历字符串
for (let code of 'hello world') {
console.log(code);
}
at
console.log('abc'.charAt(1)); // b
console.log('abc'.charCodeAt(1)); // 98
includes(), startsWith(), endsWith()
let fileName = 'test.js';
console.log(fileName.includes('es')); // true, 是否找到了参数es
console.log(fileName.startsWith('t')); // true,是否以参数t开头
console.log(fileName.endsWith('.js')); // true, 是否以参数.js结尾
repeat()
console.log('a'.repeat(3)); // aaa, 返回一个新字符串,表示将原字符串重复n次
console.log('a'.repeat(2.9)); // aa, 小数点会被去除
// console.log('a'.repeat(-2.4)); // 报错, 取证后是负数
console.log('a'.repeat('3')); // aaa, 字符串将会被转为数字
console.log('a'.repeat(NaN)); // NaN等同于0
模版字符串
现在可以将大段字符串这样写
let a = `
<h1>Index</h1>
<p>Hello</p>
`.trim(); // 输出的字符串前后都有一个换行,可以用trim去除
通过${}嵌入变量
let name = 'Alex';
console.log(`Hello ${name}`);
通过${}嵌入函数
function func() {
return 'hello world';
}
console.log(`say: ${func()}`);
如果${}内部为字符串,将会原样输出。
console.log(`say: ${'hello world'}`); // say: hello world
函数参数的默认值
ES6之前,不能直接为函数参数指定默认值,但可以用以下方法
function log(x, y) {
x = x || 'Hello'; // x, y 为false, 0之类的就会判断错误
y = y || 'World';
console.log(x, y);
}
// 修改成这样就不会误判false, 0之类的了
function log(x, y) {
if (x === undefined) {
x = 'Hello';
}
if (y === undefined) {
y = 'World';
}
console.log(x, y);
}
ES6可以使用默认函数
function log(x = 1, y = 2) {
console.log(x, y);
}
log(); // 输出 1 2
log('a'); // 输出 'a' 2
解构赋值
function log({x = 1, y = 2}) {
console.log(x, y);
}
log({}); // 输出 1 2
log(); // 报错,不能省略 {}
log({y: 'b'}); // 输出 1 'b'
解构赋值配合参数默认值
function log({x = 1, y = 2} = {}) {
console.log(x, y);
}
log(); // 不会报错,输出 1 2
length
console.log((function log(x, y = 2) {}).length); // 输出1
console.log((function log(x, y = 2, z) {}).length); // 输出1
console.log((function log(x, y, ...params) {}).length); // 输出2,不包括rest参数
不可省略参数
function throwIfMissing() {
throw new Error('Missing parameter.');
}
function log(x = throwIfMissing()) {
console.log(x);
}
log(); // 报错,抛出错误 Missing paramter
rest参数
function log(...params) {
console.log(params);
for (let p of params) {
console.log(p);
}
}
log(1, 'hello', 'world');
// 输出
// [ 1, 'hello', 'world' ]
// 1
// hello
// world
rest参数之后不能再跟其它参数了,必须是最后一个参数,会报错
function log(...params, x = 1) {
}
// 报错 Rest parameter must be last formal parameter
name属性
在ES6中, 函数的name属性会返回该函数的函数名。
// 返回函数名称
console.log((function log() {}).name);
// 匿名函数
var f = function () {};
console.log(f.name); // 输出 f
// 具名函数
var f = function log() {};
console.log(f.name); // 输出具名函数的名称 log
// Function
console.log((new Function).name); // 输出 anonymous(匿名的)
// bind
console.log(f.bind().name); // 输出 anonymous
箭头函数
ES6允许使用=>
来定义函数。
// 相当于 function f(v) { return v + 5; };
var f = v => v + 5;
// 不带参数或者带多个参数可以使用()
var f = () => 'hello world';
var f = (x, y) => x + y;
// 简化回调函数
[1, 2, 3].map(function (x) { return x * x; });
[1, 2, 3].map(x => x * x);
// 与rest参数结合
const headAndTail = (head, ...tail) => [head, tail];
console.log(headAndTail(1, 2, 3, 4, 5)); // 输出 [ 1, [ 2, 3, 4, 5 ] ]
new
命令。yield
函数,因此不可以做为Generator
函数。在箭头函数中this对象是固定的
function foo() {
setTimeout(() => {
console.log('id: ', this.id);
}, 1000);
}
var id = 21;
// call: 相当于把foo中的函数给{id:42}这个对象来执行
foo.call({id: 42});
function Timer() {
this.s1 = 0;
this.s2 = 0;
// 箭头函数中的this绑定定义时所在的作用域,Timer函数
setInterval(() => this.s1++, 1000);
// 普通函数中的this绑定运行时所在的作用域,全局对象
// 因此没有改变Timer.s2的值
setInterval(function() { this.s2++; }, 1000);
}
var timer = new Timer();
setTimeout(() => console.log('s1: ', timer.s1), 3500); // s1: 3
setTimeout(() => console.log('s2: ', timer.s2), 3500); // s2: 0
扩展运算符 …
相当于rest参数的逆运算,将一个数组转为用逗号分割的参数序列
console.log(1, ...[2, 3, 4, 5], 6); // 输出 1 2 3 4 5 6
提供了数组合并的新方法
var a = [1, 2];
var b = [3, 4];
var c = [5, 6];
// ES5
console.log(a.concat(b).concat(c));
// ES6
console.log([...a, ...b, ...c]);
与解构赋值结合
var [first, ...rest] = [1, 2, 3, 4, 5];
console.log(first, rest); // 1 [ 2, 3, 4, 5 ]
var [first, ...rest] = [1];
console.log(first, rest); // 1 []
var [first, ...rest] = [];
console.log(first, rest); // undefined []
var [...rest, first] = [];
console.log(first, rest); // 报错: Rest element must be last element
Array.from()
arrayLike对象
interface ArrayLike<T> {
readonly length: number;
readonly [n: number]: T;
}
示例
// array-like
let arrayLike = {
0: '0',
1: '1',
2: '2',
length: 3
}
console.log(Array.from(arrayLike)); // 输出 [ '0', '1', '2' ]
// iterable
console.log(Array.from('hello')); // [ 'h', 'e', 'l', 'l', 'o' ]
console.log(Array.from(new Set(['a', 'b']))); // [ 'a', 'b' ]
可以接受第二个参数,作用类似于map
函数
// 均输出 1 4 9
console.log([1, 2, 3].map(x => x * x));
console.log(Array.from([1, 2, 3], (x) => x * x));
this
,可以通过Array.from
的第三个参数传入。由于Array.from
能够将字符串转为数组,且能够正确的处理各种Unicode字符,避免JavaScript将大于\uFFFF
的Unicode字符算作两个字符的BUG,我们可以用它来返回字符串的长度
function count(string) {
return Array.from(string).length;
}
console.log(count('Hello')); // 输出 5
console.log(count('��')); // 输出 1
console.log('��'.length); // 输出 2, 这个不正确
Array.of
Array的缺陷,参数为1的时候与其它数量的表现也不同。
console.log(Array()); // []
console.log(Array(3)); // [ <3 empty items> ], 实际是指定了数组的长度
console.log(Array(3, 2)); // [ 3, 2 ]
console.log(Array(3, 2, 1)); // [ 3, 2, 1 ]
Array.of不存在参数不同而导致表现不同的行为。
console.log(Array.of()); // []
console.log(Array.of(3)); // [3]
console.log(Array.of(3, 2)); // [3, 2]
Array()
和new Array()
。copyWithin
var a = Array.of(1, 2, 3, 4, 5);
console.log(a); // [ 1, 2, 3, 4, 5 ]
a.copyWithin(0, 2, 4);
console.log(a); // [ 3, 4, 3, 4, 5 ]
find, findIndex
function(value, index, array)
, 分别是当前值,当前位置,原函数。this
对象。var l = [10, 8, 6, 4, 2];
console.log(l.find((n) => n <= 3)); // 2
console.log(l.findIndex((n) => n <= 3)); // 4
fill
用于填充一个数组,但是会抹去数组中原来的值
var l = [10, 8, 6, 4, 2];
console.log(l.fill('a')); // 修改l后返回l, [ 'a', 'a', 'a', 'a', 'a' ]
console.log(l); // [ 'a', 'a', 'a', 'a', 'a' ]
可以接受第二个和第三个参数,用于指定填充的起始位置和结束位置, 第三个参数默认到末尾
var l = [10, 8, 6, 4, 2];
console.log(l.fill('b', 2, 3)); // [ 10, 8, 'b', 4, 2 ]
console.log(l.fill('c', 2)); // [ 10, 8, 'c', 'c', 'c' ]
entries, keys, values
var l = Array.of('a', 'b', 'c');
for (let key of l.keys()) {
console.log('key: ', key);
}
// values在我这报错:l.values is not a function
for (let value of l.values() ) {
console.log('value: ', v);
}
for (let [key, value] of l.entries()) {
console.log('key: ', key, ' => value: ', value);
}
forEach()
, filter()
, every()
, some()
都会跳过空位。map()
会跳过空位,但保留这个值。join()
, toString()
会视空位为undefined
, 而undefined
和null
会被处理为空字符串。undefined
。简洁表示
属性简写
function f(x, y) {
return {x, y}; // 相当于 {x: x, y: y}
}
console.log(f(1, 2));
函数简写
var Person = {
name: 'Some one',
hello() { // 相当于 hello: function() {
return console.log('My name is', this.name);
}
}
Person.hello();
function getPoint() {
var x = 1;
var y = 2;
return {x, y};
}
var p = getPoint();
console.log(p.x, p.y);
如果函数中有Generator函数,前面需要加上*
var obj = {
* m() {
yield 'hello world';
}
}
属性名表达式
ES6允许属性名放到大括号中
var lastWord = 'last world';
var a = {
'first world': 'hello',
[lastWord]: 'world',
['h' + 'ello']: 'hello world'
}
console.log(a['first world']); // hello
console.log(a[lastWord]); // world
console.log(a['last world']); // world
console.log(a['hello']); // hello world
[object Object]
,如果内有多个对象做为属性名称,则会覆盖掉,只保留一个。 name
返回函数名,方法名。
function funcA() {}
var b = {
funcB() {},
get age() { return 100; },
set age(n) {}
}
console.log(funcA.name); // funcA
console.log(b.funcB.name); // funcB
const d = Object.getOwnPropertyDescriptor(b, 'age');
console.log(d.get.name); // get age
console.log(d.set.name); // set age
bind
返回bound
加上原函数名字。Function
函数返回anonymous
。
console.log((new Function()).name);
如果对象的方法名是一个Symbol
值,则name
返回Symbol
值的描述
const key = Symbol('hello key');
var a = {
[key]() {}
}
console.log(a[key].name);
Object.is
===
的行为基本一致。// ES5
console.log('foo' === 'foo'); // true
console.log({} === {}); // false
console.log(+0 === -0); // true
console.log(NaN === NaN); // false
// ES6
console.log(Object.is('foo', 'foo')); // true
console.log(Object.is({}, {})); // false
console.log(Object.is(+0, -0)); // false
console.log(Object.is(NaN, NaN)); // true
Object.assign
assign(target: object, ...sources: any[])
。
var target = {a: 1};
var source1 = {b: 1};
var source2 = {c: 2};
Object.assign(target, source1, source2);
console.log(target); // { a: 1, b: 1, c: 2 }
由于undefined
和null
无法转为对象,所以如果他们作为首参数,就会报错。NaN可以放在首参数。
Object.assign(undefined);
Object.assign(null);
undefined
, null
, NaN
,以及数值, 布尔值放入到非首参数的位置,都会被忽略。字符串包装的对象,才有可枚举属性(enumerable)。
var target = {a: 1};
var r = Object.assign(target, undefined, null, NaN, 10) === target;
console.log(r); // true
拷贝的属性是有限制的,只拷贝源对象的自身属性(非继承)和可枚举属性。
var target = {
a: 1
};
var source = Object.defineProperty({
b: 2
},
'invisible', {
enumerable: false,
value: 'hello'
}
);
Object.assign(target, source);
console.log(target); // { a: 1, b: 2 }
处理数组时,会把数组当成对象处理。
var a = ['a', 'b', 'c'];
var b = ['d', 'e'];
Object.assign(a, b)
console.log(a); // [ d, e, c ]
可以用于生成独一无二的属性名。
console.log(Symbol() === Symbol()); // false
Symbol
可以接受一个字符串做为参数,表示对Symbol
实例的描述,主要为了帮助的区分。
// 以下输出 Symbol() Symbol() Symbol(hello) Symbol(NaN) Symbol(null) Symbol()
console.log(Symbol(), Symbol(), Symbol('hello'), Symbol(NaN), Symbol(null), Symbol(undefined));
toString
方法,将其转为字符串。可以转为字符串和布尔值,但是不能转为数值。
console.log(String(Symbol('hello'))); // Symbol(hello)
console.log(Boolean(Symbol('world'))); // true
console.log(Number(Symbol())); // 报错 Cannot convert a Symbol value to a number
做为属性名的应用
var mySymbol = Symbol();
var a = {
[mySymbol]: 'hello',
}
console.log(a[mySymbol]); // hello
Symbol
定义属性时,Symbol
值必须放在方括号中。否则属性名就变为字符串mySymbol
了,而不是一个Symbol
值。定义常量的应用
var log = {
levels: {
DEBUG: Symbol('debug'),
INFO: Symbol('info'),
WARN: Symbol('warn'),
FATAL: Symbol('fatal')
}
}
console.log(log.levels.DEBUG);
Symbol
值的最大好处就是不可能会有相同的值。属性名的遍历
getOwnPropertySymbols
返回一个数组,成员为对象所有用作属性名的Symbol
值。Object.getOwnPropertyNames
得不到Symbol
属性名。Reflect.ownKeys
可以得到所有类型的键名。var b = Symbol('b');
var o = {
a: Symbol('a'),
[b]: 'hello',
c: 'world',
foo() {}
}
var objectSymbols = Object.getOwnPropertySymbols(o);
var objectNames = Object.getOwnPropertyNames(o);
var allKey = Reflect.ownKeys(o);
console.log('objectSymbols', objectSymbols); // objectSymbols [ Symbol(b) ]
console.log('objectNames', objectNames); // objectNames [ 'a', 'c', 'foo' ]
console.log('allKey', allKey); // allKey [ 'a', 'c', 'foo', Symbol(b) ]
Symbol.for, Symbol.keyFor
Symbol.for
与Symbol
功能相似,不过前者会在全局登记,同一个参数返回的都是同一个Symbol
值。Symbol.keyFor
,可以把登记过的Symbol
值的参数找出来。var s1 = Symbol.for('foo');
var s2 = Symbol.for('foo');
console.log(s1 === s2); // true
console.log(Symbol.keyFor(s1)); // foo
console.log(Symbol.keyFor(s2)); // foo
Symbol.hasInstance
instanceof
运算符,判断是否是该对象的实例,就会调用该方法。class MyClass {
[Symbol.hasInstance](foo) {
return foo instanceof Array;
}
}
console.log([1, 2, 3] instanceof new MyClass); // true
class MyClass2 {
[Symbol.hasInstance](value) {
return value % 2 == 0;
}
}
console.log(1 instanceof new MyClass2); // false
console.log(2 instanceof new MyClass2); // true
Symbol.isConcatSpreadable
Array.prototype.concat()
时,是否可以展开。数组的默认行为是可以展开的,Symbol.isConcatSpreadable
为true
或undefined
时,都有这个效果。
let arr1 = ['c', 'd'];
console.log(['a', 'b'].concat(arr1, ['e'])); // [ 'a', 'b', 'c', 'd', 'e' ]
console.log(arr1[Symbol.isConcatSpreadable]); // undefined
let arr2 = ['c', 'd'];
arr2[Symbol.isConcatSpreadable] = false;
// 以下输出 [ 'a', 'b', ['c', 'd', Symbol(Symbol.isConcatSpreadable)]: false], 'e' ]
console.log(['a', 'b'].concat(arr2, ['e']));
类似数组的对象这个效果是默认关闭的。
let obj = {
0: 'c',
1: 'd',
length: 2
}
console.log(['a', 'b'].concat(obj)); // [ 'a', 'b', { '0': 'c', '1': 'd', length: 2 } ]
console.log(obj[Symbol.isConcatSpreadable]); // undefined
obj[Symbol.isConcatSpreadable] = true;
console.log(['a', 'b'].concat(obj)); // [ 'a', 'b', 'c', 'd' ]
Symbol.species
指向当前对象的构造函数,创建对象是,会调用这个方法。
class MyArray extends Array {
static get[Symbol.species]() {
console.log('hello');
return Array;
}
}
var l = new MyArray(1, 2, 3);
console.log(l instanceof MyArray); // true
console.log(l instanceof Array); // true
var mapped = l.map(x => x * x); // hello
console.log(mapped instanceof MyArray); // false
console.log(mapped instanceof Array); // true
默认的Symbol.species
相当于:
static get[Symbol.species]() {
console.log('hello');
return this;
}
Symbol.match
指向一个函数,当执行String.prototype.match()
时,如果该属性存在,就会调用它,并返回该方法的值。
class MyClass {
[Symbol.match](string) {
// return 'hello world'.indexOf(string);
return 'hello world';
}
}
console.log('e'.match(new MyClass())); // hello world
Symbol.replace
指向一个函数,当执行String.prototype.replace()
时,如果该属性存在,就会调用它,并返回该方法的值。
class MyClass {
[Symbol.replace](string) {
return string + ' world';
}
}
console.log('hello'.replace(new MyClass)); // hello world
Symbol.replace
指向一个函数,当执行String.prototype.search()
时,如果该属性存在,就会调用它,并返回该方法的值。
class MyClass {
[Symbol.search](string) {
return 'hello world'.indexOf(string);
}
}
console.log('l'.search(new MyClass)); // 2
Symbol.split
指向一个函数,当执行String.prototype.split()
时,如果该属性存在,就会调用它,并返回该方法的值。
class MySplitter {
constructor(value) {
this.value = value;
}
[Symbol.split] (string) {
let index = string.indexOf(this.value);
if (index === -1) {
return string;
}
return [
string.substr(0, index),
string.substr(index + this.value.length)
];
}
}
console.log('foobar'.split(new MySplitter('ba')));
Symbol.iterator
指向对象的默认遍历器方法。
var myIterable = {
* [Symbol.iterator]() {
yield 1;
yield 2;
yield 3;
}
}
console.log([...myIterable]); // [1,2, 3]
for...of
循环时,会调用Symbol.iterator
class MyCollection {
constructor(...value) {
this.value = value;
}
*[Symbol.iterator] () {
let i = 0;
while (this.value[i] !== undefined) {
yield this.value[i];
++i;
}
}
}
let l = new MyCollection(1, 2, 3, 4);
console.log(l); // MyCollection { value: [ 1, 2, 3, 4 ] }
for (let value of l) {
console.log(value); // 输出 1, 2, 3, 4
}
Symbol.toPrimitive
指向一个方法,当对象被转为原始类型的值的时候,会调用这个方法,返回该对象对应的原始类型值。
let obj = {
[Symbol.toPrimitive](value) {
switch (value) {
case 'number':
return 123;
case 'string':
return 'string';
case 'default':
return 'default';
default:
throw new Error('unsupport');
}
}
}
console.log(2 * obj); // 246
console.log(3 + obj); //default3
console.log(3 - obj); //-120
console.log(obj == 'default'); //true
console.log(String(obj)); // string
Symbol.toStringTag
指向一个方法,当使用Object.prototype.toString
时,会调用这个方法,并返回这个方法的值。
var obj = {
get [Symbol.toStringTag]() {
return 'hello world';
}
}
console.log(String(obj));
console.log(obj.toString());
console.log(Object.prototype.toString.call(obj));
Set
Set
类似于数组,但没有重复的值。
let s = new Set();
[1, 2, 3, 3, 2, 1].forEach(x => s.add(x));
console.log(s); // Set { 1, 2, 3 }
Set
可以接收数组做为参数。
let s = new Set([1, 2, 3, 3, 2, 1]);
console.log(s); // Set { 1, 2, 3 }
Set
属性: Set.prototype.constructor
: 构造函数。Set.prototype.size
: 返回成员总数。Set
操作方法: add(value)
: 添加某个值,返回Set
结构本身。delete(value)
: 删除某个值,返回布尔值,表示是否删除成功。has(value)
: 返回布尔值,表示该值是否是Set
的成员。clear()
: 清楚所有的成员,没有返回值。Set
遍历方法:
keys
和values
, 由于Set
结构没有键名只有键值,所以二者行为一致。entries
, 返回键值对的遍历器。forEach
, 使用回调函数遍历每个成员。let s = new Set([1, 3, 2]);
console.log(s.keys()); // SetIterator { 1, 3, 2 }
console.log(s.values()); // SetIterator { 1, 3, 2 }
console.log(s.entries()); // SetIterator { [ 1, 1 ], [ 3, 3 ], [ 2, 2 ] }
/**
* 输出:
* [ 1, 1 ]
* [ 3, 3 ]
* [ 2, 2 ]
*/
for (let v of s.entries()) {
console.log(v);
}
/**
* 1 '=>' 1
* 3 '=>' 3
* 2 '=>' 2
*/
s.forEach((k, v) => console.log(k, '=>', v));
Array.of
和扩展运算符...
:
let s = new Set(Array.of(1, 2, 3));
console.log(s); // Set { 1, 2, 3 }
let l = [...s];
console.log(l); // [ 1, 2, 3 ]
求并集,交集,差集
let a = new Set([1, 2, 3]);
let b = new Set([1, 3, 4]);
// 并集
console.log('并集: ', new Set([...a, ...b])); // 并集: Set { 1, 2, 3, 4 }
// 交集
console.log('交集: ', new Set([...a].filter(x => b.has(x)))); // 交集: Set { 1, 3 }
// 差集
console.log('差集: ', new Set([...a].filter(x => !b.has(x)))); // 差集: Set { 2 }
WeakSet
和Set
类似,也是不重复的值的集合,但是有几个区别: WeakSet
的成员只能是对象,而不能是其它类型的值。WeakSet
是弱引用,当其它对象不再引用时,WeakSet
里面的值会被回收,而不会考虑到对象是否还在WeakSet
中。当外部对象消失的时候,WeakSet
里面对应的引用也会消失,由于垃圾回收机制何时运行是不可预测的,因此ES6规定,WeakSet
不可以遍历。WeakSet
可以接受数组或者类似数组的对象做为参数,实际上,具有Iterable
接口的对象,都可以作为WeakSet
的参数。WeakSet
由于弱引用的特性,使用size
和forEach
都会报错。Map
Object
本质上是键值对的集合,只是键只能是使用字符串。Map
也是键值对的集合,但是键不限于字符串,各种类型的值(包括对象)都可以当键。
let l = [1, 2, 3];
let m = new Map();
m.set(l, 'it is l');
console.log(m); // Map { [ 1, 2, 3 ] => 'it is l' }
console.log(m.get(l)); // it is l
只有对同一个对象的引用才会被Map
视为同一个键。
let m = new Map();
m.set({}, 'hello');
console.log(m.get({})); // undefined
{}
实际并不是同一个对象。另外,原书说0
与-0
, undefined
, null
, 也是两个不同的键,但是在我的运行环境里,却是被认为是同一个键。
let m = new Map();
m.set(undefined, 'hello');
console.log(m.get(undefined)); // hello
m.set(null, 'hello');
console.log(m.get(null)); // hello
m.set(NaN, 'hello');
console.log(m.get(NaN)); // hello
m.set(0, 'hello');
console.log(m.get(-0)); // hello
Map
属性 size
: 返回Map
的成员总数。Map
操作方法: get(key)
: 获取key
对应的值,如果找不到,则返回undefined
。set(key, value)
: 设置key
所对应的值,并返回Map
本身。has(key)
: 返回一个布尔值,表示某个键是否在其中。delete(key)
: 删除某个键,返回一个布尔值表示成功与否。clear()
: 清理所有成员,没有返回值。Map
遍历方法:
keys
: 返回键名的遍历器。values
: 返回键值的遍历器。entries
, 返回键值对的遍历器。forEach
, 使用回调函数遍历每个成员。let m = new Map()
.set(1, 'a')
.set(2, 'b')
.set(3, 'c');
m.forEach((value, key, map) => console.log(value, key, map));
forEach
还可以使用第二个参数,用来绑定this
。WeakMap
功能和Map
类似,不同的地方在于: WeakMap
只接受对象做为键名。WeakMap
键名为弱引用,当外部对象消失后,其在WeakMap
中的记录也会自动被移除。WeakMap
弱引用的只是键名,而不是键值。WeakMap
没有遍历的操作(没有keys()
, values()
, entries()
),没有size
属性,也没有clear()
方法。Proxy
用于修改某些操作的默认行为,相当于对一些访问进行了拦截。
var proxy = new Proxy({}, {
get: function (target, property) {
return 35;
}
});
console.log(proxy.time, proxy.age); // 35 35
function get(target: object, propertyKey: PropertyKey, receiver?: any): any;
var person = {
'name': 'Abc'
}
var proxy = new Proxy(person, {
get: function (target, property) {
if (property in target) {
return target[property];
} else {
throw new ReferenceError("Property \"" + property + "\" does not exist. ");
}
}
});
console.log(proxy.name); // Abc
console.log(proxy.age); // ReferenceError: Property "age" does not exist.
undefined
。function createArray(...elements) {
let handler = {
get(target, propertyKey, receiver) {
let index = Number(propertyKey);
if (index < 0) {
propertyKey = String(target.length + index);
}
return Reflect.get(target, propertyKey, receiver);
}
}
let target = [];
target.push(...elements);
return new Proxy(target, handler);
}
var arr = createArray('a', 'b', 'c');
console.log(arr[-1]); // c
get
拦截,实现了数组读取负数的索引。const target = Object.defineProperties({}, {
foo: {
value: 123,
writable: false,
configurable: false
}
});
const proxy = new Proxy(target, {
get: function (target, propertyKey) {
return 'Abc';
}
});
console.log(proxy.name); // Abc
console.log(target.foo); // 123
console.log(proxy.foo); // 报错
function set(target: object, propertyKey: PropertyKey, value: any, receiver?: any): boolean;
let handler = {
set: function (target, propertyKey, value) {
if (propertyKey === 'age') {
if (!Number.isInteger(value)) {
throw new TypeError('The age is not an integer');
}
if (value < 0 || value > 200) {
throw new RangeError('The age seems invalid');
}
}
target[propertyKey] = value;
console.log('Property \"' + propertyKey + '\" is ' + value)
}
};
var person = new Proxy({}, handler);
person.age = 100; // Property "age" is 100
// person.age = '100'; // TypeError: The age is not an integer
// person.age = 'hello'; // TypeError: The age is not an integer
// person.age = 201; // RangeError: The age seems invalid
set
操作,对age
属性做了判断。在严格模式下,以上代码会报错TypeError: 'set' on proxy: trap returned falsish for property 'age'
。function invariant(key, action) {
if (key[0] === '_') {
throw new Error(`Invalid attemp to ${action} private "${key}"`);
}
}
let handler = {
get: function (target, propertyKey) {
invariant(propertyKey, 'get');
return target[propertyKey];
},
set: function (target, propertyKey, value) {
invariant(propertyKey, 'set');
target[propertyKey] = value;
}
};
var proxy = new Proxy({}, handler);
console.log(proxy.age); // undefined
console.log(proxy._age); // Error: Invalid attemp to get private "_age"
function apply(target: Function, thisArgument: any, argumentsList: ArrayLike<any>): any;
call
和apply
let target = () => {
return 'I am the target .';
};
let handler = {
apply: function () {
return 'I am the proxy. ';
}
};
let p = new Proxy(target, handler);
console.log(p()); // I am the proxy.
var twice = {
apply: function (target, ctx, args) {
return Reflect.apply(...arguments) * 2;
}
};
function sum(a, b) {
return a + b;
};
var proxy = new Proxy(sum, twice);
console.log(proxy(1, 2)); // 6 拦截函数调用
console.log(proxy.call(null, 3, 4)); // 14 拦截call
console.log(proxy.apply(null, [5, 6])); // 22 拦截apply
call
和apply
的更多信息,请参考Javascript中的this,call,apply,bind函数
function has(target: object, propertyKey: PropertyKey): boolean;
HasProperty
操作,即判断对象是否具有某个属性的时候,这个方法就会生效。var handler = {
has(target, propertyKey) {
if (propertyKey[0] === '_') {
return false;
}
return propertyKey in target;
}
};
var target = {
_prop: '_foo',
prop: 'foo'
};
var proxy = new Proxy(target, handler);
console.log('_prop' in proxy, 'prop' in proxy); // false true
has
拦截了某些属性,不被in
运算符发现。Object.preventExtensions(target);
has
拦截会报错。has
对for ... in
不生效。function construct(target: Function, argumentsList: ArrayLike<any>, newTarget?: any): any;
new
命令。construct
返回必须是一个对象,否则报错。var handler = {
construct(target, args, newTarget) {
console.log(...args);
return new target(...args);
}
};
var proxy = new Proxy(function () {}, handler);
(new proxy(123, 456)).name;
function deleteProperty(target: object, propertyKey: PropertyKey): boolean;
delete
操作,当返回false
或者报错的时候,当前属性就无法被delete
删除。var handler = {
deleteProperty(target, propertyKey) {
if (propertyKey[0] === '_') {
throw new Error(`Invalid attemp to delete private ${propertyKey}`);
}
return true;
}
};
var target = {
_prop: 'foo'
};
var proxy = new Proxy(target, handler);
delete proxy._prop; // Error: Invalid attemp to delete private _prop
function defineProperty(target: object, propertyKey: PropertyKey, attributes: PropertyDescriptor): boolean;
Object.defineProperty
操作,如果返回false
或者报错的时候,就无法添加新属性。var handler = {
defineProperty(target, propertyKey, attributes) {
if (propertyKey[0] === '_') {
return false;
}
return true;
}
};
var target = {
'name': 'target name'
};
var proxy = new Proxy(target, handler);
proxy.foo = 'foo';
proxy._foo = '_foo'; // TypeError: 'defineProperty' on proxy: trap returned falsish for property '_foo'
function getOwnPropertyDescriptor(target: object, propertyKey: PropertyKey): PropertyDescriptor;
Object.getOwnPropertyDescriptor()
var handler = {
getOwnPropertyDescriptor(target, propertyKey) {
if (propertyKey[0] === '_') {
return;
}
return Object.getOwnPropertyDescriptor(target, propertyKey);
}
}
var target = {
_foo: '_foo',
bar: 'bar'
};
var proxy = new Proxy(target, handler);
console.log(Object.getOwnPropertyDescriptor(proxy, '_foo')); // undefined
console.log(Object.getOwnPropertyDescriptor(proxy, 'bar')); // { value: 'bar', writable: true, enumerable: true, configurable: true }
undefined
。function getPrototypeOf(target: object): object;
Object.prototype.__proto__
Object.prototype.isPrototypeOf()
Object.getPrototypeOf()
Reflect.getPrototypeOf()
instanceof
isExtensible? (target: T): boolean;
Object.isExtensible
方法。isExtensible
属性保持一致,否则会报错。var target = {};
var proxy = new Proxy(target, {
isExtensible: function (target) {
console.log('called');
return true;
}
});
Object.isExtensible(proxy); // called
console.log(Object.isExtensible(proxy) === Object.isExtensible(target)); // true
isExtensible
返回的false
,与target
的实际情况不同,就会报错。ownKeys? (target: T): PropertyKey[];
Object.getOwnPropertyNames()
Object.getOwnPropertySymbols()
Object.keys()
var target = {
_bar: '_bar',
_prop: '_prop',
name: 'name'
};
var handler = {
ownKeys(target) {
return Reflect.ownKeys(target).filter(key => key[0] !== '_');
}
}
var proxy = new Proxy(target, handler);
console.log(Object.keys(target)); // [ '_bar', '_prop', 'name' ]
console.log(Object.keys(proxy)); // [ 'name' ]
ownKeys
过滤,写出来也不会出现。 Symbol
值var target = {
a: 1,
b: 2,
[Symbol.for('secret')]: 3
};
Object.defineProperty(target, 'key', {
enumerable: false
});
var handler = {
ownKeys(target) {
return ['a', 'd', Symbol.for('secret'), 'key'];
}
};
var proxy = new Proxy(target, handler);
console.log(Object.keys(proxy)); // [ 'a' ]
preventExtensions? (target: T): boolean;
Object.preventExtensions()
。preventExtensions
才能返回true,否则会报错。preventExtensions
中把target
也修改下。var target = {};
var handler = {
preventExtensions(target) {
Object.preventExtensions(target);
return true;
}
};
var proxy = new Proxy(target, handler);
Object.preventExtensions(proxy);
setPrototypeOf? (target: T, v: any): boolean;
Object.setPrototypeOf
方法。setPrototypeOf
不得改变目标对象的原型。Proxy.revocable
revoke
函数后,proxy
就不可再访问了。var target = {};
var handler = {};
var {proxy, revoke} = Proxy.revocable(target, handler);
console.log(proxy.name); // undefined
revoke();
console.log(proxy.name); // TypeError: Cannot perform 'get' on a proxy that has been revoked
this问题
this
会指向Proxy
代理。var target = {
foo() {
console.log(this === proxy);
}
};
var proxy = new Proxy(target, {});
target.foo(); // false
proxy.foo(); // true this指向了proxy
this
才能取到,所以Proxy
无法代理这些原生对象的属性。var target = new Date();
var proxy = new Proxy(target, {});
console.log(proxy.getDate()); // TypeError: this is not a Date object.
this
指针绑定原始对象,才可以解决这个问题。var target = new Date();
var handler = {
get(target, propertyKey) {
if (propertyKey === 'getDate') {
return target.getDate.bind(target);
}
return Reflect.get(target, propertyKey);
}
}
var proxy = new Proxy(target, handler);
console.log(proxy.getDate()); // 25
Reflect
对象和Proxy
对象一样,也是ES6为操作对象而提供的新API。
将Object
对象的一些明显属于语言内部的方法,放到Reflect
上。目前是同时在Object
和Reflect
上部署,而未来只部署在Reflect
上。
var obj = {};
console.log(Object.isExtensible(obj)); // true
console.log(Reflect.isExtensible(obj)); // true
Object
方法的返回结果。例如Object.defineProperty()
在无法定义属性时,会抛出错误。而Reflect.defineProperty()
则会返回false
。让Object
操作变成函数行为。
var obj = {};
console.log('foo' in obj); // false
console.log(Reflect.has(obj, 'foo')); // false
Reflect
对象的方法与Proxy
对象的方法一一对应。使用Reflect
对应的方法,可以完成默认的行为。
var target = {};
var handler = {
get(target, propertyKey) {
if (propertyKey[0] === '_') {
throw new Error(`Invalid attamp to get param ${propertyKey}`);
}
// 完成默认的行为
return Reflect.get(target, propertyKey);
}
};
var proxy = new Proxy(target, handler);
console.log(proxy.name); // undefined
console.log(proxy._name); // Error: Invalid attamp to get param _name
Reflect
对象一共有13个静态方法:
function apply(target: Function, thisArgument: any, argumentsList: ArrayLike<any>): any;
function construct(target: Function, argumentsList: ArrayLike<any>, newTarget?: any): any;
function defineProperty(target: object, propertyKey: PropertyKey, attributes: PropertyDescriptor): boolean;
function deleteProperty(target: object, propertyKey: PropertyKey): boolean;
function get(target: object, propertyKey: PropertyKey, receiver?: any): any;
function getOwnPropertyDescriptor(target: object, propertyKey: PropertyKey): PropertyDescriptor;
function getPrototypeOf(target: object): object;
function has(target: object, propertyKey: PropertyKey): boolean;
function isExtensible(target: object): boolean;
function ownKeys(target: object): Array<PropertyKey>;
function preventExtensions(target: object): boolean;
function set(target: object, propertyKey: PropertyKey, value: any, receiver?: any): boolean;
function setPrototypeOf(target: object, proto: any): boolean;
Promise
是异步编程的一种解决方案。Promise
有两个特点: Promise
对象一共有三种状态: Pending
(进行中)、Fulfilled
(已成功)、Rejected
(已失败)。只有异步操作的结果可以决定当前是处于什么状态,任何其他操作都无法改变这个状态。Pending
变为Fulfilled
,从Pending
变为Rejected
。Promise
的缺点 Promise
,一旦建立就会立即执行,无法中途中断。Promise
内部抛出错误,不会反应到外部。Pending
状态的时候,无法得知目前进展到哪一个阶段(是刚开始还是将要结束了)。基本用法
创建一个Promise
实例
var promise = new Promise(function (resolve, reject) {
// 如果成功, 则调用resolve函数; 失败则调用reject函数
});
指定成功,失败状态的回调函数
promise.then(function(value) {
// promise执行resolve时调用
}, function(value) {
// rpromise执行reject时调用
});
then
方法可以接受两个回调函数做为参数。其中,第二个是可选的。这两个回调函数都接受Promise
对象传出的值做为参数。示例:
function timeout(ms) {
return new Promise((resolve, reject) => {
// 为了测试方便, 用ms的大小做为成功和失败的区别
if (ms > 2000) {
// setTimeout(resolve, ms, 'Resolve done.');
resolve('Resolve done.');
} else {
// setTimeout(reject, ms, 'Reject done.');
reject('Resolve done.');
}
});
}
timeout(2000).then((value) => {
console.log('Success: ', value);
}, (value) => {
console.log('Failed: ', value);
}); // 输出: Failed: Resolve done.
执行顺序
let promise = new Promise((resolve, reject) => {
console.log('before resolve');
resolve();
console.log('after resolve');
});
promise.then((value) => {
console.log('promise then');
});
console.log('hello world');
// 输出:
// before resolve
// after resolve
// hello world
// promise then
Promise
建立后立即执行,所以会先输出before resolve
和after resolve
。then
指定的回调函数,需要在当前脚本所有函数都执行完之后才会执行,所以会输出hello world
。promise then
。一个异步的操作结果是返回另一个异步操作
var p1 = new Promise((resolve, reject) => {
setTimeout(() => resolve('p1 done'), 10000);
});
var p2 = new Promise((resolve, reject) => {
setTimeout(() => resolve(p1), 1000);
});
p2.then(result => console.log(result)).catch(error => console.log(error));
p2
需要等待p1
结束后才会执行。Promise.prototype.then()
Promise
添加状态发生改变时候触发的回调函数,可以添加两个函数。resolve
(成功)时触发的。reject
(失败)时触发的,这个可选。then
返回的是一个新的Promise
实例,因此可以采用链式写法。var p1 = new Promise((resolve, reject) => {
resolve();
});
p1.then(() => {
console.log('first then');
return 'first then end';
}).then((value) => {
console.log('second then', value);
});
// 输出
// first then
// second then first then end
Promise
实例。var p1 = new Promise((resolve, reject) => {
resolve();
});
p1.then(() => {
console.log('first then');
return new Promise((resolve, reject) => {
setTimeout(() => resolve('first setTimeout end'), 5000);
});
}).then((value) => {
console.log('second then', value);
});
// 输出
// first then
// second then first setTimeout end
Promise.prototype.catch()
var promise = new Promise((resolve, reject) => {
reject(new Error('Promise reject.'));
});
promise.then(() => {
}, () => {
console.log('then reject');
}).catch(() => {
console.log('catch');
}); // 输出 then reject
then
中指定了reject
的回调函数,则不会触发catch
。即使Promise
中抛出异常,也会被then
中的reject
获取。var promise = new Promise((resolve, reject) => {
reject('hello reject');
});
promise.then(() => {
}).catch(() => {
console.log('catch');
}); // 输出 catch
then
中指定reject
的回调函数,就会被catch
获取。var promise = new Promise((resolve, reject) => {
resolve('Promise resolve');
throw new Error('Promise Error');
});
promise.then(() => {
console.log('then resolve');
}, () => {
console.log('then reject');
}).catch(() => {
console.log('catch');
}); // 输出 then resolve
resolve
,那么再抛出错误也是无效的。promise.then(() => {
}).then(() => {
}).catch(() => {
});
Promise
的错误会一直传递,直到被捕获位置,上面的代码中有三个Promise
实例,任何一个抛出错误,都会被catch
捕获。then
中定义reject
的回调函数,而是使用catch
方法。Promise
抛出的错误是不会传递到外层代码的。var promise = new Promise((resolve, reject) => {
resolve('hello');
});
promise.then(() => {
x + 2;
}).catch(() => {
y + 2;
}).catch(() => {
console.log('last catch')
});
catch
中还可以再抛出错误,这个错误可以被之后的catch
捕获到。Promise.all(iterable)
examples:
Promise.all
waits for all fulfillments (or the first rejection).
// ----------------- all fulfillments
var p1 = 1337;
var p2 = Promise.resolve(3);
var p3 = new Promise((resolve, reject) => {
setTimeout(resolve, 3000, 'p3 resolve');
});
Promise.all([p1, p2, p3]).then(values => {
console.log(values);
});
// [ 1337, 3, 'p3 resolve' ]
// ----------------- return first rejection
var p1 = 1337;
var p2 = Promise.reject(3);
var p3 = new Promise((resolve, reject) => {
setTimeout(reject, 3000, 'p3 reject');
});
Promise.all([p1, p2, p3]).then(values => {
console.log('Promise all then ', values);
}).catch(error => {
console.log('Promise all catch ', error)
});
// Promise all catch 3
Promise.race(iterable)
examples:
// ----------------- the promise returned will be forever pending
var promise = Promise.race([]);
promise.then(value => {
console.log('Promise race then');
}).catch(error => {
console.log('Promise race catch');
});
console.log(promise); // Promise { <pending> }
// ----------------- then Promise.race will resolve to the first of these values found in the iterable.
var p1 = new Promise((resolve, reject) => {
setTimeout(resolve, 400, 'p1 resolve')
});
var p2 = new Promise((resolve, reject) => {
setTimeout(reject, 300, 'p2 reject')
});
var p3 = new Promise((resolve, reject) => {
setTimeout(resolve, 200, 'p3 resolve')
});
var promise = Promise.race([p1, p2, p3]);
promise.then(value => {
console.log('Promise race then ', value);
}).catch(error => {
console.log('Promise race catch', error);
});
console.log(promise);
// output:
// Promise { <pending> }
// Promise race then p3 resolve
Promise.resolve()
Promise.resolve(value)
Promise.resolve(promise)
Promise.resolve(thenable)
: a object has a then methodexamples:
Promise.resolve(value)
var p1 = Promise.resolve('hello').then(value => console.log('then ', value)); // then hello
var p2 = Promise.resolve([1, 2, 3]).then(value => console.log('then ', value)); // then [ 1, 2, 3 ]
Promise.resolve(promise)
// ----------------- return the param as result
var p1 = new Promise((resolve, reject) => {
resolve('hello p1');
});
var p2 = Promise.resolve(p1);
console.log(p1 === p2); // true
p2.then(value => console.log(value)); // hello p1
Promise.resolve(thenable)
var obj = {
then (resolve, reject) {
resolve('hello, obj resolve');
}
};
var promise = Promise.resolve(obj);
console.log(obj === promise); // false
promise.then(value => console.log(value)); // hello, obj resolve
Promise.reject(reason)
done
, value
). String
, Array
, TypedArray
, Map
and Set
are all built-in iterables, because each of their prototype objects implements an @@iterator method.Iterable examples:
User-defined iterables:
var iter = {};
iter[Symbol.iterator] = function* () {
yield 1;
yield 2;
yield 3;
};
for (let v of iter) {
console.log(v);
};
If an iterable’s @@iterator method doesn’t return an iterator object, it is likely to result in runtime exceptions or buggy behavior
var iter = {};
iter[Symbol.iterator] = () => 1;
for (let v of iter) {
console.log(v);
};
// TypeError: Result of the Symbol.iterator method is not an object
Iterator examples:
Simple iterator
function makeIterator(array) {
var index = 0;
return {
next: function () {
return index < array.length ? {
value: array[index++],
done: false
} : {
done: true
};
}
};
};
var iter = makeIterator([1, 2]);
console.log(iter.next()); // { value: 1, done: false }
console.log(iter.next()); // { value: 2, done: false }
console.log(iter.next()); // { done: true }
With ES6 class
class MyClass {
constructor(data) {
this.index = 0;
this.data = data;
}
[Symbol.iterator]() {
return {
next: () => {
if (this.index < this.data.length) {
return {
value: this.data[this.index++],
done: false
};
} else {
return {
done: true
};
}
},
};
};
};
var l = new MyClass([1, 2, 3, 4]);
for (let o of l) {
console.log(o);
}
Generator
function return a generator object which conforms to both the iterable protocol and the iterator protocol.yield
expression.Examples:
function* gen() {
yield 'hello';
yield 'world';
return 'ending';
};
var g = gen();
console.log(g.next()); // { value: 'hello', done: false }
console.log(g.next()); // { value: 'world', done: false }
console.log(g.next()); // { value: 'ending', done: true }
console.log(g.next()); // { value: undefined, done: true }
next(value)
function* gen(x) {
let y = yield(x + 1);
let z = yield(y * 2);
return (x + y + z);
};
var g = gen(1);
console.log(g.next()); // { value: 2, done: false }, x = 1, return valus is 2
console.log(g.next(10)); // { value: 6, done: false }, yield(x + 1) = 10, so y = 10, and return value is 20
console.log(g.next(100)); // { value: 8, done: true }, yield(y * 2) = 100, and x = 1, y = 10, so return value is 111
x
is treated as the last yield value.return(value)
If return(value) is called on a generator that is already in “completed” state, the generator will remain in “completed” state.
function* gen(x) {
yield 1;
yield 2;
yield 3;
};
var g = gen();
console.log(g.next()); // { value: 1, done: false }
console.log(g.return('hello')); // { value: 'hello', done: true } it is over.
console.log(g.next()); // { value: undefined, done: true }
If try … finally in generator function.
function* gen(x) {
try {
yield 1;
yield 2;
} finally {
yield 3;
yield 4;
}
yield 5;
};
var g = gen();
console.log(g.next()); // { value: 1, done: false }
console.log(g.return('hello')); // { value: 3, done: false }, finally is called first
console.log(g.next()); // { value: 4, done: false }
console.log(g.next()); // { value: 'hello', done: true }, return 'hello' after finally
console.log(g.next()); // { value: undefined, done: true }, generator function is over
console.log(g.next()); // { value: undefined, done: true }, generator function is over
yield*
yield* [[expression]]
examples:
function* gen1() {
yield 1;
yield 2;
};
function* gen2() {
yield 'a';
yield* gen1();
yield 'b';
};
var g = gen2();
for (let v of g) {
console.log(v);
}
// output: a 1 2 b
function* gen() {
yield* 'hello';
yield* [1, 2, 3];
}
var g = gen();
for (let value of g) {
console.log(value);
};
// output: h e l l o 1 2 3
async
和await
看上去就像是简化Promise
或Generator
操作。一个简单的异步示例, 每个5秒输出一些文字,有多个这样的异步操作
Promise
版本
var promise1 = new Promise((resolve, reject) => {
setTimeout(resolve, 5000, 'promise1 timeout')
});
var promise2 = new Promise((resolve, reject) => {
setTimeout(resolve, 5000, 'promise2 timeout')
});
promise1.then(value => {
console.log('promise1 then', value);
return promise2;
}).then(value => {
console.log('promise2 then', value);
}).catch(error => {
console.log('promise catch', error)
});
// 输出:
// promise1 then promise1 timeout
// promise2 then promise2 timeout
Promise
串行执行的情况,需要在then
里执行下一个的Promise
实例,看上去就是一层层的嵌套。Generator
版本
function timeout(value) {
setTimeout(() => console.log(value), 5000);
}
var gen = function* () {
yield timeout('timeout 1');
yield timeout('timeout 2');
};
var g = gen();
g.next();
g.next();
// 输出
// timeout 1
// timeout 2
Promise
版本好看的多,但如果不借助co
模块或类似的辅助,对于多个异步,我们需要写出多个next
函数。async & await
版本
function timeout(value, ms) {
return new Promise((resolve, reject) => {
setTimeout(resolve, ms, value);
});
}
async function foo() {
let x = await timeout('hello timeout 1', 5000);
let y = await timeout('hello timeout 2', 5000);
return [x, y];
}
foo().then(v => console.log(v));
// 输出: [ 'hello timeout 1', 'hello timeout 2' ]
async
和await
,简单明了。async
函数最终返回一个Promise
示例,所以,后面可以接then
, catch
等。await
命令后是一个Promise
对象,如果不是,会被转为Promise
对象。await
命令只能在async
函数中使用。async
函数返回的Promise
实例,需要等到内部所有的await
命令后的Promise
实例执行完,才会发生状态改变。一旦其中一个await
命令后的Promise
实例抛出错误或者reject
,async
函数返回的Promise
实例就会立即改变状态,且后续的await
命令就会中断执行。为了防止抛出错误中断执行,可以使用try...catch
,或者是await
命令后的Promise
实例接上catch
。
// -------------------- try ... catch
function timeout(value, ms) {
return new Promise((resolve, reject) => {
if (value === 'hello timeout 1') {
reject('reject 1'); // 第一个直接reject
} else {
setTimeout(resolve, ms, value);
}
});
}
async function foo() {
try {
let x = await timeout('hello timeout 1', 5000);
} catch (error) {
console.log('time 1 catch,', error)
}
let y = await timeout('hello timeout 2', 5000);
return 'foo end';
}
let pre = new Date().getTime();
foo().then(v => {
console.log(v);
let end = new Date().getTime();
console.log('time: ', end - pre);
}).catch(error => console.log(error));
// 输出:
// time 1 catch, reject 1
// foo end
// time: 5026
// -------------------- Promise.catch
async function foo() {
await timeout('hello timeout 1', 5000).catch(error => console.log('error: ', error));
let y = await timeout('hello timeout 2', 5000);
return 'foo end';
}
在上面的async & await
版本中,后一个异步操作要等到前一个异步操作执行完后才会执行,如果希望同时执行,可以修改为以下:
function timeout(value, ms) {
return new Promise((resolve, reject) => {
setTimeout(resolve, ms, value);
});
}
async function foo() {
let x = timeout('hello timeout 1', 5000);
let y = timeout('hello timeout 2', 5000);
return [await x, await y]; // <--------- 修改await位置
}
let pre = new Date().getTime();
foo().then(v => {
console.log(v);
let end = new Date().getTime();
console.log('time: ', end - pre);
});
// 输出:
// [ 'hello timeout 1', 'hello timeout 2' ]
// time: 10029
Promise.all
中。async function foo() {
return await Promise.all([x = timeout('timeout 1', 5000), timeout('timeout 2', 5000)]);
}
Class简介
生成对象的传统方法就是通过构造函数。
function Point(x, y) {
this.x = x;
this.y = y;
}
Point.prototype.hi = function () {
return `(${this.x}, ${this.y})`;
}
var p = new Point(1, 2);
console.log(p.hi());
使用ES6
的class
关键字
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
hi() {
return `(${this.x}, ${this.y})`;
};
}
var p = new Point(1, 2);
console.log(p.hi());
console.log(typeof Point); // function
console.log(Point === Point.prototype.constructor); // true
ES6
的类,完全可以看作是构造函数的另一种写法。类的数据类型就是函数,类本身就指向构造函数。ES6
类内部所定义的方法,都是不可以枚举的。
class Point1 {
constructor(x, y) {
this.x = x;
this.y = y;
}
hi() {
return `(${this.x}, ${this.y})`;
};
}
function Point2(x, y) {
this.x = x;
this.y = y;
}
Point2.prototype.hi = function () {
return `(${this.x}, ${this.y})`;
}
console.log(Object.keys(Point1.prototype)); // [], 不可枚举
console.log(Object.keys(Point2.prototype)); // ['hi']
与ES5
一样,实例的属性除非是显示定义在其本身(也就是定义在this
上),否则都是定义在原型上(也就是定义在class
上)。
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
hi() {
return `(${this.x}, ${this.y}, ${z})`;
};
}
var p = new Point(1, 2);
console.log(p.hasOwnProperty('x')); //true
console.log(p.hasOwnProperty('y')); //true
console.log(p.hasOwnProperty('hi')); //false
console.log(p.__proto__.hasOwnProperty('hi')); //true
x
和y
都是实例point
自身的属性,因为定义在this
上,而hi
是定义在Point
类上。和ES5
一样,类的所有实例共享一个原型对象
var p1 = new Point(1, 2);
var p2 = new Point(3, 4);
console.log(p1.__proto__ === p2.__proto__); // true
__proto__
__proto__
并不是语言本身的特性,而是各大厂商具体实现时添加的私有属性,虽然很多现代的浏览器都支持这个私有属性,但仍然不建议在生产中使用,避免对环境产生依赖。在生产环境中,可以使用Object.getPrototypeOf
来获取实例对象的原型。var p1 = new Point(1, 2);
var p2 = new Point(3, 4);
console.log(p1.__proto__ === Object.getPrototypeOf(p2)); // true
Class
表达式
const Point = class {
constructor(x, y) {
this.x = x;
this.y = y;
}
hi() {
return `(${this.x}, ${this.y})`;
};
}
let p = new Point(1, 2);
console.log(p.hi());
new.target属性
new
命令作用于的那个构造函数。new
调用的,那么new.target
返回undefined
。示例: 类构造函数必须用new
命令调用
class Point {
constructor () {
if (new.target === 'undefined') {
throw new Error('new error');
}
}
}
示例: 子类继承父类时,父类构造函数中的new.target
返回的是子类
class Point {
constructor() {
console.log('new.target', new.target);
console.log(new.target === Point);
if (new.target === 'undefined') {
throw new Error('new error');
}
}
}
class SubPoint extends Point {
}
var p = new Point(); // true
var sp = new SubPoint(); // false
继承
Class
可以通过extends
关键字实现继承。constructor
方法中调用super
方法,否则新建实例时报错。这是因为子类没有自己的this
对象,而是继承父类的this
对象。如果不调用super
方法,子类就得不到this
对象。且只有先调用super
后,子类才能使用this
。Object.getPrototypeOf
可以从子类上获取父类。
console.log(Object.getPrototypeOf(SubPoint) === Point); // true
console.log(SubPoint.__proto__ === Point); // true
console.log(SubPoint.prototype.__proto__ === Point.prototype); // true
__proto__
属性,表示构造函数的继承,指向父类。prototype
属性的__proto__
属性,表示方法的继承,指向父类的prototype
。实例的__proto__
属性
__proto__
属性的__proto__
属性,指向父类实例的__proto__
属性。var p1 = new Point(1, 2);
var p2 = new SubPoint(2, 3);
console.log(p2.__proto__ === p1.__proto__); // false
console.log(p2.__proto__.__proto__ === p1.__proto__); // true
Mixin
模式实现
Mixin
模式是指,将多个类的接口混入另一个类。可以将多个对象合成一个类。class Point {
hi() {
console.log('hi Point');
}
}
class Color {
bye() {
console.log('bye Color');
}
}
var copyProperties = function (target, source) {
for (let key of Reflect.ownKeys(source)) {
if (key !== 'constructor' && key !== 'prototype' && key !== 'name') {
let value = Object.getOwnPropertyDescriptor(source, key);
Object.defineProperty(target, key, value);
}
}
}
var mix = function (...mixins) {
class Mix {}
for (let mixin of mixins) {
copyProperties(Mix, mixin);
copyProperties(Mix.prototype, mixin.prototype);
}
return Mix;
}
class Item extends mix(Point, Color) {
}
var item = new Item();
item.hi();
item.bye();
'use strict';
。 with
语句。delete prop
删除变量,只能删除属性delete global[prop]
。eval
不会在它的外层作用域引入变量。eval
和arguments
不能被重新赋值。arguments
不会自动反映函数参数的变化。arguments.callee
。arguments.caller
。this
指向全局对象。fn.caller
和fn.arguments
获取函数调用的堆栈。protected
、static
和interface
)。export
export
关键字输出该变量。比如有一个文件为config.js
,专门存储一些配置:
var config = {
dialect: 'mysql',
database: 'nodejs_learn',
username: 'root',
password: '123456',
host: 'localhost',
port: 3306
};
export {config};
config.js
为一个模块,里面用export
对外部输出了这个变量。import
使用export
命令定义了模块的对外接口后,其它JS文件就可以通过import
命令加载这个模块。
// db.js
import {config} from './config.js';
import
还可以指定导入变量名,当然这个变量名要在export
中找的到。还可以为导入的变量取别名。
import defaultMember from "module-name";
import * as name from "module-name";
import { member } from "module-name";
import { member as alias } from "module-name";
import { member1 , member2 } from "module-name";
import { member1 , member2 as alias2 , [...] } from "module-name";
import defaultMember, { member [ , [...] ] } from "module-name";
import defaultMember, * as name from "module-name";
import "module-name";
import
命令具有提升效果,会提升到整个模块头部,首先执行。import
是静态执行的,所以不能使用表达式和变量,这些只有在运行的时候才能得到结果的语法结构。版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。