前端面试题系列之-ajax及其他与服务器交互篇

手写ajax

GET请求


let xmlhttp;
if (window.XMLHttpRequest)
{
    // IE7+, Firefox, Chrome, Opera, Safari 浏览器执行代码
    xmlhttp=new XMLHttpRequest();
}
else
{
    // IE6, IE5 浏览器执行代码
    xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");
}

xmlhttp.onreadystatechange = function () {
    if (xmlhttp.readyState === 4 && xmlhttp.status === 200) {
        console.log(xmlhttp.response);  // 设置responseType为json时,用response获取数据,无需JSON.parse
    }
    if (xmlhttp.readyState === 4 && xmlhttp.status !== 200) {
        throw new Error(`请求出错,错误码:${xmlhttp.status},错误说明: ${xmlhttp.statusText}`);
    }
}
xmlhttp.open("GET", url, true);
// 服务器返回的数据为json数据,不需要parse转换
// 不设置的话客户端默认收到的是字符串,客户端需要parse转换成json数据
xmlhttp.responseType = "json";
xmlhttp.send();

POST请求

let xmlhttp;
if (window.XMLHttpRequest)
{
    // IE7+, Firefox, Chrome, Opera, Safari 浏览器执行代码
    xmlhttp=new XMLHttpRequest();
}
else
{
    // IE6, IE5 浏览器执行代码
    xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");
}

xmlhttp.onreadystatechange = function () {
    if (xmlhttp.readyState === 4 && xmlhttp.status === 200) {
        console.log(xmlhttp.response);
    }
    if (xmlhttp.readyState === 4 && xmlhttp.status !== 200) {
        throw new Error(`请求出错,错误码:${xmlhttp.status},错误说明: ${xmlhttp.statusText}`);
    }
}
xmlhttp.open("POST", url, true);
// 发送给服务器的为json数据,即请求头的content-type类型为json类型
// 不设置的话服务器默认收到的是字符串,服务器需要parse转换成json数据类型
xmlhttp.setRequestHeader("Content-Type", "application/json");
// 服务器返回的数据为json数据,不需要parse转换
// 不设置的话客户端默认收到的是字符串,客户端需要parse转换成json数据
xmlhttp.responseType = "json";
xmlhttp.send(JSON.stringify({method: "ajax",sendType: "post"}));

发送json数据

方法一

设置xmlhttp.responseType,发送时用JSON.stringify将对象转化为字符串,添加请求头content-type类型为application/json,接收数据用xmlhttp.response

xmlhttp.onreadystatechange = function () {
    if (xmlhttp.readyState === 4 && xmlhttp.status === 200) {
        console.log(xmlhttp.response);
    }
}
xmlhttp.open("POST", url, true);
// 发送给服务器的为json数据,即请求头的content-type类型为json类型
// 不设置的话服务器默认收到的是字符串,服务器需要parse转换成json数据类型
xmlhttp.setRequestHeader("Content-Type", "application/json");
// 服务器返回的数据为json数据,不需要parse转换
// 不设置的话客户端默认收到的是字符串,客户端需要parse转换成json数据
xmlhttp.responseType = "json";
xmlhttp.send(JSON.stringify({method: "ajax",sendType: "post"}));
方法二

发送时JSON.stringify格式化发送数据为json,服务器接收数据后需要调用JSON.parse将字符串转化为对象,服务器发送数据给客户端时,需要调用JSON.stringify。客户端接收数据时需要调用JSON.parse

xmlhttp.onreadystatechange = function () {
    if (xmlhttp.readyState === 4 && xmlhttp.status === 200) {
        console.log(JSON.parse(xmlhttp.responseText));
    }
}
xmlhttp.open("POST", url, true);
xmlhttp.send(JSON.stringify({method: "ajax",sendType: "post"}));

设置超时

// 默认为0,即为不超时
xmlhttp.timeout = 4000;

跨域携带cookie

xhr.withCredentials = true;

终止请求

xmlhttp.abort(); 

设置请求头

xmlhttp.setRequestHeader("Content-Type", "application/json");

设置响应类型

// ""(默认值等同于text)、arraybuffer、blob、document、json、text
// 其中如果设置json及document,但是服务器返回的据不是指定类型的化,response的值为null
xmlhttp.responseType = "json";

强制设置响应类型

xmlhttp.overrideMimeType('text/xml');

获取响应头

xmlhttp.getResponseHeader("Content-Type")

axios的用法

GET请求

axios.get(url).then(function (response) {
    console.log(response);
}).catch(function (error) {
    console.log(error);
});

