基于vue的前端性能优化

      众所周知,Vue帮我们做了很多"脏活累活",例如:数据的双向绑定;Virtual Dom技术等,我们可以把大部分的时间抽出来去实现我们的业务逻辑。但是,我们仍然需要关注vue的首屏优化,webpack的配置优化,资源loading的快慢,vue项目运行时的性能优化等等。为了更好地提升我们的网站用户体验,下面我将通过实际项目的优化实践分模块总结:

  • 基础web技术层面的优化
  • webpack打包配置方面的优化
  • vue代码层面的优化
  • 其他优化

为了更了解整个性能优化过程,我们先来梳理一件事: 从输入URL 到页面加载完成发生了什么事?

image (6).png
  • 用户输入URL
  • 浏览器先检查本地是否有对应的IP地址,若找到则返回对应的IP地址。若没找到则请求上级DNS服务器,直至找到或到根节点
  • TCP连接进行三次握手
  • 浏览器发送HTTP请求资源/数据
  • 服务端处理请求进行回应
  • 浏览器接收HTTP响应
  • 浏览器渲染页面,构建DOM树
  • 浏览器关闭TCP连接(四次挥手)

      从以上过程可以看出整个处理响应过程其实是三部分: 客户端请求,服务端响应,客户端接收响应,如此可以发现前端能做的优化其实是第一和第三部分:让客户端做出更有效且高效的请求,让客户端接收响应后更快速的渲染页面,实现功能。下面我们具体分析如何进行优化:

基础web技术层面的优化

页面渲染性能的优化

以下是浏览器渲染页面的过程:

企业微信截图_039a89cd-22f9-444b-b751-243aaf102bf0.png
  • DOMTree: 解析html构建DOM树。
  • CSSOMTree : 解析CSS生成CSSOM规则树。
  • RenderObjectTree: 将DOM树与CSSOM规则树合并在一起生成渲染对象树。
  • Layout: 遍历渲染树开始布局(layout),计算每个节点的位置大小信息。
  • Painting: 将渲染树每个节点绘制到屏幕。

具体的一些实践做法:

1.防止阻塞渲染
      由于CSS和JS会影响DOM树和CSSOM的构建,所以浏览器在加载CSS和JS文件时会阻塞HTML的解析,为了避免阻塞,我们可以做以下优化:

  • css 放在head标签内,提前加载。这样做的原因是: 通常情况下 CSS 被认为是阻塞渲染的资源,在CSSOM 构建完成之前,页面不会被渲染,放在顶部让样式表能够尽早开始加载。但如果把引入样式表的 link 放在文档底部,页面虽然能立刻呈现出来,但是页面加载出来的时候会是没有样式的,是混乱的。当后来样式表加载进来后,页面会立即进行重绘,很可能会造成页面闪烁。
  • js文件放在body底部,防止阻塞解析
  • 首页不使用或者不改变dom和css的js文件使用 defer 和 async 属性进行异步加载,不阻塞解析

2.减少重绘和回流

  • 尽量少用js访问dom节点和css属性,能用css解决的问题就不要用js去做
  • 可能会涉及动画的HTML元素可以使用使用fixed或absolute的定位,修改对应的CSS样式就不会产生回流了
  • img标签设置高宽,以减少重绘重排
  • 尽量用 transform 来做形变和位移,减少使用left,top,这样不会造成回流

3.减少DOM和CSSOM的构建时间

  • DOM的层级尽量不要太深,否则会增加DOM树构建的时间,js访问深层的DOM也会造成更大的负担。
  • 减少 CSS 嵌套层级和选择适当的选择器

需要服务端配合的操作:

GZIP压缩

      gzip 是 GNUzip 的缩写,最早用于 UNIX 系统的文件压缩。HTTP 协议上的 gzip 编码是一种用来改进 web 应用程序性能的技术,web 服务器和客户端(浏览器)必须共同支持 gzip。目前主流的浏览器,Chrome,firefox,IE等都支持该协议。常见的服务器如 Apache,Nginx,IIS 同样支持,gzip 压缩效率非常高,通常可以达到 70% 的压缩率,也就是说,如果你的网页有 30K,压缩之后就变成了 9K 左右。重启服务之后可以看到:

微信截图_20210406123135.png

vue2.0使用webpack打包,会帮我们安装好compression-webpack-plugin插件,并生成好对应的代码:

if (config.build.productionGzip) {
  const CompressionWebpackPlugin = require('compression-webpack-plugin')

  webpackConfig.plugins.push(
    new CompressionWebpackPlugin({
      asset: '[path].gz[query]',
      algorithm: 'gzip',
      test: new RegExp(
        '\\.(' +
        config.build.productionGzipExtensions.join('|') +
        ')$'
      ),
      threshold: 10240,
      minRatio: 0.8
    })
  )
}

在index.js中开启GZIP开关,剩下的就是要服务器去支持GZIP了

image.png

HTTP缓存

      缓存的目的是简化资源的请求路径,比如某些静态资源在客户端已经缓存了,再次请求这个资源,只需要使用本地的缓存,而无需走网络请求去服务端获取。具体的缓存规则服务器会将其放入http响应报文的response headers中与请求结果一起返回给浏览器。
缓存类型:


1587717684639-78b0468f-138f-4470-981a-b56bcf6c0cae.png

缓存过程:

微信截图_20210406123526.png

CDN使用

      浏览器从服务器上下载 CSS、js 和图片等文件时都要和服务器连接,而大部分服务器的带宽有限,如果超过限制,网页就半天反应不过来。而 CDN 可以通过不同的域名来加载文件,从而使下载文件的并发连接数大大增加,且CDN 具有更好的可用性,更低的网络延迟和丢包率

使用 Chrome Performance 查找性能瓶颈

chrome开发者工具性能分析工具 Performance 可以帮助我们监控并分析页面的性能情况,进而去采取对应的优化措施:

  • 打开 Chrome 开发者工具,切换到 Performance 面板
  • 点击 Record 开始录制
  • 刷新页面或展开某个节点
  • 点击 Stop 停止录制
image (1).png

webpack打包配置方面的优化

ebpack-bundle-analyzer:查看资源树 方便后续针对性的优化

  • 安装
npm i webpack-bundle-analyzer -D
  • 使用
//在webpack.prod.conf.js文件中加入以下代码

const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
new BundleAnalyzerPlugin({
    analyzerMode: 'server',    //server | static | disabled
    analyzerHost: '127.0.0.1',   // 默认值:127.0.0.1。 将在服务器模式下用于启动HTTP服务器的主机。
    analyzerPort: 8889,         // 默认值:8888。将在服务器模式下用于启动HTTP服务器的端口。
    reportFilename: 'report.html', // 默认值:report.html。 捆绑将在静态模式下生成的报告文件的路径。 相对于bundle输出目录(在webpack配置中是output.path)。
    defaultSizes: 'parsed',     // 默认值:已解析。 默认情况下在报告中显示的模块大小。 大小定义部分描述了这些值的含义。
    openAnalyzer: true,     // 默认值:true。 在默认浏览器中自动打开报告。
    generateStatsFile: false,   // 默认值:false。 如果为true,将在bundle输出目录中生成webpack stats JSON文件
    statsFilename: 'stats.json',  // 默认值:stats.json。 如果generateStatsFile为true,将生成的webpack stats JSON文件的名称。 相对于bundle输出目录。
    statsOptions: null,     // 默认值:null。 stats.toJson()方法的选项。 例如,您可以使用source:false选项从stats文件中排除模块的源。 在这里查看更多选项。
    logLevel: 'info'    // 默认值:info, 用于控制插件输出的详细信息。
  })
  • npm run build


    image (2).png

下面是针对上述依赖图进行的优化:

第三方插件的按需引入

      在我们的项目中引入了element-ui组件库,在首屏需要加载依赖包,其中element-ui就占据了553k,原本是直接引入整个插件,会导致项目的体积太大。现在对其改造,只引入需要的组件:
1.安装babel-plugin-component:

npm install babel-plugin-component -D
  1. 修改.babelrc 文件:
{
  "presets": [
    ["env", {
      "modules": false,
      "targets": {
        "browsers": ["> 1%", "last 2 versions", "not ie <= 8"]
      }
    }],
    "stage-2"
  ],
  "plugins": [
    [
      "component",
      {
        "libraryName": "element-ui",
        "styleLibraryName": "theme-chalk"
      }
    ]
  ]
}
  1. 修改main.js文件:
// 全局引入方式,打包后会放在vendor.js文件中,在首屏加载
import Vue from 'vue';
import { Button } from 'element-ui';
Vue.use(Button)
 
// 单文件引入方式,打包后会放在各自路由的js文件中,跳转到具体页面才会加载对应的js文件,不会打包到vendor.js中
import { Table, TableColumn, Dialog, Button } from 'element-ui'
<script>
  export default {
    name: 'userCenter',
    components: {
      elTable: Table,
      elTableColumn: TableColumn,
      elDialog: Dialog,
      elButton: Button
    }
}
</script>

