JavaScript高级程序设计学习笔记

[TOC]

以下均在Node REPL(read-eval-print-Loop)环境中测试

第三章

3.1 在脚本中启动严格模式

"use strict";//此为编译指示(pragma),告诉javascritpt引擎切换到严格模式
//在函数内部同样可以指定严格模式
function dosomething(){
  "use strict";
  //函数体
}

3.2 关键字和保留字不能作为标识符、属性名使用,以便与将来的ECMAScirpt版本兼容

3.4数据类型

ECMA5种基本数据类型:
Undefiend、Null(空对象指针)、Boolean、Number、String

3.4.1 typeof操作符检测给定变量的数据类型

typeof(variable)
typeof(null) //返回‘object’,null为空对象指针
/*返回值<strong>字符串</strong>("undefined"、"boolean"、"String"、"number"、"object"、"function")*/

3.4.4 Boolean类型

以下为各种数据类型及其对应的转换规则

数据类型 转换为true的值 转换为false的值
Boolean true false
String 任何非空字符串 ""(空字符串)
Number 任何非零数值(包括无穷大) 0和NaN
Object 任何对象 null
Undefined n/a undefined

3.4.5 Number类型

  1. NaN,即非数值(Not a Number),表示一个本来要返回数值的操作数未返回数值的情况(这样就不会跑出错误了)。

  2. 数值转换

Number() 转型函数用于任何数据类型

var num1 = Number("Hello,world!");  //NaN
var num2 = Number("");  //0

parseInt() 字符串转换为数值

var num1 = parseInt("1234abcd"); //1234
var num2 = parseInt(22.5)   //22
var num3 = parseInt("070", 8) //56(八进制) 0*8^0 + 7*8^1 + 0*8^2 = 56
//其上第二个参数8指定parseInt()按八进制转换

parsefloat()只解析十进制,转换为浮点数,十六进制总会被转化为0

var num1 = parseFloat("22.5")  //22.5
var num2 = parseFloat("0908.5") //908.5
var num3 = parseFloat("22.34.3") //22.34

3.4.6 String类型

toString(); 转换为字符串,null和undefined值无此方法

var age = 11;
var ageAsString = age.toString(); //字符串"11"

var b;
b.toString();// cannot read property "toString" of undefined

String(); 将任何类型的值转换为字符串

var b;
String(b);  //"undefined"

3.4.7 object类型

object的每个实例对象都具有的属性和方法:

var o = new Object();
  • constructor: 保存用于创建当前对象的函数。上🌰中构造函数(constructor)就是Object()。
  • hasOwnProperty(propertyName): 检查给定属性是否在当前的对象实例中(而不是在实例的原型中),o.hasOwnProperty("name")。
o.hasOwnProperty("name"); //false
  • isPrototypeOf(object), 检查传入的对象是否是传入对象的原型
o.isPrototypeOf(Object); //false
  • propertyIsEnumerable(propertyName) 检查给定的属性是否可用for...in 枚举
  • **toLocaleString() **返回对象的字符串表示,与执行环境的地区对应
o.toLocaleString(); //"[Object Object]"
  • **toString() **返回对象的字符串表示
  • valueOf() 返回对象的字符串、数值或布尔值表示。
o.valueOf(); //{}

data:2016.07.26

3.7 函数

3.7.1 理解参数

ECMAScript函数不介意传递进来的参数个数与类型,ECMAScript的参数在内部用一个数组表述,内部通过arguments对象访问参数数组,可以用方括号访问。

function sayHi(){
  alert("Hello" + arguments[0] + arguments[1]);
}

第四章

4.1 基本类型和引用类型的值

  • 基本类型值

简单的数据段

基本数据类型: Undefined、 Null 、 Boolean、 Number 、 String

  • 引用类型的值

保存在内存的中的对象(JavaScript不允许直接操作对象的内存空间)

实际上JScript操作的是对象的引用而不是实际的对象。为此,引用类型的值是按引用访问的。

4.1.3 传递参数

ECMAScript中的所有函数参数都是按值递的

//传递基本数据类型参数
function addTen(num){
  num += 10;
  return num;
}
var count = 10;
result = addTen(count); 
console.log(result); // 20
console.log(count); //10
//按值传递,只传递count的值,内部参数变化不改变传入参数的内存地址,不会引起count的值变化
//错觉.jpg, 误以为按引用传递
//传递对象
function setName(){
  arguments[0].name = "Mike";
}
var person = new Object();
setName(person);
console.log(person.name);  //"Mike"
person // { name : 'Mike'}
//当person传递进函数内部后,函数外部的person发生变化,误以为按引用传递
//拨乱反正.jpg 函数内传递参数为对象,依然是按值传递
function setName(obj){
  obj.name = "Mike";
  obj = new Object();
  obj.name = "Fred";
}
var person = new Object();
setName(obj);
alert(person.name); // "Mike"
//若按引用传参,结果应该为Fred

