理解路由懶加載的原理【轉(zhuǎn)】

原文地址:https://segmentfault.com/a/1190000022846552

前言

說起路由懶加載帮毁,大家很快就知道怎么實現(xiàn)它,但是問到路由懶加載的原理屹徘,怕有一部分小伙伴是一頭霧水了吧。下面帶大家一起去理解路由懶加載的原理。

路由懶加載也可以叫做路由組件懶加載,最常用的是通過import()來實現(xiàn)它究流。

function load(component) {
    return () => import(`views/${component}`)
}

然后通過Webpack編譯打包后寝殴,會把每個路由組件的代碼分割成一一個js文件,初始化時不會加載這些js文件善茎,只當激活路由組件才會去加載對應(yīng)的js文件券册。

在這里先不管Webpack是怎么按路由組件分割代碼,只管在Webpack編譯后垂涯,怎么實現(xiàn)按需加載對應(yīng)的路由組件js文件烁焙。

一、準備工作

1耕赘、搭建項目

想要理解路由懶加載的原理骄蝇,建議從最簡單的項目開始,用Vue Cli3搭建一個項目操骡,其中只包含一個路由組件九火。在main.js只引入vue-router,其它統(tǒng)統(tǒng)不要册招。

main.js

import Vue from 'vue';
import App from './App.vue';
import Router from 'vue-router';
Vue.use(Router);
//路由懶加載
function load(component) {
    return () => import(`views/${component}`)
}
// 路由配置
const router = new Router({
    mode: 'history',
    base: process.env.BASE_URL,
    routes: [
        {
            path: '/',
            name: 'home',
            component: load('Home'),
            meta: {
                title: '首頁'
            }
        },
    ]
});
new Vue({
    router,
    render: h => h(App)
}).$mount('#app')

views/Home.vue

<template>
    <div>
        {{tip}}
    </div>
</template>
<script>
export default {
    data(){
        return {
            tip:'歡迎使用Vue項目'
        }
    }
}
</script>

2岔激、webpackChunkName

利用webpackChunkName,使編譯打包后的js文件名字能和路由組件一一對應(yīng),修改一下load函數(shù)是掰。

function load(component) {
    return () => import(/* webpackChunkName: "[request]" */ `views/${component}`)
}

3虑鼎、去掉代碼壓縮混淆

去掉代碼壓縮混淆,便于我們閱讀編譯打包后的代碼。在vue.config.js中配置

module.exports={
    chainWebpack:config => {
        config.optimization.minimize(false);
    },
}

4炫彩、npm run build

執(zhí)行命令npm run build匾七,編譯打包后的dist文件結(jié)構(gòu)如下所示

[圖片上傳失敗...(image-e06cd6-1619759897206)]

其中Home.67f3cd34.js就是路由組件Home.vue編譯打包后對應(yīng)的js文件。

二媒楼、分析index.html

[圖片上傳失敗...(image-1b5e76-1619759897205)]
從上面我們可以看到乐尊,先用link定義Home.js、app.js划址、chunk-vendors.js這些資源和web客戶端的關(guān)系扔嵌。

  • ref=preload:告訴瀏覽器這個資源要給我提前加載。
  • rel=prefetch:告訴瀏覽器這個資源空閑的時候給我加載一下夺颤。
  • as=script:告訴瀏覽器這個資源是script痢缎,提升加載的優(yōu)先級。

然后在body里面加載了chunk-vendors.js世澜、app.js這兩個js資源独旷。可以看出web客戶端初始化時候就加載了這個兩個js資源寥裂。

三嵌洼、分析chunk-vendors.js

chunk-vendors.js可以稱為項目公共模塊集合,代碼精簡后如下所示封恰,

(window["webpackJsonp"] = window["webpackJsonp"] || []).push([["chunk-vendors"],{
    "01f9":(function(module,exports,__webpack_require__){
        ...//省略
    })
    ...//省略
}])

