Menu

  • Home
  • Work
    • Cloud
      • Virtualization
      • IaaS
      • PaaS
    • Java
    • Go
    • C
    • C++
    • JavaScript
    • PHP
    • Python
    • Architecture
    • Others
      • Assembly
      • Ruby
      • Perl
      • Lua
      • Rust
      • XML
      • Network
      • IoT
      • GIS
      • Algorithm
      • AI
      • Math
      • RE
      • Graphic
    • OS
      • Linux
      • Windows
      • Mac OS X
    • BigData
    • Database
      • MySQL
      • Oracle
    • Mobile
      • Android
      • IOS
    • Web
      • HTML
      • CSS
  • Life
    • Cooking
    • Travel
    • Gardening
  • Gallery
  • Video
  • Music
  • Essay
  • Home
  • Work
    • Cloud
      • Virtualization
      • IaaS
      • PaaS
    • Java
    • Go
    • C
    • C++
    • JavaScript
    • PHP
    • Python
    • Architecture
    • Others
      • Assembly
      • Ruby
      • Perl
      • Lua
      • Rust
      • XML
      • Network
      • IoT
      • GIS
      • Algorithm
      • AI
      • Math
      • RE
      • Graphic
    • OS
      • Linux
      • Windows
      • Mac OS X
    • BigData
    • Database
      • MySQL
      • Oracle
    • Mobile
      • Android
      • IOS
    • Web
      • HTML
      • CSS
  • Life
    • Cooking
    • Travel
    • Gardening
  • Gallery
  • Video
  • Music
  • Essay

ECMAScript6学习笔记

30
Nov
2016

ECMAScript6学习笔记

By Alex
/ in JavaScript
/ tags 学习笔记
0 Comments
简介

ECMAScript6发布于2015年,是下一代ECMAScript的标准。当前ES6的支持情况如下:

  1. 最新的浏览器:Chrome 54、Opera 41、Safari 10、Firefox 50、Edge 14、iOS 10等已经支持ES6的大部分特性
  2. 利用Babel等JavaScript编译器,可以把ES6编译为老旧浏览器支持的脚本。使用Babel 6.5 + core-js 2.4可以支持大部分的ES6特性。编译器的存在意味着你可以放心的使用ES6编写代码,却不需要担心浏览器能否支持
  3. 在服务器端,Node.js 6.5、7.x支撑大部分ES6特性

JavaScript原本由网景公司发明,它将其提交给标准化组织ECMA管理,ECMA发布语言标准后,将其命名为ECMAScript。可以认为我们常说的JavaScript是ECMAScript标准的实现。

新特性
ES7

本节介绍ES7(2016)版本引入的新特性。

Array.prototype.includes()

判断数组是否包含指定的值。

**

指数操作符。

ES8

本节介绍ES8(2017)版本引入的新特性。

async/await

async用于声明一个函数是异步的。await用于等待一个异步函数执行完成。await只能出现在async函数的函数体中(不能是内嵌函数的函数体)。

async会导致一个函数的返回值变成Promise:

JavaScript
1
2
3
4
5
6
async function testAsync() {
    return "hello async";
}
 
const result = testAsync();
console.log(result);  // Promise { 'hello async' }

对于上面这个返回直接量的函数,async会直接利用 Promise.resolve()将其封装为Promise。 如果函数没有返回值,则将undefined封装为Promise。

await用于等待一个表达式的结果:

  1. 如果表达式的结果是直接量,则直接返回
  2. 如果表达式的结果是一个Promise,则它会阻塞直到该Promoise被resolve

本质上async就是Promise的语法糖。下面的例子,让代码变得简洁:

JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
function takeLongTime(n) {
    return new Promise(resolve => {
        setTimeout(() => resolve(n + 200), n);
    });
}
 
function step1(n) {
    console.log(`step1 with ${n}`);
    return takeLongTime(n);
}
 
function step2(n) {
    console.log(`step2 with ${n}`);
    return takeLongTime(n);
}
 
function step3(n) {
    console.log(`step3 with ${n}`);
    return takeLongTime(n);
}
 
// Promise语法
function doIt() {
    console.time("doIt");
    const time1 = 300;
    step1(time1)
        .then(time2 => step2(time2))
        .then(time3 => step3(time3))
        .then(result => {
            console.log(`result is ${result}`);
            console.timeEnd("doIt");
        });
}
 
doIt();
 
 
// async/wait语法
async function doIt() {
    console.time("doIt");
    const time1 = 300;
    const time2 = await step1(time1);
    const time3 = await step2(time2);
    const result = await step3(time3);
    console.log(`result is ${result}`);
    console.timeEnd("doIt");
}
 
doIt();

要注意await后面的Promise可能会被Rejected,因此最好:

JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
async function f() {
  try {
    await returnPromise();
  } catch (err) {
    console.log(err);
  }
}
 
// 或者
async function f() {
  await returnPromise().catch(function (err){
    console.log(err);
  });
} 
Object.values()

返回所有属性的值。 Object.keys()用于返回所有属性的名字。

Object.entries()

返回所有属性键值对的数组。

String.prototype.padStart/padEnd

字符串补白。

函数参数列表结尾允许逗号
Object.getOwnPropertyDescriptors()

返回对象自身属性的描述符。

SharedArrayBuffer

创建通用的原始二进制数据缓冲区,类似于ArrayBuffer。

Atomics

提供SharedArrayBuffer上的原子操作。

基础新特性

ES引入了一系列的新语法,本章内容将一一介绍。

let关键字

该关键字类似于var,用于声明一个变量。但是,let声明的变量的作用域仅仅在当前代码块。也就是说,let为JavaScript引入了块级作用域:

JavaScript
1
2
3
4
5
6
{
    var user1 = 'meng';
    let user0 = 'alex';
}
user1;
user0;  // ReferenceError: user0 is not defined

let关键字适用于for循环中的计数器变量定义。

变量提升

let变量不存在var变量那种所谓变量提升(Hoisting)的特性,也就是说,必须先声明后使用,否则会报ReferenceError错。

而且,只要在一个代码块中的任何地方声明了 let varname ,则块外部声明的任何名为varname的变量都被屏蔽:

JavaScript
1
2
3
4
5
6
var varname = true;
{
    varname = false;  // ReferenceError: varname is not defined
    typeof varname;   // ReferenceError: varname is not defined 而不是返回undefined
    let varname;
}

上例中,在let声明出现之前,变量varname不可用的现象称为temporal dead zone。

let与全局变量

所谓全局对象,是指浏览器中的window/self变量、Node.js中的global变量。在全局作用域通过var声明的变量,自动成为全局对象的属性。

ES6规定,以var、function声明的全局变量,仍然是全局对象的属性,但是以let、const、class关键字声明的全局变量,不作为全局对象的属性。

块级作用域

let关键字为JavaScript引入了块级作用域,其特性如下:

  1. 块级作用域可以无限嵌套
  2. 内部块可以看到外部块的let变量
  3. 内部块可以覆盖外部块的let变量定义
  4. 外部块不能读写内部块定义的let变量

有了块级作用域后,以防止污染命名空间为目的的立即调用函数表达式(IIFE)不再需要:

JavaScript
1
2
3
4
5
6
7
8
// IIFE风格
(function () {
    var tmp = {};
}());
// 等价的块作用域风格
{
    let tmp = {};
}
块级作用域和函数

ES5规定,函数只能在全局作用域,或者函数作用域的最外层声明,不能定义在代码块中。但是,很多浏览器没有遵循此标准,仍然允许在代码块中声明函数。

ES6引入了块级作用域,在代码块中声明的函数,仅仅在块内可见,就好像加了let声明的变量一样。 但是,为了 避免兼容性问题,ES6允许在浏览器环境中运行的JavaScript不遵循此规定,而是:

  1. 允许在块级作用域中声明函数
  2. 函数声明会提升到全局或者函数作用域

由于不同浏览器的行为不同,应该避免在块级作用域声明函数,如果一定要声明,应该使用函数表达式语法:

JavaScript
1
2
3
4
5
6
7
8
9
10
{
    // 使用这种语法,明确指定函数仅仅在块中可用
    let func = function(){
        
    }
    // 这种语法,可能会导致提升,由于浏览器的差异可能导致意外行为
    function func(){
        
    }
}
const关键字

该关键字用于声明一个常量,常量一旦声明,其值就不能改变。在声明常量式必须同时指定初始化式,否则会报错:

JavaScript
1
2
const PI;
// SyntaxError: Missing initializer in const declaration

const变量在作用域上的特征、与全局对象的关系,与let变量一致。

对于对象、数组等类型,const实际上仅仅限定变量指针的不变性,这类似于Java中的常量。要实现C语言那样的常量,可以调用ES5引入的 Object.freeze() 方法。

class关键字

JavaScript语言采用了基于原型的继承机制:

JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 构造函数,定义了一个类型
function Fruit( weight ) {
    // 构造函数中,可以使用this访问正在构造的对象
    this.weight = weight;
}
// 构造函数的prototype属性,即为类型的原型对象,每个实例都继承原型的属性、方法
Fruit.prototype.getWeight = function () {
    return this.weight;
}
function Apple( weight ) {
    this.weight = weight;
}
// 可以设置原型对象,达到继承的效果
Apple.prototype = new Fruit();
 
var apple = new Apple( 1.2 );
console.log( apple.getWeight() ); // 1.2

这种机制与Java、Python等基于类的继承机制差异很大,容易让JavaScript新手困惑。

ES6引入类似于传统OO语言的类型定义语法: 

JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
// 定义类型
class Fruit {
    // 定义构造函数
    constructor( weight ) {
        this.weight = weight;
        // 默认返回this,但是你可以返回任意对象
    }
    // 方法之间不需要逗号间隔,不需要function关键字
    getWeight() {
        return this.weight;
    }
}

这种语法本质上仅仅是语法糖,并没有改变JavaScript的继承机制 :

JavaScript
1
2
3
console.log( typeof Fruit );  // 类型仍然是function
// class块中的方法,仍然是声明在原型对象上的
console.log( Fruit.prototype.getWeight  );

注意:

  1. class块中的constructor函数,不通过new是无法调用的,会报错
  2. class块中的定义的方法,都是不可枚举的
  3. 除非显式的定义在this上面,属性/方法都是定义在原型对象上的:
    JavaScript
    1
    2
    3
    4
    var f = new Fruit( 1.2 );
    console.log( f.hasOwnProperty( 'weight' ) ); // true
    console.log( f.hasOwnProperty( 'getWeight' ) ); // false
    console.log( f.__proto__.hasOwnProperty( 'getWeight' ) ); // true 
  4. 类的所有实例,共享一个原型对象
  5. 不存在Hoisting,即必须先声明class才能使用:
    JavaScript
    1
    2
    new Fruit();   // ReferenceError: Fruit is not defined
    class Fruit {}
  6. class块内部,默认是严格模式
class表达式

类似于函数,class也支持以表达式方式声明:

JavaScript
1
2
3
4
5
6
const Fruit = class F {
    getClassName() {
        return F.name;
    }
};
console.log( new Fruit().getClassName() ); // F

上面的例子中,对外暴露出来的类名是Fruit,F仅仅可以在class内部使用,代表当前类,F可以省略。 

可以定义一个立即构造的匿名类:

JavaScript
1
2
3
4
5
let apple = new class {
    constructor( weight ) {
        this.weight = weight;
    }
}( 1.2 );
私有方法

class块中定义的所有方法都对外部可见,尽管如此,我们可以利用Symbol的唯一性,来定义无法引用的私有方法:

JavaScript
1
2
3
4
5
const checkWeight = Symbol( 'checkWeight' );
class Fruit {
    [checkWeight]() {
    }
}
继承

ES6允许定义类的继承关系,使用类似于Java的extends关键字:

JavaScript
1
2
3
4
5
6
7
// 使用extends关键字继承
class Apple extends Fruit {
    constructor( weight ) {
        // 调用父构造器
        super( weight );
    }
}

在构造子类的实例时,必须先创建父类的实例,并在其基础上添加新的属性/方法。 子类构造器必须调用父类的构造器,然后才能使用this关键字。

属性__proto__用于读写一个对象的原型对象。子类的__proto__等于父类,代表了构造函数之间的继承关系:

JavaScript
1
Apple.__proto__ === Fruit // true

子类的prototype属性的__proto__属性,等于父类的prototype属性,代表了原型对象之间的继承关系:

JavaScript
1
Apple.prototype.__proto__ === Fruit.prototype // true

子类实例的__proto__的__proto__属性,等于父类实例的__proto__属性。

super关键字

此关键字可以:

  1. 代表父类的构造函数,只能出现在子类的构造函数中
  2. 代表父类的原型对象,注意定义在父类实例上的方法/属性,无法通过super访问到。当通过super调用父类的方法/属性时,方法自动绑定到子类的this,这意味着属性值会直接设置到子类实例上:
    JavaScript
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    class Fruit {}
    class Apple extends Fruit {
        constructor() {
            super();
            super.name = 'Apple';
            console.log( super.name ); // undefined
            console.log( this.name );  // Apple
        }
    }
    new Apple();
继承原生类型

在ES5中,无法扩展Boolean、Number、String、Array、Date、Function、RegExp、Error、Object等原生构造函数。这是因为子类型无法获得原生构造函数的内部属性。

ES5是先创建子类的this,然后再把父类的属性添加到此this上,而父类的内部属性无法获取。

ES6则是先创建父类的this,再基于子类构造函数修饰这个this,因此父类的所有行为都可以继承:

JavaScript
1
2
3
4
5
6
7
8
class NegIdxArray extends Array {
    constructor() {
        super( ...arguments );
    }
}
var nia = new NegIdxArray();
nia[ 0 ] = 0;
console.log( nia.length ); // 打印1,行为正常

当继承Object时,需要注意,ES6禁止向Object()传递任何参数。

getter/setter

可以在class块内定义getter/setter,其行为与ES5保持一致:

JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Fruit {
    constructor( weight ) {
        this._weight = weight;
    }
    get weight() {
        return this._weight;
    }
    set weight( weight ) {
        console.log( `Setting weight: ${weight}`);
        this._weight = weight;
    }
}
var f = new Fruit( 1.2 );
f.weight = 1.5; // Setting weight: 1.5
生成器方法

可以在class块内部定义生成器方法,例如下面的例子:

JavaScript
1
2
3
4
5
6
7
8
9
10
11
class Foo {
    constructor( ...args ) {
        this.args = args;
    }
    // 将Foo变为Iterable类型
    * [Symbol.iterator]() {
        for ( let arg of this.args ) {
            yield arg;
        }
    }
}
静态方法

可以在方法定义上添加static限定符,这样的方法不会被实例继承,但是可以被子类继承。静态方法只能通过类名调用:

JavaScript
1
2
3
4
5
6
7
8
9
class Fruit {
    static newInstance() {
        return new Fruit();
    }
}
class Apple extends Fruit {
 
}
console.log( Fruit.newInstance() ); // Fruit {}
class与属性

ES6规定,class内部仅仅包含方法的定义,不包括任何属性。要声明属性,可以:

  1. ClassName.prop:这样的属性是静态属性,必须在class块外部定义
  2. this.prop:这样的属性是实例属性,必须在构造函数、实例方法中使用

ES7包含了静态属性语法的提案,现在可以通过Babel转换器得以支持,语法如下:

JavaScript
1
2
3
4
class ClassName {
    static staticProp = 0;
    instanceProp = 0;
}
new.target

ES6允许使用new.target这个表达式:

  1. 如果构造函数不是通过new关键字调用,则在构造函数中new.target的值为undefined,否则返回当前类的名称
  2. 以new调用子类构造函数时,new.target总是等于子类的名字,即使该表达式出现在父类构造器中

使用new.target,我们可以定义“抽象类”:

JavaScript
1
2
3
4
5
6
7
class Shape {
    constructor() {
        if ( new.target === Shape ) {
            throw new Error( `Type Shape is abstract.` );
        }
    }
}
模块

JavaScript一直没有模块体系,不能自然的把大型应用程序拆分为相互依赖的小文件,并通过简单的方法拼装。

在ES6之前,一些开源框架引入了模块化方案,例如CommonJS和AMD,两者分别用于服务器和浏览器。现在ES6在语言层面上添加了通用的模块化支持。

CommonJS、AMD之类的框架,都是在运行时完成模块依赖关系的处理、导入/导出变量的推导的,例如:

JavaScript
1
2
3
4
5
6
// CommonJS模块
let { stat, exists, readFile } = require('fs');
 
// 等同于
let _fs = require('fs');
let stat = _fs.stat, exists = _fs.exists, readfile = _fs.readfile;

上述代码相当于整体的加载fs模块生成一个临时对象,然后再读取该对象的属性,导出为变量的。这是典型的运行时加载,因为临时对象必须在运行时才能创建。相比之下,ES6中的模块并非对象,采用的加载方式是静态加载,利用 import 、 export 关键字显式的导入/导出变量:

JavaScript
1
import { stat, exists, readFile } from 'fs'; 

如果要在浏览器中引用一个ES6模块,需要使用如下标签:

XHTML
1
<script type="module" src="foo.js"></script>

注意:ES6模块动采用严格模式,不需要在模块头部显式指定。

export

该关键字用于规范模块的对外接口。一个模块是一个独立文件,其内部所有变量,外部无法读取。如果希望某个变量被外部访问,就必须使用export关键字来导出此变量,export语句可以位于模块作用域的任何位置,不能位于块、函数体内部。示例:

JavaScript
1
2
3
4
5
6
7
8
9
let name = 'Alex';
let age = 30;
// 导出方式一:
export let name;
// 导出方式二:导出多个变量。这种方式更好,在脚本尾部统一导出,容易看清导出的内容
export {name, age}; 
// 函数、类的导出语法类似
export function func(){}
export class cls{}

默认情况下,导出变量的名字与其在模块中的名字一致,但可以使用as关键字修改为任意名字:

JavaScript
1
2
3
4
5
export {
    name as userName,
    age as userAge,
    age as userAge2   // 同一变量可以导出多次
};

导出的变量,与模块内对应变量是动态绑定的,在运行期间,模块内变量的值发生变化则导出变量也跟着变化:

JavaScript
1
2
3
export var num = 1;
setTimeout( () => num = 2, 1000 );
// 在一秒后,任何导入num的脚本中num的值均为2

这一动态绑定的特性与CommonJS完全不同,CommonJS的导出变量不会动态更新。 

import

该关键字用于输入其它模块提供的功能。一旦某个模块通过export暴露了某些变量,其它JS文件就可以通过import命令引用它们:

JavaScript
1
2
// 不需要模块的指定.js后缀
import {name, age} from './user';

import也支持as关键字: 

JavaScript
1
import {name as userName} from './user';

import具有Hoisting效果,即import语句总是相当于在脚本最顶端执行。 

import *

使用通配符 * 可以导入模块所有暴露的变量:

JavaScript
1
2
import * as user from './user';
console.log( user.name );