POST请求

axios.post(url, {
    "firstName": 'Fred',
    "lastName": 'Flintstone'
}).then(function (response) {
    console.log(response);
}).catch(function (error) {
    console.log(error);
});

添加请求头

  // `headers` are custom headers to be sent
  headers: {'X-Requested-With': 'XMLHttpRequest'},

设置超时时间

  // 请求超时时间(毫秒)
  timeout: 1000,

跨域携带cookie

  // 是否携带cookie信息
  withCredentials: false, // default

设置响应类型

  // 响应格式
  // 可选项 'arraybuffer', 'blob', 'document', 'json', 'text', 'stream'
  responseType: 'json', // 默认值是json

终止请求

var CancelToken = axios.CancelToken;var source = CancelToken.source();

axios.get('/user/12345', {
  cancelToken: source.token
}).catch(function(thrown) {
  if (axios.isCancel(thrown)) {
    console.log('Request canceled', thrown.message);
  } else {
    // handle error
  }});

// cancel the request (the message parameter is optional)
source.cancel('Operation canceled by the user.');
var CancelToken = axios.CancelToken;var cancel;

axios.get('/user/12345', {
  cancelToken: new CancelToken(function executor(c) {
    // An executor function receives a cancel function as a parameter
    cancel = c;
  })});

// cancel the request
cancel();

相关链接:
axios中文文档

fetch的用法

Fetch底层并不是XMLHttp技术(ajax),Fetch 的核心在于对 HTTP 接口的抽象,包括 Request,Response,Headers,Body,以及用于初始化异步请求的 global fetch。得益于 JavaScript 实现的这些抽象好的 HTTP 模块,其他接口能够很方便的使用这些功能。除此之外,Fetch 还利用到了请求的异步特性——它是基于 Promise 的。

GET请求

fetch(url)
    .then(function (response) {
        // debugger;
        // let data = response.text(); // json 数据转字符串,不报错
        // let data = response.formData(); // json 报错 类型错误
        // let data = response.json(); // json 不报错,json
        // let data = response.arrayBuffer(); // json 不报错, arraybuffer
        // let data = response.blob(); // json 不报错, blob
        // let data = response.text(); // string  不报错 string
        // let data = response.formData(); // string 报错 类型错误
        // let data = response.json(); // string 报错 类型错误
        // let data = response.arrayBuffer(); // string 不报错, arraybuffer
        // let data = response.blob(); // string 不报错, blob
        return data;
    })
    .then(function (myJson) {
        // debugger;
        console.log(myJson);
    });

注意:

  • 第一个then方法为已经发送成功并收到服务器返回的数据,准备处理response响应体数据,第二个then方法为处理完成response响应体数据。
  • 响应体数据处理时,如果服务器返回数据与准备获取的类型(let data = response.text() 这里的text即为客户端准备获取的类型)不一致时,json和formData这两个会在不一致时报错,其余的不会报错。与ajax保持类似。

POST 请求

let data = {
    method: "POST",
    sendData: "adsf"
}
fetch(url, {
    body: JSON.stringify(data),
    headers: {
        'Content-Type': 'application/json' // 不设置服务器不识别为json
    },
    method: "POST"
}).then(response => {
        // let data = response.text(); // json 数据转字符串,不报错
        // let data = response.formData(); // json 报错 类型错误
        let data = response.json(); // json 不报错,json
        // let data = response.arrayBuffer(); // json 不报错, arraybuffer
        // let data = response.blob(); // json 不报错, blob
        // let data = response.text(); // string  不报错 string
        // let data = response.formData(); // string 报错 类型错误
        // let data = response.json(); // string 报错 类型错误
        // let data = response.arrayBuffer(); // string 不报错, arraybuffer
        // let data = response.blob(); // string 不报错, blob
    return data;
}).then(response => {
    console.log(response);
})

注意:

  • 如果不设置正确的header中的content-type类型的化,服务获取到的数据类型并不会是指定类型,这一点与ajax保持一致

添加请求头

fetch(url, {
    headers: {
      'user-agent': 'Mozilla/4.0 MDN Example',
      'content-type': 'application/json'
    }
})
  .then(response => response.json()) // parses response to JSON

获取响应头

fetch(url, {
    headers: {
      'user-agent': 'Mozilla/4.0 MDN Example',
      'content-type': 'application/json'
    }
})
.then(response => {
    console.log(response.headers);// 可通过这里的content-type类型决定返回哪种数据类型
    
    if(response.headers.get("content-type") === "application/json") {
        return response.json()
    }
    return response.text()
})

跨域携带cookie

