├── .babelrc
├── .gitignore
├── LICENSE
├── README.md
├── build
└── index.js
├── dist
├── MiniVue.es5.js
├── MiniVue.js
└── MiniVue.min.js
├── image
├── 1.png
├── 2.png
└── files.png
├── package.json
├── src
├── Compiler
│ ├── index.js
│ └── vnode.js
├── Instance
│ ├── element.js
│ ├── index.js
│ └── node.js
├── Observer
│ ├── dep.js
│ ├── index.js
│ └── watcher.js
└── Util
│ ├── debug.js
│ ├── index.js
│ └── regExp.js
└── test
└── index.html
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | "@babel/preset-env"
4 | ]
5 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules/
3 | npm-debug.log*
4 | yarn-debug.log*
5 | yarn-error.log*
6 |
7 | # Editor directories and files
8 | .idea
9 | .vscode
10 | *.suo
11 | *.ntvs*
12 | *.njsproj
13 | *.sln
14 | package-lock.json
15 | mock/
16 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 xiaoai
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # miniVue
2 |
3 | mini 版本 Vue,实现了 Vue 最核心部分代码,该项目是为了对 Vue 有更加深入了解,根据对源码剖析和自己的理解模拟 Vue 写的 mini 版本的 Vue
4 | > 目前没有实现diff算法,每次都是强制更新dom
5 | ### 实例
6 |
7 | ```html
8 |
9 |
10 |
11 |
15 | ```
16 |
17 | ```javascript
18 | new MiniVue({
19 | el: '#app',
20 | data() {
21 | return {
22 | a: 1,
23 | color: '#999',
24 | classFn: 'bb'
25 | };
26 | },
27 | computed: {
28 | styleFn() {
29 | return { color: this.color };
30 | }
31 | },
32 | methods: {
33 | clickFn() {
34 | console.log('clickFn');
35 | this.a = this.a === 33333 ? 2222 : 33333;
36 | this.color = this.color === 'red' ? '#000' : 'red';
37 | }
38 | },
39 | created() {
40 | console.log('created');
41 | },
42 | beforeMount() {
43 | console.log('beforeMount');
44 | },
45 | beforeUpdate() {
46 | console.log('beforeUpdate');
47 | },
48 | mounted() {
49 | console.log('mounted');
50 | setTimeout(() => {
51 | this.a = 2222;
52 | }, 1000 * 3);
53 | }
54 | });
55 | ```
56 |
57 | ### 文件目录
58 |
59 | 
60 |
61 | Comilper--->主要是处理`dom`编译
62 |
63 | Instance--->程序入口和创建`dom`
64 |
65 | Observer--->数据拦截,数据订阅,发布,更新
66 |
67 | Util--->程序工具类(debug,nextTick...)
68 |
69 | ### 深入响应式原理
70 |
71 | 先看一张`Vue`官方文档中的图
72 |
73 | 
74 |
75 | 当你把一个普通的 JavaScript 对象传给 Vue 实例的 data 选项,Vue 将遍历此对象所有的属性,并使用 Object.defineProperty 把这些属性全部转为 getter/setter。Object.defineProperty 是 ES5 中一个无法 shim 的特性,这也就是为什么 Vue 不支持 IE8 以及更低版本浏览器。
76 |
77 | 这些 getter/setter 对用户来说是不可见的,但是在内部它们让 Vue 追踪依赖,在属性被访问和修改时通知变化。这里需要注意的问题是浏览器控制台在打印数据对象时 getter/setter 的格式化并不同,所以你可能需要安装 vue-devtools 来获取更加友好的检查接口。
78 |
79 | 每个组件实例都有相应的 watcher 实例对象,它会在组件渲染的过程中把属性记录为依赖,之后当依赖项的 setter 被调用时,会通知 watcher 重新计算,从而致使它关联的组件得以更新
80 |
81 | ### MiniVue 响应式原理
82 |
83 | MiniVue 是对 Vue 进行 深度学习得等的 产品,大部分代码都是来源`Vue`源码, 在`MiniVue`中使用了`Object.defineProperty()`进行数据劫持,来监听`setter`和`getter`
84 |
85 | - 数据初始化劫持
86 |
87 | ```javascript
88 | /**
89 | * 初始化data对象数据,收集数据添加监听
90 | */
91 | _initData() {
92 | let vm = this;
93 | let data = this.$options.data;
94 | // data支持两种写法(函数和对象)
95 | // 如果data是函数就直接执行拿到返回值,如果是对象直接返回
96 | data = vm._data = typeof data === 'function' ? data.call(vm) : data || {};
97 |
98 | const keys = Object.keys(data);
99 | let i = keys.length;
100 | while (i--) {
101 | let key = keys[i];
102 | this.proxy(vm, '_data', key);
103 | }
104 | observe(data, vm);
105 | }
106 | ```
107 |
108 | - 核心代码
109 |
110 | ```javascript
111 | Object.defineProperty(obj, key, {
112 | // 可枚举
113 | enumerable: true,
114 | configurable: true,
115 | get: function reactiveGetter() {
116 | const value = getter ? getter.call(obj) : val;
117 | // 依赖收集
118 | if (Dep.target) {
119 | dep.depend();
120 | }
121 | return value;
122 | },
123 | set: function reactiveSetter(newVal) {
124 | const value = getter ? getter.call(obj) : val;
125 | if (newVal === value || (newVal !== newVal && value !== value)) {
126 | return;
127 | }
128 | // 更新值
129 | if (setter) {
130 | setter.call(obj, newVal);
131 | } else {
132 | val = newVal;
133 | }
134 | // 新的值是object的话,进行监听
135 | childObj = observe(newVal);
136 | // 通知所有订阅者进行视图更新
137 | dep.notify();
138 | }
139 | });
140 | ```
141 |
142 | 这样我们已经可以监听每个数据的变化了,那么监听到变化之后就通知订阅者,所以接下来我们需要实现一个消息订阅器,维护一个数组,用来收集订阅者,数据变动触发`notify`,再调用订阅者的`update`方法
143 |
144 | ```javascript
145 | /**
146 | * 订阅者Dep
147 | * 主要作用是用来存放Watcher观察者对象
148 | */
149 | export default class Dep {
150 | constructor() {
151 | // 标示id防止添加重复观察者对象
152 | this.id = uid++;
153 | // 存储观察者对象
154 | this.subs = [];
155 | }
156 | /**
157 | * 添加观察者
158 | * @param {Watcher对象} sub
159 | */
160 | addSub(sub) {
161 | this.subs.push(sub);
162 | }
163 | /**
164 | * 通知所有订阅者
165 | * update方法是挂载在Watcher原型对象上面的,方法内部会把需要的更新数据push到异步队列中,等到数据所有操作完成在进行视图更新
166 | */
167 | notify() {
168 | // 拷贝观察者对象
169 | const subs = this.subs.slice();
170 | // 循环所有观察者进行更新操作
171 | subs.map(item => {
172 | item.update();
173 | return item;
174 | });
175 | }
176 | }
177 | ```
178 |
179 | 总结: 响应式原理 ---> 初始化会获取`data`里面的数据,进行数据劫持使用`Object.defineProperty`进行数据劫持,对`data`里面的每一条数据进行`setter`和`getter`,当有获取数据触发`getter`进行依赖收集,当有数据发生改变触发`setter`更新数据,并且通知订阅者触发更新
180 |
181 | ### MiniVue AST
182 |
183 | 内部通过遍历 html 文档树,通过正则匹配转换成`AST`树
184 |
185 | ```javascript
186 | /**
187 | * html转为Ast
188 | * @returns Object AST语法树
189 | */
190 | _convertHtml2Ast() {
191 | while (this.template) {
192 | let textEnd = this.template.indexOf('<');
193 |
194 | if (textEnd === 0) {
195 | // 如果是注释标签直接跳过编译
196 | if (regExp.comment.test(this.template)) {
197 | let commentEnd = this.template.indexOf('-->');
198 | this._advance(commentEnd + 3);
199 | continue;
200 | }
201 |
202 | // 匹配结束标签
203 | let endTagMatch = this.template.match(regExp.endTag);
204 | if (endTagMatch) {
205 | let _index = this.index;
206 | this._advance(endTagMatch[0].length);
207 | this._parseEndTag(endTagMatch[1], _index, this.index);
208 | continue;
209 | }
210 |
211 | // 匹配开始标签
212 | let startTagMatch = this._parseStartTag();
213 | if (startTagMatch) {
214 | this._handleStartTag(startTagMatch);
215 | continue;
216 | }
217 | }
218 | }
219 | }
220 | ```
221 |
222 | ```javascript
223 | for (var i = 0, l = attrs.length; i < l; i++) {
224 | map[attrs[i].name] = attrs[i].value;
225 | /**
226 | * 1.匹配 @ 符号表示是绑定事件
227 | * 2.匹配 :class :style 表达式class和表达式style
228 | * 3.匹配 class style 静态class和静态style
229 | * 4.普通数据(如: id)
230 | */
231 | if (attrs[i].name.match(/^@/g)) {
232 | isEvent = true;
233 | event[attrs[i].name.match(/\w*$/)[0]] = { value: attrs[i].value };
234 | } else if (class2styleReg.test(attrs[i].name)) {
235 | attrs[i].name.indexOf('class') > -1 ? (staticClass = attrs[i].value) : (staticStyle = attrs[i].value);
236 | } else if (class2styleExpReg.test(attrs[i].name)) {
237 | attrs[i].name.indexOf(':class') > -1 ? (classBinding = attrs[i].value) : (styleBinding = attrs[i].value);
238 | } else if (attrs[i].name === 'v-model') {
239 | isEvent = true;
240 | event['input'] = { value: `function($event){if($event.target.composing)return;${attrs[i].value}=$event.target.value}` };
241 |
242 | props.push({
243 | name: 'value',
244 | value: `(${attrs[i].value})`
245 | });
246 |
247 | directives.push({
248 | arg: null,
249 | modifiers: undefined,
250 | name: 'model',
251 | rawName: 'v-model',
252 | value: attrs[i].value
253 | });
254 | } else {
255 | _attrs.push({
256 | name: attrs[i].name,
257 | value: attrs[i].value
258 | });
259 | }
260 | }
261 | // 默认根ast数据结构
262 | var astMap = {
263 | type: 1,
264 | tag: startTagMatch.tagName,
265 | attrsList: attrs,
266 | attrsMap: map,
267 | parent: parent,
268 | children: []
269 | };
270 | ```
271 |
272 | 通过正则匹配把 Html 转为为 AST 树
273 |
274 | 结构如下:
275 |
276 | 
277 |
278 | ### MiniVue VNode
279 |
280 | VNode(虚拟Dom)
281 | 可以把真实 DOM 树抽象成一棵以 JavaScript 对象构成的抽象树,在修改抽象树数据后将抽象树转化成真实 DOM 重绘到页面上呢?于是虚拟 DOM 出现了,它是真实 DOM 的一层抽象,用属性描述真实 DOM 的各个特性。当它发生变化的时候,就会去修改视图。
282 |
283 | 可以想象,最简单粗暴的方法就是将整个 DOM 结构用 innerHTML 修改到页面上,但是这样进行重绘整个视图层是相当消耗性能的,我们是不是可以每次只更新它的修改呢?所以 Vue.js 将 DOM 抽象成一个以 JavaScript 对象为节点的虚拟 DOM 树,以 VNode 节点模拟真实 DOM,可以对这颗抽象树进行创建节点、删除节点以及修改节点等操作,在这过程中都不需要操作真实 DOM,只需要操作 JavaScript 对象后只对差异修改,相对于整块的 innerHTML 的粗暴式修改,大大提升了性能。修改以后经过 diff 算法得出一些需要修改的最小单位,再将这些小单位的视图进行更新。这样做减少了很多不需要的 DOM 操作,大大提高了性能。
284 |
285 | Vue 就使用了这样的抽象节点 VNode,它是对真实 DOM 的一层抽象,而不依赖某个平台,它可以是浏览器平台,也可以是 weex,甚至是 node 平台也可以对这样一棵抽象 DOM 树进行创建删除修改等操作,这也为前后端同构提供了可能
286 |
287 | ```javascript
288 | export default class VNode {
289 | constructor(tag, data, children, text, elm, context, componentOptions, asyncFactory) {
290 | /*当前节点的标签名*/
291 | this.tag = tag;
292 | /*当前节点对应的对象,包含了具体的一些数据信息,是一个VNodeData类型,可以参考VNodeData类型中的数据信息*/
293 | this.data = data;
294 | /*当前节点的子节点,是一个数组*/
295 | this.children = children;
296 | /*当前节点的文本*/
297 | this.text = text;
298 | /*当前虚拟节点对应的真实dom节点*/
299 | this.elm = elm;
300 | /*当前节点的名字空间*/
301 | this.ns = undefined;
302 | /*编译作用域*/
303 | this.context = context;
304 | /*函数化组件作用域*/
305 | this.functionalContext = undefined;
306 | /*节点的key属性,被当作节点的标志,用以优化*/
307 | this.key = data && data.key;
308 | /*组件的option选项*/
309 | this.componentOptions = componentOptions;
310 | /*当前节点对应的组件的实例*/
311 | this.componentInstance = undefined;
312 | /*当前节点的父节点*/
313 | this.parent = undefined;
314 | /*简而言之就是是否为原生HTML或只是普通文本,innerHTML的时候为true,textContent的时候为false*/
315 | this.raw = false;
316 | /*静态节点标志*/
317 | this.isStatic = false;
318 | /*是否作为跟节点插入*/
319 | this.isRootInsert = true;
320 | /*是否为注释节点*/
321 | this.isComment = false;
322 | /*是否为克隆节点*/
323 | this.isCloned = false;
324 | /*是否有v-once指令*/
325 | this.isOnce = false;
326 | }
327 | child() {
328 | return this.componentInstance;
329 | }
330 | }
331 | ```
332 |
333 | 会根据生成的`AST`树转换成 render 函数,render 函数创建每个节点 VNode
334 |
335 | - render 函数
336 |
337 | ```javascript
338 | "with(this){return _c('div',{staticClass:"aa bb",class:classFn,staticStyle:{"width":"200px","height":"50px"},style:(styleFn),attrs:{"id":"app"}},[_c('input',{directives:[{name:"model",rawName:"v-model",value:(testData),expression:"testData"}],attrs:{"type":"text","name":""},domProps:{"value":(testData)},on:{"input":function($event){if($event.target.composing)return;testData=$event.target.value}}}),_v(" "),_c('button',{on:{"click":update}},[_v("update")])])}
339 | ```
340 |
341 | \_c --->创建标签
342 |
343 | \_v --->创建文本节点
344 |
345 | \_s --->字符串序列化
346 |
347 | \_c:
348 |
349 | ```javascript
350 | /**
351 | * 创建元素
352 | * @param {Object} context miniVue实例
353 | * @param {String} tag 标签
354 | * @param {Object} data 数据
355 | * @param {Array} children 子节点
356 | */
357 | export function createElement(context, tag, data, children) {
358 | var vnode;
359 |
360 | if (!tag) {
361 | createEmptyVNode();
362 | }
363 | // 兼容不传data的情况, 处理{{a}} 这种dom情况,字符串function为: _c('span', [_v(_s(a))])
364 | if (Array.isArray(data)) {
365 | children = data;
366 | data = undefined;
367 | }
368 |
369 | if (typeof tag === 'string') {
370 | vnode = new VNode(tag, data, children, undefined, undefined, context);
371 | }
372 |
373 | if (vnode !== undefined) {
374 | return vnode;
375 | }
376 | }
377 | ```
378 |
379 | \_v:
380 |
381 | ```javascript
382 | /**
383 | * 创建文本节点
384 | */
385 | export function createTextVNode(val) {
386 | return new VNode(undefined, undefined, undefined, String(val));
387 | }
388 | ```
389 |
390 | ### 最后
391 |
392 | 
393 |
--------------------------------------------------------------------------------
/build/index.js:
--------------------------------------------------------------------------------
1 | const rollup = require('rollup');
2 | const resolve = require('rollup-plugin-node-resolve');
3 | const json = require('rollup-plugin-json');
4 | const babel = require('rollup-plugin-babel');
5 | const uglify = require('uglify-js');
6 | const fs = require('fs');
7 | const path = require('path');
8 |
9 | const builds = [
10 | {
11 | outputOptions: {
12 | file: './dist/MiniVue.min.js',
13 | format: 'umd',
14 | name: 'MiniVue.min'
15 | },
16 | inputOptions: {
17 | input: './src/Instance/index.js',
18 | plugins: [
19 | resolve(),
20 | json(),
21 | babel({
22 | exclude: 'node_modules/**'
23 | })
24 | ]
25 | }
26 | },
27 | {
28 | outputOptions: {
29 | file: './dist/MiniVue.js',
30 | format: 'umd',
31 | name: 'MiniVue'
32 | },
33 | inputOptions: {
34 | input: './src/Instance/index.js',
35 | plugins: [resolve(), json()]
36 | }
37 | },
38 | {
39 | outputOptions: {
40 | file: './dist/MiniVue.es5.js',
41 | format: 'umd',
42 | name: 'MiniVue.es5'
43 | },
44 | inputOptions: {
45 | input: './src/Instance/index.js',
46 | plugins: [
47 | resolve(),
48 | json(),
49 | babel({
50 | exclude: 'node_modules/**'
51 | })
52 | ]
53 | }
54 | }
55 | ];
56 | async function build(builds) {
57 | for (let i = 0, len = builds.length; i < len; i++) {
58 | // create a bundle
59 | const bundle = await rollup.rollup(builds[i].inputOptions);
60 |
61 | // generate code and a sourcemap
62 | const { code, map } = await bundle.generate(builds[i].outputOptions);
63 | let _codes = code;
64 | if (builds[i].outputOptions.name.indexOf('.min') > -1) {
65 | _codes = uglify.minify(code).code;
66 | }
67 | fs.writeFile(path.resolve(__dirname, '../', `dist/${builds[i].outputOptions.name}.js`), _codes, err => {
68 | if (err) {
69 | console.log(err);
70 | }
71 | });
72 | }
73 | }
74 |
75 | build(builds);
76 |
--------------------------------------------------------------------------------
/dist/MiniVue.es5.js:
--------------------------------------------------------------------------------
1 | (function (global, factory) {
2 | typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
3 | typeof define === 'function' && define.amd ? define(factory) :
4 | (global.MiniVue = global.MiniVue || {}, global.MiniVue.es5 = factory());
5 | }(this, (function () { 'use strict';
6 |
7 | function _typeof(obj) {
8 | if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") {
9 | _typeof = function (obj) {
10 | return typeof obj;
11 | };
12 | } else {
13 | _typeof = function (obj) {
14 | return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
15 | };
16 | }
17 |
18 | return _typeof(obj);
19 | }
20 |
21 | function _classCallCheck(instance, Constructor) {
22 | if (!(instance instanceof Constructor)) {
23 | throw new TypeError("Cannot call a class as a function");
24 | }
25 | }
26 |
27 | function _defineProperties(target, props) {
28 | for (var i = 0; i < props.length; i++) {
29 | var descriptor = props[i];
30 | descriptor.enumerable = descriptor.enumerable || false;
31 | descriptor.configurable = true;
32 | if ("value" in descriptor) descriptor.writable = true;
33 | Object.defineProperty(target, descriptor.key, descriptor);
34 | }
35 | }
36 |
37 | function _createClass(Constructor, protoProps, staticProps) {
38 | if (protoProps) _defineProperties(Constructor.prototype, protoProps);
39 | if (staticProps) _defineProperties(Constructor, staticProps);
40 | return Constructor;
41 | }
42 |
43 | /*
44 | * @Author: xiaoai
45 | * @Date: 2018-11-15 20:18:05
46 | * @LastEditors: xiaoai
47 | * @LastEditTime: 2018-11-16 11:57:20
48 | * @Description: Whatever is worth doing is worth doing well(任何值得做的事就值得把它做好)
49 | */
50 | var _console = window.console;
51 | function debug(type, msg) {
52 | _console[type].call(_console, msg);
53 | }
54 |
55 | /*
56 | * @Author: xiaoai
57 | * @Date: 2018-11-15 19:58:29
58 | * @LastEditors: xiaoai
59 | * @LastEditTime: 2018-12-06 19:34:55
60 | * @Description: Whatever is worth doing is worth doing well(任何值得做的事就值得把它做好)
61 | */
62 | var noop$1 = function noop(a, b, c) {};
63 | var callHook = function callHook(vm, hook) {
64 | var handlers = vm.$options[hook];
65 |
66 | if (handlers) {
67 | handlers.call(vm);
68 | }
69 | };
70 | var debug$1 = debug;
71 |
72 | /*
73 | * @Author: xiaoai
74 | * @Date: 2018-11-15 16:03:18
75 | * @LastEditors: xiaoai
76 | * @LastEditTime: 2018-11-16 11:59:29
77 | * @Description: Whatever is worth doing is worth doing well(任何值得做的事就值得把它做好)
78 | */
79 | var uid = 0;
80 | /**
81 | * 订阅者Dep
82 | * 主要作用是用来存放Watcher观察者对象
83 | */
84 |
85 | var Dep =
86 | /*#__PURE__*/
87 | function () {
88 | function Dep() {
89 | _classCallCheck(this, Dep);
90 |
91 | // 标示id防止添加重复观察者对象
92 | this.id = uid++; // 存储观察者对象
93 |
94 | this.subs = [];
95 | }
96 | /**
97 | * 添加观察者
98 | * @param {Watcher对象} sub
99 | */
100 |
101 |
102 | _createClass(Dep, [{
103 | key: "addSub",
104 | value: function addSub(sub) {
105 | this.subs.push(sub);
106 | }
107 | /**
108 | * 删除观察者
109 | * @param {Watcher对象} sub
110 | */
111 |
112 | }, {
113 | key: "removeSub",
114 | value: function removeSub(sub) {
115 | var _ref = [0, this.subs.length],
116 | i = _ref[0],
117 | len = _ref[1];
118 |
119 | for (; i < len; i++) {
120 | if (this.subs[i].id === sub.id) {
121 | this.subs.splice(i, 1);
122 | break;
123 | }
124 | }
125 | }
126 | /**
127 | * 依赖收集,当存在Dep.target的时候添加观察者对象
128 | * addDep方法是挂载在Watcher原型对象上面的,方法内部会调用Dep实例上面的addSub方法
129 | */
130 |
131 | }, {
132 | key: "depend",
133 | value: function depend() {
134 | if (Dep.target) {
135 | Dep.target.addDep(this);
136 | }
137 | }
138 | /**
139 | * 通知所有订阅者
140 | * update方法是挂载在Watcher原型对象上面的,方法内部会把需要的更新数据push到异步队列中,等到数据所有操作完成在进行视图更新
141 | */
142 |
143 | }, {
144 | key: "notify",
145 | value: function notify() {
146 | // 拷贝观察者对象
147 | var subs = this.subs.slice(); // 循环所有观察者进行更新操作
148 |
149 | subs.map(function (item) {
150 | item.update();
151 | return item;
152 | });
153 | }
154 | }]);
155 |
156 | return Dep;
157 | }(); // 依赖收集完需要将Dep.target设为null,防止后面重复添加依赖
158 | Dep.target = null;
159 | var targetStack = [];
160 | function pushTarget(_target) {
161 | if (Dep.target) {
162 | targetStack.push(Dep.target);
163 | }
164 |
165 | Dep.target = _target;
166 | }
167 | function popTarget() {
168 | Dep.target = targetStack.pop();
169 | }
170 |
171 | var sharedPropertyDefinition = {
172 | enumerable: true,
173 | configurable: true,
174 | get: function get() {},
175 | set: function set() {}
176 | };
177 | function defineReactive(obj, key, val) {
178 | // 实例订阅者对象
179 | var dep = new Dep(); // 获取对象上面的描述
180 |
181 | var property = Object.getOwnPropertyDescriptor(obj, key);
182 |
183 | if (property && property.configurable === false) {
184 | return;
185 | }
186 |
187 | var getter = property && property.get;
188 | var setter = property && property.set;
189 |
190 | if ((!getter || setter) && arguments.length === 2) {
191 | val = obj[key];
192 | }
193 |
194 | var childObj = observe(val);
195 | Object.defineProperty(obj, key, {
196 | // 可枚举
197 | enumerable: true,
198 | configurable: true,
199 | get: function reactiveGetter() {
200 | var value = getter ? getter.call(obj) : val; // 依赖收集
201 |
202 | if (Dep.target) {
203 | dep.depend();
204 | }
205 |
206 | return value;
207 | },
208 | set: function reactiveSetter(newVal) {
209 | var value = getter ? getter.call(obj) : val;
210 |
211 | if (newVal === value || newVal !== newVal && value !== value) {
212 | return;
213 | } // 更新值
214 |
215 |
216 | if (setter) {
217 | setter.call(obj, newVal);
218 | } else {
219 | val = newVal;
220 | } // 新的值是object的话,进行监听
221 |
222 |
223 | childObj = observe(newVal); // 通知所有订阅者进行视图更新
224 |
225 | dep.notify();
226 | }
227 | });
228 | } // 把computed的属性挂载到minivue实例上
229 |
230 | function defineComputed(target, key, userDef) {
231 | if (typeof userDef === 'function') {
232 | sharedPropertyDefinition.get = createComputedGetter(key);
233 |
234 | sharedPropertyDefinition.set = function () {};
235 | }
236 |
237 | Object.defineProperty(target, key, sharedPropertyDefinition);
238 | }
239 | function createComputedGetter(key) {
240 | return function computedGetter() {
241 | var watcher = this._computedWatchers && this._computedWatchers[key];
242 |
243 | if (watcher) {
244 | // if (watcher.dirty) {
245 | watcher.get(); // }
246 |
247 | if (Dep.target) {
248 | watcher.depend();
249 | }
250 |
251 | return watcher.value;
252 | }
253 | };
254 | }
255 |
256 | var Observer =
257 | /*#__PURE__*/
258 | function () {
259 | function Observer(value) {
260 | _classCallCheck(this, Observer);
261 |
262 | this.value = value;
263 | this.dep = new Dep();
264 | this.walk(value);
265 | }
266 | /**
267 | * 给每个数据属性转为为getter/setter,在读取和设置的时候都会进入对应方法进行数据监听和更新
268 | * @param {Object} obj 监听对象
269 | */
270 |
271 |
272 | _createClass(Observer, [{
273 | key: "walk",
274 | value: function walk(obj) {
275 | var keys = Object.keys(obj);
276 |
277 | for (var i = 0; i < keys.length; i++) {
278 | defineReactive(obj, keys[i]);
279 | }
280 | }
281 | }]);
282 |
283 | return Observer;
284 | }();
285 |
286 | function observe(value, vm) {
287 | if (!value || _typeof(value) !== 'object') {
288 | return;
289 | }
290 |
291 | return new Observer(value);
292 | }
293 |
294 | /*
295 | * @Author: xiaoai
296 | * @Date: 2018-12-02 20:41:41
297 | * @LastEditors: xiaoai
298 | * @LastEditTime: 2018-12-07 00:41:24
299 | * @Description: Whatever is worth doing is worth doing well(任何值得做的事就值得把它做好)
300 | */
301 | var regExp = {
302 | // 匹配结束标签
303 | endTag: /^<\/((?:[a-zA-Z_][\w\-\.]*\:)?[a-zA-Z_][\w\-\.]*)[^>]*>/,
304 | // 匹配开始打开标签
305 | startTagOpen: /^<((?:[a-zA-Z_][\w\-\.]*\:)?[a-zA-Z_][\w\-\.]*)/,
306 | // 匹配开始结束标签
307 | startTagClose: /^\s*(\/?)>/,
308 | // 匹配属性
309 | attribute: /^\s*([^\s"'<>\/=]+)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/,
310 | // 匹配注释
311 | comment: /^]+>/i,
316 | // 匹配表达式 {{}}
317 | defaultTagRE: /\{\{((?:.|\n)+?)\}\}/g
318 | };
319 |
320 | var singleLabel = ['input', 'br', 'hr'];
321 | /**
322 | * 编译类
323 | */
324 |
325 | var Compiler =
326 | /*#__PURE__*/
327 | function () {
328 | function Compiler(vm, options) {
329 | _classCallCheck(this, Compiler);
330 |
331 | this.vm = vm;
332 | this.ast = {};
333 | this.$optins = options; // 获取需要编译的dom
334 |
335 | this.$el = document.querySelector(options.el); // render
336 |
337 | this.$el.outerHTML && this.compileToFunctions(this.$el.outerHTML);
338 | }
339 | /**
340 | * 将vue中dom编译成render函数
341 | * @param template String dom字符串
342 | */
343 |
344 |
345 | _createClass(Compiler, [{
346 | key: "compileToFunctions",
347 | value: function compileToFunctions(template) {
348 | this.template = template; // 会用正则等方式解析template模板中的指令、class、style等数据,形成AST
349 |
350 | this._convertHtml2Ast(); // 将AST转化成render字符串
351 |
352 |
353 | this.render = this._converCode(this.ast);
354 | }
355 | /**
356 | * 遍历dom字符串记录当前位置和删除已经遍历过的dom
357 | * @param {Number} n 当前dom字符串下标
358 | */
359 |
360 | }, {
361 | key: "_advance",
362 | value: function _advance(n) {
363 | this.index += n;
364 | this.template = this.template.substring(n);
365 | }
366 | /**
367 | * 处理开始标签
368 | */
369 |
370 | }, {
371 | key: "_parseStartTag",
372 | value: function _parseStartTag() {
373 | var start = this.template.match(regExp.startTagOpen);
374 |
375 | if (start) {
376 | var match = {
377 | tagName: start[1],
378 | attrs: [],
379 | start: this.index
380 | };
381 |
382 | this._advance(start[0].length);
383 |
384 | var end, attr;
385 |
386 | while (!(end = this.template.match(regExp.startTagClose)) && (attr = this.template.match(regExp.attribute))) {
387 | this._advance(attr[0].length);
388 |
389 | match.attrs.push(attr);
390 | } // 如果当前标签是否是以 > 结尾
391 |
392 |
393 | if (end) {
394 | match.unarySlash = end[1];
395 |
396 | this._advance(end[0].length);
397 |
398 | match.end = this.index;
399 | return match;
400 | }
401 | }
402 | }
403 | /**
404 | * 处理结束标签
405 | */
406 |
407 | }, {
408 | key: "_parseEndTag",
409 | value: function _parseEndTag() {
410 | // 获取当前栈中最后一个元素
411 | var element = this.stack[this.stack.length - 1];
412 | var lastNode = element.children[element.children.length - 1]; // 如果是注释文本就删除
413 |
414 | if (lastNode && lastNode.type === 3 && lastNode.text === ' ') {
415 | element.children.pop();
416 | } // 出栈
417 |
418 |
419 | this.stack.length -= 1;
420 | this.currentParent = this.stack[this.stack.length - 1];
421 | }
422 | /**
423 | * 处理开始标签中的匹配到属性添加ast中
424 | * @param Object startTagMatch<{attrs:Array,tagName: String, start: Number, end: Number}>
425 | */
426 |
427 | }, {
428 | key: "_handleStartTag",
429 | value: function _handleStartTag(startTagMatch) {
430 | var attrs = []; // 当前标签是否是以 > 结尾的flag
431 |
432 | this.unary = !!startTagMatch.unarySlash;
433 | startTagMatch.attrs.map(function (item) {
434 | attrs.push({
435 | name: item[1],
436 | value: item[3] || item[4] || item[5] || ''
437 | });
438 | });
439 |
440 | var element = this._createASTElement(startTagMatch, attrs, this.currentParent); // 创建ast
441 |
442 |
443 | if (!this.ast) {
444 | this.ast = element;
445 | }
446 |
447 | if (this.currentParent) {
448 | this.currentParent.children.push(element);
449 | element.parent = this.currentParent;
450 | } // 如果不是结束 > 标签就添加到堆栈中和记录当前父级
451 |
452 |
453 | if (!this.unary && singleLabel.indexOf(element.tag) < 0) {
454 | this.stack.push(element);
455 | this.currentParent = element;
456 | }
457 | }
458 | /**
459 | * 创建ast树
460 | * @param {Object} startTagMatch
461 | * @param {Array} attrs
462 | * @param {Object} parent
463 | */
464 |
465 | }, {
466 | key: "_createASTElement",
467 | value: function _createASTElement(startTagMatch, attrs, parent) {
468 | // 根元素 type为1
469 | var class2styleExpReg = /^:(class|style)$/;
470 | var class2styleReg = /^(class|style)$/;
471 | var map = {};
472 | var event = {};
473 | var _attrs = [];
474 | var props = [];
475 | var directives = [];
476 | var isEvent = false;
477 | var staticClass, staticStyle, styleBinding, classBinding;
478 |
479 | for (var i = 0, l = attrs.length; i < l; i++) {
480 | map[attrs[i].name] = attrs[i].value;
481 | /**
482 | * 1.匹配 @ 符号表示是绑定事件
483 | * 2.匹配 :class :style 表达式class和表达式style
484 | * 3.匹配 class style 静态class和静态style
485 | * 4.普通数据(如: id)
486 | */
487 |
488 | if (attrs[i].name.match(/^@/g)) {
489 | isEvent = true;
490 | event[attrs[i].name.match(/\w*$/)[0]] = {
491 | value: attrs[i].value
492 | };
493 | } else if (class2styleReg.test(attrs[i].name)) {
494 | attrs[i].name.indexOf('class') > -1 ? staticClass = attrs[i].value : staticStyle = attrs[i].value;
495 | } else if (class2styleExpReg.test(attrs[i].name)) {
496 | attrs[i].name.indexOf(':class') > -1 ? classBinding = attrs[i].value : styleBinding = attrs[i].value;
497 | } else if (attrs[i].name === 'v-model') {
498 | isEvent = true;
499 | event['input'] = {
500 | value: "function($event){if($event.target.composing)return;".concat(attrs[i].value, "=$event.target.value}")
501 | };
502 | props.push({
503 | name: 'value',
504 | value: "(".concat(attrs[i].value, ")")
505 | });
506 | directives.push({
507 | arg: null,
508 | modifiers: undefined,
509 | name: 'model',
510 | rawName: 'v-model',
511 | value: attrs[i].value
512 | });
513 | } else {
514 | _attrs.push({
515 | name: attrs[i].name,
516 | value: attrs[i].value
517 | });
518 | }
519 | } // 默认根ast数据结构
520 |
521 |
522 | var astMap = {
523 | type: 1,
524 | tag: startTagMatch.tagName,
525 | attrsList: attrs,
526 | attrsMap: map,
527 | parent: parent,
528 | children: []
529 | }; // 如果有事件绑定就添加到ast中
530 |
531 | if (isEvent) {
532 | astMap = Object.assign({}, astMap, {
533 | event: event
534 | }); // 处理v-model指令
535 |
536 | props.length && (astMap = Object.assign({}, astMap, {
537 | props: props,
538 | directives: directives
539 | }));
540 | } // 属性值
541 |
542 |
543 | if (_attrs.length) {
544 | astMap = Object.assign({}, astMap, {
545 | attrs: _attrs
546 | });
547 | } // 静态class
548 |
549 |
550 | if (staticClass) {
551 | astMap = Object.assign({}, astMap, {
552 | staticClass: staticClass
553 | });
554 | } // 静态样式
555 |
556 |
557 | if (staticStyle) {
558 | astMap = Object.assign({}, astMap, {
559 | staticStyle: staticStyle
560 | });
561 | } // 表达式样式
562 |
563 |
564 | if (styleBinding) {
565 | astMap = Object.assign({}, astMap, {
566 | styleBinding: styleBinding
567 | });
568 | } // 表达式class
569 |
570 |
571 | if (classBinding) {
572 | astMap = Object.assign({}, astMap, {
573 | classBinding: classBinding
574 | });
575 | }
576 |
577 | return astMap;
578 | }
579 | /**
580 | * 转换attrs
581 | * @param {Array} attrs
582 | * @returns Object
583 | */
584 |
585 | }, {
586 | key: "_makeAttrsMap",
587 | value: function _makeAttrsMap(attrs) {
588 | var map = {};
589 |
590 | for (var i = 0, l = attrs.length; i < l; i++) {
591 | map[attrs[i].name] = attrs[i].value;
592 | }
593 |
594 | return map;
595 | }
596 | /**
597 | * 处理文本内容
598 | * @param String text 文本节点内容
599 | */
600 |
601 | }, {
602 | key: "_chars",
603 | value: function _chars(text) {
604 | if (!this.currentParent) {
605 | return;
606 | }
607 |
608 | var children = this.currentParent.children;
609 | text = text.trim();
610 |
611 | if (text) {
612 | var res; // 文本节点并且是表达式
613 |
614 | if (text !== ' ' && (res = this._parseText(text))) {
615 | children.push({
616 | type: 2,
617 | expression: res.expression,
618 | tokens: res.tokens,
619 | text: text
620 | });
621 | } else if (text !== ' ' || !children.length || children[children.length - 1].text !== ' ') {
622 | // 普通文本节点
623 | children.push({
624 | type: 3,
625 | text: text
626 | });
627 | }
628 | }
629 | }
630 | /**
631 | * 如果文本中含有{{}}表达式进行转换
632 | * @param {String} text text 文本节点内容
633 | */
634 |
635 | }, {
636 | key: "_parseText",
637 | value: function _parseText(text) {
638 | if (!regExp.defaultTagRE.test(text)) {
639 | return;
640 | }
641 |
642 | var tokens = [];
643 | var rawTokens = [];
644 | var lastIndex = regExp.defaultTagRE.lastIndex = 0;
645 | var match, index, tokenValue;
646 |
647 | while (match = regExp.defaultTagRE.exec(text)) {
648 | index = match.index;
649 |
650 | if (index > lastIndex) {
651 | rawTokens.push(tokenValue = text.slice(lastIndex, index));
652 | tokens.push(JSON.stringify(tokenValue));
653 | } // 构造表达式
654 |
655 |
656 | var exp = match[1].trim();
657 | tokens.push('_s(' + exp + ')');
658 | rawTokens.push({
659 | '@binding': exp
660 | });
661 | lastIndex = index + match[0].length;
662 | }
663 |
664 | if (lastIndex < text.length) {
665 | rawTokens.push(tokenValue = text.slice(lastIndex));
666 | tokens.push(JSON.stringify(tokenValue));
667 | }
668 |
669 | return {
670 | expression: tokens.join('+'),
671 | tokens: rawTokens
672 | };
673 | }
674 | /**
675 | * html转为Ast
676 | * @returns Object AST语法树
677 | */
678 |
679 | }, {
680 | key: "_convertHtml2Ast",
681 | value: function _convertHtml2Ast() {
682 | this.ast = null;
683 | this.stack = [];
684 | this.index = 0;
685 |
686 | while (this.template) {
687 | var textEnd = this.template.indexOf('<');
688 |
689 | if (textEnd === 0) {
690 | // 如果是注释标签直接跳过编译
691 | if (regExp.comment.test(this.template)) {
692 | var commentEnd = this.template.indexOf('-->');
693 |
694 | this._advance(commentEnd + 3);
695 |
696 | continue;
697 | } // 匹配结束标签
698 |
699 |
700 | var endTagMatch = this.template.match(regExp.endTag);
701 |
702 | if (endTagMatch) {
703 | var _index = this.index;
704 |
705 | this._advance(endTagMatch[0].length);
706 |
707 | this._parseEndTag(endTagMatch[1], _index, this.index);
708 |
709 | continue;
710 | } // 匹配开始标签
711 |
712 |
713 | var startTagMatch = this._parseStartTag();
714 |
715 | if (startTagMatch) {
716 | this._handleStartTag(startTagMatch);
717 |
718 | continue;
719 | }
720 | }
721 |
722 | var text;
723 |
724 | if (textEnd >= 0) {
725 | // 匹配标签文本内容
726 | text = this.template.substring(0, textEnd);
727 |
728 | this._advance(textEnd);
729 | }
730 |
731 | if (textEnd < 0) {
732 | text = this.template;
733 | this.template = '';
734 | }
735 |
736 | if (text) {
737 | this._chars(text);
738 | }
739 | }
740 | }
741 | /**
742 | * 根据ast转成字符串code
743 | * html代码:
744 | *
745 | * {{testData}}
746 | *
747 | *
748 | * 转换后的code:
749 | * _c('div', { attrs: { id: 'app' } }, [_c('span', { style: testComputed }, [_v(_s(testData))]), _c('span', { on: { click: clickFn } })]);
750 | */
751 |
752 | }, {
753 | key: "_converCode",
754 | value: function _converCode(el) {
755 | var data = this._setGenCode(el);
756 |
757 | var children = this._getChildren(el); // 处理文本表达式
758 |
759 |
760 | if (!el.tag && el.type === 2) {
761 | return "_v(".concat(el.expression, ")");
762 | } // 处理文本
763 |
764 |
765 | if (!el.tag && el.type === 3) {
766 | return "_v(\"".concat(el.text, "\")");
767 | }
768 |
769 | return "_c('" + el.tag + "'" + (data ? ',' + data : '') + (children ? ',' + children : '') + ')';
770 | }
771 | /**
772 | * 生成code
773 | * @param {Object} el ast树
774 | */
775 |
776 | }, {
777 | key: "_setGenCode",
778 | value: function _setGenCode(el) {
779 | var data = '{';
780 |
781 | if (el.staticClass) {
782 | data += "staticClass:\"".concat(el.staticClass, "\",");
783 | }
784 |
785 | if (el.classBinding) {
786 | data += "class:".concat(el.classBinding, ",");
787 | }
788 |
789 | if (el.staticStyle) {
790 | data += "staticStyle:\"".concat(el.staticStyle, "\",");
791 | }
792 |
793 | if (el.styleBinding) {
794 | data += "style:".concat(el.styleBinding, ",");
795 | } // 处理属性
796 |
797 |
798 | if (el.attrs) {
799 | data += "attrs:{".concat(this._genProps(el.attrs), "},");
800 | } // 处理事件
801 |
802 |
803 | if (el.event) {
804 | data += "on:{".concat(this._genHandlers(el.event), "},");
805 | } // 处理指令
806 |
807 |
808 | if (el.directives) {
809 | data += "directives:".concat(this._genDirectives(el.directives), ",");
810 | } // 处理domProps
811 |
812 |
813 | if (el.props) {
814 | data += "domProps:{".concat(this._genProps(el.props, true), "},");
815 | }
816 |
817 | data = data.replace(/,$/, '') + '}'; // 如果没有属性就直接返回空
818 |
819 | if (/^\{\}$/.test(data)) {
820 | data = '';
821 | }
822 |
823 | return data;
824 | }
825 | /**
826 | * 处理属性字段
827 | * @param {Array} props 标签属性元素集合
828 | */
829 |
830 | }, {
831 | key: "_genProps",
832 | value: function _genProps(props, flag) {
833 | var res = '';
834 |
835 | for (var i = 0; i < props.length; i++) {
836 | var prop = props[i];
837 | {
838 | flag ? res += '"' + prop.name + '":' + prop.value + ',' : res += '"' + prop.name + '":"' + prop.value + '",';
839 | }
840 | }
841 |
842 | return res.slice(0, -1);
843 | }
844 | /**
845 | * 处理事件
846 | */
847 |
848 | }, {
849 | key: "_genHandlers",
850 | value: function _genHandlers(events) {
851 | var res = '';
852 |
853 | for (var name in events) {
854 | res += '"' + name + '":' + events[name].value + ',';
855 | }
856 |
857 | return res.slice(0, -1);
858 | }
859 | /**
860 | * 处理指令
861 | * @param {Array} directives
862 | */
863 |
864 | }, {
865 | key: "_genDirectives",
866 | value: function _genDirectives(directives) {
867 | var _code = '[';
868 | directives.map(function (item, index) {
869 | _code += "{name:\"".concat(item.name, "\",rawName:\"").concat(item.rawName, "\",value:(").concat(item.value, "),expression:\"").concat(item.value, "\"}").concat(index === directives.length - 1 ? ']' : ',');
870 | });
871 | return _code;
872 | }
873 | /**
874 | * 处理子节点
875 | * @param {Object} el 当前节点的Ast树
876 | */
877 |
878 | }, {
879 | key: "_getChildren",
880 | value: function _getChildren(el) {
881 | var _this = this;
882 |
883 | var children = el.children;
884 |
885 | if (children && children.length) {
886 | return '[' + children.map(function (item) {
887 | return _this._converCode(item);
888 | }) + ']';
889 | }
890 | }
891 | }]);
892 |
893 | return Compiler;
894 | }();
895 |
896 | var uid$1 = 0;
897 | /**
898 | * 观察者 Watcher
899 | * 主要作用是进行依赖收集的观察者和更新视图
900 | * 当依赖收集的时候会调用Dep对象的addSub方法,在修改data中数据的时候会触发Dep对象的notify,通知所有Watcher对象去修改对应视图
901 | */
902 |
903 | var Watcher =
904 | /*#__PURE__*/
905 | function () {
906 | /**
907 | * @param {Object} vm miniVue实例对象
908 | * @param {Function} expOrFn watch监听函数
909 | * @param {Function} cb 回调触发视图更新函数
910 | */
911 | function Watcher(vm, expOrFn) {
912 | var cb = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : noop$1;
913 |
914 | _classCallCheck(this, Watcher);
915 |
916 | this.vm = vm; // 设置id防止重复添加
917 |
918 | this.id = uid$1++; // 保存监听函数为字符串,错误提示会使用
919 |
920 | this.expression = expOrFn.toString(); // 新的依赖项id集合
921 |
922 | this.newDepIds = new Set(); // 新的依赖项 临时值在依赖收集完成之后会马上清除
923 |
924 | this.newDeps = []; // 添加后的依赖项id集合
925 |
926 | this.depIds = new Set(); // 添加后的依赖项 依赖收集完成会从newDeps中取出值赋值给自己
927 |
928 | this.deps = []; // 回调触发视图更新函数
929 |
930 | this.cb = cb; // 获取当前watcher表达式
931 |
932 | if (typeof expOrFn === 'function') {
933 | this.getter = expOrFn;
934 | } else {
935 | debug$1('error', this.expression + 'Not a function');
936 | }
937 | }
938 |
939 | _createClass(Watcher, [{
940 | key: "get",
941 | value: function get() {
942 | // 更新当前watcher赋值给Dep.target,并且添加到target栈
943 | pushTarget(this);
944 | var value = this.getter.call(this.vm, this.vm); // 将观察者实例从target栈中取出并设置给Dep.target
945 |
946 | popTarget(); // 清除依赖
947 |
948 | this.cleanupDeps();
949 | this.value = value;
950 | return value;
951 | }
952 | /**
953 | * 添加依赖
954 | * @param {Object} dep Dep实例对象
955 | */
956 |
957 | }, {
958 | key: "addDep",
959 | value: function addDep(dep) {
960 | var _id = dep.id; // 如果没有添加依赖项就进行添加
961 |
962 | if (!this.newDepIds.has(_id)) {
963 | this.newDepIds.add(_id);
964 | this.newDeps.push(dep);
965 |
966 | if (!this.depIds.has(_id)) {
967 | dep.addSub(this);
968 | }
969 | }
970 | }
971 | /**
972 | * 清除依赖收集
973 | */
974 |
975 | }, {
976 | key: "cleanupDeps",
977 | value: function cleanupDeps() {
978 | /*移除所有观察者对象*/
979 | var i = this.deps.length;
980 |
981 | while (i--) {
982 | var dep = this.deps[i];
983 |
984 | if (!this.newDepIds.has(dep.id)) {
985 | dep.removeSub(this);
986 | }
987 | } // 清除所有依赖数据,把newDeps数据赋值给deps存储依赖
988 |
989 |
990 | var tmp = this.depIds;
991 | this.depIds = this.newDepIds;
992 | this.newDepIds = tmp;
993 | this.newDepIds.clear();
994 | tmp = this.deps;
995 | this.deps = this.newDeps;
996 | this.newDeps = tmp;
997 | this.newDeps.length = 0;
998 | }
999 | /**
1000 | * 收集依赖
1001 | */
1002 |
1003 | }, {
1004 | key: "depend",
1005 | value: function depend() {
1006 | var i = this.deps.length;
1007 |
1008 | while (i--) {
1009 | this.deps[i].depend();
1010 | }
1011 | }
1012 | /**
1013 | * 触发更新
1014 | */
1015 |
1016 | }, {
1017 | key: "update",
1018 | value: function update() {
1019 | this.run(); // queueWatcher(this);
1020 | }
1021 | /**
1022 | * update函数会调该函数进行更新回调
1023 | */
1024 |
1025 | }, {
1026 | key: "run",
1027 | value: function run() {
1028 | var value = this.get();
1029 |
1030 | if (value !== this.value) {
1031 | var oldValue = this.value;
1032 | this.value = value;
1033 | this.cb.call(this.vm, value, oldValue);
1034 | }
1035 | }
1036 | }]);
1037 |
1038 | return Watcher;
1039 | }();
1040 |
1041 | /*
1042 | * @Author: xiaoai
1043 | * @Date: 2018-12-06 15:58:11
1044 | * @LastEditors: xiaoai
1045 | * @LastEditTime: 2018-12-06 17:01:38
1046 | * @Description: Whatever is worth doing is worth doing well(任何值得做的事就值得把它做好)
1047 | */
1048 | // 代码来源:vue源码/vue-dev/src/platforms/web/runtime/node-ops.js
1049 | function createElement(tagName, vnode) {
1050 | var elm = document.createElement(tagName);
1051 |
1052 | if (tagName !== 'select') {
1053 | return elm;
1054 | } // false or null will remove the attribute but undefined will not
1055 |
1056 |
1057 | if (vnode.data && vnode.data.attrs && vnode.data.attrs.multiple !== undefined) {
1058 | elm.setAttribute('multiple', 'multiple');
1059 | }
1060 |
1061 | return elm;
1062 | } // export function createElementNS (namespace, tagName) {
1063 | // return document.createElementNS(namespaceMap[namespace], tagName)
1064 | // }
1065 |
1066 | function createTextNode(text) {
1067 | return document.createTextNode(text);
1068 | }
1069 | function createComment(text) {
1070 | return document.createComment(text);
1071 | }
1072 | function insertBefore(parentNode, newNode, referenceNode) {
1073 | parentNode.insertBefore(newNode, referenceNode);
1074 | }
1075 | function removeChild(node, child) {
1076 | node.removeChild(child);
1077 | }
1078 | function appendChild(node, child) {
1079 | node.appendChild(child);
1080 | }
1081 | function parentNode(node) {
1082 | return node.parentNode;
1083 | }
1084 | function nextSibling(node) {
1085 | return node.nextSibling;
1086 | }
1087 | function tagName(node) {
1088 | return node.tagName;
1089 | }
1090 | function setTextContent(node, text) {
1091 | node.textContent = text;
1092 | }
1093 | function setStyleScope(node, scopeId) {
1094 | node.setAttribute(scopeId, '');
1095 | }
1096 | var nodeOptions = {
1097 | createElement: createElement,
1098 | createTextNode: createTextNode,
1099 | createComment: createComment,
1100 | insertBefore: insertBefore,
1101 | removeChild: removeChild,
1102 | appendChild: appendChild,
1103 | parentNode: parentNode,
1104 | nextSibling: nextSibling,
1105 | tagName: tagName,
1106 | setTextContent: setTextContent,
1107 | setStyleScope: setStyleScope
1108 | };
1109 |
1110 | function createFnInvoker(fns) {
1111 | function invoker() {
1112 | var arguments$1 = arguments;
1113 | var fns = invoker.fns;
1114 |
1115 | if (Array.isArray(fns)) {
1116 | var cloned = fns.slice();
1117 |
1118 | for (var i = 0; i < cloned.length; i++) {
1119 | cloned[i].apply(null, arguments$1);
1120 | }
1121 | } else {
1122 | // return handler return value for single handlers
1123 | return fns.apply(null, arguments);
1124 | }
1125 | }
1126 |
1127 | invoker.fns = fns;
1128 | return invoker;
1129 | }
1130 | /**
1131 | * element类
1132 | * 根据VNode创建真实dom元素
1133 | */
1134 |
1135 |
1136 | var Element =
1137 | /*#__PURE__*/
1138 | function () {
1139 | /**
1140 | * @param {element对象} oldElem element元素
1141 | * @param {Object} vnode vnode虚拟dom
1142 | * @param {Object} prevVnode 更新上一次的虚拟dom
1143 | * @param {element对象} parentElm 父元素ele对象
1144 | */
1145 | function Element(vm, oldElem, vnode, prevVnode, parentElm) {
1146 | _classCallCheck(this, Element);
1147 |
1148 | this.vm = vm;
1149 |
1150 | if (prevVnode) {
1151 | callHook(vm, 'beforeUpdate');
1152 | }
1153 |
1154 | this.prevVnode = prevVnode ? prevVnode : {
1155 | data: {}
1156 | };
1157 | this.createElement(vnode, parentElm, prevVnode);
1158 | this.removeVnodes(oldElem);
1159 | }
1160 | /**
1161 | * vue diff算法实现
1162 | */
1163 |
1164 |
1165 | _createClass(Element, [{
1166 | key: "path",
1167 | value: function path() {
1168 | return true;
1169 | }
1170 | /**
1171 | * 删除老元素
1172 | * @param {element对象} oldElem 上一次的元素
1173 | */
1174 |
1175 | }, {
1176 | key: "removeVnodes",
1177 | value: function removeVnodes(oldElem) {
1178 | nodeOptions.removeChild(oldElem.parentNode, oldElem);
1179 | }
1180 | /**
1181 | * 根据VNode创建真实dom元素
1182 | * @param {Object} vnode VNode
1183 | * @param {ele} parentElm 父元素
1184 | */
1185 |
1186 | }, {
1187 | key: "createElement",
1188 | value: function createElement$$1(vnode, parentElm) {
1189 | this.path(); // 没有父元素就直接默认body
1190 |
1191 | if (!parentElm) {
1192 | parentElm = document.querySelector('body');
1193 | }
1194 |
1195 | var data = vnode.data;
1196 | var children = vnode.children;
1197 | var tag = vnode.tag; // 有tag就创建一个标签,没有就当成文本节点创建
1198 |
1199 | if (tag) {
1200 | vnode.elm = nodeOptions.createElement(tag, vnode);
1201 | } else {
1202 | vnode.elm = nodeOptions.createTextNode(vnode.text);
1203 | } // 如果有子元素数据,递归创建子元素
1204 |
1205 |
1206 | this.createChildren(vnode, children);
1207 |
1208 | if (data) {
1209 | this.updateAttrs(this.prevVnode, vnode);
1210 | this.updateClass(this.prevVnode, vnode);
1211 | this.updateDOMListeners(this.prevVnode, vnode);
1212 | this.updateStyle(this.prevVnode, vnode);
1213 | } // 添加都对应父元素下面
1214 |
1215 |
1216 | if (parentElm !== undefined || parentElm !== null) {
1217 | nodeOptions.appendChild(parentElm, vnode.elm);
1218 | }
1219 | }
1220 | /**
1221 | * 递归创建孩子节点
1222 | * @param {Object} vnode VNode
1223 | * @param {Array} children 孩子VNode
1224 | */
1225 |
1226 | }, {
1227 | key: "createChildren",
1228 | value: function createChildren(vnode, children) {
1229 | if (Array.isArray(children)) {
1230 | var _ref = [0, children.length],
1231 | i = _ref[0],
1232 | len = _ref[1];
1233 |
1234 | for (; i < len; i++) {
1235 | this.createElement(children[i], vnode.elm);
1236 | }
1237 | }
1238 | }
1239 | /**
1240 | * 更新元素属性
1241 | * @param oldVnode
1242 | * @param vnode
1243 | */
1244 |
1245 | }, {
1246 | key: "updateAttrs",
1247 | value: function updateAttrs(oldVnode, vnode) {
1248 | var elm = vnode.elm;
1249 | var oldAttrs = oldVnode.data.attrs || {};
1250 | var attrs = vnode.data.attrs || {}; // 整合attrs和domProps
1251 |
1252 | if (vnode.data.domProps) {
1253 | attrs = Object.assign({}, attrs, vnode.data.domProps);
1254 | }
1255 |
1256 | var cur, old;
1257 |
1258 | for (var key in attrs) {
1259 | cur = attrs[key];
1260 | old = oldAttrs[key]; // if (old !== cur) {
1261 |
1262 | elm.setAttribute(key, cur); // }
1263 | }
1264 | }
1265 | /**
1266 | * 更新元素class
1267 | * @param oldVnode
1268 | * @param vnode
1269 | */
1270 |
1271 | }, {
1272 | key: "updateClass",
1273 | value: function updateClass(oldVnode, vnode) {
1274 | var elm = vnode.elm;
1275 | var oldStaticClass = oldVnode.data.staticClass || '';
1276 | var staticClass = vnode.data.staticClass || '';
1277 | var oldClass = oldVnode.data.class || '';
1278 |
1279 | var _class = vnode.data.class || '';
1280 |
1281 | if (staticClass || _class) {
1282 | var _cls = [].concat(staticClass, _class);
1283 |
1284 | elm.setAttribute('class', _cls.join(' '));
1285 | }
1286 | }
1287 | /**
1288 | * 绑定元素事件
1289 | * @param oldVnode
1290 | * @param vnode
1291 | */
1292 |
1293 | }, {
1294 | key: "updateDOMListeners",
1295 | value: function updateDOMListeners(oldVnode, vnode) {
1296 | var on = vnode.data.on || {};
1297 | var oldOn = oldVnode.data.on || {};
1298 | var cur;
1299 |
1300 | for (var name in on) {
1301 | cur = createFnInvoker(on[name]);
1302 | vnode.elm.addEventListener(name, cur, false);
1303 | }
1304 | }
1305 | /**
1306 | * 更新元素样式
1307 | * @param oldVnode
1308 | * @param vnode
1309 | */
1310 |
1311 | }, {
1312 | key: "updateStyle",
1313 | value: function updateStyle(oldVnode, vnode) {
1314 | var elm = vnode.elm;
1315 | var oldStaticStyle = oldVnode.data.staticStyle || '';
1316 | var staticStyle = vnode.data.staticStyle || '';
1317 | var oldStyle = oldVnode.data.style || {};
1318 |
1319 | var _style = vnode.data.style || {}; // 如果直接写在标签上面的style遍历属性添加到最新的dom上面
1320 |
1321 |
1322 | var styleArray = staticStyle.split(';');
1323 |
1324 | if (styleArray && styleArray.length) {
1325 | styleArray.map(function (item) {
1326 | var _s = item.split(':');
1327 |
1328 | if (_s && _s.length) {
1329 | elm.style[_s[0]] = _s[1];
1330 | }
1331 | });
1332 | } // 添加样式
1333 |
1334 |
1335 | for (var key in _style) {
1336 | elm.style[key] = _style[key];
1337 | }
1338 | }
1339 | }]);
1340 |
1341 | return Element;
1342 | }();
1343 |
1344 | /*
1345 | * @Author: xiaoai
1346 | * @Date: 2018-12-04 19:53:19
1347 | * @LastEditors: xiaoai
1348 | * @LastEditTime: 2018-12-06 15:28:47
1349 | * @Description: Whatever is worth doing is worth doing well(任何值得做的事就值得把它做好)
1350 | */
1351 |
1352 | /**
1353 | * 虚拟Dom基类
1354 | */
1355 | var VNode =
1356 | /*#__PURE__*/
1357 | function () {
1358 | function VNode(tag, data, children, text, elm, context, componentOptions, asyncFactory) {
1359 | _classCallCheck(this, VNode);
1360 |
1361 | /*当前节点的标签名*/
1362 | this.tag = tag;
1363 | /*当前节点对应的对象,包含了具体的一些数据信息,是一个VNodeData类型,可以参考VNodeData类型中的数据信息*/
1364 |
1365 | this.data = data;
1366 | /*当前节点的子节点,是一个数组*/
1367 |
1368 | this.children = children;
1369 | /*当前节点的文本*/
1370 |
1371 | this.text = text;
1372 | /*当前虚拟节点对应的真实dom节点*/
1373 |
1374 | this.elm = elm;
1375 | /*当前节点的名字空间*/
1376 |
1377 | this.ns = undefined;
1378 | /*编译作用域*/
1379 |
1380 | this.context = context;
1381 | /*函数化组件作用域*/
1382 |
1383 | this.functionalContext = undefined;
1384 | /*节点的key属性,被当作节点的标志,用以优化*/
1385 |
1386 | this.key = data && data.key;
1387 | /*组件的option选项*/
1388 |
1389 | this.componentOptions = componentOptions;
1390 | /*当前节点对应的组件的实例*/
1391 |
1392 | this.componentInstance = undefined;
1393 | /*当前节点的父节点*/
1394 |
1395 | this.parent = undefined;
1396 | /*简而言之就是是否为原生HTML或只是普通文本,innerHTML的时候为true,textContent的时候为false*/
1397 |
1398 | this.raw = false;
1399 | /*静态节点标志*/
1400 |
1401 | this.isStatic = false;
1402 | /*是否作为跟节点插入*/
1403 |
1404 | this.isRootInsert = true;
1405 | /*是否为注释节点*/
1406 |
1407 | this.isComment = false;
1408 | /*是否为克隆节点*/
1409 |
1410 | this.isCloned = false;
1411 | /*是否有v-once指令*/
1412 |
1413 | this.isOnce = false;
1414 | }
1415 |
1416 | _createClass(VNode, [{
1417 | key: "child",
1418 | value: function child() {
1419 | return this.componentInstance;
1420 | }
1421 | }]);
1422 |
1423 | return VNode;
1424 | }();
1425 | var createEmptyVNode = function createEmptyVNode() {
1426 | var text = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
1427 | var node = new VNode();
1428 | node.text = text;
1429 | node.isComment = true;
1430 | return node;
1431 | };
1432 | /**
1433 | * 创建文本节点
1434 | */
1435 |
1436 | function createTextVNode(val) {
1437 | return new VNode(undefined, undefined, undefined, String(val));
1438 | }
1439 | /**
1440 | * 创建元素
1441 | * @param {Object} context miniVue实例
1442 | * @param {String} tag 标签
1443 | * @param {Object} data 数据
1444 | * @param {Array} children 子节点
1445 | */
1446 |
1447 | function createElement$1(context, tag, data, children) {
1448 | var vnode;
1449 |
1450 | if (!tag) {
1451 | createEmptyVNode();
1452 | } // 兼容不传data的情况, 处理{{a}} 这种dom情况,字符串function为: _c('span', [_v(_s(a))])
1453 |
1454 |
1455 | if (Array.isArray(data)) {
1456 | children = data;
1457 | data = undefined;
1458 | }
1459 |
1460 | if (typeof tag === 'string') {
1461 | vnode = new VNode(tag, data, children, undefined, undefined, context);
1462 | }
1463 |
1464 | if (vnode !== undefined) {
1465 | return vnode;
1466 | }
1467 | }
1468 |
1469 | var uid$2 = 0;
1470 | /**
1471 | * 主函数入口
1472 | */
1473 |
1474 | var MiniVue =
1475 | /*#__PURE__*/
1476 | function () {
1477 | function MiniVue(options) {
1478 | _classCallCheck(this, MiniVue);
1479 |
1480 | if ((this instanceof MiniVue ? this.constructor : void 0) !== MiniVue) {
1481 | throw new Error('必须使用 new 命令生成实例');
1482 | }
1483 |
1484 | this.id = uid$2++;
1485 | this._self = this;
1486 | this.$options = options;
1487 | this.init(options);
1488 | }
1489 |
1490 | _createClass(MiniVue, [{
1491 | key: "init",
1492 | value: function init() {
1493 | var vm = this;
1494 | callHook(vm, 'beforeCreated'); // 创建元素
1495 |
1496 | this._c = function (a, b, c, d) {
1497 | return createElement$1(vm, a, b, c, d);
1498 | }; // 创建文本节点
1499 |
1500 |
1501 | this._v = createTextVNode; // 序列化字符串 创建文本节点并且里面含有{{}}表达式,先处理表达式得到值,在进行字符串转换
1502 |
1503 | this._s = function (val) {
1504 | return val.toString();
1505 | }; // 初始化data数据,代理数据和数据劫持
1506 |
1507 |
1508 | this._initData(); // 初始化computed
1509 |
1510 |
1511 | this._initComputed(); // 初始化methods
1512 |
1513 |
1514 | this._initMethod(); // 编译render,创建虚拟Dom
1515 |
1516 |
1517 | this.mounted();
1518 | }
1519 | /**
1520 | * 代理函数
1521 | * 将data上面的属性代理到了vm实例上,这样就可以用app.text代替app._data.text了
1522 | * @param {Object} target 代理目标对象
1523 | * @param {String} sourceKey 代理key
1524 | * @param {String} key 目标key
1525 | */
1526 |
1527 | }, {
1528 | key: "proxy",
1529 | value: function proxy(target, sourceKey, key) {
1530 | var vm = this;
1531 | Object.defineProperty(target, key, {
1532 | enumerable: true,
1533 | configurable: true,
1534 | get: function get() {
1535 | return vm[sourceKey][key];
1536 | },
1537 | set: function set(val) {
1538 | vm[sourceKey][key] = val;
1539 | }
1540 | });
1541 | }
1542 | /**
1543 | * 初始化data对象数据,收集数据添加监听
1544 | */
1545 |
1546 | }, {
1547 | key: "_initData",
1548 | value: function _initData() {
1549 | var vm = this;
1550 | var data = this.$options.data; // data支持两种写法(函数和对象)
1551 | // 如果data是函数就直接执行拿到返回值,如果是对象直接返回
1552 |
1553 | data = vm._data = typeof data === 'function' ? data.call(vm) : data || {};
1554 | var keys = Object.keys(data);
1555 | var i = keys.length;
1556 |
1557 | while (i--) {
1558 | var key = keys[i];
1559 | this.proxy(vm, '_data', key);
1560 | }
1561 |
1562 | observe(data, vm);
1563 | }
1564 | /**
1565 | * 初始化methods方法挂载miniVue实例上
1566 | */
1567 |
1568 | }, {
1569 | key: "_initMethod",
1570 | value: function _initMethod() {
1571 | for (var key in this.$options.methods) {
1572 | this[key] = this.$options.methods[key] == null ? noop : this.$options.methods[key].bind(this);
1573 | }
1574 | }
1575 | /**
1576 | * 初始化computed,添加依赖监听
1577 | */
1578 |
1579 | }, {
1580 | key: "_initComputed",
1581 | value: function _initComputed() {
1582 | var vm = this;
1583 |
1584 | var noop = function noop() {};
1585 |
1586 | var watchers = vm._computedWatchers = Object.create(null);
1587 |
1588 | if (this.$options.computed) {
1589 | for (var key in this.$options.computed) {
1590 | var userDef = this.$options.computed[key];
1591 | watchers[key] = new Watcher(vm, userDef, noop);
1592 |
1593 | if (!(key in vm)) {
1594 | defineComputed(vm, key, userDef);
1595 | }
1596 | }
1597 | }
1598 | }
1599 | /**
1600 | * 编译模版
1601 | */
1602 |
1603 | }, {
1604 | key: "mounted",
1605 | value: function mounted() {
1606 | var vm = this;
1607 | callHook(vm, 'created');
1608 |
1609 | if (this.$options.el) {
1610 | callHook(vm, 'beforeMount'); // 编译template
1611 |
1612 | var compiler = new Compiler(vm, this.$options); // 将AST转化成render function字符串
1613 |
1614 | this.$options.render = new Function('with(this){return ' + compiler.render + '}');
1615 |
1616 | var updateComponent = function updateComponent() {
1617 | var prevVnode = vm._vnode;
1618 | var oldElem = document.querySelector(this.$options.el); // 根据render function字符串创建VNode虚拟Dom
1619 |
1620 | vm.vnode = vm.$options.render.call(vm);
1621 | vm._vnode = vm.vnode; // 根据VNode创建真实dom元素
1622 |
1623 | new Element(vm, oldElem, vm.vnode, prevVnode, null);
1624 | }; // 把渲染函数添加到wather里面,如果有数据更新就重新执行渲染函数进行页面更新
1625 |
1626 |
1627 | new Watcher(vm, updateComponent, function () {}).get();
1628 | callHook(vm, 'mounted');
1629 | }
1630 | }
1631 | }]);
1632 |
1633 | return MiniVue;
1634 | }();
1635 |
1636 | return MiniVue;
1637 |
1638 | })));
1639 |
--------------------------------------------------------------------------------
/dist/MiniVue.js:
--------------------------------------------------------------------------------
1 | (function (global, factory) {
2 | typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
3 | typeof define === 'function' && define.amd ? define(factory) :
4 | (global.MiniVue = factory());
5 | }(this, (function () { 'use strict';
6 |
7 | /*
8 | * @Author: xiaoai
9 | * @Date: 2018-11-15 20:18:05
10 | * @LastEditors: xiaoai
11 | * @LastEditTime: 2018-11-16 11:57:20
12 | * @Description: Whatever is worth doing is worth doing well(任何值得做的事就值得把它做好)
13 | */
14 | let _console = window.console;
15 |
16 | function debug(type, msg) {
17 | _console[type].call(_console, msg);
18 | }
19 |
20 | /*
21 | * @Author: xiaoai
22 | * @Date: 2018-11-15 19:58:29
23 | * @LastEditors: xiaoai
24 | * @LastEditTime: 2018-12-06 19:34:55
25 | * @Description: Whatever is worth doing is worth doing well(任何值得做的事就值得把它做好)
26 | */
27 |
28 | let noop$1 = function(a, b, c) {};
29 |
30 | let callHook = function(vm, hook) {
31 | let handlers = vm.$options[hook];
32 | if (handlers) {
33 | handlers.call(vm);
34 | }
35 | };
36 | const debug$1 = debug;
37 |
38 | /*
39 | * @Author: xiaoai
40 | * @Date: 2018-11-15 16:03:18
41 | * @LastEditors: xiaoai
42 | * @LastEditTime: 2018-11-16 11:59:29
43 | * @Description: Whatever is worth doing is worth doing well(任何值得做的事就值得把它做好)
44 | */
45 | let uid = 0;
46 | /**
47 | * 订阅者Dep
48 | * 主要作用是用来存放Watcher观察者对象
49 | */
50 | class Dep {
51 | constructor() {
52 | // 标示id防止添加重复观察者对象
53 | this.id = uid++;
54 | // 存储观察者对象
55 | this.subs = [];
56 | }
57 | /**
58 | * 添加观察者
59 | * @param {Watcher对象} sub
60 | */
61 | addSub(sub) {
62 | this.subs.push(sub);
63 | }
64 | /**
65 | * 删除观察者
66 | * @param {Watcher对象} sub
67 | */
68 | removeSub(sub) {
69 | let [i, len] = [0, this.subs.length];
70 |
71 | for (; i < len; i++) {
72 | if (this.subs[i].id === sub.id) {
73 | this.subs.splice(i, 1);
74 | break;
75 | }
76 | }
77 | }
78 | /**
79 | * 依赖收集,当存在Dep.target的时候添加观察者对象
80 | * addDep方法是挂载在Watcher原型对象上面的,方法内部会调用Dep实例上面的addSub方法
81 | */
82 | depend() {
83 | if (Dep.target) {
84 | Dep.target.addDep(this);
85 | }
86 | }
87 | /**
88 | * 通知所有订阅者
89 | * update方法是挂载在Watcher原型对象上面的,方法内部会把需要的更新数据push到异步队列中,等到数据所有操作完成在进行视图更新
90 | */
91 | notify() {
92 | // 拷贝观察者对象
93 | const subs = this.subs.slice();
94 | // 循环所有观察者进行更新操作
95 | subs.map(item => {
96 | item.update();
97 | return item;
98 | });
99 | }
100 | }
101 |
102 | // 依赖收集完需要将Dep.target设为null,防止后面重复添加依赖
103 | Dep.target = null;
104 |
105 | const targetStack = [];
106 |
107 | function pushTarget(_target) {
108 | if(Dep.target) {
109 | targetStack.push(Dep.target);
110 | }
111 | Dep.target = _target;
112 | }
113 |
114 | function popTarget() {
115 | Dep.target = targetStack.pop();
116 | }
117 |
118 | /*
119 | * @Author: xiaoai
120 | * @Date: 2018-11-16 17:50:52
121 | * @LastEditors: xiaoai
122 | * @LastEditTime: 2018-12-07 16:22:45
123 | * @Description: Whatever is worth doing is worth doing well(任何值得做的事就值得把它做好)
124 | */
125 |
126 | let sharedPropertyDefinition = {
127 | enumerable: true,
128 | configurable: true,
129 | get: function(){},
130 | set: function(){}
131 | };
132 | function defineReactive(obj, key, val) {
133 | // 实例订阅者对象
134 | const dep = new Dep();
135 | // 获取对象上面的描述
136 | const property = Object.getOwnPropertyDescriptor(obj, key);
137 |
138 | if (property && property.configurable === false) {
139 | return;
140 | }
141 |
142 | const getter = property && property.get;
143 | const setter = property && property.set;
144 |
145 | if ((!getter || setter) && arguments.length === 2) {
146 | val = obj[key];
147 | }
148 | var childObj = observe(val);
149 | Object.defineProperty(obj, key, {
150 | // 可枚举
151 | enumerable: true,
152 | configurable: true,
153 | get: function reactiveGetter() {
154 | const value = getter ? getter.call(obj) : val;
155 | // 依赖收集
156 | if (Dep.target) {
157 | dep.depend();
158 | }
159 | return value;
160 | },
161 | set: function reactiveSetter(newVal) {
162 | const value = getter ? getter.call(obj) : val;
163 | if (newVal === value || (newVal !== newVal && value !== value)) {
164 | return;
165 | }
166 | // 更新值
167 | if (setter) {
168 | setter.call(obj, newVal);
169 | } else {
170 | val = newVal;
171 | }
172 | // 新的值是object的话,进行监听
173 | childObj = observe(newVal);
174 | // 通知所有订阅者进行视图更新
175 | dep.notify();
176 | }
177 | });
178 | }
179 |
180 | // 把computed的属性挂载到minivue实例上
181 | function defineComputed(target, key, userDef) {
182 | if (typeof userDef === 'function') {
183 | sharedPropertyDefinition.get = createComputedGetter(key);
184 | sharedPropertyDefinition.set = function() {};
185 | }
186 |
187 | Object.defineProperty(target, key, sharedPropertyDefinition);
188 | }
189 |
190 | function createComputedGetter(key) {
191 | return function computedGetter() {
192 | var watcher = this._computedWatchers && this._computedWatchers[key];
193 | if (watcher) {
194 | // if (watcher.dirty) {
195 | watcher.get();
196 | // }
197 | if (Dep.target) {
198 | watcher.depend();
199 | }
200 | return watcher.value;
201 | }
202 | };
203 | }
204 |
205 | class Observer {
206 | constructor(value) {
207 | this.value = value;
208 | this.dep = new Dep();
209 | this.walk(value);
210 | }
211 | /**
212 | * 给每个数据属性转为为getter/setter,在读取和设置的时候都会进入对应方法进行数据监听和更新
213 | * @param {Object} obj 监听对象
214 | */
215 | walk(obj) {
216 | const keys = Object.keys(obj);
217 | for (let i = 0; i < keys.length; i++) {
218 | defineReactive(obj, keys[i]);
219 | }
220 | }
221 | }
222 |
223 | function observe(value, vm) {
224 | if (!value || typeof value !== 'object') {
225 | return;
226 | }
227 | return new Observer(value);
228 | }
229 |
230 | /*
231 | * @Author: xiaoai
232 | * @Date: 2018-12-02 20:41:41
233 | * @LastEditors: xiaoai
234 | * @LastEditTime: 2018-12-07 00:41:24
235 | * @Description: Whatever is worth doing is worth doing well(任何值得做的事就值得把它做好)
236 | */
237 | const regExp = {
238 | // 匹配结束标签
239 | endTag: /^<\/((?:[a-zA-Z_][\w\-\.]*\:)?[a-zA-Z_][\w\-\.]*)[^>]*>/,
240 | // 匹配开始打开标签
241 | startTagOpen: /^<((?:[a-zA-Z_][\w\-\.]*\:)?[a-zA-Z_][\w\-\.]*)/,
242 | // 匹配开始结束标签
243 | startTagClose: /^\s*(\/?)>/,
244 | // 匹配属性
245 | attribute: /^\s*([^\s"'<>\/=]+)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/,
246 | // 匹配注释
247 | comment: /^]+>/i,
252 | // 匹配表达式 {{}}
253 | defaultTagRE: /\{\{((?:.|\n)+?)\}\}/g
254 | };
255 |
256 | /*
257 | * @Author: xiaoai
258 | * @Date: 2018-11-17 14:45:25
259 | * @LastEditors: xiaoai
260 | * @LastEditTime: 2018-12-07 21:47:48
261 | * @Description: Whatever is worth doing is worth doing well(任何值得做的事就值得把它做好)
262 | */
263 | const singleLabel = ['input', 'br', 'hr'];
264 | /**
265 | * 编译类
266 | */
267 | class Compiler {
268 | constructor(vm, options) {
269 | this.vm = vm;
270 | this.ast = {};
271 | this.$optins = options;
272 | // 获取需要编译的dom
273 | this.$el = document.querySelector(options.el);
274 | // render
275 | this.$el.outerHTML && this.compileToFunctions(this.$el.outerHTML);
276 | }
277 | /**
278 | * 将vue中dom编译成render函数
279 | * @param template String dom字符串
280 | */
281 | compileToFunctions(template) {
282 | this.template = template;
283 | // 会用正则等方式解析template模板中的指令、class、style等数据,形成AST
284 | this._convertHtml2Ast();
285 | // 将AST转化成render字符串
286 | this.render = this._converCode(this.ast);
287 | }
288 | /**
289 | * 遍历dom字符串记录当前位置和删除已经遍历过的dom
290 | * @param {Number} n 当前dom字符串下标
291 | */
292 | _advance(n) {
293 | this.index += n;
294 | this.template = this.template.substring(n);
295 | }
296 | /**
297 | * 处理开始标签
298 | */
299 | _parseStartTag() {
300 | let start = this.template.match(regExp.startTagOpen);
301 | if (start) {
302 | let match = {
303 | tagName: start[1],
304 | attrs: [],
305 | start: this.index
306 | };
307 | this._advance(start[0].length);
308 | var end, attr;
309 | while (!(end = this.template.match(regExp.startTagClose)) && (attr = this.template.match(regExp.attribute))) {
310 | this._advance(attr[0].length);
311 | match.attrs.push(attr);
312 | }
313 | // 如果当前标签是否是以 > 结尾
314 | if (end) {
315 | match.unarySlash = end[1];
316 | this._advance(end[0].length);
317 | match.end = this.index;
318 | return match;
319 | }
320 | }
321 | }
322 | /**
323 | * 处理结束标签
324 | */
325 | _parseEndTag() {
326 | // 获取当前栈中最后一个元素
327 | let element = this.stack[this.stack.length - 1];
328 | let lastNode = element.children[element.children.length - 1];
329 |
330 | // 如果是注释文本就删除
331 | if (lastNode && lastNode.type === 3 && lastNode.text === ' ') {
332 | element.children.pop();
333 | }
334 |
335 | // 出栈
336 | this.stack.length -= 1;
337 | this.currentParent = this.stack[this.stack.length - 1];
338 | }
339 | /**
340 | * 处理开始标签中的匹配到属性添加ast中
341 | * @param Object startTagMatch<{attrs:Array,tagName: String, start: Number, end: Number}>
342 | */
343 | _handleStartTag(startTagMatch) {
344 | let attrs = [];
345 |
346 | // 当前标签是否是以 > 结尾的flag
347 | this.unary = !!startTagMatch.unarySlash;
348 | startTagMatch.attrs.map(item => {
349 | attrs.push({
350 | name: item[1],
351 | value: item[3] || item[4] || item[5] || ''
352 | });
353 | });
354 | let element = this._createASTElement(startTagMatch, attrs, this.currentParent);
355 | // 创建ast
356 | if (!this.ast) {
357 | this.ast = element;
358 | }
359 |
360 | if (this.currentParent) {
361 | this.currentParent.children.push(element);
362 | element.parent = this.currentParent;
363 | }
364 |
365 | // 如果不是结束 > 标签就添加到堆栈中和记录当前父级
366 | if (!this.unary && singleLabel.indexOf(element.tag) < 0) {
367 | this.stack.push(element);
368 | this.currentParent = element;
369 | }
370 | }
371 | /**
372 | * 创建ast树
373 | * @param {Object} startTagMatch
374 | * @param {Array} attrs
375 | * @param {Object} parent
376 | */
377 | _createASTElement(startTagMatch, attrs, parent) {
378 | // 根元素 type为1
379 | var class2styleExpReg = /^:(class|style)$/;
380 | var class2styleReg = /^(class|style)$/;
381 | var map = {};
382 | var event = {};
383 | var _attrs = [];
384 | var props = [];
385 | var directives = [];
386 | var isEvent = false;
387 | var staticClass, staticStyle, styleBinding, classBinding;
388 |
389 | for (var i = 0, l = attrs.length; i < l; i++) {
390 | map[attrs[i].name] = attrs[i].value;
391 | /**
392 | * 1.匹配 @ 符号表示是绑定事件
393 | * 2.匹配 :class :style 表达式class和表达式style
394 | * 3.匹配 class style 静态class和静态style
395 | * 4.普通数据(如: id)
396 | */
397 | if (attrs[i].name.match(/^@/g)) {
398 | isEvent = true;
399 | event[attrs[i].name.match(/\w*$/)[0]] = { value: attrs[i].value };
400 | } else if (class2styleReg.test(attrs[i].name)) {
401 | attrs[i].name.indexOf('class') > -1 ? (staticClass = attrs[i].value) : (staticStyle = attrs[i].value);
402 | } else if (class2styleExpReg.test(attrs[i].name)) {
403 | attrs[i].name.indexOf(':class') > -1 ? (classBinding = attrs[i].value) : (styleBinding = attrs[i].value);
404 | } else if (attrs[i].name === 'v-model') {
405 | isEvent = true;
406 | event['input'] = { value: `function($event){if($event.target.composing)return;${attrs[i].value}=$event.target.value}` };
407 |
408 | props.push({
409 | name: 'value',
410 | value: `(${attrs[i].value})`
411 | });
412 |
413 | directives.push({
414 | arg: null,
415 | modifiers: undefined,
416 | name: 'model',
417 | rawName: 'v-model',
418 | value: attrs[i].value
419 | });
420 | } else {
421 | _attrs.push({
422 | name: attrs[i].name,
423 | value: attrs[i].value
424 | });
425 | }
426 | }
427 | // 默认根ast数据结构
428 | var astMap = {
429 | type: 1,
430 | tag: startTagMatch.tagName,
431 | attrsList: attrs,
432 | attrsMap: map,
433 | parent: parent,
434 | children: []
435 | };
436 |
437 | // 如果有事件绑定就添加到ast中
438 | if (isEvent) {
439 | astMap = Object.assign({}, astMap, { event });
440 | // 处理v-model指令
441 | props.length && (astMap = Object.assign({}, astMap, { props, directives }));
442 | }
443 | // 属性值
444 | if (_attrs.length) {
445 | astMap = Object.assign({}, astMap, { attrs: _attrs });
446 | }
447 | // 静态class
448 | if (staticClass) {
449 | astMap = Object.assign({}, astMap, { staticClass });
450 | }
451 | // 静态样式
452 | if (staticStyle) {
453 | astMap = Object.assign({}, astMap, { staticStyle });
454 | }
455 | // 表达式样式
456 | if (styleBinding) {
457 | astMap = Object.assign({}, astMap, { styleBinding });
458 | }
459 | // 表达式class
460 | if (classBinding) {
461 | astMap = Object.assign({}, astMap, { classBinding });
462 | }
463 | return astMap;
464 | }
465 | /**
466 | * 转换attrs
467 | * @param {Array} attrs
468 | * @returns Object
469 | */
470 | _makeAttrsMap(attrs) {
471 | var map = {};
472 | for (var i = 0, l = attrs.length; i < l; i++) {
473 | map[attrs[i].name] = attrs[i].value;
474 | }
475 | return map;
476 | }
477 | /**
478 | * 处理文本内容
479 | * @param String text 文本节点内容
480 | */
481 | _chars(text) {
482 | if (!this.currentParent) {
483 | return;
484 | }
485 | let children = this.currentParent.children;
486 | text = text.trim();
487 |
488 | if (text) {
489 | var res;
490 | // 文本节点并且是表达式
491 | if (text !== ' ' && (res = this._parseText(text))) {
492 | children.push({
493 | type: 2,
494 | expression: res.expression,
495 | tokens: res.tokens,
496 | text: text
497 | });
498 | } else if (text !== ' ' || !children.length || children[children.length - 1].text !== ' ') {
499 | // 普通文本节点
500 | children.push({
501 | type: 3,
502 | text: text
503 | });
504 | }
505 | }
506 | }
507 | /**
508 | * 如果文本中含有{{}}表达式进行转换
509 | * @param {String} text text 文本节点内容
510 | */
511 | _parseText(text) {
512 | if (!regExp.defaultTagRE.test(text)) {
513 | return;
514 | }
515 | var tokens = [];
516 | var rawTokens = [];
517 | var lastIndex = (regExp.defaultTagRE.lastIndex = 0);
518 | var match, index, tokenValue;
519 | while ((match = regExp.defaultTagRE.exec(text))) {
520 | index = match.index;
521 |
522 | if (index > lastIndex) {
523 | rawTokens.push((tokenValue = text.slice(lastIndex, index)));
524 | tokens.push(JSON.stringify(tokenValue));
525 | }
526 | // 构造表达式
527 | var exp = match[1].trim();
528 | tokens.push('_s(' + exp + ')');
529 | rawTokens.push({ '@binding': exp });
530 | lastIndex = index + match[0].length;
531 | }
532 | if (lastIndex < text.length) {
533 | rawTokens.push((tokenValue = text.slice(lastIndex)));
534 | tokens.push(JSON.stringify(tokenValue));
535 | }
536 | return {
537 | expression: tokens.join('+'),
538 | tokens: rawTokens
539 | };
540 | }
541 | /**
542 | * html转为Ast
543 | * @returns Object AST语法树
544 | */
545 | _convertHtml2Ast() {
546 | this.ast = null;
547 | this.stack = [];
548 | this.index = 0;
549 |
550 | while (this.template) {
551 | let textEnd = this.template.indexOf('<');
552 |
553 | if (textEnd === 0) {
554 | // 如果是注释标签直接跳过编译
555 | if (regExp.comment.test(this.template)) {
556 | let commentEnd = this.template.indexOf('-->');
557 | this._advance(commentEnd + 3);
558 | continue;
559 | }
560 |
561 | // 匹配结束标签
562 | let endTagMatch = this.template.match(regExp.endTag);
563 | if (endTagMatch) {
564 | let _index = this.index;
565 | this._advance(endTagMatch[0].length);
566 | this._parseEndTag(endTagMatch[1], _index, this.index);
567 | continue;
568 | }
569 |
570 | // 匹配开始标签
571 | let startTagMatch = this._parseStartTag();
572 | if (startTagMatch) {
573 | this._handleStartTag(startTagMatch);
574 | continue;
575 | }
576 | }
577 | var text;
578 | if (textEnd >= 0) {
579 | // 匹配标签文本内容
580 | text = this.template.substring(0, textEnd);
581 | this._advance(textEnd);
582 | }
583 |
584 | if (textEnd < 0) {
585 | text = this.template;
586 | this.template = '';
587 | }
588 |
589 | if (text) {
590 | this._chars(text);
591 | }
592 | }
593 | }
594 | /**
595 | * 根据ast转成字符串code
596 | * html代码:
597 | *
598 | * {{testData}}
599 | *
600 | *
601 | * 转换后的code:
602 | * _c('div', { attrs: { id: 'app' } }, [_c('span', { style: testComputed }, [_v(_s(testData))]), _c('span', { on: { click: clickFn } })]);
603 | */
604 | _converCode(el) {
605 | let data = this._setGenCode(el);
606 |
607 | let children = this._getChildren(el);
608 |
609 | // 处理文本表达式
610 | if (!el.tag && el.type === 2) {
611 | return `_v(${el.expression})`;
612 | }
613 |
614 | // 处理文本
615 | if (!el.tag && el.type === 3) {
616 | return `_v("${el.text}")`;
617 | }
618 | return "_c('" + el.tag + "'" + (data ? ',' + data : '') + (children ? ',' + children : '') + ')';
619 | }
620 | /**
621 | * 生成code
622 | * @param {Object} el ast树
623 | */
624 | _setGenCode(el) {
625 | let data = '{';
626 |
627 | if (el.staticClass) {
628 | data += `staticClass:"${el.staticClass}",`;
629 | }
630 | if (el.classBinding) {
631 | data += `class:${el.classBinding},`;
632 | }
633 | if (el.staticStyle) {
634 | data += `staticStyle:"${el.staticStyle}",`;
635 | }
636 | if (el.styleBinding) {
637 | data += `style:${el.styleBinding},`;
638 | }
639 | // 处理属性
640 | if (el.attrs) {
641 | data += `attrs:{${this._genProps(el.attrs)}},`;
642 | }
643 | // 处理事件
644 | if (el.event) {
645 | data += `on:{${this._genHandlers(el.event)}},`;
646 | }
647 | // 处理指令
648 | if(el.directives) {
649 | data += `directives:${this._genDirectives(el.directives)},`;
650 | }
651 |
652 | // 处理domProps
653 | if(el.props) {
654 | data += `domProps:{${this._genProps(el.props, true)}},`;
655 | }
656 |
657 | data = data.replace(/,$/, '') + '}';
658 |
659 | // 如果没有属性就直接返回空
660 | if (/^\{\}$/.test(data)) {
661 | data = '';
662 | }
663 |
664 | return data;
665 | }
666 | /**
667 | * 处理属性字段
668 | * @param {Array} props 标签属性元素集合
669 | */
670 | _genProps(props, flag) {
671 | let res = '';
672 | for (let i = 0; i < props.length; i++) {
673 | let prop = props[i];
674 | {
675 | flag ? res += '"' + prop.name + '":' + prop.value + ',' : res += '"' + prop.name + '":"' + prop.value + '",';
676 | }
677 | }
678 | return res.slice(0, -1);
679 | }
680 | /**
681 | * 处理事件
682 | */
683 | _genHandlers(events) {
684 | let res = '';
685 | for (var name in events) {
686 | res += '"' + name + '":' + events[name].value + ',';
687 | }
688 | return res.slice(0, -1);
689 | }
690 | /**
691 | * 处理指令
692 | * @param {Array} directives
693 | */
694 | _genDirectives(directives) {
695 | let _code = '[';
696 | directives.map((item,index) => {
697 | _code += `{name:"${item.name}",rawName:"${item.rawName}",value:(${item.value}),expression:"${item.value}"}${index === directives.length - 1 ? ']': ','}`;
698 | });
699 | return _code
700 | }
701 | /**
702 | * 处理子节点
703 | * @param {Object} el 当前节点的Ast树
704 | */
705 | _getChildren(el) {
706 | let children = el.children;
707 | if (children && children.length) {
708 | return (
709 | '[' +
710 | children.map(item => {
711 | return this._converCode(item);
712 | }) +
713 | ']'
714 | );
715 | }
716 | }
717 | }
718 |
719 | /*
720 | * @Author: xiaoai
721 | * @Date: 2018-11-15 15:56:03
722 | * @LastEditors: xiaoai
723 | * @LastEditTime: 2018-12-07 16:15:48
724 | * @Description: Whatever is worth doing is worth doing well(任何值得做的事就值得把它做好)
725 | */
726 |
727 | let uid$1 = 0;
728 | /**
729 | * 观察者 Watcher
730 | * 主要作用是进行依赖收集的观察者和更新视图
731 | * 当依赖收集的时候会调用Dep对象的addSub方法,在修改data中数据的时候会触发Dep对象的notify,通知所有Watcher对象去修改对应视图
732 | */
733 | class Watcher {
734 | /**
735 | * @param {Object} vm miniVue实例对象
736 | * @param {Function} expOrFn watch监听函数
737 | * @param {Function} cb 回调触发视图更新函数
738 | */
739 | constructor(vm, expOrFn, cb = noop$1) {
740 | this.vm = vm;
741 | // 设置id防止重复添加
742 | this.id = uid$1++;
743 | // 保存监听函数为字符串,错误提示会使用
744 | this.expression = expOrFn.toString();
745 | // 新的依赖项id集合
746 | this.newDepIds = new Set();
747 | // 新的依赖项 临时值在依赖收集完成之后会马上清除
748 | this.newDeps = [];
749 | // 添加后的依赖项id集合
750 | this.depIds = new Set();
751 | // 添加后的依赖项 依赖收集完成会从newDeps中取出值赋值给自己
752 | this.deps = [];
753 | // 回调触发视图更新函数
754 | this.cb = cb;
755 | // 获取当前watcher表达式
756 | if (typeof expOrFn === 'function') {
757 | this.getter = expOrFn;
758 | } else {
759 | debug$1('error', this.expression + 'Not a function');
760 | }
761 | }
762 | get() {
763 | // 更新当前watcher赋值给Dep.target,并且添加到target栈
764 | pushTarget(this);
765 | let value = this.getter.call(this.vm, this.vm);
766 | // 将观察者实例从target栈中取出并设置给Dep.target
767 | popTarget();
768 | // 清除依赖
769 | this.cleanupDeps();
770 | this.value = value;
771 | return value;
772 | }
773 | /**
774 | * 添加依赖
775 | * @param {Object} dep Dep实例对象
776 | */
777 | addDep(dep) {
778 | let _id = dep.id;
779 | // 如果没有添加依赖项就进行添加
780 | if (!this.newDepIds.has(_id)) {
781 | this.newDepIds.add(_id);
782 | this.newDeps.push(dep);
783 | if (!this.depIds.has(_id)) {
784 | dep.addSub(this);
785 | }
786 | }
787 | }
788 | /**
789 | * 清除依赖收集
790 | */
791 | cleanupDeps() {
792 | /*移除所有观察者对象*/
793 | let i = this.deps.length;
794 | while (i--) {
795 | const dep = this.deps[i];
796 | if (!this.newDepIds.has(dep.id)) {
797 | dep.removeSub(this);
798 | }
799 | }
800 | // 清除所有依赖数据,把newDeps数据赋值给deps存储依赖
801 | let tmp = this.depIds;
802 | this.depIds = this.newDepIds;
803 | this.newDepIds = tmp;
804 | this.newDepIds.clear();
805 | tmp = this.deps;
806 | this.deps = this.newDeps;
807 | this.newDeps = tmp;
808 | this.newDeps.length = 0;
809 | }
810 | /**
811 | * 收集依赖
812 | */
813 | depend() {
814 | let i = this.deps.length;
815 | while (i--) {
816 | this.deps[i].depend();
817 | }
818 | }
819 | /**
820 | * 触发更新
821 | */
822 | update() {
823 | this.run();
824 | // queueWatcher(this);
825 | }
826 | /**
827 | * update函数会调该函数进行更新回调
828 | */
829 | run() {
830 | let value = this.get();
831 | if (value !== this.value) {
832 | let oldValue = this.value;
833 | this.value = value;
834 | this.cb.call(this.vm, value, oldValue);
835 | }
836 | }
837 | }
838 |
839 | /*
840 | * @Author: xiaoai
841 | * @Date: 2018-12-06 15:58:11
842 | * @LastEditors: xiaoai
843 | * @LastEditTime: 2018-12-06 17:01:38
844 | * @Description: Whatever is worth doing is worth doing well(任何值得做的事就值得把它做好)
845 | */
846 | // 代码来源:vue源码/vue-dev/src/platforms/web/runtime/node-ops.js
847 | function createElement (tagName, vnode) {
848 | const elm = document.createElement(tagName);
849 | if (tagName !== 'select') {
850 | return elm
851 | }
852 | // false or null will remove the attribute but undefined will not
853 | if (vnode.data && vnode.data.attrs && vnode.data.attrs.multiple !== undefined) {
854 | elm.setAttribute('multiple', 'multiple');
855 | }
856 | return elm
857 | }
858 |
859 | // export function createElementNS (namespace, tagName) {
860 | // return document.createElementNS(namespaceMap[namespace], tagName)
861 | // }
862 |
863 | function createTextNode (text) {
864 | return document.createTextNode(text)
865 | }
866 |
867 | function createComment (text) {
868 | return document.createComment(text)
869 | }
870 |
871 | function insertBefore (parentNode, newNode, referenceNode) {
872 | parentNode.insertBefore(newNode, referenceNode);
873 | }
874 |
875 | function removeChild (node, child) {
876 | node.removeChild(child);
877 | }
878 |
879 | function appendChild (node, child) {
880 | node.appendChild(child);
881 | }
882 |
883 | function parentNode (node) {
884 | return node.parentNode
885 | }
886 |
887 | function nextSibling (node) {
888 | return node.nextSibling
889 | }
890 |
891 | function tagName (node) {
892 | return node.tagName
893 | }
894 |
895 | function setTextContent (node, text) {
896 | node.textContent = text;
897 | }
898 |
899 | function setStyleScope (node, scopeId) {
900 | node.setAttribute(scopeId, '');
901 | }
902 |
903 | var nodeOptions = {
904 | createElement,createTextNode,createComment,insertBefore,removeChild,appendChild,parentNode,nextSibling,tagName,setTextContent,setStyleScope
905 | };
906 |
907 | /*
908 | * @Author: xiaoai
909 | * @Date: 2018-12-06 17:03:04
910 | * @LastEditors: xiaoai
911 | * @LastEditTime: 2018-12-07 21:52:28
912 | * @Description: Whatever is worth doing is worth doing well(任何值得做的事就值得把它做好)
913 | */
914 |
915 | function createFnInvoker(fns) {
916 | function invoker() {
917 | var arguments$1 = arguments;
918 |
919 | var fns = invoker.fns;
920 | if (Array.isArray(fns)) {
921 | var cloned = fns.slice();
922 | for (var i = 0; i < cloned.length; i++) {
923 | cloned[i].apply(null, arguments$1);
924 | }
925 | } else {
926 | // return handler return value for single handlers
927 | return fns.apply(null, arguments);
928 | }
929 | }
930 | invoker.fns = fns;
931 | return invoker;
932 | }
933 |
934 | /**
935 | * element类
936 | * 根据VNode创建真实dom元素
937 | */
938 | class Element {
939 | /**
940 | * @param {element对象} oldElem element元素
941 | * @param {Object} vnode vnode虚拟dom
942 | * @param {Object} prevVnode 更新上一次的虚拟dom
943 | * @param {element对象} parentElm 父元素ele对象
944 | */
945 | constructor(vm, oldElem, vnode, prevVnode, parentElm) {
946 | this.vm = vm;
947 | if (prevVnode) {
948 | callHook(vm, 'beforeUpdate');
949 | }
950 | this.prevVnode = prevVnode ? prevVnode : { data: {} };
951 | this.createElement(vnode, parentElm, prevVnode);
952 | this.removeVnodes(oldElem);
953 | }
954 | /**
955 | * vue diff算法实现
956 | */
957 | path() {
958 | return true;
959 | }
960 | /**
961 | * 删除老元素
962 | * @param {element对象} oldElem 上一次的元素
963 | */
964 | removeVnodes(oldElem) {
965 | nodeOptions.removeChild(oldElem.parentNode, oldElem);
966 | }
967 | /**
968 | * 根据VNode创建真实dom元素
969 | * @param {Object} vnode VNode
970 | * @param {ele} parentElm 父元素
971 | */
972 | createElement(vnode, parentElm) {
973 | this.path();
974 | // 没有父元素就直接默认body
975 | if (!parentElm) {
976 | parentElm = document.querySelector('body');
977 | }
978 | let data = vnode.data;
979 | let children = vnode.children;
980 | let tag = vnode.tag;
981 |
982 | // 有tag就创建一个标签,没有就当成文本节点创建
983 | if (tag) {
984 | vnode.elm = nodeOptions.createElement(tag, vnode);
985 | } else {
986 | vnode.elm = nodeOptions.createTextNode(vnode.text);
987 | }
988 |
989 | // 如果有子元素数据,递归创建子元素
990 | this.createChildren(vnode, children);
991 |
992 | if (data) {
993 | this.updateAttrs(this.prevVnode, vnode);
994 | this.updateClass(this.prevVnode, vnode);
995 | this.updateDOMListeners(this.prevVnode, vnode);
996 | this.updateStyle(this.prevVnode, vnode);
997 | }
998 | // 添加都对应父元素下面
999 | if (parentElm !== undefined || parentElm !== null) {
1000 | nodeOptions.appendChild(parentElm, vnode.elm);
1001 | }
1002 | }
1003 | /**
1004 | * 递归创建孩子节点
1005 | * @param {Object} vnode VNode
1006 | * @param {Array} children 孩子VNode
1007 | */
1008 | createChildren(vnode, children) {
1009 | if (Array.isArray(children)) {
1010 | let [i, len] = [0, children.length];
1011 | for (; i < len; i++) {
1012 | this.createElement(children[i], vnode.elm);
1013 | }
1014 | }
1015 | }
1016 | /**
1017 | * 更新元素属性
1018 | * @param oldVnode
1019 | * @param vnode
1020 | */
1021 | updateAttrs(oldVnode, vnode) {
1022 | let elm = vnode.elm;
1023 | let oldAttrs = oldVnode.data.attrs || {};
1024 | let attrs = vnode.data.attrs || {};
1025 |
1026 | // 整合attrs和domProps
1027 | if(vnode.data.domProps) {
1028 | attrs = Object.assign({}, attrs, vnode.data.domProps);
1029 | }
1030 |
1031 | var cur, old;
1032 | for (let key in attrs) {
1033 | cur = attrs[key];
1034 | old = oldAttrs[key];
1035 | // if (old !== cur) {
1036 | elm.setAttribute(key, cur);
1037 | // }
1038 | }
1039 | }
1040 | /**
1041 | * 更新元素class
1042 | * @param oldVnode
1043 | * @param vnode
1044 | */
1045 | updateClass(oldVnode, vnode) {
1046 | let elm = vnode.elm;
1047 | let oldStaticClass = oldVnode.data.staticClass || '';
1048 | let staticClass = vnode.data.staticClass || '';
1049 |
1050 | let oldClass = oldVnode.data.class || '';
1051 | let _class = vnode.data.class || '';
1052 |
1053 | if (staticClass || _class) {
1054 | let _cls = [].concat(staticClass, _class);
1055 | elm.setAttribute('class', _cls.join(' '));
1056 | }
1057 | }
1058 | /**
1059 | * 绑定元素事件
1060 | * @param oldVnode
1061 | * @param vnode
1062 | */
1063 | updateDOMListeners(oldVnode, vnode) {
1064 | let on = vnode.data.on || {};
1065 | let oldOn = oldVnode.data.on || {};
1066 | var cur;
1067 | for (let name in on) {
1068 | cur = createFnInvoker(on[name]);
1069 | vnode.elm.addEventListener(name, cur, false);
1070 | }
1071 | }
1072 | /**
1073 | * 更新元素样式
1074 | * @param oldVnode
1075 | * @param vnode
1076 | */
1077 | updateStyle(oldVnode, vnode) {
1078 | let elm = vnode.elm;
1079 | let oldStaticStyle = oldVnode.data.staticStyle || '';
1080 | let staticStyle = vnode.data.staticStyle || '';
1081 |
1082 | let oldStyle = oldVnode.data.style || {};
1083 | let _style = vnode.data.style || {};
1084 |
1085 | // 如果直接写在标签上面的style遍历属性添加到最新的dom上面
1086 | let styleArray = staticStyle.split(';');
1087 |
1088 | if (styleArray && styleArray.length) {
1089 | styleArray.map(item => {
1090 | let _s = item.split(':');
1091 | if (_s && _s.length) {
1092 | elm.style[_s[0]] = _s[1];
1093 | }
1094 | });
1095 | }
1096 |
1097 | // 添加样式
1098 | for (let key in _style) {
1099 | elm.style[key] = _style[key];
1100 | }
1101 | }
1102 | }
1103 |
1104 | /*
1105 | * @Author: xiaoai
1106 | * @Date: 2018-12-04 19:53:19
1107 | * @LastEditors: xiaoai
1108 | * @LastEditTime: 2018-12-06 15:28:47
1109 | * @Description: Whatever is worth doing is worth doing well(任何值得做的事就值得把它做好)
1110 | */
1111 |
1112 | /**
1113 | * 虚拟Dom基类
1114 | */
1115 | class VNode {
1116 | constructor(tag, data, children, text, elm, context, componentOptions, asyncFactory) {
1117 | /*当前节点的标签名*/
1118 | this.tag = tag;
1119 | /*当前节点对应的对象,包含了具体的一些数据信息,是一个VNodeData类型,可以参考VNodeData类型中的数据信息*/
1120 | this.data = data;
1121 | /*当前节点的子节点,是一个数组*/
1122 | this.children = children;
1123 | /*当前节点的文本*/
1124 | this.text = text;
1125 | /*当前虚拟节点对应的真实dom节点*/
1126 | this.elm = elm;
1127 | /*当前节点的名字空间*/
1128 | this.ns = undefined;
1129 | /*编译作用域*/
1130 | this.context = context;
1131 | /*函数化组件作用域*/
1132 | this.functionalContext = undefined;
1133 | /*节点的key属性,被当作节点的标志,用以优化*/
1134 | this.key = data && data.key;
1135 | /*组件的option选项*/
1136 | this.componentOptions = componentOptions;
1137 | /*当前节点对应的组件的实例*/
1138 | this.componentInstance = undefined;
1139 | /*当前节点的父节点*/
1140 | this.parent = undefined;
1141 | /*简而言之就是是否为原生HTML或只是普通文本,innerHTML的时候为true,textContent的时候为false*/
1142 | this.raw = false;
1143 | /*静态节点标志*/
1144 | this.isStatic = false;
1145 | /*是否作为跟节点插入*/
1146 | this.isRootInsert = true;
1147 | /*是否为注释节点*/
1148 | this.isComment = false;
1149 | /*是否为克隆节点*/
1150 | this.isCloned = false;
1151 | /*是否有v-once指令*/
1152 | this.isOnce = false;
1153 | }
1154 | child() {
1155 | return this.componentInstance;
1156 | }
1157 | }
1158 | /**
1159 | * 创建空节点
1160 | */
1161 | const createEmptyVNode = (text = '') => {
1162 | const node = new VNode();
1163 | node.text = text;
1164 | node.isComment = true;
1165 | return node;
1166 | };
1167 | /**
1168 | * 创建文本节点
1169 | */
1170 | function createTextVNode(val) {
1171 | return new VNode(undefined, undefined, undefined, String(val));
1172 | }
1173 | /**
1174 | * 创建元素
1175 | * @param {Object} context miniVue实例
1176 | * @param {String} tag 标签
1177 | * @param {Object} data 数据
1178 | * @param {Array} children 子节点
1179 | */
1180 | function createElement$1(context, tag, data, children) {
1181 | var vnode;
1182 |
1183 | if(!tag) {
1184 | createEmptyVNode();
1185 | }
1186 | // 兼容不传data的情况, 处理{{a}} 这种dom情况,字符串function为: _c('span', [_v(_s(a))])
1187 | if(Array.isArray(data)) {
1188 | children = data;
1189 | data = undefined;
1190 | }
1191 |
1192 | if(typeof tag === 'string') {
1193 | vnode = new VNode(tag, data, children, undefined, undefined, context);
1194 | }
1195 |
1196 | if(vnode !== undefined) {
1197 | return vnode
1198 | }
1199 | }
1200 |
1201 | /*
1202 | * @Author: xiaoai
1203 | * @Date: 2018-11-15 15:55:52
1204 | * @LastEditors: xiaoai
1205 | * @LastEditTime: 2018-12-08 19:36:05
1206 | * @Description: Whatever is worth doing is worth doing well(任何值得做的事就值得把它做好)
1207 | */
1208 |
1209 | let uid$2 = 0;
1210 | /**
1211 | * 主函数入口
1212 | */
1213 | class MiniVue {
1214 | constructor(options) {
1215 | if (new.target !== MiniVue) {
1216 | throw new Error('必须使用 new 命令生成实例');
1217 | }
1218 | this.id = uid$2++;
1219 | this._self = this;
1220 | this.$options = options;
1221 | this.init(options);
1222 | }
1223 | init() {
1224 | let vm = this;
1225 | callHook(vm, 'beforeCreated');
1226 | // 创建元素
1227 | this._c = function(a, b, c, d) {
1228 | return createElement$1(vm, a, b, c, d);
1229 | };
1230 | // 创建文本节点
1231 | this._v = createTextVNode;
1232 | // 序列化字符串 创建文本节点并且里面含有{{}}表达式,先处理表达式得到值,在进行字符串转换
1233 | this._s = function(val) {
1234 | return val.toString();
1235 | };
1236 | // 初始化data数据,代理数据和数据劫持
1237 | this._initData();
1238 | // 初始化computed
1239 | this._initComputed();
1240 | // 初始化methods
1241 | this._initMethod();
1242 | // 编译render,创建虚拟Dom
1243 | this.mounted();
1244 | }
1245 | /**
1246 | * 代理函数
1247 | * 将data上面的属性代理到了vm实例上,这样就可以用app.text代替app._data.text了
1248 | * @param {Object} target 代理目标对象
1249 | * @param {String} sourceKey 代理key
1250 | * @param {String} key 目标key
1251 | */
1252 | proxy(target, sourceKey, key) {
1253 | let vm = this;
1254 | Object.defineProperty(target, key, {
1255 | enumerable: true,
1256 | configurable: true,
1257 | get: function() {
1258 | return vm[sourceKey][key];
1259 | },
1260 | set: function(val) {
1261 | vm[sourceKey][key] = val;
1262 | }
1263 | });
1264 | }
1265 | /**
1266 | * 初始化data对象数据,收集数据添加监听
1267 | */
1268 | _initData() {
1269 | let vm = this;
1270 | let data = this.$options.data;
1271 | // data支持两种写法(函数和对象)
1272 | // 如果data是函数就直接执行拿到返回值,如果是对象直接返回
1273 | data = vm._data = typeof data === 'function' ? data.call(vm) : data || {};
1274 |
1275 | const keys = Object.keys(data);
1276 | let i = keys.length;
1277 | while (i--) {
1278 | let key = keys[i];
1279 | this.proxy(vm, '_data', key);
1280 | }
1281 | observe(data, vm);
1282 | }
1283 | /**
1284 | * 初始化methods方法挂载miniVue实例上
1285 | */
1286 | _initMethod() {
1287 | for (let key in this.$options.methods) {
1288 | this[key] = this.$options.methods[key] == null ? noop : this.$options.methods[key].bind(this);
1289 | }
1290 | }
1291 | /**
1292 | * 初始化computed,添加依赖监听
1293 | */
1294 | _initComputed() {
1295 | let vm = this;
1296 | let noop = function() {};
1297 | let watchers = (vm._computedWatchers = Object.create(null));
1298 |
1299 | if (this.$options.computed) {
1300 | for (let key in this.$options.computed) {
1301 | var userDef = this.$options.computed[key];
1302 | watchers[key] = new Watcher(vm, userDef, noop);
1303 |
1304 | if (!(key in vm)) {
1305 | defineComputed(vm, key, userDef);
1306 | }
1307 | }
1308 | }
1309 | }
1310 | /**
1311 | * 编译模版
1312 | */
1313 | mounted() {
1314 | let vm = this;
1315 | callHook(vm, 'created');
1316 | if (this.$options.el) {
1317 | callHook(vm, 'beforeMount');
1318 | // 编译template
1319 | let compiler = new Compiler(vm, this.$options);
1320 | // 将AST转化成render function字符串
1321 | this.$options.render = new Function('with(this){return ' + compiler.render + '}');
1322 |
1323 | let updateComponent = function() {
1324 | let prevVnode = vm._vnode;
1325 | let oldElem = document.querySelector(this.$options.el);
1326 | // 根据render function字符串创建VNode虚拟Dom
1327 | vm.vnode = vm.$options.render.call(vm);
1328 | vm._vnode = vm.vnode;
1329 | // 根据VNode创建真实dom元素
1330 | new Element(vm, oldElem, vm.vnode, prevVnode, null);
1331 | };
1332 |
1333 | // 把渲染函数添加到wather里面,如果有数据更新就重新执行渲染函数进行页面更新
1334 | new Watcher(vm, updateComponent, function() {}).get();
1335 | callHook(vm, 'mounted');
1336 | }
1337 | }
1338 | }
1339 |
1340 | return MiniVue;
1341 |
1342 | })));
1343 |
--------------------------------------------------------------------------------
/dist/MiniVue.min.js:
--------------------------------------------------------------------------------
1 | !function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t.MiniVue=t.MiniVue||{},t.MiniVue.min=e())}(this,function(){"use strict";function n(t){return(n="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(t)}function c(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}function i(t,e){for(var n=0;n]*>/,startTagOpen:/^<((?:[a-zA-Z_][\w\-\.]*\:)?[a-zA-Z_][\w\-\.]*)/,startTagClose:/^\s*(\/?)>/,attribute:/^\s*([^\s"'<>\/=]+)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/,comment:/^]+>/i,defaultTagRE:/\{\{((?:.|\n)+?)\}\}/g},y=["input","br","hr"],b=function(){function n(t,e){c(this,n),this.vm=t,this.ast={},this.$optins=e,this.$el=document.querySelector(e.el),this.$el.outerHTML&&this.compileToFunctions(this.$el.outerHTML)}return a(n,[{key:"compileToFunctions",value:function(t){this.template=t,this._convertHtml2Ast(),this.render=this._converCode(this.ast)}},{key:"_advance",value:function(t){this.index+=t,this.template=this.template.substring(t)}},{key:"_parseStartTag",value:function(){var t=this.template.match(g.startTagOpen);if(t){var e,n,i={tagName:t[1],attrs:[],start:this.index};for(this._advance(t[0].length);!(e=this.template.match(g.startTagClose))&&(n=this.template.match(g.attribute));)this._advance(n[0].length),i.attrs.push(n);if(e)return i.unarySlash=e[1],this._advance(e[0].length),i.end=this.index,i}}},{key:"_parseEndTag",value:function(){var t=this.stack[this.stack.length-1],e=t.children[t.children.length-1];e&&3===e.type&&" "===e.text&&t.children.pop(),this.stack.length-=1,this.currentParent=this.stack[this.stack.length-1]}},{key:"_handleStartTag",value:function(t){var e=[];this.unary=!!t.unarySlash,t.attrs.map(function(t){e.push({name:t[1],value:t[3]||t[4]||t[5]||""})});var n=this._createASTElement(t,e,this.currentParent);this.ast||(this.ast=n),this.currentParent&&(this.currentParent.children.push(n),n.parent=this.currentParent),!this.unary&&y.indexOf(n.tag)<0&&(this.stack.push(n),this.currentParent=n)}},{key:"_createASTElement",value:function(t,e,n){for(var i,a,s,r,o=/^:(class|style)$/,u=/^(class|style)$/,c={},l={},h=[],d=[],v=[],p=!1,f=0,m=e.length;f 结尾
60 | if (end) {
61 | match.unarySlash = end[1];
62 | this._advance(end[0].length);
63 | match.end = this.index;
64 | return match;
65 | }
66 | }
67 | }
68 | /**
69 | * 处理结束标签
70 | */
71 | _parseEndTag() {
72 | // 获取当前栈中最后一个元素
73 | let element = this.stack[this.stack.length - 1];
74 | let lastNode = element.children[element.children.length - 1];
75 |
76 | // 如果是注释文本就删除
77 | if (lastNode && lastNode.type === 3 && lastNode.text === ' ') {
78 | element.children.pop();
79 | }
80 |
81 | // 出栈
82 | this.stack.length -= 1;
83 | this.currentParent = this.stack[this.stack.length - 1];
84 | }
85 | /**
86 | * 处理开始标签中的匹配到属性添加ast中
87 | * @param Object startTagMatch<{attrs:Array,tagName: String, start: Number, end: Number}>
88 | */
89 | _handleStartTag(startTagMatch) {
90 | let attrs = [];
91 |
92 | // 当前标签是否是以 > 结尾的flag
93 | this.unary = !!startTagMatch.unarySlash;
94 | startTagMatch.attrs.map(item => {
95 | attrs.push({
96 | name: item[1],
97 | value: item[3] || item[4] || item[5] || ''
98 | });
99 | });
100 | let element = this._createASTElement(startTagMatch, attrs, this.currentParent);
101 | // 创建ast
102 | if (!this.ast) {
103 | this.ast = element;
104 | }
105 |
106 | if (this.currentParent) {
107 | this.currentParent.children.push(element);
108 | element.parent = this.currentParent;
109 | }
110 |
111 | // 如果不是结束 > 标签就添加到堆栈中和记录当前父级
112 | if (!this.unary && singleLabel.indexOf(element.tag) < 0) {
113 | this.stack.push(element);
114 | this.currentParent = element;
115 | }
116 | }
117 | /**
118 | * 创建ast树
119 | * @param {Object} startTagMatch
120 | * @param {Array} attrs
121 | * @param {Object} parent
122 | */
123 | _createASTElement(startTagMatch, attrs, parent) {
124 | // 根元素 type为1
125 | var class2styleExpReg = /^:(class|style)$/;
126 | var class2styleReg = /^(class|style)$/;
127 | var map = {};
128 | var event = {};
129 | var _attrs = [];
130 | var props = [];
131 | var directives = [];
132 | var isEvent = false;
133 | var staticClass, staticStyle, styleBinding, classBinding;
134 |
135 | for (var i = 0, l = attrs.length; i < l; i++) {
136 | map[attrs[i].name] = attrs[i].value;
137 | /**
138 | * 1.匹配 @ 符号表示是绑定事件
139 | * 2.匹配 :class :style 表达式class和表达式style
140 | * 3.匹配 class style 静态class和静态style
141 | * 4.普通数据(如: id)
142 | */
143 | if (attrs[i].name.match(/^@/g)) {
144 | isEvent = true;
145 | event[attrs[i].name.match(/\w*$/)[0]] = { value: attrs[i].value };
146 | } else if (class2styleReg.test(attrs[i].name)) {
147 | attrs[i].name.indexOf('class') > -1 ? (staticClass = attrs[i].value) : (staticStyle = attrs[i].value);
148 | } else if (class2styleExpReg.test(attrs[i].name)) {
149 | attrs[i].name.indexOf(':class') > -1 ? (classBinding = attrs[i].value) : (styleBinding = attrs[i].value);
150 | } else if (attrs[i].name === 'v-model') {
151 | isEvent = true;
152 | event['input'] = { value: `function($event){if($event.target.composing)return;${attrs[i].value}=$event.target.value}` };
153 |
154 | props.push({
155 | name: 'value',
156 | value: `(${attrs[i].value})`
157 | });
158 |
159 | directives.push({
160 | arg: null,
161 | modifiers: undefined,
162 | name: 'model',
163 | rawName: 'v-model',
164 | value: attrs[i].value
165 | });
166 | } else {
167 | _attrs.push({
168 | name: attrs[i].name,
169 | value: attrs[i].value
170 | });
171 | }
172 | }
173 | // 默认根ast数据结构
174 | var astMap = {
175 | type: 1,
176 | tag: startTagMatch.tagName,
177 | attrsList: attrs,
178 | attrsMap: map,
179 | parent: parent,
180 | children: []
181 | };
182 |
183 | // 如果有事件绑定就添加到ast中
184 | if (isEvent) {
185 | astMap = Object.assign({}, astMap, { event });
186 | // 处理v-model指令
187 | props.length && (astMap = Object.assign({}, astMap, { props, directives }));
188 | }
189 | // 属性值
190 | if (_attrs.length) {
191 | astMap = Object.assign({}, astMap, { attrs: _attrs });
192 | }
193 | // 静态class
194 | if (staticClass) {
195 | astMap = Object.assign({}, astMap, { staticClass });
196 | }
197 | // 静态样式
198 | if (staticStyle) {
199 | astMap = Object.assign({}, astMap, { staticStyle });
200 | }
201 | // 表达式样式
202 | if (styleBinding) {
203 | astMap = Object.assign({}, astMap, { styleBinding });
204 | }
205 | // 表达式class
206 | if (classBinding) {
207 | astMap = Object.assign({}, astMap, { classBinding });
208 | }
209 | return astMap;
210 | }
211 | /**
212 | * 转换attrs
213 | * @param {Array} attrs
214 | * @returns Object
215 | */
216 | _makeAttrsMap(attrs) {
217 | var map = {};
218 | for (var i = 0, l = attrs.length; i < l; i++) {
219 | map[attrs[i].name] = attrs[i].value;
220 | }
221 | return map;
222 | }
223 | /**
224 | * 处理文本内容
225 | * @param String text 文本节点内容
226 | */
227 | _chars(text) {
228 | if (!this.currentParent) {
229 | return;
230 | }
231 | let children = this.currentParent.children;
232 | text = text.trim();
233 |
234 | if (text) {
235 | var res;
236 | // 文本节点并且是表达式
237 | if (text !== ' ' && (res = this._parseText(text))) {
238 | children.push({
239 | type: 2,
240 | expression: res.expression,
241 | tokens: res.tokens,
242 | text: text
243 | });
244 | } else if (text !== ' ' || !children.length || children[children.length - 1].text !== ' ') {
245 | // 普通文本节点
246 | children.push({
247 | type: 3,
248 | text: text
249 | });
250 | }
251 | }
252 | }
253 | /**
254 | * 如果文本中含有{{}}表达式进行转换
255 | * @param {String} text text 文本节点内容
256 | */
257 | _parseText(text) {
258 | if (!regExp.defaultTagRE.test(text)) {
259 | return;
260 | }
261 | var tokens = [];
262 | var rawTokens = [];
263 | var lastIndex = (regExp.defaultTagRE.lastIndex = 0);
264 | var match, index, tokenValue;
265 | while ((match = regExp.defaultTagRE.exec(text))) {
266 | index = match.index;
267 |
268 | if (index > lastIndex) {
269 | rawTokens.push((tokenValue = text.slice(lastIndex, index)));
270 | tokens.push(JSON.stringify(tokenValue));
271 | }
272 | // 构造表达式
273 | var exp = match[1].trim();
274 | tokens.push('_s(' + exp + ')');
275 | rawTokens.push({ '@binding': exp });
276 | lastIndex = index + match[0].length;
277 | }
278 | if (lastIndex < text.length) {
279 | rawTokens.push((tokenValue = text.slice(lastIndex)));
280 | tokens.push(JSON.stringify(tokenValue));
281 | }
282 | return {
283 | expression: tokens.join('+'),
284 | tokens: rawTokens
285 | };
286 | }
287 | /**
288 | * html转为Ast
289 | * @returns Object AST语法树
290 | */
291 | _convertHtml2Ast() {
292 | this.ast = null;
293 | this.stack = [];
294 | this.index = 0;
295 |
296 | while (this.template) {
297 | let textEnd = this.template.indexOf('<');
298 |
299 | if (textEnd === 0) {
300 | // 如果是注释标签直接跳过编译
301 | if (regExp.comment.test(this.template)) {
302 | let commentEnd = this.template.indexOf('-->');
303 | this._advance(commentEnd + 3);
304 | continue;
305 | }
306 |
307 | // 匹配结束标签
308 | let endTagMatch = this.template.match(regExp.endTag);
309 | if (endTagMatch) {
310 | let _index = this.index;
311 | this._advance(endTagMatch[0].length);
312 | this._parseEndTag(endTagMatch[1], _index, this.index);
313 | continue;
314 | }
315 |
316 | // 匹配开始标签
317 | let startTagMatch = this._parseStartTag();
318 | if (startTagMatch) {
319 | this._handleStartTag(startTagMatch);
320 | continue;
321 | }
322 | }
323 | var text;
324 | if (textEnd >= 0) {
325 | // 匹配标签文本内容
326 | text = this.template.substring(0, textEnd);
327 | this._advance(textEnd);
328 | }
329 |
330 | if (textEnd < 0) {
331 | text = this.template;
332 | this.template = '';
333 | }
334 |
335 | if (text) {
336 | this._chars(text);
337 | }
338 | }
339 | }
340 | /**
341 | * 根据ast转成字符串code
342 | * html代码:
343 | *
344 | * {{testData}}
345 | *
346 | *
347 | * 转换后的code:
348 | * _c('div', { attrs: { id: 'app' } }, [_c('span', { style: testComputed }, [_v(_s(testData))]), _c('span', { on: { click: clickFn } })]);
349 | */
350 | _converCode(el) {
351 | let data = this._setGenCode(el);
352 |
353 | let children = this._getChildren(el);
354 |
355 | // 处理文本表达式
356 | if (!el.tag && el.type === 2) {
357 | return `_v(${el.expression})`;
358 | }
359 |
360 | // 处理文本
361 | if (!el.tag && el.type === 3) {
362 | return `_v("${el.text}")`;
363 | }
364 | return "_c('" + el.tag + "'" + (data ? ',' + data : '') + (children ? ',' + children : '') + ')';
365 | }
366 | /**
367 | * 生成code
368 | * @param {Object} el ast树
369 | */
370 | _setGenCode(el) {
371 | let data = '{';
372 |
373 | if (el.staticClass) {
374 | data += `staticClass:"${el.staticClass}",`;
375 | }
376 | if (el.classBinding) {
377 | data += `class:${el.classBinding},`;
378 | }
379 | if (el.staticStyle) {
380 | data += `staticStyle:"${el.staticStyle}",`;
381 | }
382 | if (el.styleBinding) {
383 | data += `style:${el.styleBinding},`;
384 | }
385 | // 处理属性
386 | if (el.attrs) {
387 | data += `attrs:{${this._genProps(el.attrs)}},`;
388 | }
389 | // 处理事件
390 | if (el.event) {
391 | data += `on:{${this._genHandlers(el.event)}},`;
392 | }
393 | // 处理指令
394 | if(el.directives) {
395 | data += `directives:${this._genDirectives(el.directives)},`
396 | }
397 |
398 | // 处理domProps
399 | if(el.props) {
400 | data += `domProps:{${this._genProps(el.props, true)}},`
401 | }
402 |
403 | data = data.replace(/,$/, '') + '}';
404 |
405 | // 如果没有属性就直接返回空
406 | if (/^\{\}$/.test(data)) {
407 | data = '';
408 | }
409 |
410 | return data;
411 | }
412 | /**
413 | * 处理属性字段
414 | * @param {Array} props 标签属性元素集合
415 | */
416 | _genProps(props, flag) {
417 | let res = '';
418 | for (let i = 0; i < props.length; i++) {
419 | let prop = props[i];
420 | {
421 | flag ? res += '"' + prop.name + '":' + prop.value + ',' : res += '"' + prop.name + '":"' + prop.value + '",';
422 | }
423 | }
424 | return res.slice(0, -1);
425 | }
426 | /**
427 | * 处理事件
428 | */
429 | _genHandlers(events) {
430 | let res = '';
431 | for (var name in events) {
432 | res += '"' + name + '":' + events[name].value + ',';
433 | }
434 | return res.slice(0, -1);
435 | }
436 | /**
437 | * 处理指令
438 | * @param {Array} directives
439 | */
440 | _genDirectives(directives) {
441 | let _code = '['
442 | directives.map((item,index) => {
443 | _code += `{name:"${item.name}",rawName:"${item.rawName}",value:(${item.value}),expression:"${item.value}"}${index === directives.length - 1 ? ']': ','}`
444 | })
445 | return _code
446 | }
447 | /**
448 | * 处理子节点
449 | * @param {Object} el 当前节点的Ast树
450 | */
451 | _getChildren(el) {
452 | let children = el.children;
453 | if (children && children.length) {
454 | return (
455 | '[' +
456 | children.map(item => {
457 | return this._converCode(item);
458 | }) +
459 | ']'
460 | );
461 | }
462 | }
463 | }
464 |
--------------------------------------------------------------------------------
/src/Compiler/vnode.js:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: xiaoai
3 | * @Date: 2018-12-04 19:53:19
4 | * @LastEditors: xiaoai
5 | * @LastEditTime: 2018-12-08 20:36:17
6 | * @Description: Whatever is worth doing is worth doing well(任何值得做的事就值得把它做好)
7 | */
8 |
9 | /**
10 | * 虚拟Dom基类
11 | */
12 | export default class VNode {
13 | constructor(tag, data, children, text, elm, context, componentOptions, asyncFactory) {
14 | /*当前节点的标签名*/
15 | this.tag = tag;
16 | /*当前节点对应的对象,包含了具体的一些数据信息,是一个VNodeData类型,可以参考VNodeData类型中的数据信息*/
17 | this.data = data;
18 | /*当前节点的子节点,是一个数组*/
19 | this.children = children;
20 | /*当前节点的文本*/
21 | this.text = text;
22 | /*当前虚拟节点对应的真实dom节点*/
23 | this.elm = elm;
24 | /*当前节点的名字空间*/
25 | this.ns = undefined;
26 | /*编译作用域*/
27 | this.context = context;
28 | /*函数化组件作用域*/
29 | this.functionalContext = undefined;
30 | /*节点的key属性,被当作节点的标志,用以优化*/
31 | this.key = data && data.key;
32 | /*组件的option选项*/
33 | this.componentOptions = componentOptions;
34 | /*当前节点对应的组件的实例*/
35 | this.componentInstance = undefined;
36 | /*当前节点的父节点*/
37 | this.parent = undefined;
38 | /*简而言之就是是否为原生HTML或只是普通文本,innerHTML的时候为true,textContent的时候为false*/
39 | this.raw = false;
40 | /*静态节点标志*/
41 | this.isStatic = false;
42 | /*是否作为跟节点插入*/
43 | this.isRootInsert = true;
44 | /*是否为注释节点*/
45 | this.isComment = false;
46 | /*是否为克隆节点*/
47 | this.isCloned = false;
48 | /*是否有v-once指令*/
49 | this.isOnce = false;
50 | }
51 | child() {
52 | return this.componentInstance;
53 | }
54 | }
55 | /**
56 | * 创建空节点
57 | */
58 | export const createEmptyVNode = (text = '') => {
59 | const node = new VNode();
60 | node.text = text;
61 | node.isComment = true;
62 | return node;
63 | };
64 | /**
65 | * 创建文本节点
66 | */
67 | export function createTextVNode(val) {
68 | return new VNode(undefined, undefined, undefined, String(val));
69 | }
70 | /**
71 | * 创建元素
72 | * @param {Object} context miniVue实例
73 | * @param {String} tag 标签
74 | * @param {Object} data 数据
75 | * @param {Array} children 子节点
76 | */
77 | export function createElement(context, tag, data, children) {
78 | var vnode
79 |
80 | if(!tag) {
81 | createEmptyVNode()
82 | }
83 | // 兼容不传data的情况, 处理{{a}} 这种dom情况,字符串function为: _c('span', [_v(_s(a))])
84 | if(Array.isArray(data)) {
85 | children = data
86 | data = undefined
87 | }
88 |
89 | if(typeof tag === 'string') {
90 | vnode = new VNode(tag, data, children, undefined, undefined, context)
91 | }
92 |
93 | if(vnode !== undefined) {
94 | return vnode
95 | }
96 | }
--------------------------------------------------------------------------------
/src/Instance/element.js:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: xiaoai
3 | * @Date: 2018-12-06 17:03:04
4 | * @LastEditors: xiaoai
5 | * @LastEditTime: 2018-12-07 21:52:28
6 | * @Description: Whatever is worth doing is worth doing well(任何值得做的事就值得把它做好)
7 | */
8 | import nodeOptions from './node';
9 | import { callHook } from '../Util';
10 |
11 | function createFnInvoker(fns) {
12 | function invoker() {
13 | var arguments$1 = arguments;
14 |
15 | var fns = invoker.fns;
16 | if (Array.isArray(fns)) {
17 | var cloned = fns.slice();
18 | for (var i = 0; i < cloned.length; i++) {
19 | cloned[i].apply(null, arguments$1);
20 | }
21 | } else {
22 | // return handler return value for single handlers
23 | return fns.apply(null, arguments);
24 | }
25 | }
26 | invoker.fns = fns;
27 | return invoker;
28 | }
29 |
30 | /**
31 | * element类
32 | * 根据VNode创建真实dom元素
33 | */
34 | export default class Element {
35 | /**
36 | * @param {element对象} oldElem element元素
37 | * @param {Object} vnode vnode虚拟dom
38 | * @param {Object} prevVnode 更新上一次的虚拟dom
39 | * @param {element对象} parentElm 父元素ele对象
40 | */
41 | constructor(vm, oldElem, vnode, prevVnode, parentElm) {
42 | this.vm = vm;
43 | if (prevVnode) {
44 | callHook(vm, 'beforeUpdate');
45 | }
46 | this.prevVnode = prevVnode ? prevVnode : { data: {} };
47 | this.createElement(vnode, parentElm, prevVnode);
48 | this.removeVnodes(oldElem);
49 | }
50 | /**
51 | * vue diff算法实现
52 | */
53 | path() {
54 | return true;
55 | }
56 | /**
57 | * 删除老元素
58 | * @param {element对象} oldElem 上一次的元素
59 | */
60 | removeVnodes(oldElem) {
61 | nodeOptions.removeChild(oldElem.parentNode, oldElem);
62 | }
63 | /**
64 | * 根据VNode创建真实dom元素
65 | * @param {Object} vnode VNode
66 | * @param {ele} parentElm 父元素
67 | */
68 | createElement(vnode, parentElm) {
69 | this.path();
70 | // 没有父元素就直接默认body
71 | if (!parentElm) {
72 | parentElm = document.querySelector('body');
73 | }
74 | let data = vnode.data;
75 | let children = vnode.children;
76 | let tag = vnode.tag;
77 |
78 | // 有tag就创建一个标签,没有就当成文本节点创建
79 | if (tag) {
80 | vnode.elm = nodeOptions.createElement(tag, vnode);
81 | } else {
82 | vnode.elm = nodeOptions.createTextNode(vnode.text);
83 | }
84 |
85 | // 如果有子元素数据,递归创建子元素
86 | this.createChildren(vnode, children);
87 |
88 | if (data) {
89 | this.updateAttrs(this.prevVnode, vnode);
90 | this.updateClass(this.prevVnode, vnode);
91 | this.updateDOMListeners(this.prevVnode, vnode);
92 | this.updateStyle(this.prevVnode, vnode);
93 | }
94 | // 添加都对应父元素下面
95 | if (parentElm !== undefined || parentElm !== null) {
96 | nodeOptions.appendChild(parentElm, vnode.elm);
97 | }
98 | }
99 | /**
100 | * 递归创建孩子节点
101 | * @param {Object} vnode VNode
102 | * @param {Array} children 孩子VNode
103 | */
104 | createChildren(vnode, children) {
105 | if (Array.isArray(children)) {
106 | let [i, len] = [0, children.length];
107 | for (; i < len; i++) {
108 | this.createElement(children[i], vnode.elm);
109 | }
110 | }
111 | }
112 | /**
113 | * 更新元素属性
114 | * @param oldVnode
115 | * @param vnode
116 | */
117 | updateAttrs(oldVnode, vnode) {
118 | let elm = vnode.elm;
119 | let oldAttrs = oldVnode.data.attrs || {};
120 | let attrs = vnode.data.attrs || {};
121 |
122 | // 整合attrs和domProps
123 | if(vnode.data.domProps) {
124 | attrs = Object.assign({}, attrs, vnode.data.domProps)
125 | }
126 |
127 | var cur, old;
128 | for (let key in attrs) {
129 | cur = attrs[key];
130 | old = oldAttrs[key];
131 | // if (old !== cur) {
132 | elm.setAttribute(key, cur);
133 | // }
134 | }
135 | }
136 | /**
137 | * 更新元素class
138 | * @param oldVnode
139 | * @param vnode
140 | */
141 | updateClass(oldVnode, vnode) {
142 | let elm = vnode.elm;
143 | let oldStaticClass = oldVnode.data.staticClass || '';
144 | let staticClass = vnode.data.staticClass || '';
145 |
146 | let oldClass = oldVnode.data.class || '';
147 | let _class = vnode.data.class || '';
148 |
149 | if (staticClass || _class) {
150 | let _cls = [].concat(staticClass, _class);
151 | elm.setAttribute('class', _cls.join(' '));
152 | }
153 | }
154 | /**
155 | * 绑定元素事件
156 | * @param oldVnode
157 | * @param vnode
158 | */
159 | updateDOMListeners(oldVnode, vnode) {
160 | let on = vnode.data.on || {};
161 | let oldOn = oldVnode.data.on || {};
162 | var cur;
163 | for (let name in on) {
164 | cur = createFnInvoker(on[name]);
165 | vnode.elm.addEventListener(name, cur, false);
166 | }
167 | }
168 | /**
169 | * 更新元素样式
170 | * @param oldVnode
171 | * @param vnode
172 | */
173 | updateStyle(oldVnode, vnode) {
174 | let elm = vnode.elm;
175 | let oldStaticStyle = oldVnode.data.staticStyle || '';
176 | let staticStyle = vnode.data.staticStyle || '';
177 |
178 | let oldStyle = oldVnode.data.style || {};
179 | let _style = vnode.data.style || {};
180 |
181 | // 如果直接写在标签上面的style遍历属性添加到最新的dom上面
182 | let styleArray = staticStyle.split(';');
183 |
184 | if (styleArray && styleArray.length) {
185 | styleArray.map(item => {
186 | let _s = item.split(':');
187 | if (_s && _s.length) {
188 | elm.style[_s[0]] = _s[1];
189 | }
190 | });
191 | }
192 |
193 | // 添加样式
194 | for (let key in _style) {
195 | elm.style[key] = _style[key];
196 | }
197 | }
198 | }
199 |
--------------------------------------------------------------------------------
/src/Instance/index.js:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: xiaoai
3 | * @Date: 2018-11-15 15:55:52
4 | * @LastEditors: xiaoai
5 | * @LastEditTime: 2018-12-08 20:11:27
6 | * @Description: Whatever is worth doing is worth doing well(任何值得做的事就值得把它做好)
7 | */
8 |
9 | import observe, { defineComputed } from '../Observer';
10 |
11 | import Compiler from '../Compiler';
12 |
13 | import Watcher from '../Observer/watcher';
14 |
15 | import Element from './element';
16 |
17 | import { createEmptyVNode, createTextVNode, createElement } from '../Compiler/vnode';
18 |
19 | import { callHook } from '../Util';
20 |
21 | import { version } from '../../package.json';
22 |
23 | let uid = 0;
24 |
25 | let _version = version
26 | /**
27 | * 主函数入口
28 | */
29 | export default class MiniVue {
30 | constructor(options) {
31 | if (new.target !== MiniVue) {
32 | throw new Error('必须使用 new 命令生成实例');
33 | }
34 | this.id = uid++;
35 | this._self = this;
36 | this.$options = options;
37 | this.init(options);
38 | }
39 | init() {
40 | let vm = this;
41 | callHook(vm, 'beforeCreated');
42 | // 创建元素
43 | this._c = function(a, b, c, d) {
44 | return createElement(vm, a, b, c, d);
45 | };
46 | // 创建文本节点
47 | this._v = createTextVNode;
48 | // 序列化字符串 创建文本节点并且里面含有{{}}表达式,先处理表达式得到值,在进行字符串转换
49 | this._s = function(val) {
50 | return val.toString();
51 | };
52 | // 初始化data数据,代理数据和数据劫持
53 | this._initData();
54 | // 初始化computed
55 | this._initComputed();
56 | // 初始化methods
57 | this._initMethod();
58 | // 编译render,创建虚拟Dom
59 | this.mounted();
60 | }
61 | /**
62 | * 代理函数
63 | * 将data上面的属性代理到了vm实例上,这样就可以用app.text代替app._data.text了
64 | * @param {Object} target 代理目标对象
65 | * @param {String} sourceKey 代理key
66 | * @param {String} key 目标key
67 | */
68 | proxy(target, sourceKey, key) {
69 | let vm = this;
70 | Object.defineProperty(target, key, {
71 | enumerable: true,
72 | configurable: true,
73 | get: function() {
74 | return vm[sourceKey][key];
75 | },
76 | set: function(val) {
77 | vm[sourceKey][key] = val;
78 | }
79 | });
80 | }
81 | /**
82 | * 初始化data对象数据,收集数据添加监听
83 | */
84 | _initData() {
85 | let vm = this;
86 | let data = this.$options.data;
87 | // data支持两种写法(函数和对象)
88 | // 如果data是函数就直接执行拿到返回值,如果是对象直接返回
89 | data = vm._data = typeof data === 'function' ? data.call(vm) : data || {};
90 |
91 | const keys = Object.keys(data);
92 | let i = keys.length;
93 | while (i--) {
94 | let key = keys[i];
95 | this.proxy(vm, '_data', key);
96 | }
97 | observe(data, vm);
98 | }
99 | /**
100 | * 初始化methods方法挂载miniVue实例上
101 | */
102 | _initMethod() {
103 | for (let key in this.$options.methods) {
104 | this[key] = this.$options.methods[key] == null ? noop : this.$options.methods[key].bind(this);
105 | }
106 | }
107 | /**
108 | * 初始化computed,添加依赖监听
109 | */
110 | _initComputed() {
111 | let vm = this;
112 | let noop = function() {};
113 | let watchers = (vm._computedWatchers = Object.create(null));
114 |
115 | if (this.$options.computed) {
116 | for (let key in this.$options.computed) {
117 | var userDef = this.$options.computed[key];
118 | watchers[key] = new Watcher(vm, userDef, noop);
119 |
120 | if (!(key in vm)) {
121 | defineComputed(vm, key, userDef);
122 | }
123 | }
124 | }
125 | }
126 | /**
127 | * 编译模版
128 | */
129 | mounted() {
130 | let vm = this;
131 | callHook(vm, 'created');
132 | if (this.$options.el) {
133 | callHook(vm, 'beforeMount');
134 | // 编译template
135 | let compiler = new Compiler(vm, this.$options);
136 | // 将AST转化成render function字符串
137 | this.$options.render = new Function('with(this){return ' + compiler.render + '}');
138 |
139 | let updateComponent = function() {
140 | let prevVnode = vm._vnode;
141 | let oldElem = document.querySelector(this.$options.el);
142 | // 根据render function字符串创建VNode虚拟Dom
143 | vm.vnode = vm.$options.render.call(vm);
144 | vm._vnode = vm.vnode;
145 | // 根据VNode创建真实dom元素
146 | new Element(vm, oldElem, vm.vnode, prevVnode, null);
147 | };
148 |
149 | // 把渲染函数添加到wather里面,如果有数据更新就重新执行渲染函数进行页面更新
150 | new Watcher(vm, updateComponent, function() {}).get();
151 | callHook(vm, 'mounted');
152 | }
153 | }
154 | }
155 |
156 |
--------------------------------------------------------------------------------
/src/Instance/node.js:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: xiaoai
3 | * @Date: 2018-12-06 15:58:11
4 | * @LastEditors: xiaoai
5 | * @LastEditTime: 2018-12-06 17:01:38
6 | * @Description: Whatever is worth doing is worth doing well(任何值得做的事就值得把它做好)
7 | */
8 | // 代码来源:vue源码/vue-dev/src/platforms/web/runtime/node-ops.js
9 | export function createElement (tagName, vnode) {
10 | const elm = document.createElement(tagName)
11 | if (tagName !== 'select') {
12 | return elm
13 | }
14 | // false or null will remove the attribute but undefined will not
15 | if (vnode.data && vnode.data.attrs && vnode.data.attrs.multiple !== undefined) {
16 | elm.setAttribute('multiple', 'multiple')
17 | }
18 | return elm
19 | }
20 |
21 | // export function createElementNS (namespace, tagName) {
22 | // return document.createElementNS(namespaceMap[namespace], tagName)
23 | // }
24 |
25 | export function createTextNode (text) {
26 | return document.createTextNode(text)
27 | }
28 |
29 | export function createComment (text) {
30 | return document.createComment(text)
31 | }
32 |
33 | export function insertBefore (parentNode, newNode, referenceNode) {
34 | parentNode.insertBefore(newNode, referenceNode)
35 | }
36 |
37 | export function removeChild (node, child) {
38 | node.removeChild(child)
39 | }
40 |
41 | export function appendChild (node, child) {
42 | node.appendChild(child)
43 | }
44 |
45 | export function parentNode (node) {
46 | return node.parentNode
47 | }
48 |
49 | export function nextSibling (node) {
50 | return node.nextSibling
51 | }
52 |
53 | export function tagName (node) {
54 | return node.tagName
55 | }
56 |
57 | export function setTextContent (node, text) {
58 | node.textContent = text
59 | }
60 |
61 | export function setStyleScope (node, scopeId) {
62 | node.setAttribute(scopeId, '')
63 | }
64 |
65 | export default {
66 | createElement,createTextNode,createComment,insertBefore,removeChild,appendChild,parentNode,nextSibling,tagName,setTextContent,setStyleScope
67 | }
--------------------------------------------------------------------------------
/src/Observer/dep.js:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: xiaoai
3 | * @Date: 2018-11-15 16:03:18
4 | * @LastEditors: xiaoai
5 | * @LastEditTime: 2018-12-08 20:14:58
6 | * @Description: Whatever is worth doing is worth doing well(任何值得做的事就值得把它做好)
7 | */
8 | let uid = 0;
9 | /**
10 | * 订阅者Dep
11 | * 主要作用是用来存放Watcher观察者对象
12 | */
13 | export default class Dep {
14 | constructor() {
15 | // 标示id防止添加重复观察者对象
16 | this.id = uid++;
17 | // 存储观察者对象
18 | this.subs = [];
19 | }
20 | /**
21 | * 添加观察者
22 | * @param {Watcher对象} sub
23 | */
24 | addSub(sub) {
25 | this.subs.push(sub);
26 | }
27 | /**
28 | * 删除观察者
29 | * @param {Watcher对象} sub
30 | */
31 | removeSub(sub) {
32 | let [i, len] = [0, this.subs.length];
33 |
34 | for (; i < len; i++) {
35 | if (this.subs[i].id === sub.id) {
36 | this.subs.splice(i, 1);
37 | break;
38 | }
39 | }
40 | }
41 | /**
42 | * 依赖收集,当存在Dep.target的时候添加观察者对象
43 | * addDep方法是挂载在Watcher原型对象上面的,方法内部会调用Dep实例上面的addSub方法
44 | */
45 | depend() {
46 | if (Dep.target) {
47 | Dep.target.addDep(this);
48 | }
49 | }
50 | /**
51 | * 通知所有订阅者
52 | * update方法是挂载在Watcher原型对象上面的,方法内部会把需要的更新数据push到异步队列中,等到数据所有操作完成在进行视图更新
53 | */
54 | notify() {
55 | // 拷贝观察者对象
56 | const subs = this.subs.slice();
57 | // 循环所有观察者进行更新操作
58 | subs.map(item => {
59 | item.update();
60 | return item;
61 | });
62 | }
63 | }
64 |
65 | // 依赖收集完需要将Dep.target设为null,防止后面重复添加依赖
66 | Dep.target = null;
67 |
68 | const targetStack = []
69 |
70 | export function pushTarget(_target) {
71 | if(Dep.target) {
72 | targetStack.push(Dep.target)
73 | }
74 | Dep.target = _target
75 | }
76 |
77 | export function popTarget() {
78 | Dep.target = targetStack.pop()
79 | }
--------------------------------------------------------------------------------
/src/Observer/index.js:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: xiaoai
3 | * @Date: 2018-11-16 17:50:52
4 | * @LastEditors: xiaoai
5 | * @LastEditTime: 2018-12-07 16:22:45
6 | * @Description: Whatever is worth doing is worth doing well(任何值得做的事就值得把它做好)
7 | */
8 | import { def } from '../Util';
9 | import Dep from './dep';
10 |
11 | let sharedPropertyDefinition = {
12 | enumerable: true,
13 | configurable: true,
14 | get: function(){},
15 | set: function(){}
16 | };
17 | export function defineReactive(obj, key, val) {
18 | // 实例订阅者对象
19 | const dep = new Dep();
20 | // 获取对象上面的描述
21 | const property = Object.getOwnPropertyDescriptor(obj, key);
22 |
23 | if (property && property.configurable === false) {
24 | return;
25 | }
26 |
27 | const getter = property && property.get;
28 | const setter = property && property.set;
29 |
30 | if ((!getter || setter) && arguments.length === 2) {
31 | val = obj[key];
32 | }
33 | var childObj = observe(val);
34 | Object.defineProperty(obj, key, {
35 | // 可枚举
36 | enumerable: true,
37 | configurable: true,
38 | get: function reactiveGetter() {
39 | const value = getter ? getter.call(obj) : val;
40 | // 依赖收集
41 | if (Dep.target) {
42 | dep.depend();
43 | }
44 | return value;
45 | },
46 | set: function reactiveSetter(newVal) {
47 | const value = getter ? getter.call(obj) : val;
48 | if (newVal === value || (newVal !== newVal && value !== value)) {
49 | return;
50 | }
51 | // 更新值
52 | if (setter) {
53 | setter.call(obj, newVal);
54 | } else {
55 | val = newVal;
56 | }
57 | // 新的值是object的话,进行监听
58 | childObj = observe(newVal);
59 | // 通知所有订阅者进行视图更新
60 | dep.notify();
61 | }
62 | });
63 | }
64 |
65 | // 把computed的属性挂载到minivue实例上
66 | export function defineComputed(target, key, userDef) {
67 | if (typeof userDef === 'function') {
68 | sharedPropertyDefinition.get = createComputedGetter(key);
69 | sharedPropertyDefinition.set = function() {};
70 | }
71 |
72 | Object.defineProperty(target, key, sharedPropertyDefinition);
73 | }
74 |
75 | export function createComputedGetter(key) {
76 | return function computedGetter() {
77 | var watcher = this._computedWatchers && this._computedWatchers[key];
78 | if (watcher) {
79 | // if (watcher.dirty) {
80 | watcher.get();
81 | // }
82 | if (Dep.target) {
83 | watcher.depend();
84 | }
85 | return watcher.value;
86 | }
87 | };
88 | }
89 |
90 | class Observer {
91 | constructor(value) {
92 | this.value = value;
93 | this.dep = new Dep();
94 | this.walk(value);
95 | }
96 | /**
97 | * 给每个数据属性转为为getter/setter,在读取和设置的时候都会进入对应方法进行数据监听和更新
98 | * @param {Object} obj 监听对象
99 | */
100 | walk(obj) {
101 | const keys = Object.keys(obj);
102 | for (let i = 0; i < keys.length; i++) {
103 | defineReactive(obj, keys[i]);
104 | }
105 | }
106 | }
107 |
108 | export default function observe(value, vm) {
109 | if (!value || typeof value !== 'object') {
110 | return;
111 | }
112 | return new Observer(value);
113 | }
114 |
--------------------------------------------------------------------------------
/src/Observer/watcher.js:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: xiaoai
3 | * @Date: 2018-11-15 15:56:03
4 | * @LastEditors: xiaoai
5 | * @LastEditTime: 2018-12-07 16:15:48
6 | * @Description: Whatever is worth doing is worth doing well(任何值得做的事就值得把它做好)
7 | */
8 | import Dep, { pushTarget, popTarget } from './dep';
9 |
10 | import { noop, debug } from '../Util';
11 |
12 | let uid = 0;
13 | /**
14 | * 观察者 Watcher
15 | * 主要作用是进行依赖收集的观察者和更新视图
16 | * 当依赖收集的时候会调用Dep对象的addSub方法,在修改data中数据的时候会触发Dep对象的notify,通知所有Watcher对象去修改对应视图
17 | */
18 | export default class Watcher {
19 | /**
20 | * @param {Object} vm miniVue实例对象
21 | * @param {Function} expOrFn watch监听函数
22 | * @param {Function} cb 回调触发视图更新函数
23 | */
24 | constructor(vm, expOrFn, cb = noop) {
25 | this.vm = vm;
26 | // 设置id防止重复添加
27 | this.id = uid++;
28 | // 保存监听函数为字符串,错误提示会使用
29 | this.expression = expOrFn.toString();
30 | // 新的依赖项id集合
31 | this.newDepIds = new Set();
32 | // 新的依赖项 临时值在依赖收集完成之后会马上清除
33 | this.newDeps = [];
34 | // 添加后的依赖项id集合
35 | this.depIds = new Set();
36 | // 添加后的依赖项 依赖收集完成会从newDeps中取出值赋值给自己
37 | this.deps = [];
38 | // 回调触发视图更新函数
39 | this.cb = cb;
40 | // 获取当前watcher表达式
41 | if (typeof expOrFn === 'function') {
42 | this.getter = expOrFn;
43 | } else {
44 | debug('error', this.expression + 'Not a function');
45 | }
46 | }
47 | get() {
48 | // 更新当前watcher赋值给Dep.target,并且添加到target栈
49 | pushTarget(this);
50 | let value = this.getter.call(this.vm, this.vm);
51 | // 将观察者实例从target栈中取出并设置给Dep.target
52 | popTarget();
53 | // 清除依赖
54 | this.cleanupDeps();
55 | this.value = value
56 | return value;
57 | }
58 | /**
59 | * 添加依赖
60 | * @param {Object} dep Dep实例对象
61 | */
62 | addDep(dep) {
63 | let _id = dep.id;
64 | // 如果没有添加依赖项就进行添加
65 | if (!this.newDepIds.has(_id)) {
66 | this.newDepIds.add(_id);
67 | this.newDeps.push(dep);
68 | if (!this.depIds.has(_id)) {
69 | dep.addSub(this);
70 | }
71 | }
72 | }
73 | /**
74 | * 清除依赖收集
75 | */
76 | cleanupDeps() {
77 | /*移除所有观察者对象*/
78 | let i = this.deps.length;
79 | while (i--) {
80 | const dep = this.deps[i];
81 | if (!this.newDepIds.has(dep.id)) {
82 | dep.removeSub(this);
83 | }
84 | }
85 | // 清除所有依赖数据,把newDeps数据赋值给deps存储依赖
86 | let tmp = this.depIds;
87 | this.depIds = this.newDepIds;
88 | this.newDepIds = tmp;
89 | this.newDepIds.clear();
90 | tmp = this.deps;
91 | this.deps = this.newDeps;
92 | this.newDeps = tmp;
93 | this.newDeps.length = 0;
94 | }
95 | /**
96 | * 收集依赖
97 | */
98 | depend() {
99 | let i = this.deps.length;
100 | while (i--) {
101 | this.deps[i].depend();
102 | }
103 | }
104 | /**
105 | * 触发更新
106 | */
107 | update() {
108 | this.run();
109 | // queueWatcher(this);
110 | }
111 | /**
112 | * update函数会调该函数进行更新回调
113 | */
114 | run() {
115 | let value = this.get();
116 | if (value !== this.value) {
117 | let oldValue = this.value;
118 | this.value = value;
119 | this.cb.call(this.vm, value, oldValue);
120 | }
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/src/Util/debug.js:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: xiaoai
3 | * @Date: 2018-11-15 20:18:05
4 | * @LastEditors: xiaoai
5 | * @LastEditTime: 2018-11-16 11:57:20
6 | * @Description: Whatever is worth doing is worth doing well(任何值得做的事就值得把它做好)
7 | */
8 | let _console = window.console
9 |
10 | export default function debug(type, msg) {
11 | _console[type].call(_console, msg)
12 | }
13 |
--------------------------------------------------------------------------------
/src/Util/index.js:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: xiaoai
3 | * @Date: 2018-11-15 19:58:29
4 | * @LastEditors: xiaoai
5 | * @LastEditTime: 2018-12-06 19:34:55
6 | * @Description: Whatever is worth doing is worth doing well(任何值得做的事就值得把它做好)
7 | */
8 | import debugUtil from './debug';
9 |
10 | export let noop = function(a, b, c) {};
11 |
12 | export let callHook = function(vm, hook) {
13 | let handlers = vm.$options[hook];
14 | if (handlers) {
15 | handlers.call(vm);
16 | }
17 | };
18 | /**
19 | * 定义属性.
20 | */
21 | export function def(obj, key, val, enumerable) {
22 | Object.defineProperty(obj, key, {
23 | value: val,
24 | enumerable: !!enumerable,
25 | writable: true,
26 | configurable: true
27 | });
28 | }
29 | export const debug = debugUtil
30 |
--------------------------------------------------------------------------------
/src/Util/regExp.js:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: xiaoai
3 | * @Date: 2018-12-02 20:41:41
4 | * @LastEditors: xiaoai
5 | * @LastEditTime: 2018-12-07 00:41:24
6 | * @Description: Whatever is worth doing is worth doing well(任何值得做的事就值得把它做好)
7 | */
8 | export const regExp = {
9 | // 匹配结束标签
10 | endTag: /^<\/((?:[a-zA-Z_][\w\-\.]*\:)?[a-zA-Z_][\w\-\.]*)[^>]*>/,
11 | // 匹配开始打开标签
12 | startTagOpen: /^<((?:[a-zA-Z_][\w\-\.]*\:)?[a-zA-Z_][\w\-\.]*)/,
13 | // 匹配开始结束标签
14 | startTagClose: /^\s*(\/?)>/,
15 | // 匹配属性
16 | attribute: /^\s*([^\s"'<>\/=]+)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/,
17 | // 匹配注释
18 | comment: /^]+>/i,
23 | // 匹配表达式 {{}}
24 | defaultTagRE: /\{\{((?:.|\n)+?)\}\}/g
25 | };
26 |
--------------------------------------------------------------------------------
/test/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Document
9 |
10 |
11 |
12 |
{{a}}
13 |
14 |
xx
15 |
16 |
17 |
58 |
--------------------------------------------------------------------------------