[译]ES6简明参考手册

本文翻译自github上的一篇文章
原文地址:https://github.com/DrkSephy/es6-cheatsheet

es6-参考手册

该手册包括ES2015[ES6]的知识点、技巧、建议和每天工作用的代码段例子。欢迎补充和建议。

var 和 let / const

除了var,我们现在有了两种新的标示符用来存储值——letconst。与var不同的是,letconst 声明不会提前到作用域的开头。(译注:即不发生声明提前)

一个使用var的例子:

var snack = 'Meow Mix';

function getFood(food) {
    if (food) {
        var snack = 'Friskies';
        return snack;
    }
    return snack;
}

getFood(false); // undefined

然而, 我们把var替换成let,观察会发生什么:

let snack = 'Meow Mix';

function getFood(food) {
    if (food) {
        let snack = 'Friskies';
        return snack;
    }
    return snack;
}

getFood(false); // 'Meow Mix'

这种行为变化提示我们,在使用var重构遗留代码的时候需要小心,盲目的把let替换成var会导致以外的行为

注意: letconst具有块作用域。因此在它们定义前调用会引发ReferenceError

console.log(x); // ReferenceError: x is not defined

let x = 'hi';

建议:在遗留代码中的保留var声明表示需要小心的重构。在新建代码库时,使用let声明以后会只会发生改变的变量,用const声明那些以后不会被改变的变量。

译注:const修饰的基本类型不能改变,而其修饰的对象、函数、数组等引用类型,可以改变内部的值,但不能改变其引用。

用块(Blocks)替换立即执行函数(IIFEs)

立即执行函数常被用作闭包,把变量控制在作用域内。在ES6中,我们可以创建一个块作用域而且不仅仅是基于函数的作用域。

(function () {
    var food = 'Meow Mix';
}());

console.log(food); // Reference Error
Using ES6 Blocks:

{
    let food = 'Meow Mix';
};

console.log(food); // Reference Error

箭头函数(Arrow Functions)

通常我们会建立嵌套函数,当我们想保留this上下文作用域的时候(该怎么办?)。如下就是一个例子:

function Person(name) {
    this.name = name;
}

Person.prototype.prefixName = function (arr) {
    return arr.map(function (character) {
        return this.name + character; // Cannot read property 'name' of undefined
    });
};

一个常见的解决方法是利用一个变量存储这个this的上下文。

function Person(name) {
    this.name = name;
}

Person.prototype.prefixName = function (arr) {
    var that = this; // Store the context of this
    return arr.map(function (character) {
        return that.name + character;
    });
};

我们也可以传入这个this的上下文:

function Person(name) {
    this.name = name;
}

Person.prototype.prefixName = function (arr) {
    return arr.map(function (character) {
        return this.name + character;
    }, this);
};

还可以用bind绑定这个上下文:

function Person(name) {
    this.name = name;
}

Person.prototype.prefixName = function (arr) {
    return arr.map(function (character) {
        return this.name + character;
    }.bind(this));
};

使用箭头函数,this的词法作用域不会被影响,我们可以像以下这样重写之前的代码

function Person(name) {
    this.name = name;
}

Person.prototype.prefixName = function (arr) {
    return arr.map(character => this.name + character);
};

建议: 只要当你想保留this的词法作用域时,就使用箭头函数

对于一个简单返回一个值的函数来说,箭头函数显得更加简洁

var squares = arr.map(function (x) { return x * x }); // Function Expression
const arr = [1, 2, 3, 4, 5];
const squares = arr.map(x => x * x); // Arrow Function for terser implementation

建议: 尽可能用箭头函数替换你的函数定义

字符串(Strings)

在ES6中, 标准库也在不断的扩充。在这些变化中就有很多方法可以用于字符串,比如.includes().repeat()

.includes()

var string = 'food';
var substring = 'foo';

console.log(string.indexOf(substring) > -1);

检测返回值是否大于-1表示字符串是否存在,我们可以用.includes()替换,它返回一个boolean值。

const string = 'food';
const substring = 'foo';

console.log(string.includes(substring)); // true

.repeat()

function repeat(string, count) {
    var strings = [];
    while(strings.length < count) {
        strings.push(string);
    }
    return strings.join('');
}

