JS Array 对象——(1)

一、Array 对象

  Array 对象用于在变量中存储多个值;数组是一种具有相同类型值的集合,它的每一个值称为数组的一个元素。数组用于在内存中存储大量相同类型的数据,可以通过数组的名称和下标来访问数组中的元素。

二、数组属性

属 性 含 义
constructor         引用数组对象的构造函数
length       返回数组元素的个数。如果在创建数组时指定了数组的长度,那么无论数组元素中是否超出了实际数据,该属性的值都是这个指定的长度值
prototype       用于在定义数组时添加新的属性和方法。Prototype 是数组对象的静态属性。

三、Array对象方法

序 号 方法 描述
1 concat()   连接两个或更多的数组,并返回结果;
2 conpyWithin()   浅复制数组的一部分到同一数组中的另一个位置,并返回它,不会改变原数组的长度;
3 entries()   从原数组创建一个可迭代对象, 该对象包含了数组的键/值对
4 every()   测试一个数组内的元素是否都能通过某个指定函数的测试,返回一布尔值;
5 fill()   用一个固定值填充一个数组中从起始索引到终止索引内的全部元素。不包括终止索引;
6 filter()   创建一个新数组, 其包含通过所提供函数实现的测试的所有元素
7 find()   返回数组中满足提供的测试函数的第一个元素的值,否则返回 undefined
8 findIndex()   返回数组中满足提供的测试函数的第一个元素的索引,否则返回-1
9 flat()   按照一个可指定的深度递归遍历数组,将嵌套数组和原数组组合成一维数组返回;
10 flatMap()   首先使用映射函数映射每个元素,然后将结果压缩成一个新数组。它与 map 和 深度值1的 flat 几乎相同,但 flatMap 通常在合并成一种方法的效率稍微高一些;
11 forEach()   对数组的每个元素都执行一次回调函数;
12 from()   从一个类似数组或可迭代的对象中创建一个新的、浅拷贝的数组实例;
13 includes()   判断一个数组是否包含一个指定的值,包含则返回 true,否则返回false;
14 indexOf()   在数组中找到给定的元素的第一个索引,如果找不到,则返回-1。
15 isArray()   判断对象是否为数组;
16 join()   把数组的所有元素放入一个字符串;
17 keys()   返回数组的可迭代对象,包含原始数组的键(key);
18 lastIndexOf()   搜索数组中的元素,并返回它最后出现的位置索引,没有则返回-1;
19 map()   通过指定函数处理数组的每个元素,并返回处理后的数组;
20 pop()   删除数组的最后一个元素并返回删除的元素;
21 push()   向数组的末尾添加一个或更多元素,并返回新的长度;
22 reduce()   将数组元素计算为一个值(从左到右);
23 reduceRight()   将数组元素计算为一个值(从右到左);
24 reverse()   反转数组的元素顺序;
25 shift()   删除并返回数组的第一个元素;
26 slice()   选取数组的的一部分,并返回一个新数组;
27 some()   检测数组元素中是否有元素符合指定条件;
28 sort()   对数组的元素进行排序;
29 splice()   从数组中添加或删除元素;
30 toString()   把数组转换为字符串,并返回结果;
31 unshift()   向数组的开头添加一个或更多元素,并返回新的长度;
32 values() 返回一个新的 Array Iterator 对象,该对象包含数组每个索引的值;

具体用法:

1、concat() 的使用

   用于合并两个或多个数组。此方法不会更改现有数组,而是返回一个新数组

语法: var new_array = old_array.concat(value1[, value2[, ...[, valueN]]])

参数:
  |---valueN 可选,将数组和/或值连接成新数组。详情请参阅下文描述;

返回值:
  |---新的Array实例。

  合并三个数组的值

   var arr1 = ["A", "B"];
   var arr2 = ["a", "b", "c"];
   var arr3 = ["Aa"];
   var arr4 = t1.concat(t2,t3);
结果:
       arr4 = ["A", "B", "a", "b", "c", "Aa"]
2、conpyWithin() 的使用

  浅复制数组的一部分到同一数组中的另一个位置,并返回它,不会改变原数组的长度。

语法:arr.copyWithin(target[, start[, end]])

