面試官: 說(shuō)說(shuō)你對(duì)async的理解

大家好,我是小雨小雨泄私,致力于分享有趣的房揭、實(shí)用的技術(shù)文章。

內(nèi)容分為翻譯和原創(chuàng)晌端,如果有問(wèn)題捅暴,歡迎隨時(shí)評(píng)論或私信,希望和大家一起進(jìn)步咧纠。

分享不易蓬痒,希望能夠得到大家的支持和關(guān)注。

TL;DR

async是generator和promise的語(yǔ)法糖漆羔,利用迭代器的狀態(tài)機(jī)和promise來(lái)進(jìn)行自更新!

如果懶得往下看梧奢,可以看下這個(gè)極其簡(jiǎn)易版本的實(shí)現(xiàn)方式:

// 復(fù)制粘貼即可直接運(yùn)行
function stateMac (arr) {
    let val;
    return {
        next(){
            if ((val = arr.shift())) {
                return {
                    value: val,
                    done: false
                }
            } else {
                return {
                    done: true
                }
            }
        }
    }
}


function asyncFn(arr) {
    const iterator = stateMac(arr);
    function doSelf () {
        const cur = iterator.next();
        const value = cur.value;
        if (cur.done) {
            console.log('done');
            return;
        }
        switch (true) {
            case value.then && value.toString() === '[object Promise]':
                value.then((result) => {
                    console.log(result);
                    doSelf();
                })
                break;
            case typeof value === 'function':
                value();
                doSelf();
                break;
            default:
                console.log(value);
                doSelf();
        }
    }
    doSelf();
}

const mockAsync = [
    1,
    new Promise((res) => {
        setTimeout(function () {
            res('promise');
        }, 3000);
    }),
    function () {
        console.log('測(cè)試');
    }
];
console.log('開(kāi)始');
asyncFn(mockAsync);
console.log('結(jié)束');

前言

async & await 和我們的日常開(kāi)發(fā)緊密相連,但是你真的了解其背后的原理嗎演痒?

本文假設(shè)你對(duì)promise亲轨、generator有一定了解。

簡(jiǎn)述promise

promise就是callback的另一種寫(xiě)法鸟顺,避免了毀掉地獄惦蚊,從橫向改為縱向剑辫,大大提升了可讀性和美觀逃贝。

至于promise的實(shí)現(xiàn),按照promise A+規(guī)范一點(diǎn)點(diǎn)寫(xiě)就好了荣挨,完成后可以使用工具進(jìn)行測(cè)試欧芽,確保你的寫(xiě)的東西是符合規(guī)范的莉掂。

具體實(shí)現(xiàn)原理,市面上有各種各樣的寫(xiě)法渐裸,我就不多此一舉了巫湘。

簡(jiǎn)述generator

generator就不像promise那樣装悲,他改變了函數(shù)的執(zhí)行方式∩蟹眨可以理解為協(xié)程诀诊,就是說(shuō)多個(gè)函數(shù)互相配合完成任務(wù)。類(lèi)似于這個(gè)東西:

function generator() {
    return {
        _value: [1, 2, 3, 4],
        next() {
            return {
                value: this._value.shift(),
                done: !this._value.length
            };
        }
    };
}
const it = generator();

console.log(it.next());
console.log(it.next());
console.log(it.next());
console.log(it.next());

這只是一個(gè)demo阅嘶,僅供參考属瓣。

具體請(qǐng)參考MDN.

async & await

照我的理解,其實(shí)就是generator和promise相交的產(chǎn)物讯柔,被解析器識(shí)別抡蛙,然后轉(zhuǎn)換成我們熟知的語(yǔ)法。

這次要做的就是去看編譯之后的結(jié)果是什么樣的魂迄。

既然如此粗截,我們就帶著問(wèn)題去看,不然看起來(lái)也糟心不是~

async包裝的函數(shù)會(huì)返回一個(gè)什么樣的promise捣炬?

// 源代碼:
async function fn() {}

fn();
// 編譯后變成了一大坨:

