《JavaScript 权威指南(第六版)》一——词法、类型、值、变量

第一章:JavaScript概述
第二章:词法结构
第三章:类型、值和变量

2017.02.16-2017.02.20

JavaScript 概述

JavaScript 是一门高端的、动态的、弱类型的编程语言,适合面向对象和函数式的编程风格。

JavaScript 语法源自 Java,一等函数(first-class function)来自于Scheme,基于原型(prototype-based)的继承来自于Self。

JavaScript 语言核心

var a=[];//创建一个空数组
a.push(1,2,3);//push()方法向数组中添加元素
a.reverse();//将数组元素次序反转

当函数和对象合写在一起时,函数就变成了”方法“。

points.dist=function(){
    var p1=this[0];
    var p2=this[1];
    var a=p2.x-p1.x;
    var b=p2.y-p1.y;
    return Math.sqrt(a*a+b*b); //勾股定理计算两点之间距离
}
points.dist();

面向对象编程 【示例】

//定义构造函数以初始化一个新的Point对象
function Point(x,y){
    this.x=x;
    this.y=y;
};
//使用new关键字和构造函数创建一个实例
var p=new Point(2,1);//p Point {x: 2, y: 1}
//通过给构造函数的prototype对象赋值来给Point定义方法
Point.prototype.r=function(){
    return Math.sqrt(this.x*this.x+this.y*this.y);
};
//Point的实例对象p以及所有的Point实例对象继承了方法 r()
p.r();

客户端 JavaScript

JavaScript 语言核心

语法结构

字符集

JS 用 Unicode 字符集编写。Unicode和UTF-8有何区别?-知乎

区分大小写

html不区分大小写(xhtml区分大小写),标签和属性名可以使用大写也可以是小写。也可以是大小写混搭。比如<buTTon>123</button>,不过不推荐,除了属性值全部小写就好了。

空格、换行符、格式控制符

Unicode 转义序列

使用6个ASCII自负代表任意16位Unicode内码。转义序列以\u为前缀,其后跟随4个十六进制数。

//café 小实例
cafe\u0301=1// cafe添加音调
1
caf\u00e9=2
2
café
2 //"café"==="caf\u00e9"
"café"==="caf\u00e9"
true
"café"==="cafe\u0301"
false

标准化

Unicode 允许使用多种方法对一个字符进行编码。如上述实例中的字符é,其两种编码显示结果一样,但是二进制编码不同,在计算机里也不想等。

Unicode标准为所有字符定义了一个首选的编码格式,并给出了一个标准化的处理方式将文本转换为一种适合比较的标准格式,js会认为它正在解析的程序编码已经是这种标准格式,不会再对其标志符、字符串或正则表达式做标准化处理。

注释

//注释当前行
/**/注释段。不能嵌套注释。

直接量

直接量 (literal) 就是程序中直接使用的数据值。

标志符和保留字

标识符

字母、下换线、美元符开头。

保留字

【关键字】

break   delete  function    return  typeof
case    do  if  switch  var
catch   else    in  this    void    
continue    false   instanceof  throw   while
debugger    finally new true    with
default for null    try

【保留字】

class   const   enum    export  extends import  super

下面是 普通JavaScript合法。严格模式下是保留字的

implements  let private public  yield   
interface   package protected   static

严格模式同样对下面的标识符的使用做了严格限制,它们不完全是保留字,但不能用做变量名、函数名或参数名:

arguments   eval

ES3 将 Java 所有关键字列为保留字,尽管这些保留字在 ES5 上放宽了限制,如果希望代码能在 ES3实现的解释器上运行,应当避免间使用下面关键字:

abstract    double  goto    native  static
boolean enum    implements  package super
byte    export  import  private synchronized
char    extends int protected   throws
class   final   interface   public  transient
const   float   long    short   volatile

JavaScript 预定义了很多全局变量和函数,应当避免把它们的名字 用作变量名和函数名:

arguments   encodeURI   Infinity    Number  RegExp
Array   encodeURIComponent  isFinite    Object  String
Boolean Error   isNan   parseFloat  SyntaxError
Date    eval    JSON    parseInt    TypeError
decodeURI   EvalError   Math    RangeError  undefined
decodeURIComponent  Funcgtion   NaN ReferenceError  URIError 

可选的分号

  • 如果当前语句和下一行语句无法合并解析,JS则在第一行后填补分号(return,break,continue除外,如果这三个关键字紧跟着换行,则JS会在换行出填补分号)。
  • 涉及++,--运算符时,如果将其作为后缀,应该放在同一行。