在ES6中,我们一种更简洁的实现方法:

// String.repeat(numberOfRepetitions)
'meow'.repeat(3); // 'meowmeowmeow'

模板字面量(Template Literals)

(译注:原文是Template Literals,而非Template Strings)

利用模板字面量,我们可以直接在字符串使用特殊字符而不用转义它们。

var text = "This string contains \"double quotes\" which are escaped.";
let text = `This string contains "double quotes" which don't need to be escaped anymore.`;

模板字面量还支持插值,可以轻松完成连接字符串和值的任务:

var name = 'Tiger';
var age = 13;

console.log('My cat is named ' + name + ' and is ' + age + ' years old.');
const name = 'Tiger';
const age = 13;

console.log(`My cat is named ${name} and is ${age} years old.`);

在ES5中,我们这样操作多行字符串:

var text = (
    'cat\n' +
    'dog\n' +
    'nickelodeon'
);

或者

var text = [
    'cat',
    'dog',
    'nickelodeon'
].join('\n');

模板字面量可以保留多行字符串,我们无需显式的放置它们:

let text = ( `cat
dog
nickelodeon`
);

模板字面量可以接受表达式, 比如:

let today = new Date();
let text = `The time and date is ${today.toLocaleString()}`;

解构赋值(Destructuring)

解构赋值允许我们从数组或对象中提取出值(甚至深度嵌套的值),并把他们存入变量的简单语法

解构数组(Destructuring Arrays)

var arr = [1, 2, 3, 4];
var a = arr[0];
var b = arr[1];
var c = arr[2];
var d = arr[3];
let [a, b, c, d] = [1, 2, 3, 4];

console.log(a); // 1
console.log(b); // 2

解构对象(Destructuring Objects)

var luke = { occupation: 'jedi', father: 'anakin' };
var occupation = luke.occupation; // 'jedi'
var father = luke.father; // 'anakin'
let luke = { occupation: 'jedi', father: 'anakin' };
let {occupation, father} = luke;

console.log(occupation); // 'jedi'
console.log(father); // 'anakin'

模块(Modules)

ES6之前,我们只用如Browserify的库在客户端创建模块,并且需要用到Node.js。利用ES6,我们现在可以直接使用任何类型的模块(AMD和CommonJS)

CommonJS中的exports

module.exports = 1;
module.exports = { foo: 'bar' };
module.exports = ['foo', 'bar'];
module.exports = function bar () {};

ES6中的export

在ES6中,提供各种不同类型的exports,我们可以运行如下:

export let name = 'David';
export let age  = 25;​​

输出对象列表:

function sumTwo(a, b) {
    return a + b;
}

function sumThree(a, b, c) {
    return a + b + c;
}

export { sumTwo, sumThree };

我们也可以简单地通过export关键字输出函数、对象和值(等等):

export function sumTwo(a, b) {
    return a + b;
}

export function sumThree(a, b, c) {
    return a + b + c;
}
And lastly, we can export default bindings:

function sumTwo(a, b) {
    return a + b;
}

function sumThree(a, b, c) {
    return a + b + c;
}

let api = {
    sumTwo,
    sumThree
};

输出默认api:

/* Which is the same as
 * export { api as default };
 */

建议:在模块结束的地方,始终输出默认的方法。这样可以清晰地看到接口,并且通过弄清楚输出值的名称节省时间。所以在CommonJS模块中通常输出一个对象或值。坚持使用这种模式,会使我们的代码易读,并且可以轻松的在ES6和CommonJS中进行插补。

ES6

ES6 提供提供各种不同的imports,我们输入一整个文件:

import 'underscore';

这里值得注意的是,简单的输入一文件会在文件的最顶层执行代码。和Python类似,我们已经命名了imports:

import { sumTwo, sumThree } from 'math/addition';

我们还可以重命名这些已经有名的imports:

import {
    sumTwo as addTwoNumbers,
    sumThree as sumThreeNumbers
} from 'math/addition';

此外,我们可以输入各种东西(也叫做 namespace import)

import * as util from 'math/addition';

最后,我们可以从模块输入一列值:

import * as additionUtil from 'math/addition';
const { sumTwo, sumThree } = additionUtil;

如下这样从默认绑定进行输入

