第2章 值

第 2 章值

数组( array)、字符串( string)和数字( number)是一个程序最基本的组成部分

2.1 数组

var a = [ 1, "2", [3] ];
a[2][0] === 3; // true

对数组声明后即可向其中加入值,不需要预先设定大小

var a = [ ];
a.length; // 0
a[0] = 1;
a[1] = "2";
a[2] = [ 3 ];
a.length; // 3

使用 delete 运算符可以将单元从数组中删除,但是请注意,单元删除后,数
组的 length 属性并不会发生变化。

在创建“稀疏”数组( sparse array,即含有空白或空缺单元的数组)时要特别注意:

var a = [ ];
a[0] = 1;
// 此处没有设置a[1]单元
a[2] = [ 3 ];
a[1]; // undefined
a.length; // 3

a[1] 的值为 undefined,但这与将其显式赋值为 undefined( a[1] = undefined)还是
有所区别

数组通过数字进行索引,但有趣的是它们也是对象,所以也可以包含字符串键值和属性
(但这些并不计算在数组长度内):

var a = [ ];
a[0] = 1;
a["foobar"] = 2;
a.length; // 1
a["foobar"]; // 2
a.foobar; // 2

这里有个问题需要特别注意,如果字符串键值能够被强制类型转换为十进制数字的话,它
就会被当作数字索引来处理。

var a = [ ];
a["13"] = 42;
a.length; // 14

使用对象来存放键值 / 属性值,用数组来存放数字索引值。

类数组

有时需要将类数组(一组通过数字索引的值)转换为真正的数组,这一般通过数组工具函数(如 indexOf(..)、 concat(..)、 forEach(..) 等)来实现。

一些 DOM 查询操作会返回 DOM 元素列表,它们并非真正意义上的数组,但十分
类似。另一个例子是通过 arguments 对象(类数组)将函数的参数当作列表来访问(从
ES6 开始已废止)。

function foo() {
var arr = Array.prototype.slice.call( arguments );
arr.push( "bam" );
console.log( arr );
}
foo( "bar", "baz" ); // ["bar","baz","bam"]

用 ES6 中的内置工具函数 Array.from(..) 也能实现同样的功能:

...
var arr = Array.from( arguments );
...

2.2 字符串

var a = "foo";
var b = ["f","o","o"];

字符串和数组的确很相似,它们都是类数组

a.length; // 3
b.length; // 3
a.indexOf( "o" ); // 1
b.indexOf( "o" ); // 1
var c = a.concat( "bar" ); // "foobar"
var d = b.concat( ["b","a","r"] ); // ["f","o","o","b","a","r"]
a === c; // false
b === d; // false
a; // "foo"
b; // ["f","o","o"]

但这并不意味着它们都是“字符数组”,比如:

a[1] = "O";
b[1] = "O";
a; // "foo"
b; // ["f","O","o"]

JavaScript 中字符串是不可变的,而数组是可变的。并且 a[1] 在 JavaScript 中并非总是合法语法,在老版本的 IE 中就不被允许(现在可以了)。 正确的方法应该是 a.charAt(1)。

字符串不可变是指字符串的成员函数不会改变其原始值,而是创建并返回一个新的字符
串。而数组的成员函数都是在其原始值上进行操作。

c = a.toUpperCase();
a === c; // false
a; // "foo"
c; // "FOO"
b.push( "!" );
b; // ["f","O","o","!"]

许多数组函数用来处理字符串很方便。虽然字符串没有这些函数,但可以通过“借用”数
组的非变更方法来处理字符串:

a.join; // undefined
a.map; // undefined
var c = Array.prototype.join.call( a, "-" );
var d = Array.prototype.map.call( a, function(v){
return v.toUpperCase() + ".";
} ).join( "" );
c; // "f-o-o"
d; // "F.O.O.

另一个不同点在于字符串反转( JavaScript 面试常见问题)。数组有一个字符串没有的可变更成员函数 reverse():