4.1.4 检测类型

  • typeof 确定变量是否是基本数据类型,但不能判断变量是什么类型的对象
var o = new Object();
typeof o; // "object"
var n = null;
typeof n; //'object'
  • instanceof 判断变量是否是给定引用类型的实例

语法:result = variable instanceof constructor

var array = [1, 2, 3];
typeof array; // 'object'
array instanceof Array;// true
array instanceof RegExp //false

基本数据类型都不是对象

array instanceof String //false

4.2.2 没有块级作用域

C语言中,由花括号封闭的代码都有自己的作用域,但在JavaScript中if、for中定义的变量都被添加至当前的执行环境中(全局环境)。

if(true){
  var color = "blue";
}
console.log(color); //'blue'
  1. 声明变量

    var声明的变量会自动被添加到最接近的环境中(全局与局部环境)。

  2. 查询标识符

    引用(读取或写入)一个标识符,并确定其实际意义。必须从当前环境的作用域链的前端开始向上逐级查询,直至找到该标识符。

4.3.4 管理内存

一旦数据不再有用,最好通过将其值设置为null来释放其引用-解除引用(dereference)

适用于全局变量和全局对象的属性,局部变量会在离开之行环境时自动被解除引用。

小结

  • 基本类型值在内存中占据固定大小的空间,因此被保存在栈内存中。
  • 引用类型的值是对象,不知其具体需要的内存空间,保存在堆内存中。

栈内存与堆内存的思考?😔

Stack Or Heap ?

第五章 引用类型

引用类型与类并不是相同的概念

引用类型被称为对象定义,描述一类对象所具有的属性和方法。

对象是某个特定引用类型的实例。

新对象使用new操作符后跟一个构造函数来创建

构造函数本身是一个函数,处于构建新对象的目的而定义的。

5.1 Object类型

创建Object引用类型实例的两种方法:

  • new操作符 + Object构造函数
var person = new Object();
person.name = "Mike";
person.age = 29;
  • 对象字面量( 逗号分隔不同的属性)
var person = {
  name : "Mike",
  age : 29,
  5 : true // 数值属性自动转化为字符串
}

//访问其属性
person[name]  // ‘Mike’
person.name // 'Mike'
person[first name] = "Nicholas";  //属性中有空格只能用"[]" 访问

5.2 Array类型

  • new操作符 + Array构造函数
var colors = new Array(20); //创建length值为20的数组
var colors = Array(20); //同上
var colors = Array("Gred");
var colors = new Array("Gred");
  • 数组字面量表示法
var colors = ["red", "blue", "green"];

5.2.1 检测数组

  • instanceof 对于只有一个网页或全局作用域而言可以使用instanceof
colors instanceof Array; // true
  • Array.isArray() 不管数组在哪个全局环境中创建的
Array.isArray(colors) // true

兼容性:IE 9+、Firefox 4+、Safari 5+、 Opera 10.5+ 和Chrome

5.2.2 转换方法

所有的对象都具有3.4.7 Object中所述的属性和方法

  • toLocaleString() 返回数组中每个值的字符串形式拼接而成的一个以逗号分隔的字符串
colors.toLocaleString(); // 'red,blue,green'
  • valueOf() 返回数组
colors.valueOf(); //['red', 'blue', 'green']
  • toString() 返回数组中每个值的字符串形式拼接而成的一个以逗号分隔的字符串

特别的 可以使用 join()方法指定分隔符的形式

colors.join("||"); // 'red||blue||green'

5.2.3 栈方法

栈是一种LIFO( Last-In-First-Out, 后进先出)的数据结构。 单向的,只发生在顶部。

  • push()方法 插入(推入)栈中 ,接受任意数量的参数,添加至数组末尾,返回修改后数组的长度
var count = colors.push("white", "pink");
colors // [ 'red', 'blue', 'green', 'white', 'pink' ]
count // 5
  • pop()方法 移除栈顶元素,不接受,返回一个移除的元素
colors.pop(); //pink
colors // [ 'red', 'blue', 'green', 'white' ]
colors.pop(2); "white"

