Part1.模块一:函数式编程与 JS 异步编程、手写 Promise

简答题

一、谈谈你是如何理解JS异步编程的,EcentLoop、消息队列都是做什么的,什么是宏任务,什么是微任务?

  • JS 异步编程:
    • 解答:
             JavaScript语言的执行环境是单线程,单线程是指一次只能完成一个任务,如果有多个任务,则需要排队,等待前一个任务完成后,才能开始后一个任务。基于这种原因而产生了两种执行任务的模式:同步模式和异步模式,且随着JavaScript面临的需求要来越多,它可以运行在浏览器、服务器上等,为了满足这些需求,使得JavaScript的规模和复杂性也在持续增长,所以JavaScriptd中的异步编程也在不断地调整,往更友好的方向发展,JavaScript异步编程经历了回调函数、Promise、生成器函数Generator、以及现在Async/Await等几个发展阶段。
             在第一阶段使用的是回调函数,它是最基本的异步操作方式,以Ajax请求最为常见,它是最简单、容易理解和实现的异步编程;但是不利于代码的阅读和维护,各部分之间高度耦合,使得程序结构混乱,流程难以追踪,每个任务只能指定一个回调函数,不能使用try catch捕获错误,不能直接return,且极易出现回调地狱导致的调式困难,以及控制反转导致的一系列信任问题
ajax(urlA, () => {
    // 处理逻辑
    ajax(urlB, () => {
        // 处理逻辑
        ajax(urlC, () => {
            // 处理逻辑
        })
    })
})

       在第二阶段引入了Promise,它是ES6推出的一种异步编程的解决方案。Promise是承诺的意思,这个承诺在未来某个时刻会有一个确定的答复,该承诺有三种状态:等待中(pending)、完成(resolved)、拒绝(rejected)。Promise是个构造函数,接受一个函数作为参数。作为参数的函数有两个参数:resolve和reject,分别对应完成和拒绝两种状态。我们可以选择在不同时候执行resolve或reject去触发下一个动作,执行then方法里的函数。Promise实现了链式调用,每次调用then之后返回的都是一个Promise对象,如果在then使用了return,return返回的值会被Promise.resolve()包装。它很好的解决了回调函数中的回调地狱,解决了控制反转导致的信任问题,将代码的执行主动权拿了回来;但是Promise一旦状态从等待改变为其他状态就不再可变了,还有如果不设置回调函数,Promise内部抛出的错误。

new Promise((resolve, reject) => {   
    console.log('new Promise');   
    resolve('success'); }) 
    console.log('end');

       第三阶段使用生成器函数Generator,它是一种特殊的函数,其最大的特点是控制函数的执行。可以让我们用同步的方式写代码,可以分步执行并得到异步操作的结果,能够知晓异步操作的过程,以及切入修改异步操作的过程。但是需要手动去控制next(...),将回调成功的返回数据送回JavaScript的主流程中;

function *foo(x) {
  let y = 2 * (yield (x + 1));
  let z = yield (y / 3);
  return (x + y + z);
}
let it = foo(5);
console.log(it.next());   // => {value: 6, done: false}
console.log(it.next(12)); // => {value: 8, done: false}
console.log(it.next(13));// => {value: 42, done: true}

       最新阶段的Async/Await异步处理编程,其中async函数返回一个 Promise 对象,就是将函数返回使用Promise.resolve(),和then处理返回值一样,可以使用then方法添加回调函数。await后边一般跟Promise对象,async函数执行遇到await后,等待后面的Promise对象的状态从pending变成resolve后,将resolve的参数返回并自动往下执行直到下一个await或结束。它解决了Generator需要手动控制next(...)执行的问题。但它存在一个缺陷是如果多个异步代码没有依赖性却使用了await会导致性能降低。

