ES6学习笔记二 (扩展对象的功能性、解构、Symbol)

第四章 扩展对象的功能性

1. 对象字面量语法扩展

直接看例子

function createPersonES5(name, age){
    return {
        name: name,
        age: age
    }
}

function createPersonES6(name, age){
    return {
        name,
        age
    }
}

ES6中通过属性初始化的简写语法,可以消除这种属性名称与局部变量之间的重复书写。当字面量里只有一个属性的名称时,JavaScript引擎会在可访问作用域中查找其同名变量。

2. 对象方法语法扩展

var personES5 = {
    name: "haha",
    sayName: function () {
        console.log(this.name);
    }
}

var personES6 = {
    name: "haha",
    sayName() {
        console.log(this.name);
    }
}

ES6中消除了冒号和function关键字。在personES6中创建一个sayName()方法,该属性被赋值为一个匿名函数表达式,它拥有在ES5中定义的对象方法所具有的全部特性。二者唯一的区别就是,简写方法可以使用super关键字。

3. 可计算属性

ES5中想使用变量的值作为对象的属性

var lastName = "last name",
    person = {};
person[lastName] = "YouYou";

ES6中可在创建对象字面量的同时使用可计算属性名称。

var lastName = "last name";
person = {
    [lastName]: 'HAHAHAHA'
}

4. 新增方法

ES的一个设计目标是不在创建新的全局函数,也不在Object.prototype上创建新的方法。当开发者想向标准添加新方法时,他们会找一个适当的现有对象,让这些方法可用。结果,当没有其他合适的对象时,全局Object对象会收到越来越多的对象方法。

4.1 Object.is()方法

ES6引入Object.is()方法来弥补全等运算符的不准确运算。
主要处理的是+0和-0; NaN和NaN

console.log(+0 == -0);              // true
console.log(+0 === -0);             // true
console.log(Object.is(+0, -0));     // false

console.log(NaN == NaN);            // false
console.log(NaN === NaN);           // false
console.log(Object.is(NaN, NaN));   // true

console.log(5 == 5);                // true
console.log(5 == '5');              // true
console.log(5 === 5);               // true
console.log(5 === '5');             // false
console.log(Object.is(5, 5));       // true
console.log(Object.is(5, '5'));     // false

4.2 Object.assign()方法

混合方法,和zepto的extend方法完成同样的功能。
接受一个接收对象和任意数量的源对象,最终返回接收对象。
assign使用赋值操作符“=”来赋值相关属性,所以是浅赋值。

var receiver = {};
var object1 = {
    array: [1,2,3,4],
    name: "HaHa"
}

Object.assign(receiver, object1);

console.log(receiver.array);    // [1, 2, 3, 4]
console.log(receiver.name);     // HaHa
receiver.array[0] = 123;
console.log(object1.array);     // [123, 2, 3, 4]

如果多个源对象具有同名属性,则排位靠后的源对象会覆盖靠前的。

请记住,Object.assign()方法不能将提供者的访问器属性复制到接收对象中。由于Object.assign()方法执行了赋值操作,因此提供者的访问器属性最终会转变为接收对象中的一个数据属性。

var receiver = {},
supplier = {
    get name(){
        return "HeiHei"
    }
}
Object.assign(receiver, supplier); 

var descriptor = Object.getOwnPropertyDescriptor(receiver, "name");
console.log(descriptor.value);      // HeiHei
console.log(descriptor.get);        // undefined

在这段代码中,supplier有一个名为name的访问器属性。当调用Object.assign()方法时返回字符串"HeiHei",因此receiver接收这个字符串后将其存为数据属性receiver.name。

