001.【葵花寶典】之 javascript 執(zhí)行棧與執(zhí)行上下文

古往今來最難的學(xué)的武功【葵花寶典】(javascript)算其一否过。
欲練此功必先自宮,愿少俠習(xí)的此功蔓姚,笑傲江湖躏嚎。

你將了解

  • 執(zhí)行棧(Execution stack)
  • 執(zhí)行上下文(Execution Context)
  • 作用域鏈(scope chains)
  • 變量提升(hoisting)
  • 閉包(closures)
  • this 綁定

執(zhí)行棧

又叫調(diào)用棧,具有 LIFO(last in first out 后進先出)結(jié)構(gòu)罢荡,用于存儲在代碼執(zhí)行期間創(chuàng)建的所有執(zhí)行上下文赡突。

當(dāng) JavaScript 引擎首次讀取你的腳本時,它會創(chuàng)建一個全局執(zhí)行上下文并將其推入當(dāng)前的執(zhí)行棧区赵。每當(dāng)發(fā)生一個函數(shù)調(diào)用惭缰,引擎都會為該函數(shù)創(chuàng)建一個新的執(zhí)行上下文并將其推到當(dāng)前執(zhí)行棧的頂端。
引擎會運行執(zhí)行上下文在執(zhí)行棧頂端的函數(shù)笼才,當(dāng)此函數(shù)運行完成后漱受,其對應(yīng)的執(zhí)行上下文將會從執(zhí)行棧中彈出,上下文控制權(quán)將移到當(dāng)前執(zhí)行棧的下一個執(zhí)行上下文骡送。

我們通過下面的示例來說明一下

function one() {
  console.log('one')
  two()
}
function two() {
  console.log('two')
}
one()

當(dāng)程序(代碼)開始執(zhí)行時 javscript 引擎創(chuàng)建 GobalExecutionContext (全局執(zhí)行上下文)推入當(dāng)前的執(zhí)行棧昂羡,此時 GobalExecutionContext 處于棧頂會立刻執(zhí)行全局執(zhí)行上下文 然后遇到 one() 引擎都會為該函數(shù)創(chuàng)建一個新的執(zhí)行上下文 oneFunctionExecutionContext 并將其推到當(dāng)前執(zhí)行棧的頂端并執(zhí)行,然后遇到two() twoFunctionExecutionContext 入棧并執(zhí)行至出棧摔踱,回到 oneFunctionExecutionContext 繼續(xù)執(zhí)行至出棧 ,最后剩余一個 GobalExecutionContext 它會在程序關(guān)閉的時候出棧虐先。

然后調(diào)用棧如下圖:


image

如果是這樣的代碼

function foo() {
  foo()
}
foo()

如下


image

當(dāng)一個遞歸沒有結(jié)束點的時候就會出現(xiàn)棧溢出

什么是執(zhí)行上下文

了解 JavaScript 的執(zhí)行上下文,有助于你理解更高級的內(nèi)容比如變量提升派敷、作用域鏈和閉包蛹批。既然如此,那到底什么是“執(zhí)行上下文”呢篮愉?

執(zhí)行上下文是當(dāng)前 JavaScript 代碼被解析和執(zhí)行時所在環(huán)境的抽象概念腐芍。

Javascript 中代碼的執(zhí)行上下文分為以下三種:

  1. 全局執(zhí)行上下文(Global Execution Context)- 這個是默認的代碼運行環(huán)境,一旦代碼被載入试躏,引擎最先進入的就是這個環(huán)境猪勇。
  2. 函數(shù)執(zhí)行上下文(Function Execution Context) - 當(dāng)執(zhí)行一個函數(shù)時,運行函數(shù)體中的代碼颠蕴。
  3. Eval - 在 Eval 函數(shù)內(nèi)運行的代碼泣刹。

javascript 是一個單線程語言,這意味著在瀏覽器中同時只能做一件事情犀被。當(dāng) javascript 解釋器初始執(zhí)行代碼项玛,它首先默認進入全局上下文。每次調(diào)用一個函數(shù)將會創(chuàng)建一個新的執(zhí)行上下文弱判。

