從Google V8引擎剖析Promise實現(xiàn)

本文閱讀的源碼為Google V8 Engine v3.29.45虱岂,此版本的promise實現(xiàn)為js版本,在后續(xù)版本Google繼續(xù)對其實現(xiàn)進(jìn)行了處理叮贩。引入了es6語法等几于,在7.X版本迭代后,逐漸迭代成了C版本實現(xiàn)筹陵。

貼上源碼地址刽锤,大家自覺傳送。

代碼中所有類似%functionName的函數(shù)均是C語言實現(xiàn)的運(yùn)行時函數(shù)朦佩。

事件循環(huán)

JavaScript的一大特點就是單線程并思,而這個線程中擁有唯一的一個事件循環(huán)。

一個線程中语稠,事件循環(huán)是唯一的宋彼,但是任務(wù)隊列可以擁有多個。

任務(wù)隊列又分為macro-task(宏任務(wù))與micro-task(微任務(wù))仙畦。

宏任務(wù)包括setTimeout, setInterval输涕,微任務(wù)包括process.nextTick, Promise, Object.observe等。

不同的任務(wù)會進(jìn)入不同的隊列慨畸,而Promise屬于微任務(wù)莱坎,會進(jìn)入微任務(wù)隊列進(jìn)行處理。

編程模型

常見的編程模型有三種:單線程同步模型寸士、多線程同步模型檐什、異步編程模型碴卧。

單線程同步模型
多線程同步模型
異步編程模型

單線程同步模型就像一個先入先出的隊列,后一個任務(wù)必須等待前一個任務(wù)完成才能繼續(xù)乃正。多線程同步模型克服了單線程等待的問題住册,但是多線程執(zhí)行由內(nèi)核調(diào)度,線程間切換代價高烫葬,高并發(fā)環(huán)境下對CPU界弧、內(nèi)存消耗較大。Javascript的作者意識到大部分耗時來源于IO等待搭综,沒有必要為IO等待耗費(fèi)昂貴的計算資源垢箕。同步任務(wù)必須在主線程上排隊執(zhí)行,前一個任務(wù)結(jié)束兑巾,后一個任務(wù)才能執(zhí)行条获。異步任務(wù)可以被掛起進(jìn)入異步隊列等待調(diào)度。瀏覽器的異步隊列分為兩種蒋歌,宏任務(wù)隊列(Scirpt帅掘、setTimeout、setInterval)和微任務(wù)隊列(Promise堂油、process.nextTick)修档。Javascript運(yùn)行時會將同步代碼放入執(zhí)行棧,當(dāng)執(zhí)行棧為空或者同步代碼執(zhí)行完畢府框,主線程會先執(zhí)行微任務(wù)隊列里的任務(wù)吱窝,再執(zhí)行宏任務(wù),如此反復(fù)執(zhí)行迫靖,直到清空執(zhí)行棧和異步隊列院峡。

Promise解決了什么問題

Javascript是函數(shù)式編程語言,函數(shù)是一等公民系宜,異步任務(wù)被主線程掛起包裝成回調(diào)函數(shù)放入任務(wù)隊列照激。這樣的設(shè)計解決了性能問題,但是多級回調(diào)的關(guān)聯(lián)變得難以維護(hù)盹牧。Promise正是針對這個問題而誕生的俩垃。

Google V8 Promise.js實現(xiàn)

整體實現(xiàn)

整體結(jié)構(gòu)

Promise.js聲明$Promise構(gòu)造函數(shù),使用PromiseSet初始化Promise對象汰寓,每個Promise對象具有4個屬性吆寨。

1. Promise#value(返回值)

2. Promise#status(狀態(tài),0代表pending踩寇,+1代表resolved啄清,-1代表rejected)

3. Promise#onResolve(resolve后執(zhí)行隊列)

4. Promise#onReject(reject后執(zhí)行隊列)

Promise#raw變量可以看作Javascript中的空對象{}。

InternalArray相當(dāng)于Array函數(shù)。

global變量代表瀏覽器window對象辣卒,相當(dāng)于Javascript代碼:

var Global = (function() {

? ? try { return self.self } catch (x) {}

? ? try { return global.global } catch (x) {}

? ? return null;

})();

%AddNamedProperty宏可以將變量掛載在對象上掷贾,可以看作Javascript中代碼:

function AddNamedProperty(target, name, value) {

? ? if (!_defineProperty) {

? ? ? ? target[name] = value;

? ? ? ? return;

? ? }

? ? _defineProperty(target, name, {

? ? ? ? configurable: true,

? ? ? ? writable: true,

? ? ? ? enumerable: false,

? ? ? ? value: value

? ? });

}

%AddNamedProperty(global, 'Promise', $Promise, DONT_ENUM)將$Promise構(gòu)造函數(shù)掛載到瀏覽器window對象上。

隨后使用InstallFunctions方法分別將defer荣茫、accept想帅、reject、all啡莉、race港准、resolve掛載在$Promise上,chain咧欣、then浅缸、catch掛載在$Promise的原型鏈上。

InstallFunctions宏相當(dāng)于Javascript代碼:

function InstallFunctions(target, attr, list) {

? ? for (var i = 0; i < list.length; i += 2)

? ? ? ? AddNamedProperty(target, list[i], list[i + 1]);

}

我們來創(chuàng)建一個Promise對象看看它長什么樣子魄咕。

> var p = new $Promise(function(){})

> Promise?{Promise#status: 0, Promise#value: undefined, Promise#onResolve: Array(0), Promise#onReject: Array(0)}

Promise的底層依賴于微任務(wù)隊列衩椒,Promise.js中的宏%EnqueueMicrotask會將異步函數(shù)掛起放入微任務(wù)隊列中等待主線程調(diào)度。當(dāng)時間片來臨時哮兰,Javascrip解釋器會依次執(zhí)行微任務(wù)隊列中的所有任務(wù)毛萌。你可以簡單地使用setTImeout來模擬宏%EnqueueMicrotask的入隊操作:

var runLater = (function() {

? ? return function(fn) { setTimeout(fn, 0); };

})();

var EnqueueMicrotask = (function() {

? ? var queue = null;

? ? function flush() {

? ? ? ? var q = queue;

? ? ? ? queue = null;

? ? ? ? for (var i = 0; i < q.length; ++i)

? ? ? ? ? ? q[i]();

? ? }

? ? return function PromiseEnqueueMicrotask(fn) {

? ? ? ? // fn must not throw

? ? ? ? if (!queue) {

? ? ? ? ? ? queue = [];

? ? ? ? ? ? runLater(flush);

? ? ? ? }

? ? ? ? queue.push(fn);

? ? };

})();

構(gòu)造函數(shù)

如果你來實現(xiàn)Promise,最容易想到的就是構(gòu)造一個函數(shù)喝滞,這個函數(shù)包含一個參數(shù)阁将,這個參數(shù)同樣是函數(shù),他接受一個resolve和一個reject右遭,最終的值通過參數(shù)函數(shù)內(nèi)部調(diào)用resolve和reject來返回做盅。源碼的內(nèi)部實現(xiàn)也差不多,構(gòu)造函數(shù)$Promise調(diào)用PromiseSet方法返回一個對象狸演,這個對象就是Promise言蛇。PromiseSet方法代碼如下:

function PromiseSet(promise, status, value, onResolve, onReject) {

? ? SET_PRIVATE(promise, promiseStatus, status);

? ? SET_PRIVATE(promise, promiseValue, value);

? ? SET_PRIVATE(promise, promiseOnResolve, onResolve);

? ? SET_PRIVATE(promise, promiseOnReject, onReject);

? ? return promise;

}

SET_PRIVATE可以看作Javascript代碼

function SET_PRIVATE(obj, prop, val) { obj[prop] = val; }

promiseStatus = "Promise#status"代表Promise的狀態(tài)僻他,初始值為0代表pending宵距,它的狀態(tài)只能改變1次,要么成功為+1吨拗,要么失敗為-1满哪。

promiseValue = "Promise#value"用來記錄Promise回調(diào)運(yùn)行結(jié)果。

promiseOnResolve = "Promise#onResolve"初始值為空數(shù)組劝篷,當(dāng)異步操作串聯(lián)前面的回調(diào)沒有resolve的時候哨鸭,promiseOnResolve用來記錄后續(xù)成功回調(diào)操作。

promiseOnReject = "Promise#onReject"類似娇妓。

此外構(gòu)造函數(shù)還在內(nèi)部調(diào)用了傳入的函數(shù)resolver像鸡,并給resolver傳入了兩個值function(x) { PromiseResolve(promise, x) }function(r) { PromiseReject(promise, r) }),這兩個值便是我們經(jīng)常在Promise內(nèi)執(zhí)行的resolve和reject函數(shù)哈恰。好了只估,看來關(guān)鍵邏輯都在function(x) { PromiseResolve(promise, x) }function(r) { PromiseReject(promise, r) })這兩個值上志群。