從代碼中可以看出麻养,執(zhí)行chunk-vendors.js,僅僅把下面這個數(shù)組pushwindow["webpackJsonp"]中诺舔,而數(shù)組第二項是個對象鳖昌,對象的每個value值是一個函數(shù)表達式,不會執(zhí)行低飒。就這樣結(jié)束了许昨,當然不是,我們帶著window["webpackJsonp"]去app.js中找找褥赊。

四糕档、分析app.js

app.js可以稱為項目的入口文件。

app.js里面是一個自執(zhí)行函數(shù)拌喉,通過搜索window["webpackJsonp"]可以找到如下相關(guān)代碼翼岁。

(function(modules){
    //省略...
    var jsonpArray = window["webpackJsonp"] = window["webpackJsonp"] || [];
    var oldJsonpFunction = jsonpArray.push.bind(jsonpArray);
    jsonpArray.push = webpackJsonpCallback;
    jsonpArray = jsonpArray.slice();
    for (var i = 0; i < jsonpArray.length; i++) webpackJsonpCallback(jsonpArray[i]);
    var parentJsonpFunction = oldJsonpFunction;
    //省略...
}({
    0:(function(module, exports, __webpack_require__) {
        module.exports = __webpack_require__("56d7");
    })
    //省略...
}))
  • 先把window["webpackJsonp"]賦值給jsonpArray
  • jsonpArraypush方法賦值給oldJsonpFunction司光。
  • webpackJsonpCallback函數(shù)攔截jsopArraypush方法,也就是說調(diào)用window["webpackJsonp"]push方法都會執(zhí)行webpackJsonpCallback函數(shù)悉患。
  • jsonpArray淺拷貝一下再賦值給jsonpArray残家。
  • 因為執(zhí)行chunk-vendors.js中的window["webpackJsonp"].pushpush方法還未被webpackJsonpCallback函數(shù)攔截,所以要循環(huán)jsonpArray售躁,將每項作為參數(shù)傳入webpackJsonpCallback函數(shù)并調(diào)用坞淮。
  • jsonpArraypush方法再賦值給parentJsonpFunction茴晋。

1、webpackJsonpCallback函數(shù)

接下來我們看一下webpackJsonpCallback這個函數(shù)回窘。