参数:
  |--- target:复制序列开始的位置(即,原数据保留的元素位置),负数target 将从末尾开始计算,如果 target 大于等于 arr.length,将会不发生拷贝;
  |--- start:复制元素的起始位置。如果是负数,start 将从末尾开始计算,如果 start 被忽略,将会从0开始复制;
  |--- end:复制元素的结束位置,将会拷贝到该位置,但不包括 end 这个位置的元素。如果是负数,将从末尾开始计算,如果 end 被忽略,将会一直复制至数组结尾;

返回值:
  |--- 改变后的数组。

实例:Array.conpyWithin()

  let numbers = [1, 2, 3, 4, 5];

  numbers.copyWithin(-2);
  // [1, 2, 3, 1, 2]

  numbers.copyWithin(0, 3);
  // [4, 5, 3, 4, 5]

  numbers.copyWithin(0, 3, 4);
  // [4, 2, 3, 4, 5]

  numbers.copyWithin(-2, -3, -1);
  // [1, 2, 3, 3, 4]

3、entries() 的使用

  从数组 arr 创建一个可迭代对象, 该对象包含了数组的键值对

语法:arr.entries()

返回值:
  |--- 一个新的 Array迭代器对象。Array Iterato是对象,它的原型(proto:Array Iterator)上有一个next 方法,可用用于遍历迭代器取得原数组的[key,value]。

实例1:Array.entries()

  var array1 = ['a', 'b', 'c'];
  var iterator1 = array1.entries();
  console.log(iterator1.next().value);
  //  [0, "a"]
  console.log(iterator1.next().value);
  //  [1, "b"]

实例2:iterator.next()

  var arr = ["a", "b", "c"];
  var iterator = arr.entries();
  console.log(iterator.next());

  /*{value: Array(2), done: false}
          done:false
          value:(2) [0, "a"]
           __proto__: Object
  */
  // iterator.next()返回一个对象,对于有元素的数组,
  // 是next{ value: Array(2), done: false };
  // next.done 用于指示迭代器是否完成:在每次迭代时进行更新而且都是false,
  // 直到迭代器结束done才是true。
  // next.value是一个["key","value"]的数组,是返回的迭代器中的元素值。

实例3:iterator.next方法运行

  var arr = ["a", "b", "c"];
  var iter = arr.entries();
  var a = [];

  // for(var i=0; i< arr.length; i++){   // 实际使用的是这个 
  for(var i=0; i< arr.length+1; i++){    // 注意,是length+1,比数组的长度大
      var tem = iter.next();             // 每次迭代时更新next
      console.log(tem.done);             // 这里可以看到更新后的done都是false
      if(tem.done !== true){             // 遍历迭代器结束done才是true
          console.log(tem.value);
          a[i]=tem.value;
      }
  }
      
  console.log(a);                         // 遍历完毕,输出next.value的数组

结果:
  false
  [0, "a"]
  false
  [1, "b"]
  false
  [2, "c"]
  true
  (3) [Array(2), Array(2), Array(2)]
  0: (2) [0, "a"]
  1: (2) [1, "b"]
  2: (2) [2, "c"]
    length: 3
  __proto__: Array(0)

实例4:二维数组按行排序

function sortArr(arr) {
    var goNext = true;
    var entries = arr.entries();
    while (goNext) {
        var result = entries.next();
        if (result.done !== true) {
            result.value[1].sort((a, b) => a - b);
            goNext = true;
        } else {
            goNext = false;
        }
    }
    return arr;
}

var arr = [[1,34],[456,2,3,44,234],[4567,1,4,5,6],[34,78,23,1]];
sortArr(arr);

/*(4) [Array(2), Array(5), Array(5), Array(4)]
    0:(2) [1, 34]
    1:(5) [2, 3, 44, 234, 456]
    2:(5) [1, 4, 5, 6, 4567]
    3:(4) [1, 23, 34, 78]
    length:4
    __proto__:Array(0)
*/

实例5:使用 for…of 循环

var arr = ["a", "b", "c"];
var iterator = arr.entries();
// undefined

for (let e of iterator) {
    console.log(e);
}

// [0, "a"] 
// [1, "b"] 
// [2, "c"]
4、every() 的使用

   测试一个数组内的所有元素是否都能通过某个指定函数的测试,返回一个布尔值;
注意:若收到一个空数组,此方法在一切情况下都会返回 true

语法:arr.every(callback[, thisArg])

参数:
  |---callback:用来测试每个元素的函数,它可以接收三个参数:
     |--- currentValue: 用于测试的当前值;
     |--- index: 可选,用于测试的当前值的索引;
     |--- array: 可选,调用 every 的当前数组;
  |---thisArg:执行 callback 时使用的 this 值;

