《JavaScript 权威指南(第六版)》二——表达式运算符

第四章:表达式和运算符

2017.02.21-2017.02.22

表达式和运算符

表达式(expression):JavaScript解释器会将其计算出一个结果。

运算符(operator):将简单表达式组合成复杂表达式最常用的方法就是运算符。

原始表达式

原始表达式子(primary expression):表达式的最小单位,它们不再包含其他表达式。JavaScript中的原始表达式包含常量或直接量,关键字和变量。

直接量

1.23 //数字直接量
"hello" //字符串直接量
/pattern/ //正则表达式直接量

保留字

true 
false
null
this

变量

i
sum
undefined // undefined是全局变量,与null不同,他不是关键字

对象和数组的初始化表达式

对象和数组初始化表达式实际上是一个新创建的对象和数组。这些初始化表达式有时称作“对象直接量”和“数组直接量”。它们不是原始表达式,它们所包含的成员或者元素都是子表达式。

数组初始化表达式

[] //空数组
[1+2,3+4] //有两个元素的数组
var matrix=[[1,2,3],[4,5,6],[7,8,9]];

JavaScript对数组初始化表达式进行求值的时候,数组初始化表达式中的元素表达式也都会各自计算一次。

var x=1;
var a=[[x+++1,2],[x+++1,2],[x+++1,2]] //结果[ [ 2, 2 ], [ 3, 2 ], [ 4, 2 ] ]

数组直接两种列表逗号之间的元素可以省略,省略的空位会填充值undefined。

var arr=[1,,,,5];//arr打印结果:[ 1, , , , 5 ]
arr[0]; //1
arr[1]; //undefined
arr[4]; //5

数组直接量的元素列表结尾可以留下单个逗号,这时并不会创建一个新的值为undefined的元素。

var arr=[1,,,,5,]; //arr打印结果[ 1, , , , 5 ] 

对象初始化表达式

var p={x:2.3,y:-1.2}; //拥有两个属性成员的对象
var q={}; //空对象
q.x=2.3;q.y=-1.2; //此时q是{x:2.3;y:-1.2}
var rectangle={upperLeft:{x:2,y:2},lowerRight:{x:4,y:4}};
var side=1;
var square={upperLeft:{x:p.x,y:p.y},lowerRight:{x:p.x+side,y:p.y+side}};
var p={"x":2.3,"y":-1.2}; //打印结果 { x: 2.3, y: -1.2 },参考json
p.x //2.3
p."x" //SyntaxError: Unexpected string
p[x] //undefined,此处x会被当做变量。倘若var x="x",那么p[x] 也是2.3
p["x"] //2.3

函数定义表达式

函数定义表达式定义一个新的JavaScript函数。表达式的值是新定义的函数。函数定义表达是可称为“函数直接量”。

一个典型的函数定义表达式包含关键字function,跟随其后的是一对圆括号,括号内是一个以逗号分割的列表,列表含有0个或多个标识符(参数名),然后在跟随一个由花括号包裹的代码段(函数体)。

var square=function(x){return x*x};

函数定义表达式同样可以包含函数的名字。函数可以通过函数语句来定义,而不是函数表达式。

属性访问表达式

属性访问表达式运算得到一个对象属性或一个数组元素的值。

【语法】

expression.identifier //用于对象
expression[expression] //用于对象、数组

【示例】

var o={x:1,y:{z:3}};
var a=[o,4,[5,6]];
o.x;//1
o.y.z;//3
o["x"];//1
a[0];//{ x: 1, y: { z: 3 } }
a[1];//4
a[2][1];//6
a[2]["1"];//6    a["2"][1];//6    a["2"]["1"];//6
a[0].x;//1  
a[0]["x"];//1


for(var each in this){console.log(each+":"+this[each])} //打印对象中所有属性以及其值

在"."和"["之前的表达式总是会首先计算。如果计算结果是null或undefined,表达式会抛出类型错误异常。(因为这两个值不包含任意属性)。如果运算结果不是对象(或数组),JavaScript会将其转换成对象。如果对象表达式后紧随句点和标识符,则会差债有这个标识符所指定的属性的值,并将其作为整个表达式的值返回。如果对象表达式后紧随一堆方括号,则会计算方括号内的表达式的值并将它转换为字符串。如果属性不存在,返回undefined。

