自己動(dòng)手寫一個(gè)簡易版本Vue(一)

Vue由于其高效的性能和靈活入門簡單、輕量的特點(diǎn)下變得火熱。在當(dāng)今前端越來越普遍的使用,今天來動(dòng)手寫一下Vue

主要實(shí)現(xiàn)
1.構(gòu)建Vue實(shí)例查找指令和模板
2.數(shù)據(jù)驅(qū)動(dòng)界面更新
3.界面驅(qū)動(dòng)數(shù)據(jù)更新
4.事件相關(guān)指令
5.Vuex基本實(shí)現(xiàn)
6.Vue-Router基本實(shí)現(xiàn)

一溺蕉、構(gòu)建Vue實(shí)例

大家至此應(yīng)該已經(jīng)在項(xiàng)目中使用Vue,在此就不過多講解Vue使用悼做,直接從0開始盡量模仿Vue的語法疯特,首先我們看一下簡單的Vue案例

<body>
<div id="app">
    <input type="text" v-model="name">
    <p>{{ name }}</p>
    <p>{{age}}</p>
    <ul>
        <li>6</li>
        <li>6</li>
        <li>6</li>
    </ul>
</div>
<!--1.下載導(dǎo)入Vue.js-->
<script src="js/vue.js"></script>
<script>
    // 2.創(chuàng)建一個(gè)Vue的實(shí)例對(duì)象
    let vue = new Vue({
        // 3.告訴Vue的實(shí)例對(duì)象, 將來需要控制界面上的哪個(gè)區(qū)域
        el: '#app',
        // 4.告訴Vue的實(shí)例對(duì)象, 被控制區(qū)域的數(shù)據(jù)是什么
        data: {
            name: "張三",
            age : '18'
        }
    });
    console.log(vue.$el);
    console.log(vue.$data);
</script>
</body>
  1. 通過觀察我們得知,要想使用Vue必須先創(chuàng)建Vue的實(shí)例, 創(chuàng)建Vue的實(shí)例通過new來創(chuàng)建, 所以說明Vue是一個(gè)類
    所以我們要想使用自己的Vue,就必須定義一個(gè)名稱叫做Vue的類

  2. 只要?jiǎng)?chuàng)建好了Vue的實(shí)例, Vue就會(huì)根據(jù)指定的區(qū)域和數(shù)據(jù), 去編譯渲染這個(gè)區(qū)域
    所以我們需要在自己編寫的Vue實(shí)例中拿到數(shù)據(jù)和控制區(qū)域, 去編譯渲染這個(gè)區(qū)域
    注意點(diǎn): 創(chuàng)建Vue實(shí)例的時(shí)候指定的控制區(qū)域可以是一個(gè)ID名稱, 也可以是一個(gè)Dom元素
    注意點(diǎn): Vue實(shí)例會(huì)將傳遞的控制區(qū)域和數(shù)據(jù)都綁定到創(chuàng)建出來的實(shí)例對(duì)象上
    $el/$data

  3. 來到我們自己創(chuàng)建的js文件肛走,簡單命名為lue.js

Lue.js文件
class Lue {
    constructor(options){
        // 1.保存創(chuàng)建時(shí)候傳遞過來的數(shù)據(jù)
        if(this.isElement(options.el)){
            this.$el = options.el;
        }else{
            this.$el = document.querySelector(options.el);
        }
        this.$data = options.data;
        // 2.根據(jù)指定的區(qū)域和數(shù)據(jù)去編譯渲染界面
        if(this.$el){
            new Compiler(this)
        }
    }
    // 判斷是否是一個(gè)元素
    isElement(node){
        return node.nodeType === 1;
    }
}
class Compiler {
    constructor(vm){
        this.vm = vm;
    }
}

