( 栈区,堆区 ) + ( 字符串扩展 ) + ( Error对象 ) + ( 数组扩展 )

(一) 栈区和堆区

https://segmentfault.com/a/1190000002789651 (重要)

js变量有两种不同的数据类型: ( 基本类型 ) 和 ( 引用类型 )

  • 基本数据类型有:number,string,boolean,null,undefined
  • 引用数据类型有:array,object。
  • array (是特殊的对象,属性是数值字符串)

(1) 基本类型的变量存放在栈区 (栈区指内存里的栈内存)

  • 栈区包括了( 变量的标识符 ) 和 ( 变量的值 )。
  • 不能给基本类型添加属性和方法,基本类型是不可变得;


    栈区:变量的标识符,变量的值

(2) 引用类型的值是同时保存在栈内存和堆内存中的对象

  • 引用类型的存储需要内存的栈区和堆区(堆区是指内存里的堆内存)共同完成,栈区内存保存变量标识符和指向堆内存中该对象的指针,也可以说是该对象在堆内存的地址。
  • 引用类型的存储需要 ( 栈区 ) 和 ( 堆区 )
  • 栈区保存 ( 变量的标识符 ) 和 ( 指向堆区中该对象的指针 )
  • 引用类型可以拥有属性和方法,属性又可以包含基本类型和引用类型。


    引用类型的值存储需要栈区和堆区,栈区中是标识符和指针,堆区中的对象

(3) 基本类型赋值

对基本类型的直接复制,是拷贝,值互不影响

(4) 对象的引用

对象直接复制,是对象的引用,复制的是指向堆区中同一对象的指针,改变一个,另一个也会变






(一) ES6字符串扩展

es5中,只有 indexOf() 方法可以确定一个字符串是否包含在另一个字符串中

(1) includes():返回布尔值,表示是否找到了参数字符串。

(2) startsWith():返回布尔值,表示参数字符串是否在原字符串的头部。

(3) endsWith():返回布尔值,表示参数字符串是否在原字符串的尾部。

  • 这三个方法都支持第二个参数,表示开始搜索的位置
  • 使用第二个参数n时,endsWith的行为与其他两个方法有所不同。它针对前n个字符,而其他两个方法针对从第n个位置直到字符串结束。

let s = 'Hello world!';

s.startsWith('Hello') // true
s.endsWith('!') // true
s.includes('o') // true


--------------------------------------------------------------------


let s = 'Hello world!';

s.startsWith('world', 6) // true
s.endsWith('Hello', 5) // true             // 注意注意注意! 5表示  前5个字符
s.includes('Hello', 6) // false

上面代码表示,使用第二个参数n时,endsWith的行为与其他两个方法有所不同。

endsWith 针对前n个字符,而其他两个方法针对从第n个位置直到字符串结束。

(4) repeat()

repeat方法返回一个新字符串,表示将原字符串重复n次。

  • 参数如果是小数,会被取整,即只取整数部分。(!!!!)
  • 如果repeat的参数是负数或者Infinity,会报错
  • 如果参数是 0 到-1 之间的小数,则等同于 0,这是因为会先进行取整运算。0 到-1 之间的小数,取整以后等于-0,repeat视同为 0。
  • 参数NaN等同于 0。
  • 如果repeat的参数是字符串,则会先转换成数字。

'x'.repeat(3) // "xxx"
'hello'.repeat(2) // "hellohello"
'na'.repeat(0) // ""
'na'.repeat(2.9) // "nana"    ---- 参数是小数,取整

'na'.repeat(Infinity)         ---- 报错
// RangeError
'na'.repeat(-1)               ---- 报错
// RangeError

'na'.repeat(-0.9) // ""
'na'.repeat(NaN) // ""
'na'.repeat('na') // ""
'na'.repeat('3') // "nanana"      ---- 参数是字符串,会被转换成数字

(5) padStart(),padEnd()

ES2017 引入了字符串补全长度的功能。如果某个字符串不够指定长度,会在头部或尾部补全。padStart()用于头部补全,padEnd()用于尾部补全。

  • pad:是垫,补全的意思
  • padStart和padEnd一共接受两个参数:
    第一个参数用来指定字符串的长度。
    第二个参数是用来补全的字符串。
  • 如果原字符串的长度,等于或大于指定的最小长度,则返回原字符串。
  • 如果用来补全的字符串与原字符串,两者的长度之和超过了指定的最小长度,则会截去超出位数的补全字符串。
  • 如果省略第二个参数,默认使用空格补全长度。