5.2.4 队列方法

访问顺序:FIFO(First-In-First-Out,先进先出),从列表末端添加项,从列表前端移除项。

模拟队列的方法

  • push() + shif()

shift()方法从数组前端移除项

colors // [ 'red', 'blue', 'green']
colors.push("white"); // 4
color.shift();  // 'red'
  • unshift() + pop() 反方向模拟队列

unshift() 方法向数组前端添加任意项并返回新数组的长度

colors //[ 'blue', 'green', 'white' ]
colors.unshift("red", "black"); // 5
colors // [ 'red', 'black', 'blue', 'green', 'white' ]
colors.pop(); // white
colors // [ 'red', 'black', 'blue', 'green']

5.2.5 重排序方法

  • reverse() 反转数组

    colors // [ 'red', 'black', 'blue', 'green']
    colros.reverse(); // [ 'green', 'blue', 'black', 'red' ]
    
  • sort() 升序排列,先调用每个数组的toString()转型方法,然后(按照字符编码顺序)比较得到的字符串。

colors // [ 'green', 'blue', 'black', 'red' ]
colors.sort(); // [ 'black', 'blue', 'green', 'red' ]

WARNING!!!

var values = [1, 4, 3, 10, 5];
values.sort(); // [1, 10, 3, 4, 5]
10 ASCII码:48
5  ASCII码:53

5.2.6 操作方法

  • concat() 返回基于当前数组中的所有项创建新数组。
var colors = [ "red", "green", "blue"];
colors.concat("yellow", ["black", "brown"]); 
// [ 'red', 'green', 'blue', 'yellow', 'black', 'brown' ]
  • slice() 接受一个或两个参数,返回数组指定位置起始和结束位置中的所有项(不包括起始位置)
colors // [ "red", "green", "blue"]
colors.slice(1); // ["green", "blue"]
colors.slice(2, 3); // ['blue']
colors.slice(-1); // ['blue']
  • splice() 向数组中部插入项
colors // [ "red", "green", "blue"]
//删除 splice(start, count) 从start位置开始删除count项
colors.splice(0, 1) // ['red']

//插入 splice(start, 0, ["item1","item2",] )从start位置开始删除0项插入item1,item2,item3等,也可用于替换数组中的项
colors.splice(1, 0, "black", "white"); //[]
colors; //["green", "blue", "black", "white"]

5.2.7 位置方法

  • indexOf() 接收两个参数要查找的项和(可选的)查找起点位置的索引。从数组的开头开始向后查找。
var car = ["volvo", "volkswagen", "nissan", "land rover"]
car.indexOf("nissan"); // 0
  • lastIndexOf() 与indexOf()方法查找方向相反,都返回查找元素在数组中的位置
car.lastIndexOf("land rover"); // 3

5.2.8 迭代方法

5个迭代方法都接受两个参数,1.要在每一项上运行的函数 2. (可选的)运行该函数的作用域对象-影响this的值。 对数组中的每一项运行给定函数。

var numbers =[1, 2, 3, 4, 5, 4, 3, 2,  2];
//1. every() 每一项都返回true,则返回true
numbers.every(fucntion( item, index, array){
  return (item > 2);
}) // false

//2. some() 只要其中有一项返回true,则返回true
numbers.some(fucntion( item, index, array){
  return (item > 2);
}) // false

//3. filter() 返回true的项组成的数组
numbers.filter(fucntion( item, index, array){
  return (item > 2);
}) // [3, 4, 5, 4, 3]

//4. map() 返回经函数运行后的数组
numbers.map(fucntion( item, index, array){
  return item * 2;
}) //[ 2, 4, 6, 8, 10, 8, 6, 4, 4]

//5. forEach(), 无返回值,对数组每一项执行给定函数
numbers.forEach(fucntion( item, index, array){
  //执行操作
})

5.4 RegExp类型

ECMAScript通过RegExp类型支持正则

// 创建正则表达式
var expression = / pattern / flags;
  • pattern : 任何简单或复杂的正则表达式。

    • pattern中含有的元字符包括以下

    ( [ { \ ^ $ | ) ? * + . ] }

  • flags : 标明正则表达式的行为。 三种标志: g, i, m

flags:

g: (global)全局模式,应用于所有字符串,而非在发现第一个匹配项时立即停止

i: (case-insensitive)不区分大小写模式, 忽略匹配字符串大小写

m: (multiline)多行模式, 匹配至一行文本末继续查找下一行是否有匹配项

1.字面量形式创建正则表达式

