每日一博丨前端技术及开发模式的演进,带你了解前端技术的前世今生

先声明,本篇不会讲带有年代性的前端发展史,不讲故事,想了解的读者可以去查阅一些其他的资料和文章,本篇仅仅从技术发展角度结合案例分析,说明前端技术的发展和开发模式的演进变化。本篇内容重点说明PC端技术,移动端、桌面端本篇不涉及,防止读者看到后面有疑惑,这里强调一下。

这里先讲一个需求,有一个系统需要实现一个模块,用户管理,模块的功能很简单,就是查询、删除。基于这个需求,南风哥会使用几代不同的前端技术分别予以实现,让读者感受其中的变化和奥秘。界面大概是这个样子。

非常老土,非常简单,这都不是重点,重点是能说明问题就行。

第一代:单文件模式

何为单文件模式,解释一下就是一个模块所有的代码都集中在一个文件中,实现上面说的需求,目录大概是这个样子的。

image

然后看「user.html」具体里面的内容。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>用户管理</title>

    <style>
        .bg{
            background-image: url("../img/bg.jpg");
        }

        .text-center{
            text-align: center;
        }

        .condition-box{
            margin-bottom: 10px;
        }

        table{
            width:90%;
        }

        .table-header{
            background-color: #dddddd;
        }

        .delete{
            text-decoration: none;
        }

    </style>
</head>

<body class="bg">
    <div class="text-center">
        <h3>用户添加</h3>
    </div>

    <div class="text-center">

        <!-- 查询条件 -->
        <div class="condition-box">
            用户姓名:<input id="name" type="text"> <button id="search" onclick="search()">查询</button>
        </div>

        <!-- 用户列表 -->
        <table align="center" border="1" cellpadding="0" cellspacing="0">
            <tr class="table-header">
                <td>id</td>
                <td>姓名</td>
                <td>性别</td>
                <td>年龄</td>
                <td>联系电话</td>
                <td>创建时间</td>
                <td>操作</td>
            </tr>

            <tr>
                <td>1</td>
                <td>张三</td>
                <td>男</td>
                <td>22</td>
                <td>123456789</td>
                <td>2018-08-08</td>
                <td><a class="delete" href="javascript:void(0);" onclick="deleteUser(this,1)">删除</a></td>
            </tr>

            <tr id="5">
                <td>2</td>
                <td>李四</td>
                <td>女</td>
                <td>18</td>
                <td>987654321</td>
                <td>2018-07-18</td>
                <td><a class="delete" href="javascript:void(0);" onclick="deleteUser(this,2)">删除</a></td>
            </tr>
        </table>
    </div>
</body>

<script src="../js/jquery-2.1.1.min.js"></script>
<script type="text/javascript">

    function deleteUser(e,id){
        // 调用控制器删除方法
        // 后端逻辑是,控制器调用删除业务方法后,返回到user.html页面,即当前页面
        location.href = "/userDelete?id=" + id;
    }

    function search(){
        var name = $("#name").val();
        // 调用控制器搜索方法
        // 后端逻辑是,控制器调用删除业务方法后,返回到user.html页面,即当前页面
        location.href = "/userList?name=" + name;
    }

</script>

</html>

我们会发现,所有的样式声明,js代码以及html代码都会集中在一个文件中。这样一个功能如果较为复杂,页面代码看起来会非常复杂,久而久之就会变得不易维护。

当然有一些优化的方式。就是目录划分更友好一些,剥离css和js脚本,采用外部引入的方式,这种方式也是后来采用的比较多的方式,像下面这样,看起来就清爽多了。

image
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>用户管理</title>

    <!-- 用户样式 -->
    <link type="text/css" rel="stylesheet" href="../css/user.css" />
</head>