(function(modules){
    function webpackJsonpCallback(data) {
        var chunkIds = data[0];
        var moreModules = data[1];
        var executeModules = data[2];
        var moduleId, chunkId, i = 0, resolves = [];
        for (; i < chunkIds.length; i++) {
            chunkId = chunkIds[i];
            if (Object.prototype.hasOwnProperty.call(installedChunks, chunkId)
            && installedChunks[chunkId]) {
                resolves.push(installedChunks[chunkId][0]);
            }
            installedChunks[chunkId] = 0;
        }
        for (moduleId in moreModules) {
            if (Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {
                modules[moduleId] = moreModules[moduleId];
            }
        }
        if (parentJsonpFunction) parentJsonpFunction(data);
        while (resolves.length) {
            resolves.shift()();
        }
        deferredModules.push.apply(deferredModules, executeModules || []);
        return checkDeferredModules();
    };
    var installedChunks = {
        "app": 0
    };
    //省略...
}({
    0:(function(module, exports, __webpack_require__) {
        module.exports = __webpack_require__("56d7");
    })
    //省略...
}))

想知道webpackJsonpCallback函數(shù)有什么作用诺擅,要先弄明白modulesinstalledChunks啡直、deferredModules這三個變量的作用烁涌。

  • module是指任意的代碼塊,chunk是webpack處理過程中被分組的module的合集酒觅。
  • modules緩存所有的module(代碼塊)撮执,調(diào)用modules中的module就可以執(zhí)行里面的代碼。
  • installedChunks緩存所有chunk的加載狀態(tài)舷丹,如果installedChunks[chunk]為0抒钱,代表chunk已經(jīng)加載完畢。
  • deferredModules中每項也是一個數(shù)組颜凯,例如[module,chunk1,chunk2,chunk3],其作用是如果要執(zhí)行module谋币,必須在chunk1、chunk2症概、chunk3都加載完畢后才能執(zhí)行蕾额。

if (parentJsonpFunction) parentJsonpFunction(data)這句代碼在多入口項目中才有作用,在前面提到過jsonpArraypush方法被賦值給parentJsonpFunction穴豫,調(diào)用parentJsonpFunction是真正把chunk中push方法中的參數(shù)push到window["webpackJsonp"]這個數(shù)組中凡简。

比如說現(xiàn)在項目有兩個入口,app.js和app1.js精肃,app.js中緩存一些module秤涩,在app1.js就可以通過window["webpackJsonp"]來調(diào)用這些module,調(diào)用代碼如下司抱。

for (var i = 0; i < jsonpArray.length; i++) webpackJsonpCallback(jsonpArray[i]);

再來理解webpackJsonpCallback函數(shù)是不是清楚了很多筐眷,接下來看一下checkDeferredModules這個函數(shù)。

2习柠、checkDeferredModules函數(shù)

var deferredModules = [];
var installedChunks = {
    "app": 0
}
function checkDeferredModules() {
    var result;
    for (var i = 0; i < deferredModules.length; i++) {
        var deferredModule = deferredModules[i];
        var fulfilled = true;
        for (var j = 1; j < deferredModule.length; j++) {
            var depId = deferredModule[j];
            if (installedChunks[depId] !== 0) fulfilled = false;
        }
        if (fulfilled) {
            deferredModules.splice(i--, 1);
            result = __webpack_require__(__webpack_require__.s = deferredModule[0]);
        }
    }
    return result;
}
  • 循環(huán)deferredModules匀谣,創(chuàng)建變量fulfilled表示deferredModule中的chunk加載情況,true表示全部加載完畢资溃,false表示未全部加載完畢武翎。
  • j=1開始循環(huán)deferredModule中的chunk,因為deferredModule[0]是module溶锭,如果installedChunks[chunk]!==0宝恶,則這個chunk未加載完畢,把變量fulfilled設(shè)置為false。循環(huán)結(jié)束后返回result垫毙。
  • 經(jīng)循環(huán)deferredModule中的chunk并判斷chunk的加載狀態(tài)后霹疫,fulfilled還是為true,則調(diào)用__webpack_require__函數(shù)综芥,將deferredModule[0](module)作為參數(shù)傳入執(zhí)行丽蝎。
  • deferredModules.splice(i--, 1),刪除滿足條件的deferredModule,并將i減一,其中i--是先使用i,然后在減一藐握。

因為在webpackJsonpCallback函數(shù)中deferredModules[],所以回到主體函數(shù)繼續(xù)往下看栏笆。

deferredModules.push([0, "chunk-vendors"]);
return checkDeferredModules();

按上面邏輯分析后,會執(zhí)行__webpack_require__(0),那么來看一下__webpack_require__這個函數(shù)臊泰。

3蛉加、webpack_require函數(shù)

var installedModules = {};
function __webpack_require__(moduleId) {
    if (installedModules[moduleId]) {
        return installedModules[moduleId].exports;
    }
    var module = installedModules[moduleId] = {
        i: moduleId,
        l: false,
        exports: {}
    };
    modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
    module.l = true;
    return module.exports;
}

從代碼可知__webpack_require__就是一個執(zhí)行module的方法。

  • installedModules用來緩存module的執(zhí)行狀態(tài)缸逃。
  • 通過moduleId在modules(在webpackJsonpCallback函數(shù)中緩存所有module的集合)獲取對應(yīng)的module用call方法執(zhí)行针饥。
  • 將執(zhí)行結(jié)果賦值到module.exports并返回。

所以執(zhí)行__webpack_require__(0),其實就是執(zhí)行下面的代碼需频。

(function (module, exports, __webpack_require__) {
    module.exports = __webpack_require__("56d7");
}),

在里面又用__webpack_require__執(zhí)行id為56d7的module丁眼,我們找到對應(yīng)的module繼續(xù)看,看一下里面關(guān)鍵的代碼片段昭殉。

function load(component) {
    return function () {
        return __webpack_require__("9dac")("./".concat(component));
    };
}
var routes = [{
    path: '/',
    name: 'home',
    component: load('Home'),
    meta: {
        title: '首頁'
    }
}, {
    path: '*',
    redirect: {
        path: '/'
    }
}];

看到這里是不是非常熟悉了苞七,就是配置路由的地方。load還是作為加載路由組件的函數(shù)挪丢,里面用__webpack_require__("9dac")返回的方法來執(zhí)行加載路由組件蹂风,我們來看一下__webpack_require__("9dac")

(function (module, exports, __webpack_require__) {
    var map = {
        "./Home": [
            "bb51",
            "Home"
        ],
        "./Home.vue": [
            "bb51",
            "Home"
        ]
    };
    function webpackAsyncContext(req) {
        if (!__webpack_require__.o(map, req)) {
            return Promise.resolve().then(function () {
                var e = new Error("Cannot find module '" + req + "'");
                e.code = 'MODULE_NOT_FOUND';
                throw e;
            });
        }
        var ids = map[req], id = ids[0];
        return __webpack_require__.e(ids[1]).then(function () {
            return __webpack_require__(id);
        });
    }
    webpackAsyncContext.keys = function webpackAsyncContextKeys() {
        return Object.keys(map);
    };
    webpackAsyncContext.id = "9dac";
    module.exports = webpackAsyncContext;
})

4乾蓬、webpackAsyncContext函數(shù)

其中的關(guān)鍵函數(shù)為webpackAsyncContext,調(diào)用load('Home')時惠啄,req'./Home'__webpack_require__.o方法為

__webpack_require__.o = function (object, property) {
    return Object.prototype.hasOwnProperty.call(object, property);
};

這個方法就是判斷在變量map中有沒有key為./Home的項任内,如果沒有拋出Cannot find module './Home'的錯誤撵渡。有執(zhí)行__webpack_require__.e方法,參數(shù)為Home死嗦。

5趋距、webpack_require.e方法

var installedChunks = {
    "app": 0
}
__webpack_require__.p = "/";
function jsonpScriptSrc(chunkId) {
    return __webpack_require__.p + "js/" + ({ "Home": "Home" }[chunkId] || chunkId) +
    "." + { "Home": "37ee624e" }[chunkId] + ".js"
}
__webpack_require__.e = function requireEnsure(chunkId) {
    var promises = [];
    var installedChunkData = installedChunks[chunkId];
    if (installedChunkData !== 0) {
        if (installedChunkData) {
            promises.push(installedChunkData[2]);
        } else {
            var promise = new Promise(function (resolve, reject) {
                installedChunkData = installedChunks[chunkId] = [resolve, reject];
            });
            promises.push(installedChunkData[2] = promise);
            var script = document.createElement('script');
            var onScriptComplete;
            script.charset = 'utf-8';
            script.timeout = 120;
            if (__webpack_require__.nc) {
                script.setAttribute("nonce", __webpack_require__.nc);
            }
            script.src = jsonpScriptSrc(chunkId);
            var error = new Error();
            onScriptComplete = function (event) {
                // 避免IE內(nèi)存泄漏。
                script.onerror = script.onload = null;
                clearTimeout(timeout);
                var chunk = installedChunks[chunkId];
                if (chunk !== 0) {
                    if (chunk) {
                        var errorType = event && 
                        (event.type === 'load' ? 'missing' : event.type);
                        var realSrc = event && event.target && event.target.src;
                        error.message = 'Loading chunk ' + chunkId 
                        + ' failed.\n(' + errorType + ': ' + realSrc + ')';
                        error.name = 'ChunkLoadError';
                        error.type = errorType;
                        error.request = realSrc;
                        chunk[1](error);
                    }
                    installedChunks[chunkId] = undefined;
                }
            };
            var timeout = setTimeout(function () {
                onScriptComplete({ type: 'timeout', target: script });
            }, 120000);
            script.onerror = script.onload = onScriptComplete;
            document.head.appendChild(script);
        }
    }
    return Promise.all(promises);
};

__webpack_require__.e方法是實現(xiàn)懶加載的核心越除,在這個方法里面處理了三件事情节腐。

  • 使用JSONP模式加載路由對應(yīng)的js文件靠欢,也可以稱為chunk。
  • 設(shè)置chunk加載的三種狀態(tài)并緩存在installedChunks中铜跑,防止chunk重復加載。
  • 處理chunk加載超時和加載出錯的場景骡澈。

chunk加載的三種狀態(tài)

  • installedChunks[chunkId]0锅纺,代表該chunk已經(jīng)加載完畢。
  • installedChunks[chunkId]undefined肋殴,代表該chunk加載失敗囤锉、加載超時、從未加載過护锤。
  • installedChunks[chunkId]Promise對象官地,代表該chunk正在加載。

chunk加載超時處理

script.timeout = 120;
var timeout = setTimeout(function () {
    onScriptComplete({ type: 'timeout', target: script });
}, 120000);

script.timeout = 120代表該chunk加載120秒后還沒加載完畢則超時烙懦。
setTimeout設(shè)置個120秒的計時器驱入,在120秒后執(zhí)行onScriptComplete({ type: 'timeout', target: script })

在看一下onScriptComplete函數(shù)

var onScriptComplete = function (event) {
    // 避免IE內(nèi)存泄漏氯析。
    script.onerror = script.onload = null;
    clearTimeout(timeout);
    var chunk = installedChunks[chunkId];
    if (chunk !== 0) {
        if (chunk) {
            var errorType = event && (event.type === 'load' ? 'missing' : event.type);
            var realSrc = event && event.target && event.target.src;
            error.message = 'Loading chunk ' + chunkId 
            + ' failed.\n(' + errorType + ': ' + realSrc + ')';
            error.name = 'ChunkLoadError';
            error.type = errorType;
            error.request = realSrc;
            chunk[1](error);
        }
        installedChunks[chunkId] = undefined;
    }
};

此時chunkId為Home亏较,加載是Home.js,代碼是

(window["webpackJsonp"] = window["webpackJsonp"] || []).push([["Home"],{
    "bb51":(function(module, __webpack_exports__, __webpack_require__){
        //省略...
    })
}]))

