如果想看前端的演进历史,可直接跳转。本文篇幅较长,请有选择阅读。

前后端分离

前后端分离的历史

软件开发早期是没有前后端分离的概念的,为什么呢?因为当时压根儿就没有前端工程师,专门的前端工程师大约是在 2005 年。在此之前前端是不受重视的。这其实和软件的发展有关。说到这里又不得不提到 js 的发展历史。JavaScript 诞生于 1995 年。起初它的主要目的是处理以前由服务器端负责的一些表单验证。后来大家对页面的要求越来越高,js 又给 web 多了一些动态功能。大家对前端的需求就是展示静态内容或者简单的动态内容(比如 CGI 返回数据拼接输出“动态”内容)。不过这种直接通过 CGI 或者 servlet 输出 HTML 的方法会使得前后端严重耦合,修改 HTML 需要改后端代码。为了将数据和模板分离,出现了很多动态拼接的技术,ASP,JSP,ASP.net 就是那个时期的产物。但是这种方式还是有问题,因此这种方式数据严重依赖于请求流,它是将请求流解析,然后将响应流输出到模板作为模板数据解析出 HTML 返回到后端,另外后端需要懂得前端的知识,HTML 语法,JS 语法,甚至要自己写 CSS,这个时期也是 MVC 盛行的时期,这个时期的 View 是放在后端的。

1998 年 ajax 技术的出现,允许客户端脚本发送 HTTP 请求(XMLHTTP),并且局部刷新页面,这种突破性的创新使得 web 高速发展,推动了 web 的发展。随着 HTML5,CSS3,ES6(简称 356)的出现,web 正以前所未有的速度前进,web 工程师从无到有,再到现在 web 工程师被赋予了很多花环,机遇和挑战。也因此前后端不得不逐渐的分离。

这只后又出现了将数据和模板完全分离的技术如 velocity 等,写法上完全改变了。 下面是一个 velocity 的 demo:

import java.io.StringWriter;
import org.apache.velocity.app.VelocityEngine;
import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
public class  Velocity {
    public static void main( String[] args )  throws Exception   {

    VelocityEngine ve = new VelocityEngine();
    ve.init();

    Template t = ve.getTemplate( "hellosite.vm" );

    VelocityContext context = new VelocityContext();
    context.put("name", "Eiffel Qiu");
    context.put("site", "http://www.eiffelqiu.com");

    StringWriter writer = new StringWriter();
    t.merge( context, writer );

    System.out.println( writer.toString() );
    }
}

这种思想是一大创新,包括 react 的渲染都是这样的思路,react 的渲染方式是 render,本质上开发者写的 jsx 就是 template,app 的 state 和 props 就是 context,然后根据纯函数的特性,生成 view。

虽然这是一个重大创新,不过它是严重依赖语言实现的,我们从上面的例子就可以看出来。那么如何使用一种通用的模板引擎?答案就是将模板放到前端,将模板这部分工作交给前端,这个时候逐渐又了前后端分离的概念。说到这里,不得不提到 2010 年 facebook 工程师提出的 bigpipe。他将页面渲染分为八个关键阶段,每一个阶段叫做 pagelet。先输出页面主体到前端,也就是前面提到的 Template,然后前端 ajax 请求数据,将请求的数据在前端进行拼接,这种方式比后端拼接,优点就是用户可以看到一个渐进式的过程,这在当时是一个巨大创新。缺点就是浪费若干 RTT(多了若干请求)。但是经过测试,在大多数浏览器中,BigPipe 都能将用户感受到的延迟时间降低一半,除了 Firefox 3.6,BigPipe 可以将 Firefox 3.6 的延迟时间降低 50ms 左右,大约降低了 22%左右。

然后开发者将模板引擎放到了前端,后端只是放一个 HTML 的空壳,代表作是 react。这个时候模板解析的任务是前端,后端完全从模板的地狱中逃了出来。但是还是有问题,就是用户看到的页面会是一个空壳,并不是一个 app shell。因此出现了 ssr 等技术,本质上就是回到之前的渲染方式,区别在于目前不是将所有的逻辑都交给后端,而是后端只处理首屏内容。