【示例】

x
++
y
解析为x;++y;而不是x++;y

类型、值、变量

JavaScript数据类型分类:

  • 原始类型(primitive type):数字、字符串、布尔值、null、undefined
  • 对象类型(object type):属性的几个。每个属性都由“名/值对”构成。普通的js对象是“命名值”的无序集合。js定义了一种特殊对象数组,表示带编号的值得有序集合。js还定义了一种特殊对象函数。函数是具有与它相关联的可执行代码的对象,通过调用函数来运行可执行代码,并返回计算结果。

如果函数用来初始化(new)一个新建的对象,称之为构造函数(constructor)。每个构造函数定义一类(class)对象——由构造函数初始化的对象组成的集合。除了数组,函数外,JS定义了其他三种有用的类。日期(Date)类定义了代表日期的对象。正则(RegExp)类定义了表示正则表达式的对象。错误(Error)类定义了表示JS程序中运行时错误和语法错误的对象。可以通过定义自己的构造函数来定义需要的类。

JavaScript 解释器有自己的内存管理机制,可以自动对内存进行垃圾回收(garbage collection)。

数字

JavaScript 中的所有数字均用浮点数值表示。JS采用IEEE754标准定义的64位浮点格式表示数字,这意味着它能表示的最大值是±1.7976031348623157×10308,最小值是±5×10-324

按照JS中的数字格式,能够表示的整数范围是-9007199254740992~9007199254740992(即 -253~253)。如果使用了超过此范围的整数,则无法保证低位数字的精度。然而需要注意的是,JS中实际的操作(比如数组索引,位操作符)则是基于32位整数。

当一个数字直接出现在JS中,我们称之为数字直接量。

整型直接量

//十进制
123456
// 十六进制
0xff
// 八进制,某些可能不支持,在ES6严格模式下,八进制是禁止的
0377

浮点型直接量

[digits][.digits][(E|e)(+|-)digits]

例如:

3.14
.333
6.2e23
1.4E-32

算术运算

Math.pow(2,3) //8;2的3次幂
Math.round(.6) //1.0;四舍五入
Math.ceil(.6) //1.0;向上取整
Math.floor(.6) //0.0;向下取整
Math.abs(-5) //5;求绝对值
Math.max(x,y,z) //返回最大值
Math.min(x,y,z) //返回最小值
Math.random() //生成一个大于等于0小于1.0的伪随机数
Math.PI //π;圆周率
Math.E //e;自然对数的底数
Math.sqrt(3) //3的平方根
Math.pow(3,1/3) //3的立方根
Math.sin(0) //三角函数。Math.cos(),Math.tan(),Math.sin(30/180*Math.PI) 
Math.log(10) //10的自然对数
Math.log(100)/Math.LN10 //以10为底100的对数
Math.log(512)/Math.LN2 //以2为底512的对数
Math.exp(3) //e的3次幂

JS的算术运算在溢出(overflow)、下溢(underflow)或被零整除时不会报错.

下溢,当运算结果无限接近于零并比JavaScript能表示的最小值还小的时候发生的情形。这时候,JavaScript会返回0,负数的下溢会返回-0。

无穷大除以无穷大、给负数开放、算术运算符与非数字或无法转换位数字的操作数一起使用时都会返回NaN。

无穷大(Infinity),负无穷大(-Infinity),负零(-0),NaN(not a number)

二进制浮点数和四舍五入错误

JavaScript 采用IEEE-754浮点数表示法。可以精确地表示分数 1/2,1/8等,但不能精确地表示十进制分数。

.3-.2
0.09999999999999998
.2-.1
0.1
0.5-0.125
0.375

日期和时间

Date()构造函数,用于创建表示日期和时间的对象。

var then=new Date(2017,0,1);//2017年1月1日
var later=new Date(2017,0,1,17,10,30);//2017年1月1日 17:10:30
var now=new Date();//当前日期和时间
var elapsed=now-then;//日期减法,计算时间间隔的毫秒数
later.getFullYear();//获取年份
later.getMonth();//获取月份,从0开始计数,一月份是0
later.getDate();//获取日期,从1开始计数,一号是1
later.getDay();//星期几,0是星期日,5是星期五
later.getHours();//获取小时
later.getMinutes();//获取分钟
later.getSeconds();//获取秒
later.getUTCHours();//获取使用UTC表示的小时的时间

文本