在前面有提到window["webpackJsonp"]的push方法被webpackJsonpCallback函數(shù)攔截了,如果Home.js加載成功會自動執(zhí)行掩缓,隨后會執(zhí)行webpackJsonpCallback函數(shù)雪情,其中有installedChunks[chunkId] = 0;會把installedChunks['Home']的值置為0。

也就是說,如果Home.js加載超時了你辣,就不能執(zhí)行巡通,就不能將installedChunks['Home']的值置為0,所以此時installedChunks['Home']的值還是Promise對象舍哄。那么就會進入以下代碼執(zhí)行宴凉,最后chunk[1](error)將錯誤拋出去。

var chunk = installedChunks[chunkId];
if(chunk!==0){
    if(chunk){
        var errorType = event && (event.type === 'load' ? 'missing' : event.type);
        var realSrc = event && event.target && event.target.src;
        error.message = 'Loading chunk ' + chunkId 
        + ' failed.\n(' + errorType + ': ' + realSrc + ')';
        error.name = 'ChunkLoadError';
        error.type = errorType;
        error.request = realSrc;
        chunk[1](error);
    }
}

chunk[1]其實就是reject函數(shù)蠢熄,在以下代碼中給它賦值的跪解。

var promise = new Promise(function (resolve, reject) {
    installedChunkData = installedChunks[chunkId] = [resolve, reject];
});