前后端合并

正所谓合久必分,分久必合。 前几年被热炒的全栈工程师,以及前后端同构技术都反映了前后端在分离之后又逐渐合并的迹象。为什么会这样呢?前后端分离虽然减少了后端开发的工作(最初前端都是后端写的,比如.net 的 aspx java 的 jsp 等)。但前后端分离地不干净导致一些沟通问题反而不如一个人来做。为了解决这个问题,主要又两种方式:

  • 全栈工程师。 将所有工作交给一个人,或者有着前后端知识的一群人,这样沟通起来成本比较低。

  • 稍后讲。

    那还又必要分离吗?

    上面讲述了前后端合并,那么还有必要分离吗? 非常有必要!!!

刚才说为了解决前后端沟通问题主要又两种方式,下面说下第二种。

  • 建立中间层。 前后端问题产生的原因主要是两个,一个是知识背景,技术栈不同,难以互相理解。二是 前后端是一个依赖的关系,前端需要依赖后端的数据接口,因此存在工作上的先后关系。 建立中间层可以有效减少上述的问题。 目前淘宝,挖财,51,有赞,二维火都在尝试这种方式。这也是我将会在后面重点描述的方式。

最近出现的 docker 容器技术等有效地减少了后端的工作量,让后端更加专注业务逻辑的编写。我觉得 node 的出现也大概如此吧,它的出现不是用来替代 apache 成为下一个 web 容器,我觉得他更是扩展了前端的领域,使得可以将一部分后端无法做(比如 ssr)或者不好做(不愿意做)的东西(比如接口聚合),移交给前端。

我不喜欢被冠以前端工程师,后端工程师的帽子。 我喜欢工程师这个称呼,我觉得我是以工程化的方式解决问题的角色,而不应该限定于某一部分职责。这个我主张前后端分离不矛盾,每个角色都自己的分工,都有自己精细化的一面。我们不试图取代后端,我们只是专注做好自己罢了。

前后端耦合

我有幸在我的职业生涯中经历了前后端耦合,前后端开发分离,部署分离的“分离阶段”, 以及大厂(alibaba)在这些方面的解决方案,所以非常荣幸能够将自己的经验和心得与大家分享。

最初在百世做前端的时候,我经历了前后端极度耦合的方式前后端虽然由不同的人来书写,那恰好也是我的领导首次尝试前后端分离模式,在此之前前后端通常都是一名后端人员来写的。这种模式虽然将前后端工作进行了切分,但是其耦合的成分还是非常大。

首先我们来看下我们的具体做法。 1. 前后端约定接口。 2. 前端根据后端接口进行开发。 3. 后端进行开发。(2 与 3 基本并行) 4. 前端打包代码,并将代码发送给后端。后端放到 war 包下。

tomcat/conf/web.xml 配置如下:

<welcome-file-list>
<welcome-file>index.html</welcome-file>
<welcome-file>index.htm</welcome-file>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>

将 war 文件直接复制到 tomcat/webapps 下

我们后端使用的是 java,具体做法:

  • 如果是 Intellij Idea,在导入前端项目之后,右键项目 add framework support --> web application,这时将会把前端项目转换为一个 javaweb 项目,然后将静态资源放在生成的 web 目录下即可。

  • 如果是 eclipse,可以新建一个 javaweb 项目然后将静态资源放入 web 或者 webcontent 目录下,或者直接先导入前端项目,然后通过 project facts 将项目转换为 dynamic web 项目并勾选 js 等相关配置。

    然后,运行项目时把后端的 war 包和前端的 war 包一同添加到 deployment 中运行即可

这一阶段我们实现了初步的前后端分离。 但是上述做法有两个问题。 1. 由于上述的做法存在严重的问题在于前端对于发布控制力明显不足,比如版本控制不好做。 另外由于发布通常需要两个编译环境,即 jdk 编译后端代码,node 环境编译前端代码。前端通常需要安装后端环境如 jdk,后端也需要安装前端环境如 node。不管是学习成本还是沟通成本都是一个问题。

  1. 前端通常需要等待后端给出测试数据后才能开工。所以我上面说“基本并行”