import api from 'math/addition';
// 例如: import { default as api } from 'math/addition';

虽然最好要保持输出简单,但是如果我们需要,我们有时可以混用默认输入,当我们想如下输出的时候:

// foos.js
export { foo as default, foo1, foo2 };

我们可以如下输入它们:

import foo, { foo1, foo2 } from 'foos';

当使用commonj语法(如React)输入一个模型的输出时,我们可以这样做:

import React from 'react';
const { Component, PropTypes } = React;

这个也可以进一步简化,使用:

import React, { Component, PropTypes } from 'react';

注意:输出的值是绑定,不是引用。因此,绑定的值发生变化会影响输出的模型中的值。避免修改这些输出值的公共接口。

参数(Parameters)

在ES5中,我们可以很多方法操作函数参数的默认值、未定义的参数和有定义的参数。在ES6中,我们可以用更简单的语法实现这一切。

默认参数(Default Parameters)

function addTwoNumbers(x, y) {
    x = x || 0;
    y = y || 0;
    return x + y;
}

在ES6中,我们可以简单的把默认值赋给参数:

function addTwoNumbers(x=0, y=0) {
    return x + y;
}
addTwoNumbers(2, 4); // 6
addTwoNumbers(2); // 2
addTwoNumbers(); // 0

剩余参数(Rest Parameters)

在ES5中,我们这样操作一个未定义的参数:

function logArguments() {
    for (var i=0; i < arguments.length; i++) {
        console.log(arguments[i]);
    }
}

使用休止符(...)我们可以传入大量未定义的参数:

function logArguments(...args) {
    for (let arg of args) {
        console.log(arg);
    }
}

已命名的参数(Named Parameters)

ES5中,一种处理已命名参数的方式是使用选项方式,这种方法来自jQuery。

function initializeCanvas(options) {
    var height = options.height || 600;
    var width  = options.width  || 400;
    var lineStroke = options.lineStroke || 'black';
}

通过解构成正式参数的方式,我们可以实现同样的功能:

function initializeCanvas(
{ height=600, width=400, lineStroke='black'}) {
    // Use variables height, width, lineStroke here
}

如果我们想使全部参数值可选,我们可以用一个空对象这样结构:

function initializeCanvas(
    { height=600, width=400, lineStroke='black'} = {}) {
        // ...
    }

展开运算符(Spread Operator)

在ES5中,查找一个array中的最大值需要使用Math.max的apply方法:

Math.max.apply(null, [-1, 100, 9001, -32]); // 9001

在es6中,我们使用展开运算符将array传递给函数作为参数:

Math.max(...[-1, 100, 9001, -32]); // 9001

我们可以用这样简洁的语法链接数组字面量:

let cities = ['San Francisco', 'Los Angeles'];
let places = ['Miami', ...cities, 'Chicago']; // ['Miami', 'San Francisco', 'Los Angeles', 'Chicago']

类(classes)

在ES6之前,我们通过创建构造器函数,并且在其prototype上添加属性的方法创建一个类:

function Person(name, age, gender) {
    this.name   = name;
    this.age    = age;
    this.gender = gender;
}

Person.prototype.incrementAge = function () {
    return this.age += 1;
};

并且用一下方法创建继承类:

function Personal(name, age, gender, occupation, hobby) {
    Person.call(this, name, age, gender);
    this.occupation = occupation;
    this.hobby = hobby;
}

Personal.prototype = Object.create(Person.prototype);
Personal.prototype.constructor = Personal;
Personal.prototype.incrementAge = function () {
    Person.prototype.incrementAge.call(this);
    this.age += 20;
    console.log(this.age);
};

ES6 提供了十分有用的句法在后台实现这一切,我们可以这样直接创建一个类:

class Person {
    constructor(name, age, gender) {
        this.name   = name;
        this.age    = age;
        this.gender = gender;
    }

    incrementAge() {
      this.age += 1;
    }
}

并且用关键字extends实现继承:

class Personal extends Person {
    constructor(name, age, gender, occupation, hobby) {
        super(name, age, gender);
        this.occupation = occupation;
        this.hobby = hobby;
    }

    incrementAge() {
        super.incrementAge();
        this.age += 20;
        console.log(this.age);
    }
}