最后项目采用的是单文件引入所需要的组件,vendor.js的文件大小减小到267k


企业微信截图_16157758443453.png

使用 CDN加载外部资源

      项目中引用的第三方资源或者组件库很多,比如vue,vue-router,axios,swiper等等,在很多vue-处理搭建的项目都会用到,我们可以采用cdn引入,从别的服务器上加载第三方库而非自己的服务器,既能节省自己服务器的贷款,又能减少vendor.js文件的大小,会比原来webpack打包后加载快不少。

// index.html
<head>
 <!-- 引入样式 -->
 <link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
 <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/Swiper/4.5.0/css/swiper.min.css">
</head>
<body>
 <div id="app></div>
 <!-- 引入组件库 -->
 <script src="https://unpkg.com/element-ui/lib/index.js"></script>
 <script src="https://cdn.bootcdn.net/ajax/libs/axios/axios.min.js"></script>
 <script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
 <script src="https://cdnjs.cloudflare.com/ajax/libs/Swiper/4.5.0/js/swiper.min.js"></script>
</body>
// webpack.base.conf.js  添加externals对象,告诉webpack以下第三方库不需要打包 
module.exports = {
  ......
  externals: {   // 键: 库的名称, 值: 在项目中的别名,
    'swiper': 'Swiper',
    'vue': 'Vue',
    'vue-router': 'VueRouter',
    'axios': 'axios',
    'element-ui': 'ELEMENT'
  },
}

对图片进行压缩

      在 vue 项目中除了可以在 webpack.base.conf.js 中 url-loader 中设置 limit 大小来对图片处理,对小于 limit 的图片转化为 base64 格式,其余的不做操作。我们可以用 image-webpack-loader来压缩处理较大的图片资源:

  • 安装 image-webpack-loader :
npm install image-webpack-loader -D
  • 使用
// webpack.base.conf.js
  module: {
    rules: [
      {
        test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
        use: [
          {
            loader: 'url-loader',
            options: {
              limit: 10000,
              name: utils.assetsPath('img/[name].[hash:7].[ext]')
            }
          },
          {
            loader: 'image-webpack-loader',// 压缩图片
            options: {
              mozjpeg: { // jpeg压缩
                progressive: true,
                quality: 65
              },
              // optipng.enabled: false will disable optipng
              optipng: {//png压缩
                enabled: false,
              },
              pngquant: { // png压缩
                quality: [0.65, 0.90],
                speed: 4
              },
              gifsicle: { // gif压缩
                interlaced: false,
              }
              // the webp option will enable WEBP
              //webp: {
              // quality: 75
              //}
            }
          }
        ]
      }
    ]
  },

使用image-webpack-loader之后处理前后的对比:

15.png
156.png

减少 ES6 转为 ES5 的冗余代码

      默认情况下, Babel 会在每个输出文件中内嵌一些依赖的辅助函数代码,如果多个源代码文件都依赖这些辅助函数,那么这些辅助函数的代码将会出现很多次,造成代码冗余。为了不让这些辅助函数的代码重复出现,可以使用babel-plugin-transform-runtime 插件,通过 require('babel-runtime/helpers/createClass') 的方式导入,做到只引入一次。

  • 安装 babel-plugin-transform-runtime
npm install babel-plugin-transform-runtime --save-dev
  • 修改 .babelrc 配置文件:
"plugins": [
    "transform-runtime",
    [
      "component",
      {
        "libraryName": "element-ui",
        "styleLibraryName": "theme-chalk"
      }
    ]
]

以下是vue2.0X使用webpack打包前置帮我们安装好的插件

  • extract-text-webpack-plugin: 把css代码从js文件中抽离出来,单独出一个模块
  • optimize-css-assets-webpack-plugin: 压缩css文件
  • uglifyjs-webpack-plugin: 压缩js文件

vue代码层面的优化

路由懒加载

      vue是单页面应用,而我们的网站通常又是有多个页面组成,所以会引入很多路由,如果统一都在首屏加载,那么经过webpack 打包之后文件会很大,减缓首屏加载速度,降低用户体验。因此,我们要使用路由懒加载,将不同路由对应的组件分割成不同的代码块,当路由被访问的时候才加载对应的组件。

{
  path: '/index',
  name: '首頁',
  component: r =>
    require.ensure([], () => r(require('@/page/index')), 'index'),
  meta: {
    type: 'index',
    title: 'XXX'
  }
}

v-if和v-show的应用

      v-if 是 真正 的条件渲染,需要操作dom元素,有更高的切换消耗;v-show控制的元素总是会被渲染,简单地基于 CSS 的 display 属性进行切换。因此,如果需要非常频繁的切换,建议使用v-show,如果在运行时条件很少改变,则使用v-if。