字符串string是一组由16位值组成的不可变的有序序列,每个字符通常来自于Unicode字符集。JavaScript通过字符串类型来表示文本。字符串的长度是其所含16位值得个数。

字符串直接量

由单引号或双引号括起来的字符序列。单引号定界的字符串中可以包含双引号,双引号定界的字符串中可以包含单引号。

ES3中字符串直接量必须写在一行中,ES5中字符串可以拆分为数行,每行必须以反斜线\结束。

var str="Hello\
World"
"Hello    World"//tab键等都会计算进字符串中

转义字符

\

\o NULL字符(\u0000)
\b 退格符(\u0008)
\t 水平制表符(\u0009)
\n 换行符(\u000A)
\v 垂直制表符(\u000B)
\f 换页符(\u000C)
\r 回车符(\u000D)
\" 双引号(\u0022)
\' 撇号 单引号 (\u0027)
\\ 反斜线(\u005C)
\xXX 由两位十六进制数XX指定的Latin-1字符
\uXXXX 由四位十六进制数XXXX指定的Unicode字符

字符串的使用

+用于数字,表示两数相加。用于字符串表示字符串连接。

var s="hello,world";
s.charAt(0);//"h";第一个字符
s[0];
s.charAt(s.length-1);//"d";最后一个字符
s[s.length-1]
s.substring(1,4);//"ell";第二到四个字符
s.slice(1,4);//"ell";同上
s.slice(-4);//"orld";最后四个字符
s.indexOf("l");//2;字符l首次出现的位置
s.lastIndexOf("l");//9;字符l最后出现的位置
s.indexOf("l",3);//3;在位置3及之后首次出现l的位置
s.split(",");//["hello","world"];通过逗号分隔成子串
s.replace("h","H");//"Hello,world";全文文本替换
s.toUpperCase();//"HELLO,WORLD";大写

模式匹配

RegExp()构造函数,用于创建表示文本匹配模式的对象。”正则表达式“(regular expression),JavaScript采用Perl中的正则表达式语法。String和RegExp对象均定义了利用正则表达式进行模式匹配和查找替换的函数。

var text="testing:1,2,3";
var pattern=/\d+/g;//匹配所有包含一个或多个数字的实例
pattern.test(test);//true;匹配成功
text.search(pattern);//9;首次匹配成功的位置
text.match(pattern);//["1","2","3"];所有匹配组成的数组
text.replace(pattern,"#");//"testing:#,#,#"
text.spilt(/\D+/);//["","1","2","3"];用非数字字符截取字符串

布尔值

true,false

任意JavaScript的值都可以转换为布尔值。下面这些值会被转换成false:

undefined
null
0
-0
NaN
""

所有其他值,包括对象数组都会转换成true

null undefined

null是JavaScript的关键字,表示一个特殊值“空值”。typeof(null)返回object。可以将其认为是一个特殊的对象值,含义是非对象。实际上,通常认为null是它自由类型的唯一一个成员,可以表示数字、字符串、对象是“无值”的。

JavaScript用过未定义的值表示更深层次的“空值”。它是变量的一种取值,表示变量没有初始化,如果要查询对象属性或数组元素的值时返回undefined则说明这个属性或元素不存在。如果函数没有返回任何职,则返回undefined。引用没有提供实参的函数形参的值也只会得到undefined。undefined是预定义的全局变量。typeof(undefined)返回undefined,表明这个值是这个类型的唯一成员 。

二者都表示“值的空缺”,两者往往可以互换。“==”认为二者相等(严格区分需要使用“===”)。

如果想赋值给变量或者属性,或是将其作为参数传入函数,最佳选择是使用null。

全局对象

全局对象(global object)

  • 全局属性,比如undefined、Infinity、NaN
  • 全局函数,比如 isNaN()、parseInt()、eval()
  • 构造函数,比如Date()、RegExp()、String()、Object()、Array()
  • 全局对象,比如Math、JSON

在代码最顶级,this代表全局对象。在客户端JavaScript中,Windows对象充当了全局对象。Window对象定义了核心全局属性,也针对Web浏览器和客户端JavaScript定义了以少部分其他全局属性,如果代码中声明了全局变量,那么这个全局变量就是全局对象的一个属性。

包装对象

JavaScript对象是一种复合值:它是属性或已命名值得集合。通过.引用属性值。当属性值是一个函数的时候,称其为方法。

关于字符串的属性:只要引用了字符串s的属性,JavaScript就会将字符串值通过调用new String(s)的方式转换成对象,这个对象集成了字符串的方法,并被用来处理属性的引用。一旦属性引用结束,这个新创建的对象就会销毁。