identitier写法简单,但要注意,此方式只适用于要访问的属性名称合法,并且需要知道要访问的属性的名字。如果属性名称是一个保留字或者包含空格和标点符号或是一个数字,则必须使用方括号。当属姓名是通过运算得出来的值而不是固定的值得时候,这是必须使用方括号。

调用表达式

调用表达式(invocation expression):是一种调用(或者执行)函数或方法的语法表示。以一个函数表达式开始,这个函数表达式指代要调用的函数,函数表达式后紧随一对包含一逗号隔开的参数列表的圆括号。

f(0)
Math.max(x,y,z)
a.sort() //关于这个函数,戳下面链接

JavaScript sort() 方法

当调用表达式进行求职的时候,首先计算函数表达式,然后计算参数表达式,得到一组参数值。如果函数表达式的值不是一个可调用的对象,则抛出一个类型错误异常。然后,实参的值被依次赋给形参,接下来执行函数体。如果函数使用return语句给出一个返回值,那么这个返回值就是整个调用表达式的值,否则,调用表达式的值就是undefined。

如果调用表达式是一个属性访问表达式,则此调用称作“方法调用”。

对象创建表达式

对象创建表达是(object creation expression):创建一个对象并调用一个函数(构造函数)初始化新对象的属性。

new Object()
new Point(2,3)

如果一个对象创建表达式不需要传入任何参数给构造函数的话,那么这对空圆括号是可以省略掉的。

new Object
new Date

当计算一个对象创建表达式的值时,和对象初始化表达式通过{}创建对象的做法一样,JavaScript首先创建一个新的空对象,然后JavaScript通过传入指定的参数并将这个新对象当做this的值来调用一个指定的函数。这个函数可以使用this来初始化这个新创建对象的属性。那些被当做构造函数的函数不会反悔一个值,并且这个新创建并被初始化后的对象就是整个对象创建表达式的值。如果一个构造函数确实返回了一个对象值,那么这个对象就作为整个对象创建表达式的值,而新创建的对象就废弃了。

运算符概述

JavaScript中的运算符用于算术表达式、比较表达式、逻辑表达式、复制表达式等。

包括关键字运算符(delete,instanceof)和标点符号(+,=)所表示的运算符。

JavaScript 运算符

操作数的个数

  • 一元运算符 unary operator
  • 二元运算符 binary operator
  • 三元运算符 ternary operator

操作数类型和结果类型

一些运算符可以作用于任何数据类型,但仍希望它们的操作数是指定类型的数据,并大多数运算符返回一个特定类型的值。

左值

左值是指:表达式只能出现在赋值运算符的左侧。在JavaScript中,变量、对象属性和数组元素均是左值。

ECMAScript规范允许内置函数返回一个左值,但自定义的函数则不能返回左值。(?)

运算优先级

属性访问表达式和调用表达式的优先级比表中运算符都要高。

运算顺序

JavaScript总是严格按照从左至右的顺序来计算表达式。

算术表达式

* / % -无法转换为数字的操作数都转换为NaN,结果也是NaN。

对于除法/,JavaScript中所有数字都是浮点型的,除法运算的结果也是浮点型。

5/2 //2.5
0/0 //NaN
1/0 //Infinity
-1/0 //-Infinity

对于取模%,结果和第一个操作数的符号保持一致。操作数通常是整数,但也适用于浮点数。

5%2 //1
-5%2 //-1
6.5%2.1 //0.19999999999999973

+运算符

1+2 //3
"hello"+" "+"there" //"hello there"
"1"+"2" //"12"
  • 如果其中一个操作数是对象,则对象会遵循对象到原始值的转换规则转换为原始类值。日期独享通过toString()方法转换,群殴他对象通过valueOf()转换(如果valueOf()返回原始值的话)。由于多数都不具备valueOf()方法,因此会通过toString()转换。
  • 在进行了对象到运势值的转换之后,如果一个操作数是字符串,另一个操作数也会转换为字符串,然后进行字符串连接。
  • 否则,两个操作数都转换为数字(或NaN),然后进行加法运算

【示例】

1+2 //3;加法
"1"+"2" // "12";字符串连接
"1"+2 //"12";数字转换为字符串后进行字符串连接
1+{} //"1[object Object]" ;对象转换为字符串后进行字符串连接
true+true //2;布尔值转换为数字后加法
2+null; //2;null转换为0后加法
2+undefined //NaN;undefined转换为NaN后加法

需要注意的是,加号运算符和字符串和数字一起使用时,需要考虑加法的结核性对运算顺序的影响。

