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