首先在第一步判斷傳遞的數(shù)據(jù)是否是元素漓雅,如果是則直接賦值給this.$el,不是則通過選擇器查找一下再賦值朽色。然后再把外面?zhèn)鬟f的data賦值給this.$data邻吞。
接下來在根據(jù)指定數(shù)據(jù),指定的區(qū)域葫男,去編譯渲染到界面中吃衅,專門定義一個(gè)Compiler類用于編譯渲染。

接下來就可以使用了腾誉,只需要導(dǎo)入我們剛剛創(chuàng)建的Lue.js文件,將Vue替換Lue即可

 let vue = new Lue({
       el: '#app',
       data: {
            name: "張三",
            age : '18'
        }
    });
    console.log(vue.$el);
    console.log(vue.$data);

二峻呕、提取元素到內(nèi)存

在后面實(shí)現(xiàn)數(shù)據(jù)驅(qū)動(dòng)界面時(shí)利职,數(shù)據(jù)發(fā)生改變,就替換元素瘦癌。在js每次操作dom時(shí)都會(huì)對(duì)DOM進(jìn)行一次重排猪贪,所謂重排也就是當(dāng)元素的大小,位置結(jié)構(gòu)發(fā)生變化的時(shí)候讯私,就會(huì)引起瀏覽器對(duì)當(dāng)前頁面的結(jié)構(gòu)進(jìn)行一次重新的計(jì)算热押,這是非常耗費(fèi)瀏覽器性能的西傀。
虛擬DOM的出現(xiàn)很好的解決了這一問題,而js中的文檔碎片就類似于虛擬DOM
可以使用文檔碎片(Fragment)桶癣,
類似于虛擬DOM的思想拥褂,在內(nèi)存中先開辟出一塊地方,對(duì)于所有節(jié)點(diǎn)的操作都在內(nèi)存中先完成牙寞,完成后再一次更新到頁面中饺鹃,優(yōu)化了瀏覽器的性能。

Compiler編譯渲染的類
class Compiler {
    constructor(vm){
        this.vm = vm;
        // 1.將網(wǎng)頁上的元素放到內(nèi)存中
        let fragment = this.node2fragment(this.vm.$el);
        console.log(fragment);
        // 2.利用指定的數(shù)據(jù)編譯內(nèi)存中的元素
        // 3.將編譯好的內(nèi)容重新渲染會(huì)網(wǎng)頁上
    }
    node2fragment(app){
        // 1.創(chuàng)建一個(gè)空的文檔碎片對(duì)象
        let fragment = document.createDocumentFragment();
        // 2.編譯循環(huán)取到每一個(gè)元素
        let node = app.firstChild;
        while (node){
            // 注意點(diǎn): 只要將元素添加到了文檔碎片對(duì)象中, 那么這個(gè)元素就會(huì)自動(dòng)從網(wǎng)頁上消失
            fragment.appendChild(node);
            node = app.firstChild;
        }
        // 3.返回存儲(chǔ)了所有元素的文檔碎片對(duì)象
        return fragment;
    }
}

三间雀、編譯指令和模板數(shù)據(jù)

將網(wǎng)頁中的元素放到內(nèi)存中后,接下就是利用指定的數(shù)據(jù)編譯內(nèi)存中的元素悔详。