async function test() {
    console.log('1')
}
console.log(test)   // Promise {<resolve>: "1"}

       整个异步过程都是通过内部的消息队列和事件循环实现的
       每个阶段的突破都是为了解决现有阶段的技术问题,异步编程的发展也是一个循序渐进的过程。

  • EventLoop、消息队列:
    • 解答:
             事件循环机制和消息队列的维护是由事件触发线程控制的,事件触发线程是由浏览器渲染引擎提供的,它会维护一个消息队列
             EventLoop是事件循环,主要负责监听调用栈(执行栈)和消息队列(任务队列),一旦调用栈中所有的任务都结束了,事件循环就会从消息队列中取出第一个回调函数,然后压入到调用栈中,一旦消息队列中发生了变化,事件循环就会监听到
             消息队列:消息队列是类似队列的数据结构,遵循先入先出(FIFO)的规则。如果把调用栈理解为正在执行的工作表,那么消息队列则可以理解成待办的工作表,JS引擎会先做完调用栈中所有的任务,然后通过事件循环从消息队列中再取一个任务出来继续执行,以此类推,整个过程随时可以往消息队列中再去放任务,这些任务在消息队列中会排队等待事件循环
             事件循环机制:
                 1.JS引擎线程会维护一个执行栈,同步代码会依次加入执行栈中,然后依次执行并出栈
                 2.JS引擎线程遇到异步函数,会将异步函数交给相应的WebApi,并继续执行后面的任务
                 3.WebApi会在条件满足的时候,将异步对应的回调加入到消息队列中,等待执行
                 4.执行栈为空时,JS引擎线程会去取消息队列中的回调函数(如果有的话),并加入到执行栈中执行
                 5.完成后出栈,继续执行4的操作,直至消息队列中的回调函数为空,以上便是事件循环的机制
  • 宏任务、微任务
    • 解答:
             在JS中,有两类任务队列:宏任务队列(macrotask)和微任务队列(microtask),宏任务可以由多个,微任务只有一个
      • macrotask:主代码块、setTimeout、setInterval、setImmediate、I/O、UI rendering等(可以看到,事件队列中的每一个事件都是一个 macrotask,现在称之为宏任务队列)
      • microtask:Promise、process.nextTick、Object.observer等

       每次执行栈执行的代码即是一个宏任务,包括任务队列(宏任务队列)中的,因为执行栈中的宏任务执行完后会去取任务队列(宏任务队列)中的任务加入执行栈中
       在执行宏任务时遇到Promise等,会创建微任务(.then()里面的回调),并加入到微任务队列队尾;微任务必然是在某个宏任务执行的时候创建的,而在下一个宏任务开始之前,浏览器会对页面重新渲染。同时,在上一个宏任务执行完成后,渲染页面之前,会执行当前微任务队列中的所有微任务。
       执行机制
           1.执行一个宏任务(执行栈中没有就从消息队列中获取)
           2.执行过程中如果遇到微任务,就将微任务添加刀微任务的任务队列中
           3.宏任务执行完毕后,立即执行当前微任务队列中的所有微任务(依次执行)
           4.当前宏任务执行完毕,开始检查渲染,然后GUI接管渲染
           5.喧渲染完毕后,JS引擎线程继续,开始下一个宏任务(从宏任务队列中获取)

代码题

一、将下面异步代码使用Promise的方式改进

异步代码.png

解答:使用Promise的方式改进实现如下:

// 使用Promise改进setTimeout异步代码
new Promise((resolve, reject) => {
    resolve('hello')
}).then(value => {
    return value + ' ' + 'lagou' // value = hello 
})
.then(value => {
    return value + ' ' + 'I ♥ U'  // value = hello lagou 
})
.then(value => {
    console.log(value)   // hello lagou I ♥ U
})

二、基于以下代码完成四个练习

代码1-题目.png

代码2-题目.png
练习1:使用组合函数fp.flowRight()重新实现下面这个函数

练习1-题目.png

解答:fp.flowRight()重新实现如下:

//练习1
let isLastInstock = fp.flowRight(fp.prop('in_stock') , fp.last)
console.log(isLastInstock(cars)) //false   即最后一条数据的in_stock属性值为fase
练习2:使用fp.flowRight(),fp.prop(),fp.first()获取第一个car的name

解答:获取第一个car的name实现如下:

// 练习2
let getFirstCarName = fp.flowRight(fp.props('name'), fp.first)
console.log(getFirstCarName(cars))  //[ 'Ferrari FF' ]
练习3:使用帮助函数_average()重构averageDollarValue,使用函数组合方式实现

练习3-题目.png

解答:重构averageDollarValue实现如下:

let averageDollarValue = fp.flowRight(_average,fp.map(car => car.dollar_value))
console.log(averageDollarValue(cars))  //790700
练习4:使用flowRight写一个sanitizeName()函数,返回一个下划线连接的小写字符串,把数组中的name转换为这种形式,例如:sanitizeName(["Hello World"]) => ["hello_world"]

