Vue 3 常見面試題匯總【持續更新中】

前言

最近兩年許多大廠都在實行“降本增效”、“優化組織架構”,然后 “為社會輸送了大量人才”,今年(2023)更是不容易,一些外資企業也陸續撤離,各行各業訂單大量減少,業務大量裁撤,導致工作崗位大幅度減少。程序員們不得不重新找工作,有人回到了老家做起了生意,有人送起了外賣,有人跑起了網約車。當然也有不少人選擇繼續堅持,不管做出了什么選擇,各位的選擇都值得被尊重。在這個時代環境下,大家都是好樣的。

而我選擇了繼續堅持,最近也在找工作的路上,總結了一些面試題,如果你的技術棧是 Vue,或者需要從 React 轉到 Vue,希望這篇面試題能幫到你。

談談你對 Vue 的理解?為什么選擇 Vue?

根據官方說法,Vue 是一套用于構建用戶界面的漸進式框架。Vue 的設計受到了 MVVM 的啟發。Vue 的兩個核心是數據驅動組件系統

我為什么使用 Vue,有以下幾個原因:

  • Vue 對于前端初學者比較友好。一個 Vue 文件的結構和原生 HTML 保持了高度相似,分為模板、腳本和樣式,這種寫法可以讓前端初學者快速入門。

  • 其次,就是 Vue 提供一套高效的響應式系統用于更新 DOM,可以讓開發者專注于處理業務。

  • 最后,Vue 提供了許多 JS 定制化的操作,比如指令和是修飾符,開發者可以直接使用,幫助開發者們減少了大量時間。

什么是 MVVM,可以介紹一下嗎?

MVVM,即 Model–View–ViewModel,是一種軟件架構模式。

  • Model

    即模型,是指代表真實狀態內容的領域模型(面向對象),或指代表內容的數據訪問層(以數據為中心)。

  • View

    即視圖,是用戶在屏幕上看到的結構、布局和外觀(UI)。

  • ViewModel

    即視圖模型,是暴露公共屬性和命令的視圖的抽象。用于把 ModelView 關聯起來。ViewModel 負責把 Model 的數據同步到 View 顯示出來,還負責把 View 的修改同步回 Model

MVVM

在 MVVM 架構下,ViewModel 之間并沒有直接的聯系,而是通過 ViewModel 進行交互,ModelViewModel 之間的交互是雙向的,View 數據的變化會同步到 Model 中,而 Model 數據的變化也會立即反應到 View 上。

因此開發者只需關注業務邏輯,不需要手動操作 DOM,不需要關注數據狀態的同步問題,復雜的數據狀態維護完全由 MVVM 來統一管理。

Vue 響應式系統的原理

Vue 實現響應式主要是采用數據劫持結合發布者-訂閱者模式的方式。具體實現就是整合 Observer,Compiler 和 Watcher 三者。

  • Observer

    觀察者。Vue 通過 Observer 對數據對象的所有屬性進行監聽,當把一個普通對象傳給 Vue 實例的 data 選項時,Observer 將遍歷它的所有屬性,并為其添加 gettersettergetter 將收集此屬性所有的訂閱者,setter 將在屬性發生變動的時候,重新為此屬性賦值,并通知訂閱者調用其對應的更新函數。

    在 Vue 2 中是通過 ES5 的 Object.defineProperty() 方法實現。

    在 Vue 3 中是通過 ES6 的 new Proxy() 實現的。

  • Compiler

    模板編譯器。它的作用是對每個元素節點的指令 v- 和模板語法 {{}} 進行掃描,替換對應的真實數據,或綁定相應的事件函數。

  • Watcher

    發布者/訂閱者。Watcher 作為連接 Observer 和 Compiler 的橋梁,能夠訂閱并收到每個屬性變動的通知,然后執行相應的回調函數。Compiler 在編譯時通過 Watcher 綁定對應的數據更新回調函數,Observer 在監聽到數據變化時執行此回調。在 Observer 中,Watcher 就是訂閱者,在 Compiler 中,Watcher 就是發布者。

