JS数组&函数&对象

数组

  • 所谓数组 Array 就是将多个元素(通常是一个类型)按一定顺序排列放到一个集合中,那么这个集合就称为数组
  • 创建数组最简单的方式就是数组字面量方式
  • 数组字面量:[]
  • 一般将数组字面量赋值给一个变量,方便后期对数组进行操作
  • 如果存放多个数据,每个数据之间用逗号分隔,最后一个后面不需要加逗号
//创建一个空数组
var arr = [];
//创建包括多个数据的数组,数据类型是不限制
var arr2 = [1,true,false,null]
获取数组元素
  • 数组可以通过一个index 索引值、下标去获取对应的某一项数据,进行下一步操作
  • index: 从0开始,按照整数排序往后顺序排序,例如0 1 2 3
  • 可以通过index获取某一项值之后,使用或者更改数组项的值
  • 调用数据:利用数组变量名后面直接加[index]方式
//获取 arr 下标为 0 的项
console.log(arr2[0])
  • 注意: 如果索引值超过了数组最大项,相当于这一项没有赋值,内部储存的就是你 undefined
  • 更改数据:arr[index]调用这一项数据,后面等号赋值更改数据
//更改数组中某一项的值
arr2[0] = 5
数组的长度
  • 数组中有一个length属性,记录的是数组的数据总长度
  • 使用方法: 变量名.length
    console.log(arr.length)
  • 数组的长度与数组最后一项的下标存在关系,最后一项的下标等于数组的length-1.
  • 获取最后一项的数据时,可以这样写
    console.log(arr[arr.length-1])
  • 数组的长度不是固定不变的,可以发生更改
  • 增加数组长度:直接给数组length属性赋一个大于原来长度的值。赋值方式使用等号赋值。
  • 或者,可以给一个大于最大下标的项直接赋值,还可以强制拉长数组
  • 缩短数组长度:强制给length属性赋值,后面数据会被直接删除,删除是不可逆的
数组的遍历
  • 遍历:遍及所有,对数组的每一个元素都访问一次就是遍历。利用for循环,将数组中的每一项单独拿出来,进行一些操作
  • 根据下标在0到arr.length-1之间,进行for循环遍历
//定义一个数组
var arr = [45,56,76,88,89,22,44,66];
for(var i = 0 ; i <= arr.length - 1 ; i++){
    console.log(arr[i])
}

函数

函数的概念

函数(function),也叫做功能、方法,函数可以将一段代码一起封装起来,被封装起来的函数具备某一项特殊功能,内部封装的一段代码作为一个完整的结构体,要执行就都执行,要不执行就都不执行

  • 函数的作用就是封装一段代码,将来可以重复使用
            //定义函数
            function fun() {
                console.log(1);
                console.log(2);
                console.log(3);
                console.log(4);
            }
            //调用函数
            fun()
            fun() //可多次调用
            fun()
            fun()
函数的声明和调用
  • 函数声明又叫函数定义,函数必须先定义然后才能使用
  • 如果没有定义函数直接使用,会出现一个引用错误
  • 函数声明语法:
function 函数名(参数){
    封装的结构体;
}
特点: 函数声明的时候,函数体并不会执行,只有当函数被调用的时候才会执行

函数调用

  • 调用方法:函数名();
  • 函数调用也叫做函数执行,调用时会将函数内部封装的所有的结构体的代码立即执行。
  • 函数内部语句执行的位置,与函数定义的位置无关,与函数调用位置有关
  • 函数可以一次调用,多次执行

参数

  • 我们希望函数执行结果不是一成不变的,可以根据自定义的内容发生一些变化
  • 函数预留了一个接口,专门用于让用户自定义内容,使函数发生一些执行效果变化
  • 接口:就是函数的参数,函数参数的本质就是变量,可以接收任意类型的数据,导致函数执行结果参数不同,结果也不同。
  • 一个函数可以设置0个或者多个参数,参数之间用逗号分隔
//定义函数
function sum(a, b) {
     console.log(a+b)
}
//调用函数
sum(3, 4)
  • 函数的参数根据书写位置不同,名称也不同。
  • 形式参数:定义的()内部的参数,叫做形式参数,本质是变量,可以接收实际参数传递过来的数据。简称形参。
  • 实际参数:调用的()内部的参数,叫做实际参数,本质就是传递的各种类型的数据,传递给每个形参,简称实参。
  • 函数执行过程,伴随着传参的过程。

