经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » HTML/CSS » HTML5 » 查看文章
Vue 2.0 深入源码分析(三) 基础篇 模板渲染 el、emplate、render属性详解
来源:cnblogs  作者:大沙漠  时间:2019/6/14 9:38:28  对本文有异议

用法


 Vue有三个属性和模板有关,官网上是这样解释的:

el     ;提供一个在页面上已存在的 DOM 元素作为 Vue 实例的挂载目标

template  ;一个字符串模板作为 Vue 实例的标识使用。模板将会 替换 挂载的元素。挂载元素的内容都将被忽略,除非模板的内容有分发插槽。

render    ;字符串模板的代替方案,允许你发挥 JavaScript 最大的编程能力。该渲染函数接收一个 createElement 方法作为第一个参数用来创建 VNode

 

简单说一下,就是:

  Vue内部会判断如果没有render属性则把template属性的值作为模板,如果template不存在则把el对应的DOM节点的outerHTML属性作为模板,经过一系列正则解析和流程生成一个render函数,最后通过with(this){}来执行。

  也就是说template的优先级大于el。

  render的参数是Vue内部的$createElement函数(位于4486行),它的可扩展性更强一些,在一些项目的需求中,可以用很简单的代码得到一个模板。例如Vue实战9.3里介绍的例子,有兴趣可以看看

render可以带4个参数,分别如下:

    tag          ;元素的标签名,也可以是组件名
    data        ;该VNode的属性,是个对象
    children        ;子节点,是个数组
、其中参数2可以省略的,在4335行做了修正,最后执行_createElement()函数,如下:

  1. function createElement (  //第4335行
  2. context,
  3. tag,
  4. data,
  5. children,
  6. normalizationType,
  7. alwaysNormalize
  8. ) {
  9. if (Array.isArray(data) || isPrimitive(data)) { //如果data是个数组或者是基本类型
  10. normalizationType = children;
  11. children = data; //修正data为children
  12. data = undefined; //修正data为undefined
  13. }
  14. if (isTrue(alwaysNormalize)) {
  15. normalizationType = ALWAYS_NORMALIZE;
  16. }
  17. return _createElement(context, tag, data, children, normalizationType)     //最后执行_createElement创建一个虚拟VNode
  18. }

 

例如下面三个Vue实例,分别用el、template和rentder指定模板,它们的输出是一样的

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <script src="https://cdn.bootcss.com/vue/2.5.16/vue.js"></script>
  6. <title>Document</title>
  7. </head>
  8. <body>
  9. <div id="app1">{{message}}</div>
  10. <div id="app2"></div>
  11. <div id="app3"></div>
  12. <script>
  13. var data={message:'you are so annoying'}
  14. new Vue({el:'#app1',data}) //用el做模板
  15. new Vue({el:'#app2',data,template:"<div>{{message}}</div>"}) //用template做模板
  16. new Vue({el:'#app3',data,render:function(h){return h('div',this.message)}}) //直接用render函数指定模板
  17. </script>
  18. </body>
  19. </html>

 

、浏览器显示结果:

可以看到输出是一摸一样的

 

 源码分析