為什么 Vue 3.x 采用了 Proxy 拋棄了 Object.defineProperty() ?

  • Proxy 可以代理任何對象,包括數組,而 Vue 2 中是通過重寫數組的以下七種方法實現的。

    • push()(將一個或多個元素添加到數組的末尾,并返回該數組的新長度)

    • pop()(移除并返回數組的最后一個元素)

    • unshift()(將一個或多個元素添加到數組的開頭,并返回該數組的新長度)

    • shift()(移除并返回數組的第一個元素)

    • splice()(刪除數組中的一個或多個元素,并將其返回)

    • sort()(對數組進行排序)

    • reverse()(對數組進行反轉)

  • Proxy 可以直接監聽整個對象而非屬性,而 Object.defineProperty() 只能先遍歷對象屬性再去進行監聽。相比之下 Proxy 更加簡潔,更加高效,更加安全。

  • Proxy 返回的是一個新對象,我們可以只操作新的對象達到目的。

    const cat = {
      name: "Tom",
    }
    
    const myCat = new Proxy(cat, {
      get(target, property) {
        console.log(`我的 ${property} 被讀取了`)
        return property in target ? target[property] : undefined
      },
      set(target, property, value) {
        console.log(`我的 ${property} 被設置成了 ${value}`)
        target[property] = value
        return true
      },
    })
    
    myCat.name // expected output: 我被讀取了:name
    myCat.name = "Kitty" // expected output: 我的 name 被設置成了 Kitty
    
  • Object.defineProperty() 的本質是在一個對象上定義一個新屬性,或者修改一個現有屬性。

    const cat = {
      name: "Tom",
    }
    
    Object.defineProperty(cat, "name", {
      get() {
        console.log(`我被讀取了`)
      },
      set(value) {
        console.log(`我被設置成了 ${value}`)
      },
    })
    
    cat.name // expected output: 我被讀取了
    cat.name = "Kitty" // expected output: 我被設置成了 Kitty
    
  • 而 Proxy 天生用于代理一個對象,它有 13 種基本操作的攔截方法,是 Object.defineProperty() 不具備的。

    • apply()(攔截函數的調用)

    • construct()(攔截構造函數的調用)

    • defineProperty()(攔截屬性的定義)

    • deleteProperty()(攔截屬性的刪除)

    • get()(攔截對象屬性的讀取)

    • getOwnPropertyDescriptor()(攔截對象屬性的描述)

    • getPrototypeOf()(攔截對象的原型)

    • has()(攔截對象屬性的檢查)

    • isExtensible()(攔截對象是否可擴展的檢查)

    • ownKeys()(攔截對象的屬性列表)

    • preventExtensions()(攔截對象是否可擴展的設置)

    • set()(攔截對象屬性的設置)

    • setPrototypeOf()(攔截對象的原型的設置)

Vue 是如何實現數據雙向綁定的?v-model 的原理?

Vue 組件可以通過使用 v-model 指令以實現雙向綁定。v-model 是 vue 的一個語法糖,它用于監聽數據的改變并將數據更新。以 input 元素為例:

<el-input v-model="foo" />

其實就等價于

<input :value="searchText" @input="searchText = $event.target.value" />

如何在組件中實現 v-model ?

在 Vue 2 組件中實現 v-model,只需定義 model 屬性即可。

export default {
  model: {
    prop: "value", // 屬性
    event: "input", // 事件
  },
}

在 Vue 3 組合式 API 實現 v-model,需要定義 modelValue 參數,和 emits 方法。

defineProps({
  modelValue: { type: String, default: "" },
})

const emits = defineEmits(["update:modelValue"])

function onInput(val) {
  emits("update:modelValue", val)
}

當數據改變時,Vue 是如何更新 DOM 的?(Diff 算法和虛擬 DOM)

當我們修改了某個數據時,如果直接重新渲染到真實 DOM,開銷是很大的。Vue 為了減少開銷和提高性能采用了 Diff 算法。當數據發生改變時,Observer 會通知所有 WatcherWatcher 就會調用 patch() 方法(Diff 的具體實現),把變化的內容更新到真實的 DOM,俗稱打補丁

Diff 算法會對新舊節點進行同層級比較,當兩個新舊節點是相同節點的時候,再去比較他們的子節點(如果是文本則直接更新文本內容),逐層比較然后找到最小差異部分,進行 DOM 更新。如果不是相同節點,則刪除之前的內容,重新渲染。

逐層比較

patch() 方法先根據真實 DOM 生成一顆虛擬 DOM,保存到變量 oldVnode,當某個數據改變后會生成一個新的 Vnode,然后 VnodeoldVnode 進行對比,發現有不一樣的地方就直接修改在真實 DOM 上,最后再返回新節點作為下次更新的 oldVnode

什么是虛擬 DOM?有什么用?

虛擬 DOM(Virtual DOM)就是將真實 DOM 的主要數據抽取出來,并以對象的形式表達,用于優化 DOM 操作。虛擬 DOM 的主要目的是提高性能和減少實際 DOM 操作的次數,從而改善用戶界面的渲染速度和響應性。

