├── .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 |
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 |
--------------------------------------------------------------------------------