1+2+"hello" // "3hello"
1+(2+"hello") //"12hello"

一元算术运算符

一元运算符作用域一个单独的操作数,产生一个新值。一元运算符优先级很高,右结合(right-associative)。

+

把操作数转换为数字(或NaN),并返回转换后的数字。如果操作数本身就是数字,则直接返回数字。

-

把操作数转换为数字,然后改变运算结果的符号。

++

加一,操作数是一个左值。将操作数转换为数字,然后给数字加一,并将加一后的数值重新赋值给操作数(变量、数组元素或者对象属性)。

前增量:运算符在操作数之前的。

后增量:运算符在操作数之后的。不允许后增量运算符和操作数之间插入换行符。

var i=1;j=++1; //i=2,j=2
var i=1;j=i++; //i=2,j=1

x++不等于x=x+1++从不进行字符串连接操作。

var x='1';
x++; //2
x=x+1; // 11
--

减一,操作数是一个左值。将操作数转换为数字,然后减一,并将减一后的数值重新赋值给操作数(变量、数组元素或者对象属性)。

运算符在操作数之前,操作数减一并返回减一后的值。

运算符在操作数之后,操作数减一并返回减一前的值。不允许运算符和操作数之间有换行符。

位运算符

位运算符要求其操作数是整数,这些证书表示为32位整型而不是64位浮点型。必要时,位运算符首先将操作符转换为数字,并将数字强制表示为32位整型,这会忽略原格式中的小数部分和任何超过32位的二进制位。移位运算符要求又操作数在0-31之间。在将其操作数转换为无符号32位整数后,它们将舍弃第5位之后的二进制位,以便生成一个位数正确的数字。位运算符将NaN、Infinity、-Infinity转换为0。

  • & 按位与
  • | 按位或
  • ^ 按位异或
  • ~ 按位非,相当于改变符号并减一。 ~0x0f=0xfffffff0 即15按位非后是-16
  • << 左移,新的位用0补充,并舍弃高位。左移n位相当于乘以2^n
  • >> 带符号右移,如果第一个操作数是正数,用0填补最高位;如果第一个操作室是负数,用1填补最高位。将一个值右移n位,相当于除以2^n(忽略余数)。7>>1=3;-7>>1=-4
  • >>> 无符号右移,最高位总是补0。 -1>>4=1-;-1>>>4=0x0FFFFFFF

关于无符号数,有符号数。

> 0xFFFFFFFF
4294967295
> 0xFFFFFFFF>>>0
4294967295
> 0xFFFFFFFF<<0
-1
> 0xFFFFFFFF>>0
-1

关系表达式

相等,不相等

===严格相等运算符(恒等运算符),用来检测两个操作数是否严格相等。对应的有!==

==相等运算符,用来检测两个操作数是否相等,可以允许进行类型转换。对应的有!=

===
  • 如果两个值类型不相同,则它们不相等。
  • 如果两个值都是null或者都是undefined,则它们相等。null===null(true)undefined===undefined(true) 。(注意:中文版,淘宝团队翻译的有错误。翻译为不想等了。)null,undefined,Infinity,-Infinity只要两者本身值或者计算后的值相等,那么他们就恒等。
  • 如果两个值都是布尔值true或者都是布尔值false,则它们相等
  • 如果其中一个值是NaN,或者两个值都是NaN,则它们不相等。NaN和其他任何值都不想等,包括他自己。通过x!=x判断x是否位NaN,只有在x为NaN时,这个表达式的值才是true。
  • 如果两个值为数字且数值相等,则它们相等。如果一个值为0,另一个值为-0,则它们同样相等。
  • 如果两个值为字符串,且所含的对应位上的16位数完全相等,则它们相等。如果长度或内容不同,则它们不等。两个字符串可能含义完全一样且所显示出的字符也一样,但是具有不同编码的16位值。JavaScript不对Unicode进行标准化的转换,因此像这样的字符串通过=== ==比较的结果不相等。String.localeCompare()提供了另外一种比较字符串的方法。
  • 如果两个引用值指向同一个对象、数组或函数,则它们相等。如果指向不同对象,则它们不相等,尽管两个对象具有完全一样的属性。