padStart的常见用途是为数值补全指定位数。下面代码生成 10 位的数值字符串。
'1'.padStart(10, '0') // "0000000001"              // padStart主要用于给数值补全位数
'12'.padStart(10, '0') // "0000000012"
'123456'.padStart(10, '0') // "0000123456"
padStart另一个用途是提示字符串格式。
'12'.padStart(10, 'YYYY-MM-DD') // "YYYY-MM-12"
'09-12'.padStart(10, 'YYYY-MM-DD') // "YYYY-09-12"

'x'.padStart(5, 'ab') // 'ababx'
'x'.padStart(4, 'ab') // 'abax'

'x'.padEnd(5, 'ab') // 'xabab'
'x'.padEnd(4, 'ab') // 'xaba'

-----------------------------------------------------

'xxx'.padStart(2, 'ab') // 'xxx'       -- 指定的字符串长度,小于原字符串长度,则返回原字符串
'xxx'.padEnd(2, 'ab') // 'xxx'

-----------------------------------------------------

'abc'.padStart(10, '0123456789')
// '0123456abc'

-----------------------------------------------------

'x'.padStart(4) // '   x'
'x'.padEnd(4) // 'x   '







(二) js错误的类型 ( 6种 ) ( 前4种常用 )

  1. SyntaxError : 语法错误
  2. ReferenceError : 引用错误 ( 要用的东西没有找到 ) ( reference 是引用的意思 )
  3. RangeError : 范围错误 ( 专指参数超出范围 )
  4. TypeError : 类型错误 ( 错误的调用了对象的方法 )
  5. EvalError: eval()方法错误的使用
  6. URIError: url地址错误

(1) Error对象

JavaScript 解析或运行时,一旦发生错误,引擎就会抛出一个错误对象。JavaScript 原生提供Error构造函数,所有抛出的错误都是这个构造函数的实例。

  • js解析或运行出错,都会抛出一个错误对象
  • 所有抛出的错误都是 Error构造函数的实例
  • 抛出Error实例对象以后,整个程序就中断在发生错误的地方,不再往下执行。
  • Error构造函数,接受一个参数,表示错误提示。可以从 实例对象的 message 属性读到这个参数

Error实例对象属性: ( message , name , stack )

  1. message:错误提示信息
  2. name:错误名称(非标准属性)
  3. stack:错误的堆栈(非标准属性)

let err = new Error('这是错误信息提示')      // Error构造函数,new执行构造函数,返回实例对象
       
console.log( err.message );                // 实例的 message 属性,读取Error构造函数的 参数
console.log( err.name );
console.log( err.stack );


// 抛出Error实例对象以后,整个程序就 ( 中断 ) 在发生错误的地方,不再往下执行。

// Error实例对象必须有message属性

SyntaxError对象是解析代码时发生的语法错误。    // 语法错误



ReferenceError对象是引用一个不存在的变量时发生的错误。  // 引用不存在的变量



RangeError对象是一个值超出有效范围时发生的错误。主要有几种情况,  // 超出范围,数组,对象方法,堆栈
一是 : 数组长度为负数,
二是 : Number对象的方法参数超出范围,以及函数堆栈超过最大值。



TypeError对象是变量或参数不是预期类型时发生的错误。                    // 类型错误
比如,对字符串、布尔值、数值等原始类型的值使用new命令,就会抛出这种错误,
因为new命令的参数应该是一个构造函数。



URIError对象是 URI 相关函数的参数不正确时抛出的错误,主要涉及这六个函数:
encodeURI()、decodeURI()、encodeURIComponent()、decodeURIComponent()、escape()、 unescape()



// 以上这6种派生错误,连同原始的Error对象,都是构造函数

// 这些构造函数都接受一个函数,代表错误提示信息(message)。

(2) throw 语句

throw语句的作用是手动中断程序执行,抛出一个错误。

  • throw语句,手动中断程序执行,并抛出一个错误
  • throw可以抛出任何类型的值。也就是说,它的参数可以是任何值。
let a=9;

if (a<10) {
  throw new Error('a不能小于10')   // throw抛出的错误,就是 Error构造函数的参数,程序会中止执行
}


// 对于 JavaScript 引擎来说,遇到throw语句,程序就中止了。

// 引擎会接收到throw抛出的信息,可能是一个错误实例,也可能是其他类型的值。



----------------------------------------------

// 抛出一个字符串
throw 'Error!';
// Uncaught Error!

// 抛出一个数值
throw 42;
// Uncaught 42

