├── Mvue.js ├── complie.js └── index.html /Mvue.js: -------------------------------------------------------------------------------- 1 | class Mvue { 2 | constructor(options) { 3 | this.$options = options 4 | this.$data = options.data 5 | this.$options.$methods = options.methods 6 | this.observer(this.$data) // 观察数据 7 | 8 | new Complie(options.el, this) 9 | 10 | if(options.create()) options.create.call(this) 11 | 12 | } 13 | 14 | observer(obj) { 15 | if(obj === null || typeof obj !== 'object') return 16 | Object.keys(obj).forEach(key => { 17 | this.defineProperty(obj, key, obj[key]) 18 | this.poxryData(key) 19 | }) 20 | } 21 | 22 | /* 23 | * 给元素添加数据,并且在数据发生变化的时候通知视图更新(双向绑定) 24 | * 25 | * */ 26 | defineProperty(obj, key, val) { 27 | this.observer(val) // 递归监听子元素是否存在对象 28 | const dep = new Dep() 29 | Object.defineProperty(obj, key, { 30 | get() { 31 | Dep.target && dep.addDep(Dep.target) 32 | return val 33 | }, 34 | set(newVal) { 35 | if(newVal === val) return 36 | val = newVal 37 | dep.notify() // 通知更新 38 | } 39 | }) 40 | } 41 | 42 | /* 43 | * 代理数据使得能用this.xxx直接访问到this.$data.xxx 44 | * 45 | * */ 46 | poxryData(key) { 47 | Object.defineProperty(this, key, { 48 | configurable: true, 49 | get() { 50 | return this.$data[key] 51 | }, 52 | set(newValue) { 53 | this.$data[key] = newValue 54 | } 55 | }) 56 | } 57 | } 58 | 59 | /* 60 | * 用于收集依赖 61 | * */ 62 | 63 | class Dep { 64 | constructor() { 65 | this.deps = [] // 依赖容器 66 | } 67 | 68 | addDep(dep) { // 收集当前实例 69 | this.deps.push(dep) 70 | } 71 | 72 | notify() { 73 | this.deps.forEach(dep => dep.update()) 74 | } 75 | 76 | } 77 | 78 | /* 79 | * 用于观察数据是否发生变化 80 | * 81 | * */ 82 | class Watcher { 83 | constructor(vm, key, cb) { 84 | this.vm = vm 85 | this.key = key 86 | this.cb = cb 87 | Dep.target = this 88 | this.vm[this.key] // 触发get, 进行依赖收集 89 | Dep.target = null 90 | } 91 | 92 | update() { 93 | this.cb.call(this.vm, this.vm[this.key]) 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /complie.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | class Complie { 4 | constructor(el, vm) { 5 | 6 | this.$el = document.querySelector(el) 7 | this.$vm = vm 8 | 9 | // 编译 10 | if(this.$el) { 11 | // 转换内部内容为Fragment片段 12 | this.$fragment = this.node2Fragment(this.$el) 13 | // 执行编译 14 | this.compile(this.$fragment) 15 | // 将编译完的html结果追加至$el 16 | this.$el.appendChild(this.$fragment) 17 | } 18 | } 19 | 20 | node2Fragment(el) { // 将宿主片段的代码拿出来便利 21 | const frag = document.createDocumentFragment() 22 | let child; 23 | while(child = el.firstChild) { // 将el的子元素搬家至frag中 24 | frag.appendChild(child) 25 | } 26 | return frag 27 | } 28 | 29 | compile(el) { 30 | const nodeChilds = el.childNodes 31 | Array.from(nodeChilds).forEach(nodeChild => { // 节点数组转换成数组 32 | if(this.isElement(nodeChild)) { 33 | const nodeAttrs = nodeChild.attributes // 获取元素属性 34 | Array.from(nodeAttrs).forEach(attr => { // 循环遍历元素属性值 35 | const attrName = attr.name // 属性名 36 | const attrVal = attr.value // 键值 37 | if(this.isDirective(attrName)) { // 是否是规则定义的属性 38 | const dir = attrName.substring(2) 39 | this[dir] && this[dir](nodeChild, this.$vm, attrVal) // 执行函数 40 | } 41 | if (this.isEvent(attrName)) { // 事件 42 | const dir = attrName.substring(1) 43 | this.event(nodeChild, this.$vm, dir, attrVal) // 执行函数 44 | } 45 | }) 46 | } else if (this.isInterpolations(nodeChild)) { // 插值表达式 47 | this.compileText(nodeChild) 48 | } 49 | 50 | if (nodeChild.childNodes && nodeChild.childNodes.length > 0) { // 递归查找子元素 51 | this.compile(nodeChild) 52 | } 53 | }) 54 | } 55 | 56 | compileText(node) { 57 | this.updata(node, this.$vm, RegExp.$1, 'text') 58 | } 59 | 60 | updata(node, vm, exp, dir) { 61 | const updataFn = this[dir + 'Updater'] // 匹配执行函数 62 | updataFn && updataFn(node, vm[exp]) // 初始化 63 | new Watcher(vm, exp, function (value) { // 收集依赖 64 | updataFn && updataFn(node, value) 65 | }) 66 | } 67 | 68 | textUpdater(node, value) { // text 69 | node.textContent = value 70 | } 71 | 72 | modelUpdater(node, value) { // model 73 | node.value = value 74 | } 75 | 76 | htmlUpdater(node, value) { // html 77 | node.innerHTML = value 78 | } 79 | 80 | text(node, vm, exp) { // 替换 81 | node.textContent = vm[exp] 82 | } 83 | 84 | model(node, vm, exp) { 85 | this.updata(node, vm, exp, 'model') 86 | node.addEventListener('input', e => { 87 | vm[exp] = e.target.value 88 | }) 89 | } 90 | 91 | html(node, vm, exp) { 92 | this.updata(node, vm, exp, 'html') 93 | } 94 | 95 | event(node, vm, dir, exp) { 96 | const fn = vm.$options.$methods && vm.$options.$methods[exp] // 事件函数 97 | console.log(dir) 98 | if(dir && exp) { // 绑定事件 99 | node.addEventListener(dir, fn.bind(vm)) 100 | } 101 | } 102 | 103 | isEvent(attrName) { // 事件 104 | return attrName.indexOf('@') === 0 105 | } 106 | 107 | isDirective(attrName) { // 元素 108 | return attrName.indexOf('m-') === 0 109 | } 110 | 111 | isElement(node) { // 是否是元素节点 112 | return node.nodeType === 1 113 | } 114 | 115 | isInterpolations(node) { // 插值文本 116 | return node.nodeType === 3 && /\{\{(.*)\}\}/.test(node.textContent) 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 7 | 8 |{{bar}}
15 |{{foo.bar}}
16 | 17 | 18 | 19 |{{model}}
20 | 21 | 22 | 23 |