返回值:
  |---如果回调函数的每一次返回都为 truthy 值,返回 true ,否则返回 false。

说明:
every() 方法用于检测数组所有元素是否都符合指定条件(通过函数提供)。
every() 方法使用指定函数检测数组中的所有元素:

  • 如果数组中检测到有一个元素不满足,则整个表达式返回 false ,且剩余的元素不会再进行检测。
  • 如果所有元素都满足条件,则返回 true。

注意: every() 不会对空数组进行检测。
注意: every() 不会改变原始数组。

实例1:检测所有数组元素的大小

  //检测数组中的所有元素是否都大于 10
  function isBigEnough(currentValue, index, array) {
    return element >= 10;
  }
  [12, 5, 8, 130, 44].every(isBigEnough);   // false
  [12, 54, 18, 130, 44].every(isBigEnough); // true

实例2:使用箭头函数

  [12, 5, 8, 130, 44].every(x => x >= 10); // false
  [12, 54, 18, 130, 44].every(x => x >= 10); // true
5、fill() 的使用

   用一个固定值填充一个数组中从起始索引到终止索引内的全部元素。不包括终止索引;

语法:arr.fill(value[, start[, end]])

参数:
  |---value:用来填充数组元素的值;
  |---start:可选,起始索引,默认值为0;
  |---end:可选,终止索引,默认值为 this.length;

返回值:
  |--- 修改后的数组。

实例1:

  [1, 2, 3].fill(4);               // [4, 4, 4]
  [1, 2, 3].fill(4, 1);            // [1, 4, 4]
  [1, 2, 3].fill(4, 1, 2);         // [1, 4, 3]
  [1, 2, 3].fill(4, 1, 1);         // [1, 2, 3]
  [1, 2, 3].fill(4, 3, 3);         // [1, 2, 3]
  [1, 2, 3].fill(4, -3, -2);       // [4, 2, 3]
  [1, 2, 3].fill(4, NaN, NaN);     // [1, 2, 3]
  [1, 2, 3].fill(4, 3, 5);         // [1, 2, 3]
  Array(3).fill(4);                // [4, 4, 4]
  [].fill.call({ length: 3 }, 4);  // {0: 4, 1: 4, 2: 4, length: 3}
  
  // Objects by reference.
  var arr = Array(3).fill({}) // [{}, {}, {}];
  arr[0].hi = "hi"; // [{ hi: "hi" }, { hi: "hi" }, { hi: "hi" }]
6、filter() 的使用

   filter() 方法创建一个新数组, 其包含通过所提供函数实现的测试的所有元素

语法:var newArray = arr.filter(callback(element[, index[, array]])[, thisArg])

参数:
  |---callback:用来测试每个元素的函数,它可以接收三个参数:
     |--- currentValue:数组中当前正在处理的值;
     |--- index: 可选,正在处理的元素在数组中的索引;
     |--- array: 可选,调用了 filter 的数组本身;
  |---thisArg:可选,执行 callback 时,用于 this 的值;

返回值:
  |---一个新的、由通过测试的元素组成的数组,如果没有任何数组元素通过测试,则返回空数组。

实例1:筛选排除所有较小的值

 function isBigEnough(element) {
      return element >= 10;
  }
 var filtered = [12, 5, 8, 130, 44].filter(isBigEnough);
  //  [12, 130, 44]

 //回调函数箭头函数写法
 var filtered2 = [12, 5, 8, 130, 44].filter((element)=> element >= 10);
 //  [12, 130, 44]

实例2:过滤 JSON 中的无效条目

     //使用 filter() 创建具有非零 id 的元素的 json
        var arr = [
            { id: 15 },
            { id: -1 },
            { id: 0 },
            { id: 3 },
            { id: 12.2 },
            { },
            { id: null },
            { id: NaN },
            { id: 'undefined' }
        ];

        var invalidEntries = 0;

        function isNumber(obj) {
            return obj !== undefined && typeof(obj) === 'number' && !isNaN(obj);
        }

        function filterByID(item) {
            if (isNumber(item.id) && item.id !== 0) {
                return true;
            }
            invalidEntries++;
            return false;
        }

        var arrByID = arr.filter(filterByID);

        console.log('过滤后的数组:\n', arrByID);
        // Filtered Array
        // [{ id: 15 }, { id: -1 }, { id: 3 }, { id: 12.2 }]

        console.log('无效的条目数 = ', invalidEntries);
        // 无效的条目数  = 5