// 抛出一个布尔值
throw true;
// Uncaught true

// 抛出一个对象
throw {
  toString: function () {
    return 'Error!';
  }
};
// Uncaught {toString: ƒ}

(3) try…catch 结构

一旦发生错误,程序就中止执行了。JavaScript 提供了try...catch结构,允许对错误进行处理,选择是否往下执行。

  • try...catch结构,对错误进行处理,选择是否往下执行
  • catch代码块捕获错误之后,程序不会中断,会按照正常流程继续执行下去。( 重要 )
  • catch代码块之中,还可以再抛出错误,甚至使用嵌套的try...catch结构。
  • 为了捕捉不同类型的错误,catch代码块之中可以加入判断语句。
       let a=9;

       try {
           if (a<10) {
               throw new Error('错误,a不能小于10')    // throw手动中止执行,并抛出错误
               console.log('该条语句不会被执行,因为throw命令会中断代码执行')!!!!!!!!!
           }
       } catch (error) {    // 错误被catch代码块捕获,参数是try代码块抛出的值
           console.log(error.name + ':' + error.message)
       }
        console.log('该条语句会执行,因为catch捕获错误后,程序会按正常执行')!!!!!!!!!!


// 上面代码中,try代码块抛出错误(throw语句),JavaScript 引擎就立即把代码的执行,转到catch代码块,

// 或者说错误被catch代码块捕获了。catch接受一个参数,表示try代码块抛出的值。

(4) finally 代码块

try...catch结构允许在最后添加一个finally代码块,表示不管是否出现错误,都必需在最后运行的语句。

  • finally 表示不管是否出现错误,都必须在最后执行的语句

function cleansUp() {
  try {          // try代码块
    throw new Error('出错了……');  // throw手动中止程序,并抛出错误
    console.log('此行不会执行');    // trow会中止代码执行
  } finally {   // try抛出错误,会立即执行catch语句。 finally语句表示不管是否出现错误,都必须在最后执行
    console.log('完成清理工作');    // 因为没有catch ,所以执行完 finally后,程序就会中止( 因为throw )
  }
}

cleansUp()
// 完成清理工作
// Error: 出错了……


// 上面代码中,由于没有catch语句块,所以错误没有捕获。
// 执行finally代码块以后,程序就中断在错误抛出的地方。
(重要)

function idle(x) {
  try {
    console.log(x);
    return 'result';
  } finally {        // try语句未发生错误,finally依旧会执行。执行完finally,才会执行return !!!!
    console.log("FINALLY");
  }
}

idle('hello')
// hello
// FINALLY
// "result"


// 上面代码说明,try代码块没有发生错误,而且里面还包括return语句,但是finally代码块依然会执行。

// 注意,只有在其执行完毕后,才会显示return语句的值。




-------------------------------------

try {
   console.log('111111');
    return console.log('222222222');
} catch (err) {
     console.log('不执行');
} finally {
    console.log('333333333');
}

结果是: 
11111111
22222222
33333333
// 说明 return语句的执行是排在finally代码之前,只是等finally代码执行完毕后才返回。




------------------------------------------------

function test() {
   try {
      console.log('111111');
      return '222222222';
   } catch (err) {
      console.log('不执行');
  } finally {
      console.log('333333333');
  }
}
const returnValue = test();
console.log(returnValue)

结果是:
11111111
33333333
22222222
(重要)

var count = 0;
function countUp() {
  try {
    return count;
  } finally {
    count++;
  }
}

countUp()
// 0
count
// 1

// return语句的执行是排在finally代码之前,只是等finally代码执行完毕后才返回。

// return语句的count的值,是在finally代码块运行之前就获取了。

总结:

componentDidMount() {
      let x = 10;
      try {
          if (x < 11) {
              console.log('Error的实例中有 message,name,stack等属性');
              console.log('throw可以抛出任何类型的值');
              throw new Error('错误信息:x不能小于11');  // throw手动中断代码执行,并抛出错误
              console.log('该语句不会执行,trorw 会中止程序');
          } 
      } catch (err) {   // catch 捕获 try 代码块的错误,catch执行完,会正常执行程序
        console.log(err.message, 'err');   // err.message输出 Error 构造函数的参数
        console.log('catch 中还能抛出错误,也能再嵌套 try...catch...');
      } finally {   
        console.log('不管有没有错误,都会执行finally语句');
      }
      console.log('这句会正常执行,catch不会中止程序执行');
      console.log('js错误类型有 SyntaxError语法错误');
      console.log('js错误类型有 ReferenceError引用错误,引用不存在的变量');
      console.log('js错误类型有 RangeError范围错误,如数组的长度为负');
      console.log('js错误类型有 TypeError类型错误,变量或参数不是预期类型');
}


