字節前端必會面試題

常見的水平垂直方式有幾種?

//利用絕對定位,先將元素的左上角通過 top:50%和 left:50%定位到頁面的中心,然后再通過 translate 來調整元素的中心點到頁面的中心。該方法需要考慮瀏覽器兼容問題。
.parent {
    position: relative;
}

.child {
    position: absolute;
    left: 50%;
    top: 50%;
    transform: translate(-50%,-50%);
}
//利用絕對定位,設置四個方向的值都為 0,并將 margin 設置為 auto,由于寬高固定,因此對應方向實現平分,可以實現水平和垂直方向上的居中。該方法適用于盒子有寬高的情況:
.parent {
    position: relative;
}

.child {
    position: absolute;
    top: 0;
    bottom: 0;
    left: 0;
    right: 0;
    margin: auto;
}
//利用絕對定位,先將元素的左上角通過 top:50%和 left:50%定位到頁面的中心,然后再通過 margin 負值來調整元素的中心點到頁面的中心。該方法適用于盒子寬高已知的情況
.parent {
    position: relative;
}

.child {
    position: absolute;
    top: 50%;
    left: 50%;
    margin-top: -50px;     /* 自身 height 的一半 */
    margin-left: -50px;    /* 自身 width 的一半 */
}
//使用 flex 布局,通過 align-items:center 和 justify-content:center 設置容器的垂直和水平方向上為居中對齊,然后它的子元素也可以實現垂直和水平的居中。該方法要**考慮兼容的問題**,該方法在移動端用的較多:
.parent {
    display: flex;
    justify-content:center;
    align-items:center;
}
//另外,如果父元素設置了flex布局,只需要給子元素加上`margin:auto;`就可以實現垂直居中布局
.parent{
    display:flex;
}
.child{
    margin: auto;
}

----問題知識點分割線----

代碼輸出結果

const promise = new Promise((resolve, reject) => {
  console.log(1);
  setTimeout(() => {
    console.log("timerStart");
    resolve("success");
    console.log("timerEnd");
  }, 0);
  console.log(2);
});
promise.then((res) => {
  console.log(res);
});
console.log(4);

輸出結果如下:

1
2
4
timerStart
timerEnd
success

代碼執行過程如下:

  • 首先遇到Promise構造函數,會先執行里面的內容,打印1
  • 遇到定時器steTimeout,它是一個宏任務,放入宏任務隊列;
  • 繼續向下執行,打印出2;
  • 由于Promise的狀態此時還是pending,所以promise.then先不執行;
  • 繼續執行下面的同步任務,打印出4;
  • 此時微任務隊列沒有任務,繼續執行下一輪宏任務,執行steTimeout
  • 首先執行timerStart,然后遇到了resolve,將promise的狀態改為resolved且保存結果并將之前的promise.then推入微任務隊列,再執行timerEnd
  • 執行完這個宏任務,就去執行微任務promise.then,打印出resolve的結果。

----問題知識點分割線----

函數柯里化

柯里化(currying) 指的是將一個多參數的函數拆分成一系列函數,每個拆分后的函數都只接受一個參數。

對于已經柯里化后的函數來說,當接收的參數數量與原函數的形參數量相同時,執行原函數; 當接收的參數數量小于原函數的形參數量時,返回一個函數用于接收剩余的參數,直至接收的參數數量與形參數量一致,執行原函數。

----問題知識點分割線----

對瀏覽器的緩存機制的理解

瀏覽器緩存的全過程:

  • 瀏覽器第一次加載資源,服務器返回 200,瀏覽器從服務器下載資源文件,并緩存資源文件與 response header,以供下次加載時對比使用;

  • 下一次加載資源時,由于強制緩存優先級較高,先比較當前時間與上一次返回 200 時的時間差,如果沒有超過 cache-control 設置的 max-age,則沒有過期,并命中強緩存,直接從本地讀取資源。如果瀏覽器不支持HTTP1.1,則使用 expires 頭判斷是否過期;

  • 如果資源已過期,則表明強制緩存沒有被命中,則開始協商緩存,向服務器發送帶有 If-None-Match 和 If-Modified-Since 的請求;

  • 服務器收到請求后,優先根據 Etag 的值判斷被請求的文件有沒有做修改,Etag 值一致則沒有修改,命中協商緩存,返回 304;如果不一致則有改動,直接返回新的資源文件帶上新的 Etag 值并返回 200;

  • 如果服務器收到的請求沒有 Etag 值,則將 If-Modified-Since 和被請求文件的最后修改時間做比對,一致則命中協商緩存,返回 304;不一致則返回新的 last-modified 和文件并返回 200;

    很多網站的資源后面都加了版本號,這樣做的目的是:每次升級了 JS 或 CSS 文件后,為了防止瀏覽器進行緩存,強制改變版本號,客戶端瀏覽器就會重新下載新的 JS 或 CSS 文件 ,以保證用戶能夠及時獲得網站的最新更新。