比如真實 DOM 如下:

<div id="hello">
  <h1>123</h1>
</div>

對應的虛擬 DOM 就是(偽代碼):

const vnode = {
  type: "div",
  props: {
    id: "hello",
  },
  children: [
    {
      type: "h1",
      innerText: "123",
    },
  ],
}

Vue 中的 key 有什么用?

  • 在 Vue 中,key 被用來作為 VNode 的唯一標識。

  • key 主要用在虛擬 DOM Diff 算法,在新舊節點對比時作為識別 VNode 的一個線索。如果新舊節點中提供了 key,能更快速地進行比較及復用。反之,Vue 會盡可能復用相同類型元素。

    <ul>
      <li v-for="item in items" :key="item.id">{{ item.name }}</li>
    </ul>
    
  • 手動改變 key 值,可以強制 DOM 進行重新渲染。

    <transition>
      <span :key="text">{{ text }}</span>
    </transition>
    

watch 和 computed 分別是做什么的?有何區別?

watch 和 computed 都可以用于監聽數據,區別是使用場景不同,watch 用于監聽一個數據,當數據改變時,可以執行傳入的回調函數:

<script setup>
  import { reactive, watch } from "vue"

  const state = reactive({ count: 0 })
  watch(
    () => state.count,
    (count, prevCount) => {
      // do something
    }
  )
</script>

computed 用于返回一個新的數據,在 Vue 3.x 中會返回一個只讀的響應式 ref 對象。但是可以接受一個帶有 get 和 set 函數的對象來創建一個可寫的 ref 對象。

<script setup>
  import { reactive, computed } from "vue"

  const state = reactive({ numA: 1, numB: 2 })
  const plusOne = computed(() => state.numA + state.numB)

  console.log(plusOne.value) // 3
</script>

有些人會提到 computed 支持緩存,不支持異步,也是和 watch 的區別。

但這里要告訴大家的是,computed 本身的設計就是為了計算,而非異步的獲取一個數據,詳情請參考官網

至于緩存,這同樣屬于 computed 的特性,它支持緩存,這是和調用普通函數的區別,而不應該和 watch 進行比較,watch 本身用于監聽數據變化,在根本上不存在緩存的概念。

Vue 3.x 帶來了哪些新的特性和性能方面的提升?

  1. 引入了 Composition API(組合式 API)。允許開發者更靈活地組織和重用組件邏輯。它使用函數而不是選項對象來組織組件的代碼,使得代碼更具可讀性和維護性。

  2. 多根組件。可以直接在 template 中使用多個根級別的元素,而不需要額外的包裝元素。這樣更方便地組織組件的結構。

  3. 引入了 Teleport(傳送)。可以將組件的內容渲染到指定 DOM 節點的新特性。一般用于創建全局彈窗和對話框等組件。

  4. 響應式系統升級。從 defineProperty 升級到 ES2015 原生的 Proxy,不需要初始化遍歷所有屬性,就可以監聽新增和刪除的屬性。

  5. 編譯優化。重寫了虛擬 DOM,提升了渲染速度。diff 時靜態節點會被直接跳過。

  6. 源碼體積優化。移除了一些非必要的特性,如 filter,一些新增的模塊也將會被按需引入,減小了打包體積。

  7. 打包優化。更強的 Tree Shaking,可以過濾不使用的模塊,沒有使用到的組件,比如過渡(transition)組件,則打包時不會包含它。

Vue 3 移除了哪些特性

  • 移除了過濾器 filter,可以使用 computed 或函數代替

    filter 在 Vue 2 的用法:

    <template>
      <p>{{ accountBalance | currencyUSD }}</p>
    </template>
    
    <script>
      export default {
        data() {
          return {
            accountBalance: "99",
          }
        },
        filters: {
          currencyUSD(value) {
            return "$" + value
          },
        },
      }
    </script>
    
  • 移除了 .native .sync 修飾符

  • 移除了 $listeners

  • 移除了 EventBus 的相關屬性:on,off 和 $once

  • 移除了 $children,可以使用 ref 代替

  • ...

Vue 3 對 diff 算法進行了哪些優化

在 Vue 2 中,每當數據發生變化時,Vue 會創建一個新的虛擬 DOM 樹,并對整個虛擬 DOM 樹進行遞歸比較,即使其中大部分內容是靜態的,最后再找到不同的節點,然后進行更新。