/*******************************************************************************************/
   //用箭头函数简化代码,如下
        var invalidEntries2 = 0;
        var arrByID2 = arr.filter((item) =>
            item.id !== undefined && typeof(item.id ) === 'number' && !isNaN(item.id ) && item.id !== 0 ?
            true : (invalidEntries2++,false)
        );
        console.log('\n过滤后的数组2:\n', arrByID2);
        // Filtered Array
        // [{ id: 15 }, { id: -1 }, { id: 3 }, { id: 12.2 }]

        console.log('无效的条目数2 = ', invalidEntries2);
        // 无效的条目数  = 5

实例3:在数组中搜索

      //使用 filter() 根据搜索条件来过滤数组内容
        var fruits = ['apple', 'banana', 'grapes', 'mango', 'orange'];

        /**
         * Array filters items based on search criteria (query)
         */
        function filterItems(query) {
            return fruits.filter(function(el) {
                return el.toLowerCase().indexOf(query.toLowerCase()) > -1;
            })
        }
        console.log(filterItems('ap')); // ['apple', 'grapes']
        console.log(filterItems('an')); // ['banana', 'mango', 'orange']

 /*************************************************************************************/
        //箭头函数写法
        const filterItems2 = (query) => {
            return fruits.filter((el) =>
                el.toLowerCase().indexOf(query.toLowerCase()) > -1
            );
        }
        console.log(filterItems2('ap')); // ['apple', 'grapes']
        console.log(filterItems2('an')); // ['banana', 'mango', 'orange']
7、find() 的使用

   find() 方法返回数组中满足提供的测试函数的第一个元素的值。否则返回 undefined

语法:arr.find(callback[, thisArg])

参数:
  |--- callback:在数组每一项上执行的函数,接收 3 个参数:
     |--- currentValue:当前遍历到的元素;
     |--- index: 可选,当前遍历到的索引;
     |--- array: 可选,数组本身;
  |---thisArg:执行回调时用作this 的对象;

返回值:
  |--- 数组中第一个满足所提供测试函数的元素的值,否则返回 undefined。

实例1:用对象的属性查找数组里的对象

       var inventory = [
            {name: 'apples', quantity: 2},
            {name: 'bananas', quantity: 0},
            {name: 'cherries', quantity: 5}
        ];

        function findCherries(fruit) {
            return fruit.name === 'cherries';
        }
        console.log(inventory.find(findCherries)); // { name: 'cherries', quantity: 5 }

/********************************************************************************/
        //箭头函数简化写法
        console.log(inventory.find((fruit)=>fruit.name === 'cherries'));

实例2:寻找数组中的质数

//如何从一个数组中寻找质数(如果找不到质数则返回undefined)
      function isPrime(element, index, array) {
           var start = 2;
           while (start <= Math.sqrt(element)) {
               if (element % start++ < 1) {
                    return false;
               }
            }
            return element > 1;
        }

        console.log([4, 6, 8, 12].find(isPrime)); // undefined, not found
        console.log([4, 5, 8, 12].find(isPrime)); // 5
 /*****************************************************************************/
        console.log([4, 5, 8, 12].find((item)=>{
            var st = 2;
            while (st <= Math.sqrt(item)){
                if (item % st++ < 1) {
                    return false;
                }
            }
            return item > 1;
        })); // 5
8、findIndex() 的使用

  findIndex()方法返回数组中满足提供的测试函数的第一个元素的索引。否则返回-1

语法:arr.findIndex(callback[, thisArg])

参数:
  |--- callback:针对数组中的每个元素, 都会执行该回调函数, 执行时会自动传入下面三个参数:
     |--- currentValue:当前的元素;
     |--- index: 当前元素的索引;
     |--- array: 调用findIndex的数组;
  |---thisArg:可选。执行callback时作为this对象的值;

实例1:查找数组中首个质数元素的索引

      //查找数组中素数的元素的索引(如果不存在素数,则返回-1)。
        function isPrime(element, index, array) {
            var start = 2;
            while (start <= Math.sqrt(element)) {
                if (element % start++ < 1) {
                    return false;
                }
            }
            return element > 1;
        }

        console.log([4, 6, 8, 12].findIndex(isPrime)); // -1, not found
        console.log([4, 6, 7, 12].findIndex(isPrime)); // 2