class Compiler {
    constructor(vm){
        this.vm = vm;
        // 1.將網(wǎng)頁上的元素放到內(nèi)存中
        let fragment = this.node2fragment(this.vm.$el);
        // 2.利用指定的數(shù)據(jù)編譯內(nèi)存中的元素
        this.buildTemplate(fragment);
        // 3.將編譯好的內(nèi)容重新渲染會(huì)網(wǎng)頁上
        this.vm.$el.appendChild(fragment);
    }
    node2fragment(app){
        // 1.創(chuàng)建一個(gè)空的文檔碎片對(duì)象
        let fragment = document.createDocumentFragment();
        // 2.編譯循環(huán)取到每一個(gè)元素
        let node = app.firstChild;
        while (node){
            // 注意點(diǎn): 只要將元素添加到了文檔碎片對(duì)象中, 那么這個(gè)元素就會(huì)自動(dòng)從網(wǎng)頁上消失
            fragment.appendChild(node);
            node = app.firstChild;
        }
        // 3.返回存儲(chǔ)了所有元素的文檔碎片對(duì)象
        return fragment;
    }
    buildTemplate(fragment){
        //為了實(shí)現(xiàn)遍歷,通過解構(gòu)賦值將偽數(shù)組轉(zhuǎn)成數(shù)組
        let nodeList = [...fragment.childNodes];
        nodeList.forEach(node=>{
            // 需要判斷當(dāng)前遍歷到的節(jié)點(diǎn)是一個(gè)元素還是一個(gè)文本
            // 如果是一個(gè)元素, 我們需要判斷有沒有v-model屬性
            // 如果是一個(gè)文本, 我們需要判斷有沒有{{}}的內(nèi)容
            if(this.vm.isElement(node)){
                // 是一個(gè)元素
                this.buildElement(node);
                // 處理子元素(處理后代)
                this.buildTemplate(node);
            }else{
                // 不是一個(gè)元素
                this.buildText(node);
            }
        })
    }
    buildElement(node){
        let attrs = [...node.attributes];
        attrs.forEach(attr => {
            let {name, value} = attr; // v-model="name" / name:v-model / value:name
            if(name.startsWith('v-')){ // v-model / v-html / v-text / v-xxx
                let [_, directive] = name.split('-'); // v-model -> [v, model]
                CompilerUtil[directive](node, value, this.vm);
            }
        })
    }
    buildText(node){
        let content = node.textContent;
        let reg = /\{\{.+?\}\}/gi;
        if(reg.test(content)){
            console.log('是{{}}的文本, 需要我們處理', content);
        }
    }
}

buildTemplate方法中惹挟,通過fragment.childNodes可以拿到內(nèi)存中所有的子節(jié)點(diǎn)茄螃,然后遍歷判斷是否時(shí)元素還是文本。
但是子節(jié)點(diǎn)中可能包含多個(gè)子節(jié)點(diǎn)连锯,所以需要再次遞歸遍歷下

 // 處理子元素(處理后代) this.buildTemplate(node);

如果是元素則來到buildElement方法中归苍。在遍歷元素的屬性,name為屬性名萎庭,value為屬性值霜医。再判斷是否包含v-,包含的話代表是vue指令,取出指令賦值給directive驳规。

CompilerUtil[directive](node, value, this.vm);,調(diào)用工具對(duì)象的方法給節(jié)點(diǎn)賦值肴敛。

接著看工具CompilerUtil的實(shí)現(xiàn)

let CompilerUtil = {
    //根據(jù)屬性名稱獲取值 vm:vue實(shí)例對(duì)象,value : 指令的值吗购,(v-model = "name",值就是name)
    getValue(vm, value){
        // time.h --> [time, h] 医男,http://www.reibang.com/p/e375ba1cfc47
       return value.split('.').reduce((data, currentKey) => {
            // 第一次執(zhí)行: data=$data, currentKey=time
            // 第二次執(zhí)行: data=time, currentKey=h
            return data[currentKey];
        }, vm.$data);
    },
    /*
    node : 當(dāng)前節(jié)點(diǎn)
    value : 指令的值,(v-model = "name",值name)
    vm : lue實(shí)例對(duì)象
     */
    model: function (node, value, vm) { // value=time.h
        /*node.value = vm.$data[value]; // vm.$data[time.h] --> vm.$data[time] --> time[h]*/
        let val = this.getValue(vm, value);
        node.value = val;
    },
    html: function (node, value, vm) {
        let val = this.getValue(vm, value);
        node.innerHTML = val;
    },
    text: function (node, value, vm) {
        let val = this.getValue(vm, value);
        node.innerText = val;
    }
}

在getValue(vm, value)方法中又有注意點(diǎn)捻勉,此時(shí)我們修改下html代碼