// 注意fetch在不跨域的情况下是携带cookie的,接收set-cookie头。 在跨域的情况下是不携带cookie,不接受set-cookie
// 这一点和ajax一致
fetch(url, {credentials: 'include' })

超时设置

方案一 设置setTimeout
function request(url, wait) {
    return new Promise((resolve, reject) => {
        let status = 0; // 0 等待 1 完成 2 超时
        // 设置定时器,超时reject,这个只有一个promise
        let timer = setTimeout(() => {
            if (status === 0) {
                status = 2;
                timer = null;
                console.log("超时");
                reject(new Error("request timeout error"));
            }
        }, wait);
        fetch(url)
            .then(res => res.json())
            .then(res => {
                // 清除定时器
                if (status !== 2) {
                    clearTimeout(timer);
                    timer = null;
                    status = 1;
                    resolve(res);
                }
            });
    });
}
// 调用

request(url).then(json => {
    console.log(json);
}).catch(err => {
    console.log(err);
    // 处理超时
})
方案二 使用Promise.race
// 创建一个setTimeout的promise和一个fetch的promise,两个promise进行赛跑(race),如果setTimeout先完成,则结束promise.race。
function request(fetchPromise, timeout) {
    return Promise.race([
        fetchPromise,
        new Promise((resolve, reject) => {
            setTimeout(() => {
                console.log("超时了");
                reject(new Error('request timeout'))  // 创建Error
            }, 3000)
        })
    ]);
}
// 调用
request(fetch(url), 3000).then(response => response.json()).then(json => {
    console.log(json);
}).catch(err => {
    console.log(err)  // 这个就是刚才创建的超时异常,或其他请求异常
    // 这里处理超时
})

取消请求

方案一 通过reject取消Promise,并不会取消请求的发送

原理为劫持Promise,添加一个abort方法

// 劫持全局Promise
Promise.prototype.abort = () => Promise.reject(new Error("abort promise"));
// 用函数包装
function fetchAbort1(fetchPromise) {
    fetchPromise.abort = () => Promise.reject(new Error("abort promise"));
    return fetchPromise
}
// 示例
var p = fetch(url).then(response => response.json()).then(json => {
    console.log(json);
}).catch(error => {
    console.log(error);
});
setTimeout(() => {
    console.log("4s后取消了")
    p.abort()
}, 4000)

另一种实现(加上Promise.race)

function fetchAbort(fetchPromise) {
    var abort_fn = null;
    //这是一个可以被reject的promise
    var abort_promise = new Promise(function (resolve, reject) {
        abort_fn = function () {
            reject('abort promise');
        };
    });
    //这里使用Promise.race,以最快 resolve 或 reject 的结果来传入后续绑定的回调
    var abortable_promise = Promise.race([
        fetchPromise,
        abort_promise
    ]);
    abortable_promise.abort = abort_fn;
    return abortable_promise;
}
// 调用
var p = fetchAbort(fetch('//a.com/b/c'));
p.then(function(res) {
    console.log(res)
}, function(err) {
    console.log(err);
});
//假设fetch要3秒,但是你想在2秒就放弃了:
setTimeout(function() {
    p.abort(); // -> will print "abort promise"
}, 2000);
方案二 通过AbortController 取消fetch请求(处于实验阶段,慎用),会取消请求的发送,终止fetch请求
let controller = new AbortController();
let signal = controller.signal;

let timeoutPromise = (timeout) => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(new Response("timeout", { status: 504, statusText: "timeout " }));
            controller.abort();
        }, timeout);
    });
}
let requestPromise = (url) => {
    return fetch(url, {
        signal: signal
    });
};
Promise.race([timeoutPromise(1000), requestPromise("https://www.baidu.com")])
.then(resp => {
    console.log(resp);
})
.catch(error => {
    console.log(error);
});

参考链接:
Fetch超时设置和终止请求
AbortController

postMessage

postMessage()方法允许来自不同源的脚本采用异步方式进行有限的通信,可以实现跨文本档、多窗口、跨域消息传递,只适用于有iframe标签的页面

父页面

<iframe src="./child1.html" frameborder="1"></iframe>
<iframe src="./child2.html" frameborder="1"></iframe>
<script>
    window.onload = function(params) {
        var child1 = window.frames[0];
        child1.postMessage('message from parentwindow', '*');// 给1发消息
        var child2 = window.frames[1];
        child2.postMessage('message from parentwindow', '*');// 给1发消息
        window.addEventListener('message', function (e) {
            console.log(e.data)  // 接收消息
        }, false)
    }