PromiseResolve和PromiseReject

說到這PromiseResolvePromiseReject就不得不說PromiseDone,因為他們只是的PromiseDone語法糖蛔钙。

PromiseResolve = function PromiseResolve(promise, x) {

? ? PromiseDone(promise, +1, x, promiseOnResolve);

};

PromiseReject = function PromiseReject(promise, r) {

? ? PromiseDone(promise, -1, r, promiseOnReject);

};

PromiseDone

Promise的狀態(tài)只能改變一次锌云,因此PromiseDone方法首先判斷Promise狀態(tài)是否改變,如果沒有改變則調(diào)用函數(shù)PromiseEnqueue吁脱,然后調(diào)用PromiseSet函數(shù)改變了Promise的狀態(tài)和返回值桑涎。

function PromiseDone(promise, status, value, promiseQueue) {

? ? if (GET_PRIVATE(promise, promiseStatus) === 0) {

? ? ? ? PromiseEnqueue(value, GET_PRIVATE(promise, promiseQueue), status);

? ? ? ? PromiseSet(promise, status, value);

? ? }

}

PromiseEnqueue

前面我們說過宏%EnqueueMicrotaskPromiseEnqueue函數(shù)使用宏%EnqueueMicrotask將任務(wù)task包裝到PromiseHandle函數(shù)中壓入微任務(wù)隊列兼贡。

function PromiseEnqueue(value, tasks, status) {

? ? var id, name, instrumenting = DEBUG_IS_ACTIVE;

? ? %EnqueueMicrotask(function() {

? ? ? if (instrumenting) {

? ? ? ? %DebugAsyncTaskEvent({ type: "willHandle", id: id, name: name });

? ? ? }

? ? ? for (var i = 0; i < tasks.length; i += 2) {

? ? ? ? PromiseHandle(value, tasks[i], tasks[i + 1])

? ? ? }

? ? ? if (instrumenting) {

? ? ? ? %DebugAsyncTaskEvent({ type: "didHandle", id: id, name: name });

? ? ? }

? ? });

? ? if (instrumenting) {

? ? ? id = ++lastMicrotaskId;

? ? ? name = status > 0 ? "Promise.resolve" : "Promise.reject";

? ? ? %DebugAsyncTaskEvent({ type: "enqueue", id: id, name: name });

? ? }

? }

上面的代碼很多是針對調(diào)試過程的攻冷,我們可以忽略宏%DebugAsyncTaskEvent的相關(guān)代碼,上面的代碼可以簡化成Javascript代碼:

function PromiseEnqueue(value, tasks, status) {

? ? EnqueueMicrotask(function() {

? ? ? ? for (var i = 0; i < tasks.length; i += 2)

? ? ? ? ? ? PromiseHandle(value, tasks[i], tasks[i + 1]);

? ? });

}