javascript執(zhí)行棧中不同執(zhí)行上下文之間的詞法環(huán)境有一種關(guān)聯(lián)關(guān)系襟沮,從棧頂?shù)綏5祝◤木植恐钡饺郑?這種關(guān)系被叫做作用域鏈

簡單的說昌腰,每次你試圖訪問函數(shù)執(zhí)行上下文中的變量時开伏,進程總是從自己上下文環(huán)境中開始查找。如果在自己的上下文中沒發(fā)現(xiàn)要查找的變量遭商,繼續(xù)搜索下一層上下文固灵。它將檢查執(zhí)行棧中每一個執(zhí)行上下文環(huán)境,尋找和變量名稱匹配的值劫流,直到找到為止巫玻,如果到全局都沒有則拋出錯誤丛忆。

執(zhí)行上下文的創(chuàng)建過程

我們現(xiàn)在已經(jīng)知道,每當(dāng)調(diào)用一個函數(shù)時仍秤,一個新的執(zhí)行上下文就會被創(chuàng)建出來熄诡。然而,在 javascript 引擎內(nèi)部诗力,這個上下文的創(chuàng)建過程具體分為兩個階段:

創(chuàng)建階段 > 執(zhí)行階段

創(chuàng)建階段

執(zhí)行上下文在創(chuàng)建階段創(chuàng)建凰浮。在創(chuàng)建階段發(fā)生以下事情:

  1. LexicalEnvironment 組件已創(chuàng)建。
  2. VariableEnvironment 組件已創(chuàng)建苇本。

因此袜茧,執(zhí)行上下文可以在概念上表示如下:

ExecutionContext = {
  LexicalEnvironment = <詞法環(huán)境>,
  VariableEnvironment = <變量環(huán)境>,
}

詞法環(huán)境(Lexical Environment)

官方 ES6 文檔將詞法環(huán)境定義為:

詞法環(huán)境是一種規(guī)范類型,基于 ECMAScript 代碼的詞法嵌套結(jié)構(gòu)來定義標識符與特定變量和函數(shù)的關(guān)聯(lián)關(guān)系窝撵。詞法環(huán)境由環(huán)境記錄(environment record)和可能為空引用(null)的外部詞法環(huán)境組成。

簡而言之递递,詞法環(huán)境是一個包含標識符變量映射的結(jié)構(gòu)。(這里的標識符表示變量/函數(shù)的名稱啥么,變量是對實際對象【包括函數(shù)類型對象】或原始值的引用)

詞法環(huán)境有兩種類型

  • 全局環(huán)境(在全局執(zhí)行上下文中)是一個沒有外部環(huán)境的詞法環(huán)境登舞。全局環(huán)境的外部環(huán)境引用為 null。它擁有一個全局對象(window 對象)及其關(guān)聯(lián)的方法和屬性(例如數(shù)組方法)以及任何用戶自定義的全局變量悬荣,this 的值指向這個全局對象菠秒。
  • 函數(shù)環(huán)境,用戶在函數(shù)中定義的變量被存儲在環(huán)境記錄中氯迂。對外部環(huán)境的引用可以是全局環(huán)境践叠,也可以是包含內(nèi)部函數(shù)的外部函數(shù)環(huán)境。

每個詞匯環(huán)境都有三個組成部分:

1)環(huán)境記錄(environment record)

2)對外部環(huán)境的引用(outer)

3) 綁定 this

環(huán)境記錄 同樣有兩種類型(如下所示):

  • 聲明性環(huán)境記錄 存儲變量嚼蚀、函數(shù)和參數(shù)禁灼。一個函數(shù)環(huán)境包含聲明性環(huán)境記錄。
  • 對象環(huán)境記錄 用于定義在全局執(zhí)行上下文中出現(xiàn)的變量和函數(shù)的關(guān)聯(lián)轿曙。全局環(huán)境包含對象環(huán)境記錄

抽象地說弄捕,詞法環(huán)境在偽代碼中看起來像這樣:

詞法環(huán)境和環(huán)境記錄值是純粹的規(guī)范機制,ECMAScript 程序不能直接訪問或操縱這些值导帝。

GlobalExectionContext = {
  // 詞法環(huán)境
  LexicalEnvironment:{
    // 功能環(huán)境記錄
    EnvironmentRecord:{
      Type:"Object",
      // Identifier bindings go here
     }
    outer:<null>,
    this:<global object>
  }
}
FunctionExectionContext = {
  LexicalEnvironment:{
    EnvironmentRecord:{
      Type:"Declarative",
      // Identifier bindings go here
     }
    outer:<Global or outer function environment reference>,
    this:<取決于函數(shù)的調(diào)用方式>
  }
}

變量環(huán)境:

它也是一個詞法環(huán)境守谓,其 EnvironmentRecord 包含了由 VariableStatements 在此執(zhí)行上下文創(chuàng)建的綁定。
如上所述您单,變量環(huán)境也是一個詞法環(huán)境斋荞,因此它具有上面定義的詞法環(huán)境的所有屬性。
在 ES6 中虐秦,LexicalEnvironment 組件和 VariableEnvironment 組件的區(qū)別在于前者用于存儲函數(shù)聲明和變量( let 和 const )綁定平酿,而后者僅用于存儲變量( var )綁定凤优。
讓我們結(jié)合一些代碼示例來理解上述概念:

let a = 20
const b = 30
var c

function multiply(e, f) {
  var g = 20
  return e * f * g
}

c = multiply(20, 30)

執(zhí)行上下文如下所示:

GlobalExectionContext = {
  LexicalEnvironment:{
    EnvironmentRecord:{
      Type:"Object",
      // Identifier bindings go here
      a:<uninitialized>,
      b:<uninitialized>,
      multiply:<func>
    },
    outer:<null>,
    ThisBinding:<Global Object>
  },
  VariableEnvironment:{
    EnvironmentRecord:{
      Type:"Object",
      // Identifier bindings go here
      c:undefined,
    }
    outer:<null>,
    ThisBinding:<Global Object>
  }
}

在執(zhí)行階段,完成變量賦值蜈彼。因此筑辨,在執(zhí)行階段,全局執(zhí)行上下文將看起來像這樣柳刮。

// 執(zhí)行
GlobalExectionContext = {
LexicalEnvironment: {
    EnvironmentRecord: {
      Type: "Object",
      // Identifier bindings go here
      a: 20,
      b: 30,
      multiply: < func >
    }
    outer: <null>,
    ThisBinding: <Global Object>
  },
VariableEnvironment: {
    EnvironmentRecord: {
      Type: "Object",
      // Identifier bindings go here
      c: undefined,
    }
    outer: <null>,
    ThisBinding: <Global Object>
  }
}

當(dāng) multiply(20, 30)遇到函數(shù)調(diào)用時,會創(chuàng)建一個新的函數(shù)執(zhí)行上下文來執(zhí)行函數(shù)代碼痒钝。因此秉颗,在創(chuàng)建階段,函數(shù)執(zhí)行上下文將如下所示:

// multiply 創(chuàng)建
FunctionExectionContext = {
LexicalEnvironment: {
    EnvironmentRecord: {
      Type: "Declarative",
      Arguments: {0: 20, 1: 30, length: 2},
    },
    outer: <GlobalLexicalEnvironment>,
    ThisBinding: <Global Object or undefined>,
  },
VariableEnvironment: {
    EnvironmentRecord: {
      Type: "Declarative",
      g: undefined
    },
    outer: <GlobalLexicalEnvironment>,
    ThisBinding: <Global Object or undefined>
  }
}

在此之后送矩,執(zhí)行上下文將執(zhí)行執(zhí)行階段蚕甥,這意味著完成了對函數(shù)內(nèi)部變量的賦值。因此栋荸,在執(zhí)行階段菇怀,函數(shù)執(zhí)行上下文將如下所示:

// multiply 執(zhí)行
FunctionExectionContext = {
LexicalEnvironment: {
    EnvironmentRecord: {
      Type: "Declarative",
      Arguments: {0: 20, 1: 30, length: 2},
    },
    outer: <GlobalLexicalEnvironment>,
    ThisBinding: <Global Object or undefined>,
  },
VariableEnvironment: {
    EnvironmentRecord: {
      Type: "Declarative",
      g: 20
    },
    outer: <GlobalLexicalEnvironment>,
    ThisBinding: <Global Object or undefined>
  }
}

函數(shù)完成后,返回的值賦值給c晌块。因此爱沟,全局詞法環(huán)境得到了更新。之后匆背,全局代碼完成呼伸,程序結(jié)束。

注: 在執(zhí)行階段钝尸,如果 Javascript 引擎在源代碼中聲明的實際位置找不到 let 變量的值括享,那么將為其分配 undefined 值。

變量提升

在網(wǎng)上一直看到這樣的總結(jié): 在函數(shù)中聲明的變量以及函數(shù)珍促,其作用域提升到函數(shù)頂部铃辖,換句話說,就是一進入函數(shù)體猪叙,就可以訪問到其中聲明的變量以及函數(shù)娇斩。這是對的,但是知道其中的緣由嗎穴翩?相信你通過上述的解釋應(yīng)該也有所明白了成洗。不過在這邊再分析一下。

你可能已經(jīng)注意到了在創(chuàng)建階段 letconst 定義的變量沒有任何與之關(guān)聯(lián)的值藏否,但 var 定義的變量設(shè)置為 undefined瓶殃。
這是因為在創(chuàng)建階段,代碼會被掃描并解析變量和函數(shù)聲明副签,其中函數(shù)聲明存儲在環(huán)境中遥椿,而變量會被設(shè)置為 undefined(在 var 聲明變量的情況下)或保持未初始化(在 letconst 聲明變量的情況下)基矮。
這就是為什么你可以在聲明之前訪問var 定義的變量(盡管是 undefined ),但如果在聲明之前訪問letconst 定義的變量就會提示引用錯誤的原因冠场。
這就是我們所謂的變量提升家浇。

思考題:

console.log('step1:',a)
var a = 'artiely'
console.log('step2:',a)
function bar (a){
  console.log('step3:',a)
  a = 'TJ'
  console.log('step4:',a)
  function a(){
  }
}
bar(a)
console.log('step5:',a)

對外部環(huán)境的引用

上面代碼如果我們改用調(diào)用方式如下:

let a = 20
const b = 30
var c

function multiply() {
  var g = 20
  return a * b * g
}

c = multiply()

其實你會發(fā)現(xiàn)結(jié)果是一樣的
但是 multiply 的執(zhí)行上下文卻發(fā)生一些變化

// 創(chuàng)建
FunctionExectionContext = {
LexicalEnvironment: {
    EnvironmentRecord: {
      Type: "Declarative",
      Arguments: { length: 0},
    },
    outer: <GlobalLexicalEnvironment>,
    ThisBinding: <Global Object or undefined>,
  },
VariableEnvironment: {
    EnvironmentRecord: {
      Type: "Declarative",
      // Identifier bindings go here
      g: undefined
    },
    outer: <GlobalLexicalEnvironment>,
    ThisBinding: <Global Object or undefined>
  }
}

// 執(zhí)行
FunctionExectionContext = {
LexicalEnvironment: {
    EnvironmentRecord: {
      Type: "Declarative",
      Arguments: { length: 0},
    },
    outer: <GlobalLexicalEnvironment>,
    ThisBinding: <Global Object or undefined>,
  },
VariableEnvironment: {
    EnvironmentRecord: {
      Type: "Declarative",
      // Identifier bindings go here
      g: 20
    },
    outer: <GlobalLexicalEnvironment>,
    ThisBinding: <Global Object or undefined>
  }
}

multiply() 執(zhí)行的時候會直接在 outer: <GlobalLexicalEnvironment>,中查找a,b