练习4-题目.png

解答:使用flowRight写一个sanitizeName()函数实现如下:

// 练习4
let _underscore = fp.replace(/\W+/g, '_')

//先遍历数组把里面字符串转成小写,在遍历数组将非单字字符转换成下划线
let sanitizeName1 = fp.flowRight( fp.map(_underscore), fp.map(fp.toLower))
//遍历数组中使用fp.flowRight组合函数,先转换成小写,再将非单字字符转换成下划线
let sanitizeName2 = fp.flowRight( fp.map(fp.flowRight(_underscore, fp.toLower)))
console.log(sanitizeName2(["Hello World",'LaGou Study']))  //[ 'hello_world', 'lagou_study' ]

三、基于下面提供的四个代码,完成后续的四个练习

代码1-题目.png

代码2-题目.png

代码3-题目.png
练习1:使用fp.add(x,y)和fp.map(f,x)创建一个能让functor里的值增加的函数ex1

练习1-题目.png

解答:让functor里的值增加的函数实现如下:

// 练习1
let ex1 = () => {
    return fp.map(fp.add(1), maybe.map(x => x)._value)
}
console.log(ex1())  //[ 6, 7, 2 ]
练习2:实现一个函数ex2,能够fp.first获取列表的第一个元素

练习2-题目.png

解答:获取列表的第一个元素实现如下:

//练习2
let xs = Container.of(['do', 'ray', 'me', 'fa', 'so', 'la', 'ti', 'do'])

let ex2 = () => {return fp.first(xs.map(x => x)._value)}
console.log(ex2())  //do
练习3:实现一个函数ex3,使用safeProp和fp.first找到user的名字的首字母

练习3-题目.png

解答:使用safeProp和fp.first找到user的名字的首字母实现如下:

// 练习3
let safeProp = fp.curry(function (x, o){
    return Maybe.of(o[x])
})
let user = {id: 2, name: 'Albert'}
let ex3 = () => {
    return fp.first(safeProp(Object.keys(user).indexOf('name'),Object.values(user))._value)
}
console.log(ex3()) //A
练习4:使用MayBe重写ex4,不要有if语句
练习4.png
// 练习4
let ex4 = function (n) {
    if(n){
        return(parseInt(n))
    }
}

let ex5 = n =>{
    return Maybe.of(n)._value 
}

let ex6 = n =>{
    return !!n? Maybe.of(n)._value : undefined
}
console.log(ex4(''))
console.log(ex5(''))
console.log(ex6(''))
注:ex5的写法会在空字符串和null的时候导致输出和ex4,ex6不一样

四、手写实现MyPromise源码

要求:尽可能还原Promise中的每一个API,并通过注释的方式描述思路和原理

MyPromise源码(myPromise.js)
/*由于这个状态频繁使用,为了使用这个常量时编辑器有代码提示并能够复用,故把它定义为常量*/
const PENDING = 'pending'  //等待
const FULFILLED = 'fulfilled'  //成功
const REJECTED = 'rejected'  //失败

class MyPromise{
    //构造函数  ==》 立即执行执行器  ==》 指的是传递过来的回调函数
    constructor(executor) {

        // 执行器错误处理 当执行器中代码在执行过程中发生错误的时候,这个时候就让promise状态变成失败
        try{
            executor(this.resolve, this.reject)
        }catch (e) {
            //捕获执行器的错误
            this.reject(e)
        }
    }

    /*状态是每个promise对象独有的,故因该把状态属性定义为实例属性*/
    status = PENDING   //promise状态  ===》 默认值为等待

    //由于每个promise对象都有自己成功之后的值,都有自己失败的原因,故应该把这两个属性定义为实例的属性
    value = undefined  //成功之后的值
    reason = undefined //失败后的原因

    //由于then方法可能会被多次调用,联想到数组能够同时存储多个函数 故在这里将属性定义为数组
    successCallback = [] //成功回调
    failCallback = []    //失败回调