</script>

子页面一

<h1>child1 page</h1>
<script>
    window.onload = function() {
        window.top.postMessage('message from child1', "*");  // 给父页面发消息
        window.addEventListener('message', function (e) {
            console.log("child1", e.data);  // 接收数据
        }, false)
    }
</script>

子页面二

<h1>child2 page</h1>
<script>
    window.onload = function() {
        window.top.postMessage('message from child2', "*");  // 给父页面发消息
        window.addEventListener('message', function (e) {
            console.log("child2", e.data);  // 接收数据
        }, false)
    }
</script>

参考连接:
window.postMessage

websocket

// 需要自定义头协议
// 需要保持心跳连接
// 需要断开重连

const sleep = time => new Promise(resolve => {
    setTimeout(resolve, time);
})

let count = 0; // 链接次数
function soket(url, wsType = "arraybuffer") {
    var ws = new WebSocket(url);
    ws.binaryType = wsType;  // 设置数据接收类型
    ws.onopen = () => {
        ws.send("Hello world!");  // 建立连接后发送验证消息
    };
    ws.onmessage = evt => {
        console.log(evt.data);  // 接收消息后的处理
        ws.send("abcd");
    }
    ws.onclose = async() => {
        console.log("ws 连接失败")  // 断开连接后的处理
        count++;
        if (count < 5) {
            await sleep(500);
            soket(url, wsType);
        }
    };
    return ws;
}

Server Sent Events

严格地说,HTTP 协议无法做到服务器主动推送信息。但是,有一种变通方法,就是服务器向客户端声明,接下来要发送的是流信息(streaming)。

也就是说,发送的不是一次性的数据包,而是一个数据流,会连续不断地发送过来。这时,客户端不会关闭连接,会一直等着服务器发过来的新的数据流,视频播放就是这样的例子。本质上,这种通信就是以流信息的方式,完成一次用时很长的下载。

SSE 就是利用这种机制,使用流信息向浏览器推送信息。它基于 HTTP 协议,目前除了 IE/Edge,其他浏览器都支持。

SSE 与 WebSocket 作用相似,都是建立浏览器与服务器之间的通信渠道,然后服务器向浏览器推送信息。

总体来说,WebSocket 更强大和灵活。因为它是全双工通道,可以双向通信;SSE 是单向通道,只能服务器向浏览器发送,因为流信息本质上就是下载。如果浏览器向服务器发送信息,就变成了另一次 HTTP 请求。

SSE和websoket的区别

  • SSE 使用 HTTP 协议,现有的服务器软件都支持。WebSocket 是一个独立协议。
  • SSE 属于轻量级,使用简单;WebSocket 协议相对复杂。
  • SSE 默认支持断线重连,WebSocket 需要自己实现。
  • SSE 一般只用来传送文本,二进制数据需要编码后传送,WebSocket 默认支持传送二进制数据。
  • SSE 支持自定义发送的消息类型。

基本用法

// 客户端
// 判断是否支持
if("EventSource" in window) {
    // 生成EventSource实例
    var source = new EventSource(url);
    // 建立连接时触发
    source.onopen = function (event) {
      // ...
    };
    // 接收消息时触发
    source.onmessage = function (event) {
      var data = event.data;
      // handle message
    };
    // 发生通信错误时触发
    source.onerror = function (event) {
      // handle error event
    };
}
// 服务器端
// 发送以下响应头
Content-Type: text/event-stream
Cache-Control: no-cache
Connection: keep-alive

// 发送响应体数据示例,每一条数据以\n\n结尾,每行按\n分割

: this is a test stream\n\n

data: some text\n\n

data: another message\n
data: with two lines \n\n

服务器发送的响应体字段

data
// 数据内容用data字段表示。

data: begin message\n  // \n为换行
data: continue message\n\n  // \n\n为消息结尾

data: {\n
data: "foo": "bar",\n
data: "baz", 555\n
data: }\n\n
event
// event字段表示自定义的事件类型,默认是message事件。浏览器可以用addEventListener()监听该事件。

event: foo\n
data: a foo event\n\n

data: an unnamed event\n\n

event: bar\n
data: a bar event\n\n
id
// id为每一条数据的标识,可在连接断线后,重新连接时同步信息使用, 因此,这个头信息可以被视为一种同步机制。客户端可以用lastEventId属性读取这个值
id: msg1\n
retry
// 服务器端发送以下数据,客户端会在报错后等待指定时间重新连接
// 适用于服务器主动关闭连接、网络出错及超时、时间间隔到期等。
retry: 10000\n

关闭SSE连接