//匹配字符串中所有的"at"的实例
var pattern1 = /at/g;

//匹配所有以"at"结尾的3个字符的组合,不区分大小写
var  pattern2 = /.at/gi;

//带有元字符的正则表达式
// 匹配第一个‘bat’或‘cat’,不区分大小写
var parttern3 = /[bc]at/i

2.使用RegExp构造函数创建正则, 参数均为字符串,必须对其中的元字符进行双重转义

var pattern = new RegExp(a, b);
//a为要匹配的字符串模式,b为可选的标志字符串

//example: 
  //1. 构造函数形式
    var pattern1 = new RegExp('/[bc]at', "g");

  //2. 对象字面量创建
    var pattern2 = /[bc]at/g

字面量模式与其等价的字符串

字面量模式 等价的字符串
/[bc]at/ "\[bc\]at"
/.at/ "//.at"

5.4.1 RegExp实例属性

每一个RegExp都具有以下属性:

  • global : 布尔值 , 是否设置了g 标志
  • ignoreCase : 布尔值, 是否设置了i标志
  • lastIndex : 整数,搜索下一个匹配项的字符位置,从0算起
  • multiline : 布尔值, 是否设置了m标志。
  • source : 返回匹配正则的字符串形式,而非返回传入构造函数中的字符串。
var pattern = /\[bc\]at/i;
pattern.global; //false
pattern.source; // "/\[bc\]at;

5.4.2 实例方法

  • exec(); 接受一个参数,要应用的字符串。返回包含第一个匹配项的信息的数组(Array的实例),或无匹配项的null
var text = "mom and dad and baby";
var pattern = /mom(and dad (and baby)?)?/gi;
var matches = pattern.exec(text);
matches; 
/*['mom and dad and baby',
   'and dad and baby',
   'and baby',
    index: 0,
    input: 'mom and dad and baby']
 */
matches.index; // 0
matches.input; // 'mom and dad and baby'
matches[2]; // 'and baby'
         
  • test(); 接受一个参数,传入应用的字符串,测试目标字符串是否与某个模式匹配,返回true or false。
var text = '000-00-0000';
var pattern = /\d{3}-\d{2}-d{4}/;
if(pattern.test(text)){
  console.log(The pattern was matched);
}
var tel = /\^1\d{10}/; 匹配11位手机号

5.5 Function 类型

每个函数都是Function类型的实例,都与其他引用类型一样具有属性和方法。

由于函数是对象,因此函数名实际上也是一个指向函数对象的指针,不会与某个函数绑定。

//函数声明语法定义
function functionName ( arg1, arg2, ...){
  //表达式;
}

5.5.1 函数没有重载

依次声明的重名函数,后面的函数会覆盖前面的函数。

5.5.2 函数声明与函数表达式

代码执行前,解析器通过函数声明提升( function declaration hoisting),读取并将函数声明添加到执行环境中。

//函数声明提升(function declaration hoisting)
alert(sum(10, 10)); //20
function sum(num1, num2){
  return num1 + num2;
}

// 通过函数初始化执行,初始化需在调用函数表达式之前
alert(sum(10, 10)); // sum is not defined
var sum = function( sum1, sum2){
  return sum1 + sum2;
};

5.5.3 作为值的函数

要访问函数的指针而不执行函数的话,必须去掉函数后面的圆括号。

function callSomeFunction( someFunction, someArgument){
  return someFunction (someArgument);
}
function sum(num){
  return num + 10;
}
var result = callSomeFunction(sum, 10); //访问函数sum的指针而不执行,不必加括号
result; //20

5.5.4 函数内部属性