由于存在上面的两个问题,我们进一步探索出了下面的前后端”分离“模式。

前后端“分离”

这一阶段我们通过 nginx 将前后端部署分离,通过 mock 服务器将前后端时间上的耦合给减少了。 下面是我们的具体做法: 1. 前后端约定接口 2. 前端开发 3. 后端开发(2 和 3 完全并行) 4. 前端打包,并将代码通过 ftp 上传到文件服务器。 5. 配置 nginx 将静态请求代理到上面发布文件的文件服务器,后台接口代理到 apache web server。

和上面不同的地方,我们只需要将代码上传到文件服务器或者 CDN 上。 通过 nginx 转发控制即可。 避免了上面必须同时安装后端和前端编译环境的繁琐。

nginx 配置 通常是这样的:

server {
        listen       80;
        server_name  example.com;

        charset utf-8;

        #access_log  logs/host.access.log  main;

        location / {
              proxy_pass http://tomcat_pool;
              proxy_redirect off;
              proxy_set_header HOST $host;
              proxy_set_header X-Real-IP $remote_addr;
              proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
              client_max_body_size 10m;
              client_body_buffer_size 128k;
              proxy_connect_timeout 90;
              proxy_send_timeout 90;
              proxy_read_timeout 90;
              proxy_buffer_size 4k;
              proxy_buffers 4 32k;
              proxy_busy_buffers_size 64k;
              proxy_temp_file_write_size 64k;
        }

        location ~ .*\.(html|htm|gif|jpg|jpeg|bmp|png|ico|txt|js|css|woff|woff2|ttf|eot|map)$  {
             root D:\Workspaces\front-end;
             index index.html;
        }

大厂的方案

这里主要介绍下阿里巴巴-钉钉的前后端分离解决方案。阿里这样的大厂通常有着自己内部的成熟系统支撑业务,比如阿里的持续集成系统 aone,配置服务 diamond,分布式服务系统 hsf 等等。 小公司通常没有经济实力去搭建自己的内部系统,我有幸接触到了这些业界较为顶级的系统,对它们的运作方式有了一定的了解。这里简单介绍下阿里巴巴-钉钉的前后端分离解决方案。 钉钉的具体做法如下:

  1. 前后端约定接口

  2. 约定接口上 dip(一个 mock 服务器)

  3. 前端开发

  4. 后端开发(2 和 3 完全并行)

  5. 前端打包,push 触发测试环境和预发环境的云构建,

    打 tag 触发线上环境云构建自动发布静态资源到 cdn。

  6. 需要发布只需要负责人修改 diamond 配置即可,版本控制变得简单。

可以发现这种方式其实就是比上面多了两个点,一是云构建自动化,二是 diamond 配置。 云构建其实属于自动化,我将在流程自动化里面详细描述,云构建本身和 前后端分离没有关系。 其实两者的区别在于 diamond 配置。那么为什么配置 diamond 是前后端分离的工作呢?作为一个配置服务,diamond 将专业性比较强的东西抽离出来,将包括但不限于版本管理的内容可以交给没有任何前端背景的人来做。

前后端分离的理想方式

上面的这种方法是非常高效的一种方式,但仍然不是我认为的理想的前后端分离方式。 我认为理想的前后端分离方式是职能上,后端提供纯粹的接口,只需要提供数据-系统的数据或者根据根据二方库获取数据返回前端,剩下的逻辑前端做。时间上,前后端可以并行开发,就像下面这张图一样。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-n8lv4ZHp-1603471763382)(https://raw.github.com/azl397985856/automate-everything/master/illustrations/图4.04.png)]

前后端的约定就像 HTML 一样,是前置资源,是前提。当这个前置资源定了之后,前端(就像上图的 CSS)和后端(就像上图的 JS)可以并行加载,它们中全部完成了,页面就可以加载完毕(这里不考虑其他外部资源)。

职能上由于后端提供元数据,前端只需要组合,前后端在逻辑和时间上没有了耦合。先来一张图来描述下: [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zVH5k8vX-1603471763385)(https://raw.github.com/azl397985856/automate-everything/master/illustrations/图1.1.png)]

如图,后端只是提供原子数据,保证数据稳定输出就可以了,事实上保证系统稳定很多已经是运维再做的事了。前端需要根据需要进行接口整合,服务端渲染,mock 数据等工作。 那么整个流程具体是怎么工作的呢? 可以下面这张图: [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UcRqbHJk-1603471763388)(https://raw.github.com/azl397985856/automate-everything/master/illustrations/图1.2.png)]

可以看出请求首先留到 ngxin(反向代理),nginx 判断是否是静态请求(html),如果是则转发到 node 服务器,node 服务器会判断是否需要进行 ssr,如果需要则调用后台接口拼装 html,将html 和应用状态一起返回给前端。 如果不需要 ssr,则直接返回静态资源,并设置缓存信息。 如果不是静态资源,判断头部信息(比如有一个自定义字段 reselect: ‘node’ | ‘’),是否需要请求合并,如果需要则请求到 node 端,如果不需要直接转发给后端服务器。 ngxin 配置大概是这样:

map $reselect/node $reselect {
  default "";
  "node" "reselect/node";
}

server {
  listen                    80;
  server_name               demo.io;

  charset                   utf-8;
  autoindex                 off;

  location / {
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header X-Forwarded-Port $server_port;
    proxy_set_header X-Forwarded-Ssl on;

    if ($reselect="reselect/node"){
      proxy_pass      http://node-demo.io;
      break;
    }

    proxy_pass      http://java-demo.io;
  }
  location ~ .*\.(html|htm|gif|jpg|jpeg|bmp|png|ico|txt|js|css|woff|woff2|ttf|eot|map)$  {
       root D:\Workspaces\front-end;
       index index.html;
  }
}

中间层可以做的事情非常多,除了我刚才说的服务端渲染,接口组合,mock 数据,还可以做很多别的事。我在这里不会讲述 ssr,接口组合,mock 如何具体做,因为这不是本章的重点,而且也有很多最佳实践,我要说的是如何将这些“最佳实践“ 组合起来,如何在我们工作中将其应用起来,并且具有良好的扩展性。 那么中间层拿到请求具体流程上面已经讲过了,现在我们以实战的角度,讲下代码该怎么组织。 但是为了方便大家理解,我简单介绍下。 首先是服务端渲染,我以 react+redux 做服务端渲染讲解,为了简单起见,没有引入 react-router,大家直接看代码理解:

服务端:

<body>
    <div id="container"><%- html %></div>
    <script>
        window.__INITIAL_STATE__ = <%- JSON.stringify(initState) %>
    </script>
</body>
const store = buildStore(rootReducer, {});
Promise.all([
  store.dispatch(fetchUserInfo()),
  store.dispatch(fetchPosts())
]).then(() => {
  const html = renderToString(
    <Provider store={store}>
      <RouterContext {...renderProps} />
    </Provider>
  );
  const initState = store.getState();
  res.render("index", html, initState);
});

客户端:

const initState = window.__INITIAL_STATE__;
const store = storeApp(initState);
ReactDOM.render(
  <Provider store={store}>
    <Router history={browserHistory}>{routesApp}</Router>
  </Provider>,
  document.getElementById("container")
);

上面这是对服务端渲染的一个极简实现,那么接口聚合呢?mock 呢? 加入其他功能呢? 是不是对我们 express 本身侵入性太大。 在这里我借鉴了微服务的理念,同时利用第二章节要讲模块化的思想,组织了中间层。 那么究竟我是怎么设计的呢,请继续往下看。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nistalXB-1603471763390)(https://raw.github.com/azl397985856/automate-everything/master/illustrations/图1.3.png)]

我们的关注点就是服务集群,如果需要增加集群就直接修改配置即可。 下面基于 docker + docker-compose + node + nginx 做一个中间层系统

docker-compose.yml

version: '2'

services:


    nginx:
        build: ./nginx
        container_name: ms_nginx
        links:
            - posts
            - users
        ports:
            - "80:80"

    api:
        build: ./api
        container_name: ms_posts
        environment:
            - loglevel=none

        volumes:
            - "./posts:/src/app"
        working_dir: "/src/app"
        ports:
            - "8080:8080"
            - "5858:5858"
        # command: npm run start
        command: npm run start:dev

    sever-render:
        build: ./sever-render
        container_name: ms_sever-render

        volumes:
            - "./users:/src/app"
        working_dir: "/src/app"
        command: npm start

nginx 的配置

worker_processes 4;

events { worker_connections 1024; }

http {

    server {
          listen 80;
          charset utf-8;

          location / {
            proxy_pass http://server-render:8080;
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection 'upgrade';
            proxy_set_header Host $host;
            proxy_cache_bypass $http_upgrade;
          }

          location ~ ^/api {
            rewrite ^/api/(.*) /$1 break;
            proxy_pass http://users:8080;
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection 'upgrade';
            proxy_set_header Host $host;
            proxy_cache_bypass $http_upgrade;
          }
    }
}

这样我们实现了不同的系统分流,并且彼此之间接触耦合,通过 nginx 去 分发。这样还存在一个问题就是样板代码比较多, 有没有一种方法,让我们只关注业务本身,而不需要样板代码呢?大家可以关注下 faas, 这里不在赘述。 当我们的业务逐渐复杂,系统逐渐增多,域名逐渐增多,会发现很多东西都在 nginx 中,这时候需要将配置分开,每一个子业务一个配置文件。

  1. 在 nginx 安装目前下 新建 vhost 文件夹

  2. 在文件夹下创建 *.conf 的文件 ,如 a.example.com.conf ,命名规则大多以域名的方法来命名文件。

  3. 辑 conf 文件,把我们平常放在 nginx.conf 里的 server{…} 段复制过来直接粘贴到 conf 里。

    在 nginx.conf 的 http{…} 段中加入 include E:/nginx-1.8.1/vhosts/.conf;

注:这里 include 需要用到全路径,且文件后缀是用 conf**

这里介绍一个淘宝开源软件 tengine,他是 nginx 的超集。有很多强大的功能,我之前的公司百世就是用的tengine

前后端分离的好处

前后端分离的一个好处就是将人员分工更加明确,前端只关注界面,后端只负责数据,从而使效率显著提高。 再有就是前后端分离可以使得后端接口复用性更强(前端的复用通常可以采取组件的方式),如果公司需要针对 同一产品开发多套 UI(原生,手机端,PC 端等),理论上如果后端接口设计地足够好,是不需要改变的。 前端只需要根据不同的 UI 设备的呈现能力(手机屏幕小,因此元素更加紧凑,简约),实现不同的交互方式。

前后端分离从技术角度上讲是后端技术和前端技术的解耦,从业务角度上讲,是业务逻辑和用户体验的解耦。

前后端分离的挑战

要实行前后端分离,不得不面对的一个问题就是前后端沟通问题。前面提到前后端分离,本质上是基于契约式的开发, 如果契约发生改变,那么就需要前后端再次进行沟通。不要小看沟通问题,沟通是软件开发环节中最难控制的一环。 如果是跨部门甚至跨公司沟通,沟通成本会显著上升。

在有一个问题就是接口设计,前面提到前后端分离的核心是契约式开发。契约本质上就是 API 接口,因此 API 接口的 设计直接影响了前后端分离的实现。目前 facebook 提出的 graphql 也非常火热,其本质上是应用层查询语言。解决的 问题就是 restfull 接口的数据,接口冗余的问题。而目前绝大多数的前后端分离 API 设计都是基于 restfull 或者类 restfull 的设计。如果感兴趣,可以自行查阅 graphql 的参考资料。

前后端分离之前,路由是放到后台的。如果做前后端分离,前端负责页面的路由,后端暴漏若干接口。这种方式相比 后台直出页面有一个问题就是,我们的接口是暴漏的,这就对安全问题提出了更高的要求。这部分不是本文的讨论重点, 不再赘述。

前后端分离之后,文档的重要性显得更加重要。这就要求有一个明确的文档化的产品落地,而不是存在于人的头脑之中。

总结

本章介绍了四种前后端分离的方式和阶段,这里需要强调的是并不是越往后的方式越好,问题的关键点还是选择合适的 方式,根据当前所处阶段选择合适的分离方式,提高单体和整体作战效率才是明智之举。

前端历史

前端开发的历史和趋势


什么是前端

  • 前端:针对浏览器的开发,代码在浏览器运行
  • 后端:针对服务器的开发,代码在服务器运行

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eDdZFyby-1603472276672)(./images/frontend.png)]


前后端不分的时代

互联网发展的早期,前后端开发是一体的,前端代码是后端代码的一部分。

  1. 后端收到浏览器的请求
  2. 生成静态页面
  3. 发送到浏览器

后端 MVC 的开发模式

那时的网站开发,采用的是后端 MVC 模式。

  • Model(模型层):提供/保存数据
  • Controller(控制层):数据处理,实现业务逻辑
  • View(视图层):展示数据,提供用户界面

前端只是后端 MVC 的 V。


以 PHP 框架 Laravel 为例。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oDNrMexO-1603472276675)(./images/laravel-mvc.png)]


