zoukankan      html  css  js  c++  java
  • 前端技术及开发模式的演进,带你了解前端技术的前世今生

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

     

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

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

     

    第一代:单文件模式

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

    然后看「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>
    View Code

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

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

    <!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>
    View Code

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

    与后端的交互都是后端建立一个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

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

    -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');
    
    }
    View Code

    -另一种方式模板引擎

    <!-- 定义模板 -->
    <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>
    View Code

    -使用

    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');
    }
    View Code

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

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

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

    但是一个稍微复杂点的系统或功能,不可能只引入这么几个外部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;
    }); 
    View Code

    -使用模块

     

    完整页面代码

    <!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>
    View Code

    第三代代表技术: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');
            }
        }
    })
    View Code

    -使用

    <!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>
    View Code

    第五代代表技术: 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的技术,让我们拭目以待吧。

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

    文章篇幅较长,能坚持看完着实不易,给自己点个赞吧。  

    创作也不易,给作者点个赞。 

  • 相关阅读:
    如何使用SAP Intelligent Robotic Process Automation自动操作Excel
    OpenSAML 使用引导 IV: 安全特性
    Spring Cloud Zuul 网关使用与 OAuth2.0 认证授权服务
    微服务架构集大成者—Spring Cloud (转载)
    Spring Cloud Eureka 服务注册列表显示 IP 配置问题
    使用 Notification API 开启浏览器桌面提醒
    SignalR 中使用 MessagePack 序列化提高 WebSocket 通信性能
    配置 Nginx 的目录浏览功能
    关于 Nginx 配置 WebSocket 400 问题
    Migrate from ASP.NET Core 2.0 to 2.1
  • 原文地址:https://www.cnblogs.com/yuboon/p/10415898.html
Copyright © 2011-2022 走看看