执行结果:
//Error的实例中有 message,name,stack等属性
//username.js:78 throw可以抛出任何类型的值
//username.js:83 错误信息:x不能小于11 err
//username.js:84 catch 中还能抛出错误,也能再嵌套 try...catch...
//username.js:86 不管有没有错误,都会执行finally语句
//username.js:88 这句会正常执行,catch不会中止程序执行
//username.js:89 js错误类型有 SyntaxError语法错误
//username.js:90 js错误类型有 ReferenceError引用错误,引用不存在的变量
//username.js:91 js错误类型有 RangeError范围错误,如数组的长度为负
//username.js:92 js错误类型有 TypeError类型错误,变量或参数不是预期类型






(三) 数组的扩展

(1) 扩展运算符

  • 扩展运算符后面还可以放置表达式。
  • 如果扩展运算符后面是一个空数组,则不产生任何效果。
表达式
const arr = [
  ...(x > 0 ? ['a'] : []),
  'b',
];



空数组
[...[], 1]
// [1]
  • 获取数组最大的元素 (重要)
// ES5 的写法
Math.max.apply(null, [14, 3, 77])     // apply第一个参数是需要绑定的对象,第二个参数只能是数组

// ES6 的写法
Math.max(...[14, 3, 77])

// 等同于
Math.max(14, 3, 77);

扩展运算符的运用

(1) 复制数组

数组是复合类型的数据结构,直接复制,只是复制了指向底层数据结构的指针,而不是克隆一个全新的数组。

  • 基本类型和引用类型 https://segmentfault.com/a/1190000002789651
  • 直接复制引用类型的值(数组),其实只是复制了栈区 指向 堆区同一对象的 指针,并不是对象的克隆,改变其中一个值,另一个值就会跟着改变。
const a1 = [1, 2];
const a2 = a1;

a2[0] = 2;
a1 // [2, 2]

// 上面代码中,a2并不是a1的克隆,而是指向同一份数据的另一个指针。修改a2,会直接导致a1的变化

// a1有指向堆区 该数组的指针
// a2只是复制了 栈区,栈区的指针也指向了 堆区的该数组
// 改变其中一个变量,另一个也会改变
真正的克隆 (复制)

(1) es5中的 concat() 方法  ------------------------ concat()方法

const a1 = [1, 2];
const a2 = a1.concat();

a2[0] = 2;
a1 // [1, 2]

// concat方法用于多个数组的合并。它将新数组的成员,添加到原数组成员的后部,然后返回一个新数组,原数组不变。
// 除了数组作为参数,concat也接受其他类型的值作为参数,添加到目标数组尾部。



------------------------------------

(2) es6 扩展运算符 ------------------------------- 数组的浅拷贝

let a1 = [1,2,3];
// let a2 = [...a1];
let [...a2] = a1;
a2.push(4);
console.log(a1);
console.log(a2);

// 上面的两种写法,a2都是a1的克隆。

(2) 将字符串转为真正的数组。-------- (重要)

[...'hello']          

// [ "h", "e", "l", "l", "o" ]

// 展开运算符可以用于所有具有iterator接口的数据


// 原生具有 iterator 接口的数据结构 :
(1) array,
(2) string,
(3) NodeList对象, ----- 如:document.querySellectorAll()返回值
(4) TypedArray,  
(5) 函数的 arguments 对象     ----------- arguments对象包含了函数运行时的所有参数,用[下标]取值
(6) Map
(7) Set
  • 注意:对于那些没有部署 Iterator 接口的类似数组的对象,扩展运算符就无法将其转为真正的数组。
let arrayLike = {
  '0': 'a',
  '1': 'b',
  '2': 'c',
  length: 3
};

// TypeError: Cannot spread non-iterable object.              // spread 是扩展的意思
let arr = [...arrayLike];

(2) Array.from()

Array.from方法用于将两类对象转为真正的数组:
(1) 类似数组的对象(array-like object),
(2) 可遍历(iterable)的对象(包括 ES6 新增的数据结构 Set 和 Map)。