    //定义为箭头函数的目的:将来在直接调用某个普通函数时,这个函数的this指向是window或者undefined,为了能让this的指向为类的实例对象即promise对象,故而使用箭头函数
    resolve = value => {

        //为了确保状态确定后就不可更改,需加以判断
        //如果状态不是等待,阻止程序向下执行
        if(this.status !== PENDING) return

        //将状态更改为成功
        this.status = FULFILLED

        //将传递过来的值进行赋值 ==》 保存成功后的值
        this.value = value

        //判断成功回调是否存在 如果存在 则调用并传递相应的参数
        //数组中存储了多个回调函数,我们需要循环遍历这个数组,并在循环的过程中调用这个回调函数
        //当数组的长度不为0时,就继续执行循环体中的代码
        //考虑到需要从前往后执行,故调用数组的shift方法,把前面的回调函数弹出来
        while(this.successCallback.length) this.successCallback.shift()()
    }

    reject = reason => {

        //为了确保状态确定后就不可更改,需加以判断
        //如果状态不是等待,阻止程序向下执行
        if(this.status !== PENDING) return

        //将状态更改为失败
        this.status = REJECTED

        //将传递过来的值进行赋值 ==》 失败后的原因
        this.reason = reason

        //判断失败回调是否存在 如果存在 则调用并传递相应的参数
        //数组中存储了多个回调函数,我们需要循环遍历这个数组,并在循环的过程中调用这个回调函数
        //当数组的长度不为0时,就继续执行循环体中的代码
        //考虑到需要从前往后执行,故调用数组的shift方法,把前面的回调函数弹出来
        while(this.failCallback.length) this.failCallback.shift()()
    }

    //then要被定义在原型对象中,并接受成功回调和失败回调两个参数
    then (successCallback, failCallback){

        //在调用then方法不传递任何参数的时候,要将最先的成功状态依次传递给有回调函数的then方法
        successCallback = successCallback? successCallback : value => value
        failCallback = failCallback? failCallback : reason => {throw reason}

        //then要实现链式调用,then方法必须返回promise对象,上一个回调的返回值传递给下一个then成功的回调
        let promise2 = new MyPromise((resolve, reject)=>{

            //判断状态并调用对应的回调函数并传递相应的参数
            if(this.status === FULFILLED){

                //由于promise2还没被实例完,在实例过程中获取不到,故使用异步实现,让同步代码先实现,再来执行异步代码,这时候就能获取到promise2
                setTimeout(()=>{

                    //then方法中的回调函数在执行过程中报错,这个错误要在下一个then的reject中捕获到
                    try{
                        let x = successCallback(this.value)
                        resolvePromise( promise2, x, resolve, reject)
                    }catch (e) {
                        //捕获then回调的错误
                        reject(e)
                    }
                }, 0)
            }else if(this.status ===REJECTED){

                //由于promise2还没被实例完,在实例过程中获取不到,故使用异步实现,让同步代码先实现,再来执行异步代码,这时候就能获取到promise2
                setTimeout(()=>{

                    // then方法中的回调函数在执行过程中报错,这个错误要在下一个then的reject中捕获到
                    try{
                        let x = failCallback(this.reason)
                        resolvePromise( promise2, x, resolve, reject)
                    }catch (e) {
                        //捕获then回调的错误
                        reject(e)
                    }
                }, 0)
            }else{

                //说明当前状态等待,但不知是调用成功还是失败的回调
                //故将成功回调和是失败回调存储起来
                this.successCallback.push(() => {
                    successCallback()
                    setTimeout(()=>{
                        try{
                            let x = successCallback(this.value)
                            resolvePromise( promise2, x, resolve, reject)
                        }catch (e) {
                            //捕获then回调的错误
                            reject(e)
                        }
                    }, 0)
                })
                this.failCallback.push(() => {
                    setTimeout(()=>{
                        try{
                            let x = failCallback(this.reason)
                            resolvePromise( promise2, x, resolve, reject)
                        }catch (e) {
                            //捕获then回调的错误
                            reject(e)
                        }
                    }, 0)
                })
            }
        });
        return promise2
    }

    //无论当前这个promise对象最终的状态是成功还是失败,finally中的这个回调函数始终都会执行一次e
    //在finally方法的后面可以链式的调用then方法拿到当前这个promise对象返回的结果
    finally (callBack){
        //如何得到当前promise对象的状态  ==》 this.then方法可以
        return this.then(value=>{
            //在finally中return了一个promise对象
            return MyPromise.resolve(callBack()).then(()=>value)
        },reason=>{
            return MyPromise.resolve(callBack()).then(()=> {throw reason})
        })
    }