建议:使用ES6的语法创建类模糊了后台的实现和原型如何工作,这个好特性可以使我们的代码更整洁。

Symbols

Symbol在ES6之前就已经出现了, 但是现在我们有了一个公共的接口可以直接使用。Symbol是唯一且不可改变的值,被用作哈希中的键。

Symbol()

调用Symbol()或者Symbol(description)会创建一个不能在全局查找的独一无二的符号。一种使用symbol()的情况是,利用自己的逻辑修补第三方的对象或命名空间,但不确定会不会在库更新时产生冲突。例如,如果你想添加一个方法refreshCompontentReact.Component,并且确信这个方法他们不会在以后的更新中添加。

const refreshComponent = Symbol();

React.Component.prototype[refreshComponent] = () => {
    // do something
}

###Symbol.for(key)

Symbol.for(key) 依然会创建一个唯一且不能修改的Symbol,但是它可以在全局被查找。两次调用相同的Symbol.for(key) 会创建一样的Symbol实例。注意,他和Symbol(description)不是相同的:

Symbol('foo') === Symbol('foo') // false
Symbol.for('foo') === Symbol('foo') // false
Symbol.for('foo') === Symbol.for('foo') // true

一个常见的symbol方法Symbol.for(key)是可互操作的。(使用这个方法)这个可以通过使用自己的代码在包括已知接口的第三方的对象参数中查找symbol成员实现,例如:

function reader(obj) {
    const specialRead = Symbol.for('specialRead');
    if (obj[specialRead]) {
        const reader = obj[specialRead]();
        // do something with reader
    } else {
        throw new TypeError('object cannot be read');
    }
}

在另一个库中:

const specialRead = Symbol.for('specialRead');

class SomeReadableType {
    [specialRead]() {
        const reader = createSomeReaderFrom(this);
        return reader;
    }
}

ES6中,一个值得注意的是关于Symbol的互操作性的例子是Symbol.iterator,它存在于Arrays、Strings、Generators等等的所有可迭代类型中。当作为一个方法调用的时候,它会返回一个具有迭代器接口的对象。

Maps

Maps是JavaScript中十分有用的结构。在ES6之前, 我们通过对象创建哈希maps:

var map = new Object();
map[key1] = 'value1';
map[key2] = 'value2';

但是,这样不能保护我们对已有属性以外的重写:

> getOwnProperty({ hasOwnProperty: 'Hah, overwritten'}, 'Pwned');
> TypeError: Property 'hasOwnProperty' is not a function

Map允许我们使用set、get和search(等等)访问属性值。

let map = new Map();
> map.set('name', 'david');
> map.get('name'); // david
> map.has('name'); // true

最意想不到的是Map不再限制我们只使用字符串作为键,我们现在可以使用任何类型作为键而不会发生类型转换。

let map = new Map([
    ['name', 'david'],
    [true, 'false'],
    [1, 'one'],
    [{}, 'object'],
    [function () {}, 'function']
]);

for (let key of map.keys()) {
    console.log(typeof key);
    // > string, boolean, number, object, function
}

注意:当使用如map.get()等方法测试相等的时候,诸如function和object这样的非原始值不能正常工作。因此,依然应该使用原始值(作为键),比如String、Boolean和Number。
我们也可以使用.entries()方法作为迭代器遍历Map

for (let [key, value] of map.entries()) {
    console.log(key, value);
}

WeakMaps

ES6之前,为了保存私有数据,我们采取了很多方式。其中一个方法就是命名转换:

class Person {
    constructor(age) {
        this._age = age;
    }

    _incrementAge() {
        this._age += 1;
    }
}

但是命名转换会引起代码库混乱,并且不能保证总是被支持。为此,我们使用WeakMaps存储数据:

let _age = new WeakMap();
class Person {
    constructor(age) {
        _age.set(this, age);
    }

    incrementAge() {
        let age = _age.get(this) + 1;
        _age.set(this, age);
        if (age > 50) {
            console.log('Midlife crisis');
        }
    }
}

使用WeakMap存储数据时的一个很有趣的事情是,这个key不会暴露出属性名,需要使用Reflect.ownKeys()实现:

> const person = new Person(50);
> person.incrementAge(); // 'Midlife crisis'
> Reflect.ownKeys(person); // []