----問題知識點分割線----

如何避免ajax數據請求重新獲取

一般而言,ajax請求的數據都放在redux中存取。

----問題知識點分割線----

Promise.resolve

Promise.resolve = function(value) {
    // 1.如果 value 參數是一個 Promise 對象,則原封不動返回該對象
    if(value instanceof Promise) return value;
    // 2.如果 value 參數是一個具有 then 方法的對象,則將這個對象轉為 Promise 對象,并立即執行它的then方法
    if(typeof value === "object" && 'then' in value) {
        return new Promise((resolve, reject) => {
           value.then(resolve, reject);
        });
    }
    // 3.否則返回一個新的 Promise 對象,狀態為 fulfilled
    return new Promise(resolve => resolve(value));
}

----問題知識點分割線----

數組去重

使用 indexOf/includes 實現

function unique(arr) {
    var res = [];
    for(var i = 0; i < arr.length; i++) {
        if(res.indexOf(arr[i]) === -1) res.push(arr[i]);
        // if(!res.includes(arr[i])) res.push(arr[i]);
    }
    return res;
}

使用 filter(forEach) + indexOf/includes 實現

// filter
function unique(arr) {
    var res = arr.filter((value, index) => {
        // 只存第一個出現的元素
        return arr.indexOf(value) === index;
    });
    return res;
}
// forEach
function unique(arr) {
    var res = [];
    arr.forEach((value) => {
        if(!res.includes(value)) res.push(value);
    });
    return res;
}

非 API 版本(原生)實現

function unique(arr) {
    var res = [];
    for(var i = 0; i < arr.length; i++) {
        var flag = false;
        for(var j = 0; j < res.length; j++) {
            if(arr[i] === res[j]) {
                flag = true;
                break;
            }
        }
        if(flag === false) res.push(arr[i]);
    }
    return res;
}

ES6 使用 Set + 擴展運算符(...)/Array.from() 實現

function unique(arr) {
    // return [...new Set(arr)];
    return Array.from(new Set(arr));
}

----問題知識點分割線----

documentFragment 是什么?用它跟直接操作 DOM 的區別是什么?

MDN中對documentFragment的解釋:

DocumentFragment,文檔片段接口,一個沒有父對象的最小文檔對象。它被作為一個輕量版的 Document使用,就像標準的document一樣,存儲由節點(nodes)組成的文檔結構。與document相比,最大的區別是DocumentFragment不是真實 DOM 樹的一部分,它的變化不會觸發 DOM 樹的重新渲染,且不會導致性能等問題。

當我們把一個 DocumentFragment 節點插入文檔樹時,插入的不是 DocumentFragment 自身,而是它的所有子孫節點。在頻繁的DOM操作時,我們就可以將DOM元素插入DocumentFragment,之后一次性的將所有的子孫節點插入文檔中。和直接操作DOM相比,將DocumentFragment 節點插入DOM樹時,不會觸發頁面的重繪,這樣就大大提高了頁面的性能。

----問題知識點分割線----

寫版本號排序的方法

題目描述:有一組版本號如下['0.1.1', '2.3.3', '0.302.1', '4.2', '4.3.5', '4.3.4.5']。現在需要對其進行排序,排序的結果為 ['4.3.5','4.3.4.5','2.3.3','0.302.1','0.1.1']

實現代碼如下:

arr.sort((a, b) => {
  let i = 0;
  const arr1 = a.split(".");
  const arr2 = b.split(".");

  while (true) {
    const s1 = arr1[i];
    const s2 = arr2[i];
    i++;
    if (s1 === undefined || s2 === undefined) {
      return arr2.length - arr1.length;
    }

    if (s1 === s2) continue;

    return s2 - s1;
  }
});
console.log(arr);

----問題知識點分割線----

談一談隊頭阻塞問題

