HTML在各开发模式中如何渲染

249 阅读4分钟

渲染模式介绍

前端发展至今已经有了无数种的开发模式,而各种开发模式使用了不同的前端框架,造成他的渲染流程也有些许差异。

但大体上我们分为服务端渲染客户端渲染

服务端渲染

服务端渲染指页面上的元素、css均在服务器中进行排列组合好形成完整的html页面返回给前端。 优点:

  • 有利益网络爬虫搜索
  • 首次熏染快并且相对完整,用户体验较好 缺点:
  • 服务器压力较大

当然,一般情况下服务器渲染也并不是所有的数据请求都在服务器端完成,他的部分还是依赖js通过请求在客户端进行熏染。以JSP的项目为例子,服务端渲染的流程大概如下:

  1. 根据用户访问路径寻找指定JSP。
  2. 解析HTML中的相关JSP语法。
  3. 根据数据构建HTML。
  4. 返回给用户。
  5. 部分通过JS的在此刻发起请求获取指定的数据,
  6. 通过template模版进行构建HTML。
  7. 将HTML添加到页面当中。

在该过程中,前四步都在服务器完成,每一个用户访问每一个页面都需要执行一遍,无疑这会让服务器压力越来越大。

客户端渲染

客户端渲染指服务器指返回静态的html和js,该html在服务器不存在动态构建的过程。随后的页面呈现都以客户端通过http请求的方式呈现。以vue渲染为例:

  1. 用户访问路径,服务器返回指定的静态HTML。
  2. 根据用户访问的vue-router匹配不同的页面或者说组件。
  3. 在app根节点下把获取的组件渲染到浏览器。
  4. 通过http请求获取数据,通过虚拟dom,在diff算法的计算下根据局部dom。

客户端渲染在返回静态html之后,和dom相关的操作都跟服务器没有任何关系了,它所提供的只剩下数据。

各端渲染可采用的框架

两种方式各有差异,我们需要根据业务来确定渲染方式。而前端的技术栈中也根据各渲染方式提供了很多框架。

服务端渲染
  1. JSP模式
  2. ftl模板
  3. vue ssr
  4. next.js
客户端渲染
  1. vue.js
  2. react.js
  3. angular.js

当然,在前端的生态中,这些只是冰山一角。而每个框架的理念存在共同点和不同点,在这里不做赘述。但我们会发现无论是那种开发模式,其中都脱离不了template模版。

template模版

使用方法
// 声明模版内容
<script id="test" type="text/html" >
{{if isAdmin}}
<h1>{{title}}</h1 >
<ul>
  {{each list as value i}}
   <li>索引 {{i + 1}} :{{value}}</li>
  {{/each}}
</ul>
{{/if}}
</script >

// 声明接受模版DOM
<div id="content"></div>

// 将模版添加到指定dom中
<scropt>
  title: '基本例子',
  isAdmin: true,
  list: [1, 2, 3, 4, 5]
};
var html = template('content', data);
document.getElementById('content').innerHTML = html;
</script>

我们可以看到,template模版会通过template方法,将我们传过去的list在特定语句下渲染成指定的dom。之后我们便可以将该dom渲染到视图中。这样可以省区自己通过js构建dom的流程。 目前在客户端熏染过程中,无论是vue还是react或者是比较老的html+css+js项目都会采用该插件来做底层的dom渲染。 而template的实现原理也很简单。

实现原理

第一步是通过渲染器生成指定函数,该函数接受一个参数(数据),通过数据渲染成dom字符串。

// tempalte的编译器
function compiler(tpl: string, opt: Option = o): Function {】
    1. 对模版进行转换,转换成指定的代码。
    var mainCode = parse(tpl, opt); 
    2. 生成指定的代码头和尾
    var headerCode = '\n' +
        '    var html = (function (__data__, __modifierMap__) {\n' +
        '        var __str__ = "", __code__ = "";\n' +
        '        for(var key in __data__) {\n' +
        '            __str__+=("var " + key + "=__data__[\'" + key + "\'];");\n' +
        '        }\n' +
        '        eval(__str__);\n\n';

    var footerCode = '\n' +
        '        ;return __code__;\n' +
        '    }(__data__, __modifierMap__));\n' +
        '    return html;\n';
    3. 将头,内容,尾拼接成字符串
    var code = headerCode + mainCode + footerCode;
    4. 去除部分浏览器不必要的符号
    code = code.replace(/[\r]/g, ' '); // ie 7 8 会报错,不知道为什么
    5. 生成制定的函数
    try {
        var Render = new Function('__data__', '__modifierMap__', code);
        Render.toString = function () {
            return mainCode;
        };
        return Render;
    } catch (e) {
        e.temp = 'function anonymous(__data__, __modifierMap__) {' + code + '}';
        throw e;
    }
}
  1. 对该方法增加一些容错处理。
function compile(tpl: string, opt: Option = o) {
    opt = clone(o, opt);

    try {
        var Render = compiler(tpl, opt);
    } catch (e) {
        e.name = 'CompileError';
        e.tpl = tpl;
        e.render = e.temp;
        delete e.temp;
        return handelError(e);
    }

    function render(data: object): string {
        data = clone(functionMap, data);
        try {
            var html = Render(data, modifierMap);
            html = opt.compress ? compress(html) : html;
            return html;
        } catch (e) {
            e.name = 'RenderError';
            e.tpl = tpl;
            e.render = Render.toString();
            return handelError(e)();
        }
    }

    render.toString = function () {
        return Render.toString();
    };
    return render;
}
  1. 通过规则生成的dom树直接返回dom字符串。
    if (typeof tpl !== 'string') {
        return '';
    }

    var fn = compile(tpl);
    if (type(data) !== 'object') {
        return fn;
    }

    return fn(data);
}