函数的参数优点

  • 不论使用自己封装的函数,还是其他人封装的函数,只需要知道传递什么参数,执行什么功能,没必要知道内部的结构什么
  • 一般自己封装的函数或者其他人封装的函数需要有一个API接口说明,告诉用户参数需要传递什么类型的数据,实现什么功能

函数的返回值

  • 函数能够通过参数接收数据,也能够将函数执行结果返回一个值
  • 利用函数内部的一个return的关键字设置函数的返回值
  • 作用1:函数内部如果结构体执行到一个return的关键字,会立即停止后面代码的执行。
  • 作用2:可以在return关键字后面添加空格,空格后面任意定义一个数据字面量或者表达式,函数在执行完自身功能之后,整体会被return矮化成一个表达式,表达式必须求出一个值继续可以参与程序,表达式的值就是reyurn后面的数据。
//定义函数
function sum(a,b) {
    return a + b
}
//调用函数
console.log(sum(1+2))
  • 函数如果有返回值,执行结果可以当成普通数据参与程序
  • 函数如果有返回值,可以作为一个普通数据赋值给一个变量,甚至赋值给其他函数的实际参数。
    注意:如果函数没有设置return语句,那么函数有默认的返回值undefined;如果函数使用return语句,但是return后面没有任何值,那么函数的返回值也是undefined

函数表达式

  • 函数表达式是函数定义的另一种方式
  • 定义方法:就是将函数的定义、匿名函数赋值给一个变量
  • 函数定义赋值给一个变量,相当于将函数整体矮化成了一个表达式
  • 匿名函数:函数没有函数名
  • 调用函数表达式,方法是给变量名加()执行,不能使用函数名加()执行
//定义函数表达式
var foo = function fun(a,b) {
    console.log(1);
      };
//定义匿名函数
var foo2 = function () {
    console.log(2);
      };
//调用函数式,只能用变量名调用,函数名调用不成功

函数的数据类型

  • 函数是一种单独的数据类型Function
  • 由于函数是一种数据类型,可以参与其他程序
  • 例如,可以把函数作为另一个函数的参数,在另一个函数中调用
  • 或者,可以把函数可以作为返回值从函数内部返回
arguments对象
  • JS中,arguments对象是比较特别的一个对象,实际上是当前函数的一个内置属性。也就是说所有函数都内置了一个arguments对象,arguments对象中存储了传递的所有的实参。arguments是一个伪数组,因此及可以进行遍历。
  • 函数的实参个数和形参个数可以不一致,所有的实参都会存储在函数内部的arguments类型数组对象中。
//定义函数表达式
            function sumplus(a,b,c){
                switch(arguments.length){
                    case 1:
                        return a;
                        break
                    case 2:
                        return a+b;
                        break;
                    case 3:
                        return a > b ? a + c : b + c ;
                    default:
                        throw new Error('输入的不对!!!')
                }
                
            }
函数递归
  • 函数内部可以通过函数名调用函数自身的方式,就是函数递归现象
  • 递归的次数太多容易出现错误:超出计算机的计算最大能力
  • 更多时候,使用递归去解决一些数字中的现象
  • 例如可以输出斐波纳切数列的某一项的值
//斐波纳切数列
            function fibo(a){
                if(a === 1 || a === 2){
                    return 1;
                }else{
                    return fibo(a - 1) + fibo(a - 2)
                }
            }
作用域
  • 作用域:变量可以起作用的范围

  • 如果变量定义在一个函数内部,只能在函数内部被访问到,在函数外部不能使用这个变量,函数就是变量定义的作用域

  • 任何一对花括号{}中的结构都属于一个块,在这之中定义的所有变量在代码块外都是不可见的,我们称之为块级作用域

  • 在es6之前没有块级作用域的概念,只有函数作用域

  • 局部变量:定义在函数内部的变量,只能在函数作用域内部被访问到,在外面没有定义的

  • 全局变量:从广义上来说,也是一种局部变量,定义在全局的变量,作用域范围是全局,在整个js程序任意位置都能够被访问到

  • 变量退出作用域之后会销毁,全局变量关闭网页或浏览器才会销毁

参数和函数的作用域

  • 函数的参数本质是一个变量,也有自己的作用域,函数的参数也是属于函数自己内部的局部变量,只能在函数内部被使用,在函数外面没有定义

  • 函数也有自己的作用域,定义在哪个作用域内部,只能在这个作用域范围内被访问,出了作用域不能被访问

  • 函数定义在另一个函数内部,如果外部函数没有执行时,相当于内部代码没写