什么是隊頭阻塞?

對于每一個HTTP請求而言,這些任務是會被放入一個任務隊列中串行執行的,一旦隊首任務請求太慢時,就會阻塞后面的請求處理,這就是HTTP隊頭阻塞問題。

有什么解決辦法嗎??

并發連接

我們知道對于一個域名而言,是允許分配多個長連接的,那么可以理解成增加了任務隊列,也就是說不會導致一個任務阻塞了該任務隊列的其他任務,在RFC規范中規定客戶端最多并發2個連接,不過實際情況就是要比這個還要多,舉個例子,Chrome中是6個。

域名分片

  • 顧名思義,我們可以在一個域名下分出多個二級域名出來,而它們最終指向的還是同一個服務器,這樣子的話就可以并發處理的任務隊列更多,也更好的解決了隊頭阻塞的問題。
  • 舉個例子,比如TianTian.com,可以分出很多二級域名,比如Day1.TianTian.comDay2.TianTian.com,Day3.TianTian.com,這樣子就可以有效解決隊頭阻塞問題。

----問題知識點分割線----

對原型、原型鏈的理解

在JavaScript中是使用構造函數來新建一個對象的,每一個構造函數的內部都有一個 prototype 屬性,它的屬性值是一個對象,這個對象包含了可以由該構造函數的所有實例共享的屬性和方法。當使用構造函數新建一個對象后,在這個對象的內部將包含一個指針,這個指針指向構造函數的 prototype 屬性對應的值,在 ES5 中這個指針被稱為對象的原型。一般來說不應該能夠獲取到這個值的,但是現在瀏覽器中都實現了 proto 屬性來訪問這個屬性,但是最好不要使用這個屬性,因為它不是規范中規定的。ES5 中新增了一個 Object.getPrototypeOf() 方法,可以通過這個方法來獲取對象的原型。

當訪問一個對象的屬性時,如果這個對象內部不存在這個屬性,那么它就會去它的原型對象里找這個屬性,這個原型對象又會有自己的原型,于是就這樣一直找下去,也就是原型鏈的概念。原型鏈的盡頭一般來說都是 Object.prototype 所以這就是新建的對象為什么能夠使用 toString() 等方法的原因。

特點: JavaScript 對象是通過引用來傳遞的,創建的每個新對象實體中并沒有一份屬于自己的原型副本。當修改原型時,與之相關的對象也會繼承這一改變。

----問題知識點分割線----

函數柯里化

什么叫函數柯里化?其實就是將使用多個參數的函數轉換成一系列使用一個參數的函數的技術。還不懂?來舉個例子。

function add(a, b, c) {
    return a + b + c
}
add(1, 2, 3)
let addCurry = curry(add)
addCurry(1)(2)(3)

現在就是要實現 curry 這個函數,使函數從一次調用傳入多個參數變成多次調用每次傳一個參數。

function curry(fn) {
    let judge = (...args) => {
        if (args.length == fn.length) return fn(...args)
        return (...arg) => judge(...args, ...arg)
    }
    return judge
}

----問題知識點分割線----

如何判斷數組類型

Array.isArray

----問題知識點分割線----

數組能夠調用的函數有那些?

  • push
  • pop
  • splice
  • slice
  • shift
  • unshift
  • sort
  • find
  • findIndex
  • map/filter/reduce 等函數式編程方法
  • 還有一些原型鏈上的方法:toString/valudOf

----問題知識點分割線----

單行、多行文本溢出隱藏

  • 單行文本溢出
overflow: hidden;            // 溢出隱藏
text-overflow: ellipsis;      // 溢出用省略號顯示
white-space: nowrap;         // 規定段落中的文本不進行換行

  • 多行文本溢出
overflow: hidden;            // 溢出隱藏
text-overflow: ellipsis;     // 溢出用省略號顯示
display:-webkit-box;         // 作為彈性伸縮盒子模型顯示。
-webkit-box-orient:vertical; // 設置伸縮盒子的子元素排列方式:從上到下垂直排列
-webkit-line-clamp:3;        // 顯示的行數

注意:由于上面的三個屬性都是 CSS3 的屬性,沒有瀏覽器可以兼容,所以要在前面加一個-webkit- 來兼容一部分瀏覽器。

----問題知識點分割線----

介紹一下HTTPS和HTTP區別