這里我們先考慮最簡單的情況紧显,先忽略then鏈?zhǔn)秸{(diào)用讲衫,因此task應(yīng)該為空。到這里就可以理解new Promise(function(resolve, reject){resolve();})的運(yùn)行過程了孵班。

PromiseThen

下面我們進(jìn)入then鏈?zhǔn)秸{(diào)用的過程涉兽,PromiseThen方法在原型鏈上,因此new Promise(function(resolve, reject){resolve();})返回的Promise對象可以調(diào)用then方法級聯(lián)篙程。PromiseThen方法內(nèi)部主要通過PromiseChain方法實現(xiàn)枷畏。我們忽略特殊情況,x應(yīng)該為resolve返回的值虱饿,因此PromiseChain方法接收onResolve和onReject兩個函數(shù)拥诡。

PromiseThen = function PromiseThen(onResolve, onReject) {

? ? onResolve = IS_SPEC_FUNCTION(onResolve) ? onResolve : PromiseIdResolveHandler;

z? ? onReject = IS_SPEC_FUNCTION(onReject) ? onReject : PromiseIdRejectHandler;

? ? var that = this;

? ? var constructor = this.constructor;

? ? return PromiseChain.call(

? ? ? ? this,

? ? ? ? function(x) {

? ? ? ? ? ? x = PromiseCoerce(constructor, x);

? ? ? ? ? ? return x === that ? onReject(MakeTypeError('promise_cyclic', [x])) :

? ? ? ? ? ? ? ? IsPromise(x) ? x.then(onResolve, onReject) :

? ? ? ? ? ? ? ? onResolve(x);

? ? ? ? },

? ? ? ? onReject);

}

PromiseCoerce

承接上面x是resolve返回的值,下面的PromiseCoerce應(yīng)該直接返回x氮发。

function PromiseCoerce(constructor, x) {

? ? if (!IsPromise(x) && IS_SPEC_OBJECT(x)) {

? ? ? ? var then;

? ? ? ? try {

? ? ? ? ? ? then = x.then;

? ? ? ? } catch(r) {

? ? ? ? ? ? return PromiseRejected.call(constructor, r);

? ? ? ? }

? ? ? ? if (IS_SPEC_FUNCTION(then)) {

? ? ? ? ? ? var deferred = PromiseDeferred.call(constructor);

? ? ? ? ? ? try {

? ? ? ? ? ? ? ? then.call(x, deferred.resolve, deferred.reject);

? ? ? ? ? ? } catch(r) {

? ? ? ? ? ? ? ? deferred.reject(r);

? ? ? ? ? ? }

? ? ? ? ? ? return deferred.promise;

? ? ? ? }

? ? }

? ? return x;

}

PromiseChain

如果讓你來實現(xiàn)Promise的鏈?zhǔn)秸{(diào)用渴肉,能夠想到的方法或許是每個then都返回一個promise對象,這個返回的promise對象就是鏈?zhǔn)秸{(diào)用的關(guān)鍵爽冕。事實上Promise.js正是這么做的仇祭,只不過它做的更精妙一些,它封裝了一個PromiseDeferred方法颈畸。每個PromiseChain內(nèi)部都調(diào)用了PromiseDeferred獲取一個deferred對象乌奇,這個對象包含一個status為0的promise對象和改變promise狀態(tài)的resolve方法和reject方法。

function PromiseDeferred() {

? ? if (this === $Promise) {

? ? ? ? // Optimized case, avoid extra closure.

? ? ? ? var promise = PromiseInit(new $Promise(promiseRaw));

? ? ? ? return {

? ? ? ? ? ? promise: promise,

? ? ? ? ? ? resolve: function(x) { PromiseResolve(promise, x) },

? ? ? ? ? ? reject: function(r) { PromiseReject(promise, r) }

? ? ? ? };

? ? } else {

? ? ? ? var result = {};

? ? ? ? result.promise = new this(function(resolve, reject) {

? ? ? ? ? ? result.resolve = resolve;

? ? ? ? ? ? result.reject = reject;

? ? ? ? });

? ? ? ? return result;

? ? }

}

接著PromiseChain進(jìn)行了一次switch判斷眯娱,前面我們忽略了then鏈?zhǔn)秸{(diào)用礁苗,PromiseEnqueue的時候task為空。現(xiàn)在我們帶上then的鏈?zhǔn)秸{(diào)用徙缴,當(dāng)前序調(diào)用未完成试伙,執(zhí)行then的時候就匹配promiseStatus為0的情況,這時就將[onResolve, deferred]加入數(shù)組promiseOnResolve,[onReject, deferred]加入數(shù)組promiseOnReject疏叨,在執(zhí)行微任務(wù)時依次執(zhí)行吱抚。如果前序resolve調(diào)用完成,就會匹配promiseStatus為+1的情況考廉,這時就可以執(zhí)行OnResolve了秘豹,因為Promise是異步的不會立即執(zhí)行,而是加入異步隊列昌粤,因此將當(dāng)前promise的onResolve使用PromiseEnqueue方法入隊既绕。前序reject的情況類似。最后返回一個新的promise對象deferred.promise涮坐,保證then的鏈?zhǔn)秸{(diào)用凄贩。到這里鏈?zhǔn)秸{(diào)用的邏輯就走通了。

PromiseChain = function PromiseChain(onResolve, onReject) {

? ? onResolve = IS_UNDEFINED(onResolve) ? PromiseIdResolveHandler : onResolve;

? ? onReject = IS_UNDEFINED(onReject) ? PromiseIdRejectHandler : onReject;

? ? var deferred = PromiseDeferred.call(this.constructor);

? ? switch (GET_PRIVATE(this, promiseStatus)) {

? ? ? ? case UNDEFINED:

? ? ? ? ? ? throw MakeTypeError('not_a_promise', [this]);

? ? ? ? case 0:? // Pending

? ? ? ? ? ? GET_PRIVATE(this, promiseOnResolve).push(onResolve, deferred);

? ? ? ? ? ? GET_PRIVATE(this, promiseOnReject).push(onReject, deferred);

? ? ? ? ? ? break;

? ? ? ? case +1:? // Resolved

? ? ? ? ? ? PromiseEnqueue(GET_PRIVATE(this, promiseValue), [onResolve, deferred], +1);

? ? ? ? ? ? break;

? ? ? ? case -1:? // Rejected

? ? ? ? ? ? PromiseEnqueue(GET_PRIVATE(this, promiseValue), [onReject, deferred], -1);

? ? ? ? ? ? break;

? ? }

? ? // Mark this promise as having handler.

? ? SET_PRIVATE(this, promiseHasHandler, true);

? ? return deferred.promise;

l}

PromiseAll

下面我們說說Promise的all方法袱讹,Promise.all方法用于將多個 Promise 實例疲扎,包裝成一個新的 Promise 實例,當(dāng)所有實例都返回時捷雕,Promise.all才會完成椒丧,只要有一個reject,結(jié)果就會reject救巷。

function PromiseAll(values) {

? ? var deferred = PromiseDeferred.call(this);

? ? var resolutions = [];

? ? if (!IsArray(values)) {

? ? ? ? deferred.reject(MakeTypeError('invalid_argument'));

? ? ? ? return deferred.promise;

? ? }

? ? try {

? ? ? ? var count = values.length;

? ? ? ? if (count === 0) {

? ? ? ? ? ? deferred.resolve(resolutions);

? ? ? ? } else {

? ? ? ? ? ? for (var i = 0; i < values.length; ++i) {

? ? ? ? ? ? ? ? this.resolve(values[i]).then(

? ? ? ? ? ? ? ? ? ? (function() {

? ? ? ? ? ? ? ? ? ? ? ? // Nested scope to get closure over current i (and avoid .bind).

? ? ? ? ? ? ? ? ? ? ? ? var i_captured = i;

? ? ? ? ? ? ? ? ? ? ? ? return function(x) {

? ? ? ? ? ? ? ? ? ? ? ? ? ? resolutions[i_captured] = x;

? ? ? ? ? ? ? ? ? ? ? ? ? ? if (--count === 0) deferred.resolve(resolutions);

? ? ? ? ? ? ? ? ? ? ? ? };

? ? ? ? ? ? ? ? ? ? })(),

? ? ? ? ? ? ? ? ? ? function(r) { deferred.reject(r) });

? ? ? ? ? ? }

? ? ? ? }

? ? } catch (e) {

? ? ? ? deferred.reject(e);

? ? }

? ? return deferred.promise;

}

有了前面的分析壶熏,這里就可以看出PromiseAll使用PromiseDeferred創(chuàng)建了一個deferred對象,通過count--的邏輯浦译,是否所有promise都返回棒假,如果全都成功就會resolve,一個失敗結(jié)果就會失敗精盅。PromiseAll在實際應(yīng)用中可以實現(xiàn)并發(fā)的邏輯帽哑,幾個沒有依賴關(guān)系的接口可以并發(fā)調(diào)度,減少整個接口的響應(yīng)延遲叹俏。

PromiseOne

PromiseOne的實現(xiàn)也類似妻枕,它在實際使用中可以實現(xiàn)接口響應(yīng)超時的邏輯,例如一個http接口依賴于三方接口她肯,三方接口阻塞怎么都不返回佳头,使用PromiseOne可以實現(xiàn)如果三方接口2秒內(nèi)不返回就直接給用戶返回失敗的邏輯鹰贵,避免用戶不必要的等待時間晴氨。

function PromiseOne(values) {

? ? var deferred = PromiseDeferred.call(this);

? ? if (!IsArray(values)) {

? ? ? ? deferred.reject(MakeTypeError('invalid_argument'));

? ? ? ? return deferred.promise;

? ? }

? ? try {

? ? ? ? for (var i = 0; i < values.length; ++i) {

? ? ? ? ? ? this.resolve(values[i]).then(

? ? ? ? ? ? ? ? function(x) { deferred.resolve(x) },

? ? ? ? ? ? ? ? function(r) { deferred.reject(r) });

? ? ? ? }

? ? } catch (e) {

? ? ? ? deferred.reject(e);

? ? }

? ? return deferred.promise;

}

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市碉输,隨后出現(xiàn)的幾起案子籽前,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,348評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件枝哄,死亡現(xiàn)場離奇詭異肄梨,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)挠锥,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,122評論 2 385
  • 文/潘曉璐 我一進(jìn)店門众羡,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人蓖租,你說我怎么就攤上這事粱侣。” “怎么了蓖宦?”我有些...
    開封第一講書人閱讀 156,936評論 0 347
  • 文/不壞的土叔 我叫張陵齐婴,是天一觀的道長。 經(jīng)常有香客問我稠茂,道長柠偶,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,427評論 1 283
  • 正文 為了忘掉前任睬关,我火速辦了婚禮诱担,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘电爹。我一直安慰自己该肴,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 65,467評論 6 385
  • 文/花漫 我一把揭開白布藐不。 她就那樣靜靜地躺著匀哄,像睡著了一般。 火紅的嫁衣襯著肌膚如雪雏蛮。 梳的紋絲不亂的頭發(fā)上涎嚼,一...
    開封第一講書人閱讀 49,785評論 1 290
  • 那天,我揣著相機(jī)與錄音挑秉,去河邊找鬼法梯。 笑死,一個胖子當(dāng)著我的面吹牛犀概,可吹牛的內(nèi)容都是我干的立哑。 我是一名探鬼主播,決...
    沈念sama閱讀 38,931評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼姻灶,長吁一口氣:“原來是場噩夢啊……” “哼铛绰!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起产喉,我...
    開封第一講書人閱讀 37,696評論 0 266
  • 序言:老撾萬榮一對情侶失蹤捂掰,失蹤者是張志新(化名)和其女友劉穎敢会,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體这嚣,經(jīng)...
    沈念sama閱讀 44,141評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡鸥昏,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,483評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了姐帚。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片吏垮。...
    茶點故事閱讀 38,625評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖罐旗,靈堂內(nèi)的尸體忽然破棺而出惫皱,到底是詐尸還是另有隱情,我是刑警寧澤尤莺,帶...
    沈念sama閱讀 34,291評論 4 329
  • 正文 年R本政府宣布旅敷,位于F島的核電站,受9級特大地震影響颤霎,放射性物質(zhì)發(fā)生泄漏媳谁。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,892評論 3 312
  • 文/蒙蒙 一友酱、第九天 我趴在偏房一處隱蔽的房頂上張望晴音。 院中可真熱鬧,春花似錦缔杉、人聲如沸锤躁。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽系羞。三九已至,卻和暖如春霸琴,著一層夾襖步出監(jiān)牢的瞬間椒振,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評論 1 265
  • 我被黑心中介騙來泰國打工梧乘, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留澎迎,地道東北人。 一個月前我還...
    沈念sama閱讀 46,324評論 2 360
  • 正文 我出身青樓选调,卻偏偏與公主長得像夹供,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子仁堪,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,492評論 2 348

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

  • 官方中文版原文鏈接 感謝社區(qū)中各位的大力支持哮洽,譯者再次奉上一點點福利:阿里云產(chǎn)品券,享受所有官網(wǎng)優(yōu)惠枝笨,并抽取幸運(yùn)大...
    HetfieldJoe閱讀 8,677評論 0 29
  • JavaScript絕對是最火的編程語言之一袁铐,一直具有很大的用戶群,隨著在服務(wù)端的使用(NodeJs)横浑,更是爆發(fā)了...
    不去解釋閱讀 2,411評論 1 16
  • V8的前世今生 V8是JavaScript渲染引擎剔桨,第一個版本隨著Chrome的發(fā)布而發(fā)布(具體時間為2008年9...
    燕京博士閱讀 2,616評論 1 3
  • 特別說明,為便于查閱徙融,文章轉(zhuǎn)自https://github.com/getify/You-Dont-Know-JS...
    殺破狼real閱讀 645評論 0 3
  • 晚飯后正準(zhǔn)備整理下衣柜洒缀,林毅說“你今天晚上不是很忙嗎,還有時間整理衣柜欺冀?”是笆骷ā!可我哪天不忙呢隐轩?本來下班回家應(yīng)該除...
    蝶戀花5閱讀 185評論 1 0