作用域链

  • 只有函数可以制造作用域结构,那么只要是代码,就至少有一个作用域,即全局作用域。凡是代码中有函数,那么这个函数就构成另一个作用域。如果函数中还有函数,那么在这个作用域中就又可以诞生一个作用域
  • 将这样的所有的作用域列出来,可以有一个结构:函数内指向函数外的链式结构。就称作作用域链。

定义的时候不写var,会使局部变量污染全局变量

预解析
  • JS代码的执行是由浏览器中的JS解析器来执行的。JS解析器执行JS代码的时候,分为两个过程:预解析过程和代码执行过程
  • 预解析过程:
    1 把变量的声明提升到当前作用域的最前面,只会提升声明,不会提升赋值
    2 把函数的声明提升到当前作用域的最前面,只会提升声明,不会提升调用
    3 先提升var,再提升function
  • JS的执行过程:在预解析之后,根据新的代码顺序,从上往下按照既定规律执行js代码

变量声明提升

  • 在预解析过程中,所有定义的变量,都会将声明的过程提升到所在的作用域最上面,在将来的代码执行过程中,按照先后顺序会执行被提升的声明变量过程。
  • 提升过程中,只提升声明过程,不提升变量赋值,相当于变量定义未赋值,变量内存储undefined值
  • 因此,在js中会出现一种现象,在前面调用后定义的变量,不会报错,只会使用undefined值

IIFE自调用函数

  • 即使调用的函数表达式,也叫做自调用函数,表示函数在定义时就立即调用

  • 函数调用方式:函数名或函数表达式的变量名后面加()运算符

  • 函数名定义的形式不能实现立即执行自调用,函数使用甘薯表达式形式可以实现立即执行,原因是因为函数表达式定义过程中,将一个函数矮化成了一个表达式,后面加()运算符就可以立即执行

  • 启发:如果想实现IIFE,可以想办法将函数矮化成表达式

  • 函数矮化成表达式的方法,可以让函数参与一下运算,也就是说给函数前面加一些运算符
    数学运算符:+ - ()
    逻辑运算符: ! 非运算

(function(a){
    console.log(a);
})(1)

对象

  • 如果有一组相关的数据,松散的存储不利于使用,存入数组中受下标限制又必须有固定的顺序,而对象可以自定义名称存储一系列无序的相关数据

对象字面量

  • 创建一个对象最简单的方式是使用对象字面量赋值给变量。类似数组
  • 对象字面量语法:{}
  • 内部可以存放多条数据,数据与数据之间用逗号分隔,最后一个后面不要加逗号。
  • 每条数据都是有属性名和属性值组成,键值对写法: k:v
  • k:属性名
  • v:属性值,可以是任意类型的数据,比如简单类型数据、函数、对象
var person1 = {
                name : 'aaa',
                age : 16,
                sex : "female",
                sayHi : function(){
                    console.log(this.name + "像你说你好")
                }
            }
//调用
            console.log(person1.name);
            console.log(person1.age);
            console.sayHi();
//中括号调用
            console.log(person1["name"]);
            person1["sayHi"]();
//更改数据
            person1.age = 19;
//添加新数据
            person1.weight = 140;
//删除属性
            delete person1.sex

new Object()创建对象

  • Object()构造函数,是一种特殊的函数。主要用来创建对象时初始化对象,即为对象成员变量赋初始值,总与new运算符一起使用在创建对象的语句中
    1 构造韩素用于创建一类对象,首字母要大写
    2 构造函数要和new一起使用才有意义
var person2 = new Object();
person2.name = "zsls";
person2.age = 19;

工厂函数方法创建对象

  • 如果要创建多个类似的对象,可以将new Object()过程封装到一个函数中,将来调用函数就能创建一个对象,相当于一个生产对象的函数工厂,用来简化代码。
function createPerson(name,age,sex){
                //创建一个空对象
                var person = new Object();
                //添加属性和方法,属性可以接受参数的值
                person.name = name;
                person.age = age;
                person.sex = sex;
                person.sayHi = function () {
                    console.log("hello");
                };
                //将对象作为函数的返回值
                return person;
            }
//创建对象,可以调用工厂函数
var p1 = createPerson('zx',18,ture);