Array.from还可以接受第二个参数,作用类似于数组的map方法,用来对每个元素进行处理,将处理后的值放入返回的数组。

  • Array.from()将 类似数组的对象可遍历的对象 转化成真正的 数组
  • 只要是部署了 Iterator 接口的数据结构,Array.from 都能将其转为数组。!!!
  • 只要是部署了 Iterator 接口的数据结构,展开运算符 都能将其转为数组。!!!
  • 如果参数是一个真正的数组,Array.from会返回一个一模一样的新数组。
  • 所谓类似数组的对象,本质特征只有一点,即必须有length属性。因此,任何有length属性的对象,都可以通过Array.from方法转为数组,而此时扩展运算符就无法转换。(重要) !!!
let arrayLike = {
    '0': 'a',
    '1': 'b',
    '2': 'c',
    length: 3
};

// ES5的写法
var arr1 = [].slice.call(arrayLike); // ['a', 'b', 'c']

// ES6的写法
let arr2 = Array.from(arrayLike); // ['a', 'b', 'c']

// Array.from() 将类似数组的对象 转换成真正的 数组    ( array like object )

// ( 展开运算符 ) 只能是对具有iterator接口的数据类型转换成数组,不适用于一般的类似数组的对象
  • 实际应用中,常见的类似数组的对象是 DOM 操作返回的 NodeList 集合,以及函数内部的arguments对象。Array.from都可以将它们转为真正的数组。
  • NodeList对象 和 arguments对象具有iterator接口,所以也能用 展开运算符转换成数组
function a(x,y,z) {
    console.log( arguments );  // Arguments(3) [1, 2, 3, callee: ƒ, Symbol(Symbol.iterator): ƒ]
    console.log( arguments[0]);               // 1
    console.log( Array.from(arguments) );     // [1, 2, 3]
    console.log( [...arguments]);             // [1, 2, 3]
    return x+y+z;
}
a(1,2,3);

总结: 
(1) Array.from能将类似数组的对象 转换成真正的数组
(2) 具有iterator接口的数据结构,都可以通过展开运算符,转换成数组。
(3) 原生具有iterator接口的数据结构有:array,string,NodeList,arguments,map,set,TypedArray
(4)  ( NodeList对象 ),( arguments对象 ) 可以通过 ( Array.from() ) 和 ( 展开运算符 )转化为数组

  • 所谓类似数组的对象,本质特征只有一点,即必须有length属性。因此,任何有length属性的对象,都可以通过Array.from方法转为数组,而此时扩展运算符就无法转换。(重要) !!!
Array.from({ length: 3 });
// [ undefined, undefined, undefined ]
Array.from还可以接受第二个参数,作用类似于数组的map方法,用来对每个元素进行处理,将处理后的值放入返回的数组。 (重要)!!!
Array.from(arrayLike, x => x * x);
// 等同于
Array.from(arrayLike).map(x => x * x);

Array.from([1, 2, 3], (x) => x * x)
// [1, 4, 9]

(重要)(重要)(重要)(重要)(重要)(重要)(重要)(重要)(重要)(重要)(重要)

function a(x,y,z) {
    console.log( Array.from(arguments, x => (x+'').repeat(4)) );    // ["1111", "2222", "3333"]
    console.log( [...arguments]);
    return x+y+z;
}
a(1,2,3)

下面的例子将数组中布尔值为false的成员转为0。

Array.from([1, , 2, , 3], (n) => n || 0)
// [1, 0, 2, 0, 3]

Array.from()可以将各种值转为真正的数组,并且还提供map功能。这实际上意味着,只要有一个原始的数据结构,你就可以先对它的值进行处理,然后转成规范的数组结构,进而就可以使用数量众多的数组方法

Array.from({ length: 2 }, () => 'jack')
// ['jack', 'jack']

(3) Array.of()

Array.of方法用于将一组值,转换为数组。

  • 这个方法的主要目的,是弥补数组构造函数Array()的不足。因为参数个数的不同,会导致Array()的行为有差异。
  • Array.of基本上可以用来替代Array()或new Array(),并且不存在由于参数不同而导致的重载。它的行为非常统一。
  • Array.of总是返回参数值组成的数组。如果没有参数,就返回一个空数组
Array.of(3, 11, 8) // [3,11,8]
Array.of(3) // [3]
Array.of(3).length // 1
Array.of() // []
Array.of(undefined) // [undefined]
Array.of(1) // [1]
Array.of(1, 2) // [1, 2]
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,634评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,951评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,427评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,770评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,835评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,799评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,768评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,544评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,979评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,271评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,427评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,121评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,756评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,375评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,579评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,410评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,315评论 2 352

推荐阅读更多精彩内容