// generator的polyfill
require("regenerator-runtime/runtime");

function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) {
  try {
    var info = gen[key](arg);
    var value = info.value;
  } catch (error) {
    reject(error);
    return;
  }
  if (info.done) {
    resolve(value);
  } else {
    Promise.resolve(value).then(_next, _throw);
  }
}

function _asyncToGenerator(fn) {
  return function() {
    var self = this,
      args = arguments;
    return new Promise(function(resolve, reject) {
      var gen = fn.apply(self, args);
      function _next(value) {
        asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value);
      }
      function _throw(err) {
        asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err);
      }
      _next(undefined);
    });
  };
}

function fn() {
  return _fn.apply(this, arguments);
}

function _fn() {
  _fn = _asyncToGenerator(
    /*#__PURE__*/ regeneratorRuntime.mark(function _callee() {
      return regeneratorRuntime.wrap(function _callee$(_context) {
        while (1) {
          switch ((_context.prev = _context.next)) {
            case 0:
            case "end":
              return _context.stop();
          }
        }
      }, _callee);
    })
  );
  return _fn.apply(this, arguments);
}

fn();

內(nèi)容也不是很多熊昌,我們一點(diǎn)點(diǎn)來(lái)看:

generator包裝

fn內(nèi)部調(diào)用的是_fn,一個(gè)私有方法湿酸,使用的apply綁定的this婿屹,并傳入了動(dòng)態(tài)參數(shù)。

_fn內(nèi)調(diào)用了_asyncToGenerator方法推溃,由于js調(diào)用棧后進(jìn)先出:

讀起來(lái)是這樣的:fn() => _asyncToGenerator => .mark()

執(zhí)行是反過(guò)來(lái)的:.mark() => _asyncToGenerator => fn()

我們先往里看昂利,映入眼簾的是regeneratorRuntime.mark,該方法是generator的polyfill暴露的方法之一铁坎,我們?nèi)?nèi)部(require('regenerator-runtime/runtime'))簡(jiǎn)單看下這個(gè)mark是用來(lái)干什么的蜂奸。

// 立即執(zhí)行函數(shù),適配commonjs和瀏覽器
(function (exports) {
    // 暴露mark方法
    exports.mark = function (genFun) {
        // 兼容判斷__proto__厢呵,處理老舊環(huán)境
        if (Object.setPrototypeOf) {
            Object.setPrototypeOf(genFun, GeneratorFunctionPrototype);
        } else {
            genFun.__proto__ = GeneratorFunctionPrototype;
            // 設(shè)置Symbol.toStringTag窝撵,適配toString
            if (!(toStringTagSymbol in genFun)) {
                genFun[toStringTagSymbol] = 'GeneratorFunction';
            }
        }
        // 設(shè)置原型
        genFun.prototype = Object.create(Gp);
        return genFun;
    };
})(typeof module === 'Object' ? module.exports : {});

mark做了兩個(gè)操作,一個(gè)是設(shè)置genFunproto襟铭,一個(gè)是設(shè)置prototype,可能有人會(huì)好奇:

proto不是對(duì)象上的嗎短曾?prototype不是函數(shù)上的嗎寒砖?為啥兩個(gè)同時(shí)應(yīng)用到一個(gè)上面了

這樣操作是沒(méi)問(wèn)題的,genFun不僅是函數(shù)啊嫉拐,函數(shù)還是對(duì)象哩都,js中萬(wàn)物皆對(duì)象哦。你想想是不是可以通過(guò)Function構(gòu)造函數(shù)new出一個(gè)函數(shù)婉徘?

然后開(kāi)始設(shè)置proto和prototype漠嵌,在次之前咐汞,我們來(lái)簡(jiǎn)單捋一下原型。

原型

下面是個(gè)人理解的一個(gè)說(shuō)法儒鹿,未查閱v8引擎化撕,但是這樣是說(shuō)得通的。如果有問(wèn)題约炎,歡迎指出植阴,一起溝通,我也會(huì)及時(shí)修改圾浅,以免誤導(dǎo)他人B邮帧!狸捕!喷鸽。

