浅析Vue原理(部分源码解析)
响应式
Object.defineProperty
Object.defineProperty(obj, prop, descriptor) // 对象、属性、描述符
Object.defineProperty是es5新加的给对象属性设置描述符的方法,可以用来监听属性值的变化
var obj ={}; var _name ='张三' Object.defineProperty(obj,'name',{ get:function () { return _name; }, set:function (value) { _name=value; } })
调用方式:
obj.name ="里斯"; alert(obj.name);
模拟Vue响应式(data的属性代理到vm上)
var vm= {}; var data= { items: [ { message: 'Foo' }, { message: 'Bar' } ] }; var i; for (i in data){ if(data.hasOwnProperty(i)){ (function(i){ // 独立函数作用域 Object.defineProperty(vm,i,{ //将data对象的属性代理到vm get: function () { return data[i]; }, set:function (newVal) { data[i]=newVal; } }) }(i)) } } vm.items[0].message='张三' console.log(vm.items);
模板解析
- 本质:字符串
- 有逻辑: 例如v-if、v-for 等
- html模版是静态的,Vue模板是动态
- 模板必须转换成JS函数(render函数),进而转换成html渲染页面
with
//模板 function UserInfo() { this.name = "kobe bryant"; this.age = "28"; this.gender = "boy"; } var people=new UserInfo(); function fn(){ with(people) { var str = "姓名: " + name + "\n"; str += "年龄:" + age + "\n"; str += "性别:" + gender; alert(str); } } fn();
render 函数
<div class="main" :class="bindClass"> <div>{{text}}</div> <div>hello world</div> <div v-for="(item, index) in arr"> <p>{{item.name}}</p> <p>{{item.value}}</p> <p>{{index}}</p> <p>---</p> </div> <div v-if="text"> {{text}} </div> <div v-else></div> </div>
Vue 源码将HTML string 转换成AST
模版转换成js
with(this){ return _c( 'div', { /*static class*/ staticClass:"main", /*bind class*/ class:bindClass }, [ _c( 'div', [_v(_s(text))]), _c('div',[_v("hello world")]), /*这是一个v-for循环*/ _l( (arr), function(item,index){ return _c( 'div', //_c创建标签 [_c('p',[_v(_s(item.name))]), //_v 创建文本; _s 转换成字符串 _c('p',[_v(_s(item.value))]), _c('p',[_v(_s(index))]), _c('p',[_v("---")])] ) } ), /*这是v-if*/ (text)?_c('div',[_v(_s(text))]):_c('div',[_v("no text")])], 2 ) }
其中,this 即使Vue构造函数对象(假定为vm),item即this.item,也是data中的item
针对于上一篇的随笔:浅谈Jquery和常用框架Vue变化,我们来解析一下它的render模板
<div id="example-1"> <input v-model="title" /> <button v-on:click="add">udto list</button> <ul> <li v-for="item in items"> {{ item.message }} </li> </ul> </div>
模板render解析
with (this) { // noinspection JSAnnotator return _c('div', {attrs: {"id": "example-1"}}, [ //div _c('input', { //input directives: [{ name: "model", rawName: "v-model", value: (title), expression: "title" }], domProps: {"value": (title)}, //model 往 view on: { "input": function ($event) { if ($event.target.composing) return; title = $event.target.value //view 往 model } } }), _v(" "), //换行 _c('button', { on: { "click": add //绑定 methods add } }, [ _v("udto list") //文本子节点 ] ), _v(" "), //换行 _c('ul', _l((items), function (item) { // <li v-for="item in items"> _l v-for return _c('li', [ _v("\n " + _s(item.message) + "\n ") //item 对应 vm.item 即 data中的item ] ) })) ]) }
渲染
从上面例子可以看出,vue通过借鉴改造snabbdom,h函数返回的vNode,vm._c返回的也是vNode,从Vue源码中也验证了这一点,从下面Vue源码看出Vue是通过updateComponent 完成render渲染
var prevVnode = vm._vnode; //旧的vnode,initial render 第一次渲染 vnode 没有存在,装载在容器中,全部渲染
// updates
vm.$el = vm.__patch__(prevVnode, vnode); //第二次 新旧vNode对比
Vue 整个工作流程
- 将模板解析成Js,即render函数
- 监听模板,通过MVVM响应式绑定model,并完成监听
- render函数渲染成Virtual Node
- 初次渲染完成DOM节点的创建,再次渲染新旧Vittual Node对比