使用WeakMap更实际的例子是在不污染DOM自身的情况下存储与DOM元素相关的数据:

let map = new WeakMap();
let el  = document.getElementById('someElement');

// 给元素存一个弱引用
map.set(el, 'reference');

// 获得元素的值
let value = map.get(el); // 'reference'

// 移除引用
el.parentNode.removeChild(el);
el = null;

// 元素被回收后,map是空的

如上所示,当一个对象被GC回收后,WeakMap会自动移除以其为标识符的键值对。

注意:为了进一步说明这个例子的实用性。当一个与DOM对应的对象的具有引用时,考虑jQuery如何存储它。使用WeakMaps,jQuery可以在DOM元素被删除时自动释放与之关联的内存。总而言之,对任何库而言,WeakMaps对操作DOM元素是非常实用的。

Promises

Promise允许我们把水平的代码(回调函数的地狱):

func1(function (value1) {
    func2(value1, function (value2) {
        func3(value2, function (value3) {
            func4(value3, function (value4) {
                func5(value4, function (value5) {
                    // Do something with value 5
                });
            });
        });
    });
});

转换为竖直的代码:

func1(value1)
    .then(func2)
    .then(func3)
    .then(func4)
    .then(func5, value5 => {
        // Do something with value 5
    });

在ES6之前,我们使用bluebird或是Q,现在我们有了Promises:

new Promise((resolve, reject) =>
    reject(new Error('Failed to fulfill Promise')))
        .catch(reason => console.log(reason));

这里我们有2个handlers,resolve(Promise执行成功时调用的函数)和reject(Promise失败时调用的函数)。

使用Promise的好处:使用嵌套的回调函数处理错误会很混乱。使用Promise,我们可以很清晰的使错误冒泡,并且就近处理它们。更好的是,在它处理成功(或失败)之后Promise的值是不可修改的。

以下是个使用Promise的实例:

var request = require('request');

return new Promise((resolve, reject) => {
  request.get(url, (error, response, body) => {
    if (body) {
        resolve(JSON.parse(body));
      } else {
        resolve({});
      }
  });
});

我们可以使用Promise.all()并行的处理一个异步操作数组:

let urls = [
  '/api/commits',
  '/api/issues/opened',
  '/api/issues/assigned',
  '/api/issues/completed',
  '/api/issues/comments',
  '/api/pullrequests'
];

let promises = urls.map((url) => {
  return new Promise((resolve, reject) => {
    $.ajax({ url: url })
      .done((data) => {
        resolve(data);
      });
  });
});

Promise.all(promises)
  .then((results) => {
    // Do something with results of all our promises
 });

Generators

和Promise使我们避免回调函数的地狱相似,Generators可以扁平化我们的代码——给我们一种同步执行异步代码的感觉,Generators是个很重要的函数,它使我们可以暂停操作的执行,随后返回表达式的值。

下面是使用Generator的一个简单例子:

function* sillyGenerator() {
    yield 1;
    yield 2;
    yield 3;
    yield 4;
}

var generator = sillyGenerator();
> console.log(generator.next()); // { value: 1, done: false }
> console.log(generator.next()); // { value: 2, done: false }
> console.log(generator.next()); // { value: 3, done: false }
> console.log(generator.next()); // { value: 4, done: false }

这里,next()使我们generator继续推进,并且得到新的表达式的值(译注:每次推进到下一个yield值)。当然,上面的例子很牵强,我们可以利用Generator以同步的方式写异步代码:

// 利用Generator屏蔽异步过程

function request(url) {
    getJSON(url, function(response) {
        generator.next(response);
    });
}

下面我们写一个generator函数用来返回我们自己的数据:

function* getData() {
    var entry1 = yield request('http://some_api/item1');
    var data1  = JSON.parse(entry1);
    var entry2 = yield request('http://some_api/item2');
    var data2  = JSON.parse(entry2);
}

利用yield的功能,我们确保entry1可以获得返回的数据,用于解析并存储到data1中。
当我们利用generator以同步的方式写异步的代码时,其中的错误不会简单清晰的传递。因此,我们利用Promise加强generator:

function request(url) {
    return new Promise((resolve, reject) => {
        getJSON(url, resolve);
    });
}