    //用于当前promise对象失败的情况,如果没有传递失败的回调函数,则会被catch捕获到
    catch(failCallback){
        return this.then(undefined, failCallback)
    }

    // all方法是用来解决异步并发问题的,它允许我们按照异步代码调用的顺序得到异步执行的结果
    // all的返回值也是一个promise对象,在后面可以链式对用then方法
    // 在all中的promise对象都是成功的,那么all最后的结果也是成功,如果有一个失败,那么结果就是失败的
    // 通过类调用,故为静态方法,该方法接收一个数组作为参数
    static all (array){
        let results = []
        let index = 0

        return new MyPromise((resolve, reject) => {
            function addData(key, value) {
                results[key] = value
                index++
                // 针对异步情况下,判断index的值和数组的长度相等,说明数组中所有的内容都执行完了,从而而决定是否调用resolve回调函数
                if(index === array.length){
                    resolve(results)
                }
            }

            //循环该数组
            for(let i = 0; i < array.length; i++){
                let current = array[i]
                if(current instanceof MyPromise){
                    //promise对象,若成功的话将成功的值添加到数组中,若有一个失败的话失败的话则将失败的原因通过reject回调
                    current.then(value => addData(i, value), reason => reject(reason))
                }else{
                    //普通值 则直接添加到数组中
                    addData(i, array[i])
                }
            }

        })
    }

    //如果是普通值,则创建一个promise对象,被将这个值包裹在promise对象中,然后把创建出来的promise对象作为resolve方法的返回值,才能在后面链式调用then方法
    //如果是promise对象,则原封不动的将这个promise对象再作为resolve方法的返回值,所以才能在后面调用then方法,通过then方法的成功回调拿到这个promise对象的返回值
    static  resolve (value){
        if(value instanceof  MyPromise) return value
        return new MyPromise(resolve => resolve(value))
    }
}

//判断 x 值时普通值还是promise对象
//如果是普通值,直接调用resolve
//如果是promise对象 查看promise对象返回的结果
//再根据promise对象返回的结果 决定调用resolve 还是调用reject
function resolvePromise(promise2, x, resolve, reject){
    //相等表示自己返回自己(Promise对象自返回),这时应该调用reject回调函数
    if(promise2 === x){
        return reject(new TypeError('Chaining cycle detected for promise #<Promise>'))
    }
    if( x instanceof  MyPromise){
        //promise对象
        x.then(resolve,reject)
    }else{
        //普通值
        resolve(x)
    }
}

//导出当前类
module.exports = MyPromise;

测试代码(index.js)
/**
 * 了解promise原理及原理代码实现
 * 1.Promise 就是一个类 在执行这个类的时候 需要传递一个执行器进去 执行器会立即执行 故放在promise类的构造器中
 * 2.Promise 中有三种状态 分别为 成功(fulfilled) 失败(rejected) 等待(pending)
 *  pending -> fulfilled
 *  pending -> rejected
 ** 一旦状态确定就不可更改
 * 3.resolve和reject函数是用来更改状态
 * resolve:fulfilled
 * reject:rejected
 * 4.then方法内部做的事情就是判断状态 如果状态成功 调用成功的回调函数 如果状态失败 调用失败的回调函数 then被定义为原型对象当中
 * 5.then成功回调有一个参数 表示成功之后的值 then失败回调有一个参数 表示失败的原因
 **/
//导向MyPromise
const MyPromise = require('./myPromise')

//创建promise对象    resolve  reject 两个函数参数
let promise = new Promise((resolve, reject) => {
    //把状态变成成功 参数为成功后的值
    resolve()

    //把状态编程失败  参数为失败的原因
    reject()
})

//需实现promise的then方法,要先了解其中传递两个回调函数及其含义
//调用then方法时,首先要去判断promise状态,成功的话则调用成功回调函数,失败的话则调用失败回调函数
//由于then能被任意一个promise对象调用,故应该将它定义在原型对象中
//then成功回调有一个参数(value) 表示成功之后的值 then失败回调有一个参数(reason) 表示失败的原因
promise.then(value=>{},reason=>{})