长列表/无限列表渲染

      在我们的数据平台或者沉淀多年的业务数据可能会有几十万,上百万条数据,这时我们出了要应用分页,无限滚动的思路,最好做窗口化渲染来优化性能,只渲染可视区域内的内容,减少重新渲染组件和创建 dom 节点的时间。具体可以参考使用vue-virtual-scroll-listvue-virtual-scroller插件来实现。

条件语句优化

      在我们的项目中经常会遇到有四五个判断条件甚至更多的情况,这时如果嵌套过多过深,就会导致代码难以理解,维护困难,也会降低运行时性能。

  • 我们可以使用return优先返回错误语句而不使用 if else模块:
if(res.code === -1) return false
......   //其他需要进行的操作
  • 也可以利用Map数据结构来判断,减少循环和更多的判断:
let map = new Map();
let s = 'abbgfffklisfb'
let a = 0
let b = 0
for(let i=0; i<s.length; i++){
  if(!map.has(s[i])){ // 判断是否已经存在
     a++
  } else {
    let temp = map.get(s[i]);  // 获取对应的键值
    a = temp > a ? temp: a
    b++
  }
  map.set(s[i], i); // 将某个字符赋予值
}

其他优化

压缩图片

      在做官网或者其他视效丰富的页面时包含大量图片,如果是用PSD切下来的图直接提到线上,肯定是大大影响首屏资源加载和页面渲染的,所以我们需要对其进行压缩。推荐采用 熊猫压缩,基本上是最大程度的压缩,另外,推荐用jpg,占用内存比png格式的小。

image (3).png

图片资源预加载

项目是否需要预加载取决于开发者,用预加载一定会有一个从0到100的资源loading的过程。

<template>
    <div
        class="page-container"
        style="text-align: center;"
    >
        <div id="loading-panel">
            <p><img
                    src="../../static/logo.png"
                    alt=""
                ></p>
            <h1>Loading...</h1>
            <h2>{{percent}}</h2>
        </div>
    </div>
</template>

<script>
    export default {
        data() {
            return {
                count: 0,
                percent: "",
            };
        },
        mounted() {
            this.preload();
      console.log('hrthrth')
        },
    created (){
       let script = document.createElement('script')
       script.src = '../utils/'
    },
        methods: {
            preload() {
                let imgs = [
                    "static/img/card1.png",
                    "static/img/card2.png",
                    "static/img/card3.png",
                    "static/img/card4.png",
                    "static/img/card5.png",
                    "static/img/devil1.png",
                    "static/img/devil2.png",
                    "static/img/earth.png",
                    "static/img/earth1.png",
                    "static/img/earth2.png",
                    "static/img/female-as.png",
                    "static/img/female-de.png",
                    "static/img/female-h.png",
                    "static/img/404.png",
                    "static/img/404_clond.png",
                    "static/img/app.png",
                    "static/img/fb.png",
                    "static/img/bg.jpg"
                ];
                for (let img of imgs) {
                    let image = new Image();
                    image.src = img;
          const that = this
                    image.onload = function(e) {
                        that.count++;
                        // 计算图片加载的百分数,绑定到percent变量
                        let percentNum = Math.floor((that.count / 14) * 100);
                        that.percent = `${percentNum}%`;
                    };
                }
            }
        },

        watch: {
            count: function (val) {
                if (val === 18) {
                    // 图片加载完成后跳转到首页
                    this.$router.push({ path: "index" });
                }
            },
        },
    };
</script>

快捷一点的方式是使用第三方插件 Preload.js,可以预加载音视频和图片等资源。首先在index.html中引入preload.js

  <script src="https://code.createjs.com/1.0.0/preloadjs.min.js"></script>

然后新建一个loading.vue文件:

<template>
    <div>
        <div id="preload_panel">
            <p><img
                    src="../../static/logo.png"
                    alt=""
                ></p>
            <h1>Loading...{{percent}} %</h1>
        </div>
    </div>
</template>