Vue 3 引入了靜態標記的概念,通過靜態標記,Vue 3 可以將模板中的靜態內容和動態內容區分開來。這樣,在更新過程中,Vue 3 只會關注動態部分的比較,而對于靜態內容,它將跳過比較的步驟,從而避免了不必要的比較,提高了性能和效率。

<div>
  <!-- 需靜態提升 -->
  <div>foo</div>
  <!-- 需靜態提升 -->
  <div>bar</div>
  <div>{{ dynamic }}</div>
</div>

Vue 實例的生命周期鉤子都有哪些?

生命周期鉤子是指一個組件實例從創建到卸載(銷毀)的全過程,例如,設置數據監聽、編譯模板、將實例掛載到 DOM 并在數據變化時更新 DOM 等。在這個過程中會運行一些叫做生命周期鉤子的函數,從而可以使開發者們在不同階段處理不同的業務。

Vue 2 和 Vue 3 選項式 API 的鉤子大致是一樣的,有以下鉤子:

  • beforeCreate

    實例初始化之前,$eldata 都為 undefined

  • created

    實例創建完成,data 已經綁定。但 $el 不可用。

  • beforeMount

    <template>data 生成虛擬 DOM 節點,可以訪問到 $el,但還沒有渲染到 html 上。

  • mounted

    實例掛載完成,渲染到 html 頁面中。

  • beforeUpdate

    data 更新之前,虛擬 DOM 重新渲染之前。

  • updated

    由于 data 更新導致的虛擬 DOM 重新渲染之后。

  • beforeDestroy(Vue 2) | beforeUnmount(Vue 3)

    實例銷毀之前(實例仍然可用)。

  • destroyed(Vue 2) | beforeUnmount(Vue 3)

    實例銷毀之后。所有的事件監聽器會被移除,所有的子實例也會被銷毀,但 DOM 節點依舊存在。該鉤子在服務器端渲染期間不被調用。

  • activated

    keep-alive 專用,實例被激活時調用。

  • deactivated

    keep-alive 專用,實例被移除時調用。

  • errorCaptured

    在捕獲了后代組件傳遞的錯誤時調用。

第一次頁面加載會觸發這四個鉤子:

  • beforeCreate

  • created

  • beforeMount

  • mounted

Vue 3 組合式 API 有以下鉤子:

  • onBeforeMount()

    在組件被掛載之前被調用。

  • onMounted()

    在組件掛載完成后執行。

  • onBeforeUpdate()

    在組件即將因為響應式狀態變更而更新其 DOM 樹之前調用。

  • onUpdated()

    在組件因為響應式狀態變更而更新其 DOM 樹之后調用。

  • onBeforeUnmount()

    在組件實例被卸載之前調用。

  • onUnmounted()

    在組件實例被卸載之后調用。相當于 Vue 2 的 destroyed

  • onErrorCaptured()

    在捕獲了后代組件傳遞的錯誤時調用。

  • onRenderTracked()

    當組件渲染過程中追蹤到響應式依賴時調用。只在開發環境生效。

  • onRenderTriggered()

    當響應式依賴的變更觸發了組件渲染時調用。只在開發環境生效。

  • onActivated()

    keep-alive 專用,當組件被插入到 DOM 中時調用。

  • onDeactivated()

    keep-alive 專用,當組件從 DOM 中被移除時調用。

  • onServerPrefetch()

    在組件實例在服務器上被渲染之前調用。只在 SSR 模式下生效。

nextTick 的使用場景和原理

使用場景

nextTick 是在下次 DOM 更新循環結束之后執行的一個方法。一般在修改數據之后使用這個方法操作更新后的 DOM。

export default {
  data() {
    return {
      message: "Hello Vue!",
    }
  },
  methods: {
    example() {
      // 修改數據
      this.message = "changed"
      // DOM 尚未更新
      this.$nextTick(() => {
        // DOM 現在更新了
        console.log("DOM 現在更新了")
      })
    },
  },
}

原理

在 Vue2 當中,nextTick 可以理解為就是收集異步任務到隊列當中并且開啟異步任務去執行它們。它可以同時收集組件渲染的任務,以及用戶手動放入的任務。組件渲染的任務是由 watcher 的 update 觸發,并且將回調函數包裝為異步任務,最后推到 nextTick 的隊列里,等待執行。

而在 Vue3 當中,nextTick 則是利用 promise 的鏈式調用,將用戶放入的回調放在更新視圖之后的 then 里面調用,用戶調用多少次 nextTick,就接著多少個 then。

為什么 Vue 組件中的 data 必須是函數?