<body class="bg">
    <div class="text-center">
        <h3>用户添加</h3>
    </div>

    <div class="text-center">

        <!-- 查询条件 -->
        <div class="condition-box">
            用户姓名:<input id="name" type="text"> <button id="search" onclick="search()">查询</button>
        </div>

        <!-- 用户列表 -->
        <table align="center" border="1" cellpadding="0" cellspacing="0">
            <tr class="table-header">
                <td>id</td>
                <td>姓名</td>
                <td>性别</td>
                <td>年龄</td>
                <td>联系电话</td>
                <td>创建时间</td>
                <td>操作</td>
            </tr>

            <tr>
                <td>1</td>
                <td>张三</td>
                <td>男</td>
                <td>22</td>
                <td>123456789</td>
                <td>2018-08-08</td>
                <td><a class="delete" href="javascript:void(0);" onclick="deleteUser(this,1)">删除</a></td>
            </tr>

            <tr id="5">
                <td>2</td>
                <td>李四</td>
                <td>女</td>
                <td>18</td>
                <td>987654321</td>
                <td>2018-07-18</td>
                <td><a class="delete" href="javascript:void(0);" onclick="deleteUser(this,2)">删除</a></td>
            </tr>
        </table>
    </div>
</body>

<script src="../js/jquery-2.1.1.min.js"></script>
<script src="../js/user.js"></script>

</html>

以上两种形式,本质上没有什么区别,所以就放在一起说明,以上方式的优点就是简单粗暴,代码开发的速度较高。

与后端的交互都是后端建立一个Controller或Servlet 这么一个控制器,请求到达控制器,处理完逻辑,然后控制器中跳转到一个页面,页面进行数据渲染展示。每次交互过程目标跳转页面的内容都需要全部刷新加载,即使多次交互跳转的是同一个页面。

这种有什么问题吗?我们来看一下,在用户管理中的用户列表页我们有个查询功能,我们输入一个姓名,点击查询,如果采用上面的方式,想想看,点一下查询,到控制器,后端执行完SQL拿到数据后,又会跳转到用户列表页,我点5次查询,整个列表页就会加载5次,而且整个页面只有用户表格这个部分是变化的,其他部分是没有变化的,这样资源(背景图、jquery库、css)每次就是重复加载,带来的问题就是一、体验不好,二、资源重复加载,给web服务器带来一定的请求压力。

这里补充多说一点内容,就是jsp这个东西,虽然现在用的不多,但还是说一下,之前南风哥面试问过很多面试者,前端都会什么技术,面试者张口就来,jsp .... 什么什么,这里南风哥想说,jsp不属于前端技术,为什么 ?

因为jsp本身执行前是需要编译的,编程成class文件,是在服务端执行的,而真正的前端技术一定是在浏览器或相关前端技术执行引擎上(类Chrome V8)解释执行的。像jsp里写的css、html、js这些东西属于前端技术的范畴,这些都是由浏览器解释执行的,很多人被jsp这种表象迷惑了,看到jsp里面写的都是页面展现相关的代码,认为jsp就是前端技术。

回到上面的问题,我们再来看如何解决上面的问题,这就是第二代技术。

第一代代表技术:html、css、javascript、jquery

第二代:SPA

单页Web应用(single page web application,SPA),简称SPA,由于ajax技术的兴起,使得局部加载变得流行,单页应用页得到了广大开发者和用户的青睐。

简单理解单页应用,就是整个应用加载都在一个页面当中,做的就是局部替换,比如点击一个菜单,系统整个头部,底部,菜单部分都不变化,只变化中间区域的模块内容。

再比如上面说到的查询需求,点击查询后,用户列表整体内容不变,通过ajax请求,数据回来后,通过js仅仅修改表格部分的数据内容。

这样就解决了上面说的一、体验的问题,二、资源重复加载的问题。

大部分情况下,我们只需要在整体页面中引入需要的所有资源,模块中就不需要在引入资源,只处理模块自身的内容和业务即可。

这种方式与后端的交互都是就是建立一个Controller或Servlet 这么一个控制器,请求到达控制器,处理完逻辑,然后返回局部页面片段或者json数据,页面进行渲染展示。每次交互过程只刷新局部,整体页面不做刷新。