<body>
<div id="app">
    <input type="text" v-model="name">
    <input type="text" v-model="time.h">
    <input type="text" v-model="time.m">
    <input type="text" v-model="time.s">
    <div v-html="html">abc</div>
    <div v-text="text">123</div>
    <p>{{ name }}</p>
    <p>{{age}}</p>
    <p>{{time.h}}</p>
    <p>{{name}}-{{age}}</p>
    <ul>
        <li>6</li>
        <li>6</li>
        <li>6</li>
    </ul>
</div>
<script>
    // 2.創(chuàng)建一個(gè)Vue的實(shí)例對(duì)象
    // let vue = new Vue({
    let vue = new Lue({
        // 3.告訴Vue的實(shí)例對(duì)象, 將來需要控制界面上的哪個(gè)區(qū)域
        el: '#app',
        // el: document.querySelector('#app'),
        // 4.告訴Vue的實(shí)例對(duì)象, 被控制區(qū)域的數(shù)據(jù)是什么
        data: {
            name: "張三",
            age: 18,
            time: {
                h: 11,
                m: 12,
                s: 13
            },
            html: `<div>我是div</div>`,
            text: `<div>我是div</div>`
        }
    });
</script>
</body>

在data中可能有多層數(shù)據(jù)镀梭,也就是對(duì)象類似的。

<input type="text" v-model="time.h">
<input type="text" v-model="time.m">
<input type="text" v-model="time.s">
time: {
   h: 11,
   m: 12,
   s: 13
 },

所以在CompilerUtil的model方法中僅僅使用 node.value = vm.$data[value]不行的踱启,所以抽取一個(gè)getValue方法报账,通過reduce遍歷,取出data.time.h的值

  //根據(jù)屬性名稱獲取值 vm:vue實(shí)例對(duì)象埠偿,value : 指令的值透罢,(v-model = "name",值就是name)
    getValue(vm, value){
        // time.h --> [time, h] ,http://www.reibang.com/p/e375ba1cfc47
       return value.split('.').reduce((data, currentKey) => {
            // 第一次執(zhí)行: data=$data, currentKey=time
            // 第二次執(zhí)行: data=time, currentKey=h
            return data[currentKey];
        }, vm.$data);
    },

元素中帶有v-model指令就能夠?qū)崿F(xiàn)綁定數(shù)據(jù)了冠蒋。接下來在看插值語法綁定模板數(shù)據(jù)羽圃。

    buildText(node){
        let content = node.textContent;
        //看看是否匹配{{name}}(插值語法)
        let reg = /\{\{.+?\}\}/gi;
        if(reg.test(content)){
            CompilerUtil['content'](node, content, this.vm);
        }
    }

通過正則表達(dá)式匹配{{}},匹配到后再調(diào)用CompilerUtil工具對(duì)象的content方法抖剿。

    content: function (node, value, vm) {
        // console.log(value); // {{ name }} -> name -> $data[name]
        let val = this.getContent(vm, value);
        node.textContent = val;
    }
    getContent(vm, value){
        // console.log(value); //  {{name}}-{{age}} ->張三-{{age}}  -> 張三-18
        // (.+?) 取出值
        let reg = /\{\{(.+?)\}\}/gi;
        let val = value.replace(reg, (...args) => {
            // 第一次執(zhí)行 args[1] = name
            // 第二次執(zhí)行 args[1] = age
            // console.log(args);
            return this.getValue(vm, args[1]); // 張三, 18
        });
        // console.log(val);
        return val;
    },