首先要知道這三個(gè)的概念:搞清對(duì)象的原型對(duì)象(proto)、構(gòu)造函數(shù)的原型(prototype)灸拍、構(gòu)造方法(constructor)做祝。

方便記憶,只需要記住下面幾條即可:

  • prototype是構(gòu)造函數(shù)(注意:構(gòu)造函數(shù)也是對(duì)象嗷)上特有的屬性株搔,代表構(gòu)造函數(shù)的原型剖淀。舉個(gè)例子:

有一位小明同學(xué)(指代構(gòu)造函數(shù)),他有自己的朋友圈子(指代prototype)纤房,通過(guò)小明可以找到小紅(構(gòu)造函數(shù).prototype.小紅)纵隔,在通過(guò)小紅的朋友圈子(prototype)還能找到小藍(lán),直到有一個(gè)人(指代null)炮姨,孑然一身捌刮、無(wú)欲無(wú)求,莫得朋友舒岸。

上面這個(gè)關(guān)系鏈就可以理解為原型鏈绅作。

  • proto是每一個(gè)對(duì)象上特有的屬性,指向當(dāng)前對(duì)象構(gòu)造函數(shù)的prototype蛾派。再舉個(gè)例子:

小明家里催的急俄认,不就就生了個(gè)大胖小子(通過(guò)構(gòu)造函數(shù){小明}創(chuàng)造出對(duì)象{大胖小子}),可以說(shuō)這個(gè)大胖小子一出生就被眾星捧月洪乍,小明的朋友們紛紛表示眯杏,以后孩子有啥事需要幫忙找我就成。這就指代對(duì)象上的__proto__壳澳,__proto__可以引用構(gòu)造函數(shù)的任何關(guān)系岂贩。

所以說(shuō),代碼源于生活~

  • constructor是啥呢巷波,就是一個(gè)prototype上的屬性萎津,表示這個(gè)朋友圈子是誰(shuí)的卸伞,對(duì)于小明來(lái)說(shuō): 小明.prototype.constructor === 小明。所以锉屈,當(dāng)我們進(jìn)行繼成操作的時(shí)候荤傲,有必要修正一下constructor,不然朋友圈子就亂了~

  • js中函數(shù)和對(duì)象有點(diǎn)套娃的意思部念,萬(wàn)物皆對(duì)象弃酌,對(duì)象又是從構(gòu)造函數(shù)構(gòu)造而來(lái)。對(duì)于小明來(lái)說(shuō)儡炼,就是我生我生我~~

來(lái)看兩個(gè)判斷:

proto 指向構(gòu)造當(dāng)前對(duì)象的構(gòu)造函數(shù)的prototype妓湘,由于萬(wàn)物皆對(duì)象,對(duì)象又是通過(guò)構(gòu)造函數(shù)構(gòu)造而來(lái)乌询。故Object通過(guò)Function構(gòu)造而來(lái)榜贴,所以指向了Function.prototype

console.log(Object.__proto__ === Function.prototype); // => true

proto 指向構(gòu)造當(dāng)前對(duì)象的構(gòu)造函數(shù)的prototype,由于萬(wàn)物皆對(duì)象妹田,對(duì)象又是通過(guò)構(gòu)造函數(shù)構(gòu)造而來(lái)唬党。故Function通過(guò)Function構(gòu)造而來(lái),所以指向了Function.prototype

console.log(Function.__proto__ === Function.prototype); // => true

有興趣的朋友可以再看看這篇文章


然后鬼佣,我們?cè)賮?lái)看看這張圖驶拱,跟著箭頭走一遍,是不是就很清晰了晶衷?

image
繼續(xù)generator包裝

mark方法會(huì)指定genFun的proto和prototype蓝纲,完完全全替換了genFun的朋友圈以及創(chuàng)造genFun的構(gòu)造函數(shù)的朋友圈,現(xiàn)在genFun就是Generator的克隆品了晌纫。