==
  • 如果两个操作数的类型相同,则和上文所述的比较规则一样。如果严格相等,那么比较结果相等,如果不严格相等,则比较结果不相等。
  • 如果两个操作数类型不同:
    • 一个null,另一个undefined,则它们相等。null==undefined
    • 一个值是数字,另一个是字符串,现将字符串转换为数字,然后比较
    • 如果一个值为true,则将其转换为1再比较。如果一个值位false,则将其转换为0再比较true==1:true;true==2:false;true=="1":true
    • 如果一个值是对象,另一个值是数字或字符串,则使用转换规则将对象转换为原始值,然后比较。
    • 其他的不同类型之间的比较均不相等。

比较运算符

  • < 小于
  • > 大于
  • <= 小于等于
  • >= 大于等于

只有数字和字符串才能执行比较操作,非数字和字符串都要进行类型转换。规则如下:

  • 如果操作数是对象,转换为原始值:如果valueOf()返回原始值,那么直接使用这个原始值。否则,使用toString()的转换结果进行比较。
  • 在对象转换为原始值之后,如果两个操作数都是字符串,将依照字母表的顺序对两个字符串比较。
  • 在对象转换为原始值之后,如果至少有一个操作数不是字符串,那么两个操作数都将转换为数字进行述职比较。0和-0相等。Infinity大于任何数字(除了自己)。-Infinity小于任何数字(除了自己)。如果其中一个操作数是NaN(或转换后是NaN),那么比较操作符总是返回false。

所有的大写的ASCII字母都小鱼小写的ASCII字母。 'Zoo'<'apple'返回true

in 运算符

in运算符希望它的左操作数是一个字符串还或可以转换为字符串,希望它的右操作数是一个对象。如果右侧的对象拥有一个名为左操作数值得属性名,那么表达式返回true。

var point={x:1,y:2}; //定义一个对象
"x" in point; //true
"z" in point; //false
"toString" in point  //true ,对象继承了toString()方法

var data=[7,8,9];//定义一个数组
"0" in data; //true
1 in data; //true,数字转换为字符串
3 in data; //false,不存在索引3的元素
"toString" in t //true,数组有toString()方法

instanceof 运算符

instanceof运算符希望左操作数是一个对象,右操作数标识对象的类。如果左侧的对象是右侧类的实例,则表达式返回true,否则返回false。

var d = new Date(); 
d instanceof Date;//true
d instanceof Object; //true
d instanceof Number; //false
var a = [1, 2, 3];
a instanceof Array;//true 
a instanceof Object; //true
a instanceof RegExp;//false

所有的对象都是Object的实例,当通过instanceof判断一个对象是否是一个类的实例的时候,这个判断也会对其父类检测。

如果instanceof左操作数不是对象,返回false。如果右操作数不是函数,抛出类型错误异常。

instanceof 工作原理(原型链prototype chain):原型链是JavaScript的继承机制。为了计算表达式 o instanceof f,JavaScript首先计算f.prototype,然后在原型链中查找o,如果找到,那么o是f的一个实例,表达式返回true。否则返回false。

逻辑表达式

&& || !
返回true,false。

&& 逻辑与

第一层理解:当操作数都是布尔值的时候,返回true或false。

假值:false,null,undefined,0,-0,NaN,""。所有其他的值包括所有对象都是真值。

第二层理解:对真值和假值进行与操作。在JavaScript中任何希望使用布尔值的地方,表达式和语句都会将其当做真值或者假值来对待。因此并不总是返回true或false。

第三层理解:运算符首先计算左操作数的值,如果是假值,则整个表达式也是假值。因此&&返回左操作数的值,而不会对右操作数进行计算。如果左操作数是假值,则表达式结果取决于右操作数的值。&&将计算右操作数的值并将其返回作为整个表达式的计算结果。

> 1&&false
false
> 1&&true
true
> 1&&2
2
> 2&&1
1
> 0&&1
0
> true&&1
1
> false&&1
false
> true&&0
0

> var o={x:1}
undefined
> var p=null
undefined
> o&&o.x
1
> p&&p.x
null

&&的行为有时被称作“短路”。

if(a==b) stop();
(a==b) && stop();//二者等价

|| 逻辑或

&&对比理解。

如果第一个操作数为真,则返回真值。否则计算第二个操作数,并返回表达式的计算结果。

> 1||NaN
1
> 0||NaN
NaN

! 逻辑非

!是一元运算符。将操作数的布尔值求反。

首先将其操作数转换为布尔值,然后在对布尔值求反,总是返回true或false,并且可以通过两次逻辑非得到等价布尔值。!!x。优先级很高。