source.close();

跨域携带cookie

var source = new EventSource(url, { withCredentials: true });

自定义事件

// 客户端 自定义事件foo
source.addEventListener('foo', function (event) {
  var data = event.data;
  // handle message
}, false);
// 服务器端 发送自定义事件foo
event: foo\n
data: a foo event\n\n

关闭、超时及网络出错时的重连

retry: 10000\n

示例

客户端

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width">
    <title>测试SSE</title>
</head>
<body>
    <div id="example"></div>
    <script>
        var source = new EventSource('http://127.0.0.1:8844/stream');
        var div = document.getElementById('example');
        source.onopen = function (event) {
            div.innerHTML += '<p>Connection open ...</p>';
        };
        source.onerror = function (event) {
            div.innerHTML += '<p>Connection close.</p>';
        };
        source.addEventListener('connecttime', function (event) {
            div.innerHTML += ('<p>Start time: ' + event.data + '</p>');
        }, false);
        source.onmessage = function (event) {
            div.innerHTML += ('<p>Ping: ' + event.data + '</p>');
        };
    </script>
</body>
</html>

服务器端

var http = require("http");
var count = 0;
http.createServer(function (req, res) {
    var fileName = "." + req.url;
    count++;
    if (fileName === "./stream") {
        res.writeHead(200, {
            "Content-Type": "text/event-stream",
            "Cache-Control": "no-cache",
            "Connection": "keep-alive",
            "Access-Control-Allow-Origin": '*',
        });
        res.write(`: This is a ${count} comment\n`)
        res.write("retry: 3000\n");
        res.write(`id: ${count}\n`)
        res.write("event: connecttime\n");
        res.write("data: " + (new Date()) + "\n\n");
        // res.end();  // 服务器关闭,客户端在retry时间段后会重新向服务器发送连接
        
        // 定时发送数据给客户端
        interval = setInterval(function () {
            res.write("data: " + (new Date()) + "\n\n");
        }, 1000);
        // 关闭时清除定时器
        req.connection.addListener("close", function () {
            clearInterval(interval);
        }, false);
    }
}).listen(8844, "127.0.0.1");

相关连接:
Server-Sent Events 教程
Server-sent events

ajax、axios、fetch的区别

  • ajax:底层是ajax,可设置超时,可取消请求,没有promise封装,自己处理非200的错误
  • axios:底层是ajax,可设置超时,可取消请求,通过promise封装,非200的错误进入catch
  • fetch:底层是fetch API,不可设置超时,不可取消请求,通过promise封装,非200的错误并不会catch

ajax断点续传

什么是短链接、长连接、短轮询、长轮询

  • 短链接:请求-响应,响应完成后即关闭连接的为短链接
  • 长连接:建立完成连接后,不会主动关闭连接,长时间保持客户端与服务器端的通讯
  • 短轮询:客户端向服务器发送请求,连接建立很短时间后,服务器关闭请求,随即客户端再次向服务器发送请求。 与长轮询相比连接持续时间短,建立次数多。
  • 长轮询:客户端向服务器发送请求,连接建立很长时间后,服务器关闭请求,随即客户端再次向服务器发送请求。与短轮询相比连接持续时间长,建立次数少。

什么是跨域?什么是同源策略,什么是cors

什么是跨域

跨域,是指浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的,是浏览器对JavaScript实施的安全限制。"协议+域名+端口"不同会产生跨域。

什么是同源策略

同源策略/SOP(Same origin policy)是一种约定,由Netscape公司1995年引入浏览器,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,浏览器很容易受到XSS、CSFR等攻击。所谓同源是指"协议+域名+端口"三者相同,即便两个不同的域名指向同一个ip地址,也非同源。

同源策略限制以下几种行为:

  • Cookie、LocalStorage 和 IndexDB 无法读取
  • DOM 和 Js对象无法获得
  • AJAX 请求不能发送

什么是cors

CORS是一个W3C标准,全称是"跨域资源共享"(Cross-origin resource sharing)。
它允许浏览器向跨源服务器,发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制。

Access-Control-Allow-Origin: http://api.bob.com
Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: X-Custom-Header
Access-Control-Allow-Credentials: true

跨域的几种方式

  1. 通过jsonp跨域
  2. document.domain + iframe跨域
  3. location.hash + iframe
  4. window.name + iframe跨域
  5. postMessage跨域
  6. 跨域资源共享(CORS)
  7. nginx代理跨域
  8. nodejs中间件代理跨域
  9. WebSocket协议跨域

参考链接
详解跨域(最全的解决方案)

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