├── test ├── test.html └── test.js ├── src ├── parsers │ ├── parserc │ │ ├── readme.md │ │ ├── state.js │ │ └── parser.js │ ├── directiveParser.js │ └── arrayPathParser.js ├── config.js ├── parser.js ├── watcher.js ├── observer.js ├── directives │ ├── v-if.js │ ├── index.js │ └── v-for.js ├── event.js ├── utils.js ├── component.js ├── binding.js └── main.js ├── .babelrc ├── .gitignore ├── examples ├── parser.html ├── demo.html ├── components.html └── tiny-vue.html ├── .eslintrc ├── .editorconfig ├── index.js ├── webpack.config.js ├── package.json └── readme.md /test/test.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/parsers/parserc/readme.md: -------------------------------------------------------------------------------- 1 | an combinator parse libaray. -------------------------------------------------------------------------------- /src/config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | prefix: 'v' 3 | }; 4 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "stage-1", "stage-2"] 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | dist/ 4 | npm-debug.log 5 | yarn-error.log 6 | -------------------------------------------------------------------------------- /src/parser.js: -------------------------------------------------------------------------------- 1 | import DirectiveParser from './parsers/directiveParser.js'; 2 | 3 | 4 | export { 5 | DirectiveParser 6 | }; -------------------------------------------------------------------------------- /examples/parser.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Document 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | var name = require('../src/main.js'); 2 | var chai = require('chai'); 3 | 4 | var expect = chai.expect; 5 | 6 | describe('vue', function () { 7 | it('should hava an name "vue"', function () { 8 | expect(name).to.be.equal('vue'); 9 | }) 10 | }) -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es6": true 5 | }, 6 | "parserOptions": { 7 | "ecmaVersion": 6, 8 | "sourceType": "module" 9 | }, 10 | "rules": { 11 | "camelcase": 2, 12 | "brace-style": [2, "1tbs"], 13 | "quotes": [2, "single"], 14 | "semi": [2, "always"] 15 | } 16 | } -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # http://editorconfig.org 4 | 5 | root = true 6 | 7 | [*] 8 | 9 | # Change these settings to your own preference 10 | indent_style = space 11 | indent_size = 4 12 | 13 | # We recommend you to keep these unchanged 14 | end_of_line = lf 15 | charset = utf-8 16 | trim_trailing_whitespace = true 17 | insert_final_newline = true 18 | 19 | [*.md] 20 | trim_trailing_whitespace = false 21 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | import Main from './src/main.js'; 2 | import Directives from './src/directives/index.js'; 3 | 4 | export default class TinyVue extends Main { 5 | constructor (opts) { 6 | super(opts); 7 | } 8 | 9 | /** 10 | * 自定义指令 11 | */ 12 | static $directive (name, fn) { 13 | if (!fn) { 14 | return Directives[name]; 15 | } else { 16 | Directives[name] = fn; 17 | } 18 | } 19 | 20 | /** 21 | * 定义全局组件 22 | */ 23 | static component (componentName, opts) { 24 | this.components[componentName] = opts; 25 | } 26 | } 27 | 28 | window.TinyVue = TinyVue; -------------------------------------------------------------------------------- /src/watcher.js: -------------------------------------------------------------------------------- 1 | import { observer } from './observer.js'; 2 | 3 | /** 4 | * watcher a key 5 | */ 6 | 7 | export default class Watcher { 8 | constructor (binding) { 9 | binding.watcher = this; 10 | 11 | this.binding = binding; 12 | this.dependencies = []; 13 | } 14 | 15 | getDeps () { 16 | observer.on('get', (dep)=>{ 17 | this.dependencies.push(dep); 18 | }); 19 | } 20 | 21 | off () { 22 | observer.off('get'); 23 | } 24 | 25 | watch () { 26 | let self = this; 27 | this.dependencies.forEach((dep)=>{ 28 | observer.on(dep.key, ()=>{ 29 | self.binding.refresh(); 30 | }); 31 | }); 32 | } 33 | } -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | devtool: 'source-map', 3 | entry: { 4 | tinyVue: './index.js', 5 | arrayParser: './src/parsers/arrayPathParser.js' 6 | }, 7 | output: { 8 | path: './dist', 9 | filename: '[name].js' 10 | }, 11 | module: { 12 | preLoaders: [ 13 | // { 14 | // test: /\.js$/, 15 | // loader: "eslint-loader", 16 | // exclude: /node_modules/ 17 | // } 18 | ], 19 | loaders: [ 20 | { 21 | test: /\.(js)$/, 22 | loader: 'babel', 23 | exclude: /node_modules/ 24 | } 25 | ] 26 | } 27 | } -------------------------------------------------------------------------------- /src/observer.js: -------------------------------------------------------------------------------- 1 | import Event from './event.js'; 2 | 3 | let observer = new Event(); 4 | 5 | /** 6 | * Array observer 7 | */ 8 | let arrProto = Array.prototype; 9 | let arrayMethods = Object.create(arrProto); 10 | const mutationMethods = [ 11 | 'pop', 12 | 'push', 13 | 'reverse', 14 | 'shift', 15 | 'unshift', 16 | 'splice', 17 | 'sort' 18 | ]; 19 | 20 | function arrayObserver (arr, cb) { 21 | mutationMethods.forEach(function (method) { 22 | // 原生的数组方法 23 | let originalMethod = arrProto[method]; 24 | 25 | arrayMethods[method] = function () { 26 | originalMethod.apply(this, arguments); 27 | 28 | cb && cb({ 29 | event: method, 30 | args: arguments, 31 | array: arr 32 | }); 33 | }; 34 | }); 35 | arr.__proto__ = arrayMethods; 36 | } 37 | 38 | export { 39 | observer, 40 | arrayObserver 41 | }; 42 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tiny-vue", 3 | "version": "0.1.0", 4 | "description": "rewrite vue.js", 5 | "main": "./dist/tinyVue.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "build": "webpack", 9 | "build:dev": "webpack -w" 10 | }, 11 | "devDependencies": { 12 | "babel-core": "^6.14.0", 13 | "babel-loader": "^6.2.5", 14 | "babel-preset-es2015": "^6.14.0", 15 | "babel-preset-stage-1": "^6.16.0", 16 | "babel-preset-stage-2": "^6.17.0", 17 | "chai": "^3.5.0", 18 | "eslint": "^3.10.0", 19 | "eslint-loader": "^1.6.1", 20 | "mocha": "^3.1.2", 21 | "webpack": "^1.13.2" 22 | }, 23 | "keywords": [ 24 | "vue", 25 | "revue" 26 | ], 27 | "repository": { 28 | "type": "git", 29 | "url": "https://github.com/xiaofuzi/re-vue" 30 | }, 31 | "bugs": { 32 | "url": "https://github.com/xiaofuzi/re-vue/issues" 33 | }, 34 | "homepage": "https://github.com/xiaofuzi/deep-in-vue", 35 | "author": "yangxiaofu", 36 | "license": "ISC" 37 | } 38 | -------------------------------------------------------------------------------- /examples/demo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | the super tiny vue.js 6 | 19 | 20 | 21 |
22 |
23 |