函数内部有两个特殊的对象:arguments 和 this;

  • arguments : 类数组对象,包括一个callee的属性,为指针,指向拥有这个arguments对象的函数。

    function sum(num){
      if(num <= 1){
          return 1;
      }else {
        return num + sum(num-1);
      }
    }
    sum(5); //15
    
    // 使用arguments的callee属性重写, 内部函数表达式不受外部函数名影响。(非严格模式下)
    function sum(num){
      if(num <= 1){
        return 1;
      }else{
        return num + arguments.callee(num-1);
      }
    }
    
    • this : 函数据以执行的环境对象。

    5.5.5 函数属性和方法

    函数包含的共有属性:length 和 prototype。

    • length : 函数接受参数的个数。
    • prototype : 保存所有的实例方法,不可枚举,无法用for-in发现。

    函数包含的非继承的共有方法 :apply() 和 call(), 都是在特定的作用域中调用函数,实际上等于设置函数体内this对象的值。接受两个参数:1.函数运行的作用域,2. 参数数组(arguments对象或Array的实例)。使用call()方法时,传递给函数的参数必须逐个列举出来。

    function sum(num1, num2){
      return num1 + num2;
    }
    function callSum(num1, num2){
      return sum.apply(this, arguments);
    }
    //或
    function callSum(num1, num2){
      return sum.apply(this, [num1, num2]);
    }
    //call()方法的应用
    function callSum(num1, num2){
      return sun.call(this, num1, num2); //其中传入的参数要在call()中列出,而不是以arguments对象形式或数组形式
    }
    
    callSum(10, 10); //20  
    
    • bind() : 创建一个函数的实例,其this值会被绑定到传给bind()函数的值。

      兼容性:IE9+, Safari 5.1+, Firefox 4+, Opera 12+ 和 Chrome

    window.color = "blue";
    var o = { color : "red"};
    function sayColor(){
      console.log(this.color);
    }
    var objectSayColor = sayColor.bind(o);
    objectSayColor();  // "red"
    

    5.6 基本包装类型

    ECMAScript 的三个特殊引用类型: Boolean 、Number和String。

    基本包装类型:具有与各自的基本类型相应的特殊行为。

    引用类型:在执行流离开当前作用域之前一直保存在内存中

    基本包装类型:自动创建的基本包装类型的对象只存在于一行代码的执行瞬间,然后立即被销毁,意味着不能在运行时为基本类型添加属性和方法。

5.6.3 String类型

  1. trim()方法:ECMASctipt 5为所有字符串定义了trim()方法,创建一个字符串副本,删除前置及后缀所有空格。

    var stringValue = "  Hello world   ";
    stringValue.trim(); // 'Hello world'
    
  2. 字符串大小写转换

    • toLowerCase()
    • toUpperCase()
    • toLocaleLowerCase() 针对特定地区的实现
    • toLocaleUpperCase() 针对特定地区的实现
    var stringValue = "hello world";
    stringValue.toUpperCase(); // 'HELLO WORLD'
    stringValue.toLowerCase(); //'hello world'
    stringValue.toLocaleUpperCase(); //'HELLO WORLD'
    stringValue.toLocaleLowerCase(); // 'hello world'
    

    5.7 单体内置对象

    5.7.1 Global对象

    所有在全局作用域中定义的属性和方法都是Global对象的属性。

    1. URL编码方法

    encodeURI()和encodeURIComponent()方法都可以对URI(Uniform Resource Identifiers,通用资源标识符)尽兴编码,用特殊的UTF-8编码替换所有无效的字符,从而让浏览器能够接受和理解。

    • encodeURI()主要用于整个URI,只会替换空格,替换为%20。

      var uri = "http://www.wrox.com/illegal value.htm#start";
      encodeURI(uri); // 'http://www.wrox.com/illegal%20value.htm#start'
      
    • encodeURIComponent() 主要用于URI中的某一段,对其中的任何非标准字符进行编码。

      encodeURIComponent(uri); 
      //'http%3A%2F%2Fwww.wrox.com%2Fillegal%20value.htm%23start'
      

    decodeURI()和decodeURIComponent()可以对用encodeURI()和encodeURIComponent()编码的特殊字符进行解码。

    • decodeURI()和decodeURIComponent()

      var uri = "http%3A%2F%2Fwww.wrox.com%2Fillegal%20value.htm%23start";
      decodeURIComponent(uri);
      //'http://www.wrox.com/illegal value.htm#start'
      

    5.7.2 Math对象

    1. min()和max()方法,确定一组数值中的最小值和最大值。

      Math.max(3, 54, 32, 16); // 54
      Math.min(3, 54, 32, 16) // 3
      
      var values = [1, 2, 4, 5, 7];
      Math.max.apply(Math, values); // 7
      
    2. 舍入方法

      • Math.ceil(); 向上舍入

        Math.ceil(25.9); //26
        
      • Math.floor 向下舍入,舍去小数

      • Math.round(),标准舍入即四舍五入

    3. random()方法 返回大于等于0小于1的一个随机数。

      从某个整数范围内随机选择一个值

      值 = Math.floor(Math.random() * 可能值的总数 + 第一个可能的值)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,390评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,821评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,632评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,170评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,033评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,098评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,511评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,204评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,479评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,572评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,341评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,893评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,171评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,486评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,676评论 2 335

推荐阅读更多精彩内容