用來(lái)設(shè)置proto 和 prototype的值税迷,GeneratorFunctionPrototype,GP锹漱,我們也簡(jiǎn)單過(guò)一下:


// 創(chuàng)建polyfill對(duì)象
var IteratorPrototype = {};
IteratorPrototype[iteratorSymbol] = function () {
    return this;
};

// 原型相關(guān)操作
// 獲取對(duì)象的原型: __proto__
var getProto = Object.getPrototypeOf;

// 原生iterator原型
var NativeIteratorPrototype = getProto && getProto(getProto(values([])));
// IteratorPrototype設(shè)置為原生
if (
    NativeIteratorPrototype &&
    NativeIteratorPrototype !== Op &&
    hasOwn.call(NativeIteratorPrototype, iteratorSymbol)
) {
    // This environment has a native %IteratorPrototype%; use it instead
    // of the polyfill.
    IteratorPrototype = NativeIteratorPrototype;
}

// 創(chuàng)造原型
// Gp 為 迭代器原型
// IteratorPrototype作為原型對(duì)象
var Gp = (GeneratorFunctionPrototype.prototype = Generator.prototype = Object.create(
    IteratorPrototype
));

// 更新構(gòu)造函數(shù)和原型
GeneratorFunction.prototype = Gp.constructor = GeneratorFunctionPrototype;
GeneratorFunctionPrototype.constructor = GeneratorFunction;

// toString箭养,調(diào)用Object.toString.call的時(shí)候會(huì)返回GeneratorFunction
GeneratorFunctionPrototype[
    toStringTagSymbol
] = GeneratorFunction.displayName = 'GeneratorFunction';

最后再返回經(jīng)過(guò)處理的genFun,然后再回到mark函數(shù)外~

_asyncToGenerator

_asyncToGenerator 接收mark處理過(guò)的結(jié)果:

// fn 為 generator 的克隆品
function _asyncToGenerator(fn) {
    return function () {
        var self = this,
            args = arguments;
        return new Promise(function (resolve, reject) {
            // 調(diào)用_callee哥牍,先看下面毕泌,一會(huì)在回來(lái)哈~
            var gen = fn.apply(self, args);
            function _next(value) {
                asyncGeneratorStep(
                    gen,
                    resolve,
                    reject,
                    _next,
                    _throw,
                    'next',
                    value
                );
            }
            function _throw(err) {
                asyncGeneratorStep(
                    gen,
                    resolve,
                    reject,
                    _next,
                    _throw,
                    'throw',
                    err
                );
            }
            _next(undefined);
        });
    };
}
regeneratorRuntime.wrap

上面的_asyncToGenerator執(zhí)行后,會(huì)執(zhí)行mark返回的函數(shù):

function _callee() {
    return regeneratorRuntime.wrap(function _callee$(
        _context
    ) {
        // 這里就是動(dòng)態(tài)得了嗅辣,也就是根據(jù)用戶(hù)寫(xiě)的async函數(shù)懈词,轉(zhuǎn)換的記過(guò),由于我們是一個(gè)空函數(shù)辩诞,所以直接stop了
        while (1) {
            switch ((_context.prev = _context.next)) {
                case 0:
                case 'end':
                    return _context.stop();
            }
        }
    },
    _callee);
}

_callee會(huì)返回wrap處理后的結(jié)果,我們繼續(xù)看:

// innerFn是真正執(zhí)行的函數(shù)纺涤,outerFn為被mark的函數(shù)
// self, tryLocsList未傳遞译暂,為undefined
function wrap(innerFn, outerFn, self, tryLocsList) {
    // If outerFn provided and outerFn.prototype is a Generator, then outerFn.prototype instanceof Generator.
    // outerFn 的原型已經(jīng)被 mark重新設(shè)置抠忘,所以會(huì)包含generator相關(guān)原型
    var protoGenerator =
        outerFn && outerFn.prototype instanceof Generator
            ? outerFn
            : Generator;

    // 創(chuàng)建自定義原型的對(duì)象
    var generator = Object.create(protoGenerator.prototype);

    // context 實(shí)例是包含的 this.tryEntries 的
    var context = new Context(tryLocsList || []);

    // The ._invoke method unifies the implementations of the .next,
    // .throw, and .return methods.
    generator._invoke = makeInvokeMethod(innerFn, self, context);

    return generator;
}