前端工程师的角色

那时的前端工程师,实际上是模板工程师,负责编写页面模板。

后端代码读取模板,替换变量,渲染出页面。


典型的 PHP 模板

<html>
  <head><title>Car {{ $car->id }}</title></head>
  <body>
    <h1>Car {{ $car->id }}</h1>
    <ul>
      <li>Make: {{ $car->make }}</li>
      <li>Model: {{ $car->model }}</li>
      <li>Produced on: {{ $car->produced_on }}</li>
    </ul>
  </body>
</html>

Ajax

Ajax 技术诞生,改变了一切。

  • 2004年:Gmail
  • 2005年:Google 地图

前端不再是后端的模板,可以独立得到各种数据。


Web 2.0

Ajax 技术促成了 Web 2.0 的诞生。

  • Web 1.0:静态网页,纯内容展示
  • Web 2.0:动态网页,富交互,前端数据处理

从那时起,前端变得复杂了,对前端工程师的要求越来越高。


前端 MVC 框架

前端通过 Ajax 得到数据,因此也有了处理数据的需求。

前端代码变得也需要保存数据、处理数据、生成视图,这导致了前端 MVC 框架的诞生。

  • 2010年,Backbone.js

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8uvKM1JY-1603472276679)(./images/backbone.png)]


Backbone.js