<script>
    export default {
        name: "preload",
        data() {
            return {
                percent: "",
            };
        },
        mounted() {
      this.preLoad()
    },
        methods: {
            preLoad() {
                var mainfest = [
                    { src: "static/img/card1.png" },
                    { src: "static/img/card2.png" },
                    { src: "static/img/card3.png" },
                    { src: "static/img/card4.png" },
                    { src: "static/img/card5.png" },
                    { src: "static/img/devil1.png" },
                    { src: "static/img/devil2.png" },
                    { src: "static/img/earth.png" },
                    { src: "static/img/earth1.png" },
                    { src: "static/img/earth2.png" },
                    { src: "static/img/female-as.png" },
                    { src: "static/img/female-de.png" },
                    { src: "static/img/female-h.png" },
                    { src: "static/img/404.png" },
                    { src: "static/img/404_clond.png" },
                    { src: "static/img/app.png" },
                    { src: "static/img/fb.png" },
                    { src: "static/img/bg.jpg" },
                ];
      const that = this
                var preload = {
                    // 预加载函数
                    startPreload: function () {
                        var preload = new createjs.LoadQueue(true);
                        //为preloaded添加整个队列变化时展示的进度事件
                        preload.addEventListener("progress", this.handleFileProgress);
                        //注意加载音频文件需要调用如下代码行
                        // preload.installPlugin(createjs.SOUND);
                        //为preloaded添加当队列完成全部加载后触发事件
                        preload.addEventListener("complete", this.loadComplete);
                        //设置最大并发连接数  最大值为10
                        preload.setMaxConnections(1);
                        preload.loadManifest(mainfest);
                    },
                    // 当整个队列变化时展示的进度事件的处理函数
                    handleFileProgress: function (event) {
                        that.percent = Math.ceil(event.loaded * 100);
                    },
                    // 处理preload添加当队列完成全部加载后触发事件
                    loadComplete: function () {
                        that.$router.push("/index"); // 加载完成后跳转到首页
                    },
                };
                preload.startPreload();
            },
        },
    };
</script>

<style>
</style>
image (4).png

图片资源懒加载

      为了加速页面加载速度,我们也可以将未出现在可视区域内的图片先不做加载,等到滚动到可视区域后再去加载。这样对于页面加载性能上会有很大的提升,也提高了用户体验。这里采用的是第三方插件vue-lazyload:

  1. 安装插件
npm install vue-lazyload

2.main.js中全局引入

import VueLazyLoad from 'vue-lazyload'
// 第二个参数对象是自定义对象可有可无
Vue.use(VueLazyload,{
  preLoad: 1.3,
  error: 'dist/error.png',
  loading: 'dist/loading.gif',
  attempt: 1
})

3.组件中使用,将 :src 属性直接改为v-lazy

<img v-lazy="item.src'>

网络请求的优化

      1.除非首屏渲染需要用到或者是第三方埋点的sdk,其他不影响初次渲染的资源可以考虑延迟或异步加载,减少资源请求数,加快首屏渲染速度。比如FaceBook 的SDK在首页渲染时不需要用到,那我只需要在登录页面再去加载即可,在login.vue文件中:

let fbDiv = document.createElement('script')
fbDiv.setAttribute('async', 'async')
fbDiv.setAttribute('defer', 'defer')
fbDiv.setAttribute('crossorigin', 'anonymous')
fbDiv.setAttribute('src', 'https://connect.facebook.net/zh_TW/sdk.js#xfbml=1&version=v6.0&appId=xxxxxxxxx&autoLogAppEvents=1')
document.querySelector('body').appendChild(fbDiv)

第三方埋点SDK如百度统计或者Google Analysics则一定要在index.html中就引入相应的代码:

<script>
  (function(i, s, o, g, r, a, m) {
    i['GoogleAnalyticsObject'] = r;
    i[r] = i[r] || function() {
      (i[r].q = i[r].q || []).push(arguments)
    }, i[r].l = 1 * new Date();
    a = s.createElement(o),
    m = s.getElementsByTagName(o)[0];
    a.async = true;
    a.src = g;
    m.parentNode.insertBefore(a, m);
  })(window, document, 'script', 'https://www.greatytc.com/analytics.js', 'ga');
  ga( 'create', 'UA-xxxxx-x', 'auto' );
  ga( 'send', 'pageview' );
</script>
  1. 由于HTTP的限制,在建立一个tcp请求时会有一定的耗时,所以,我们要尽量减少请求的次数,对资源进行合并、压缩,其目的是减少http请求数和减小包体积,加快传输速度。如将项目中遇到的比较小的logo,图标等合成雪碧图,推荐合成雪碧图的在线工具:css sprites generator
    image (5).png

      最后总结为一下,本文由以下四个部分组成:基础Web 技术层面的优化;webpack 打包配置方面的优化;Vue 代码层面的优化;其他优化,来介绍如何优化 Vue 项目的性能。希望大家阅读完之后能有所启发,若有其他补充,欢迎交流学习!

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

推荐阅读更多精彩内容