因為在 Vue 中組件是可以被復用的,組件復用其實就是創建多個 Vue 實例,實例之間共享 prototype.data 屬性,當 data 的值引用的是同一個對象時,改變其中一個就會影響其他組件,造成互相污染,而改用函數的形式將數據 return 出去,則每次復用都是嶄新的對象。

這里我們舉個例子:

function Component() {}

Component.prototype.data = {
  name: "vue",
  language: "javascript",
}

const A = new Component()
const B = new Component()

A.data.language = "typescript"

console.log(A.data) // { name: 'vue', language: 'typescript' }
console.log(B.data) // { name: 'vue', language: 'typescript' }

此時,A 和 B 的 data 都指向了同一個內存地址,language 都變成了 'typescript'。

我們改成函數式的寫法,就不會有這樣的問題了。

function Component() {
  this.data = this.data()
}

Component.prototype.data = function () {
  return { name: "vue", language: "javascript" }
}

const A = new Component()
const B = new Component()

A.data.language = "typescript"

console.log(A.data) // { name: 'vue', language: 'typescript' }
console.log(B.data) // { name: 'vue', language: 'javascript' }

所以組件的 data 選項必須是一個函數,該函數返回一個獨立的拷貝,這樣就不會出現數據相互污染的問題。

Vue 組件之間如何通信?

傳送門:Vue 3 組件之間如何通信

Vue 項目中做過哪些性能優化?

  • UI 庫按需加載,減小打包體積,以 ElementUI 為例:

    // main.js
    import { Button, Select } from "element-ui"
    
    Vue.use(Button)
    Vue.use(Select)
    
  • 路由按需加載

    // router.js
    export default new VueRouter({
      routes: [
        { path: "/", component: () => import("@/components/Home") },
        { path: "/about", component: () => import("@/components/About") },
      ],
    })
    
  • 組件銷毀后把同時銷毀全局變量和移除事件監聽和清除定時器,防止內存泄漏

    beforeDestroy() {
      clearInterval(this.timer)
      window.removeEventListener('resize', this.handleResize)
    },
    
  • 合理使用 v-if 和 v-show 使用

Vue 和 React 的區別?

傳送門:對比其他框架 — Vue.js

Composition API(組合式 API)與 Options API(選項式 API)有什么區別?

  • Options API 會將組件中的同一邏輯相關的代碼拆分到不同選項,比如 datapropsmethods 等,而使用 Composition API 較為靈活,開發者可以將同一個邏輯的相關代碼放在一起。

  • Composition API 通過 Vue 3.x 新增的 setup 選項進行使用,該選項會在組件創建之前執行,第一個參數 props,第二個參數 context,return 的所有內容都會暴露給組件的其余部分 (計算屬性、方法、生命周期鉤子等等) 以及組件的模板。

  • Composition API 上的生命周期鉤子與 Options API 基本相同,但需要添加前綴 on,比如 onMountedonUpdated 等。

v-for 和 v-if 可以同時使用嗎?

可以同時使用,但不推薦,具體原因參考官方說明

在 Vue 3 中,當 v-if 和 v-for 同時存在于一個節點上時,v-if 比 v-for 的優先級更高,此時 v-if 無法訪問 v-for 中的對象,在 Vue 2 中相反。

當確實需要條件遍歷渲染的話,有以下幾個方法:

  • 使用數組的 fitler 過濾
<li v-for="todo in todos.filter(todo => !todo.isDone)">{{ todo.name }}</li>

使用數組的 filter 的方法可以提前對不需要的數據進行過濾,根源上解決這個問題。

  • 使用 v-show 代替
<li v-for="todo in todos" v-show="!todo.isDone">{{ todo.name }}</li>

v-show 和 v-if 都可以用于隱藏某個元素,但 v-if 用于決定是否渲染,而 v-show 則使用 display 屬性決定是否顯示。此時可以避免 v-if 和 v-for 同時使用造成的的渲染問題。

  • 添加額外的標簽
<template v-for="todo in todos">
  <li v-if="!todo.isComplete">{{ todo.name }}</li>
</template>

添加額外的標簽,根據層級的不同,可以自己決定 v-if 和 v-for 的優先級,這種方法更加靈活也更容易理解,但會有更深的代碼結構。

總結

這篇不總結了,就祝大家都能找到一份滿意的工作。


參考資料:

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,837評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,196評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,688評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,654評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,456評論 6 406
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,955評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,044評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,195評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,725評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,608評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,802評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,318評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,048評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,422評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,673評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,424評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,762評論 2 372

推薦閱讀更多精彩內容