同样,数字和布尔值也有各自的方法Number()和Boolean()。

null和undefined没有包装对象:访问他们的属性会造成一个类型错误。

【示例】

var s="test";
s.len=4;//创建临时对象并给其len属性赋值随即销毁对象。
var t=s.len;//返回undefined,访问原始字符串的对象,所以不存在
var s="test",n=1,b=true;//一个字符串,一个数字,一个布尔值  typeof分别是string,number,boolean
var S=new String(s);//一个字符串对象  typeof是object
var N=new String(N);//一个数值对象  typeof是object
var B=new Boolean(b);//一个布尔对象  typeof是object

不可变的原始值和可变的对象引用

  • 原始值的比较是值的比较:只有在值相等时它们才相等。
  • 对象的比较并非值的比较:即使两个对象包含同样的属性和值,它们也不是相等的。当且仅当它们引用同一个基对象时,它们才相等。

类型转换

JavaScript 类型转换

转换和相等性

显性类型转换

Number("3") //3
String(false) //"false"
Boolean([]) //true
Object(3) //new Number(3)

如果+的一个操作数是字符串,它将会把另一个操作数转换成字符串。一元+运算符将其操作数转换为数字。一元!将其操作数转换为布尔值并取反。

转换类型的惯用法:

x+"" //等价于String(x)
+x //等价于Number(x)
!!x //等价于Boolean(x),注意是双叹号
数字转字符串

toString(),参数可选,如果不指定参数,转换规则将是基于十进制。同样,亦可以将数字转换为其他进制数。

var n=17;
binary_string=n.toString(2);//"10001"
octal_string="0"+n.toString(8);//"021"
hex_string="0x"+n.String(16);//"0x11"
  • toFixed()根据小数点后的指定位数将数字转换为字符串,不使用指数计数法。
  • toExponential()使用指数计数法将数字转换成指数形式的字符串,其中小数点前只有一位,小数点后的位数则有参数指定(也就是说有效数字位数比指定的位数要多一位)。
  • toPrecision()根据指定的有效数字位数将数字转换成字符串。如果有效数字的位数少于数字证书部分的位数,则转换成指数形式。

【示例】

var n=123456.789;
n.toFixed(0);//"123457"
n.toFixed(2);//"123456.79"
n.toExponential(1);//"1.2e+5"
n.toExponential(3);//"1.235e+5"
n.toPrecision(7);//"123456.8"
n.toPrecision(10);//"123456.7890"

字符串转数字

Number() 将其转换为一个整数或浮点数直接量,只能给予十进制数进行转换,并且不能出现非法的尾随字符。

parseInt()函数之解析整数。如果前缀是0x0X,parseInt()将其解释为16进制数(parseFloat()不支持)。
parseFloat()函数解析整数和浮点数。
parseInt和parseFloat都会跳过任意数量的前导空格,尽可能解析更多数值字符,并忽略后面的内容。如果第一个非空格字符是非法的数字直接量,并最终返回NaN。

parseInt("3 zdy go");//3
parseFloat(" 3.14 miles");//3.14
parseInt("-12.32");//-12
parseInt("0xFF");//255
parseInt("0xff");//255
parseInt("-0xFF");//-255
parseFloat("0xff");//0。只解析到0。
parseFloat(".1");//0.1
parseInt("0.1");//0
parseInt(".1");//NaN
parseFloat("$34.33");//NaN

parseInt()可以接受第二个可选参数,这个参数指定数字转换的基数,合法的取值范围是2~36。

parseInt("11",2);//3 1*2+1
parseInt("ff",16);// 255 15*16+15
parseInt("zz",36);//1295 35*36+35
parseInt("077",8);//63 7*8+7
parseInt("077",10);//77 7*10+7

对象转换为原始值

对象到布尔值

所有对象都转换为true。对于包装对象亦是如此,new Boolean(false)是一个对象,将转换为true。

对象到字符串
toString()

数组类(Array Class)的toString()将每个数组元素转换为一个字符串,并在元素之间添加逗号后合并成结果字符串。

函数类(Function Class)的toString()返回这个函数的实际定义的表示方式。实际上,这里实现的方式通常是将用户定义的函数转换成JavaScript源代码字符串。

日期类(Date Class)的toString()方法返回了一个可读的(可悲JavaScript解析的)日期和时间字符串。

RegExp类(RegExp class)定义的toString()方法将RegExp对象转换为表示正则表达式直接量的字符串。