chunk加載失敗處理

加載失敗分為兩種情況,一是Home.js資源加載失敗签孔,二是資源加載成功了叉讥,但是執(zhí)行Home.js里面代碼出錯了導致失敗,所以chunk加載失敗處理的代碼要這么寫

script.onerror = script.onload = onScriptComplete;

后面處理的方式和處理加載超時的一樣饥追。

__webpack_require__.e最后返回是一個Promise對象图仓。回到webpackAsyncContext函數(shù)中

return __webpack_require__.e(ids[1]).then(function () {
    return __webpack_require__(id);
});

__webpack_require__.e(ids[1])執(zhí)行成功后但绕,執(zhí)行__webpack_require__(id);救崔,此時id為bb51惶看。那么又回到__webpack_require__函數(shù)中了。在前面提過__webpack_require__函數(shù)的作用就是執(zhí)行module六孵。id為bb51的nodule是在Home.js內(nèi)纬黎,在webpackJsonpCallback函數(shù)有以下代碼

function webpackJsonpCallback(data) {
    var moreModules = data[1];
    for (moduleId in moreModules) {
        if (Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {
            modules[moduleId] = moreModules[moduleId];
        }
    }
}

五、分析Home.js

Home.js

(window["webpackJsonp"] = window["webpackJsonp"] || []).push([["Home"],{
    "bb51":(function(module, __webpack_exports__, __webpack_require__){
        //省略...
    })
}]))

