渲染模式介绍
前端发展至今已经有了无数种的开发模式,而各种开发模式使用了不同的前端框架,造成他的渲染流程也有些许差异。
但大体上我们分为服务端渲染和客户端渲染
服务端渲染
服务端渲染指页面上的元素、css均在服务器中进行排列组合好形成完整的html页面返回给前端。 优点:
- 有利益网络爬虫搜索
- 首次熏染快并且相对完整,用户体验较好 缺点:
- 服务器压力较大
当然,一般情况下服务器渲染也并不是所有的数据请求都在服务器端完成,他的部分还是依赖js通过请求在客户端进行熏染。以JSP的项目为例子,服务端渲染的流程大概如下:
- 根据用户访问路径寻找指定JSP。
- 解析HTML中的相关JSP语法。
- 根据数据构建HTML。
- 返回给用户。
- 部分通过JS的在此刻发起请求获取指定的数据,
- 通过template模版进行构建HTML。
- 将HTML添加到页面当中。
在该过程中,前四步都在服务器完成,每一个用户访问每一个页面都需要执行一遍,无疑这会让服务器压力越来越大。
客户端渲染
客户端渲染指服务器指返回静态的html和js,该html在服务器不存在动态构建的过程。随后的页面呈现都以客户端通过http请求的方式呈现。以vue渲染为例:
- 用户访问路径,服务器返回指定的静态HTML。
- 根据用户访问的vue-router匹配不同的页面或者说组件。
- 在app根节点下把获取的组件渲染到浏览器。
- 通过http请求获取数据,通过虚拟dom,在diff算法的计算下根据局部dom。
客户端渲染在返回静态html之后,和dom相关的操作都跟服务器没有任何关系了,它所提供的只剩下数据。
各端渲染可采用的框架
两种方式各有差异,我们需要根据业务来确定渲染方式。而前端的技术栈中也根据各渲染方式提供了很多框架。
服务端渲染
客户端渲染
当然,在前端的生态中,这些只是冰山一角。而每个框架的理念存在共同点和不同点,在这里不做赘述。但我们会发现无论是那种开发模式,其中都脱离不了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;
}
}
- 对该方法增加一些容错处理。
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;
}
- 通过规则生成的dom树直接返回dom字符串。
if (typeof tpl !== 'string') {
return '';
}
var fn = compile(tpl);
if (type(data) !== 'object') {
return fn;
}
return fn(data);
}