!(p&&q)===!p||!q
!(p||q)===!p&&!q

赋值表达式

=运算符希望它的左操作数是一个左值(变量、对象属性或数组元素),右操作数是任意值。

赋值操作符的结合性是从右到左。

i=j=k=0; //把三个变量初始化为0

带操作的赋值运算

+=
-=
*=
/=
%=
<<=
>>=
>>>=
&=
|=
^=

大多数情况下,a op= b 等价于a = a op b

表达式计算

通过eval()解释运行由JavaScript源代码组成的字符串,并产生一个值。

eval("3+2") //5

eval()只有一个参数。如果参数不是字符串,它直接返回此参数。如果参数是字符串,他会把字符串当成JavaScript代码进行编译。编译失败将抛出语法异常,编译成功则执行代码,并返回字符串中的最后一个表达式或语句的值(如果最后一个表达式或语句没有值,则最终返回undefined)。如果字符串抛出异常,则异常将把该调用传递给eval()。

eval()查找变量的值和定义新变量和函数额操作与局部作用域中的代码完全一样。

注意:传递给eval()的字符串必须在语法上讲得通。不能通过eval()向函数中任意粘贴代码片段。否则将抛出语法错误异常。

var foo=function(a){
    eval(a);
};
foo("return;");// SyntaxError: Illegal return statement

【全局eval实例】

var geval = eval;
var x = "global", y = "global"; 
function f() {
    var x = "local";
    eval("x += 'changed';"); //直接eval更改局部变量
    return x;//返回修改后的局部变量
}
function g() {
    var y = "local";
    geval("y += 'changed';"); //间接调用改变全局变量
    return y;//返回未修改的局部变量
}
console.log(f(), x);//打印 "localchanged global"
console.log(g(), y);//打印 "local globalchanged"

严格eval()

use strict

严格模式将eval列为保留字,不允许使用别名覆盖eval()函数。并且变量名、函数名、函数参数或者异常捕获的参数都不能取名为eval。

其他运算符

?: 条件运算符

?:是JavaScript中唯一的一个三元运算符。

条件操作符的操作数可以是任意类型。第一个操作数当成布尔值,如果真则计算第二个操作数并返回计算结果,如果假计算第三个操作数并返回计算结果。第二个和第三个操作数总是会计算其中之一,不可能二者同时执行。(使用if语句也会带来同样效果)

greeting="hello"+(username?username:"there");
//上下二者等价
greeting="hello"
if(username)
    greeting+=username;
else
    greeting+="there";

typeof 运算符

一元运算符,放在其单个操作数的前面,操作数可以是任意类型。返回操作数类型的一个字符串。

typeof x
typeof (x)
x typeof x
undefined "undefined"
null "object"
true/false "boolean"
任意数字或NaN "number"
任意字符串 "string"
任意函数 "function"
任意内置对象(非函数) "object"
任意宿主对象 由编译器各自实现的字符串。但是不是"undefined" "boolean" "number" "string"

delete 运算符

一元运算符,删除对象属性或者数组元素。

var o={x:1;y:2};
delete o.x;
"x" in o;//false,这个属性不再存在
var a=[1,2,3,4];
delete a[2];//删除倒数第二个元素
2 in a;//false,这个元素不再存在
a[2];//undefined
a;// [1,2,,4]
a.length;// 4。删除操作,没有修改数组长度

delete希望他的操作数是一个左值,如果不是左值,delete将不进行任何操作并返回true。否则,delete驶入删除这个指定的左值,如果删除成功,返回true,否则返回false。内置核心和客户端属性是不能删除的,用var语句生命的变量不能删除,通过function语句定义的函数和函数参数也不能删除。

> fff=function(){}
[Function: fff]
> typeof fff
'function'
> delete fff
true
> fff
ReferenceError: fff is not defined

> function ff(){}
undefined
> delete ff
false
> ff
[Function: ff]

ES5严格模式下,有操作数是一个属性访问表达式才会正常工作,否则将会抛出语法错误。

void 运算符

一元运算符,它出现在操作数之前,操作数可以是任意类型。操作数会照常计算,但忽略结果并返回undefined。

最常用在客户端的URL中。

<a href="javascript:void window.open();">打开一个新窗口</a>

, 逗号运算符

二元运算符,操作数任意类型,先计算左操作数,再计算右操作数,最后返回右操作数的值。

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

推荐阅读更多精彩内容