├── .gitignore ├── package.json ├── doc ├── the-first-commit-of-vue.md ├── an-simple-vue.md └── deep-path-support.md ├── readme.md └── src ├── the-super-tiny-vue.html └── the-super-tiny-vue.js /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "re-vue.js", 3 | "version": "0.0.1", 4 | "description": "rewrite vue", 5 | "main": "index.js", 6 | "directories": { 7 | "doc": "doc" 8 | }, 9 | "scripts": { 10 | "test": "echo \"Error: no test specified\" && exit 1" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/xiaofuzi/deep-in-vue.git" 15 | }, 16 | "keywords": [ 17 | "vue" 18 | ], 19 | "author": "yangxiaofu", 20 | "license": "ISC", 21 | "bugs": { 22 | "url": "https://github.com/xiaofuzi/deep-in-vue/issues" 23 | }, 24 | "homepage": "https://github.com/xiaofuzi/deep-in-vue#readme" 25 | } 26 | -------------------------------------------------------------------------------- /doc/the-first-commit-of-vue.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: the first commit of vue (or element) 3 | date: 2016-11-08 4 | --- 5 | 6 | 2013年7月28日,作者提交了第一个版本,那时命名为 element, 还不能真正的说是vue,但确是vue这个项目的起点。 7 | 8 | 首先来看一下项目的目录结构: 9 | 10 | * src(源码目录,包含一个main.js文件) 11 | * test(测试代码) 12 | * .gitignore 13 | * .jshintrc 14 | * component.json 15 | * Gruntfile.js 16 | * package.json 17 | 18 | 这个时候还没有什么实质性的代码,main.js中只有短短的一行`module.exports = 123`,但却包含了一个项目所必须的东西。 19 | 20 | * 版本控制(git/.gitignore) 21 | * 构建工具(Grunt) 22 | * 测试(mocha) 23 | * 代码质量检测(jshint) 24 | 25 | 版本控制和构建工具基本是现在项目的标配了,但是测试和代码质量检测通常会忽视,尤其是测试部门常被忽略,个人写业务代码时基本不会写测试的,但是作为开源的框架或是库,测试还是很有必要的。 26 | 27 | 一般都会觉得写测试过于麻烦,想着等项目稳定了在慢慢加测试,但其实测试是很有必要的,可以提前发现许多问题,避免许多不必要的bug. -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | ## vue成长系列文章 2 | 3 | 作为一个成功的开源项目,其成长历程是有许多值得我们借鉴和学习的。通过源码的学习,我们可以发现: 4 | 5 | Git-book: [https://xiaofuzi.gitbooks.io/rebuild-vuejs/content/](https://xiaofuzi.gitbooks.io/rebuild-vuejs/content/) 6 | 7 | * 作者的设计思路 8 | * 抛弃了的特性(不代表无价值) 9 | * 作者的编码习惯、编码细节 10 | * 完整的项目所具备的东西 11 | * 打磨自己的设计、架构能力 12 | * more and more 13 | 14 | [re-vue](https://github.com/xiaofuzi/re-vue),与该项目所对应的源码重写,尽量保证与vue源码一致,同时每一个版本会更新一个完整的功能。 15 | 16 | * [the first commit of vue](./doc/the-first-commit-of-vue.md) 17 | * [vue的雏形,小而全面](./doc/an-simple-vue.md) 18 | * [the-super-tiny-vue.js](./src/the-super-tiny-vue.js) --- [demo](http://yangxiaofu.com/deep-in-vue/src/the-super-tiny-vue.html) --- [es6版本源码](https://github.com/xiaofuzi/re-vue) 19 | * [谈谈get/set与getState/setState的优劣](https://github.com/xiaofuzi/deep-in-vue/issues/3) 20 | * [指令式声明的替代解决方案](https://github.com/xiaofuzi/deep-in-vue/issues/4) 21 | * [数组类型响应式实现原理分析](https://github.com/xiaofuzi/deep-in-vue/issues/5) 22 | * [深层次对象响应式实现](./doc/deep-path-support.md) 23 | * [支持watch监测、计算属性、自定义指令](https://github.com/xiaofuzi/deep-in-vue/issues/6) 24 | * [watch监测、计算属性实现原理](https://github.com/xiaofuzi/deep-in-vue/issues/7) 25 | * [数据驱动更新原理补充说明](https://github.com/xiaofuzi/deep-in-vue/issues/8) 26 | * [v-if/v-for指令实现原理](https://github.com/xiaofuzi/deep-in-vue/issues/9) 27 | * [组件系统实现原理](https://github.com/xiaofuzi/deep-in-vue/issues/10) 28 | 29 | -------------------------------------------------------------------------------- /src/the-super-tiny-vue.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | the super tiny vue.js 6 | 19 | 20 | 21 |
22 |

23 | 24 | 25 |

26 |
27 | 28 | 54 | 55 | -------------------------------------------------------------------------------- /doc/an-simple-vue.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: vue的雏形,小而全面 3 | date: 2016-11-09 4 | --- 5 | 6 | * 提交:a5e27b1174e9196dcc9dbb0becc487275ea2e84c 7 | * commit: naive implementation 8 | 9 | 这可以说是vue的第一份正式源码,已经有了基本的骨架原型。 10 | 11 | 源码主要包含三个文件: main.js、directives.js、filters.js 12 | 13 | ### 运行原理: 14 | 15 | * 节点收集,找到根节点以及根节点之下的所有包含指令的节点. 16 | * 解析指令节点 17 | * 关联数据与节点 18 | * 监测指令节点的set操作,并调用指令的更新函数 19 | 20 | 伪代码: 21 | 22 | ```js 23 | /*根节点以及所有指令节点获取,这里指令节点的selector采用属性选择器来选择*/ 24 | root = document.getElementById(opts.id), 25 | els = root.querySelectorAll(selector) 26 | 27 | /** 28 | * 节点处理 29 | */ 30 | ;[].forEach.call(els, processNode) 31 | processNode(root) 32 | 33 | /** 34 | * processNode中主要做了如下三步 35 | */ 36 | parseDirective() 37 | bindDirective() 38 | bindAccessors() 39 | ``` 40 | 这里需要注意一点是 scope 的概念,vue是采用数据响应式的思想,这里的数据即对应一个vue实例里的 scope(也可称它为作用域,最新版本已改为data/vueInstance.$data)。上面所说的绑定操作都是针对于这个scope来的。 41 | 42 | 如: 43 | ```js 44 | scope = { 45 | hello: 'ahahah' 46 | } 47 | ``` 48 | 则在模板中声明一个`v-text='hello'`指令的时候,即实现了v-text指令与scope.hello的绑定。 49 | 50 | 如上便是当前版本vue的基本运行原理。 51 | 52 | ### 作者设计思想解读 53 | 54 | * 通过指令的声明方式实现某一DOM片段与某一javascript对象的关联 55 | * 数值关联,JS中的一个 String 对应于DOM中的一个或多个 textNode 56 | * 函数关联,JS中的一个方法 对应于DOM节点的事件函数 57 | * set监测 58 | 59 | 通过如下示例来观看作者指令语法的设计思想: 60 | 61 | ### 模板 62 | 63 | ```html 64 |
65 |

66 |

YOYOYO

67 |

68 |

69 |
70 | ``` 71 | ### JS 72 | ```javascript 73 | var Seed = require('seed') 74 | var app = Seed.create({ 75 | id: 'test', 76 | // template 77 | scope: { 78 | msg: 'hello', 79 | hello: 'WHWHWHW', 80 | changeMessage: function () { 81 | app.scope.msg = 'hola' 82 | } 83 | } 84 | }) 85 | ``` 86 | 87 | 指令语句即 DOM 节点中的一个属性,如`sd-text="msg | capitalize"`,等号前面为指令的名称,等号后面为指令的值。 88 | 89 | 受于字符串所能表达信息量的限制,作者在指令名称上采用 '-' 让指令名变成结构类型的数据,以此来增加指令的灵活性(标签的属性是不区分大小写的,所以不能采用驼峰式的命名).值对应组件作用域中的一个键名,这里通过管道符可以扩展相应的功能。 90 | 91 | 实质上来说,与普通的变量声明方式是一样的: 92 | 93 | ```js 94 | /*javascript 变量声明*/ 95 | var text = 'ahahah'; 96 | var onClick = function changeMessage () {}; 97 | 98 | /*vue 指令声明, hello 和 changeMessage 则对应 scope 中的 hello 和 changeMessage的值*/ 99 | v-text='hello'; 100 | v-on-click='changeMessage'; 101 | ``` 102 | 103 | 注:组件对象数据与指令关联是一对多的关系 104 | 105 | 这样,一个微型的vue就成型了。 106 | 107 | 108 | 109 | 110 | 111 | -------------------------------------------------------------------------------- /doc/deep-path-support.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 嵌套对象响应式支持 3 | date: 2016-11-18 4 | --- 5 | 6 | 我们先来看如下一段模板: 7 | 8 | ```html 9 |
10 |

11 | 12 | 13 |

14 |

15 |
16 | ``` 17 | 18 | ```js 19 | var vm = new TinyVue({ 20 | el: 'app', 21 | data: { 22 | counter: 1, 23 | hello: 'ahahah!', 24 | isShow: true, 25 | info: { 26 | age: 18 27 | } 28 | }, 29 | methods: { 30 | add: function () { 31 | vm.counter += 1; 32 | vm.info.age += 1; 33 | } 34 | }, 35 | ready () { 36 | 37 | } 38 | }) 39 | ``` 40 | 41 | 这里需要注意的是,`info.age`,在框架内部绑定的时候,绑定的是 key 是 `info.age`, 但取值的时候是不能按`this['info.age']`的形式来取的,即需要考虑对象的层次问题。 42 | 43 | 44 | 所以对于这种深层次的key,我们需要单独的处理,代码如下: 45 | 46 | [代码出处](https://github.com/xiaofuzi/re-vue/blob/0c10af3987358985760cc8146b6e0e2f5adec02a/src/binding.js) 47 | ```js 48 | defineRective (obj, path) { 49 | let self = this, 50 | key = path[0]; 51 | 52 | if (path.length === 1) { 53 | def(obj, key, { 54 | get () { 55 | return self.value; 56 | }, 57 | set (value) { 58 | if (value !== self.value) { 59 | self.value = value; 60 | self.update(value); 61 | } 62 | } 63 | }); 64 | } else { 65 | let subObj = obj[key]; 66 | if (!subObj) { 67 | subObj = {}; 68 | 69 | def(obj, key, { 70 | get () { 71 | return subObj; 72 | }, 73 | set (value) { 74 | objectEach(value, (key)=>{ 75 | subObj[key] = value[key]; 76 | }); 77 | } 78 | }); 79 | } 80 | self.defineRective(subObj, path.slice(1)); 81 | } 82 | } 83 | ``` 84 | 85 | ```js 86 | //path 87 | let key = 'info.page' 88 | let path = key.split('.'); //['info', 'page'] 89 | ``` 90 | 91 | 这里根据path的长度来判断是单路径还是深层次路径,从而进行不同的处理。单层次路径的处理没什么特别的,重点看下深层次路径的处理。 92 | 93 | ```js 94 | let subObj = obj[key]; 95 | if (!subObj) { 96 | subObj = {}; 97 | 98 | def(obj, key, { 99 | get () { 100 | return subObj; 101 | }, 102 | set (value) { 103 | objectEach(value, (key)=>{ 104 | subObj[key] = value[key]; 105 | }); 106 | } 107 | }); 108 | } 109 | ``` 110 | 111 | 注意看这里的 set 操作,这里对中间赋值时对其所有子节点进行赋值操作,如果其子节点的子节点还是对象那么会一直将该操作传递下去,直到跟节点,从而触发根节点绑定的指令更新操作(根节点才可以绑定指令)。 112 | 113 | 如下所示: 114 | ```js 115 | //这里会正常的触发'info.page'所绑定的指令更新操作 116 | this.info.page += 1; 117 | 118 | /** 119 | * 这里我们直接给'info'赋值,如果没有我们之前的操作,那么'info.age'的更新 120 | * 操作是不会触发的,因为我们还没有对'info'进行监测,但当我们进行如上的转换后, 121 | * 对'info'进行监测,当'info'的赋值操作发生时,触发'info'的子属性的赋值操作, 122 | * 从而触发子属性的更新,所以进行如下的操作也实现了'info.age'的指令更新 123 | */ 124 | this.info = { 125 | age: 24 126 | } 127 | 128 | /** 129 | * 附加说明,存储绑定过程的对象如下所示 130 | this._bindings = { 131 | 'info.page': { 132 | value: 18, 133 | directives: [] 134 | } 135 | } 136 | */ 137 | ``` 138 | 139 | 如下为对深层次 key 的取值和赋值的辅助函数,这里直接给 Object 扩展了 $get 和 $set 函数, 从而可以实现如下操作: 140 | 141 | ```js 142 | let person = { 143 | info: { 144 | age: 18 145 | } 146 | } 147 | 148 | person['info.age'] = 24; 149 | console.log(person.info.age); //24 150 | ``` 151 | 152 | ```js 153 | /** 154 | * Object extend 155 | */ 156 | Object.prototype.$get = function (path='') { 157 | path = path.split('.'); 158 | if (path.length == 1) { 159 | return this[path[0]]; 160 | } else { 161 | this[path[0]] = this[path[0]] || {}; 162 | return this[path[0]].$get(path.slice(1).join('.')); 163 | } 164 | }; 165 | 166 | Object.prototype.$set = function (path='', value) { 167 | path = path.split('.'); 168 | if (path.length == 1) { 169 | this[path[0]] = value; 170 | } else { 171 | this[path[0]] = this[path[0]] || {}; 172 | this[path[0]].$set(path.slice(1).join('.'), value); 173 | } 174 | }; 175 | ``` 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | -------------------------------------------------------------------------------- /src/the-super-tiny-vue.js: -------------------------------------------------------------------------------- 1 | /** 2 | * the super tiny vue.js. 3 | * 代码总共200行左右(去掉注释) 4 | 5 | 简介:一个迷你vue库,虽然小但功能全面,可以作为想了解vue背后思想以及想学习vue源码而又不知如何入手的入门学习资料。 6 | 7 | 特性: 8 | * 数据响应式更新 9 | * 指令模板 10 | * MVVM 11 | * 轻量级 12 | 13 | ## 功能解读 14 | 15 | 16 |
17 |
18 | 19 | 20 |

21 |
22 |
23 |
24 | 25 | 38 | 39 | 如上为一段模板以及js脚本,我们所要实现的目标就是将 vm 实例与id为app的DOM节点关联起来,当更改vm data 的counter属性的时候, 40 | input的值和p标签的文本会响应式的改变,method中的add方法则和button的click事件绑定。 41 | 简单的说就是, 当点击button按钮的时候,触发button的点击事件回调函数add,在add方法中使counter加1,counter变化后模板中的input 42 | 和p标签会自动更新。vm与模板之间是如何关联的则是通过 v-model、v-on-click、v-text这样的指令声明的。 43 | 44 | ### 实现思路详解 45 | 46 | * 查找含指令的节点 47 | * 对查找所得的节点进行指令解析、指令所对应的实现与节点绑定、 节点指令值所对应的data属性与前一步关联的指令实现绑定、data属性值通过setter通知关联的指令进行更新操作 48 | * 含指令的每一个节点单独执行第二步 49 | * 绑定操作完成后,初始化vm实例属性值 50 | 51 | #### 指令节点查找 52 | 53 | 首先来看第一步,含指令节点的查找,因为指令声明是以属性的形式,所以可以通过属性选择器来进行查找,如下所示: 54 | 55 | `` 56 | 则可通过 querySelectorAll('[v-model]') 查找即可。 57 | 58 | root = this.$el = document.getElementById(opts.el), 59 | els = this.$els = root.querySelectorAll(getDirSelectors(Directives)) 60 | root对于根节点,els对应于模板内含指令的节点。 61 | 62 | #### 指令解析,绑定 63 | 64 | * 1.指令解析 65 | 同样以``为例,解析即得到 66 | var directive = { 67 | name: 'v-model', 68 | value: 'counter' 69 | } 70 | name对应指令名,value对应指令值。 71 | 72 | * 2.指令对应实现与当前节点的绑定(bindDirective) 73 | 指令实现可简单分为函数或是包含update函数的对象,如下便是`v-text`指令的实现代码: 74 | 75 | ```js 76 | text: function (el, value) { 77 | el.textContent = value || ''; 78 | } 79 | ``` 80 | 指令与节点的绑定即将该函数与节点绑定起来,即该函数负责该节点的更新操作,`v-text`的功能是更新文本值,所以如上所示 81 | 更改节点的textContent属性值。 82 | 83 | * 3. 响应式数据与节点的绑定(bindAccessors) 84 | 响应式数据这里拆分为 data 和 methods 对象,分别用来存储数据值和方法。 85 | ```js 86 | var vm = new Vue({ 87 | id: 'counter', 88 | data: { 89 | counter: 1 90 | }, 91 | methods: { 92 | add: function () { 93 | this.counter += 1; 94 | } 95 | } 96 | }) 97 | ``` 98 | 我们上面解析得到 v-model 对于的指令值为 counter,所以这里将data中的counter与当前节点绑定。 99 | 100 | 通过2、3两步实现了类型与 textDirective->el<-data.counter 的关联,当data.counter发生set(具体查看defineProperty set 用法)操作时, 101 | data.counter得知自己被改变了,所以通知el元素需要进行更新操作,el则使用与其关联的指令(textDirective)对自身进行更新操作,从而实现了数据的 102 | 响应式。 103 | 104 | * textDirective 105 | * el 106 | * data.counter 107 | 这三个是绑定的主体,数据发生更改,通知节点需要更新,节点通过指令更新自己。 108 | 109 | * 4.其它相关操作 110 | */ 111 | 112 | 113 | 114 | var prefix = 'v'; 115 | /** 116 | * Directives 117 | */ 118 | 119 | var Directives = { 120 | /** 121 | * 对应于 v-text 指令 122 | */ 123 | text: function (el, value) { 124 | el.textContent = value || ''; 125 | }, 126 | show: function (el, value) { 127 | el.style.display = value ? '' : 'none'; 128 | }, 129 | /** 130 | * 对应于 v-model 指令 131 | */ 132 | model: function (el, value, dirAgr, dir, vm, key) { 133 | let eventName = 'keyup'; 134 | el.value = value || ''; 135 | 136 | /** 137 | * 事件绑定控制 138 | */ 139 | if (el.handlers && el.handlers[eventName]) { 140 | el.removeEventListener(eventName, el.handlers[eventName]); 141 | } else { 142 | el.handlers = {}; 143 | } 144 | 145 | el.handlers[eventName] = function (e) { 146 | vm[key] = e.target.value; 147 | } 148 | 149 | el.addEventListener(eventName, el.handlers[eventName]); 150 | }, 151 | on: { 152 | update: function (el, handler, eventName, directive) { 153 | if (!directive.handlers) { 154 | directive.handlers = {} 155 | } 156 | 157 | var handlers = directive.handlers; 158 | 159 | if (handlers[eventName]) { 160 | //绑定新的事件前移除原绑定的事件函数 161 | el.removeEventListener(eventName, handlers[eventName]); 162 | } 163 | //绑定新的事件函数 164 | if (handler) { 165 | handler = handler.bind(el); 166 | el.addEventListener(eventName, handler); 167 | handlers[eventName] = handler; 168 | } 169 | } 170 | } 171 | } 172 | 173 | 174 | /** 175 | * MiniVue 176 | */ 177 | function TinyVue (opts) { 178 | /** 179 | * root/this.$el: 根节点 180 | * els: 指令节点 181 | * bindings: 指令与data关联的桥梁 182 | */ 183 | var self = this, 184 | root = this.$el = document.getElementById(opts.el), 185 | els = this.$els = root.querySelectorAll(getDirSelectors(Directives)), 186 | bindings = {}; 187 | this._bindings = bindings; 188 | 189 | /** 190 | * 指令处理 191 | */ 192 | [].forEach.call(els, processNode); 193 | processNode(root); 194 | 195 | /** 196 | * vm响应式数据初始化 197 | */ 198 | 199 | let _data = extend(opts.data, opts.methods); 200 | for (var key in bindings) { 201 | if (bindings.hasOwnProperty(key)) { 202 | self[key] = _data[key]; 203 | } 204 | } 205 | 206 | function processNode (el) { 207 | getAttributes(el.attributes).forEach(function (attr) { 208 | var directive = parseDirective(attr); 209 | if (directive) { 210 | bindDirective(self, el, bindings, directive); 211 | } 212 | }) 213 | } 214 | 215 | /** 216 | * ready 217 | */ 218 | if (opts.ready && typeof opts.ready == 'function') { 219 | this.ready = opts.ready; 220 | this.ready(); 221 | } 222 | } 223 | 224 | /************************************************************** 225 | * @privete 226 | * helper methods 227 | */ 228 | 229 | /** 230 | * 获取节点属性 231 | * 'v-text'='counter' => {name: v-text, value: 'counter'} 232 | */ 233 | function getAttributes (attributes) { 234 | return [].map.call(attributes, function (attr) { 235 | return { 236 | name: attr.name, 237 | value: attr.value 238 | } 239 | }) 240 | } 241 | 242 | /** 243 | * 返回指令选择器,便于指令节点的查找 244 | */ 245 | function getDirSelectors (directives) { 246 | /** 247 | * 支持的事件指令 248 | */ 249 | let eventArr = ['click', 'change', 'blur']; 250 | 251 | 252 | return Object.keys(directives).map(function (directive) { 253 | /** 254 | * text => 'v-text' 255 | */ 256 | return '[' + prefix + '-' + directive + ']'; 257 | }).join() + ',' + eventArr.map(function (eventName) { 258 | return '[' + prefix + '-on-' + eventName + ']'; 259 | }).join(); 260 | } 261 | 262 | /** 263 | * 节点指令绑定 264 | */ 265 | function bindDirective (vm, el, bindings, directive) { 266 | //从节点属性中移除指令声明 267 | el.removeAttribute(directive.attr.name); 268 | 269 | /** 270 | * v-text='counter' 271 | * v-model='counter' 272 | * data = { 273 | counter: 1 274 | } 275 | * 这里的 counter 即指令的 key 276 | */ 277 | var key = directive.key, 278 | binding = bindings[key]; 279 | 280 | if (!binding) { 281 | /** 282 | * value 即 counter 对应的值 283 | * directives 即 key 所绑定的相关指令 284 | 如: 285 | bindings['counter'] = { 286 | value: 1, 287 | directives: [textDirective, modelDirective] 288 | } 289 | */ 290 | bindings[key] = binding = { 291 | value: '', 292 | directives: [] 293 | } 294 | } 295 | directive.el = el; 296 | binding.directives.push(directive); 297 | 298 | //避免重复定义 299 | if (!vm.hasOwnProperty(key)) { 300 | /** 301 | * get/set 操作绑定 302 | */ 303 | bindAccessors(vm, key, binding); 304 | } 305 | } 306 | 307 | /** 308 | * get/set 绑定指令更新操作 309 | */ 310 | function bindAccessors (vm, key, binding) { 311 | Object.defineProperty(vm, key, { 312 | get: function () { 313 | return binding.value; 314 | }, 315 | set: function (value) { 316 | binding.value = value; 317 | binding.directives.forEach(function (directive) { 318 | directive.update( 319 | directive.el, 320 | value, 321 | directive.argument, 322 | directive, 323 | vm, 324 | key 325 | ) 326 | }) 327 | } 328 | }) 329 | } 330 | 331 | function parseDirective (attr) { 332 | if (attr.name.indexOf(prefix) === -1) return ; 333 | 334 | /** 335 | * 指令解析 336 | v-on-click='onClick' 337 | 这里的指令名称为 'on', 'click'为指令的参数,onClick 为key 338 | */ 339 | 340 | //移除 'v-' 前缀, 提取指令名称、指令参数 341 | var directiveStr = attr.name.slice(prefix.length + 1), 342 | argIndex = directiveStr.indexOf('-'), 343 | directiveName = argIndex === -1 344 | ? directiveStr 345 | : directiveStr.slice(0, argIndex), 346 | directiveDef = Directives[directiveName], 347 | arg = argIndex === -1 348 | ? null 349 | : directiveStr.slice(argIndex + 1); 350 | 351 | /** 352 | * 指令表达式解析,即 v-text='counter' counter的解析 353 | * 这里暂时只考虑包含key的情况 354 | */ 355 | var key = attr.value; 356 | return directiveDef 357 | ? { 358 | attr: attr, 359 | key: key, 360 | dirname: directiveName, 361 | definition: directiveDef, 362 | argument: arg, 363 | /** 364 | * 指令本身是一个函数的情况下,更新函数即它本身,否则调用它的update方法 365 | */ 366 | update: typeof directiveDef === 'function' 367 | ? directiveDef 368 | : directiveDef.update 369 | } 370 | : null; 371 | } 372 | 373 | /** 374 | * 对象合并 375 | */ 376 | function extend (child, parent) { 377 | parent = parent || {}; 378 | child = child || {}; 379 | 380 | for(var key in parent) { 381 | if (parent.hasOwnProperty(key)) { 382 | child[key] = parent[key]; 383 | } 384 | } 385 | 386 | return child; 387 | } 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | --------------------------------------------------------------------------------