Backbone 将前端代码分成两个基本部分。

  • Model:管理数据
  • View:数据的展现

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UENJ0Azu-1603472276681)(./images/backbone-model-view.png)]


前端 Controller

Backbone 只有 M 和 V,没有 C。因为,前端 Controller 与后端不同。

  • 不需要,也不应该处理业务逻辑
  • 只需要处理 UI 逻辑,响应用户的一举一动

所以,前端 Controller 相对比较简单。Backbone 没有 C,只用事件来处理 UI 逻辑。

  var AppView = Backbone.View.extend({
    // ...
    events: {
      "keypress #new-todo":  "createOnEnter",
      "click #clear-completed": "clearCompleted",
      "click #toggle-all": "toggleAllComplete"
    },
  });

Router

前端还有一种天然的方法,可以切换视图,那就是 URL。

通过 URL 切换视图,这就是 Router(路由)的作用。以 Backbone 为例。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YyRaO4yB-1603472276682)(./images/backbone-routing.png)]


App.Router = Backbone.Router.extend({
  routes: {
    '': 'index',
    'show': 'show'
    },
  index: function () {
    $(document.body).append("调用了 Index 路由");
  },
  show: function () {
    $(document.body).append("调用了 Show 路由");
  },
});

示例:Backbone Router

打开demos/backbone-demo/index.html,按照《操作说明》,查看示例。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uHoGAA5u-1603472276684)(./images/backbone-demo.png)]