其中有個(gè)new Context()的操作,用來(lái)重置并記錄迭代器的狀態(tài)外永,后面會(huì)用到崎脉。
之后給返回generator掛載一個(gè)_invoke方法,調(diào)用makeInvokeMethod伯顶,并傳入self(未傳遞該參數(shù)囚灼,為undefined)和context。

function makeInvokeMethod(innerFn, self, context) {
    // state只有在該函數(shù)中備操作
    var state = GenStateSuspendedStart; // GenStateSuspendedStart: 'suspendedStart'

    // 作為外面的返回值
    return function invoke(method, arg) {
        // 這里就是generator相關(guān)的一些操作了祭衩,用到的時(shí)候再說(shuō)
    };
}

利用閉包初始化state灶体,并返回一個(gè)invoke函數(shù),接受兩個(gè)參數(shù)掐暮,方法和值蝎抽。先看到這,繼續(xù)往后看路克。

回到之前的_asyncToGenerator

// 返回帶有_invoke屬性的generator對(duì)象
var gen = fn.apply(self, args);

之后定義了一個(gè)next和throw方法樟结,隨后直接調(diào)用_next開(kāi)始執(zhí)行:

function _next(value) {
    asyncGeneratorStep(
        gen, // 迭代器函數(shù)
        resolve, // promise的resolve
        reject, // promise的project
        _next, // 當(dāng)前函數(shù)
        _throw, // 下面的_throw函數(shù)
        'next', // method名
        value // arg 參數(shù)值
    );
}
function _throw(err) {
    asyncGeneratorStep(
        gen,
        resolve,
        reject,
        _next,
        _throw,
        'throw',
        err
    );
}
_next(undefined);

其中都是用的asyncGeneratorStep,并傳遞了一些參數(shù)精算。

那asyncGeneratorStep又是啥呢:

function asyncGeneratorStep(
    gen,
    resolve,
    reject,
    _next,
    _throw,
    key,
    arg
) {
    try {
        var info = gen[key](arg);
        var value = info.value;
    } catch (error) {
        // 出錯(cuò)
        reject(error);
        return;
    }
    if (info.done) {
        // 如果完成瓢宦,直接resolve
        resolve(value);
    } else {
        // 否則,繼續(xù)下次next調(diào)用灰羽,形成遞歸
        Promise.resolve(value).then(_next, _throw);
    }
}

代碼很少驮履,獲取即將要調(diào)用的方法名(key)并傳入?yún)?shù),所以當(dāng)前info即是:

var info = gen['next'](arg);

那next是哪來(lái)的那谦趣?就是之前mark操作中定義的疲吸,如果原生支持,就是用原生的迭代器提供的next前鹅,否則使用polyfill中定義的next摘悴。

還記得之前的makeInvokeMethod嗎?

它其實(shí)是用來(lái)定義標(biāo)準(zhǔn)化next舰绘、throw和return的:

function defineIteratorMethods(prototype) {
    ['next', 'throw', 'return'].forEach(function (method) {
        prototype[method] = function (arg) {
            return this._invoke(method, arg);
        };
    });
}
// Gp在之前的原型操作有用到
defineIteratorMethods(Gp);

然后當(dāng)我們執(zhí)行的時(shí)候蹂喻,就會(huì)走到_invoke定義的invoke方法中:

function invoke(method, arg) {
    // 狀態(tài)判斷,拋錯(cuò)
    if (state === GenStateExecuting) {
        throw new Error('Generator is already running');
    }

    // 已完成捂寿,返回done狀態(tài)
    if (state === GenStateCompleted) {
        if (method === 'throw') {
            throw arg;
        }

        // Be forgiving, per 25.3.3.3.3 of the spec:
        // https://people.mozilla.org/~jorendorff/es6-draft.html#sec-generatorresume
        return doneResult();
    }

    // 這里就是之前定義的Context實(shí)例口四,下面代碼沒(méi)啥了,自己看吧
    context.method = method;
    context.arg = arg;

    while (true) {
        var delegate = context.delegate;
        if (delegate) {
            var delegateResult = maybeInvokeDelegate(delegate, context);
            if (delegateResult) {
                if (delegateResult === ContinueSentinel) continue;
                return delegateResult;
            }
        }

        if (context.method === 'next') {
            // Setting context._sent for legacy support of Babel's
            // function.sent implementation.
            context.sent = context._sent = context.arg;
        } else if (context.method === 'throw') {
            if (state === GenStateSuspendedStart) {
                state = GenStateCompleted;
                throw context.arg;
            }

            context.dispatchException(context.arg);
        } else if (context.method === 'return') {
            context.abrupt('return', context.arg);
        }

        state = GenStateExecuting;

        // innerFn就是while個(gè)循環(huán)了秦陋,使我們的代碼主體
        var record = tryCatch(innerFn, self, context);
        
        if (record.type === 'normal') {
            // If an exception is thrown from innerFn, we leave state ===
            // GenStateExecuting and loop back for another invocation.
            state = context.done
                ? GenStateCompleted
                : GenStateSuspendedYield;

            if (record.arg === ContinueSentinel) {
                continue;
            }

            return {
                value: record.arg,
                done: context.done
            };
        } else if (record.type === 'throw') {
            state = GenStateCompleted;
            // Dispatch the exception by looping back around to the
            // context.dispatchException(context.arg) call above.
            context.method = 'throw';
            context.arg = record.arg;
        }
    }
};

在之后蔓彩,就是我們熟悉的promise相關(guān)操作了,在判斷done是否為true,否則繼續(xù)執(zhí)行赤嚼,將_next和_throw作為resolve和reject傳入即可旷赖。

小結(jié)

可以看到,僅僅一個(gè)async其實(shí)做了不少工作更卒。核心就是兩個(gè)等孵,產(chǎn)出一個(gè)兼容版本的generator和使用promise,回到這節(jié)的問(wèn)題上蹂空,答案就是:

return new Promise(function (resolve, reject) {});

沒(méi)錯(cuò)俯萌,就是返回一個(gè)Promise,內(nèi)部會(huì)根據(jù)狀態(tài)及決定是否繼續(xù)執(zhí)行下一個(gè)Promise.resolve().then()上枕。

如果async函數(shù)內(nèi)有很多其他操作的代碼咐熙,那么while會(huì)跟著變化,利用prev和next來(lái)管理執(zhí)行順序姿骏。這里就不具體分析了糖声,自己寫(xiě)個(gè)例子就明白了~

可以通過(guò)babel在線(xiàn)轉(zhuǎn)換,給自己一個(gè)具象的感知分瘦,更利于理解蘸泻。

為什么下面這種函數(shù)外的console不會(huì)等待,函數(shù)內(nèi)的會(huì)等待嘲玫?

async function fn() {
    await (async () => {
        await new Promise((r) => {
            setTimeout(function () {
                r();
            }, 2000);
        });
    })();
    console.log('你好');
}
fn();
console.log(123);

因?yàn)榻馕龊蟮腸onsole.log(123); 是在整個(gè)語(yǔ)法糖之外啊悦施,log 和 fn 是主協(xié)程序,fn內(nèi)是輔協(xié)程去团。不相干的抡诞。

總結(jié)

有句話(huà)怎么說(shuō)來(lái)著,會(huì)者不難土陪,難者不會(huì)昼汗。所以人人都是大牛,只是你還沒(méi)發(fā)力而已鬼雀,哈哈~

筆者后來(lái)思考覺(jué)得這種寫(xiě)法完全就是回調(diào)函數(shù)的替代品顷窒,而且增加了空間,加深了調(diào)用堆棧源哩,或許原生的寫(xiě)法才是效率最高的吧鞋吉。