注意點(diǎn):可能數(shù)據(jù)是{{name}--{{age}}朽寞,那么則用replace方法依次查找替換识窿。

附上本章節(jié)lue.js代碼

let CompilerUtil = {

    /*
       node : 當(dāng)前接單
       value : 指令的值脑融,(v-model = "name",值name)
       vm : Lue實(shí)例對(duì)象
    */
    //根據(jù)屬性名稱獲取值 vm:vue實(shí)例對(duì)象喻频,value : 指令的值,(v-model = "name",值就是name)
    getValue(vm, value){
        // time.h --> [time, h]
       return value.split('.').reduce((data, currentKey) => {
            // 第一次執(zhí)行: data=$data, currentKey=time
            // 第二次執(zhí)行: data=time, currentKey=h
            return data[currentKey.trim()];
        }, vm.$data);
    },
    getContent(vm, value){
        // console.log(value); //  {{name}}-{{age}} -> 張三-{{age}}  -> 張三-18
        // (.+?) 取出值
        let reg = /\{\{(.+?)\}\}/gi;
        let val = value.replace(reg, (...args) => {
            // 第一次執(zhí)行 args[1] = name
            // 第二次執(zhí)行 args[1] = age
            // console.log(args);
            return this.getValue(vm, args[1]); // 張三, 18
        });
        // console.log(val);
        return val;
    },
    model: function (node, value, vm) {
        let val = this.getValue(vm, value);
        node.value = val;
    },
    html: function (node, value, vm) {
        let val = this.getValue(vm, value);
        node.innerHTML = val;
    },
    text: function (node, value, vm) {
        let val = this.getValue(vm, value);
        node.innerText = val;
    },
    content: function (node, value, vm) {
        // console.log(value); // {{ name }} -> name -> $data[name]
        let val = this.getContent(vm, value);
        node.textContent = val;
    }
}
class Lue {
    constructor(options){
        // 1.保存創(chuàng)建時(shí)候傳遞過來的數(shù)據(jù)
        if(this.isElement(options.el)){
            this.$el = options.el;
        }else{
            this.$el = document.querySelector(options.el);
        }
        this.$data = options.data;
        // 2.根據(jù)指定的區(qū)域和數(shù)據(jù)去編譯渲染界面
        if(this.$el){
            new Compiler(this);
        }
    }
    // 判斷是否是一個(gè)元素
    isElement(node){
        return node.nodeType === 1;
    }
}
class Compiler {
    constructor(vm){
        this.vm = vm;
        // 1.將網(wǎng)頁上的元素放到內(nèi)存中
        let fragment = this.node2fragment(this.vm.$el);
        // 2.利用指定的數(shù)據(jù)編譯內(nèi)存中的元素
        this.buildTemplate(fragment);
        // 3.將編譯好的內(nèi)容重新渲染會(huì)網(wǎng)頁上
        this.vm.$el.appendChild(fragment);
    }
    node2fragment(app){
        // 1.創(chuàng)建一個(gè)空的文檔碎片對(duì)象
        let fragment = document.createDocumentFragment();
        // 2.編譯循環(huán)取到每一個(gè)元素
        let node = app.firstChild;
        while (node){
            // 注意點(diǎn): 只要將元素添加到了文檔碎片對(duì)象中, 那么這個(gè)元素就會(huì)自動(dòng)從網(wǎng)頁上消失
            fragment.appendChild(node);
            node = app.firstChild;
        }
        // 3.返回存儲(chǔ)了所有元素的文檔碎片對(duì)象
        return fragment;
    }
    buildTemplate(fragment){
        //為了實(shí)現(xiàn)遍歷膜宋,通過結(jié)構(gòu)賦值將偽數(shù)組變?yōu)閿?shù)組
        let nodeList = [...fragment.childNodes];
        nodeList.forEach(node=>{
            // 需要判斷當(dāng)前遍歷到的節(jié)點(diǎn)是一個(gè)元素還是一個(gè)文本
            if(this.vm.isElement(node)){
                // 是一個(gè)元素
                this.buildElement(node);
                // 遞歸窿侈,處理子元素(處理后代)
                this.buildTemplate(node);
            }else{
                // 不是一個(gè)元素
                this.buildText(node);
            }
        })
    }
    buildElement(node){
        let attrs = [...node.attributes];
        attrs.forEach(attr => {
            let {name, value} = attr; // v-model="name" / name:v-model / value:name
            if(name.startsWith('v-')){ // v-model / v-html / v-text / v-xxx
                let [_, directive] = name.split('-'); // v-model -> [v, model]
                CompilerUtil[directive](node, value, this.vm);
            }
        })
    }
    buildText(node){
        let content = node.textContent;
        //看看是否匹配{{name}}(插值語法),'.+?' :至少一位數(shù)秋茫,'g':全局匹配史简,'i':是忽略大小寫
        let reg = /\{\{.+?\}\}/gi;
        if(reg.test(content)){
            CompilerUtil['content'](node, content, this.vm);
        }
    }
}

本章節(jié)就暫時(shí)結(jié)束,下一章講解利用 Object.defineProperty,和發(fā)布訂閱者模式實(shí)現(xiàn)數(shù)據(jù)驅(qū)動(dòng)界面更新

文章主要代碼參考李南江web前端課程
其他參考鏈接:
https://www.cnblogs.com/suihang/p/9491359.html
https://segmentfault.com/a/1190000016434836

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末肛著,一起剝皮案震驚了整個(gè)濱河市圆兵,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌枢贿,老刑警劉巖殉农,帶你破解...
    沈念sama閱讀 222,000評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異局荚,居然都是意外死亡超凳,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,745評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門耀态,熙熙樓的掌柜王于貴愁眉苦臉地迎上來轮傍,“玉大人,你說我怎么就攤上這事首装〈匆梗” “怎么了?”我有些...
    開封第一講書人閱讀 168,561評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵仙逻,是天一觀的道長驰吓。 經(jīng)常有香客問我,道長系奉,這世上最難降的妖魔是什么檬贰? 我笑而不...
    開封第一講書人閱讀 59,782評(píng)論 1 298
  • 正文 為了忘掉前任,我火速辦了婚禮喜最,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘庄蹋。我一直安慰自己瞬内,他們只是感情好迷雪,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,798評(píng)論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著虫蝶,像睡著了一般章咧。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上能真,一...
    開封第一講書人閱讀 52,394評(píng)論 1 310
  • 那天赁严,我揣著相機(jī)與錄音,去河邊找鬼粉铐。 笑死疼约,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的蝙泼。 我是一名探鬼主播程剥,決...
    沈念sama閱讀 40,952評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼汤踏!你這毒婦竟也來了织鲸?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,852評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤溪胶,失蹤者是張志新(化名)和其女友劉穎搂擦,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體哗脖,經(jīng)...
    沈念sama閱讀 46,409評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡瀑踢,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,483評(píng)論 3 341
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了懒熙。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片丘损。...
    茶點(diǎn)故事閱讀 40,615評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖工扎,靈堂內(nèi)的尸體忽然破棺而出徘钥,到底是詐尸還是另有隱情,我是刑警寧澤肢娘,帶...
    沈念sama閱讀 36,303評(píng)論 5 350
  • 正文 年R本政府宣布呈础,位于F島的核電站,受9級(jí)特大地震影響橱健,放射性物質(zhì)發(fā)生泄漏而钞。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,979評(píng)論 3 334
  • 文/蒙蒙 一拘荡、第九天 我趴在偏房一處隱蔽的房頂上張望臼节。 院中可真熱鬧,春花似錦、人聲如沸网缝。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,470評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽粉臊。三九已至草添,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間扼仲,已是汗流浹背远寸。 一陣腳步聲響...
    開封第一講書人閱讀 33,571評(píng)論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留屠凶,地道東北人驰后。 一個(gè)月前我還...
    沈念sama閱讀 49,041評(píng)論 3 377
  • 正文 我出身青樓,卻偏偏與公主長得像阅畴,于是被迫代替她去往敵國和親倡怎。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,630評(píng)論 2 359

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