MVVM 模式

另一些框架提出 MVVM 模式,用 View Model 代替 Controller。

  • Model
  • View
  • View-Model:简化的 Controller,唯一作用就是为 View 提供处理好的数据,不含其他逻辑。

本质:view 绑定 view-model,视图与数据模型强耦合。数据的变化实时反映在 view 上,不需要手动处理。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BXT6eS73-1603472276685)(./images/mvvm.png)]


SPA

前端可以做到:

  • 读写数据
  • 切换视图
  • 用户交互

这意味着,网页其实是一个应用程序。

SPA = Single-page application

2010年后,前端工程师从开发页面,变成了开发“前端应用”(跑在浏览器里面的应用程序)。


传统的架构

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6JKp8A5X-1603472276686)(./images/architecture-old.png)]

单页应用的架构

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Yf2Jo2KI-1603472276687)(./images/architecture-new.png)]

多了一个前端 MVC 层


Angular

Google 公司推出的 Angular 是最流行的 MVVM 前端框架。

它的风格属于 HTML 语言的增强,核心概念是双向绑定。


示例:Angular 的双向绑定

浏览器打开demos/angular-demo/index.html,可以看到一个输入框。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0ZJdiSZQ-1603472276688)(./images/angular-demo.png)]


<div ng-app="">
  <p>
    姓名 :
    <input
      type="text"
      ng-model="name"
      placeholder="在这里输入您的大名"
    >
  </p>
  <h1>你好,{{name}}</h1>