對外部環(huán)境的引用意味著它可以訪問其外部詞法環(huán)境。這意味著如果在當(dāng)前詞法環(huán)境中找不到它們碴裙,JavaScript 引擎可以在外部環(huán)境中查找變量钢悲。這就是之前說的 作用域鏈

閉包

MDN 解釋 閉包是由函數(shù)以及創(chuàng)建該函數(shù)的詞法環(huán)境組合而成。這個環(huán)境包含了這個閉包創(chuàng)建時所能訪問的所有局部變量

這是我認為對閉包最合理的解釋了舔株,就看你怎么理解閉包的機制了莺琳。
其實閉包與作用域鏈有著密切的關(guān)系。

首先我們來看看什么樣的代碼會產(chǎn)生閉包载慈。

function foo() {
  var name = 'artiely'
  function bar() {
    console.log(`hello `)
  }
  bar()
}
foo()

以上代碼是有閉包嗎惭等?沒有~

function foo() {
  var name = 'artiely'
  function bar() {
    console.log(`hello ${name}`)
  }
  bar()
}
foo()

我們只做了微小的調(diào)整,現(xiàn)在就有閉包了,我們只是在bar中加入了name得引用
上面的代碼還可以寫成這樣

// 或者
function foo() {
  var name = 'artiely'
  return function bar() {
    console.log(`hello ${name}`)
  }
}
foo()()

對于閉包的形成我進行了如下的幾點歸納

  1. A 函數(shù)內(nèi)必須有 B 函數(shù)的聲明办铡;
  2. B 函數(shù)必須引用 A 函數(shù)的變量辞做;
  3. B 函數(shù)被調(diào)用(當(dāng)然前提是 A 函數(shù)被調(diào)用)

以上 3 點缺一不可

我們來分析一下上面代碼的執(zhí)行上下文

// 創(chuàng)建
fooFunctionExectionContext = {
LexicalEnvironment: {
  EnvironmentRecord: {
    Type: "Declarative",
    Arguments: { length: 0},
    bar: < func >,
  },
  outer: <GlobalLexicalEnvironment>,
  ThisBinding: <Global Object or undefined>,
},
VariableEnvironment: {
  EnvironmentRecord: {
    Type: "Declarative",
    name: undefined
  },
  outer: <GlobalLexicalEnvironment>,
  ThisBinding: <Global Object or undefined>
  }
}
// 執(zhí)行 略
// 創(chuàng)建
barFunctionExectionContext = {
LexicalEnvironment: {
  EnvironmentRecord: {
    Type: "Declarative",
    Arguments: { length: 0},
  },
  outer: <fooLexicalEnvironment>,
  ThisBinding: <Global Object or undefined>,
},
VariableEnvironment: {
  EnvironmentRecord: {
    Type: "Declarative",
  },
  outer: <fooLexicalEnvironment>,
  ThisBinding: <Global Object or undefined>
  }
}
// 執(zhí)行 略

這里因為bar的創(chuàng)建存在著對fooLexicalEnvironment里變量的引用,雖然foo可能執(zhí)行已結(jié)束但變量不會被回收寡具。這種機制被叫做閉包

閉包是由函數(shù)以及創(chuàng)建該函數(shù)的詞法環(huán)境組合而成秤茅。這個環(huán)境包含了這個閉包創(chuàng)建時所能訪問的所有局部變量

我們結(jié)合上面例子重新分解一下這句話

閉包是由函數(shù)bar以及創(chuàng)建該函數(shù)foo的詞法環(huán)境組合而成。這個環(huán)境包含了這個閉包創(chuàng)建時所能訪問的所有局部變量name

但是從chrome的理解童叠,閉包并沒有包含所能訪問的所有局部變量嫂伞,僅僅包含所被引用的變量。

this 綁定

在全局執(zhí)行上下文中拯钻,值是 this 指全局對象帖努。(在瀏覽器中,this 指的是 Window 對象)粪般。

在函數(shù)執(zhí)行上下文中拼余,值 this 取決于函數(shù)的調(diào)用方式。如果它由對象引用調(diào)用亩歹,則將值 this 設(shè)置為該對象匙监,否則,將值 this 設(shè)置為全局對象或 undefined(在嚴格模式下)小作。例如:

let person = {
  name: 'peter',
  birthYear: 1994,
  calcAge: function() {
    console.log(2018 - this.birthYear)
  }
}

person.calcAge()
// 'this' 指向 'person', 因為 'calcAge' 是被 'person' 對象引用調(diào)用的亭姥。

let calculateAge = person.calcAge
calculateAge()
// 'this' 指向全局 window 對象,因為沒有給出任何對象引用

注意所有的()()自調(diào)用的函數(shù) this 都是指向Global Object的既瀏覽器中的window

最后

如果本文對你有幫助或覺得不錯請幫忙點贊,如有疑問請留言顾稀。

其他參考:
https://hackernoon.com/execution-context-in-javascript-319dd72e8e2c

https://tylermcginnis.com/ultimate-guide-to-execution-contexts-hoisting-scopes-and-closures-in-javascript/

https://hackernoon.com/javascript-execution-context-and-lexical-environment-explained-528351703922

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末达罗,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌粮揉,老刑警劉巖巡李,帶你破解...
    沈念sama閱讀 216,591評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異扶认,居然都是意外死亡侨拦,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,448評論 3 392
  • 文/潘曉璐 我一進店門辐宾,熙熙樓的掌柜王于貴愁眉苦臉地迎上來狱从,“玉大人,你說我怎么就攤上這事叠纹〖狙校” “怎么了?”我有些...
    開封第一講書人閱讀 162,823評論 0 353
  • 文/不壞的土叔 我叫張陵吊洼,是天一觀的道長训貌。 經(jīng)常有香客問我制肮,道長冒窍,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,204評論 1 292
  • 正文 為了忘掉前任豺鼻,我火速辦了婚禮综液,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘儒飒。我一直安慰自己谬莹,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,228評論 6 388
  • 文/花漫 我一把揭開白布桩了。 她就那樣靜靜地躺著附帽,像睡著了一般。 火紅的嫁衣襯著肌膚如雪井誉。 梳的紋絲不亂的頭發(fā)上蕉扮,一...
    開封第一講書人閱讀 51,190評論 1 299
  • 那天,我揣著相機與錄音颗圣,去河邊找鬼喳钟。 笑死,一個胖子當(dāng)著我的面吹牛在岂,可吹牛的內(nèi)容都是我干的奔则。 我是一名探鬼主播,決...
    沈念sama閱讀 40,078評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼蔽午,長吁一口氣:“原來是場噩夢啊……” “哼易茬!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起及老,我...
    開封第一講書人閱讀 38,923評論 0 274
  • 序言:老撾萬榮一對情侶失蹤疾呻,失蹤者是張志新(化名)和其女友劉穎除嘹,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體岸蜗,經(jīng)...
    沈念sama閱讀 45,334評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡尉咕,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,550評論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了璃岳。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片年缎。...
    茶點故事閱讀 39,727評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖铃慷,靈堂內(nèi)的尸體忽然破棺而出单芜,到底是詐尸還是另有隱情,我是刑警寧澤犁柜,帶...
    沈念sama閱讀 35,428評論 5 343
  • 正文 年R本政府宣布洲鸠,位于F島的核電站,受9級特大地震影響馋缅,放射性物質(zhì)發(fā)生泄漏扒腕。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,022評論 3 326
  • 文/蒙蒙 一萤悴、第九天 我趴在偏房一處隱蔽的房頂上張望瘾腰。 院中可真熱鬧,春花似錦覆履、人聲如沸蹋盆。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,672評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽栖雾。三九已至,卻和暖如春伟众,著一層夾襖步出監(jiān)牢的瞬間析藕,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,826評論 1 269
  • 我被黑心中介騙來泰國打工赂鲤, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留噪径,地道東北人。 一個月前我還...
    沈念sama閱讀 47,734評論 2 368
  • 正文 我出身青樓数初,卻偏偏與公主長得像找爱,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子泡孩,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,619評論 2 354

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