vue模板语法绑定及执行时源码的研究

248 阅读1分钟
  • 先研究v-bind的理解
  • 其实就只想知道。:xx="1" 做了什么?
  • 以及对于。:xx="res" 的区别在哪里?
  • 以及源码中绑定后是什么样子,及为什么只能是表达式,及为什么有白名单说法?
// 采用compiler版本的vue
// 直接采用dist/vue.js版本,然后赋值到一个新项目,script导入即可
// 代码大概如下
<!DOCTYPE html>
<head>
    <script src="./vue.js"></script>
</head>
 <body>
  <div id="app"></div>
 </body>
 <script>
    const Vue =window.Vue
     new Vue({
       data(){
           return {
               x:1
           }
       },
    template:`<div :title="x">hello word</div>`
   }).$mount('#app')
   </script>
</html>
  • 我们知道模板经历了以下过程
  • 字符串 -> ast树
  • 其中解析出tag,命令等, 属性 也是在这阶段完成的
  • 然后把ast转化为render
// ast 
attrs: Array(1)
0: {name: "title", value: "x", dynamic: false, start: 5, end: 15}
length: 1
__proto__: Array(0)
attrsList: Array(1)
0: {name: ":title", value: "x", start: 5, end: 15}
length: 1
__proto__: Array(0)
attrsMap:
:title: "x"
__proto__: Object
children: [{}]
end: 32
hasBindings: true
parent: undefined
plain: false
rawAttrsMap: {:title: {}}
start: 0
static: false
staticRoot: false
tag: "div"
type: 1

// ast -> render
 var code = generate(ast, options);
  return {
    ast: ast,
    render: code.render,
    staticRenderFns: code.staticRenderFns
  }
  • 重点看下面,with
render: "with(this){return _c('div',{attrs:{"title":x}},[_v("hello word")])}"

// 然后
res.render = createFunction(compiled.render, fnGenErrors);

// createFunction 方法为
function createFunction (code, errors) {
  try {
    return new Function(code)
  } catch (err) {
    errors.push({ err: err, code: code });
    return noop
  }
}
  • 其实关键就是with(this)
  • 由此可知,如果x是变量'x',则访问this.x
  • 如果x是常量1,则直接获取1
  • 如果x是布尔true,则直接获取true
  • 拓展:对属性,事件及{{}}中绑定值及执行的理解
 template:`<div :title="x" @click="x = 2">{{ document.location.href }}</div>`
 
 => code:
 render: "with(this){return _c('div',{attrs:{"title":x},on:{"click":function($event){x = 2}}},[_v(_s(document.location.href))])}"


// 总结:
// 1、所有属性在with(this) 作用域中获取,所以能取到this对应的值
// 2、对于事件,如果不是字符串,则外部会包裹一个函数function($event){}
// 3、对于Mustache 语法,首先_s():toString(),然后_v():createTextVNode()

// 从这边看,属性和内容,要写表达式很好理解,因为一个最终是属性,一个最终是实参
// 那事件的呢,好像是可以表达式的
// 其实,vue对于所有绑定值的地方,会校验是不是表达式
  function checkExpression (exp, text, warn, range) {
    try {
      new Function(("return " + exp));
    } catch (e) {// 略}
 }
// 目的:为了统一和模板的干净
// 那白名单又是怎么回事呢

// 因为this是代理了。执行render的时候,with(this) 会触发this的
var hasHandler = {
  has: function has (target, key) {
    var has = key in target;
    var isAllowed = allowedGlobals(key) ||
      (typeof key === 'string' && key.charAt(0) === '_' && !(key in target.$data));
    if (!has && !isAllowed) {
      if (key in target.$data) { warnReservedPrefix(target, key); }
      else { warnNonPresent(target, key); }
    }
    return has || !isAllowed
  }
};

// 在这里,会过滤掉一些名单,所谓白名单就是allowedGlobals
// 目前有:https://github.com/vuejs/vue/blob/v2.6.10/src/core/instance/proxy.js#L9
// 可以发现,白名单出现的就是一些原生js对象,不包括浏览器对象哦(保证代码可以在其它环境执行,所以环境变量写再模板里面不雅)
  • 总结:

  • 和真实值是怎么映射的,with(this)

  • 属性,事件,内容。执行时是什么样子?

  • :title="x" => ,{attrs:{"title":x} 即:在this中读取x
  • on:{"click":function(event){x = 2}} 即外面会包裹一个带event的函数
  • _v(_s(x))]) 即首先toString(),然后createTextVNode()
  • vue绑定值其实会经过是否是表达式检验,new Function(("return " + exp));所以,只有支持return 后面的语句才能通过
  • 白名单其实就是with触发proxy的has,进而约束一些对象是访问不到的。

其它

  • computed是依赖收集,watch是依赖添加