这里的数据渲染,一种是通过jquery或js的方式,字符串拼接,然后设置到对应的dom中,像这样。

页面Dom

<pre style="box-sizing: inherit; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13px; word-break: initial; word-wrap: initial; white-space: pre; overflow: auto; margin: 16px 0px 14px; padding: 14px 15px 12px; border-radius: 3px; border: none; display: block; line-height: 1.6; background: rgb(246, 246, 246); color: rgb(61, 70, 77); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px;">
<table id="userList" align="center" border="1" cellpadding="0" cellspacing="0"></table></pre>

Ajax加载渲染

function search(){
    var name = $("#name").val();
    // 调用控制器搜索方法,返回json数据
    $.get('/userList?name=' + name,{},function(data){
        var userList = data.userList;

        var row = '';
        row += '<tr class="table-header">';
        row += '    <td>id</td>';
        row += '   <td>姓名</td>';
        row += '   <td>性别</td>';
        row += '    <td>年龄</td>';
        row += '    <td>联系电话</td>';
        row += '   <td>创建时间</td>';
        row += '   <td>操作</td> ';
        row += '  </tr>';

        for(var i = 0 ; i < userList.length; i++){
            var user = userList[i];
            row += '<tr>';
            row += '    <td>' + user.id + '</td>';
            row += '    <td>' + user.name +'</td>';
            row += '    <td>' + user.sex + '</td>';
            row += '    <td>' + user.age +' '</td>';
            row += '    <td>' + user.phone + '</td>';
            row += '    <td>' + user.createDate + '</td>';
            row += '    <td><a class="delete" href="javascript:void(0);" onclick="deleteUser(this,1)">删除</a></td>';
            row += '</tr>';
        }
        $("#userList").html(row);

    },'json');

}

另一种方式模板引擎

<!-- 定义模板 -->
<script id="userListTpl" type="text/html">
    <tr class="table-header">
        <td>id</td>
        <td>姓名</td>
        <td>性别</td>
        <td>年龄</td>
        <td>联系电话</td>
        <td>创建时间</td>
        <td>操作</td>
    </tr>

    {{each list as user i}}
    <tr>
        <td>{{user.id}}</td>
        <td>{{user.name}}</td>
        <td>{{user.sex}}</td>
        <td>{{user.age}}</td>
        <td>{{user.phone}}</td>
        <td>{{user.createDate}}</td>
        <td><a class="delete" href="javascript:void(0);" onclick="deleteUser(this,{{user.id}})">删除</a></td>
    </tr>
    {{/each}}
</script>

使用

function search(){
    var name = $("#name").val();
    // 调用控制器搜索方法,返回json数据
    $.get('/userList?name=' + name,{},function(data){
        var userList = data.userList;

        var html = template('userListTpl', userList);
        document.getElementById('userList').innerHTML = html;
    },'json');
}

复杂页面建议使用模板引擎的方式,代码结构更清晰,更容易维护。

这种方式的优点,显而易见可以解决第一代技术的问题,那它有问题吗 ? 显然有,一、破坏浏览器的后退、前进功能(异步加载,地址栏不发生变化,所以是无法后退前进),当然有一些解决办法,下面再说 ,二、SEO不友好。

所以比较常见的做法是,后端管理系统一般会整体采用SPA方式,包括现在的很多系统也是,有强SEO需求的仍然会采用上面第一代方式,然后结合一点点ajax的内容,这便是第二代前端的开发模式。

局部加载的实现方式:

1、ajax 局部请求加载

2、前端hash路由,也是现在的主流方式,这种方式可以解决浏览器的前进、后退问题,通过地址栏hash值的变化,但历史页面状态无法保持,回退的页面数据仍需要重新加载、初始化,但随着前端数据持久化的逐步流行,回退历史页面状态的保存也渐渐不是问题。

3、iframe、frameset 也可以实现,但是不建议。

第二代代表技术:ajax、artTemplate

第三代:模块化