HTTPS 要比 HTTPS 多了 secure 安全性這個概念,實際上, HTTPS 并不是一個新的應用層協議,它其實就是 HTTP + TLS/SSL 協議組合而成,而安全性的保證正是 SSL/TLS 所做的工作。

SSL

安全套接層(Secure Sockets Layer)

TLS

(傳輸層安全,Transport Layer Security)

現在主流的版本是 TLS/1.2, 之前的 TLS1.0、TLS1.1 都被認為是不安全的,在不久的將來會被完全淘汰。

HTTPS 就是身披了一層 SSL 的 HTTP

[圖片上傳失敗...(image-c15684-1662526631598)]

那么區別有哪些呢??

  • HTTP 是明文傳輸協議,HTTPS 協議是由 SSL+HTTP 協議構建的可進行加密傳輸、身份認證的網絡協議,比 HTTP 協議安全。
  • HTTPS比HTTP更加安全,對搜索引擎更友好,利于SEO,谷歌、百度優先索引HTTPS網頁。
  • HTTPS標準端口443,HTTP標準端口80。
  • HTTPS需要用到SSL證書,而HTTP不用。

我覺得記住以下兩點HTTPS主要作用就行??

  1. 對數據進行加密,并建立一個信息安全通道,來保證傳輸過程中的數據安全;
  2. 對網站服務器進行真實身份認證。

HTTPS的缺點

  • 證書費用以及更新維護。
  • HTTPS 降低一定用戶訪問速度(實際上優化好就不是缺點了)。
  • HTTPS 消耗 CPU 資源,需要增加大量機器。

----問題知識點分割線----

請實現 DOM2JSON 一個函數,可以把一個 DOM 節點輸出 JSON 的格式

題目描述:

<div>
  <span>
    <a></a>
  </span>
  <span>
    <a></a>
    <a></a>
  </span>
</div>

把上訴dom結構轉成下面的JSON格式

{
  tag: 'DIV',
  children: [
    {
      tag: 'SPAN',
      children: [
        { tag: 'A', children: [] }
      ]
    },
    {
      tag: 'SPAN',
      children: [
        { tag: 'A', children: [] },
        { tag: 'A', children: [] }
      ]
    }
  ]
}

實現代碼如下:

function dom2Json(domtree) {
  let obj = {};
  obj.name = domtree.tagName;
  obj.children = [];
  domtree.childNodes.forEach((child) => obj.children.push(dom2Json(child)));
  return obj;
}

擴展思考:如果給定的不是一個 Dom 樹結構 而是一段 html 字符串 該如何解析?

那么這個問題就類似 Vue 的模板編譯原理 我們可以利用正則 匹配 html 字符串 遇到開始標簽 結束標簽和文本 解析完畢之后生成對應的 ast 并建立相應的父子關聯 不斷的 advance 截取剩余的字符串 直到 html 全部解析完畢

----問題知識點分割線----

對執行上下文的理解

1. 執行上下文類型

(1)全局執行上下文

任何不在函數內部的都是全局執行上下文,它首先會創建一個全局的window對象,并且設置this的值等于這個全局對象,一個程序中只有一個全局執行上下文。

(2)函數執行上下文

當一個函數被調用時,就會為該函數創建一個新的執行上下文,函數的上下文可以有任意多個。

(3)eval函數執行上下文

執行在eval函數中的代碼會有屬于他自己的執行上下文,不過eval函數不常使用,不做介紹。

2. 執行上下文棧
  • JavaScript引擎使用執行上下文棧來管理執行上下文
  • 當JavaScript執行代碼時,首先遇到全局代碼,會創建一個全局執行上下文并且壓入執行棧中,每當遇到一個函數調用,就會為該函數創建一個新的執行上下文并壓入棧頂,引擎會執行位于執行上下文棧頂的函數,當函數執行完成之后,執行上下文從棧中彈出,繼續執行下一個上下文。當所有的代碼都執行完畢之后,從棧中彈出全局執行上下文。
let a = 'Hello World!';
function first() {
  console.log('Inside first function');
  second();
  console.log('Again inside first function');
}
function second() {
  console.log('Inside second function');
}
first();
//執行順序
//先執行second(),在執行first()

3. 創建執行上下文

創建執行上下文有兩個階段:創建階段執行階段

1)創建階段