自定义构造函数创建对象

  • 比工厂方法更加简单
  • 自定义一个创建具体对象的构造函数,函数内部不需要new一个构造函数的过程,直接使用this代替对象进行属性和方法的书写,也不需要return一个返回值
  • 使用时,利用new关键字调用自定义的构造函数即可
  • 注意:构造函数的函数名首字母需要大写,区别于其他普通函数名
function Person(name,age,sex){
                // 不需要使用new一个新对象
                // 用this替代将来创建的新对象
                this.name = name;
                this.age = age;
                this.sex = sex;
                this.sayHi = function () {
                    console.log("hello");
                };
                // 不需要添加 return
            }

// 用 new 关键字调用构造函数
var p1 = new Person("zs",18,true);
console.log(p1);
遍历对象方法
  • for in 循环也是循环的一种,专门用来遍历对象,内部会定义一个k变量,k变量在每次循环时会从第一个开始接收属性名,一直接收到最后一条属性名,执行完后跳出循环
  • 简单的循环遍历:输出每一项的属性名和属性值
for(var k in obj){
    console.log(k + "项的属性值是" + obj[k]);
}
简单类型和复杂类型的区别
  • 基本类型又叫做值类型,复杂类型又叫做引用类型
  • 值类型:简单数据类型,基本数据类型,在存储时,变量中存储的是值本身,因此叫做值类型
  • 引用类型:复杂数据类型,在存储时,变量中存储的仅仅是地址,因此叫做引用数据类型

堆和栈

  • JS没有堆和栈的概念,只是方便理解


    堆和栈.png
  • 堆栈空间分配区别:
    1 栈(操作系统):由操作系统自动分配释放,存放函数的参数值,局部变量的值等。
    2 堆(操作系统):存储复杂类型(对象),一般由程序员分配释放,若程序员不释放,由垃圾回收机制回收。

基本类型在内存中的存储
变量中如果存储的是简单类型的数据,那么变量中存储的是值本身,如果将变量赋值给另一个变量,是将内部的值复制给一份给了另一个变量,两个变量之间没有联系,一个变化,另一个不会同时变化。

复杂数据类型在内存中的存储
如果将复杂类型的数据复制给一个变量,复杂类型的数据会在内存中创建一个原型,而变量中存储的是指向对象的一个地址,如果将变量赋值给另一个变量,相当与将地址复制一份给了新的变量,两个变量的地址相同,指向的是同一个原型,不论通过哪个地址更改了原型,都是在原型上发生的更改,两个变量下次访问时,都会发生变化

Math对象

  • Math对象它具有数学常数和函数的属性和方法,我们可以直接进行使用
  • 根据数学相关的运算来找Math中的成员(求绝对值,取整)
    Math.PI 圆周率
    Math.random() 生成随机数
    Math.floor() / Math.ceil() 向下取整/向上取整
    Math.round() 取整,四舍五入
    Math.abs() 绝对值
    Math.max() / Math.min() 求最大和最小值
    Math.sin() / Math.cos() 正弦/余弦
    Math.pow() / Math.sqrt() 求指数次幂/求平方根

eg: 得到一个两数之间的随机整数

function getRandomInt(min, max) {
  min = Math.ceil(min);
  max = Math.floor(max);
  return Math.floor(Math.random() * (max - min)) + min; //不含最大值,含最小值
}

eg: 得到一个两数之间的随机整数,包括两个数在内

function getRandomIntInclusive(min, max) {
  min = Math.ceil(min);
  max = Math.floor(max);
  return Math.floor(Math.random() * (max - min + 1)) + min; //含最大值,含最小值 
}
数组对象创建、判断数据类型

创建数组对象的两种方式
1 字面量方式
new Array()构造函数方法

//数组也是对象,可以通过构造函数生成
//空数组
var arr = new Array();
//添加数据,可以传参数
var arr2 = new Array(1,2,3);

检测数组类型

  • instanceof 检测某个实例是否是某个对象类型
//检测数组的数据类型
console.log(arr instanceof Array)
数组方法
  • toString() 把数组转换成字符串,逗号分隔每一项
  • 首尾数据操作:
    push() 在数据末尾添加一个或多个元素,并返回数组操作后的长度
    pop() 删除数据最后一项,返回删除项,不需要传参
    shift() 删除数据第一项,返回删除项
    unshift() 在数据开头添加一个或多个元素,并返回数据的新长度

数组常用方法
合并和拆分:
concat()

  • 将两个数组合并成一个新数组,愿数组不受影响。参数位置可以是一个数组字面量、数组变量、零散的值。