随着系统功能越来越多,代码文件也越来越多,相互之间的依赖调用关系变得异常复杂。这时候两个问题变得十分突出,一是js中的命名冲突问题,二是资源的加载问题。

先看js的命名问题,看一个简单的js文件

function deleteUser(e,id){
    // 调用控制器删除方法
    // 后端逻辑是,控制器调用删除业务方法后,返回到user.html页面,即当前页面
    location.href = "/userDelete?id=" + id;
}

function search(){
    var name = $("#name").val();
    // 调用控制器搜索方法
    // 后端逻辑是,控制器调用删除业务方法后,返回到user.html页面,即当前页面
    location.href = "/userList?name=" + name;
}

这是上面user模块的js文件,user.js,有两个方法,这个js会被在user.html中引入

image
image

但是一个稍微复杂点的系统或功能,不可能只引入这么几个外部js,常常有几十个甚至上百个js需要引入,还有我们自己封装的组件,通用方法等。这样的大量引入,如果都是user.js 这种全局命名形式,很容易就发生命名上的冲突,就是你定了一个search函数,另一个开发者或者插件里面也定义了一个search方法,这就会带来莫名奇妙的BUG和问题。

在来看一下资源加载的问题,同样是用户模块,引入了比如说50个js文件,但是有40个文件是点击搜索时才需要使用,加载列表时不需要,如果每次加载列表都去加载全部的js文件,那将需要多大的带宽资源和带来多大的请求压力(这里先不考虑CDN等优化方式)。能不能让资源在真正需要的时候再去加载 ?

那么前端模块化技术就是重点解决以上两个问题。我们的代码结构就会变成这个样子。

定义模块

define(function(require, exports, module) {
    var $ = require('jquery');
    var tpl = require('template');

    var user = {}
    // 初始化
    user.init = function(){
        user.deleteUser();
        user.search();
    }

    user.deleteUser = function() {
        $(".delete").click(function(){
            $.get('/userDelete?id=' + id,{},function(data){
                user.reloadData();
            },'json');
        });
    };

    user.search = function() {
       $("#search").click(function () {
           var name = $("#name").val();
           // 调用控制器搜索方法,返回json数据
           $.get('/userList?name=' + name,{},function(data){
               var userList = data.userList;

               var html = template('userListTpl', userList);
               document.getElementById('userList').innerHTML = html;
           },'json'); 
       });
    };

    exports user;
});

使用模块

image
image

完整页面代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>用户管理</title>

    <!-- 用户样式 -->
    <link type="text/css" rel="stylesheet" href="../css/user.css" />
</head>

<body class="bg">
    <div class="text-center">
        <h3>用户添加</h3>
    </div>

    <div class="text-center">

        <!-- 查询条件 -->
        <div class="condition-box">
            用户姓名:<input id="name" type="text"> <button id="search">查询</button>
        </div>

        <!-- 用户列表 -->
        <table id="userList" align="center" border="1" cellpadding="0" cellspacing="0">

        </table>
    </div>
</body>

<script src="../js/jquery-2.1.1.min.js"></script>
<script src="../js/sea.js"></script>

<script>
    seajs.use(['./js/user', 'jquery'], function(user, $) {
        user.init();
    });
</script>

<!-- 定义模板 -->
<script id="userListTpl" type="text/html">
    <tr class="table-header">
        <td>id</td>
        <td>姓名</td>
        <td>性别</td>
        <td>年龄</td>
        <td>联系电话</td>
        <td>创建时间</td>
        <td>操作</td>
    </tr>

    {{each list as user i}}
    <tr>
        <td>{{user.id}}</td>
        <td>{{user.name}}</td>
        <td>{{user.sex}}</td>
        <td>{{user.age}}</td>
        <td>{{user.phone}}</td>
        <td>{{user.createDate}}</td>
        <td><a class="delete" href="javascript:void(0);" onclick="deleteUser(this,{{user.id}})">删除</a></td>
    </tr>
    {{/each}}