</div>

Vue

Vue.js 是现在很热门的一种前端 MVVM 框架。

它的基本思想与 Angular 类似,但是用法更简单,而且引入了响应式编程的概念。


示例:Vue 的双向绑定

Vue 的模板与数据,是双向绑定的。

打开demos/vue-demo/index1.html,按照《操作说明》,查看示例。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-r3ImLpKR-1603472276692)(./images/vue-demo.png)]


HTML 代码

<div id="journal">
  <input type="text" v-model="message">
  <div>{{message}}</div>
</div>

JS 代码

var journal = new Vue({
  el: '#journal',
  data: {
    message: 'Your first entry'
  }
});

前后端分离

  • Ajax -> 前端应用兴起
  • 智能手机 -> 多终端支持

这两个原因,导致前端开发方式发生根本的变化。

前端不再是后端 MVC 中的 V,而是单独的一层。


REST 接口

前后端分离以后,它们之间通过接口通信。

后端暴露出接口,前端消费后端提供的数据。

后端接口一般是 REST 形式,前后端的通信协议一般是 HTTP。


Node

2009年,Node 项目诞生,它是服务器上的 JavaScript 运行环境。

Node = JavaScript + 操作系统 API


Node 的意义

  • JavaScript 成为服务器脚本语言,与 Python 和 Ruby 一样
  • JavaScript 成为唯一的浏览器和服务器都支持的语言
  • 前端工程师可以编写后端程序了

前端开发模式的根本改变

  • Node 环境下开发
  • 大量使用服务器端工具
  • 引入持续集成等软件工程的标准流程
  • 开发完成后,编译成浏览器可以运行的脚本,放上 CDN

全栈工程师

前端工程师正在转变为全栈工程师

  • 一个人负责开发前端和后端
  • 从数据库到 UI 的所有开发

全栈技能

怎样才能称为全栈工程师?

  • 传统前端技能:HTML、JavaScript、CSS
  • 一门后端语言
  • 移动端开发:iOS / Android / HTML5
  • 其他技能:数据库、HTTP 等等

软件行业的发展动力

历史演变:前后端不分 -> 前后端分离 -> 全栈工程师

动力:更加产业化、大规模地生产软件

  • 效率更高
  • 成本更低

通用性好、能够快速产出的技术最终会赢,单个程序员的生产力要求越来越高。


H5 就是一个最好的例子

为什么 H5 技术会赢得移动端?

  • 开发速度快:Native 需要重新编译才能看到结果,H5 是即时输出
  • 开发成本低:Native 需要两个开发团队,H5 只要一个
  • 快速发布:安卓 Native 新版本需要24小时,iOS 需要 3 ~ 4 天,H5 可以随时更新

未来软件的特点

  • 联网
  • 高并发
  • 分布式
  • 跨终端

现在基于 Web 的前端技术,将演变为未来所有软件的通用的 GUI 解决方案。


未来只有两种软件工程师

  • 端工程师
    • 手机端
    • PC 端
    • TV 端
    • VR 端
    • ……
  • 云工程师
Logo

前往低代码交流专区

更多推荐