顺便说一下Object.getOwnPropertyDescriptorMDN
返回指定对象上一个自有属性对应的属性描述符。(自有属性指的是直接赋予该对象的属性,不需要从原型链上进行查找的属性

function Sup(name) {
    this.name = name;
}
Sup.prototype.sayName = function () {
    console.log(this.name);
};
var aa = new Sup(123);
aa.func1 = function(){console.log(123)}
var descriptorSayName = Object.getOwnPropertyDescriptor(aa, 'sayName');  // undefined
var descriptorFunc1 = Object.getOwnPropertyDescriptor(aa, 'func1'); // {value: ƒ(), writable: true, enumerable: true, configurable: true}

再看一个例子:

const source = {  
    set foo(value) {  
        console.log(value);  
    },
    
    sayHi: function(){
      console.log('Hi!');
    }  
};  
const target1 = {};  
Object.assign(target1, source);  
let fooDescriptor = Object.getOwnPropertyDescriptor(target1, 'foo'); // {value: undefined, writable: true, enumerable: true, configurable: true}   
let sayHiDescriptor = Object.getOwnPropertyDescriptor(target1, 'sayHi'); // {value: ƒ, writable: true, enumerable: true, configurable: true}
console.log(sayHiDescriptor.value) // function(){console.log('Hi!')};
sayHiDescriptor.value();     // Hi!

上面代码中,source对象的foo属性的值是一个赋值函数,Object.assign方法将这个属性拷贝给target1对象,结果该属性的值变成了undefined。这是因为Object.assign方法总是拷贝一个属性的值(具体的function也是属性的值,录入sayHi方法),而不会拷贝它背后的赋值方法或取值方法。
所以,正确的实现拷贝的方法可以参考:

const shallowMerge = (target, source) => Object.defineProperties(  
    target,  
    Object.getOwnPropertyDescriptors(source)  
); 

利用了definePropertiesgetOwnPropertyDescriptors
仍对上例:

let newTarget1 = shallowMerge(target1, source);
Object.getOwnPropertyDescriptor(newTarget1, 'foo')
// {get: undefined, set: ƒ, enumerable: true, configurable: true}     TODO: ????

5. 重复的对象字面量

"use strict";  
var person = {
    name: "Name1",
    name: "Name2"       // ES5严格模式下会有语法错误
}

console.log(person.name);  // Name2

ES6中重复属性检查被移除了,无论在严格还是非严格模式下,代码不再检查重复属性,对于每一组重复属性,都会选取最后一个值。

6. 自有属性枚举顺序

ES5没有规定,由各厂商自行决定。ES6严格定义了对象的自有属性被枚举时的返回顺序。这会影响到Object.getOwnPropertyNames()方法及Reflect.ownKeys方法返回属性的方式,Object.assign()方法处理属性的顺序也将随之改变。
自有属性枚举顺序的基本规则是:

  1. 所有数字键按升序排序。
  2. 所有字符串键按照他们被加入对象的顺序排序。
  3. 所有的symbol键按照它们被加入对象的顺序排序。
 var obj = {
    a: 'a',
    1: 1,
    0: 0,
    10: 10,
    2: 2,
    '1': '字符串1',
    b: 'b',
    d: 'd'
}
obj.c = 1;
console.log(Object.getOwnPropertyNames(obj).join(""));  //01210abdc  (数字键(包含字符串形式)按升序排序)
console.log(obj[1]);  // 字符串1    前面的数字1的值被覆盖
console.log(obj['1']);  // 字符串1

对于for-in循环,由于并非所有厂商都遵循相同的实现方式,因此仍未指定一个明确的枚举顺序;而Object.keys()和JSON.stringify()方法都指明与for-in方法使用相同的枚举顺序,因此它们的枚举顺序目前也不明晰。

7. 增强对象的原型

7.1 改变对象的原型

let person = {
    getGreeting() {
        return "Hello";
    }
}

let dog = {
    getGreeting() {
        return "Woof";
    }
};

// 以person对象为原型  
let friend = Object.create(person);
console.log(friend.getGreeting());                          // Hello
console.log(Object.getPrototypeOf(friend) === person);      // true

// 将原型设置为dog
Object.setPrototypeOf(friend, dog);
console.log(friend.getGreeting());                          // Woof
console.log(Object.getPrototypeOf(friend) === dog);      // true

对象原型的真实值被存储在内部专用属性[[Prototype]]中,调用Object.getPrototypeOf会返回其中的值,调用Object.setPrototypeOf会改变其中的值。这不是唯一操作[[Prototype]]的方法。

7.2 简化原型访问的Super调用

如果你想重写对象实例的方法,有需要调用与他同名的原型方法,则在ES5中可以这样实现:

let person = {
    getGreeting() {
        return "Hello";
    }
}

let dog = {
    getGreeting() {
        return "Woof";
    }
};

let friend = {
    getGreeting() {
        return Object.getPrototypeOf(this).getGreeting.call(this) + ", hi!";
    }
}

// 将原型设置为person
Object.setPrototypeOf(friend, person);
console.log(friend.getGreeting());                          // Hello, hi!
console.log(Object.getPrototypeOf(friend) === person);      // true

对于ES6,重写friend,可以得到相同的结果。

let friend = {
    getGreeting() {
        // return Object.getPrototypeOf(this).getGreeting.call(this) + ", hi!";
        return super.getGreeting() + ", hi!"
    }
}

但是换一种写法就会出错,!!必须!!要在使用简写方法的对象中使用Super引用

let friend = {
    // getGreeting() {
    //     // return Object.getPrototypeOf(this).getGreeting.call(this) + ", hi!";
    //     return super.getGreeting() + ", hi!"
    // }
    getGreeting: function(){
        return super.getGreeting() + ", hi!"    // SyntaxError: 'super' keyword unexpected here
    }
}

这个示例中用匿名function定义一个属性,由于在当前上下文中super引用是非法的,因此当调用super.getGreeting时会报错。
Super在多重继承的情况下非常有用,因为在这种情况下,使用Object.getPrototypeOf()方法会出现问题。

let person = {
    getGreeting() {
        return "Hello";
    }
}

let friend = {
    getGreeting() {
        return Object.getPrototypeOf(this).getGreeting.call(this) + ", hi!";
        // return super.getGreeting() + ", hi!"
    }
}

Object.setPrototypeOf(friend, person);
// 原型是friend
let relative = Object.create(friend);

console.log(person.getGreeting());      // Hello
console.log(friend.getGreeting());      // Hello, hi!
console.log(relative.getGreeting());    // Maximum call stack size exceeded

this是relative,relative的原型是friend对象,当执行relative的getGreeting方法时,会调用friend的getGreeting方法,而此时的this的值为relative,所以使用这个方法时Object.getPrototypeOf(this)又找到了friend方法,造成无限死循环下去。
ES5中,这个问题很难解决。ES6中使用super即可解决这个问题。

8. 正式的方法定义(Super寻找的本质)

ES6中正式将方法定义为一个函数,他会有一个内部的[[HomeObject]]属性来容纳这个方法从属的对象。

let person = {
    // 是方法
    getGreeting(){
        return "Hello";
    }
}
// 不是方法
function shareGreeting(){
    return "hi";
}

Super的所有引用都通过[[HomeObject]]来确定后续的运行过程。

  1. 在[[HomeObject]]属性上调用Object.getPrototypeOf()方法来检索原型的引用;
  2. 搜寻原型找到同名函数;
  3. 设置this绑定并且调用相应的方法
let person = {
    getGreeting() {
        return "Hello";
    }
}

// 以person对象为原型
let friend = {
    getGreeting() {
        return super.getGreeting() + ", hi!"
    }
}
Object.setPrototypeOf(friend, person);

console.log(friend.getGreeting());      // Hello, hi!

如果不调用Object.setPrototypeOf(friend, person);方法指定原型,执行到
return super.getGreeting() + ", hi!"语句时会报错

(intermediate value).getGreeting is not a function

第五章 解构

1. 对象解构

基本语法:

let node = {
    type: "Identifier",
    name: "foo"
};
let {type, name} = node;
console.log(type);  // Identifier
console.log(name);  // foo

不要忘记初始化程序 如果使用var、let或const解构声明变量,则必须要提供初始化程序(也就是等号右边的值)

1.1 解构赋值

上一个例子是声明的时候解构,同样可以给变量赋值的时候使用解构语法。

let node = {
    type: "Identifier",
    name: "foo"
},
    type = "Literal",
    name = 5;

({ type, name } = node);    // 先后顺序无所谓

console.log(type);  // Identifier
console.log(name);  // foo

注意({ type, name } = node);语句的小括号不能忘,否则会报错

 Unexpected token =

JavaScript引擎将一对开放的花括号视为一个代码块,而语法规定,代码块语句不允许出现在赋值语句左侧,添加小括号后可以将块语句转化为一个表达式,从而实现整个解构赋值的过程
解构表达式的值与表达式右侧(=号左右)的值相等,如此一来,在任何可以使用值的地方都可以使用解构表达式

let node = {
    type: "Identifier",
    name: "foo"
},
    type = "Literal",
    name = 5;

function outputInfo(value){
    console.log(value === node);    // true
}

outputInfo({ type, name } = node);

console.log(type);  // Identifier
console.log(name);  // foo

解构表达式(也就是=号右侧的表达式)如果为null或undefined会导致程序抛出错误。也就是说,任何尝试读取null或undefined的属性的行为都会触发运行时的错误TypeError: Cannot match against 'undefined' or 'null'

1.2 默认值

let node = {
    type: "Identifier",
    name: "foo"
};

let {type, name, value} = node;

console.log(type);  // Identifier
console.log(name);  // foo
console.log(value);  // undefined

使用默认值

let node = {
    type: "Identifier",
    name: "foo"
};

let {type, name, value = true} = node;

console.log(type);  // Identifier
console.log(name);  // foo
console.log(value);  // true

1.3 为非同名局部变量赋值

let node = {
    type: "Identifier",
    name: "foo"
};

let {type: localType, name: localName, value: localValue = "123"} = node;

console.log(localType);  // Identifier
console.log(localName);  // foo
console.log(localValue);  // 123
console.log(type);  //  Uncaught ReferenceError: type is not defined
console.log(name);  //  没有执行

type: localType语法的含义是读取名为type的属性并将其存储在变量localType中。

1.4 嵌套对象解构

let node = {
    type: "Identifier",
    name: "foo",
    loc: {
        start: {
            line: 1,
            column: 1
        },
        end:{
            line:1,
            column: 4
        }
    }
};

let {loc: {start}} = node;

console.log(start.line);  // 1
console.log(start.column);  // 1

所有冒号前的标识符都代表在对象中的检索为止,其右侧为被赋值的变量名;如果冒号右侧是花括号,则意味着要赋予的最终值嵌套在对象内部更深的层级中。
同样的,也可以使用非同名变量:

let node = {
    type: "Identifier",
    name: "foo",
    loc: {
        start: {
            line: 1,
            column: 1
        },
        end:{
            line:1,
            column: 4
        }
    }
};

let {loc: {start: localStart}} = node;

console.log(localStart.line);  // 1
console.log(localStart.column);  // 1
语法警示

在使用嵌套解构功能时,你很可能无意中创建了一个无效表达式。

let {loc: {}} = node;

由于右侧只有一对花括号,因而不会声明任何绑定。这个语法在将来可能会被废弃,要警示自己不要写类似的代码。

2. 数组解构

let color = ['red', 'green', 'blue'];
let [firstColor, secondColor] = color;
console.log(firstColor);        // red
console.log(secondColor);       // green

let [, , thirdColor] = color;
console.log(thirdColor);        // blue

解构赋值

let color = ['red', 'green', 'blue'];
let firstColor = 'black';
let secondColor = 'purple';

[firstColor, secondColor] = color;
console.log(firstColor);        // red
console.log(secondColor);       // green

这与解构对象赋值类似,只不过不在需要小括号包裹。
数组解构还有一个独特的用例:交换两个变量的值

let a = 1, b = 2;
[a, b] = [b, a];
console.log(a);     // 2
console.log(b);     // 1

嵌套数组解构

let colors = ['red', ['green', 'lightgreen'], 'blue'];
let [firstColor, [secondColor]] = colors;
console.log(firstColor);    // red
console.log(secondColor);    // green

不定元素

let colors = ['red', 'green', 'lightgreen', 'blue'];
let [firstColor, ...restColor] = colors;
console.log(firstColor);            // red
console.log(restColor.length);      // 3
console.log(restColor[0]);          // green

不定元素可以用来复制数组

let colors = ['red', 'green', 'lightgreen', 'blue'];
let ES5Clone = colors.concat();
let [...ES6cloneColors] = colors;

3. 混合解构

let node = {
    type: "Identifier",
    name: "foo",
    loc: {
        start: {
            line: 1,
            column: 1
        }
    }, 
    range: [0, 3]
};  

let {
    loc : {start},
    range: [startIndex]
} = node;

console.log(start.line);        // 1
console.log(startIndex);        // 0

这个特性在从JSON配置中提取信息时极为有效。

4. 解构参数

解构可以用在函数参数的传递过程中,这种使用方式更特别。当定义一个接收大量可选参数的JavaScript函数时,我们通常会创建一个可选对象,将额外的参数定义为这个对象的属性:

// ES5
function setCookie(name, value, options) {
    options = options || {};

    let secure = options.secure,
        path = options.path,
        domain = options.domain,
        expire = options.expire;

    // 设置cookie的代码
}

setCookie('type', 'js', {
    secure: true,
    expire: 600000
})

这个函数的问题是,只查看函数的声明部分,无法辨识函数的预期参数。
如果将options定义为解构参数,则可以更清晰地了解函数预期传入的参数。

// ES6
function setCookie(name, value, {secure, path, domain, expire}) {
    // 设置cookie的代码
}

setCookie('type', 'js', {
    secure: true,
    expire: 600000
})

这个函数更简单清晰。
但是有一个问题,如果不传第三个参数会报错Cannot match against 'undefined' or 'null'.
这是因为调用setCookie函数时浏览器的实际执行为:

function setCookie(name, value, options) {
    let  {secure, path, domain, expire} = options;
    // 设置cookie的代码
}

如果解构参数是必须的,则可以忽略这个问题。但如果希望将解构参数设为可选的,那么就必须为其提供默认值来结果这个问题:

function setCookie(name, value, {secure, path, domain, expire} = {}) {
    // 设置cookie的代码
}

setCookie('type', 'js')

解构参数的默认值

可以为解构参数指定默认值

function setCookie(name, value,
    { secure = true, 
        path = '/', 
        domain = 'example.com', 
        expire = new Date(Date.now() + 360000000) 
    }) {
    // 设置cookie的代码
}

然而这样遇上不带第三个参数的情况又会报错,于是改成下面这样

function setCookie(name, value,
    { secure = true,
        path = '/',
        domain = 'example.com',
        expire = new Date(Date.now() + 360000000)
    } = {
            secure: true,
            path: '/',
            domain: 'example.com',
            expire: new Date(Date.now() + 360000000)
        }
) {
    // 设置cookie的代码
}

setCookie('type', 'js')

看起来是不是更复杂了..... 注意第三个参数 前面一部分是 = 号, 后面一部分是 : 号
那好吧,再改。

const setCookieDefaults = {
    secure: true,
    path: '/',
    domain: 'example.com',
    expire: new Date(Date.now() + 360000000)
}

function setCookie(name, value,
    { secure = setCookieDefaults.secure,
        path = setCookieDefaults.path,
        domain = setCookieDefaults.domain,
        expire = setCookieDefaults.expire
    } = setCookieDefaults
) {
    // 设置cookie的代码
}

这下真的没法再改了,这样虽然在全局里多引入了一个变量setCookieDefaults (以前明明可以没有,或者在函数里)。
使用解构参数后,不得不面对处理默认参数的复杂逻辑,但它也有好的一面,如果要改变默认值,可以立即在setCookieDefaults 中修改,数据将自动同步到所有出现过的地方。(强词夺理的感觉)

第六章 Symbol和Symbol属性

在ES5中,语言包含五中原始类型:字符串型、数字型、布尔型、null和undefined。ES6引入了第六种原始类型:Symbol。

1. 创建Symbol

所有的原始值,除了Symbol意外都有各自的字面形式,例如布尔类型的true或数字类型的42。可以通过全局的Symbol函数创建一个Symbol。

let firstName = Symbol();
let person = {};

person[firstName] = "NowhereToRun";
console.log(person[firstName]); // NowhereToRun

上述代码,创建了一个名为firstName的Symbol,用它将一个新的属性赋值给person对象,每当你想访问这个属性时一定要用到最初定义的Symbol

由于Symbol是原始值,因此调用new Symbol()会导致程序抛出错误。//Uncaught TypeError: Symbol is not a constructor

Symbol接受一个可选参数,其可以让你添加一段文本描述即将创建的Symbol,这段描述不可用于属性访问,但是建议背刺黄建Symbol时都添加,以便阅读代码和调试Symbol程序。

let firstName = Symbol("first name");
let person = {};

person[firstName] = "NowhereToRun";

console.log("first name" in person);  // false
console.log(person[firstName]); // NowhereToRun
console.log(firstName); // Symbol(first name)
console.log(typeof firstName); // symbol

Symbol的描述被存储在内部的[[Description]]属性中,只有当调用Symbol的toString()方法时才可以读取这个属性。在执行console.log()时隐式调用了firstname的toString()方法,但是不能直接在代码里访问[[Description]]。
辨识Symbol可以使用typeof操作符。

2. Symbol的使用方法

所有可计算属性名的地方,都可以使用Symbol。

let firstName = Symbol("first name");

// 使用一个可计算对象字面量属性
let person = {
  [firstName]: "NowhereToRun"
};

// 将属性设置为只读  
Object.defineProperty(person, firstName, {
  writable: false
});

let lastName = Symbol("last name");
Object.defineProperties(person,{
  [lastName]: {
    value: "wagaga",
    writable: false
  }
});

console.log(person[firstName]); // NowhereToRun
console.log(person[lastName]);  // wagaga

3. Symbol共享体系

有时我们可能希望在不同的代码中共享一个Symbol,ES6提供了一个可以随时访问的全局Symbol注册表。
如果想创建一个可共享的Symbol,要使用Symbol.for()方法,它只接受一个参数,也就是即将创建的Symbol的字符串标识符,这个参数同样也被用作Symbol的描述。

let uid = Symbol.for("uid");
let object = {
  [uid]: "12345"
}

console.log(object[uid]); // 12345
console.log(uid); // Symbol(uid)

let uid2 = Symbol.for("uid");
console.log(uid === uid2); // true
console.log(object[uid2]); // 12345

Symbol.for()方法首先在全局Symbol注册表中搜索键为“uid”的Symbol是否存在,如果存在,直接返回已有的Symbol。后续调用会返回相同的Symbol,他俩完全等价。
还有一个与Symbol共享有关的特性:可以使用Symbol.keyFor()方法在Symbol全局注册表中检索与Symbol有关的键。

let uid = Symbol.for("uid");
console.log(Symbol.keyFor(uid));  // “uid”  

let uid2 = Symbol.for("uid");
console.log(Symbol.keyFor(uid2));  // “uid”  

let uid3 = Symbol("uid");
console.log(Symbol.keyFor(uid3));  // undefined  

Symbol全局注册表是一个类似全局作用域的共享环境,也就是说你不能假设目前环境中存在哪些建。当使用第三方组件时,尽量使用Symbol键的命名空间以减少命名冲突。例如jQuery的代码可以为所有键添加“jquery”的前缀,“jquery.element”或其他类似的键

4. Symbol属性检索

Object.keys()Object.getOwnPropertyNames() 方法可以检索对象中所有的属性名:前一个方法返回所有可枚举的属性名,后一个方法不考虑属性的可枚举性,一律返回。然而为了保持ECMAScript5函数的原有功能,这两个方法都不支持Symbol属性。ES6新增了一个方法Object.getOwnPropertySymbols方法返回包含所有Symbol自由属性的数组。 如下图

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

推荐阅读更多精彩内容