</script>

</html>

****第三代代表技术:seajs、requirejs、kissy

第四代:前端MVC

MVC开始是存在于桌面程序中的,作者意图解决桌面端GUI的开发耦合问题,后来被结合后端技术,整体又组成了一个MVC模式以及纯前端的MVC模式。但思想是一致的,就是包含 控制器、模型、视图三个部分。

当一个系统的模块越来越多之后,模块内部的代码维护便是个大问题,为了让模块代码变得清晰,更易维护,相互之间没有紧密的耦合关系,技术先驱者们想了很多解决办法,MVC便是其中一种,像还有MVP模式,以及后面将提到的MVVM。

第四代代表技术:Extjs、backbone.js

Extjs不是UI框架吗?,实际上它不只有UI控件,Extjs4开始有了MVC模式,南风哥之前所在公司使用的便是Extjs,充分使用了其中的MVC模式。虽说现在收费了,用的人不怎么多了,但在当年还是有一席之地的。

如何将一个前端模块使用MVC的方式就行构建,以Extjs的为例构建用户管理就是如下。

  • UserController 注册用户管理的交互事件和方法定义
  • UserModel 数据模型,配置Store使用
  • UserStore 异步请求,负责交互后台数据
  • UserGrid 表格视图,负责展示Store请求回来的数据,绑定渲染

将各层分离,使代码整体结构更清晰,耦合度更低,可维护性更强,感觉很复杂?没错,这样本来一个简单的模块开发起来就会变得看起来复杂,需要分离,需要遵循一定的规范,但长远看这么做是有好处的,代码的可维护性和可读性会有不小的提升。

第五代: MVVM

这里就不在提MVP这种模式了,没有太流行起来,直接看MVVM模式。 拆解一下,实际上是 M-V-VM(即模型-视图-视图模型)三个部分 ,M和V还是原来的M和V,唯一不同的是VM这个东西,通过VM(ViewModel)完全将M和V分离,是M和V的连接纽带。实现了双向数据绑定,简单理解一下就是,模型发生变化,视图会自动更新,视图数据发生变化,模型会自动感知也发生变化,当然这是直观的使用感受,底层是有比较复杂的实现逻辑支撑。

对开发者来说,就可以从以前的dom操作中解放出来,想想以前的操作模式,接收到后台的数据,jquery开干,选择dom,拿到数据,拼接字符串,填充到dom中。视图数据变化了,js 每次需要主动重新取值,有了MVVM这都不需要了,做好了双向绑定。只要将数据绑定到M,V自动更新,V层表单或其他视图组件状态发生变化,M自动更新,没有了中间的DOM操作和控制。一切都变得简单。相互之间也没有耦合,自己干自己的事情。

相比前端模板引擎,它又少了模板定义这个部分,也是简单不少,省了不少事情。

使用MVVM后,代码会变成这样。

定义

new Vue({
    el: '#app',
    data: {
        name:'',
        userList:[]
    },
    methods:{
        deleteUser:function(){

        },
        search:function(){
            var _this = this;
            $.get('/userList?name=' + name,{},function(data){
                var userList = data.userList;
                // 赋值,视图自动更新
                _this.userList = userList;
            },'json');
        }
    }
})

使用

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>用户管理</title>

    <!-- 用户样式 -->
    <link type="text/css" rel="stylesheet" href="../css/user.css" />
</head>

<body class="bg">
    <div class="text-center">
        <h3>用户添加</h3>
    </div>

    <div class="text-center">

        <!-- 查询条件 -->
        <div class="condition-box">
            用户姓名:<input v-model="name" type="text"> <button v-on:click="search()">查询</button>
        </div>

        <!-- 用户列表 -->
        <table id="userList" align="center" border="1" cellpadding="0" cellspacing="0">
            <tr class="table-header">
                <td>id</td>
                <td>姓名</td>
                <td>性别</td>
                <td>年龄</td>
                <td>联系电话</td>
                <td>创建时间</td>
                <td>操作</td>
            </tr>

            <tr v-for="user in userList">
                <td>{{user.id}}</td>
                <td>{{user.name}}</td>
                <td>{{user.sex}}</td>
                <td>{{user.age}}</td>
                <td>{{user.phone}}</td>
                <td>{{user.createDate}}</td>
                <td><a class="delete" href="javascript:void(0);" onclick="deleteUser(this,{{user.id}})">删除</a></td>
            </tr>
        </table>
    </div>