Vue实例后会先执行_init()进行初始化,快结束时会判断是否有el属性,如果存在则调用$mount进行挂载,$mount函数如下:

  1. Vue.prototype.$mount = function (    //定义在10861行
  2. el,
  3. hydrating
  4. ) {
  5. el = el && query(el);
  6. /* istanbul ignore if */
  7. if (el === document.body || el === document.documentElement) {
  8. "development" !== 'production' && warn(
  9. "Do not mount Vue to <html> or <body> - mount to normal elements instead."
  10. );
  11. return this
  12. }
  13. var options = this.$options;
  14. // resolve template/el and convert to render function
  15. if (!options.render) { //如果render属性不存在
  16. var template = options.template; //则尝试获取template属性并将其编译成render
  17. if (template) {
  18. if (typeof template === 'string') {
  19. if (template.charAt(0) === '#') {
  20. template = idToTemplate(template);
  21. /* istanbul ignore if */
  22. if ("development" !== 'production' && !template) {
  23. warn(
  24. ("Template element not found or is empty: " + (options.template)),
  25. this
  26. );
  27. }
  28. }
  29. } else if (template.nodeType) {
  30. template = template.innerHTML;
  31. } else {
  32. {
  33. warn('invalid template option:' + template, this);
  34. }
  35. return this
  36. }
  37. } else if (el) { //如果templtate不存在但是el存在,则获取调用getOuterHTML()函数获取el的outerHTML属性,getOuterHTML()定义在10933行,也就是末尾,用户获取DOM的outerHTML
  38. template = getOuterHTML(el);
  39. }
  40. if (template) {
  41. /* istanbul ignore if */
  42. if ("development" !== 'production' && config.performance && mark) {
  43. mark('compile');
  44. }
  45. var ref = compileToFunctions(template, {
  46. shouldDecodeNewlines: shouldDecodeNewlines,
  47. shouldDecodeNewlinesForHref: shouldDecodeNewlinesForHref,
  48. delimiters: options.delimiters,
  49. comments: options.comments
  50. }, this); //这里调用compileToFunctions()将template解析成一个render函数,并返回
  51. var render = ref.render;
  52. var staticRenderFns = ref.staticRenderFns;
  53. options.render = render;
  54. options.staticRenderFns = staticRenderFns;
  55. /* istanbul ignore if */
  56. if ("development" !== 'production' && config.performance && mark) {
  57. mark('compile end');
  58. measure(("vue " + (this._name) + " compile"), 'compile', 'compile end');
  59. }
  60. }
  61. }
  62. return mount.call(this, el, hydrating)
  63. };
  1. compileToFunctions函数是由createCompiler()返回的(这里有点绕,研究代码的时候在里面绕了好几天),我把大致主体贴出来,如下:
  1. var baseOptions ={} //编译的配置项 第9802行
  2. function createCompileToFunctionFn(compile){
  3. var cache = Object.create(null);
  4. return return function compileToFunctions(template, options, vm) { //编译时先执行这里
  5. /**/
  6. compile(template,options)
  7. /**/
  8. }
  9. }
  10. function createCompilerCreator(baseCompile){
  11. return function(baseOptions){
  12. function compile(template, options) {/**/}
  13. return {
  14. compile: compile,
  15. compileToFunctions: createCompileToFunctionFn(compile) //难点2:匿名函数返回的值中又调用了createCompileToFunctionFn函数
  16. }
  17. }
  18. }
  19. var createCompiler = createCompilerCreator(function(){ //传入一个匿名函数
  20. var ast = parse(template.trim(), options); //编译时,第二步:再执行这里
  21. if (options.optimize !== false) {
  22. optimize(ast, options);
  23. }
  24. var code = generate(ast, options);
  25. return {ast: ast,render: code.render,staticRenderFns: code.staticRenderFns} //最后返回一个对象
  26. })
  27. var ref$1 = createCompiler(baseOptions);
  28. var compileToFunctions = ref$1.compileToFunctions; //编译的入口文件

是不是有点晕呢,我举一个例子就能看明白了,如下:

  1. function show(show){ //shou函数也直接返回一个匿名函数,带一个参数
  2. return function(info){
  3. show(info) //show通过作用域链就可以访问到参数的show函数了
  4. }
  5. }
  6. var info=show(function(info){
  7. console.log(info)
  8. })     //这里执行show函数,传入一个匿名函数
  9. info({name:'gsz'})     //控制台输出:{name: "gsz"}

Vue内部看得晦涩是因为传参的时候都注明了一个函数名,其实这个函数名是可以忽略的,这样看起来会更清晰一点    注:这样设计是为了跨平台一些代码的复用和存放吧,代码结构在node下更好理解一点

compileToFunctions函数内部会调用parse()将模板经过一系列的正则解析,用一个AST对象保存,然后调用generate()做静态节点标记,最后调用generate生成一个render函数

以上面的第一个Vue实例来说,parse()解析后的AST对象如下:

、再通过generate()后生成如下一个对象,其中render就是最终要执行的render函数了

  1. compileToFunctions函数返回值是一个对象,以上面的第一个vue实例为例,返回后的信息如下:
    {
      render:"(function anonymous() {with(this){return _c('div',{attrs:{"id":"app1"}},[_v(_s(message))])}})",     //最终渲染出来的render函数
      staticRenderFns:Function[]                                                //如果是静态节点,则保存到这里
    }
    以后分析到每个API时这里会单独分析的
    最后在mountcomponent()函数内会以当前Vue实例为上下文,执行该render函数(在2739行),此时就会完成渲染watch的收集,并生成虚拟VNode,最后调用_update()方法生成真实DOM节点。

原文链接:http://www.cnblogs.com/greatdesert/p/11011485.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号