我们写了一个函数,利用next用来按序地一步步遍历generator。该函数利用上述的请求方式并yeild一个Promise(对象)。

function iterateGenerator(gen) {
    var generator = gen();
    (function iterate(val) {
        var ret = generator.next();
        if(!ret.done) {
            ret.value.then(iterate);
        }
    })();
}

通过Promise加强generator后,我们可以利用Promise.catch和Promise.reject这样清晰的方式传播错误。只用这个加强版的Generator和以前一样简单:

iterateGenerator(function* getData() {
    var entry1 = yield request('http://some_api/item1');
    var data1  = JSON.parse(entry1);
    var entry2 = yield request('http://some_api/item2');
    var data2  = JSON.parse(entry2);
});

我们可以重用写好的代码,像过去使用Generator一样,这一点很强大。当我们利用generator以同步的方式写异步的代码的同时,利用一个不错的方式保留了错误传播的能力,我们实际上可以利用一个更为简单的方式达到同样的效果:异步等待(Async-Await)。

Async Await

这是一个在ES2016(ES7)中即将有的特性,async await允许我们更简单地使用Generator和Promise执行和已完成工作相同的任务:

var request = require('request');

function getJSON(url) {
  return new Promise(function(resolve, reject) {
    request(url, function(error, response, body) {
      resolve(body);
    });
  });
}

async function main() {
  var data = await getJSON();
  console.log(data); // NOT undefined!
}

main();

在后台,它的实现类似Generators。我(作者)强烈建议使用这个替代Generators + Promises。还会有很多的资源出现并使用ES7,同时,Babel也会用在这里。

Getter 和 Setter 函数

ES6 已经支持了GetterSetter函数,例如:

class Employee {

    constructor(name) {
        this._name = name;
    }

    get name() {
      if(this._name) {
        return 'Mr. ' + this._name.toUpperCase();  
      } else {
        return undefined;
      }  
    }

    set name(newName) {
      if (newName == this._name) {
        console.log('I already have this name.');
      } else if (newName) {
        this._name = newName;
      } else {
        return false;
      }
    }
}

var emp = new Employee("James Bond");

// 内部使用了get方法
if (emp.name) {
  console.log(emp.name);  // Mr. JAMES BOND
}

// 内部使用了setter(译注:原文中这一句和上一句注释的表述就这么不一样)
emp.name = "Bond 007";
console.log(emp.name);  // Mr. BOND 007  

最新的浏览器都支持对象中的getter/setter函数,我们可以使用他们计算属性、添加事件以及在setting和getting前的预处理

var person = {
  firstName: 'James',
  lastName: 'Bond',
  get fullName() {
      console.log('Getting FullName');
      return this.firstName + ' ' + this.lastName;
  },
  set fullName (name) {
      console.log('Setting FullName');
      var words = name.toString().split(' ');
      this.firstName = words[0] || '';
      this.lastName = words[1] || '';
  }
}

person.fullName; // James Bond
person.fullName = 'Bond 007';
person.fullName; // Bond 007
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,968评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,601评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,220评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,416评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,425评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,144评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,432评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,088评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,586评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,028评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,137评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,783评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,343评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,333评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,559评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,595评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,901评论 2 345

推荐阅读更多精彩内容

  • 异步编程对JavaScript语言太重要。Javascript语言的执行环境是“单线程”的,如果没有异步编程,根本...
    呼呼哥阅读 7,298评论 5 22
  • 本文为阮一峰大神的《ECMAScript 6 入门》的个人版提纯! babel babel负责将JS高级语法转义,...
    Devildi已被占用阅读 1,970评论 0 4
  • [TOC] 参考阮一峰的ECMAScript 6 入门参考深入浅出ES6 let和const let和const都...
    郭子web阅读 1,771评论 0 1
  • 官方中文版原文链接 感谢社区中各位的大力支持,译者再次奉上一点点福利:阿里云产品券,享受所有官网优惠,并抽取幸运大...
    HetfieldJoe阅读 6,373评论 9 19
  • 端午放假,我自然的选择了不做早饭,但天生小气的我,不舍得花钱,希望身边的这个男人我老公能做早饭,昨天的粥味道还是很...
    孤诣的花田半亩阅读 262评论 0 0