a.reverse; // undefined
b.reverse(); // ["!","o","O","f"]
b; // ["f","O","o","!"]

可惜我们无法“借用”数组的可变更成员函数,因为字符串是不可变的:

Array.prototype.reverse.call( a );
// 返回值仍然是字符串"foo"的一个封装对象

一个变通(破解)的办法是先将字符串转换为数组,待处理完后再将结果转换回字符串:

var c = a
// 将a的值转换为字符数组
.split( "" )
// 将数组中的字符进行倒转
.reverse()
// 将数组中的字符拼接回字符串
.join( "" );
c; // "oof"

请注意!上述方法对于包含复杂字符( Unicode,如星号、多字节字符等)的
字符串并不适用。这时则需要功能更加完备、能够处理 Unicode 的工具库。
可以参考 Mathias Bynen 的 Esrever( https://github.com/mathiasbynents/esrever)。

如果需要经常以字符数组的方式来处理字符串的话,倒不如直接使用数组。这样就不用在
字符串和数组之间来回折腾。可以在需要时使用 join("") 将字符数组转换为字符串。

2.3 数字

JavaScript 没有真正意义上的整数

与大部分现代编程语言(包括几乎所有的脚本语言)一样, JavaScript 中的数字类型是基
于 IEEE 754 标准来实现的,该标准通常也被称为“浮点数”。 JavaScript 使用的是“双精
度”格式(即 64 位二进制)。

2.3.1 数字的语法

数字前面的 0 可以省略:

var a = 0.42;
var b = .42;

小数点后小数部分最后面的 0 也可以省略:

var a = 42.0;
var b = 42.;

** 这种写法没问题,只是不常见,但从代码的可读性考虑,不建议这样写。**

默认情况下大部分数字都以十进制显示,小数部分最后面的 0 被省略,如:

var a = 42.300;
var b = 42.0;
a; // 42.3
b; // 42

特别大和特别小的数字默认用指数格式显示,与 toExponential() 函数的输出结果相同。
例如:

var a = 5E10;
a; // 50000000000
a.toExponential(); // "5e+10"
var b = a * a;
b; // 2.5e+21
var c = 1 / a;
c; // 2e-11

Number.prototype 中的方法(参见第 3 章)。例如, tofixed(..) 方法可指定小数部分的显示位数:

var a = 42.59;
a.toFixed( 0 ); // "43"
a.toFixed( 1 ); // "42.6"
a.toFixed( 2 ); // "42.59"
a.toFixed( 3 ); // "42.590"
a.toFixed( 4 ); // "42.5900"

上例中的输出结果实际上是给定数字的字符串形式,如果指定的小数部分的显示
位数多于实际位数就用 0 补齐。

toPrecision(..) 方法用来指定有效数位的显示位数:

var a = 42.59;
a.toPrecision( 1 ); // "4e+1"
a.toPrecision( 2 ); // "43"
a.toPrecision( 3 ); // "42.6"
a.toPrecision( 4 ); // "42.59"
a.toPrecision( 5 ); // "42.590"
a.toPrecision( 6 ); // "42.5900"

上面的方法不仅适用于数字变量,也适用于数字常量。

// 无效语法:
42.toFixed( 3 ); // SyntaxError
// 下面的语法都有效:
(42).toFixed( 3 ); // "42.000"
0.42.toFixed( 3 ); // "0.420"
42..toFixed( 3 ); // "42.000"

42.tofixed(3) 是无效语法,因为 . 被视为常量 42. 的一部分

一些工具库扩展了 Number.prototype 的内置方法(参见第 3 章)以提供更
多的数值操作,比如用 10..makeItRain() 方法来实现十秒钟金钱雨动画等
效果。

下面的语法也是有效的(请注意其中的空格):

42 .toFixed(3); // "42.000"

然而对数字常量而言,这样的语法很容易引起误会,不建议使用。

当前的 JavaScript 版本都支持这些格式:

0xf3; // 243的十六进制
0Xf3; // 同上
0363; // 243的八进制

从 ES6 开始,严格模式( strict mode)不再支持 0363 八进制格式(新格式如
下)。 0363 格式在非严格模式( non-strict mode)中仍然受支持,但是考虑到
将来的兼容性,最好不要再使用(我们现在使用的应该是严格模式)。

0o363; // 243的八进制
0O363; // 同上
0b11110011; // 243的二进制
0B11110011; // 同上

建议尽量使用小写的 0x、 0b 和 0o。

2.3.2 较小的数值

0.1 + 0.2 === 0.3; // false

二进制浮点数中的 0.1 和 0.2 并不是十分精确,它们相加的结果并非刚好等于
0.3,而是一个比较接近的数字 0.30000000000000004,所以条件判断结果为 false。

判断 0.1 + 0.2 和 0.3 是否相等。最常见的方法是设置一个误差范围值,通常称为“机器精度”( machine epsilon), 对JavaScript 的数字来说,这个值通常是 2^-52 (2.220446049250313e-16)。

从 ES6 开始,该值定义在 Number.EPSILON 中,我们可以直接拿来用,也可以为 ES6 之前
的版本写 polyfill:

if (!Number.EPSILON) {
Number.EPSILON = Math.pow(2,-52);
}

可以使用 Number.EPSILON 来比较两个数字是否相等(在指定的误差范围内):

function numbersCloseEnoughToEqual(n1,n2) {
return Math.abs( n1 - n2 ) < Number.EPSILON;
}
var a = 0.1 + 0.2;
var b = 0.3;
numbersCloseEnoughToEqual( a, b ); // true
numbersCloseEnoughToEqual( 0.0000001, 0.0000002 ); //

能够呈现的最大浮点数大约是 1.798e+308(这是一个相当大的数字),它定义在 Number.
MAX_VALUE 中。最小浮点数定义在 Number.MIN_VALUE 中,大约是 5e-324,它不是负数,但
无限接近于 0 !

2.3.3 整数的安全范围

能够被“安全”呈现的最大整数是 2^53 - 1,即 9007199254740991,在 ES6 中被定义为
Number.MAX_SAFE_INTEGER。最小整数是 -9007199254740991,在 ES6 中被定义为 Number.MIN_SAFE_INTEGER。

有时 JavaScript 程序需要处理一些比较大的数字,如数据库中的 64 位 ID 等。由于
JavaScript 的数字类型无法精确呈现 64 位数值,所以必须将它们保存(转换)为字符串。

好在大数值操作并不常见(它们的比较操作可以通过字符串来实现)。如果确实需要对大
数值进行数学运算,目前还是需要借助相关的工具库。

2.3.4 整数检测

要检测一个值是否是整数,可以使用 ES6 中的 Number.isInteger(..) 方法:

Number.isInteger( 42 ); // true
Number.isInteger( 42.000 ); // true
Number.isInteger( 42.3 ); // false

也可以为 ES6 之前的版本 polyfill Number.isInteger(..) 方法:

if (!Number.isInteger) {
Number.isInteger = function(num) {
return typeof num == "number" && num % 1 == 0;
};
}

要检测一个值是否是安全的整数,可以使用 ES6 中的 Number.isSafeInteger(..) 方法:

Number.isSafeInteger( Number.MAX_SAFE_INTEGER ); // true
Number.isSafeInteger( Math.pow( 2, 53 ) ); // false
Number.isSafeInteger( Math.pow( 2, 53 ) - 1 ); // true

可以为 ES6 之前的版本 polyfill Number.isSafeInteger(..) 方法:

if (!Number.isSafeInteger) {
Number.isSafeInteger = function(num) {
return Number.isInteger( num ) &&
Math.abs( num ) <= Number.MAX_SAFE_INTEGER;
};
}

2.3.5 32 位有符号整数

虽然整数最大能够达到 53 位,但是有些数字操作(如数位操作)只适用于 32 位数字,
所以这些操作中数字的安全范围就要小很多,变成从 Math.pow(-2,31)( -2147483648,
约-21 亿)到 Math.pow(2,31) - 1( 2147483647,约 21 亿)。

a | 0 可以将变量 a 中的数值转换为 32 位有符号整数,因为数位运算符 | 只适用于 32 位
整数(它只关心 32 位以内的值,其他的数位将被忽略)。因此与 0 进行操作即可截取 a 中
的 32 位数位

某些特殊的值并不是 32 位安全范围的,如 NaN 和 Infinity(下节将作相关
介绍),此时会对它们执行虚拟操作( abstract operation) ToInt32(参见第 4
章),以便转换为符合数位运算符要求的 +0 值。

2.4 特殊数值

2.4.1 不是值的值

undefined 类型只有一个值,即 undefined。 null 类型也只有一个值,即 null。它们的名
称既是类型也是值。

  • null 指空值( empty value)
  • undefined 指没有值( missing value)

或者:

  • undefined 指从未赋值

  • null 指曾赋过值,但是目前没有值

null 是一个特殊关键字,不是标识符,我们不能将其当作变量来使用和赋值。然而
undefined 却是一个标识符,可以被当作变量来使用和赋值。

2.4.2 undefined

在非严格模式下,我们可以为全局标识符 undefined 赋值(这样的设计实在是欠考虑!):

function foo() {
undefined = 2; // 非常糟糕的做法!
}
foo();
function foo() {
"use strict";
undefined = 2; // TypeError!
}
foo();

在非严格和严格两种模式下,我们可以声明一个名为 undefined 的局部变量。再次强调最
好不要这样做!

function foo() {
"use strict";
var undefined = 2;
console.log( undefined ); // 2
}
foo();
//永远不要重新定义 undefined。

void 运算符

undefined 是一个内置标识符通过 void 运算符即可得到该值

void 并不改变表达式的结果,
只是让表达式不返回值:

var a = 42;
console.log( void a, a ); // undefined 42

void 运算符在其他地方也能派上用场,比如不让表达式返回任何结果

function doSomething() {
// 注: APP.ready 由程序自己定义
if (!APP.ready) {
// 稍后再试
return void setTimeout( doSomething,100 );
}
var result;
// 其他
return result;
}
// 现在可以了吗?
if (doSomething()) {
// 立即执行下一个任务
}

是为了确保 if 语句不产生误报( false positive),我们要 void 掉它

if (!APP.ready) {
// 稍后再试
setTimeout( doSomething,100 );
return;
}

2.4.3 特殊的数字

1. 不是数字的数字

数学运算的操作数不是数字类型(或者无法解析为常规的十进制或十六进制数字),将它
理解为“无效数值”“失败数值”或者“坏数值”可能更准确些.

var a = 2 / "foo"; // NaN
typeof a === "number"; // true

“不是数字的数字”仍然是数字类型

var a = 2 / "foo";
a == NaN; // false
a === NaN; // false

而 NaN != NaN 为 true

var a = 2 / "foo";
isNaN( a ); // true

isNaN(..) 有一个严重的缺陷,它的检查方式过于死板,就
是“检查参数是否不是 NaN,也不是数字”。

var a = 2 / "foo";
var b = "foo";
a; // NaN
b; "foo"
window.isNaN( a ); // true
window.isNaN( b ); // true——晕!

从 ES6 开始我们可以使用工具函数 Number.isNaN(..)。 ES6 之前的浏览器的 polyfill 如下:

if (!Number.isNaN) {
Number.isNaN = function(n) {
return (
typeof n === "number" &&
window.isNaN( n )
);
};
}
var a = 2 / "foo";
var b = "foo";
Number.isNaN( a ); // true
Number.isNaN( b ); // false——好!

NaN 是 JavaScript 中唯一一个不等于自身的值。

if (!Number.isNaN) {
Number.isNaN = function(n) {
return n !== n;
};
}

尽量使用 Number.isNaN(..),无论是系统内置还是 polyfill。如果你仍在代码中使用 isNaN(..),那么你的程序迟早会出现 bug。

2. 无穷数

var a = 1 / 0; // Infinity
var b = -1 / 0; // -Infinity

上例的结果为 Infinity(即 Number.POSITIVE_INfiNITY),如 果 除 法 运 算 中 的 一 个 操 作 数 为 负 数, 则 结 果 为 -Infinity( 即 Number.NEGATIVE_INfiNITY)。

JavaScript 使用有限数字表示法( finite numeric representation,即之前介绍过的 IEEE 754
浮点数),所以和纯粹的数学运算不同, JavaScript 的运算结果有可能溢出,此时结果为
Infinity 或者 -Infinity。

var a = Number.MAX_VALUE; // 1.7976931348623157e+308
a + a; // Infinity
a + Math.pow( 2, 970 ); // Infinity
a + Math.pow( 2, 969 ); // 1.7976931348623157e+308

规范规定,如果数学运算(如加法)的结果超出处理范围,则由 IEEE 754 规范中的“就
近取整”( round-to-nearest)模式来决定最后的结果。例如,相对于 Infinity, Number.MAX_
VALUE + Math.pow(2, 969) 与 Number.MAX_VALUE 更为接近,因此它被“向下取整”( round
down);而 Number.MAX_VALUE + Math.pow(2, 970) 与 Infinity 更为接近,所以它被“向上
取整”( round up)。

Infinity/Infinity 是一个未定义操作,结果为 NaN。那么有穷正数除以 Infinity 呢?很简单,结果是 0

** 3. 零值**

var a = 0 / -3; // -0
var b = 0 * -3; // -0

加法和减法运算不会得到负零( negative zero)。

负零在开发调试控制台中通常显示为 -0,但在一些老版本的浏览器中仍然会显示为 0。

根据规范,对负零进行字符串化会返回 "0":

var a = 0 / -3;
// 至少在某些浏览器的控制台中显示是正确的
a; // -0
// 但是规范定义的返回结果是这样!
a.toString(); // "0"
a + ""; // "0"
String( a ); // "0"
// JSON也如此,很奇怪
JSON.stringify( a ); // "0"

如果反过来将其从字符串转换为数字,得到的结果是准确的:

+"-0"; // -0
Number( "-0" ); // -0
JSON.parse( "-0" ); // -0

JSON.stringify(-0) 返回 "0"

负零转换为字符串的结果令人费解

var a = 0;
var b = 0 / -3;
a == b; // true
-0 == 0; // true
a === b; // true
-0 === 0; // true
0 > -0; // false
a > b; // false

要区分 -0 和 0,不能仅仅依赖开发调试窗口的显示结果,还需要做一些特殊处理:

function isNegZero(n) {
n = Number( n );
return (n === 0) && (1 / n === -Infinity);
}
isNegZero( -0 ); // true
isNegZero( 0 / -3 ); // true
isNegZero( 0 ); // false

有些应用程序中的数据需要以级数形式来表示(比如动画帧的移动速度),数字的符号位
( sign)用来代表其他信息(比如移动的方向)。此时如果一个值为 0 的变量失去了它的符
号位,它的方向信息就会丢失。所以保留 0 值的符号位可以防止这类情况发生。

ES6 中新加入了一个工具方法 Object.is(..) 来判断两个值是否绝对相等,可以用来处理
上述所有的特殊情况:

var a = 2 / "foo";
var b = -3 * 0;
Object.is( a, NaN ); // true
Object.is( b, -0 ); // true
Object.is( b, 0 ); // false

对于 ES6 之前的版本, Object.is(..) 有一个简单的 polyfill:

if (!Object.is) {
Object.is = function(v1, v2) {
// 判断是否是-0
if (v1 === 0 && v2 === 0) {
return 1 / v1 === 1 / v2;
}
// 判断是否是NaN
if (v1 !== v1) {
return v2 !== v2;
}
// 其他情况
return v1 === v2;
};
}

Object.is(..) 主要用来处理那些特殊的相等比较

2.5 值和引用

var a = 2;
var b = a; // b是a的值的一个副本
b++;
a; // 2
b; // 3
var c = [1,2,3];
var d = c; // d是[1,2,3]的一个引用
d.push( 4 );
c; // [1,2,3,4]
d; // [1,2,3,4]

简单值(即标量基本类型值, scalar primitive) 总是通过值复制的方式来赋值 / 传递,包括
null、 undefined、字符串、数字、布尔和 ES6 中的 symbol。

复合值( compound value)——对象(包括数组和封装对象,参见第 3 章)和函数,则总
是通过引用复制的方式来赋值 / 传递。

c 和 d 则分别指向同一个复合值 [1,2,3] 的两个不同引用。请注意, c 和 d 仅仅是指向值
[1,2,3],并非持有。

var a = [1,2,3];
var b = a;
a; // [1,2,3]
b; // [1,2,3]
// 然后
b = [4,5,6];
a; // [1,2,3]
b; // [4,5,6]

function foo(x) {
x.push( 4 );
x; // [1,2,3,4]
// 然后
x = [4,5,6];
x.push( 7 );
x; // [4,5,6,7]
}
var a = [1,2,3];
foo( a );
a; // 是[1,2,3,4],不是[4,5,6,7]

向函数传递 a 的时候,实际是将引用 a 的一个复本赋值给 x,而 a 仍然指向 [1,2,3]。
在函数中我们可以通过引用 x 来更改数组的值( push(4) 之后变为 [1,2,3,4])。但 x =
[4,5,6] 并不影响 a 的指向,所以 a 仍然指向 [1,2,3,4]。

不能通过引用 x 来更改引用 a 的指向,只能更改 a 和 x 共同指向的值。

function foo(x) {
x.push( 4 );
x; // [1,2,3,4]
// 然后
x.length = 0; // 清空数组
x.push( 4, 5, 6, 7 );
x; // [4,5,6,7]
}
var a = [1,2,3];
foo( a );
a; // 是[4,5,6,7],不是[1,2,3,4]

foo( a.slice() );

slice(..) 不带参数会返回当前数组的一个浅复本( shallow copy)。由于传递给函数的是指
向该复本的引用,所以 foo(..) 中的操作不会影响 a 指向的数组。

function foo(wrapper) {
wrapper.a = 42;
}
var obj = {
a: 2
};
foo( obj );
obj.a; // 42

与预期不同的是,虽然传递的是指向数字对象的引用复本,但我们并不能通过它来更改其
中的基本类型值:

function foo(x) {
x = x + 1;
x; // 3
}
var a = 2;
var b = new Number( a ); // Object(a)也一样
foo( b );
console.log( b ); // 是2,不是3

原因是标量基本类型值是不可更改的(字符串和布尔也是如此)。如果一个数字对象的标
量基本类型值是 2,那么该值就不能更改,除非创建一个包含新值的数字对象。

x = x + 1 中, x 中的标量基本类型值 2 从数字对象中拆封(或者提取)出来后, x 就神不
知鬼不觉地从引用变成了数字对象,它的值为 2 + 1 等于 3。然而函数外的 b 仍然指向原
来那个值为 2 的数字对象。

我们还可以为数字对象添加属性(只要不更改其内部的基本类型值即可),通过它们间接
地进行数据交换。
不过这种做法不太常见,大多数开发人员可能都觉得这不是一个好办法。
相对而言,前面用 obj 作为封装对象的办法可能更好一些。这并不是说数字等封装对象没
有什么用,只是多数情况下我们应该优先考虑使用标量基本类型。

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

推荐阅读更多精彩内容