可以看出moreModules就是{"bb51":(function(module, __webpack_exports__, __webpack_require__){})},

循環(huán)moreModules劫窒,把Home.js里面的module緩存到app.js里面的modules中本今。

再看__webpack_require__函數(shù)中有這段代碼

modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);

這樣就執(zhí)行了Home.js里面的module,在module里面有渲染頁面的一系列的方法主巍,就把Home.vue這個路由組件頁面渲染出來了冠息。

到這里路由組件懶加載的整個流程就結(jié)束了,也詳細介紹了怎么加載chunk和怎么執(zhí)行module孕索。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末逛艰,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子搞旭,更是在濱河造成了極大的恐慌散怖,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,525評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件选脊,死亡現(xiàn)場離奇詭異杭抠,居然都是意外死亡,警方通過查閱死者的電腦和手機恳啥,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,203評論 3 395
  • 文/潘曉璐 我一進店門偏灿,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人钝的,你說我怎么就攤上這事翁垂。” “怎么了硝桩?”我有些...
    開封第一講書人閱讀 164,862評論 0 354
  • 文/不壞的土叔 我叫張陵沿猜,是天一觀的道長。 經(jīng)常有香客問我碗脊,道長啼肩,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,728評論 1 294
  • 正文 為了忘掉前任衙伶,我火速辦了婚禮祈坠,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘矢劲。我一直安慰自己赦拘,他們只是感情好,可當我...
    茶點故事閱讀 67,743評論 6 392
  • 文/花漫 我一把揭開白布芬沉。 她就那樣靜靜地躺著躺同,像睡著了一般阁猜。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上蹋艺,一...
    開封第一講書人閱讀 51,590評論 1 305
  • 那天剃袍,我揣著相機與錄音,去河邊找鬼捎谨。 笑死笛园,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的侍芝。 我是一名探鬼主播,決...
    沈念sama閱讀 40,330評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼埋同,長吁一口氣:“原來是場噩夢啊……” “哼州叠!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起凶赁,我...
    開封第一講書人閱讀 39,244評論 0 276
  • 序言:老撾萬榮一對情侶失蹤咧栗,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后虱肄,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體致板,經(jīng)...
    沈念sama閱讀 45,693評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,885評論 3 336
  • 正文 我和宋清朗相戀三年咏窿,在試婚紗的時候發(fā)現(xiàn)自己被綠了斟或。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,001評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡集嵌,死狀恐怖萝挤,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情根欧,我是刑警寧澤怜珍,帶...
    沈念sama閱讀 35,723評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站凤粗,受9級特大地震影響酥泛,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜嫌拣,卻給世界環(huán)境...
    茶點故事閱讀 41,343評論 3 330
  • 文/蒙蒙 一柔袁、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧亭罪,春花似錦瘦馍、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,919評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽燥筷。三九已至,卻和暖如春院崇,著一層夾襖步出監(jiān)牢的瞬間肆氓,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,042評論 1 270
  • 我被黑心中介騙來泰國打工底瓣, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留谢揪,地道東北人。 一個月前我還...
    沈念sama閱讀 48,191評論 3 370
  • 正文 我出身青樓捐凭,卻偏偏與公主長得像拨扶,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子茁肠,可洞房花燭夜當晚...
    茶點故事閱讀 44,955評論 2 355

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