9、flat() 的使用

   按照一个可指定的深度递归遍历数组,并将所有元素与遍历到的子数组中的元素合并为一个新数组返回;

语法:var newArray = arr.flat(depth)

参数:
  |--- depth:可选。指定要提取嵌套数组的结构深度,默认值为 1

返回值:
  |--- 一个包含将数组与子数组中所有元素的新数组。

实例1:扁平化嵌套数组

  var arr1 = [1, 2, [3, 4]];
  arr1.flat(); 
  // [1, 2, 3, 4]

  var arr2 = [1, 2, [3, 4, [5, 6]]];
  arr2.flat();
  // [1, 2, 3, 4, [5, 6]]

  var arr3 = [1, 2, [3, 4, [5, 6]]];
  arr3.flat(2);
  // [1, 2, 3, 4, 5, 6]

  //使用 Infinity 作为深度,展开任意深度的嵌套数组
  arr3.flat(Infinity); 
  // [1, 2, 3, 4, 5, 6]

实例2:扁平化嵌套数组

  //flat() 方法会移除数组中的空项
  var arr4 = [1, 2, , 4, 5];
  arr4.flat();
  // [1, 2, 4, 5]

替代方案
实例3:使用 reduce 与 concat 替代

  var arr = [1, 2, [3, 4]]; 
  console.log(arr.flat()); // [1, 2, 3, 4]

  // 反嵌套一层数组
  var  newArr = arr.reduce((acc, val) => acc.concat(val), []); 
  console.log(newArr); // [1, 2, 3, 4]

  // 或使用 ...
  console.log( [].concat(...arr) );  // [1, 2, 3, 4]
  // 使用 reduce、concat 和递归无限反嵌套多层嵌套的数组
  var arr = [1,2,3,[1,2,3,4, [2,3,4]]];

  //使用flat
  console.log(arr.flat(Infinity)); // [1, 2, 3, 1, 2, 3, 4, 2, 3, 4]

  //使用reduce 和 concat
  function flattenDeep(arr) {
      return arr.reduce((acc, val) => Array.isArray(val) ? 
          acc.concat(flattenDeep(val)) : acc.concat(val), []);
  }
  console.log(flattenDeep(arr)); // [1, 2, 3, 1, 2, 3, 4, 2, 3, 4]
10、flatMap() 的使用

   首先使用映射函数映射每个元素,然后将结果压缩成一个新数组。它与 map 以及 深度值为1的 flat 几乎相同,但 flatMap 通常在合并成一种方法的效率稍微高一些;

语法:var new_array = arr.flatMap(function callback(currentValue[, index[, array]]) {
      // 返回新数组的元素
     }[, thisArg])

参数:
  |--- callback:可以生成一个新数组中的元素的函数,可以传入三个参数:
     |--- currentValue:当前正在数组中处理的元素;
     |--- index: 可选的。数组中正在处理的当前元素的索引;
     |--- array: 可选的。被调用的 map 数组;
  |---thisArg:可选的。执行 callback 函数时 使用的this 值;

返回值:
  |--- 一个新的数组,其中每个元素都是回调函数的结果,并且结构深度 depth 值为1。

实例1:Map 与 flatMap

  var arr1 = [1, 2, 3, 4];

  arr1.map(x => [x * 2]); 
  // [[2], [4], [6], [8]]

  arr1.flatMap(x => [x * 2]);
  // [2, 4, 6, 8]

  // 只会将 flatMap 中的函数返回的数组 “压平” 一层
  arr1.flatMap(x => [[x * 2]]);
  // [[2], [4], [6], [8]]

  虽然上面的代码使用 map 和 flatMap 好像都可以,但这只能展示如何使用 flatMap。

  所以,为了更好的展示 flatMap 的作用,下面我们将包含几句话的数组拆分成单个汉字组成的新数组。

  let arr = ["今天天气不错", "", "早上好"]

  arr.map(s => s.split(""))
  // [["今", "天", "天", "气", "不", "错"],[""],["早", "上", "好"]]
  
  arr.flatMap(s => s.split(''));
  // ["今", "天", "天", "气", "不", "错", "早", "上", "好"]

等价操作:

  //使用 reduce 与 concat
  var arr1 = [1, 2, 3, 4];

  arr1.flatMap(x => [x * 2]);
  // 等价于
  arr1.reduce((acc, x) => acc.concat([x * 2]), []);
  // [2, 4, 6, 8]

后文,继续......

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