24 |
25 |
26 | 27 | 28 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /src/parsers/parserc/state.js: -------------------------------------------------------------------------------- 1 | class State { 2 | constructor(input){ 3 | this.input = input; 4 | this.index = 0; 5 | this.matched = ''; 6 | this.rested = this.input; 7 | this.length = this.rested.length; 8 | this.pos = { 9 | col: 0, 10 | row: 0 11 | }; 12 | } 13 | 14 | advance (num) { 15 | this.index += num; 16 | this.matched += this.rested.substring(0, num); 17 | this.rested = this.rested.substring(num); 18 | this.length = this.rested.length; 19 | 20 | this.pos.col += num; 21 | 22 | return this; 23 | } 24 | 25 | trimLeft () { 26 | let s = this.rested; 27 | let m = s.match(/^\s+/); 28 | 29 | if (m) { 30 | this.advance(m[0].length); 31 | this.pos.col += m[0].length; 32 | } 33 | 34 | return this; 35 | } 36 | 37 | posMsg () { 38 | return 'col: ' + this.pos.col + ', row: ' + this.pos.row; 39 | } 40 | 41 | substring (start, length) { 42 | return this.rested.substring(start, length); 43 | } 44 | 45 | at (index) { 46 | return this.rested.charAt(index); 47 | } 48 | } 49 | 50 | export default function parseState (str) { 51 | return new State(str); 52 | } -------------------------------------------------------------------------------- /src/directives/v-if.js: -------------------------------------------------------------------------------- 1 | import Vm from '../main.js'; 2 | 3 | /** 4 | * if directive 5 | */ 6 | export default { 7 | isBlock: true, 8 | bind () { 9 | this.parent = this.el.parentNode; 10 | this.startRef = document.createComment('Start of v-if-directive'); 11 | this.endRef = document.createComment('End of v-if-directive'); 12 | 13 | let next = this.el.nextSibling; 14 | if (next) { 15 | this.parent.insertBefore(this.startRef, next); 16 | this.parent.insertBefore(this.endRef, next); 17 | } else { 18 | this.parent.appendChild(this.startRef); 19 | this.parent.appendChild(this.endRef); 20 | } 21 | }, 22 | update (value) { 23 | if (value) { 24 | this.createDirectiveInstance(); 25 | } else { 26 | this.parent.removeChild(this.el); 27 | this.childVm&&this.childVm.remove(); 28 | } 29 | }, 30 | createDirectiveInstance () { 31 | if (this.childVm) { 32 | this.childVm = null; 33 | } 34 | 35 | let node = this.el, 36 | parentVm = this.vm; 37 | this.parent.insertBefore(node, this.endRef); 38 | 39 | let childVm = new Vm({ 40 | el: node 41 | }, this.vm); 42 | /** 43 | * 给 if 指令新建一个vm实例,该实例与父实例共享同一个上下文 44 | */ 45 | childVm.__proto__ = parentVm; 46 | childVm.appendTo(parentVm); 47 | console.log('childVm: ', childVm); 48 | this.childVm = childVm; 49 | } 50 | }; -------------------------------------------------------------------------------- /src/directives/index.js: -------------------------------------------------------------------------------- 1 | import vif from './v-if.js'; 2 | import vfor from './v-for.js'; 3 | /** 4 | * Directives 5 | */ 6 | 7 | export default { 8 | /** 9 | * 对应于 v-text 指令 10 | */ 11 | text: function (value) { 12 | this.el.textContent = value === undefined ? '' : value; 13 | }, 14 | /** 15 | * 对应于 v-model 指令 16 | */ 17 | model: function (value) { 18 | let eventName = 'keyup', 19 | el = this.el, 20 | self = this; 21 | el.value = value || ''; 22 | 23 | /** 24 | * 事件绑定控制 25 | */ 26 | if (el.handlers && el.handlers[eventName]) { 27 | el.removeEventListener(eventName, el.handlers[eventName]); 28 | } else { 29 | el.handlers = {}; 30 | } 31 | 32 | el.handlers[eventName] = function (e) { 33 | self[key] = e.target.value; 34 | }; 35 | 36 | el.addEventListener(eventName, el.handlers[eventName]); 37 | }, 38 | on: { 39 | update: function (handler) { 40 | let eventName = this.arg, 41 | el = this.el; 42 | 43 | if (!this.handlers) { 44 | this.handlers = {}; 45 | } 46 | 47 | var handlers = this.handlers; 48 | 49 | if (handlers[eventName]) { 50 | //绑定新的事件前移除原绑定的事件函数 51 | el.removeEventListener(eventName, handlers[eventName]); 52 | } 53 | //绑定新的事件函数 54 | if (handler) { 55 | handler = handler.bind(this.vm); 56 | el.addEventListener(eventName, handler); 57 | handlers[eventName] = handler; 58 | } 59 | } 60 | }, 61 | if: vif, 62 | for: vfor, 63 | each: { 64 | update: function (arr) { 65 | 66 | } 67 | } 68 | }; -------------------------------------------------------------------------------- /src/directives/v-for.js: -------------------------------------------------------------------------------- 1 | import Vm from '../main.js'; 2 | 3 | /** 4 | * v-for directive 5 | */ 6 | export default { 7 | /** 8 | * 单独进行编译 9 | */ 10 | isBlock: true, 11 | bind () { 12 | this.parent = this.el.parentNode; 13 | this.startRef = document.createComment('Start of v-for-directive'); 14 | this.endRef = document.createComment('End of v-for-directive'); 15 | 16 | let next = this.el.nextSibling; 17 | if (next) { 18 | this.parent.insertBefore(this.startRef, next); 19 | this.parent.insertBefore(this.endRef, next); 20 | } else { 21 | this.parent.appendChild(this.startRef); 22 | this.parent.appendChild(this.endRef); 23 | } 24 | 25 | this.parent.removeChild(this.el); 26 | this.parent.index++; 27 | 28 | this.childElements = []; 29 | this.childVms = [] 30 | }, 31 | update (arr=[]) { 32 | this.unbind(); 33 | arr.forEach((item, index)=>{ 34 | this.createChildInstance(item, index); 35 | }); 36 | }, 37 | createChildInstance (item, index) { 38 | let vm, node = this.el.cloneNode(true); 39 | 40 | this.parent.insertBefore(node, this.endRef); 41 | this.parent.index++; 42 | 43 | /** 44 | * array item data process 45 | */ 46 | let data = { 47 | $index: index 48 | }; 49 | data[this.subKey] = item; 50 | vm = new Vm({ 51 | el: node, 52 | data: data 53 | }, this.vm); 54 | vm.__proto__ = this.$vm; 55 | 56 | vm.appendTo(this.vm); 57 | this.childElements[index] = node; 58 | this.childVms[index] = vm; 59 | }, 60 | unbind () { 61 | if (this.childVms.length != 0) { 62 | this.childVms.forEach((child)=>{ 63 | child.$destroy(); 64 | }); 65 | } 66 | } 67 | } -------------------------------------------------------------------------------- /examples/components.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | the super tiny vue.js 6 | 19 | 20 | 21 |
22 |

组件系统示例

23 | 24 | 25 |
26 | 27 | 74 | 75 | -------------------------------------------------------------------------------- /src/event.js: -------------------------------------------------------------------------------- 1 | var slice = [].slice; 2 | 3 | /* 4 | * event control class 5 | * @param {context} 6 | */ 7 | 8 | function Event(ctx){ 9 | this._ctx = ctx || this; 10 | this._events = {}; 11 | } 12 | 13 | var EventProto = Event.prototype; 14 | 15 | /* 16 | * bind a event 17 | * @param {event} eventType 18 | * @param {fn} function 19 | */ 20 | EventProto.on = function(event, fn){ 21 | this._events[event] = this._events[event] || []; 22 | this._events[event].push(fn); 23 | 24 | return this; 25 | }; 26 | 27 | /* 28 | * bind an event but only called one time 29 | * @param {event} eventType 30 | * @param {fn} function 31 | */ 32 | EventProto.once = function(event, fn){ 33 | var self = this; 34 | 35 | //when fn is called, remove all event listener 36 | function fnWrap(){ 37 | self.off(event, fnWrap); 38 | fn.apply(this, arguments); 39 | } 40 | 41 | //to specifiy remove method 42 | fnWrap.fn = fn; 43 | this.on(event, fnWrap); 44 | return this; 45 | }; 46 | 47 | 48 | /* 49 | * unbind an event 50 | * @param {event} eventType 51 | * @param {fn} function 52 | */ 53 | 54 | EventProto.off = function(event, fn){ 55 | //remove all events 56 | if(!arguments){ 57 | this._events = {}; 58 | return this; 59 | } 60 | 61 | //there are not fn binded 62 | var events = this._events[event]; 63 | if(!events) return this; 64 | 65 | //remove an type events 66 | if(arguments.length === 1 && typeof event === 'string'){ 67 | delete this._events[event]; 68 | return this; 69 | } 70 | 71 | //remove fn 72 | var handler; 73 | for(var i = 0; i < events.length; i++){ 74 | handler = events[i]; 75 | if(handler === fn || handler.fn === fn){ 76 | events.splice(i, 1); 77 | break; 78 | } 79 | } 80 | return this; 81 | }; 82 | 83 | /* 84 | * emit 85 | * @param {event} 86 | * @param {fn param} 87 | */ 88 | EventProto.emit = function(event){ 89 | var events = this._events[event], 90 | args; 91 | if(events){ 92 | events = events.slice(0); 93 | args = slice.call(arguments, 1); 94 | events.forEach((event)=>{ 95 | event.apply(this._ctx, args); 96 | }); 97 | } 98 | return this; 99 | }; 100 | 101 | export default Event; -------------------------------------------------------------------------------- /src/parsers/directiveParser.js: -------------------------------------------------------------------------------- 1 | import Directives from '../directives/index.js'; 2 | 3 | import Config from '../config.js'; 4 | const { prefix } = Config; 5 | 6 | export default class DirectiveParser { 7 | constructor (directiveName, arg, expression) { 8 | /** 9 | * v-on:click="onChange" 10 | directiveName = 'v-on:click' 11 | expression = 'onChange' 12 | */ 13 | 14 | this.key = expression.trim(); 15 | this.name = directiveName; 16 | this.arg = arg; 17 | 18 | let directive = Directives[directiveName]; 19 | 20 | if (typeof directive === 'function') { 21 | this.update = directive; 22 | } else { 23 | for (let prop in directive) { 24 | if (prop === 'update') { 25 | this.update = directive.update; 26 | } 27 | this[prop] = directive[prop]; 28 | } 29 | } 30 | 31 | /** 32 | * directive expression process 33 | */ 34 | if (this.name == 'for') { 35 | this.rawKey = this.key; 36 | let keyArray = this.rawKey.split(' '); 37 | 38 | if (keyArray.length != 3) { 39 | console.log('Invalid expression of v-for'); 40 | } 41 | this.key = keyArray[2]; 42 | this.subKey = keyArray[0]; 43 | } 44 | } 45 | 46 | 47 | static parse (directiveName, expression) { 48 | if (directiveName.indexOf(prefix + '-') === -1) { 49 | return null; 50 | } 51 | 52 | /** 53 | * 指令解析 54 | v-on:click='onClick' 55 | 这里的指令名称为 'on', 'click'为指令的参数,onClick 为key 56 | */ 57 | 58 | //移除 'v-' 前缀, 提取指令名称、指令参数 59 | let directiveInfo = directiveName.slice(prefix.length + 1).split(':'); 60 | directiveName = directiveInfo[0]; 61 | let directiveArg = directiveInfo[1] ? directiveInfo[1] : null; 62 | let directive = Directives[directiveName]; 63 | 64 | 65 | /** 66 | * 指令校验 67 | */ 68 | if (!directive) { 69 | console.warn('unknown directive: ' + directiveName); 70 | } 71 | 72 | return directive 73 | ? new DirectiveParser(directiveName, directiveArg, expression) 74 | : null; 75 | } 76 | 77 | static directives = Directives 78 | }; 79 | -------------------------------------------------------------------------------- /src/utils.js: -------------------------------------------------------------------------------- 1 | /** 2 | * type 3 | */ 4 | const toString = Object.prototype.toString; 5 | export function typeOf (obj) { 6 | return toString.call(obj).slice(8, -1); 7 | } 8 | 9 | export function isObject (obj) { 10 | return typeOf(obj) === 'Object' ? true : false; 11 | } 12 | 13 | export function isFunc (obj) { 14 | return typeOf(obj) === 'Function' ? true : false; 15 | } 16 | 17 | /********************************************** 18 | * 字符串处理 19 | */ 20 | export function toCamels(str) { 21 | let strArr = str.split('-'), 22 | newStr = ''; 23 | 24 | strArr.forEach((s, index)=>{ 25 | let firstChar = ''; 26 | 27 | if (index) { 28 | firstChar = s[0].toUpperCase(); 29 | } else { 30 | firstChar = s[0].toLowerCase(); 31 | } 32 | newStr += firstChar; 33 | newStr += s.substring(1); 34 | }); 35 | return newStr; 36 | } 37 | 38 | export function toLineStr (str) { 39 | str = str.toLowerCase(); 40 | let newStr = ''; 41 | for(let i = 0; i < str.length; i++) { 42 | if (str[i]>= 'A'&& str[i]<='Z') { 43 | newStr += '-'; 44 | newStr += str[i].toLowerCase(); 45 | } else { 46 | newStr += str[i]; 47 | } 48 | } 49 | return newStr; 50 | } 51 | 52 | /******************************************************* 53 | * 数组相关 54 | */ 55 | export function forEach (arr=[], cb) { 56 | [].forEach.call(arr, cb); 57 | } 58 | 59 | export function map (arr=[], cb) { 60 | return [].map.call(arr, cb); 61 | } 62 | 63 | /******************************************************* 64 | * 对象相关 65 | */ 66 | 67 | /** 68 | * 对象继承 69 | */ 70 | export function extend (child, parent, isNew=false) { 71 | parent = parent || {}; 72 | child = child || {}; 73 | 74 | if (isNew) { 75 | let ret = {}; 76 | objectEach(child, (key, value)=>{ 77 | ret[key] = value; 78 | }); 79 | 80 | objectEach(parent, (key, value)=>{ 81 | ret[key] = value; 82 | }); 83 | 84 | return ret; 85 | } else { 86 | objectEach(parent, (key, value)=>{ 87 | child[key] = value; 88 | }); 89 | return child; 90 | } 91 | } 92 | 93 | /** 94 | * 对象遍历 95 | */ 96 | export function objectEach (obj={}, cb=()=>{}) { 97 | Object.keys(obj).forEach(function (key) { 98 | cb(key, obj[key]); 99 | }); 100 | } 101 | 102 | export function objectMap (obj={}, cb=()=>{}) { 103 | return Object.keys(obj).map(function (key) { 104 | return cb(key, obj[key]); 105 | }); 106 | } 107 | 108 | /** 109 | * Object extend 110 | */ 111 | export function objectGet (obj, path='') { 112 | path = path.split('.'); 113 | 114 | if (obj[path[0]] == undefined) { 115 | obj[path[0]] = {}; 116 | } 117 | 118 | if (path.length == 1) { 119 | return obj[path[0]]; 120 | } else { 121 | return objectGet(obj[path[0]], path.slice(1).join('.')); 122 | } 123 | }; 124 | 125 | export function objectSet (obj, path='', value) { 126 | path = path.split('.'); 127 | if (path.length == 1) { 128 | obj[path[0]] = value; 129 | } else { 130 | obj[path[0]] = obj[path[0]] || {}; 131 | objectSet(obj[path[0]], path.slice(1).join('.'), value); 132 | } 133 | }; 134 | 135 | export function defer (fn, Timer=0) { 136 | setTimeout(()=>{ 137 | fn(); 138 | }, Timer); 139 | } -------------------------------------------------------------------------------- /src/component.js: -------------------------------------------------------------------------------- 1 | import Vm from './main.js'; 2 | import { 3 | map, 4 | extend, 5 | objectEach, 6 | toLineStr 7 | } from './utils.js'; 8 | 9 | export default { 10 | compilerComponent (el, component, vm) { 11 | let parent = el.parentNode, 12 | container = document.createElement('div'), 13 | next = el.nextSibling, 14 | startRef = document.createComment('Start of v-component'), 15 | endRef = document.createComment('End of v-component'), 16 | componentInstance; 17 | 18 | if (next) { 19 | parent.insertBefore(startRef, next); 20 | parent.insertBefore(endRef, next); 21 | } else { 22 | parent.appendChild(startRef); 23 | parent.appendChild(endRef); 24 | } 25 | 26 | /** 27 | * store el 28 | */ 29 | this.el = el.cloneNode(true); 30 | parent.removeChild(el); 31 | parent.insertBefore(container, endRef); 32 | 33 | /** 34 | * 父节点遍历标识更新 35 | */ 36 | parent.index += 2; 37 | 38 | container.innerHTML = component.template; 39 | component.el = container; 40 | 41 | let componentName = toLineStr(el.nodeName.toLowerCase()); 42 | component.name = componentName; 43 | component.isComponent = true; 44 | 45 | componentInstance = new Vm(component, vm); 46 | 47 | vm.addComponent(componentInstance); 48 | }, 49 | propsProcess (opts={}, vm) { 50 | if (!vm.$parent&&opts.props) { 51 | console.warn('Can not set props for root component.'); 52 | } 53 | if (opts.props) { 54 | /** 55 | * 数组声明形式 56 | */ 57 | let props = {}, 58 | processProps = {}, 59 | el = this.el; 60 | if (Array.isArray(opts.props)) { 61 | opts.props.forEach((prop)=>{ 62 | props[prop] = { 63 | required: false, 64 | defaultValue: '' 65 | }; 66 | }) 67 | 68 | } else { 69 | /** 70 | * 对象声明形式 71 | */ 72 | props = opts.props; 73 | } 74 | 75 | map(el.attributes, (attribute)=>{ 76 | /** 77 | * 静态props处理 78 | */ 79 | let name = attribute.name, 80 | value = attribute.value; 81 | 82 | if (props[name]) { 83 | processProps[name] = value; 84 | } else { 85 | /** 86 | * 动态props 87 | */ 88 | let attr = ''; 89 | if (name[0] === ':') { 90 | attr = name.substring(1); 91 | if (props[attr]) { 92 | processProps[attr] = vm.$parent[value]; 93 | 94 | /** 95 | * watch change 96 | */ 97 | vm.$parent.$watch(value, ()=>{ 98 | vm[attr] = vm.$parent[value]; 99 | }); 100 | } 101 | } 102 | } 103 | }); 104 | 105 | vm.$props = processProps; 106 | 107 | return processProps; 108 | } 109 | } 110 | } -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | ## tiny-vue 2 | 3 | rewrite vue.js. 4 | 5 | 包含一个比较完整的基本项目,webpack打包、mocha测试、eslint代码校验. 6 | 7 | [online demo](http://yangxiaofu.com/re-vue/examples/tiny-vue.html) 8 | 9 | [实现原理分析:https://github.com/xiaofuzi/deep-in-vue](https://github.com/xiaofuzi/deep-in-vue) 10 | 11 | ### 特性 12 | 13 | * 双向绑定 14 | * 计算属性 15 | * 事件支持 16 | * watch监测 17 | * 生命周期函数 18 | * 预定义指令 19 | * 自定义指令 20 | * 组件系统 21 | 22 | ### Usage 23 | 24 | example: 25 | 26 | ```html 27 |
28 |

29 | 30 | 31 | 32 |

33 |

34 |

35 |
36 | ``` 37 | 38 | ```js 39 | var mvvm; 40 | var opts = { 41 | el: '#app', 42 | data: { 43 | isShow: false, 44 | counter: 1, 45 | hello: 'ahahah!', 46 | info: { 47 | age: 18 48 | }, 49 | person: { 50 | weight: 20, 51 | height: 170 52 | } 53 | }, 54 | computed: { 55 | wellcome () { 56 | return {text: this.hello + '---' + this.info.age}; 57 | } 58 | }, 59 | methods: { 60 | add: function () { 61 | this.counter += 1; 62 | this.info.age += 1; 63 | }, 64 | toggle: function () { 65 | this.isShow = !this.isShow; 66 | } 67 | }, 68 | watch: { 69 | counter (val) { 70 | console.log('counter: ', val); 71 | }, 72 | info (info) { 73 | console.log('info: ', info); 74 | }, 75 | 'info.age' () { 76 | 77 | }, 78 | wellcome () { 79 | console.log('wellcome: ', this.wellcome); 80 | } 81 | }, 82 | ready () { 83 | let self = this; 84 | self.hello = 'Ready, go!'; 85 | 86 | setTimeout(function () { 87 | self.hello = 'Done!'; 88 | }, 1000) 89 | } 90 | } 91 | 92 | TinyVue.$directive('visible', function (value) { 93 | this.el.style.visibility = value ? 'visible' : 'hidden'; 94 | }) 95 | mvvm = new TinyVue(opts); 96 | ``` 97 | ### 组件系统示例 98 | 99 | ```html 100 |
101 |

组件系统示例

102 | 103 | 104 |
105 | ``` 106 | 107 | ```js 108 | var mvvm; 109 | var subComponent = { 110 | template: '

', 111 | data: function (){ 112 | return { 113 | name: 'an new component!' 114 | } 115 | }, 116 | props: ['info'] 117 | } 118 | 119 | TinyVue.component('hello-world', { 120 | template: '

', 121 | data: function (){ 122 | return { 123 | name: 'hello world component!' 124 | } 125 | }, 126 | props: ['hello', 'msg'] 127 | }); 128 | var opts = { 129 | el: '#app', 130 | data: { 131 | message: 'the fast mvvm framework.' 132 | }, 133 | computed: { 134 | 135 | }, 136 | components: { 137 | subComponent: subComponent 138 | }, 139 | methods: { 140 | 141 | }, 142 | watch: { 143 | 144 | }, 145 | ready () { 146 | 147 | } 148 | } 149 | 150 | mvvm = new TinyVue(opts); 151 | ``` 152 | 153 | 这里定义了``局部组件以及``全局组件。 154 | 155 | ## API 156 | 157 | ### options 158 | 159 | * el 160 | 161 | Type: `String | Node` 162 | 163 | 根节点选择器或是根节点dom元素。 164 | 165 | * template 166 | Type: `String` 167 | 组件模板 168 | 169 | * data 170 | 171 | Type: `Object` 172 | 173 | 初始化响应式数据模型 174 | 175 | * computed 176 | 177 | Type: `Object` 178 | 179 | 计算属性,每一个元素对应一个函数 180 | 181 | 注: 182 | * computed属性依赖于data中的响应式数据 183 | * computed属性可依赖computed属性 184 | * computed禁止赋值操作 185 | 186 | * methods 187 | 188 | Type: `Object` 189 | 每一个元素对应一个函数,支持响应式替换 190 | 191 | * watch 192 | 193 | Type: `Object` 194 | 195 | 监测对象,监测对应的响应式数据,当数据发生更改时执行回调. 196 | 197 | ### directives 198 | * v-text 199 | * v-show 200 | * v-visible 201 | * v-model 202 | * v-on 203 | * v-if 204 | * v-for 205 | 206 | ### 实例 api 207 | 208 | * $watch 209 | 210 | Type: `Function` 211 | 监测某一数据的响应式变化 212 | 213 | 如: 214 | ```js 215 | var vm = new TinyVue({ 216 | data: { 217 | info: { 218 | age: 18 219 | } 220 | } 221 | }); 222 | vm.$watch('info', function (info) { 223 | 224 | }); 225 | 226 | vm.$watch('info.age', function (age) { 227 | 228 | }) 229 | ``` 230 | 231 | * $directive 232 | 233 | Type: `Function` 234 | 235 | 自定义指令 236 | 237 | 如: 238 | ```js 239 | vm.$directive('text', function (text) { 240 | this.el.textContent = text; 241 | }); 242 | ``` 243 | * $reactive 244 | 将一个普通对象转换为一个响应式对象 245 | 246 | * component 247 | 定义全局组件 248 | ```js 249 | TinyVue.component(componentName, opts); 250 | ``` 251 | 252 | * beforeCompiler 253 | 254 | 生命周期函数,编译前执行 255 | 256 | * ready 257 | 258 | 生命周期函数,渲染完毕后执行 259 | 260 | ### Install 261 | 262 | ```js 263 | npm install tiny-vue --save 264 | ``` 265 | -------------------------------------------------------------------------------- /src/parsers/parserc/parser.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @privite 3 | */ 4 | 5 | function toParser (p) { 6 | return typeof(p) == 'string' ? token(p) : p; 7 | } 8 | 9 | function makeResult (remaining, matched, ast) { 10 | return { 11 | remaining: remaining, 12 | matched: matched, 13 | ast: ast 14 | }; 15 | } 16 | 17 | /** 18 | * primitive parsers 19 | */ 20 | export function ch (c) { 21 | return function (state) { 22 | let r = state.length >= 1 && state.at(0) == c; 23 | if (r) { 24 | return { 25 | remaining: state.advance(1), 26 | matched: c, 27 | ast: c 28 | }; 29 | } else { 30 | //console.warn('expect ' + c + ', but get ' + state.at(0) + ' at ' + state.index + '.'); 31 | return false; 32 | } 33 | }; 34 | } 35 | 36 | export function token (str) { 37 | return function (state) { 38 | let r = state.length >= str.length && state.substring(0, str.length) == str; 39 | 40 | if (r) { 41 | return { 42 | remaining: state.advance(str.length), 43 | matched: str, 44 | ast: str 45 | }; 46 | } else { 47 | return false; 48 | } 49 | }; 50 | } 51 | 52 | export function ignoreWhitespace (p) { 53 | p = toParser(p); 54 | 55 | return function (state) { 56 | return p(state.trimLeft()); 57 | }; 58 | } 59 | 60 | export function range (lower, upper) { 61 | return function (state) { 62 | if (state.length < 1) { 63 | return false; 64 | } else { 65 | var ch = state.at(0); 66 | if (ch >= lower && ch <= upper) { 67 | return { 68 | remaining: state.advance(1), 69 | matched: ch, 70 | ast: ch 71 | }; 72 | } else { 73 | return false; 74 | } 75 | } 76 | }; 77 | } 78 | 79 | 80 | export function sequence () { 81 | var parsers = []; 82 | for (var i=0; i= 1) { 154 | if (!p(state)) { 155 | result = makeResult(state.advance(1), state.at(0), state.at(0)); 156 | } 157 | } 158 | 159 | return result; 160 | }; 161 | } 162 | 163 | export function optional (p) { 164 | p = toParser(p); 165 | 166 | return function(state){ 167 | 168 | }; 169 | } 170 | 171 | export function repeat1 (p) { 172 | p = toParser(p); 173 | 174 | return function (state) { 175 | var ast = []; 176 | var matched = ''; 177 | var result = p(state); 178 | 179 | if (result) { 180 | while (result) { 181 | ast.push(result.ast); 182 | matched = matched + result.matched; 183 | 184 | state = result.remaining; 185 | result = p(state); 186 | } 187 | result = makeResult(state, matched, ast); 188 | } else { 189 | return false; 190 | } 191 | 192 | return result; 193 | }; 194 | } 195 | 196 | export function action (p, f) { 197 | p = toParser(p); 198 | 199 | return function (state) { 200 | var result = p(state); 201 | 202 | if (result) { 203 | result.ast = f(result.ast); 204 | } else { 205 | return false; 206 | } 207 | 208 | return result; 209 | }; 210 | } 211 | -------------------------------------------------------------------------------- /examples/tiny-vue.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | the super tiny vue.js 6 | 19 | 20 | 21 |
22 |

23 | 42 |

SubComponent

43 | 46 | 47 |
48 | 49 | 50 | 164 | 165 | 166 | -------------------------------------------------------------------------------- /src/binding.js: -------------------------------------------------------------------------------- 1 | import { directiveParser } from './parser.js'; 2 | import { objectEach } from './utils.js'; 3 | import { observer, arrayObserver } from './observer.js'; 4 | import { 5 | objectGet, 6 | objectSet, 7 | isObject, 8 | isFunc 9 | } from './utils.js'; 10 | 11 | const def = Object.defineProperty; 12 | 13 | let bindingId = 0; 14 | 15 | export default class Binding { 16 | constructor (vm, key, isComputed=false) { 17 | this.vm = vm; 18 | this.key = key; 19 | this.isComputed = isComputed; 20 | this.watches = []; 21 | 22 | this.directives = []; 23 | this.parent = null; 24 | this.children = []; 25 | 26 | /** 27 | * for computed property 28 | */ 29 | this.watcher = null; 30 | /** 31 | * init 32 | */ 33 | /** 34 | * vm不存在的Binding可watch其它Binding从而触发更新 35 | */ 36 | if (this.vm) { 37 | this.defineReactive(); 38 | } 39 | 40 | this.id = bindingId++; 41 | } 42 | 43 | 44 | defineReactive () { 45 | if (this.isComputed) { 46 | this.defineComputedProperty(); 47 | return ; 48 | } 49 | 50 | let self = this; 51 | let path = this.key.split('.'); 52 | let len = path.length; 53 | 54 | let obj, key, 55 | currentBindingData = objectGet(this.vm._bindingData, this.key), 56 | isObj = isObject(currentBindingData), 57 | isArray = Array.isArray(currentBindingData); 58 | 59 | if (len === 1) { 60 | obj = this.vm; 61 | key = this.key; 62 | self.value = isObj ? objectGet(this.vm, key) : ''; 63 | 64 | } else { 65 | let lastKey = path.splice(path.length - 1); 66 | obj = objectGet(this.vm, path.join('.')); 67 | key = lastKey[0]; 68 | 69 | self.value = isObj ? objectGet(this.vm, key) : ''; 70 | } 71 | 72 | def(obj, key, { 73 | get () { 74 | observer.isObserving && observer.emit('get', self); 75 | return self.value; 76 | }, 77 | set (value) { 78 | if (value !== self.value) { 79 | self.oldValue = self.value; 80 | /** 81 | * 考虑到原始类型与赋值类型的不同 82 | */ 83 | if (typeof value === 'string') { 84 | self.value = value; 85 | self.update(value); 86 | } else if (isArray) { 87 | self.value = value; 88 | 89 | arrayObserver(self.value, ()=>{ 90 | self.update(); 91 | }); 92 | self.update(self.value); 93 | } else if (isObj) { 94 | for (let prop in value) { 95 | self.value[prop] = value[prop]; 96 | } 97 | } else { 98 | self.value = value; 99 | self.update(value); 100 | } 101 | observer.emit(self.key, self); 102 | self.refresh(); 103 | } 104 | } 105 | }); 106 | } 107 | 108 | defineComputedProperty () { 109 | let key = this.key, 110 | obj = this.vm, 111 | self = this; 112 | 113 | def(obj, key, { 114 | get () { 115 | let getter = self.vm._opts.computed[key]; 116 | if (isFunc(getter)) { 117 | self.value = getter.call(self.vm); 118 | 119 | return self.value; 120 | } 121 | }, 122 | set () { 123 | //console.warn('computed property is readonly.'); 124 | } 125 | }); 126 | } 127 | 128 | update (value=null) { 129 | if (value === null) { 130 | value = this.value; 131 | } 132 | 133 | let self = this; 134 | this.directives.forEach(function (directive) { 135 | /** 136 | * 计算属性绑定对应于多个不同key的指令,所以值需要动态获取 137 | */ 138 | if (self.isComputed) { 139 | directive.update(objectGet(self.vm, directive.key)); 140 | } else { 141 | directive.update(value); 142 | } 143 | }); 144 | 145 | this._updateChildren(); 146 | } 147 | 148 | refresh () { 149 | if (this.isComputed) { 150 | this.update(this.vm[this.key]); 151 | } 152 | /** 153 | * notify parent 154 | */ 155 | if (this.parent) { 156 | this.parent.refresh(); 157 | } 158 | 159 | this.watches.forEach((cb)=>{ 160 | cb.call(this.vm, this.value, this.oldValue); 161 | }); 162 | } 163 | 164 | add (childBinding) { 165 | this.children.push(childBinding); 166 | childBinding.parent = this; 167 | childBinding.value = this.value; 168 | } 169 | 170 | appendTo (parentBinding) { 171 | parentBinding.children.push(this); 172 | this.parent = parentBinding; 173 | this.value = parentBinding.value; 174 | } 175 | 176 | remove (childBinding) { 177 | this.children = this.children.filter((child)=>{ 178 | return child.id != childBinding.id; 179 | }) 180 | } 181 | 182 | destroy () { 183 | this.parent.remove(this); 184 | this.parent = null; 185 | } 186 | 187 | _updateChildren () { 188 | this.children.forEach((child)=>{ 189 | child.update(this.value); 190 | }); 191 | } 192 | } -------------------------------------------------------------------------------- /src/parsers/arrayPathParser.js: -------------------------------------------------------------------------------- 1 | import { 2 | ch, token, 3 | range, 4 | sequence, 5 | wsequence, 6 | choice, 7 | repeat1, 8 | action 9 | } from './parserc/parser.js'; 10 | 11 | import ps from './parserc/state.js'; 12 | 13 | /** 14 | * the parser for array path 15 | */ 16 | 17 | /** 18 | * atom parser 19 | */ 20 | function leftParen () { 21 | return ch('['); 22 | } 23 | 24 | function rightParen () { 25 | return ch(']'); 26 | } 27 | 28 | function quote () { 29 | return ch("'"); 30 | } 31 | 32 | function quotes () { 33 | return ch('"'); 34 | } 35 | 36 | /** 37 | * identifer parser 38 | * array name 39 | * a-z/A-Z/[_] 40 | */ 41 | function identifer () { 42 | let result = repeat1( 43 | choice( 44 | range('a', 'z'), 45 | range('A', 'Z'), 46 | ch('_'), 47 | ch('$') 48 | ) 49 | ); 50 | return action(result, function(ast){ 51 | return { 52 | type: 'identifer', 53 | value: ast.join('') 54 | }; 55 | }); 56 | } 57 | 58 | /** 59 | * identifer key 60 | */ 61 | function identiferKey () { 62 | let result = identifer(); 63 | 64 | return action(result, function (ast) { 65 | return { 66 | type: 'identiferKey', 67 | value: ast.value 68 | }; 69 | }); 70 | } 71 | 72 | /** 73 | * string key 74 | * 'key'/"key" 75 | */ 76 | function stringKey () { 77 | return function (state) { 78 | let result = choice( 79 | sequence( 80 | quote(), 81 | identifer(), 82 | quote() 83 | ), 84 | sequence( 85 | quotes(), 86 | identifer(), 87 | quotes() 88 | ) 89 | )(state); 90 | 91 | if (!result) return false; 92 | 93 | return { 94 | remaining: result.remaining, 95 | matched: result.matched, 96 | ast: { 97 | type: 'stringKey', 98 | value: result.ast[1] 99 | } 100 | }; 101 | }; 102 | } 103 | 104 | function pointKey () { 105 | return function (state) { 106 | let result = sequence( 107 | ch('.'), 108 | identiferKey() 109 | )(state); 110 | console.log('pointKey: ', result); 111 | 112 | if (!result) return false; 113 | return { 114 | remaining: result.remaining, 115 | matched: result.matched, 116 | ast: { 117 | type: 'pointKey', 118 | value: result.ast[1] 119 | } 120 | }; 121 | }; 122 | } 123 | 124 | function parenKey () { 125 | return function (state) { 126 | let result = sequence( 127 | leftParen(), 128 | choice( 129 | identiferKey(), 130 | stringKey()//, 131 | //getExpresion() 132 | ), 133 | rightParen() 134 | )(state); 135 | 136 | console.log('parenKey: ', result); 137 | if (!result) return false; 138 | 139 | return { 140 | remaining: result.remaining, 141 | matched: result.matched, 142 | ast: { 143 | type: 'parenKey', 144 | value: result.ast[1] 145 | } 146 | }; 147 | }; 148 | } 149 | 150 | /** 151 | * object get expression 152 | */ 153 | function getExpresion () { 154 | let result = sequence( 155 | identifer(), 156 | repeat1( 157 | choice( 158 | parenKey(), 159 | pointKey() 160 | ) 161 | ) 162 | ); 163 | 164 | return action(result, function (ast) { 165 | return { 166 | type: 'getterExpression', 167 | value: { 168 | object: ast[0], 169 | keys: ast[1] 170 | } 171 | }; 172 | }); 173 | } 174 | 175 | /** 176 | * arrayParser 177 | */ 178 | function evalArrayPathObject (pathStr, ctx) { 179 | let currentValue = null, 180 | ast = getExpresion()(ps(pathStr)).ast; 181 | 182 | if (ast) { 183 | evalValue(ast); 184 | } 185 | 186 | return currentValue; 187 | 188 | function evalValue (ast) { 189 | if (ast.type == 'getterExpression') { 190 | currentValue = ctx[ast.value.object.value]; 191 | ast.value.keys.forEach((key)=>{ 192 | if (key.value.type === 'identiferKey') { 193 | currentValue = currentValue[ctx[key.value.value]]; 194 | } else { 195 | currentValue = currentValue[key.value.value.value]; 196 | } 197 | }); 198 | } 199 | } 200 | } 201 | 202 | /* 203 | let ctx = { 204 | person: { 205 | age: 18, 206 | name: 'yang', 207 | info: { 208 | height: 170 209 | } 210 | }, 211 | key: 'info' 212 | } 213 | 214 | let val = evalArrayPathObject('person[key]["height"]', ctx); 215 | 216 | console.log('val: ', val); 217 | */ 218 | export { 219 | evalArrayPathObject 220 | }; 221 | 222 | //console.log(stringKey()(ps('"item"'))); 223 | console.log('getter: ', getExpresion()(ps('obj[a]'))); 224 | console.log(parenKey()(ps('[obj[a]]'))); 225 | console.log('getter: ', getExpresion()(ps('obj[info][age]["weight"]'))); 226 | //console.log('complex: ', complexGetExpression()(ps('person[name][info][obj["name"]]'))); -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import Binding from './binding.js'; 2 | import Watcher from './watcher.js'; 3 | import { DirectiveParser } from './parser.js'; 4 | import { observer } from './observer.js'; 5 | import ComponentParser from './component.js'; 6 | 7 | import Config from './config.js'; 8 | const { prefix } = Config; 9 | 10 | import { 11 | extend, 12 | forEach, 13 | map, 14 | objectEach, 15 | objectMap, 16 | isObject, 17 | isFunc, 18 | objectGet, 19 | objectSet, 20 | defer, 21 | toCamels, 22 | toLineStr 23 | } from './utils.js'; 24 | 25 | 26 | /** 27 | * Main class 28 | */ 29 | let vmId = 0; 30 | export default class Main { 31 | constructor (opts={}, parent) { 32 | /** 33 | * this.$el: 根节点 34 | * _bindings: 指令与data关联的桥梁 35 | */ 36 | this.$el = typeof opts.el === 'string' ? document.querySelector(opts.el) : opts.el; 37 | this.$parent = parent; 38 | this.$children = []; 39 | this.$components = []; 40 | this.$id = vmId++; 41 | this.$name = opts.name || 'vm'; 42 | this.$isComponent = opts.isComponent || false; 43 | /** 44 | * @private 45 | */ 46 | 47 | /** 48 | * data opt process 49 | */ 50 | if (isFunc(opts.data)) { 51 | opts.data = opts.data(); 52 | } 53 | 54 | this._bindings = {}; 55 | this._opts = opts; 56 | this._bindingData = {}; 57 | 58 | this.beforeCompiler(); 59 | this.init(); 60 | 61 | this.ready(); 62 | } 63 | 64 | static components = {} 65 | 66 | /** 67 | * 初始化函数 68 | */ 69 | init () { 70 | let self = this; 71 | 72 | /** 73 | * components 数据挂载 74 | */ 75 | this.components = this._opts.components; 76 | 77 | let _data = this._bindingData = extend(this._opts.data, this._opts.methods, true); 78 | objectEach(_data, (path, item) => { 79 | this._initReactive(path, item); 80 | }); 81 | 82 | this._initComputed (); 83 | 84 | this._initProps(); 85 | /** 86 | * 指令处理 87 | */ 88 | this._compileNode(this.$el); 89 | 90 | /** 91 | * vm响应式数据的初始化 92 | */ 93 | for (let key in this._bindings) { 94 | this.$set(key, objectGet(_data, key)); 95 | } 96 | 97 | /** 98 | * props 初始化 99 | */ 100 | objectEach(this.$props, (key, item)=>{ 101 | this[key] = item; 102 | }); 103 | 104 | /** 105 | * watch 函数注册 106 | */ 107 | let _watches = this._opts.watch || {}; 108 | objectEach(_watches, (key, cb)=>{ 109 | if (this._bindings[key]) { 110 | this.$watch(key, cb); 111 | } 112 | }); 113 | } 114 | 115 | //public api 116 | $watch (key, cb) { 117 | let _binding = this._bindings[key]; 118 | _binding.watches.push(cb); 119 | } 120 | 121 | /** 122 | * turn an normal object to reactive obeject 123 | */ 124 | $reactive (obj={}) { 125 | objectEach(obj, (path, item)=>{ 126 | this._initReactive(path, item); 127 | }); 128 | } 129 | 130 | $destroy () { 131 | this.remove(); 132 | forEach(this._bindings, (binding)=>{ 133 | binding.destroy(); 134 | }); 135 | this.$parent = null; 136 | this.$el.parentNode.removeChild(this.$el); 137 | } 138 | 139 | /** 140 | * 生命周期函数 141 | */ 142 | beforeCompiler () { 143 | if (this._opts.beforeCompiler && typeof this._opts.beforeCompiler === 'function') { 144 | this._opts.beforeCompiler.call(this); 145 | } 146 | } 147 | 148 | ready () { 149 | if (this._opts.ready && typeof this._opts.ready === 'function') { 150 | this._opts.ready.call(this); 151 | } 152 | } 153 | 154 | /** 155 | * @private 156 | */ 157 | /** 158 | * viewModel树形结构 159 | */ 160 | appendTo (vm) { 161 | this.$parent = vm; 162 | vm.$children.push(this); 163 | } 164 | 165 | remove () { 166 | this.$parent.$children = this.$parent.$children.filter((child)=>{ 167 | return child.$id != this.$id; 168 | }) 169 | } 170 | 171 | /** 172 | * 组件树结构 173 | */ 174 | addComponent (component) { 175 | this.$components.push(component); 176 | component.$parent = this; 177 | } 178 | 179 | _initProps () { 180 | /** 181 | * props process 182 | */ 183 | let propsData = ComponentParser.propsProcess(this._opts, this); 184 | if (propsData) { 185 | this.$reactive(propsData); 186 | 187 | this.$props = propsData; 188 | } 189 | } 190 | 191 | _initComputed () { 192 | /** 193 | * computed 属性预处理 194 | */ 195 | let _computed = this._opts.computed || {}, 196 | self = this; 197 | 198 | for (let key in _computed) { 199 | let computedGetter = _computed[key]; 200 | if (!isFunc(computedGetter)) { 201 | continue; 202 | } 203 | let binding; 204 | if (this._bindings[key]) { 205 | console.warn('Can not redefine ' + key + ' property.'); 206 | } else { 207 | let isComputed = true; 208 | binding = new Binding(this, key, isComputed); 209 | this._bindings[key] = binding; 210 | } 211 | 212 | /** 213 | * 计算属性依赖监测 214 | */ 215 | let watcher = new Watcher(binding); 216 | observer.isObserving = true; 217 | watcher.getDeps(); 218 | computedGetter.call(this); 219 | observer.isObserving = false; 220 | watcher.watch(); 221 | 222 | /** 223 | * 初始化,等待值初始化完毕后在获取计算computed的值 224 | */ 225 | setTimeout(()=>{ 226 | binding.update(binding.value); 227 | }, 0); 228 | } 229 | } 230 | 231 | _compileNode (el) { 232 | let self = this; 233 | let isCompiler = true; 234 | 235 | /** 236 | * 子组件处理 237 | */ 238 | let nodeName = el.nodeName.toLowerCase(), 239 | component = getComponent(this, toCamels(nodeName)); 240 | 241 | if (!component) { 242 | component = getComponent(this, nodeName); 243 | } 244 | 245 | if (component) { 246 | this._compilerComponent(el, component); 247 | 248 | return ; 249 | } 250 | 251 | /** 252 | * 过滤注释节点 253 | */ 254 | 255 | if (el.nodeType === 8) { 256 | return ; 257 | } 258 | 259 | if (el.nodeType === 3) { 260 | self._compileTextNode(el); 261 | } else if (el.attributes && el.attributes.length) { 262 | getAttributes(el.attributes).forEach(function (attr) { 263 | let directive = DirectiveParser.parse(attr.name, attr.value); 264 | 265 | if (directive) { 266 | if (DirectiveParser.directives[directive.name].isBlock) { 267 | isCompiler = false; 268 | } 269 | 270 | directive.vm = self; 271 | self._bind(el, directive); 272 | } 273 | }); 274 | } 275 | 276 | let len = el.childNodes.length; 277 | if (isCompiler&&len) { 278 | el.index = 0; 279 | for (; el.index < el.childNodes.length; el.index++) { 280 | /** 281 | * el.index 表示当前第几个子元素,在_compileNode函数中可能会更改el的子元素结构, 282 | * 所以需要el.index来标识编译的节点索引 283 | */ 284 | let child = el.childNodes[el.index]; 285 | self._compileNode(child); 286 | } 287 | } 288 | } 289 | 290 | _compileTextNode (el) { 291 | return el; 292 | } 293 | 294 | _compilerComponent (el, componentOpts) { 295 | ComponentParser.compilerComponent(el, componentOpts, this); 296 | } 297 | 298 | /** 299 | * bind directive 300 | */ 301 | _bind (el, directive) { 302 | let self = this, 303 | isParentVm = false; 304 | el.removeAttribute(prefix + '-' + directive.name); 305 | directive.el = el; 306 | let key = directive.key, 307 | binding = this._getBinding(this, key); 308 | 309 | processBinding(key); 310 | /** 311 | * directive hook 312 | */ 313 | if (directive.bind) { 314 | directive.bind(); 315 | } 316 | if (!binding) { 317 | /** 318 | * computed property binding hack 319 | * 针对计算属性子属性 320 | */ 321 | 322 | //get computed property key 323 | let computedKey = key.split('.')[0]; 324 | binding = this._getBinding(this, computedKey); 325 | processBinding(computedKey); 326 | if (binding.isComputed) { 327 | binding.directives.push(directive); 328 | } else { 329 | console.error(key + ' is not defined in ' + this.$name + ' component.'); 330 | } 331 | } else { 332 | binding.directives.push(directive); 333 | } 334 | 335 | /** 336 | * 子 vm bingding时更新DOM 337 | */ 338 | if (isParentVm) { 339 | binding.update(); 340 | } 341 | 342 | function processBinding (key) { 343 | /** 344 | * 根据model值是否在当前viewModel从而做不同的处理 345 | */ 346 | if (binding === true) { 347 | binding = self._bindings[key]; 348 | } else if (isObject(binding)) { 349 | isParentVm = true; 350 | let parentBinding = binding.binding; 351 | binding = self._createBinding(key); 352 | binding.appendTo(parentBinding); 353 | } 354 | } 355 | } 356 | 357 | _createBinding (key, ctx) { 358 | this._bindings[key] = new Binding(ctx||this, key); 359 | return this._bindings[key]; 360 | } 361 | 362 | /** 363 | * init reactive data 364 | */ 365 | _initReactive (path, value) { 366 | let binding; 367 | if (this._bindings[path]) { 368 | console.warn(path + ' binding had created.'); 369 | return ; 370 | } 371 | if (isObject(value)) { 372 | binding = this._createBinding(path); 373 | 374 | let bindings = objectMap(value, (key, item)=>{ 375 | let childBinding = this._initReactive(`${path}.${key}`, item); 376 | childBinding.parent = binding; 377 | return childBinding; 378 | }); 379 | binding.children = bindings; 380 | } else { 381 | binding = this._createBinding(path); 382 | } 383 | 384 | return binding; 385 | } 386 | 387 | /** 388 | * vm binding 获取,支持继承 389 | */ 390 | _getBinding (vm, key) { 391 | if (this._bindings[key]) { 392 | return true; 393 | } else if (this.$isComponent) { 394 | /** 395 | * 父子组件vm隔离 396 | */ 397 | return false; 398 | } 399 | 400 | if (!vm) { 401 | return false; 402 | } 403 | 404 | let binding = vm._bindings[key]; 405 | if (binding) { 406 | return { 407 | binding: binding, 408 | vm: vm, 409 | key: key 410 | } 411 | } else { 412 | return this._getBinding(vm.$parent, key); 413 | } 414 | } 415 | 416 | $get (path) { 417 | return objectGet(this, path); 418 | } 419 | 420 | $set (path, value) { 421 | return objectSet(this, path, value); 422 | } 423 | } 424 | 425 | /************************************************************** 426 | * @privete 427 | * helper methods 428 | */ 429 | 430 | /** 431 | * 获取节点属性 432 | * 'v-text'='counter' => {name: v-text, value: 'counter'} 433 | */ 434 | function getAttributes (attributes) { 435 | return map(attributes, function (attr) { 436 | return { 437 | name: attr.name, 438 | value: attr.value 439 | }; 440 | }); 441 | } 442 | 443 | /** 444 | * 获取 component 辅助函数 445 | * vm嵌套情况,组件声明存储于更vm实例 446 | */ 447 | function getComponent (vm, componentName) { 448 | let components = getComponents(vm); 449 | 450 | if (components) { 451 | return components[componentName] || Main.components[componentName]; 452 | 453 | } else { 454 | return Main.components[componentName]; 455 | } 456 | } 457 | 458 | function getComponents (vm) { 459 | /** 460 | * 子组件与父组件式隔离作用域的,所以不会获取父组件的vm 461 | */ 462 | if (vm.$isComponent) { 463 | return vm.components; 464 | } 465 | if (vm.$parent) { 466 | return getComponents(vm.$parent); 467 | } else { 468 | return vm.components; 469 | } 470 | } 471 | 472 | 473 | 474 | 475 | 476 | 477 | 478 | 479 | 480 | 481 | 482 | 483 | 484 | 485 | --------------------------------------------------------------------------------