</body>

<script src="../js/jquery-2.1.1.min.js"></script>
<script src="../js/vue.js"></script>
<script src="../js/user.js"></script>

</html>

第五代代表技术: Angularjs、Vue

第六代:nodejs 为基础的大前端

关于nodejs引用网上的一句解释 “Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行环境。”,简单的说 Node.js 就是运行在服务端的 JavaScript。What ?js 能运行在服务端了 ?对的。

先说下为什么出现了nodejs这个东西,nodejs作者的初衷是设计一个高性能的Web服务器,让前端开发人员通过javascript也可以进行服务端的开发,不可否认,事件驱动 + 非阻塞 + Chrome v8 引擎,nodejs的性能表现确实优异。

发展至今,纯使用nodejs作为后端开发的企业,显然不多。但是却在另外两个方面大放异彩。

一、开发时,打破前后端协作的壁垒,即使在后端没有按时提供接口的情况下,前端依旧可以按照自己的节奏完成开发任务。是大前端开发的基石。

二、生产环境中,借助其高性能,更多作为一层网关或代理,转发请求到真正的后端服务上。

随着nodejs的流行,前端变得更加独立,产生了以vue和react为代表的两大阵营,结合其他插件模块,前端也有模块依赖了,也可以管理依赖了,前端也需要打包编译了,对,没错,前端需要学的东西也越来越多了,体系也越来越庞大了,这是真正完全的前后端分离,大前端来了。

第六代代表技术:nodejs、****Vue、React、Webpack

通过以上几代技术的演进,前端技术发展一直在朝着解耦、可维护、高性能的目标不懈的努力着,致力于良好的用户体验,未来还会出现哪些NB的技术,让我们拭目以待吧。

南风哥对以上几种方式都经历过,体验过,现在每种方式肯定都有企业在用或者组合使用,技术这个东西本身就是为解决问题而存在的,不能为了技术而技术,为了追求潮流而不管不顾,立足企业的痛点和需求,结合企业的实际情况,选择合适的技术解决问题才是王道。

开源中国社区,每日推送最新优质的技术类文章,涵盖外文翻译,软件更新,技术博客等优质内容。关注开源社区简书号,每日获取最新技术资讯,点击下链接阅读原文章。↓↓↓
每日一博丨前端技术及开发模式的演进,带你了解前端技术的前世今生

关注开源中国简书号,获取最新技术资讯!
"

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

推荐阅读更多精彩内容

  • 前端集成解决方案要求: 模块化开发。最好能像写nodejs一样写js,很舒服。css最好也能来个模块化管理! 性能...
    Www刘阅读 2,921评论 1 20
  • 以下文章转载自知乎,暗灭-京华九月秋近寒,浮沉半生影长单. 暗灭 京华九月秋近寒,浮沉半生影长单 10,850 人...
    ve追风_685b阅读 3,997评论 1 15
  • 一、何为电商 所谓电商,即电子商务,就是指通过使用电子类工具,围绕着商品交易进行的一系列活动。既然是交易,那就离不...
    Rosen_Gao阅读 234评论 1 4
  • 一、何为电商 所谓电商,即电子商务,就是指通过使用电子类工具,围绕着商品交易进行的一系列活动。既然是交易,那就离不...
    乌云龙阅读 458评论 0 0
  • 我决定让小子学游泳,因为我认为这是个生存技能,在当下在今后都至关重要。 当然,直接点燃我这个想法的还是在幼儿园门口...
    烦人的昵称阅读 161评论 0 0