├── .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 |
12 |
{{a}}
13 | 14 |
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 | ![目录结构](./image/files.png) 60 | 61 | Comilper--->主要是处理`dom`编译 62 | 63 | Instance--->程序入口和创建`dom` 64 | 65 | Observer--->数据拦截,数据订阅,发布,更新 66 | 67 | Util--->程序工具类(debug,nextTick...) 68 | 69 | ### 深入响应式原理 70 | 71 | 先看一张`Vue`官方文档中的图 72 | 73 | ![vue](./image/1.png) 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 | ![ast](./image/2.png) 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 | ![vue](./image/1.png) 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 | 15 |
16 | 17 | 58 | --------------------------------------------------------------------------------