不過(guò),需要良好的編碼規(guī)范励烦,算是一種折中的方式了谓着。畢竟用這種方式來(lái)寫(xiě)業(yè)務(wù)事半功倍~

對(duì)于本文觀點(diǎn),完全是個(gè)人閱讀后的思考坛掠,如有錯(cuò)誤赊锚,歡迎指正治筒,我會(huì)及時(shí)更新,避免誤導(dǎo)他人改抡。

拜了個(gè)拜~

image
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末矢炼,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子阿纤,更是在濱河造成了極大的恐慌,老刑警劉巖夷陋,帶你破解...
    沈念sama閱讀 218,284評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件欠拾,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡骗绕,警方通過(guò)查閱死者的電腦和手機(jī)藐窄,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,115評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)酬土,“玉大人荆忍,你說(shuō)我怎么就攤上這事〕方桑” “怎么了刹枉?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,614評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)屈呕。 經(jīng)常有香客問(wèn)我微宝,道長(zhǎng),這世上最難降的妖魔是什么虎眨? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,671評(píng)論 1 293
  • 正文 為了忘掉前任蟋软,我火速辦了婚禮,結(jié)果婚禮上嗽桩,老公的妹妹穿的比我還像新娘岳守。我一直安慰自己,他們只是感情好碌冶,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,699評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布湿痢。 她就那樣靜靜地躺著,像睡著了一般种樱。 火紅的嫁衣襯著肌膚如雪蒙袍。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,562評(píng)論 1 305
  • 那天嫩挤,我揣著相機(jī)與錄音害幅,去河邊找鬼。 笑死岂昭,一個(gè)胖子當(dāng)著我的面吹牛以现,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 40,309評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼邑遏,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼佣赖!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起记盒,我...
    開(kāi)封第一講書(shū)人閱讀 39,223評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤憎蛤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后纪吮,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體俩檬,經(jīng)...
    沈念sama閱讀 45,668評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,859評(píng)論 3 336
  • 正文 我和宋清朗相戀三年碾盟,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了棚辽。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,981評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡冰肴,死狀恐怖屈藐,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情熙尉,我是刑警寧澤联逻,帶...
    沈念sama閱讀 35,705評(píng)論 5 347
  • 正文 年R本政府宣布,位于F島的核電站骡尽,受9級(jí)特大地震影響遣妥,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜攀细,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,310評(píng)論 3 330
  • 文/蒙蒙 一箫踩、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧谭贪,春花似錦境钟、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,904評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至套媚,卻和暖如春缚态,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背堤瘤。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,023評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工玫芦, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人本辐。 一個(gè)月前我還...
    沈念sama閱讀 48,146評(píng)論 3 370
  • 正文 我出身青樓桥帆,卻偏偏與公主長(zhǎng)得像医增,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子老虫,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,933評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • 轉(zhuǎn)載請(qǐng)注明出處 原文連接 http://blog.huanghanlian.com/article/5c7aa6c...
    深沉的簡(jiǎn)單閱讀 1,859評(píng)論 0 40
  • 在此處先列下本篇文章的主要內(nèi)容 簡(jiǎn)介 next方法的參數(shù) for...of循環(huán) Generator.prototy...
    醉生夢(mèng)死閱讀 1,442評(píng)論 3 8
  • PNG 有PNG8和truecolor PNG PNG8類(lèi)似GIF顏色上限為256叶骨,文件小,支持alpha透明度祈匙,...
    hudaren閱讀 1,521評(píng)論 0 0
  • 一忽刽、es6中的箭頭函數(shù)和普通函數(shù)有什么區(qū)別? 1菊卷、普通函數(shù)中的this總是指向調(diào)用它的那個(gè)對(duì)象缔恳, 箭頭函數(shù)沒(méi)有自己...
    百事皆可樂(lè)_5eed閱讀 10,094評(píng)論 0 9
  • 一、let 和 constlet:變量聲明, const:只讀常量聲明(聲明的時(shí)候賦值)洁闰。 let 與 var 的...
    dadage456閱讀 762評(píng)論 0 0