通配符导入,会自动忽略模块中的默认导出。

export default

通过import命令导入变量时,你必须知道这些变量的导出名。模块作者可以指定一个变量为“默认导出”:

export-default.js
JavaScript
1
2
3
4
5
6
7
8
9
// 默认导出函数,可以是匿名函数
export default function () { }
 
// 默认导出变量
var name = 'Alex';
export default name;
 
// 默认导出类
export default class {}

这样,导入该模块的JS,可以用任意名字指代此默认导出: 

JavaScript
1
import customName from './export-default';

进行默认导入时,变量名周围不能加大括号。 

ES6模块与CommonJS的比较

ES6模块,导出的是变量引用,而CommonJS导出的则是值的拷贝。

CommonJS导入的变量,与定义它的模块中的那个变量没有联动关系,各自独立变化。

ES6导入的变量,是一个“常指针”,不能对导入的变量进行重新赋值:

JavaScript
1
2
3
4
import { obj } from './lib';
 
obj.prop = 123; // OK
obj = {}; // TypeError

 

ES6的导入是在编译期处理的,这意味着,要导入什么模块在编译期就必须确定,不能依赖于运行时:

JavaScript
1
2
3
4
5
const path = './' + fileName;
// CommonJS 运行时决定加载的模块
const myModual = require(path);
// ES6 不支持:
import * from path; // 语法错误:string literal expected

目前有一个提案,建议引入 import() 函数,以支持运行时加载。该函数与CommonJS的 require() 不同之处是,它是异步加载,返回一个Promise。

启用ES6模块支持

目前的浏览器、Nodejs都不支持ES6模块。除了Babel以外,还可以使用:

  1. ES6模块编译器:可以将ES6模块转换为CommonJS或者AMD模块,以便在浏览器中使用
  2. SystemJS:支持在浏览器中加载ES6、AMD、CommonJS模块,并将其转换为ES5代码:
    XHTML
    1
    2
    3
    4
    5
    6
    <script src="system.js"></script>
    <script>
        // import方法是异步的,返回一个Promise对象
        // 这里的app.js可以使用ES6模块语法
        System.import('./app.js');
    </script> 
解构赋值

ES6引入一种新语法,允许当赋值语句的左值、右值遵循相同的模式(Pattern)时,进行自动赋值。

数组的解构赋值
JavaScript
1
2
3
4
let [a, b, c] = [ 1, 2, 3 ]; // 1 2 3
[ a, [ [ b ], c ] ] = [ 4, [ [ 5, 0 ], 6 ] ]; // 4 5 6
[ a, b, c ] = [ , , 9 ]; // 4 5 9
[ a, b ] = [ 1, 2, 3 ]; // 1 2 9 

除了let变量,var、const变量也支持使用解构赋值。解构赋值可以部分成功,失败的变量自动赋值为undefined。

实际上,任何Iterator都可以作为数组解构的右值:

JavaScript
1
2
3
4
5
6
7
8
9
10
11
// Set结构
let [a, b, c] = new Set( [ 0, 0, 0 ] );
// 生成器函数
function* gnerator() {
    let a = 0;
    while ( true ) {
        yield ++a;
    }
}
[ a, b, c ] = gnerator()
console.log( [ a, b, c ] ); // [ 1, 2, 3 ]
默认值

前面提到过,结构赋值失败时,变量赋值为undefined(即使此变量赋值前是有值的)。ES6允许设置解构赋值的默认值,语法如下:

JavaScript
1
2
// 在变量名后面增加 = value
let [a,b = 2,c = 3] = [ 1, null ];

判断Pattern对应位置是否有值时,ES6使用严格相等操作符===与undefined比较,因此上面的例子中,b的默认值2不会生效。

默认值可以指定为一个表达式,该表达式仅仅在需要时(Pattern对应位置为undefined)才会求值:

JavaScript
1
2
3
4
function defaultValue() {
    console.log( 'This function won't be invoked' );
}
let [a = defaultValue()] = [ null ];
对象解构赋值

与适用于Iterator的方括号语法类似,对象语法也可以用于解构赋值:

JavaScript
1
2
const { a, b, c }={ a: 1, b: 2, c: 3 }
console.log( c ); // 3

方括号语法根据位置去匹配,而对象解构赋值使用属性名去匹配,位置顺序不重要。 

如果变量名与属性名不一样,必须使用下面的语法:

JavaScript
1
2
const { propname:varname }={ propname: 1 };
console.log( varname );// 1

对象解构也可以设置默认值: const { propname:varname = 1 }={}  

对已声明的变量进行解构赋值,可能会被JavaScript引擎当作代码块来解释进而导致语法错误:

JavaScript
1
2
3
4
var a, b, c;
{ a, b, c } = { a: 1, b: 2, c: 3 };  //SyntaxError: Unexpected token =
// 正确的写法如下:
({ a, b, c } = { a: 1, b: 2, c: 3 })

使用对象解构赋值,可能很方便的把对象的属性、方法赋值给多个变量,例如:

JavaScript
1
let { log, sin, cos } = Math;
字符串解构赋值

字符串作为解构赋值的右值,可以把字符逐个赋值给变量: 

JavaScript
1
let { a, b, c } = 'alex';
数字/布尔的解构赋值

数字/布尔值作为解构赋值的右值时,引擎先将其转换为对象: 

JavaScript
1
2
let {toString: s} = true;
s === Boolean.prototype.toString
函数解构赋值

函数的参数,可以作为解构赋值的左值:

JavaScript
1
2
3
4
5
6
7
8
9
10
11
function add( [x,y] ) {
    return x + y;
}
console.log( add( [ 1, 2 ] ) ); //3
console.log( add( 1, 2 ) ); // 错误:调用必须使用解构语法
 
// 对象解构语法、默认值都是支持的
function add( { x, y = 1 } ) {
    return x + y;
}
console.log(add( { x: 1 } ));  // 2

函数的返回值,可以作为解构赋值的右值:

JavaScript
1
2
3
4
function pair() {
    return [ -1, 1 ];
}
let [l,r ] = pair();

可以看到,上例中的用法实现了类似Python函数调用的多个返回值的效果。 

解构赋值的用途

合理使用解构赋值,可以让代码更加简洁,下面是一些应用场景举例: 

交换变量的值: [x, y] = [y, x]; 
从函数返回多个值
为函数参数提供默认值:因为解构赋值的左值可以提供默认值

更丰富的参数风格:

JavaScript
1
2
3
4
5
6
7
8
9
// 普通传参风格:
function func( a, b, c ){}
func( 1, 2, 3 );
// 允许以数组放入传入所有参数:
function func( [a,b,c] ){}
func( [1, 2, 3] );
// 允许类似于Python的关键字参数,实现乱序传参
function func( { a, b, c } ){}
func( { b: 2, a: 1, c: 3 } ) 

快速数据提取:

解构赋值可以使用非常简洁的代码从JSON中提取数据:

JavaScript
1
2
3
4
5
6
7
8
9
10
let json = {
    success: true,
    message: 'OK',
    object: {
        name: 'Alex',
        age: 30
    }
};
let { object:{ name, age } } = json;
console.log( name, age );  // Alex 30 

遍历map:

任何Iterator都可以使用for...of循环来迭代,配合解构赋值,可以很方便的获得map的键值:

JavaScript
1
2
3
for ( let [key, value] of map ) {
    console.log( key + " is " + value );
}

导入模块的指定成员:

JavaScript
1
const { min, max } = require("module-stat");
展开运算符
数组展开

展开运算符也是以 ... 作为前导(类似于函数的Rest参数),展开运算符用于把数组展开为若干个值,主要用于函数调用:

JavaScript
1
2
console.log( ...[ 1, 2, 3, 4, 5 ] );  // 1 2 3 4 5
console.log( [ 1, 2, 3, 4, 5 ] );     // [ 1, 2, 3, 4, 5 ]

展开运算符可以代替函数的apply方法。

展开运算符也可以用来合并数组:

JavaScript
1
2
var array = [ 1, 2, 3 ];
var array2 = [ 0, ...array ];

也可以和解构赋值联合使用(用在左值列表的结尾):

JavaScript
1
2
let [a, ...rest] = [ 1, 2, 3, 4, 5 ];
console.log( rest ); // [ 2, 3, 4, 5 ]

展开运算符还可以:

  1. 正确的把字符串转换为字符的数组,支持所有Unicode: [...'hello']  
  2. 把任何Iterator转换为真正的数组: [...iterator] 
对象展开

和数组类似,对象也支持使用展开操作符:

JavaScript
1
2
3
4
5
6
// 用于解构赋值,z = { a: 3, b: 4 }
let { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 };
 
// 取出对象z的可枚举属性,存放到对象n中。等同于使用Object.assign方法
let z = { a: 3, b: 4 };
let n = { ...z }; 
对象
属性简写

ES6允许定义对象属性时,指定一个变量:

JavaScript
1
2
3
4
5
6
7
8
let name = 'Alex';
let age = 30;
let user = { name, age };
 
// 另外一个例子
function func( x, y ) {
    return { x, y };
}

这样,变量名就作为属性名,变量值则作为属性值。

方法简写

和属性类似,方法也允许简写:

JavaScript
1
2
3
4
5
6
7
8
9
10
var user = {
    getName(){
        return this.name
    },
    // ES5的Getter/Setter类似于方法简写
    get name(){
    },
    // 如果某个方法是生成器,则必须加*前缀
    * generator(){yield 0;}
}
表达式作为属性名

ES6允许在花括号语法中,用表达式来组成属性名、方法名:

JavaScript
1
2
3
var user = {
    ['name' + i]: 'Alex'
}

这种写法不能和属性名简写同时使用。 另外,作为属性名的表达式会自动转换为字符串。

函数
默认参数

ES6允许为函数参数提供默认值: function log( x, base = 10 ){} 

使用默认值后,函数的length属性将返回没有指定默认值的参数个数(而不是全部参数个数)。

Rest参数

ES6可以将参数列表中剩余的所有参数暴露为一个数组:

JavaScript
1
2
3
function(a, b, ...theArgs) {
    // 第三个及之后的参数,存放在theArgs变量中
}

Rest参数必须作为最后一个形参。

name属性 

函数的name属性返回函数的名称,ES6将其纳入标准。

this的指向

在全局作用域下,this指向全局对象:浏览器中的window/self或者Node.js中的global

在Node.js模块、ES6模块的定义中,this指向当前模块

在函数作用域下,如果:

  1. 函数作为对象的方法调用,则this指向当前对象
  2. 普通模式下,函数不作为方法调用,则this指向全局对象
  3. 严格模式下,函数不作为方法调用,则this指向undefined
::操作符

该操作符是ES7提案的一部分,目前可以通过Babel支持它,用于绑定函数到目标对象并调用:

JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
// 绑定
foo::bar          // 等价于bar.bind(foo)
// 绑定并调用
function showThis() {
    console.log( this );
}
""::showThis();   // 等价于showThis.bind("")()
 
 
// 将obj的foo方法绑定到obj,并返回绑定后函数
var method = obj::obj.foo;
// 上述代码等价于
var method = ::obj.foo; 
尾调用

在函数式编程领域,所谓尾调用(Tail Call)是指作为函数体最后一个步骤的函数调用:

JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function f( x ) {
    return g( x );
}
// 下面三个都不是尾调用
function f( x ) {
    let y = g( x );
    return y;
}
function f( x ) {
    return g( x ) + 1;
}
function f( x ) {
    g( x );
    // 这边相当于有一个 return undefined
}

在使用递归结构时,尾调用具有优化的空间:由于尾调用作为最后一步,因此可以安全的丢弃调用者的栈帧并将其作为尾调用函数的栈帧,这样栈长度就不会增长,也不会因为过深的递归导致太多的内存占用甚至栈溢出。ES6明确要求解释器优化尾递归调用。 

要利用尾调用优化来提升程序性能,你需要注意调整自己的代码,例如下面的阶乘实现就是正面的例子:

JavaScript
1
2
3
4
5
function factorial( n, total ) {
    if ( n === 1 ) return total;
    // 尾递归调用
    return factorial( n - 1, n * total );
}

这个例子用到了递归,但是代价却与循环差不多。 

箭头函数

关键字 =>  用于定义所谓箭头函数,类似于其它语言中的Lambda:

JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 最后一个语句作为返回值
// 单个参数
var square = x => x ** 2;
// 多个参数
var add = ( a, b ) => a + b;
// 多个语句,必须使用花括号,最后一个语句return
var sayHello = who => {
    var greetings = `Hello ${who}`;
    console.log( greetings );
    return true;
}
// 与解构赋值联用
(
    ( { name, age } ) => {
        console.log( `${name} is ${age} years old` );
    }
)( { name: 'Alex', age: 30 } );

关于箭头函数,需要注意:

  1. 函数体中的this,就是箭头函数定义处的this,与调用时刻无关
  2. 不能将箭头函数作为构造函数调用,即不能new
  3. 不可以使用arguments对象
  4. 不支持yield关键字,因为其不能作为生成器 
字符串
Unicode表示法

JavaScript之前的版本支持 \uxxxx 的Unicode字符表示法,最多支持0xFFFF之前的字符。ES6则引入 \u{xxxxx} 这样的表示法,只要把代码点(Code point)放在花括号内,可以表示任意范围的Unicode字符:

JavaScript
1
"\u{41}\u{42}\u{43}"   // abc
模板字符串语法

 JavaScript中拼写HTML模板异常的繁琐,需要很多字符串拼接操作。ES6引入了新的模板字符串语法,它允许声明多行字符串,以反引号( ` )限定:

JavaScript
1
2
3
4
5
6
7
var user = { name: 'Alex', age: 30 };
var html = `
<div>
    <div>${user.name}</div>
    <div>${user.age + 1}</div>
</div>
`;

模板字符串中的空白符会被原样保留,而且,模板中支持插入当前上下文中的变量,类似于Shell的变量替换或者Java的EL表达式。 ${} 内部可以包含任意表达式,例如函数调用、其它模板字符串。 如果模板字符串引用未声明的变量将会导致报错。

标签模板

标签模板(tagged template)是模板字符串的一种高级应用。模板模板是紧跟着函数名之后的模板字符串,该函数会被调用,来处理模板字符串:

JavaScript
1
2
3
4
5
var name = 'Alex';
console.log`${name} Hello ${name}`
// 等价于
console.log( [ 'Hello ', '' ], name, name );
// 打印: [ '', ' Hello ', '' ] 'Alex' 'Alex'

从上面的例子可以看出,所谓标签模板本质上是函数调用表达式,将模板字符串解析为参数的规则如下: 

  1. 模板字符串以变量为界,切分为字符串数组。该数组为函数的第一个实参,如果模板以变量开头或者结尾、或者出现连续两个变量,则添加空串到数组中,
  2. 变量全部取出,依据出现的顺序,依次作为函数的第2-N个实参

标签模板的函数体,通常都执行这样的逻辑:

  1. 遍历变量数组(即2-N个实参)
  2. 原样连接第一实参对应索引的元素到结果字符串
  3. 处理当前变量,连接到结果字符串
  4. 遍历完毕后, 返回结果字符串

例如下面的代码,可以过滤用户输入中的不安全字符:

JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 打印: [ '', ' Hello ', '' ] 'Alex' 'Alex'
// 这些变量来自用户输入
var name = 'Alex<script>';
var age = 30;
function escapeHTML( str ) {
    var arg = String( str );
    return arg.replace( /&/g, "&amp;" ).replace( /</g, "&lt;" ).replace( />/g, "&gt;" );
}
function safehtml( literals, ...values ) {
    var index = 0;
    var result = '';
    for ( let seg of literals ) {
        result += seg;//原样加入
        // 每个元素后面都跟着一个变量,除了最后一个元素
        if ( index < values.length ) {
            result += escapeHTML( values[ index++ ] );
        }
    }
    return result;
}
// 这个模板需要被安全的插入到DOM
var result = safehtml`
    <div>Hello ${name}, You are ${age} years old</div>
`;
console.log( result );   // <div>Hello Alex&lt;script&gt;, You are 30 years old</div> 

标签模板也提供了一种直白的语法,来把其它语言的代码嵌入到JavaScript语言中,例如jsx函数可以把DOM字符串转换为React对象。

数字

ES6引入新的二进制、八进制数值直接量语法:

JavaScript
1
2
3
4
var bin = 0b1011;
bin = 0B1011;
var oct = 0o777;
oct = 0O777;
正则式
构造函数

ES6允许正则式构造函数第二个参数指定的标记,覆盖第一个参数中内置的标记:

JavaScript
1
console.log( new RegExp( /abc/ig, 'i' ).flags ); // 打印i,第一个参数的ig被替换
u修饰符

该修饰符用于支持4字节表示的Unicode字符,以及支持\u{xxx}语法的字符串:

JavaScript
1
2
/\u{61}/.test('a')  // false
/\u{61}/u.test('a') // true
y修饰符

该修饰符类似于g,也用于全局匹配,但是y限定后一次匹配必须从上一次匹配成功的下一个字符开始。

正则式对象的sticky属性指示该修饰符是否被设置。

数组
空位

数组的空位是没有任何值的索引点:

JavaScript
1
2
// 构造函数返回的数组都是空位
console.log( Array( 2 ) );  // [ ,  ]

注意:空位与undefined不同,后者是一个确实的值。要判断某个索引是否空位可以使用in运算符:

JavaScript
1
2
console.log( 0 in [] ); // false
console.log( 0 in [ undefined ] ); // true

在ES5中:

  1. forEach()、 filter()、every()、some()函数会跳过空位,不执行回调
  2. join()、toString()则将空位作为undefined看待

ES6明确的把空位作为undefined看待。

Symbol

ES6引入了一种新的数据类型,叫做Symbol,用于表示独一无二的值(类似于Java的枚举)。该类型与Undefined、Null、Boolean、String、Number、Object并列,成为第七种类型。

注意:

  1. 尽管Boolean、String、Number、Symbol不是对象类型,但是它们的行为类似于不可变对象
  2. 数组不是独立的类型,typeof返回object
  3. 尽管函数属于对象类型,但是其typeof返回function

Symbol变量通过调用Symbol函数获得:

JavaScript
1
2
3
let name = Symbol();
// 可以入参一个字符串,但是不影响不相等性
let name = Symbol('name');

注意:

  1. 不能在Symbol调用前面增加new,因为Symbol不是Object类型,也因此,不能给Symbol添加属性
  2. 任何两个Symbol均不相等
  3. Symbol不能与其它类型进行运算,但是你可以显式的将其转换为String、Boolean类型:
    JavaScript
    1
    2
    3
    var sym = Symbol('name');
    String(sym)     // Symbol('name')
    Boolean(sym) ;  // true 

现在对象的属性名,除了支持字符串类型之外,还可以支持Symbol类型:

JavaScript
1
2
3
4
let obj = {
    [name]: 'Alex'
}
console.log( obj[ name ] ); // Alex

由于每个Symbol都不相等,因此将其作为属性名时,可以防止冲突。访问由Symbol标识的属性时,只能用方括号语法,不能用点号语法。 

可以使用Symbol定义一组常量:

JavaScript
1
2
3
4
5
log.levels = {
    DEBUG: Symbol( 'debug' ),
    INFO: Symbol( 'info' ),
    WARN: Symbol( 'warn' )
};
集合类型
Set

类似于数组,但是成员的值必须唯一,即没有重复值:

JavaScript
1
2
3
var set = new Set();
[ 1, 2, 3, '3' ].map( e => set.add( e ) );
console.log( set );  // Set { 1, 2, 3, '3' }

可以看到,判断元素相等时,使用的是精确相等(Object.is)。Set构造函数有几个变体:

JavaScript
1
2
let set = new Set( [ 1, 2, 3, 3 ] ); // 传入数组
console.log( set.size );  // 3

利用Set构造函数,可以给数组去重: [...new Set(array)]  

WeakSet

与Set类似,但是成员只能是Object类型,该集合持有对象的弱引用,GC将无视这些引用。WeakSet没有size属性,也不能遍历。但是你可以调用其add/delete/has方法

Map

JavaScript对象本质上就是一个Hash结构,可以作为Map使用。但是这种Map的Key类型只能是String/Symbol。

ES6引入的Map,支持任何类型的对象作为映射的键:

JavaScript
1
2
3
4
5
6
7
var map = new Map(); // 默认构造器
map = new Map( [
    [ 'name', 'Alex' ],
    [ 'age', 30 ],
    [ 0 , 1 ]
] );
console.log( map.has( 'age' ) ); // true

Map在判断两个键是否相等时,使用类似于Object.is()判断。 

WeakMap

类似于WeakSet,键是对象的弱引用。仅仅支持将对象作为键。你可以调用其get()、set()、has()、delete()方法,不支持遍历。

迭代器

现在JavaScript中用来表示集合的结构包括:Array、Map、Set,它们都是可迭代(Iteratble)对象。所有可迭代对象都可以使用 for ... of 循环,来遍历集合的全部成员。

任何对象,只要其具有名为 Symbol.iterator 的方法,则被认为是可迭代的。对前述方法进行调用,会返回一个迭代器对象(Iterator),你可以简单的返回具有next()方法的简单对象,JavaScript引擎负责自动设置其原型为Generator.prototype:

JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const obj = {
    [Symbol.iterator]: function () {
        // 返回迭代器
        return {
            next: function () {
                return {
                    value: 1,
                    done: false
                };
            }
        };
    }
};
for ( val of obj ) {
    console.log( val ); // 无限打印
}

for..of循环遍历迭代器的过程如下:

  1. 第N次调用迭代器的next()方法,返回包含了集合的第N个元素的数据结构
  2. 返回的数据结构为: { value : '当前元素', done: '遍历是否已经结束'  } 。如果done为true则value无意义,不需要指定
  3. 根据next()的返回值,判断遍历是否完成
迭代器的其它方法

除了必须的next()方法,迭代器对象还可以提供额外的两个方法。如果不提供,默认实现由Generator.prototype提供。这两个方法的说明如下:

  1. return() :该方法会在for...of循环提前退出(出错、break)时自动调用。可以利用此方法提供资源清理逻辑:
    JavaScript
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    function readLines( file ) {
        return {
            next() {
                // 正常退出
                if ( file.eof() ) {
                    file.close();
                    return { done: true };
                }
            },
            // 中途退出,默认逻辑是简单的把入参设置到结果数据结构的value字段
            return( v ) {
                file.close();
                return { value : v, done: true };
            },
        };
    }
  2. throw() :配合生成器使用,用于在生成器外部抛出错误,在生成器内部捕获:
    JavaScript
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    var g = function*() {
        try {
            yield;
        }
        catch ( e ) {
            console.log( e );  // 1
        }
    };
     
    var i = g();
    i.next();
     
    try {
        // 注意不能用throw关键字抛出,该关键字无法影响生成器
        i.throw( 1 );   // 这个错误在内部捕获
        i.throw( 2 );   // 这个错误在外部捕获,因为第一个错误已经触发yield并导致生成器结束
    }
    catch ( e ) {
        console.log( e ); // 2
    }
自动调用

以下场景下,解释器自动遍历目标对象的迭代器: 解构赋值、扩展运算符、 yield* 、 for...of 、 Array.from 、 Promise.all() 、 Promise.race() 等等。

迭代器与生成器

让Symbol.iterator返回一个生成器,可以简化代码:

JavaScript
1
2
3
4
5
6
7
8
9
let obj = {
    // Symbol.iterator方法可以返回生成器
    * [Symbol.iterator]() {
        yield 1;
        yield 2;
        yield* [ 3, 4, 5 ];
    }
};
console.log( [ ...obj ] );  // [ 1, 2, 3, 4, 5 ]
生成器

生成器是ES6提供的异步编程解决方案。它是一种特殊的函数,其声明语法如下:

JavaScript
1
2
3
4
5
6
7
// 要声明一个函数为生成器,必须在function后附加 * 号
function* generator() {
    // 生成器函数体可以包含yield语句
    yield 1;
    yield 2;
    return 3;
}

对生成器函数执行调用操作,你会得到一个迭代器对象,但是生成器的函数体不会 执行:

JavaScript
1
2
3
4
5
6
7
8
9
function* generator() {
    yield 1;
    yield 2;
    return 3;
}
let iterable = generator(); // 此时generator的函数体并未执行
for ( e of iterable ) {
    console.log( e );  // 1 2
}

每一个yield语句后面的表达式的值,将作为迭代器的元素,注意return语句后面的表达式不作为迭代器元素。关键字yield的本义就是“产出”。

你可以手工调用上述迭代器的next()方法:

JavaScript
1
2
3
4
let iterable = generator();
console.log( iterable.next() ); // { value: 1, done: false }
console.log( iterable.next() ); // { value: 2, done: false }
console.log( iterable.next() ); // { value: 3, done: true }  return语句后的表达式作为value

从效果上来看,执行生成器函数体时,每当遇到yield就返回(暂停)一次。返回后,生成器函数体的内部状态仍然被保留。当再一次调用next()时,自动恢复内部状态,从上一次退出的那个点继续向下执行。这种行为特点与普通函数完全不同。

next( arg )

对生成器执行附带入参,可以为生成器的yield语句提供“返回值”,这里有一个特别容易误会的地方:

JavaScript
1
2
3
4
5
var generator = (function *() {
    console.log( yield 1);
})();
generator.next( 2 ); // 什么都不打印
generator.next( 3 ); // 打印3

你可以通过next()调用来启动生成器,并让它在yield一个值(上面的1)后立即暂停。如果生成器当前正因为执行了yield语句而处于暂停中,你再一次调用next()可以让其恢复执行,这时你才可以为next()调用附带参数,此参数作为返回时yield语句的值。

上面的next(2)调用,导致生成器在标记红色的地方暂停:console.log(   yield 1),打印语句根本不会执行,因此提供参数2没有任何意义。

yield*
JavaScript
1
2
3
4
5
6
7
8
9
function* numbers() {
    yield* [ 4, 5, 6 ]
}
function* generator() {
    yield* [ 1, 2, 3 ];  // 逐个yield数组
    yield* numbers();    // 逐个yield生成器
}
// 展开操作符能够遍历迭代器
console.log( [ ...generator() ] );  // [ 1, 2, 3, 4, 5, 6 ] 
协程

yield语句可以产生一个值,但是该语句本身的返回值默认是undefined。你可以在next()调用是提供一个参数,这个参数自动成为yield的返回值。带参数的next()调用类似于Python语言生成器的send()方法,这种情况下生成器函数成为协程。

协程是不依赖于线程的、轻量级的异步编程技术,它通过几个例程(函数)相互协作来完成逻辑。下面是一个简单的例子:

JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// 服务器代理
var serverAgent = (function*() {
    var clientToRequest = new Map();
    while ( true ) {
        // 1、服务器运行到yield,放弃控制权,暂停
        // 4、服务器从暂停中恢复,从yield的返回值得到客户端ID,并异步发送Ajax请求
        clientToRequest.set( yield, sendRequest() );
        // 5、遍历客户端请求的列表,判断Ajax请求状态
        for ( e of clientToRequest ) {
            var cid = e[ 0 ];
            var xhr = e[ 1 ];
            if ( isDone( xhr ) ) {
                // 6、如果Ajax请求已经完毕,则调用客户端处理,并放弃控制权
                clients[ cid ].next( xhr.responseText );
            }
 
        }
    }
})();
serverAgent.next(); // 0、启动服务器代理
 
// 很多客户端
var clients = [];
for ( i of [ 1, 2, 3, 4, 5 ] ) {
    var client = (function*() {
        // 3、客户端向服务器代理发送请求,附带自己的ID
        // 7、客户端从yield返回,返回值为服务器代理提供的响应对象。异步处理的同步化表达
        var response = yield serverAgent.next( i );
        // 8、处理响应
        console.log( response );
    })();
    clients.push( client );
    client.next(); // 2、启动客户端
}

使用协程进行异步处理,代码要比回调简洁的多。

Promise

很多JavaScript框架都提供了Promise这种异步编程框架,终于现在可以在语言级别原生的支持了。比起传统的基于回调函数、事件的异步处理,Promise更加合理和强大。

一个Promise保存着一个未来才能完成的事件(通常是异步操作)的结果。Promise所关联的异步操作,其状态只能是Pending(正在进行)、Resolved(已经解决)、Rejected(已经失败)之一。只有异步操作的结果可以决定状态到底是哪一个。状态只能从Pending变为Resolved或者Rejected,而且只能改变一次。

Promise具有一些缺点:

  1. 一旦建立就立即执行,无法取消
  2. 如果不提供回调,其内部抛出的异常无法反应到外部
  3. 无法对Pending阶段进行细分
基本用法

Promise是一个构造函数,你可以调用它创建Promise:

JavaScript
1
2
3
4
5
6
7
8
9
10
// 创建者代码:
var promise = new Promise( function ( resolve, reject ) {
    // 执行异步操作...
    if ( succeed ) {
        resolve( value ); // 将Promise置为已解决
    }
    else {
        reject( error );  // 将Promise置为已失败
    }
} );

一旦Promise被创建,通过构造函数传入的回调就会立即执行。此回调的两个参数resolve、reject,是引擎提供的函数,你可以调用它们而把Promise置为已解决、已失败状态之一。

要知道,构造函数由Promise的创建者调用,但是Promise肯定是提供给其它模块使用的。那么其它模块拿到这个Promise有什么意义呢?

其它模块可以调用它的then方法,注册当Promise状态改变后,需要执行的回调函数:

JavaScript
1
2
3
4
5
6
// 客户代码:
promise.then( function ( value ) {
    // 成功时执行的回调
}, function ( error ) {
    // 失败时执行的回调
} );

下面是一个简单的例子:

JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function timeout( ms ) {
    return new Promise( ( resolve, reject ) => {
        // 此回调会立即执行,因此下面的语句立即打印
        console.log( 'Starting process...' );
        // 异步的设置Promise为已解决状态
        setTimeout( resolve, ms, 'Success' );
    } );
}
 
// 客户代码
// 由于setTimeout是异步操作,它必然在then调用之后,才可能执行回调
timeout( 1000 ).then( ( value ) => {
    // 异步操作完成时,打印Promise创建者提供的值
    console.log( value );
} );

下面是一个基于Promise处理Ajax的例子:

JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
var getJSON = function ( url ) {
    var promise = new Promise( function ( resolve, reject ) {
        var client = new XMLHttpRequest();
        client.open( "GET", url );
        client.onreadystatechange = handler;
        client.responseType = "json";
        client.setRequestHeader( "Accept", "application/json" );
        client.send();
 
        function handler() {
            if ( this.readyState !== 4 ) {
                return;
            }
            if ( this.status === 200 ) {
                resolve( this.response );
            }
            else {
                reject( new Error( this.statusText ) );
            }
        };
    } );
 
    return promise;
};
 
// 客户代码
getJSON( "/posts.json" ).then( function ( json ) {
    // 处理JSON
}, function ( error ) {
} );

总之,都是这样的套路:服务方执行异步操作,并给客户一个承诺,客户拿到承诺后注册期望的操作,服务方完成异步操作后,调用客户端注册的操作,完成处理。 

Promise传递给resolve

Promise的创建者,可以向resolve函数传递另外一个Promise对象:

JavaScript
1
2
3
4
5
6
7
8
var p1 = new Promise(function (resolve, reject) {
    setTimeout(() => reject(new Error('Failed')), 3000);
});
 
var p2 = new Promise(function (resolve, reject) {
    // 向resolve传递另外一个Promise p1
    setTimeout(() => resolve(p1), 100);
});

P2没Resolve为一个“依然未Resolve”的Promise,此时JS引擎不会简单的直接调用客户通过then注册的回调函数(即100ms后回调不会被调用)。而是另外的那个Promise(P1)的状态决定当前Promise(P2)的状态——如果P1处于Pending则P2会等待,直到P1变为Resolved/Rejected,才调用客户回调:

JavaScript
1
2
// 于3秒后失败,而不是100ms后成功
p2.then(result => console.log(result), error => console.log(error));

基于这种用法,可以很容易的管理多个异步操作,例如只在所有这些操作都成功的时候,才认为整个事务是成功的。

Promise.prototype.then

前面的例子已经使用过此方法,它的基本作用是注册当Promise状态改变后,需要执行的回调函数。该方法可以有两个入参,它们都必须是函数,第一个作为Promise变为Resolved时的回调,第二个可选,作为Promise变为Rejected时的回调。

then()方法的返回值是一个新的Promise实例,因此可以使用链式调用。链式调用时,前面then的回调函数的返回值,将会传递给后面then的回调函数:

JavaScript
1
2
3
4
5
6
7
new Promise((resolve, reject) => {
    setTimeout(resolve, 1000, 'Succeeded');
}).then(result => {
    console.log(result);
    return 'OK';
}).then(result => console.log(result));
// 1秒后连续打印Succeeded、OK

如果前面then的回调函数的返回值是一个Promise,则后面then的回调函数直到此Promise状态改变后才被调用:

JavaScript
1
2
3
4
5
6
7
// getJSON函数返回一个Promise
getJSON("/post/1.json").then(function (post) {
    // 继续调用getJSON,返回一个Promise
    return getJSON(post.commentURL);
}).then(result=>{
    // 此回调在第二次getJSON返回的Promised被Resolved时才调用
});
Promise.prototype.catch

此方法等价于 then(null, rejection) 调用,用于指定Rejected时的回调函数。链式调用时,前面then方法抛出的异常,也经由后面的catch方法捕获。

注意,当Promise状态已经变成Resolved后,再抛出异常是无效的:

JavaScript
1
2
3
4
5
6
7
8
9
10
var promise = new Promise(function (resolve, reject) {
    resolve('OK');
    throw new Error(); // 无效
});
promise.then(function (value) {
    console.log(value)
}).catch(function (error) {
    console.log(error)
});
// 打印OK

一般情况下,都使用catch方法,而不是then的第二回调。 

Promise.all

该静态方法用于将多个Promise实例包装为单个新的Promise实例: var p = Promise.all([p1, p2, p3]) ,当:

  1. 所有成员Promise都变为Resolved后,整体Promise变为Resolved。成员Promise传递给resolve回调的参数,构成一个数组,传递给整体Promise的then回调
  2. 一旦任何一个Promise变为Rejected,整体Promise立即变为Rejected,此Rejected Promise传递给reject回调的参数,传递给整体Promise的then第二回调或者catch回调
Promise.race

该静态方法也是用于将多个Promise实例包装为单个新的Promise实例。但是,只要有一个成员Promise的状态发生改变,则整体Promise的状态也发生改变,并且与前面成员Promise的状态一致。

Promise.resolve

该静态方法用于把一个对象转换为Promise:

JavaScript
1
2
// 把jQuery生成的deferred对象转换为Promise
var p = Promise.resolve($.ajax('/data.json'));

该方法的参数,有四种情形:

  1. 参数是一个Promise对象,这种情况下resolve不对其进行任何修改,直接将其作为返回值
  2. 是一个thenable对象,例如:
    JavaScript
    1
    2
    3
    4
    5
    let thenable = {
      then: function(resolve, reject) {
        resolve(true);
      }
    };

     新建的Promise对象会立即调用这里的then方法

  3.  如果参数不具有then方法, 或者不是对象,则该方法返回一个已经Resolved的Promise,resolve的入参作为此Promise的then回调的入参
  4. 不传递参数,返回一个已经Resolved的Promise
Promise.reject

返回一个Rejected的Promise实例。

Promise.prototype.done

Promise的链式调用,不管以then还是catch结尾,其内部的异常都不会抛出到外部。为了确保未处理的异常能够抛出,可以在调用链的结尾添加一个done()调用。

Promise.prototype.finally

该方法指定一个无论什么情况下,都会执行的回调。示例:

JavaScript
1
2
3
4
5
6
7
8
// 使用垫片库获得finally支持
require( 'promise.prototype.finally' ).shim();
 
var assert = require('assert');
// 使用finally:
Promise.resolve(42).finally(function () {
    assert.equal(arguments.length, 0);
});
Reflect & Proxy
Reflect

该对象提供以下功能:

语言内部功能相关方法

Object对象某些方法,例如Object.defineProperty(),现在同时在Reflect上部署,未来的新方法统一部署到Reflect上

注意,Reflect改进了Object某些方法的行为,例如defineProperty失败时,Object返回异常,而Reflect返回false

与Proxy配合

Reflect提供了与Proxy的Handler一一对应的方法,调用这些方法,可以执行“默认行为”

Proxy

ES6引入了代理(Proxy),利用它可以实现类似于JDK动态代理、Python装饰器/元类的功能。

Proxy在被代理对象上设置一个拦截器,对此对象的访问必须经由此拦截器,拦截器可以改变方法调用、属性读写的行为。例如下面的拦截器改写了属性读写的默认行为:

JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
let target = {};
// 改变属性读写的行为,打印调试信息
var proxy = new Proxy( target, {
    get: function ( target, key, receiver ) {
        console.log( `getting ${key}` );
        return Reflect.get( target, key, receiver );
    },
    set: function ( target, key, value, receiver ) {
        console.log( `setting ${key}` );
        return Reflect.set( target, key, value, receiver );
    }
} );
 
proxy.age = 1;  // setting age
proxy.age++;    // getting age,  setting age

Proxy构造函数接受两个参数,第一个是被代理对象,第二个是处理器(Handler)。需要拦截的操作不同,则处理器写法也不同: 

Handler的键 拦截的操作
get

签名:get(target, propKey, receiver)。拦截对象属性、数组元素的读取操作

将对象的原型设置为提供了get处理器的代理,对象自动获得代理的get行为:

JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
var proxy = new Proxy( Array.prototype, {
    // receiver为被代理的那个对象
    get( target, propKey, receiver ) {
        let index = Number( propKey );
        if ( index < 0 ) {
            propKey = String( receiver.length + index );
        }
        return receiver[ propKey ];
    }
} );
 
var a = [ 1, 2, 3, 4, 5 ];
Object.setPrototypeOf( a, proxy );
console.log( a[ -1 ] );  // 5
set 签名:set(target, propKey, value, receiver)。拦截对象属性、数组元素的设置操作
apply

仅当代理的目标是函数时使用
签名:apply(target, object, args)
拦截函数调用操作:proxy(...args)、proxy.call(object, ...args)、proxy.apply(...)

示例:

JavaScript
1
2
3
4
5
{
    apply ( target, ctx, args ) {
        return Reflect.apply( ...arguments );
    }
} 
construct 仅当代理目标是作为构造器的函数时使用
签名:construct(target, args)
拦截构造函数调用操作:new proxy(...args)
has 签名:has(target, propKey)。拦截 prop in obj、hasOwnProperty操作,返回布尔值
deleteProperty 签名:deleteProperty(target, propKey)。拦截delete obj[prop]操作,返回布尔值
ownKeys 签名:ownKeys(target)
拦截:Object.getOwnPropertyNames(proxy)、Object.getOwnPropertySymbols(proxy)、Object.keys(proxy)
返回对象自身所有属性的数组,注意Object.keys仅返回可枚举属性
getOwnPropertyDescriptor 签名:getOwnPropertyDescriptor(target, propKey)
拦截:Object.getOwnPropertyDescriptor(proxy, propKey),返回属性描述符
defineProperty 签名:defineProperty(target, propKey, propDesc)
拦截:Object.defineProperty(proxy, propKey, propDesc)、Object.defineProperties(proxy, propDescs),返回布尔值
preventExtensions 签名:preventExtensions(target)。拦截Object.preventExtensions(proxy),返回布尔值
getPrototypeOf 签名:getPrototypeOf(target)。拦截Object.getPrototypeOf(proxy),返回原型对象
isExtensible 签名:isExtensible(target)。返回布尔值
setPrototypeOf 签名:setPrototypeOf(proxy, proto)。拦截Object.setPrototypeOf(proxy, proto),返回布尔值
在浏览器中使用

考虑到浏览器的兼容性问题,不能直接在其中运行ES6代码。即使你仅仅需要支持最新版本的现代浏览器,模ES6模块之类的特性也不被支持。

因此,必须要进行转码(transpile),将ES6语法转换为通用的ES5语法,并通过垫片(Polyfill)库来适配ES5中缺失的ES6 API。

Babel

这是一个广泛使用的JavaScript编译器,请参考:使用Babel进行JS转码

Traceur

Google提供的JavaScript转码器。Traceur也允许直接在网页中使用ES6代码:

XHTML
1
2
3
4
5
6
7
8
9
10
11
<!-- Traceur库 -->
<script src="https://google.github.io/traceur-compiler/bin/traceur.js"></script>
<!-- 这两个脚本用于支持浏览器环境 -->
<script src="https://google.github.io/traceur-compiler/bin/BrowserSystem.js"></script>
<script src="https://google.github.io/traceur-compiler/src/bootstrap.js"></script>
<!-- 注意type设置为module-->
<script type="module">
    // 导入用户模块
    import './Greeter.js';
    // 可以包含任意ES6代码
</script>

 Traceur可以对支持的ES6特性进行细粒度的控制:

XHTML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<script>
    // 创建一个全局变量,引用Traceur加载器
    window.System = new traceur.runtime.BrowserTraceurLoader();
    // 进行细粒度控制
    var metadata = {
        traceurOptions: {
            experimental: true,
            properTailCalls: true,
            symbols: true,
            arrayComprehension: true,
            asyncFunctions: true,
            asyncGenerators: exponentiation,
            forOn: true,
            generatorComprehension: true
        }
    };
    // 加载自己的模块
    System.import( './module.js', { metadata: metadata } ).catch( function ( ex ) {
        // 加载失败时的处理
    } );
</script> 
常用标准库API
Object
属性/方法 说明
is()

该方法补充了既有的相等比较运算符:

  1.  == :比较相等前,进行类型转换
  2. ===:不进行类型转换,只有同类型的变量才可能相等,但是NaN不等于NaN而+0却等于-0
  3. is(),类似于===,但是NaN等于NaN,+0不等于-0

当然,对于 {} == {} 三种运算符都返回false,因为复杂对象比较的是地址

assign()

类似于Ext.apply,可以将1-N个源对象的属性覆盖到目标对象,声明在后面的源对象同名属性,优先级高

该方法进行的是浅拷贝

__proto__

用于读取、设置当前对象的原型对象,作用类似于setPrototypeOf()/getPrototypeOf()静态方法

属性遍历

ES6支持多种遍历对象属性的方法:

for...in   遍历对象自身定义和继承得到的可枚举属性,不包含Symbol属性
Object.keys(obj)  返回对象自身定义的可枚举属性的名称的数组,不包含Symbol属性
Object.values(obj) 返回对象自身定义的可枚举属性的值的数组,不包含Symbol属性
Object.entries(obj) 类似上面,但是数组的元素是[key, value]
Object.getOwnPropertyNames(obj)  返回对象自身定义的全部属性的数组,不包含Symbol属性,包含不可枚举属性
Reflect.ownKeys(obj)  返回自身所有属性的数组,包括不可能枚举属性,包括Symbol属性
Symbol
方法 说明
for()

根据键查找已经通过Symbol.for登记的Symbol,如果不存在,则定义一个并登记、返回:

JavaScript
1
2
let name = Symbol.for( 'name' );
Object.is( Symbol.for( 'name' ), name );  // true

注意,Symbol()调用生成的Symbol不参与查找,其值总是独特的:

JavaScript
1
2
let name = Symbol( 'name' );
Object.is( Symbol.for( 'name' ), name ); // false 
keyFor() 返回一个已经登记的Symbol的键
String
方法 说明
codePointAt()

返回字符串目标位置上(以2字节为单位划分位置)的字符对应的代码点

在JavaScript内部,字符使用UTF-16表示(二字节),对于需要4字节粗存储的Unicode字符,会被认为是两个字符。charAt、charCodeAt不能正确的识别4字节的Unicode字符

codePointAt方法解决此问题,它可以正确处理4字节字符,并返回正确的10进制代码点的值:

JavaScript
1
s.codePointAt(0).toString(16)  // 转为16进制

使用for ... of循环可以获得字符串中所有字符的代码点: 

JavaScript
1
2
3
for (let c of str) {
  c.codePointAt(0);
}
fromCodePoint() 该静态函数用于把代码点转换为字符
includes()
startsWith()
endsWith()

这些子串搜索函数补充了indexOf()

这三个函数都支持第二个参数,用于指定搜索的起始位置

repeat()

返回字符串重复N遍的副本

padStart()
padEnd()

使用指定的字符串左侧/右侧补全字符串,确保它到达一定的长度

raw()

一个标签模板,处理字符串,将其中的反斜杠替换为双反斜杠:

JavaScript
1
2
3
4
console.log( '\t' );              // 空白
console.log( String.raw`\t` );    // \t
console.log( '\\t' );             // \t
console.log( String.raw`\\t` );   // \\t
Number
方法 说明
isFinite()

判断一个数字是否为有限的:

JavaScript
1
2
3
4
5
6
7
8
9
Number.isFinite(0.8); // true
Number.isFinite(15); // true
Number.isFinite(Infinity); // false
Number.isFinite(-Infinity); // false
Number.isFinite('15'); // false
// 非数字一律返回false
Number.isFinite(NaN); // false
Number.isFinite(true); // false
Number.isFinite('foo'); // false
isNaN() 判断参数能否转换为数字
parseFloat() 解析为浮点数
parseFloat()  解析为整数 
isInteger()

判断是否整数,由于JavaScript以相同的方式存储整数和浮点数,因此6.0和6被作为同一个值看待:

JavaScript
1
2
Number.isInteger(6.0) // true
Number.isInteger(6.1) // false
 isSafeInteger()  判断是否安全整数,即在-2^53到2^53之间,超过此范围的整数,其值无法精确表示
Math
方法 说明
trunc() 取整数,不进行舍入
sign() 判断值的正负
cbrt() 求立方根
clz32() JavaScript中整数使用4字节表示,该函数判断整数的二进制形式,具有多少个前导的0
imul() 返回两个整数以32位带符号整数的方式的乘积
fround() 返回一个树的单精度浮点数形式
hypot() 返回所有参数平方和的平方根
expm1(x) 计算e^x - 1
log1p(x) 计算 1+ x的自然对数,即Math.log(1 + x)
log10(x) 计算以10为底的对数
log2(x) 计算以2为底的对数
sinh(x)
cosh(x)
tanh(x)
asinh(x)
acosh(x)
atanh(x)
双曲正弦
双曲余弦
双曲正切
反双曲正弦
反双曲余弦
反双曲正切
Array
方法 说明
from()

用于将两类对转换为真正的数组:

  1. 类似数组的对象:
    JavaScript
    1
    2
    3
    // 这类对象具有基于数字的key以及length属性
    Array.from( { '0': 0, '3': 3, length: 4 } );
    // [ 0, undefined, undefined, 3 ] 
  2. 可迭代(Iterable)对象,包括ES6新增的Set和Map
of() 将一组对象转换为数组: Array.of( 'Hello', 'World' ); // [ 'Hello', 'World' ]
copyWithin()

从数组内部替换元素:

JavaScript
1
2
3
4
5
6
/**
* 4 开始替换的索引
* 1 从什么索引开始拷贝元素
* 2 拷贝几个元素
*/
[ 0, 1, 2, 3, 4, 5, 6 ].copyWithin( 4, 1,2 ) // [ 0, 1, 2, 3, 1, 5, 6 ]
find() 返回第一个匹配回调的成员: [ -1, 0, 1, 2, 3 ].find( n => n > 0 ) 
findIndex() 返回第一个匹配回调的成员的索引,该函数与find()都接收第二个参数,绑定回调的this
fill() 使用指定的值填充数组
entries()

返回数组键值对的迭代器:

JavaScript
1
2
3
for (let [index, elem] of ['a', 'b'].entries()) {
  console.log(index, elem);
}
keys() 返回索引数字的迭代器
values() 返回数组元素的迭代器
includes() 判断数组是否包含指定的元素
Set
属性/方法 说明
size 返回成员总数
add() 添加一个元素,返回集合本身
delete() 删除一个元素,返回是否成功
has() 判断是否包含某个成员
clear() 清除所有成员
keys()  values() 返回遍历值的迭代器,遍历顺序与插入顺序一致
entries() 返回条目的迭代器,其key/value一直
forEach() 对每个成员执行一个回调
filter() 返回通过回调的元素构成的子集
Map
属性/方法 说明
size 返回成员总数
set() 设置键值对,返回Map本身
get() 根据键获得值,如果找不到key则返回undefined
delete() 删除一个键,返回是否成功
has() 判断是否包含某个键
clear() 清除所有成员
keys()  返回遍历键的迭代器,遍历顺序与插入顺序一致
 values() 返回遍历值的迭代器,遍历顺序与插入顺序一致
entries() 返回条目的迭代器,条目是[key,value]数组
forEach() 对每个成员执行一个回调
← Sencha Cmd学习笔记
使用Babel进行JS转码 →

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code class="" title="" data-url=""> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre class="" title="" data-url=""> <span class="" title="" data-url="">

Related Posts

  • jQuery知识集锦
  • ExtJS 4的组件机制
  • 基于Broadway的HTML5视频监控
  • Sencha Cmd学习笔记
  • ExtJS 4常用组件之表格

Recent Posts

  • Investigating and Solving the Issue of Failed Certificate Request with ZeroSSL and Cert-Manager
  • A Comprehensive Study of Kotlin for Java Developers
  • 背诵营笔记
  • 利用LangChain和语言模型交互
  • 享学营笔记
ABOUT ME

汪震 | Alex Wong

江苏淮安人,现居北京。目前供职于腾讯云,专注容器方向。

GitHub:gmemcc

Git:git.gmem.cc

Email:gmemjunk@gmem.cc@me.com

ABOUT GMEM

绿色记忆是我的个人网站,域名gmem.cc中G是Green的简写,MEM是Memory的简写,CC则是我的小天使彩彩名字的简写。

我在这里记录自己的工作与生活,同时和大家分享一些编程方面的知识。

GMEM HISTORY
v2.00:微风
v1.03:单车旅行
v1.02:夏日版
v1.01:未完成
v0.10:彩虹天堂
v0.01:阳光海岸
MIRROR INFO
Meta
  • Log in
  • Entries RSS
  • Comments RSS
  • WordPress.org
Recent Posts
  • Investigating and Solving the Issue of Failed Certificate Request with ZeroSSL and Cert-Manager
    In this blog post, I will walk ...
  • A Comprehensive Study of Kotlin for Java Developers
    Introduction Purpose of the Study Understanding the Mo ...
  • 背诵营笔记
    Day 1 Find Your Greatness 原文 Greatness. It’s just ...
  • 利用LangChain和语言模型交互
    LangChain是什么 从名字上可以看出来,LangChain可以用来构建自然语言处理能力的链条。它是一个库 ...
  • 享学营笔记
    Unit 1 At home Lesson 1 In the ...
  • K8S集群跨云迁移
    要将K8S集群从一个云服务商迁移到另外一个,需要解决以下问题: 各种K8S资源的迁移 工作负载所挂载的数 ...
  • Terraform快速参考
    简介 Terraform用于实现基础设施即代码(infrastructure as code)—— 通过代码( ...
  • 草缸2021
    经过四个多月的努力,我的小小荷兰景到达极致了状态。

  • 编写Kubernetes风格的APIServer
    背景 前段时间接到一个需求做一个工具,工具将在K8S中运行。需求很适合用控制器模式实现,很自然的就基于kube ...
  • 记录一次KeyDB缓慢的定位过程
    环境说明 运行环境 这个问题出现在一套搭建在虚拟机上的Kubernetes 1.18集群上。集群有三个节点: ...
  • eBPF学习笔记
    简介 BPF,即Berkeley Packet Filter,是一个古老的网络封包过滤机制。它允许从用户空间注 ...
  • IPVS模式下ClusterIP泄露宿主机端口的问题
    问题 在一个启用了IPVS模式kube-proxy的K8S集群中,运行着一个Docker Registry服务 ...
  • 念爷爷
      今天是爷爷的头七,十二月七日、阴历十月廿三中午,老人家与世长辞。   九月初,回家看望刚动完手术的爸爸,发

  • 6 杨梅坑

  • liuhuashan
    深圳人才公园的网红景点 —— 流花山

  • 1 2020年10月拈花湾

  • 内核缺陷触发的NodePort服务63秒延迟问题
    现象 我们有一个新创建的TKE 1.3.0集群,使用基于Galaxy + Flannel(VXLAN模式)的容 ...
  • Galaxy学习笔记
    简介 Galaxy是TKEStack的一个网络组件,支持为TKE集群提供Overlay/Underlay容器网 ...
TOPLINKS
  • Zitahli's blue 91 people like this
  • 梦中的婚礼 64 people like this
  • 汪静好 61 people like this
  • 那年我一岁 36 people like this
  • 为了爱 28 people like this
  • 小绿彩 26 people like this
  • 彩虹姐姐的笑脸 24 people like this
  • 杨梅坑 6 people like this
  • 亚龙湾之旅 1 people like this
  • 汪昌博 people like this
  • 2013年11月香山 10 people like this
  • 2013年7月秦皇岛 6 people like this
  • 2013年6月蓟县盘山 5 people like this
  • 2013年2月梅花山 2 people like this
  • 2013年淮阴自贡迎春灯会 3 people like this
  • 2012年镇江金山游 1 people like this
  • 2012年徽杭古道 9 people like this
  • 2011年清明节后扬州行 1 people like this
  • 2008年十一云龙公园 5 people like this
  • 2008年之秋忆 7 people like this
  • 老照片 13 people like this
  • 火一样的六月 16 people like this
  • 发黄的相片 3 people like this
  • Cesium学习笔记 90 people like this
  • IntelliJ IDEA知识集锦 59 people like this
  • Bazel学习笔记 38 people like this
  • 基于Kurento搭建WebRTC服务器 38 people like this
  • PhoneGap学习笔记 32 people like this
  • NaCl学习笔记 32 people like this
  • 使用Oracle Java Mission Control监控JVM运行状态 29 people like this
  • Ceph学习笔记 27 people like this
  • 基于Calico的CNI 27 people like this
Tag Cloud
ActiveMQ AspectJ CDT Ceph Chrome CNI Command Cordova Coroutine CXF Cygwin DNS Docker eBPF Eclipse ExtJS F7 FAQ Groovy Hibernate HTTP IntelliJ IO编程 IPVS JacksonJSON JMS JSON JVM K8S kernel LB libvirt Linux知识 Linux编程 LOG Maven MinGW Mock Monitoring Multimedia MVC MySQL netfs Netty Nginx NIO Node.js NoSQL Oracle PDT PHP Redis RPC Scheduler ServiceMesh SNMP Spring SSL svn Tomcat TSDB Ubuntu WebGL WebRTC WebService WebSocket wxWidgets XDebug XML XPath XRM ZooKeeper 亚龙湾 单元测试 学习笔记 实时处理 并发编程 彩姐 性能剖析 性能调优 文本处理 新特性 架构模式 系统编程 网络编程 视频监控 设计模式 远程调试 配置文件 齐塔莉
Recent Comments
  • qg on Istio中的透明代理问题
  • heao on 基于本地gRPC的Go插件系统
  • 黄豆豆 on Ginkgo学习笔记
  • cloud on OpenStack学习笔记
  • 5dragoncon on Cilium学习笔记
  • Archeb on 重温iptables
  • C/C++编程:WebSocketpp(Linux + Clion + boostAsio) – 源码巴士 on 基于C/C++的WebSocket库
  • jerbin on eBPF学习笔记
  • point on Istio中的透明代理问题
  • G on Istio中的透明代理问题
  • 绿色记忆:Go语言单元测试和仿冒 on Ginkgo学习笔记
  • point on Istio中的透明代理问题
  • 【Maven】maven插件开发实战 – IT汇 on Maven插件开发
  • chenlx on eBPF学习笔记
  • Alex on eBPF学习笔记
  • CFC4N on eBPF学习笔记
  • 李运田 on 念爷爷
  • yongman on 记录一次KeyDB缓慢的定位过程
  • Alex on Istio中的透明代理问题
  • will on Istio中的透明代理问题
  • will on Istio中的透明代理问题
  • haolipeng on 基于本地gRPC的Go插件系统
  • 吴杰 on 基于C/C++的WebSocket库
©2005-2025 Gmem.cc | Powered by WordPress | 京ICP备18007345号-2