slice(start,end)

  • 从当前数组中截取一个新的数组,不影响原来的数组,返回一个新的数组,包含从start到end(不包括该元素)的元素
  • 参数区分正负,正值表示下标位置,负值表示从后面往前数第几个位置,参数可以只传递一个,表示从开始位置截取到字符串结尾。

splice(index,howmany,element1,element2,……)

  • 删除、插入、替换:
    用于插入、删除或替换数组的元素
    index: 删除元素的开始位置
    howmany: 删除元素的个数,可以是0
    element1,element2: 要替换的新的数据
var arr = [1,2,3,4,5,6,7,8]
//删除功能,传前两个参数
arr.splice(2,5); 
//替换功能,传三个及以上参数
arr.splice(2,5,"hhh","hello");
//插入功能,传3个以上的参数,但第二个参数必须为0
arr.splice(2,0,"hellohi","1234")

位置方法
indexOf()

  • 查找数据在数组中最先出现的下标
  • lastIndexOf() 查找数据在数组中最后一次出现的下标
    注意:如果没找到返回-1
var arr = [1,2,3,4,5,6,7,8,4];

//查找某个元素在数组中从前往后第一次 出现位置的下标
console.log(arr.indexOf(4));
//查找数据在数组中最后一次出现的下标
console.log(arr.lastIndexOf(4));
  • 倒叙:reverse() 将数组完全颠倒,第一项变成最后一项,最后一项变成第一项
  • 排序:sort() 默认根据字符编码顺序,从小到大排序
    如果想要根据数值大小进行排序,必须添加sort的比较函数参数
    该函数要比较两个值,然后返回一个用于说明这两个值的相对顺序的数字。比较函数应该具有两个参数a和b,根据a和b的关系作为判断条件,返回值根据条件分为三个分支,正数、负数、0:
    返回值是负数-1:a排在b前面
    返回值是整数1: a排在b后面
    返回值是0:a和b的顺序保持不变
    人为能控制是判断条件
var arr = [1,20,3,4,5,6,7,80,4];

            arr.sort(function(a,b){
                if(a>b){
                    return -1;
                }else if(a<b){
                    return 1;
                }else{
                    return 0;
                }
            })
            console.log(arr)

转字符串方法
将数组的所有元素连接到一个字符串中
join() 通过参数作为连字符将数组中的每一项用连字符连成一个完整的字符串

基本包装类型

基本类型的数据:没有属性和方法
对象数据类型:有属性和方法
但是字符串可以调用一些属性和方法

字符串的特点
字符串是不可变的
由于字符串的不可变,在大量拼接字符串的时候会有效率问题
字符串所有的方法,都不会修改字符串本身(字符串是不可变的),操作完成会返回一个新的字符串。

字符串属性
长度属性:str.length
字符串长度指的是一个字符串中所有的字符总和

  • charAT() 方法可返回指定位置的字符
    参数是index字符串的下标。也是从0开始
    表示返回指定的下标位置的字符
  • indexOf() 方法可返回某个指定的字符串值在字符串中首次出现的位置
    找到指定的子字符串在原字符串中第一次出现的位置的下标。如果子字符串在原字符串中没有,返回值是-1
  • concat() 方法用于连接两个或多个字符串
    参数比较灵活,可以是字符串、或者字符串变量、多个字符串
    生成的是一个新的字符串,原字符串不发生变化
  • split() 方法用于把一个字符串分割成字符串数组
    参数部分是分隔符,利用分隔符将字符分割成多个部分,多个部分作为数组的每一项组成数组
    如果分割符是空字符串,相当于将每个字符拆分成数组中的每一项
  • toLowerCase() 把字符串转换成小写
    toUpperCase() 把字符串转换成大写
    将所有的英文字符串转为大写或者小写,生成的是新的字符串
  • slice 方法可提取字符串的某个部分,并以新的字符串返回被提取的部分
  • substr() 方法可在字符串中抽取从start下标开始的指定数目的字符
    语法:substr(start,howmany)
    从开始位置截取到指定长度的字符串
    start参数区分正负,正值表示下标位置,负值表示从后往前数第几个位置
    howmany参数必须为正数,也可以不写,不写表示从stary截取到最后
  • substring() 方法用于提取字符串中介于两个指定下标之间的字符 参数只能是正数
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,293评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,604评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,958评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,729评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,719评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,630评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,000评论 3 397
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,665评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,909评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,646评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,726评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,400评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,986评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,959评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,996评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,481评论 2 342

推荐阅读更多精彩内容