/** 1.类核心逻辑实现调用 **/
/*let promise1 = new MyPromise((resolve, reject) => {
    resolve('成功')

    // reject('失败')
})

promise1.then(value => {
    console.log(value)  // 成功
}, reason => {
    console.log(reason)  // 失败
})*/

/** 2.在Promise类中加入异步逻辑 **/
/*let promise2 = new MyPromise((resolve, reject) => {
    setTimeout(() => {
        resolve('成功')
    },2000)

    // reject('失败')
})

promise2.then(value => {
    console.log(value)  // 等待2s后输出成功
}, reason => {
    console.log(reason)  // 等待2s后输出失败
})*/

/** 3.实现then方法多次调用添加多个处理函数 **/
/*let promise3 = new MyPromise((resolve, reject) => {
    resolve('成功')

    // reject('失败')
})

promise3.then(value => {
    console.log(1)  // 1
    console.log(value)  // 成功
}, reason => {
    console.log(reason)  // 失败
})

promise3.then(value => {
    console.log(2)  // 2
    console.log(value)  // 成功
}, reason => {
    console.log(reason)  // 失败
})

promise3.then(value => {
    console.log(3)  // 3
    console.log(value)  // 成功
}, reason => {
    console.log(reason)  // 失败
})*/

/** 4./5.实现then方法的链式调用 **/
//后面then方法拿到的值实际上是上一个回调函数的返回值
/*let promise4 = new MyPromise((resolve, reject) => {
    resolve('成功')

    // reject('失败')
})
//测试4 then中返回值的情况
promise4.then(value => {
    console.log(value)  // 成功
    return 100
}).then(value => {
    console.log(value)
})
//测试5  then中返回promise情况
function other(){
    return new MyPromise((resolve, reject) => {
        resolve('other')
    })
}
promise4.then(value => {
    console.log(value)  // 成功
    return other()
}).then(value => {
    console.log(value)  // other
})*/

/** 6.then方法链式调用识别Promise对象自返回 **/
/*let promise6 = new MyPromise((resolve, reject) => {
    resolve('成功')
    // reject('失败')
})

let p6 = promise6.then(value => {
    console.log(value)
    return p6
})

p6.then(value => {
    console.log(value)  //成功
},reason => {
    console.log(reason.message)  //Chaining cycle detected for promise #<Promise>
})*/

/** 7.捕获错误及then链式调用其他状态代码补充 **/
// 执行器错误   then方法中的回调函数在执行过程中报错,这个错误要在下一个then的reject中捕获到
/*let promise7 = new MyPromise((resolve, reject) => {
    // throw new Error('executor error')
    // resolve('成功')
    reject('失败')
})

promise7.then(value => {
    console.log(value)  //成功
},reason => {
    console.log(reason.message)  //executor error
})

promise7.then(value => {
    console.log(value)  //成功
    throw new Error('then error')
},reason => {
    console.log(reason.message)  //executor error
    return 10000
}).then(value => {
    console.log(value)  // 成功  10000
    // throw new Error('then error')
},reason => {
    console.log('xxx')   //xxx
    console.log(reason.message)  //then error
})*/

/** 8.then中方法编程可选参数 **/

/*let promise8 = new MyPromise((resolve, reject) => {
    resolve('成功')
    // reject('失败')
})
promise8.then().then().then(value =>
    console.log(value) //成功  失败
)*/


function p1() {
    return new MyPromise((resolve, reject) => {
        setTimeout(() => {
            resolve('p1')
        }, 2000)
    })
}

function p2() {
    return new MyPromise((resolve, reject) => {
        // resolve('p2')
        reject('失败')
    })
}

/** 9.promise.all方法的实现 **/
/*MyPromise.all(['a', 'b', p1(), p2(), 'c']).then(result =>
    console.log(result)  // [ 'a', 'b', 'p1', 'p2', 'c' ]

)*/

/** 10.promise.resolve方法的实现 **/
/*MyPromise.resolve(100).then(value => console.log(value))
MyPromise.resolve(p1()).then(value => console.log(value))*/

/** 10.promise.finally方法的实现  **/
/*p2().finally(()=>{
    console.log('finally')
    return p1()
}).then(value=>{
    console.log(value)  // p2
},reason=>{
    console.log(reason)
})*/

/** 11.promise.catch方法的实现 **/
/*p2().then(value => console.log(value))
    .catch(reason => console.log(reason))*/
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念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