({x:1,y:2}).toString()//[object Object]"
[1,2,3].toString();//"1,2,3"
(function(x) { f(x); }).toString();//实际测试:"function (x) { f(x); }",原书:"function (x) {\n f(x);\n }"
/\d+/g.toString();//    /\\d+/g
new Date(2000,0,1).toString();// "Sat Jan 01 2000 00:00:00 GMT+0800 (CST)"
valueOf()

如果存在任意原始值,它将默认将对象转换为表示它的原始值。对象是复合值,而且大多数对象无法真正表示一个原始值,因此默认的valueOf()方法简单地返回对象本身,而不是返回一个原始值。数组、函数和正则表达式简单地继承了这个默认的方法,调用这些类型的实例的valueOf()方法只是简单返回对象本身。日期类定义的valueOf()方法会返回它的一个内部表示:1970年1月1日以来的毫秒数。

var fun=function (a,b){return a+b;}
fun.valueOf()//输出是:function (a,b){return a+b;}
var now=new Date();
now.valueOf()//输出是:1487555446043
JavaScript 中对象到字符串的转换
  • 如果对象具有toString()方法,则调用这个方法。如果它返回一个原始值,JavaScript将这个值转换为字符串(如果本身不是字符串),并返回这个字符串结果。
  • 如果对象没有toString()方法,或者这个方法不返回一个原始值,那么JavaScript会调用valueOf()方法。如果存在这个方法,则调用它。如果返回值是原始值,JavaScript将这个值转换为字符串(如果本身不是字符串),并返回这个字符串结果。
  • 否则,抛出类型错误异常。
JavaScript 中对象到数字的转换
  • 如果对象具有ValueOf()方法,如果返回一个原始值,则JavaScript将原始值转换为数字(如果需要)并返回这个数字。
  • 如果对象具有toString()方法,如果返回一个原始值(字符串直接量),则JavaScript将其转换并返回。

3.8.3看的懵懵的,部分内容截图如下。

JavaScript 权威指南 3.8.3

变量声明

var i;
var sum;
var i,sum;
var message="hello";
var i=0,j=0,str="hello world",sign=false;

动态语言类型。

var i=10;
i="ten";//合法

变量作用域

var scope="global";
function checkscope() {
    var scope = "local";
    function nested() {
        var scope = "nested";
        return scope;
    }
    return nested();
}
checkscope();//"nested"
函数作用域和声明提前

类似C语言的编程语言中,花括号内的每一段代码都具有各自的作用域,而且变量在声明它们的代码段之外是不可见的,我们称之为块级作用域(block scope)。

JavaScript中没有块级作用域,使用函数作用域(function scope):变量在声明它们的函数体以及这个函数体嵌套的任意函数体内都有定义。

即if for while等循环内声明的变量实际作用域也是整个函数内。

function test(o){
    var i=0;//i在函数体内均有定义
    if(typeof(o)=="object"){
        var j=0;//j在函数体内均有定义,不仅仅是在代码段
        for(var k=0;k<10;k++){//k在函数体内均有定义,不仅仅是在循环内
            console.log(k);//打印0-9
        }
        console.log(k);//k已经定义了,打印10
    }
    console.log(j);//j已经定义了,可能没有初始化。如果o是对象,打印0。否则打印undefined。
}

JavaScript的函数作用域是指在函数内声明的所有变量在函数体内始终是可见的。这个特性是声明提前,即JavaScript函数内声明的所有变量(但不涉及赋值)都被提前到函数体的顶部,变量初始化保留在原来的位置。

var scope = "global";
function f() {
    console.log(scope);
    var scope = "local";
    console.log(scope);
}
f();//打印undefined local

作为属性的变量

当使用var声明一个变量时,创建的这个属性是不可配置的,即这个变量无法通过delete删除。如果没有使用严格模式并给一个未命名的变量赋值的话,JavaScript会自动创建一个全局变量。以这种方式创建的变量是全局对象的正常的可配置属性,并可以删除它们。

var trueval=1;
fakeval=2;
this.fakevar2=3;
delete trueval;//false,变量并没有删除
delete fakeval;//true,变量被删除
delete this.fakeval2;//true,变量被删除

作用域链

在JavaScript的最顶层代码中,作用域链由一个全局对象组成。

在不包含嵌套的函数体内,作用域链上有两个对象,第一个是定义函数参数和局部变量的对象,第二个是全局对象。

在一个嵌套的函数体内,作用域链至少有三个对象。

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

推荐阅读更多精彩内容