(1)this綁定

  • 在全局執行上下文中,this指向全局對象(window對象)
  • 在函數執行上下文中,this指向取決于函數如何調用。如果它被一個引用對象調用,那么 this 會被設置成那個對象,否則 this 的值被設置為全局對象或者 undefined

(2)創建詞法環境組件

  • 詞法環境是一種有標識符——變量映射的數據結構,標識符是指變量/函數名,變量是對實際對象或原始數據的引用。
  • 詞法環境的內部有兩個組件:加粗樣式:環境記錄器:用來儲存變量個函數聲明的實際位置外部環境的引用:可以訪問父級作用域

(3)創建變量環境組件

  • 變量環境也是一個詞法環境,其環境記錄器持有變量聲明語句在執行上下文中創建的綁定關系。

2)執行階段 此階段會完成對變量的分配,最后執行完代碼。

簡單來說執行上下文就是指:

在執行一點JS代碼之前,需要先解析代碼。解析的時候會先創建一個全局執行上下文環境,先把代碼中即將執行的變量、函數聲明都拿出來,變量先賦值為undefined,函數先聲明好可使用。這一步執行完了,才開始正式的執行程序。

在一個函數執行之前,也會創建一個函數執行上下文環境,跟全局執行上下文類似,不過函數執行上下文會多出this、arguments和函數的參數。

  • 全局上下文:變量定義,函數聲明
  • 函數上下文:變量定義,函數聲明,thisarguments

----問題知識點分割線----

對Promise的理解

Promise是異步編程的一種解決方案,它是一個對象,可以獲取異步操作的消息,他的出現大大改善了異步編程的困境,避免了地獄回調,它比傳統的解決方案回調函數和事件更合理和更強大。

所謂Promise,簡單說就是一個容器,里面保存著某個未來才會結束的事件(通常是一個異步操作)的結果。從語法上說,Promise 是一個對象,從它可以獲取異步操作的消息。Promise 提供統一的 API,各種異步操作都可以用同樣的方法進行處理。

(1)Promise的實例有三個狀態:

  • Pending(進行中)
  • Resolved(已完成)
  • Rejected(已拒絕)

當把一件事情交給promise時,它的狀態就是Pending,任務完成了狀態就變成了Resolved、沒有完成失敗了就變成了Rejected。

(2)Promise的實例有兩個過程

  • pending -> fulfilled : Resolved(已完成)
  • pending -> rejected:Rejected(已拒絕)

注意:一旦從進行狀態變成為其他狀態就永遠不能更改狀態了。

Promise的特點:

  • 對象的狀態不受外界影響。promise對象代表一個異步操作,有三種狀態,pending(進行中)、fulfilled(已成功)、rejected(已失敗)。只有異步操作的結果,可以決定當前是哪一種狀態,任何其他操作都無法改變這個狀態,這也是promise這個名字的由來——“承諾”;
  • 一旦狀態改變就不會再變,任何時候都可以得到這個結果。promise對象的狀態改變,只有兩種可能:從pending變為fulfilled,從pending變為rejected。這時就稱為resolved(已定型)。如果改變已經發生了,你再對promise對象添加回調函數,也會立即得到這個結果。這與事件(event)完全不同,事件的特點是:如果你錯過了它,再去監聽是得不到結果的。

Promise的缺點:

  • 無法取消Promise,一旦新建它就會立即執行,無法中途取消。
  • 如果不設置回調函數,Promise內部拋出的錯誤,不會反應到外部。
  • 當處于pending狀態時,無法得知目前進展到哪一個階段(剛剛開始還是即將完成)。

總結: Promise 對象是異步編程的一種解決方案,最早由社區提出。Promise 是一個構造函數,接收一個函數作為參數,返回一個 Promise 實例。一個 Promise 實例有三種狀態,分別是pending、resolved 和 rejected,分別代表了進行中、已成功和已失敗。實例的狀態只能由 pending 轉變 resolved 或者rejected 狀態,并且狀態一經改變,就凝固了,無法再被改變了。

狀態的改變是通過 resolve() 和 reject() 函數來實現的,可以在異步操作結束后調用這兩個函數改變 Promise 實例的狀態,它的原型上定義了一個 then 方法,使用這個 then 方法可以為兩個狀態的改變注冊回調函數。這個回調函數屬于微任務,會在本輪事件循環的末尾執行。

注意: 在構造 Promise 的時候,構造函數內部的代碼是立即執行的

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

推薦閱讀更多精彩內容