├── .gitignore ├── src ├── core │ ├── vdom │ │ ├── helpers │ │ │ ├── index.js │ │ │ └── normalize-children.js │ │ ├── dom-props.js │ │ ├── vnode.js │ │ ├── node-ops.js │ │ ├── attrs.js │ │ ├── class.js │ │ ├── events.js │ │ └── patch.js │ ├── util │ │ ├── index.js │ │ ├── debug.js │ │ ├── env.js │ │ └── lang.js │ ├── index.js │ ├── instance │ │ ├── render-helpers │ │ │ ├── check-keycodes.js │ │ │ └── render-list.js │ │ ├── init.js │ │ ├── render.js │ │ ├── old.js │ │ ├── state.js │ │ └── index.js │ ├── global-api │ │ └── index.js │ └── observer │ │ ├── array.js │ │ ├── dep.js │ │ ├── watcher.js │ │ └── index.js ├── entries │ └── index.js ├── compiler │ ├── index.js │ ├── parser │ │ └── text-parser.js │ └── codegen │ │ ├── events.js │ │ └── index.js └── shared │ └── util.js ├── branches.txt ├── .editorconfig ├── examples ├── 2.1 │ └── todo │ │ ├── js │ │ ├── store.js │ │ └── app.js │ │ ├── index.html │ │ └── node_modules │ │ └── todomvc-common │ │ ├── base.css │ │ └── base.js ├── 2.5 │ └── todo │ │ ├── js │ │ ├── store.js │ │ ├── routes.js │ │ └── app.js │ │ ├── index.html │ │ └── node_modules │ │ └── todomvc-common │ │ └── base.css ├── 2.2.1 │ ├── todo │ │ ├── js │ │ │ ├── store.js │ │ │ └── app.js │ │ ├── index.html │ │ └── node_modules │ │ │ └── todomvc-common │ │ │ ├── base.css │ │ │ └── base.js │ ├── examples.css │ └── examples1.html ├── 2.2.2 │ └── todo │ │ ├── js │ │ ├── store.js │ │ └── app.js │ │ ├── index.html │ │ └── node_modules │ │ └── todomvc-common │ │ ├── base.css │ │ └── base.js ├── 2.3.1 │ └── todo │ │ ├── js │ │ ├── store.js │ │ ├── routes.js │ │ └── app.js │ │ ├── index.html │ │ └── node_modules │ │ └── todomvc-common │ │ ├── base.css │ │ └── base.js ├── 2.3.2 │ └── todo │ │ ├── js │ │ ├── store.js │ │ ├── routes.js │ │ └── app.js │ │ ├── index.html │ │ └── node_modules │ │ └── todomvc-common │ │ ├── base.css │ │ └── base.js ├── 2.4.1 │ ├── todo │ │ ├── js │ │ │ ├── store.js │ │ │ ├── routes.js │ │ │ └── app.js │ │ ├── index.html │ │ └── node_modules │ │ │ └── todomvc-common │ │ │ ├── base.css │ │ │ └── base.js │ ├── examples.css │ └── examples1.html ├── 2.4.2 │ ├── todo │ │ ├── js │ │ │ ├── store.js │ │ │ ├── routes.js │ │ │ └── app.js │ │ ├── node_modules │ │ │ └── todomvc-common │ │ │ │ ├── base.css │ │ │ │ └── base.js │ │ └── index.html │ ├── examples.css │ └── examples1.html ├── 1.1 │ ├── examples.css │ ├── examples1.html │ ├── examples2.html │ └── examples3.html ├── 1.2 │ ├── examples.css │ ├── examples1.html │ ├── examples2.html │ └── examples3.html ├── 1.3 │ ├── examples.css │ ├── examples1.html │ └── examples2.html └── 2.2.2.1 │ ├── examples.css │ └── examples1.html ├── webpack.config.js ├── package.json ├── tools └── line.js └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | .DS_Store 3 | -------------------------------------------------------------------------------- /src/core/vdom/helpers/index.js: -------------------------------------------------------------------------------- 1 | export * from './normalize-children' -------------------------------------------------------------------------------- /src/entries/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'core/index' 2 | 3 | window.Vue = Vue 4 | -------------------------------------------------------------------------------- /src/core/util/index.js: -------------------------------------------------------------------------------- 1 | export * from 'shared/util' 2 | export * from './lang' 3 | export * from './env' 4 | export * from './debug' -------------------------------------------------------------------------------- /branches.txt: -------------------------------------------------------------------------------- 1 | 1.1 2 | 1.2 3 | 1.2.1 4 | 1.3 5 | 2.1 6 | 2.2.1 7 | 2.2.2 8 | 2.2.2.1 9 | 2.3.1 10 | 2.3.2 11 | 2.4.1 12 | 2.4.2 13 | 2.5 14 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | indent_style = space 3 | indent_size = 2 4 | end_of_line = lf 5 | charset = utf-8 6 | trim_trailing_whitespace = true 7 | [*.md] 8 | indent_style = tab 9 | -------------------------------------------------------------------------------- /src/core/index.js: -------------------------------------------------------------------------------- 1 | import Vue from './instance/index' 2 | import { initGlobalAPI } from './global-api/index' 3 | 4 | initGlobalAPI(Vue) 5 | 6 | Vue.version = '2.2.0' 7 | 8 | export default Vue 9 | -------------------------------------------------------------------------------- /src/core/util/debug.js: -------------------------------------------------------------------------------- 1 | const hasConsole = typeof console !== 'undefined' 2 | 3 | export function warn (msg, vm) { 4 | if (hasConsole) { 5 | console.error(`[Vue warn]: ${msg} `) 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/core/vdom/helpers/normalize-children.js: -------------------------------------------------------------------------------- 1 | 2 | // 对v-for的复杂的情况做处理 _c('ul', undefined, [_c('div'), _l(xxx), _c('div')]) 3 | // _l(xxx) 返回是一个 [VNode, VNode] 数组 4 | export function simpleNormalizeChildren (children) { 5 | for (let i = 0; i < children.length; i++) { 6 | if (Array.isArray(children[i])) { 7 | return Array.prototype.concat.apply([], children) 8 | } 9 | } 10 | return children 11 | } 12 | -------------------------------------------------------------------------------- /src/core/instance/render-helpers/check-keycodes.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Runtime helper for checking keyCodes from config. 3 | */ 4 | // _k($event.keyCode,"enter",13) 5 | export function checkKeyCodes (eventKeyCode, key, builtInAlias) { 6 | const keyCodes = builtInAlias 7 | if (Array.isArray(keyCodes)) { 8 | return keyCodes.indexOf(eventKeyCode) === -1 9 | } else { 10 | return keyCodes !== eventKeyCode 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /examples/2.1/todo/js/store.js: -------------------------------------------------------------------------------- 1 | /*jshint unused:false */ 2 | 3 | (function (exports) { 4 | 5 | 'use strict'; 6 | 7 | var STORAGE_KEY = 'todos-vuejs'; 8 | 9 | exports.todoStorage = { 10 | fetch: function () { 11 | return JSON.parse(localStorage.getItem(STORAGE_KEY) || '[]'); 12 | }, 13 | save: function (todos) { 14 | localStorage.setItem(STORAGE_KEY, JSON.stringify(todos)); 15 | } 16 | }; 17 | 18 | })(window); 19 | -------------------------------------------------------------------------------- /examples/2.5/todo/js/store.js: -------------------------------------------------------------------------------- 1 | /*jshint unused:false */ 2 | 3 | (function (exports) { 4 | 5 | 'use strict'; 6 | 7 | var STORAGE_KEY = 'todos-vuejs'; 8 | 9 | exports.todoStorage = { 10 | fetch: function () { 11 | return JSON.parse(localStorage.getItem(STORAGE_KEY) || '[]'); 12 | }, 13 | save: function (todos) { 14 | localStorage.setItem(STORAGE_KEY, JSON.stringify(todos)); 15 | } 16 | }; 17 | 18 | })(window); 19 | -------------------------------------------------------------------------------- /examples/2.2.1/todo/js/store.js: -------------------------------------------------------------------------------- 1 | /*jshint unused:false */ 2 | 3 | (function (exports) { 4 | 5 | 'use strict'; 6 | 7 | var STORAGE_KEY = 'todos-vuejs'; 8 | 9 | exports.todoStorage = { 10 | fetch: function () { 11 | return JSON.parse(localStorage.getItem(STORAGE_KEY) || '[]'); 12 | }, 13 | save: function (todos) { 14 | localStorage.setItem(STORAGE_KEY, JSON.stringify(todos)); 15 | } 16 | }; 17 | 18 | })(window); 19 | -------------------------------------------------------------------------------- /examples/2.2.2/todo/js/store.js: -------------------------------------------------------------------------------- 1 | /*jshint unused:false */ 2 | 3 | (function (exports) { 4 | 5 | 'use strict'; 6 | 7 | var STORAGE_KEY = 'todos-vuejs'; 8 | 9 | exports.todoStorage = { 10 | fetch: function () { 11 | return JSON.parse(localStorage.getItem(STORAGE_KEY) || '[]'); 12 | }, 13 | save: function (todos) { 14 | localStorage.setItem(STORAGE_KEY, JSON.stringify(todos)); 15 | } 16 | }; 17 | 18 | })(window); 19 | -------------------------------------------------------------------------------- /examples/2.3.1/todo/js/store.js: -------------------------------------------------------------------------------- 1 | /*jshint unused:false */ 2 | 3 | (function (exports) { 4 | 5 | 'use strict'; 6 | 7 | var STORAGE_KEY = 'todos-vuejs'; 8 | 9 | exports.todoStorage = { 10 | fetch: function () { 11 | return JSON.parse(localStorage.getItem(STORAGE_KEY) || '[]'); 12 | }, 13 | save: function (todos) { 14 | localStorage.setItem(STORAGE_KEY, JSON.stringify(todos)); 15 | } 16 | }; 17 | 18 | })(window); 19 | -------------------------------------------------------------------------------- /examples/2.3.2/todo/js/store.js: -------------------------------------------------------------------------------- 1 | /*jshint unused:false */ 2 | 3 | (function (exports) { 4 | 5 | 'use strict'; 6 | 7 | var STORAGE_KEY = 'todos-vuejs'; 8 | 9 | exports.todoStorage = { 10 | fetch: function () { 11 | return JSON.parse(localStorage.getItem(STORAGE_KEY) || '[]'); 12 | }, 13 | save: function (todos) { 14 | localStorage.setItem(STORAGE_KEY, JSON.stringify(todos)); 15 | } 16 | }; 17 | 18 | })(window); 19 | -------------------------------------------------------------------------------- /examples/2.4.1/todo/js/store.js: -------------------------------------------------------------------------------- 1 | /*jshint unused:false */ 2 | 3 | (function (exports) { 4 | 5 | 'use strict'; 6 | 7 | var STORAGE_KEY = 'todos-vuejs'; 8 | 9 | exports.todoStorage = { 10 | fetch: function () { 11 | return JSON.parse(localStorage.getItem(STORAGE_KEY) || '[]'); 12 | }, 13 | save: function (todos) { 14 | localStorage.setItem(STORAGE_KEY, JSON.stringify(todos)); 15 | } 16 | }; 17 | 18 | })(window); 19 | -------------------------------------------------------------------------------- /examples/2.4.2/todo/js/store.js: -------------------------------------------------------------------------------- 1 | /*jshint unused:false */ 2 | 3 | (function (exports) { 4 | 5 | 'use strict'; 6 | 7 | var STORAGE_KEY = 'todos-vuejs'; 8 | 9 | exports.todoStorage = { 10 | fetch: function () { 11 | return JSON.parse(localStorage.getItem(STORAGE_KEY) || '[]'); 12 | }, 13 | save: function (todos) { 14 | localStorage.setItem(STORAGE_KEY, JSON.stringify(todos)); 15 | } 16 | }; 17 | 18 | })(window); 19 | -------------------------------------------------------------------------------- /src/core/global-api/index.js: -------------------------------------------------------------------------------- 1 | import { 2 | warn, 3 | } from '../util/index' 4 | 5 | import { 6 | set, 7 | del, 8 | } from '../observer/index' 9 | 10 | export function initGlobalAPI (Vue) { 11 | 12 | // exposed util methods. 13 | // NOTE: these are not considered part of the public API - avoid relying on 14 | // them unless you are aware of the risk. 15 | Vue.util = { 16 | warn, 17 | } 18 | 19 | Vue.set = set 20 | Vue.delete = del 21 | } -------------------------------------------------------------------------------- /examples/2.1/todo/js/app.js: -------------------------------------------------------------------------------- 1 | /*global Vue, todoStorage */ 2 | 3 | (function (exports) { 4 | 5 | 'use strict'; 6 | 7 | var vm = exports.app = new Vue({ 8 | 9 | template: document.getElementById("tmpl").innerHTML, 10 | 11 | // app initial state 12 | data: { 13 | todos: todoStorage.fetch(), 14 | newTodo: 'default newTodo text', 15 | editedTodo: null, 16 | visibility: 'all' 17 | }, 18 | }); 19 | 20 | vm.$mount("todoapp") 21 | 22 | })(window); 23 | -------------------------------------------------------------------------------- /examples/2.2.1/todo/js/app.js: -------------------------------------------------------------------------------- 1 | /*global Vue, todoStorage */ 2 | 3 | (function (exports) { 4 | 5 | 'use strict'; 6 | 7 | var vm = exports.app = new Vue({ 8 | 9 | template: document.getElementById("tmpl").innerHTML, 10 | 11 | // app initial state 12 | data: { 13 | todos: todoStorage.fetch(), 14 | newTodo: 'default newTodo text', 15 | editedTodo: null, 16 | visibility: 'all' 17 | }, 18 | }); 19 | 20 | vm.$mount("todoapp") 21 | 22 | })(window); 23 | -------------------------------------------------------------------------------- /src/core/vdom/dom-props.js: -------------------------------------------------------------------------------- 1 | export function updateDOMProps (oldVnode, vnode) { 2 | if (!oldVnode.data.domProps && !vnode.data.domProps) { 3 | return 4 | } 5 | let key, cur 6 | const elm = vnode.elm 7 | const oldProps = oldVnode.data.domProps || {} 8 | 9 | let props = vnode.data.domProps || {} 10 | 11 | for (key in oldProps) { 12 | if (props[key] == null) { 13 | elm[key] = '' 14 | } 15 | } 16 | for (key in props) { 17 | elm[key] = props[key] 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /examples/2.3.1/todo/js/routes.js: -------------------------------------------------------------------------------- 1 | /*global app, Router */ 2 | 3 | (function (app, Router) { 4 | 5 | 'use strict'; 6 | 7 | var router = new Router(); 8 | 9 | ['all', 'active', 'completed'].forEach(function (visibility) { 10 | router.on(visibility, function () { 11 | app.visibility = visibility; 12 | }); 13 | }); 14 | 15 | router.configure({ 16 | notfound: function () { 17 | window.location.hash = ''; 18 | app.visibility = 'all'; 19 | } 20 | }); 21 | 22 | router.init(); 23 | 24 | })(app, Router); 25 | -------------------------------------------------------------------------------- /examples/2.3.2/todo/js/routes.js: -------------------------------------------------------------------------------- 1 | /*global app, Router */ 2 | 3 | (function (app, Router) { 4 | 5 | 'use strict'; 6 | 7 | var router = new Router(); 8 | 9 | ['all', 'active', 'completed'].forEach(function (visibility) { 10 | router.on(visibility, function () { 11 | app.visibility = visibility; 12 | }); 13 | }); 14 | 15 | router.configure({ 16 | notfound: function () { 17 | window.location.hash = ''; 18 | app.visibility = 'all'; 19 | } 20 | }); 21 | 22 | router.init(); 23 | 24 | })(app, Router); 25 | -------------------------------------------------------------------------------- /examples/2.4.1/todo/js/routes.js: -------------------------------------------------------------------------------- 1 | /*global app, Router */ 2 | 3 | (function (app, Router) { 4 | 5 | 'use strict'; 6 | 7 | var router = new Router(); 8 | 9 | ['all', 'active', 'completed'].forEach(function (visibility) { 10 | router.on(visibility, function () { 11 | app.visibility = visibility; 12 | }); 13 | }); 14 | 15 | router.configure({ 16 | notfound: function () { 17 | window.location.hash = ''; 18 | app.visibility = 'all'; 19 | } 20 | }); 21 | 22 | router.init(); 23 | 24 | })(app, Router); 25 | -------------------------------------------------------------------------------- /examples/2.4.2/todo/js/routes.js: -------------------------------------------------------------------------------- 1 | /*global app, Router */ 2 | 3 | (function (app, Router) { 4 | 5 | 'use strict'; 6 | 7 | var router = new Router(); 8 | 9 | ['all', 'active', 'completed'].forEach(function (visibility) { 10 | router.on(visibility, function () { 11 | app.visibility = visibility; 12 | }); 13 | }); 14 | 15 | router.configure({ 16 | notfound: function () { 17 | window.location.hash = ''; 18 | app.visibility = 'all'; 19 | } 20 | }); 21 | 22 | router.init(); 23 | 24 | })(app, Router); 25 | -------------------------------------------------------------------------------- /examples/2.5/todo/js/routes.js: -------------------------------------------------------------------------------- 1 | /*global app, Router */ 2 | 3 | (function (app, Router) { 4 | 5 | 'use strict'; 6 | 7 | var router = new Router(); 8 | 9 | ['all', 'active', 'completed'].forEach(function (visibility) { 10 | router.on(visibility, function () { 11 | app.visibility = visibility; 12 | }); 13 | }); 14 | 15 | router.configure({ 16 | notfound: function () { 17 | window.location.hash = ''; 18 | app.visibility = 'all'; 19 | } 20 | }); 21 | 22 | router.init(); 23 | 24 | })(app, Router); 25 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | 3 | module.exports = { 4 | entry: './src/entries/index.js', 5 | output: { 6 | filename: './dist/v2.5.js' 7 | }, 8 | module: { 9 | rules: [ 10 | { 11 | test: /\.js$/, 12 | use: [{ 13 | loader: 'babel-loader', 14 | options:{ 15 | "presets": ["es2015"] 16 | } 17 | }], 18 | include: __dirname 19 | }] 20 | }, 21 | resolve: { 22 | modules: [ 23 | path.resolve('./src') 24 | ] 25 | } 26 | } -------------------------------------------------------------------------------- /src/core/instance/init.js: -------------------------------------------------------------------------------- 1 | import { initState } from './state' 2 | 3 | let uid = 0 4 | 5 | export function initMixin (Vue) { 6 | Vue.prototype._init = function (options) { 7 | const vm = this 8 | const template = options.template 9 | 10 | vm._uid = uid++ 11 | 12 | // a flag to avoid this being observed 13 | // 避免 vm对象 被注入订阅 14 | vm._isVue = true 15 | 16 | vm.$options = options 17 | 18 | initState(vm) 19 | 20 | if (vm.$options.el) { 21 | vm.$mount(vm.$options.el) 22 | } 23 | } 24 | 25 | } -------------------------------------------------------------------------------- /src/core/instance/render.js: -------------------------------------------------------------------------------- 1 | import { _toString } from '../util/index' 2 | import { createTextVNode, createElementVNode, createEmptyVNode } from '../vdom/vnode' 3 | 4 | import { renderList } from './render-helpers/render-list' 5 | import { checkKeyCodes } from './render-helpers/check-keycodes' 6 | 7 | export function renderMixin (Vue) { 8 | Vue.prototype._c = createElementVNode 9 | Vue.prototype._v = createTextVNode 10 | Vue.prototype._s = _toString 11 | Vue.prototype._l = renderList 12 | Vue.prototype._k = checkKeyCodes 13 | Vue.prototype._e = createEmptyVNode 14 | } -------------------------------------------------------------------------------- /src/compiler/index.js: -------------------------------------------------------------------------------- 1 | import { parse } from './parser/index' 2 | import { warn } from 'core/util/debug' 3 | import { noop } from 'shared/util' 4 | import { generate } from './codegen/index' 5 | 6 | function makeFunction (code, errors) { 7 | try { 8 | return new Function(code) 9 | } catch (err) { 10 | errors.push({ err, code }) 11 | return noop 12 | } 13 | } 14 | 15 | export default function compile (template) { 16 | const ast = parse(template.trim()) 17 | const code = generate(ast) 18 | return { 19 | ast, 20 | render: makeFunction(code.render) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /examples/2.2.2/todo/js/app.js: -------------------------------------------------------------------------------- 1 | /*global Vue, todoStorage */ 2 | 3 | (function (exports) { 4 | 5 | 'use strict'; 6 | 7 | todoStorage.save([ 8 | { "title": "item 1", "completed": true }, 9 | { "title": "item 2", "completed": false }, 10 | { "title": "item 3", "completed": true }, 11 | { "title": "item 4", "completed": false } 12 | ]) 13 | 14 | var vm = exports.app = new Vue({ 15 | 16 | template: document.getElementById("tmpl").innerHTML, 17 | 18 | // app initial state 19 | data: { 20 | todos: todoStorage.fetch(), 21 | newTodo: '', 22 | editedTodo: null, 23 | visibility: 'all' 24 | }, 25 | }); 26 | 27 | vm.$mount("todoapp") 28 | 29 | })(window); 30 | -------------------------------------------------------------------------------- /src/core/util/env.js: -------------------------------------------------------------------------------- 1 | // can we use __proto__? 2 | export const hasProto = '__proto__' in {} 3 | 4 | /* istanbul ignore next */ 5 | export function isNative (Ctor) { 6 | return /native code/.test(Ctor.toString()) 7 | } 8 | 9 | let _Set 10 | if (typeof Set !== 'undefined' && isNative(Set)) { 11 | _Set = Set 12 | } else { 13 | // Set polyfill 14 | // 搞个简单的Set polyfill 15 | _Set = class Set { 16 | constructor () { 17 | this.set = Object.create(null) 18 | } 19 | has (key) { 20 | return this.set[key] === true 21 | } 22 | add (key) { 23 | this.set[key] = true 24 | } 25 | clear () { 26 | this.set = Object.create(null) 27 | } 28 | } 29 | } 30 | 31 | export { _Set } -------------------------------------------------------------------------------- /src/core/instance/old.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | import patch from 'core/vdom/patch' 4 | import compile from 'compiler/index' 5 | import generate from 'compiler/codegen/index' 6 | 7 | import { _toString } from '../util/index' 8 | import { createTextVNode, createElementVNode, createEmptyVNode, renderList } from '../vdom/vnode' 9 | import { 10 | set, 11 | del, 12 | observe 13 | } from '../observer/index' 14 | import Watcher from '../observer/watcher' 15 | 16 | import { 17 | warn, 18 | hasOwn, 19 | isReserved, 20 | isPlainObject, 21 | bind, 22 | noop 23 | } from '../util/index' 24 | 25 | /* 26 | // 废弃 27 | Vue.prototype.setData = function (data) { 28 | this._initData(data) 29 | this._update() 30 | } 31 | */ 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ow-to-learn-vue2", 3 | "version": "1.0.0", 4 | "description": "how to learn vue2 by raphealguo", 5 | "main": "dist/vue.js", 6 | "homepage": "https://github.com/raphealguo/how-to-learn-vue2#readme", 7 | "keywords": [ 8 | "Vue", 9 | "Vue2.0", 10 | "how to learn vue" 11 | ], 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/raphealguo/how-to-learn-vue2.git" 15 | }, 16 | "author": "raphealguo", 17 | "license": "MIT", 18 | "bugs": { 19 | "url": "https://github.com/raphealguo/how-to-learn-vue2/issues" 20 | }, 21 | "devDependencies": { 22 | "babel-core": "^6.25.0", 23 | "babel-loader": "^7.1.1", 24 | "babel-preset-es2015": "^6.24.1", 25 | "webpack": "^2.0.0" 26 | }, 27 | "dependencies": {}, 28 | "scripts": { 29 | "line": "node ./tools/line.js ./src" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/core/util/lang.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Check if a string starts with $ or _ 3 | */ 4 | export function isReserved (str) { 5 | // '$' (charCode) 0x24 6 | // '_' (charCode) 0x5F 7 | const c = (str + '').charCodeAt(0) 8 | return c === 0x24 || c === 0x5F 9 | } 10 | 11 | /** 12 | * Define a property. 13 | */ 14 | export function def (obj, key, val, enumerable) { 15 | Object.defineProperty(obj, key, { 16 | value: val, 17 | enumerable: !!enumerable, 18 | writable: true, 19 | configurable: true 20 | }) 21 | } 22 | 23 | /** 24 | * Parse simple path. 25 | */ 26 | const bailRE = /[^\w.$]/ 27 | export function parsePath (path) { 28 | if (bailRE.test(path)) { 29 | return 30 | } else { 31 | const segments = path.split('.') 32 | return function (obj) { 33 | for (let i = 0; i < segments.length; i++) { 34 | if (!obj) return 35 | obj = obj[segments[i]] 36 | } 37 | return obj 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /tools/line.js: -------------------------------------------------------------------------------- 1 | var fs = require("fs") 2 | var path = require("path") 3 | var args = process.argv.slice(2); 4 | var readDir = args[0] 5 | 6 | function readDirSync(path) { 7 | var line = 0 8 | fs.readdirSync(path).forEach(function(ele, index){ 9 | var filePath = path + "/" + ele 10 | var info = fs.statSync(filePath) 11 | if (info.isDirectory()) { 12 | line += readDirSync(filePath) 13 | } else { 14 | // console.log("file: " + filePath) 15 | line += getLineByFilepath(filePath) 16 | } 17 | }) 18 | 19 | return line 20 | } 21 | 22 | function getLineByFilepath(filePath) { 23 | var data = fs.readFileSync(filePath, "utf-8"); 24 | var match = data.match(/\n/g) 25 | var line = match && match.length 26 | return line ? line + 1 : 0 27 | } 28 | 29 | if (!!readDir) { 30 | var line = readDirSync(readDir) 31 | console.log("total line: " + line) 32 | } else { 33 | console.log("node line.js ") 34 | } -------------------------------------------------------------------------------- /src/compiler/parser/text-parser.js: -------------------------------------------------------------------------------- 1 | const defaultTagRE = /\{\{((?:.|\n)+?)\}\}/g 2 | 3 | export function parseText (text) { // text = "abc{{a}}xxx{{b}}def" -> tokens = ["abc", _s(a)", "xx", "_s(b)", "def"] 4 | const tagRE = defaultTagRE 5 | if (!tagRE.test(text)) { 6 | return 7 | } 8 | const tokens = [] 9 | let lastIndex = tagRE.lastIndex = 0 10 | let match, index 11 | while ((match = tagRE.exec(text))) { 12 | index = match.index 13 | // push text token 14 | // push("abc") push("xxx") 15 | if (index > lastIndex) { 16 | tokens.push(JSON.stringify(text.slice(lastIndex, index))) 17 | } 18 | // tag token 19 | // push("_s(a)") push("_s(b)") 20 | const exp = match[1].trim() 21 | tokens.push(`_s(${exp})`) 22 | lastIndex = index + match[0].length 23 | } 24 | if (lastIndex < text.length) { // push("def") 25 | tokens.push(JSON.stringify(text.slice(lastIndex))) 26 | } 27 | return tokens.join('+') 28 | } 29 | -------------------------------------------------------------------------------- /src/core/vdom/vnode.js: -------------------------------------------------------------------------------- 1 | import { simpleNormalizeChildren } from './helpers/index' 2 | 3 | export default class VNode { 4 | constructor ( 5 | tag, // 标签名 6 | data, // data = { attrs: 属性key-val } 7 | children, // 孩子 [VNode, VNode] 8 | text, // 文本节点 9 | elm // 对应的真实dom对象 10 | ) { 11 | this.tag = tag 12 | this.data = data 13 | this.children = children 14 | this.text = text 15 | this.elm = elm 16 | this.key = data && data.key 17 | } 18 | } 19 | 20 | export function createElementVNode(tag, data, children) { 21 | if (!tag) { 22 | return createEmptyVNode() 23 | } 24 | 25 | return new VNode(tag, data, simpleNormalizeChildren(children), undefined, undefined) 26 | } 27 | 28 | export const createEmptyVNode = () => { 29 | const node = new VNode() 30 | node.text = '' 31 | return node 32 | } 33 | 34 | export function createTextVNode (val) { 35 | return new VNode(undefined, undefined, undefined, String(val)) 36 | } 37 | -------------------------------------------------------------------------------- /src/core/instance/render-helpers/render-list.js: -------------------------------------------------------------------------------- 1 | import { isObject } from 'core/util/index' 2 | // v-for="(item, index) in list" 3 | // alias = item, iterator1 = index 4 | 5 | // v-for="(value, key, index) in object" 6 | // alias = value, iterator1 = key, iterator2 = index 7 | 8 | // val = list 9 | // render = function (alias, iterator1, iterator2) { return VNode } 10 | export function renderList (val, render) { 11 | let ret, i, l, keys, key 12 | if (Array.isArray(val) || typeof val === 'string') { 13 | ret = new Array(val.length) 14 | for (i = 0, l = val.length; i < l; i++) { 15 | ret[i] = render(val[i], i) 16 | } 17 | } else if (typeof val === 'number') { // 支持 v-for="n in 10" 18 | ret = new Array(val) 19 | for (i = 0; i < val; i++) { 20 | ret[i] = render(i + 1, i) 21 | } 22 | } else if (isObject(val)) { 23 | keys = Object.keys(val) 24 | ret = new Array(keys.length) 25 | for (i = 0, l = keys.length; i < l; i++) { 26 | key = keys[i] 27 | ret[i] = render(val[key], key, i) 28 | } 29 | } 30 | return ret 31 | } 32 | -------------------------------------------------------------------------------- /src/core/vdom/node-ops.js: -------------------------------------------------------------------------------- 1 | // 真实的dom操作 2 | 3 | export function createElement (tagName) { 4 | return document.createElement(tagName) 5 | } 6 | 7 | export function createTextNode (text) { 8 | return document.createTextNode(text) 9 | } 10 | 11 | export function createComment (text) { 12 | return document.createComment(text) 13 | } 14 | 15 | export function insertBefore (parentNode, newNode, referenceNode) { 16 | parentNode.insertBefore(newNode, referenceNode) 17 | } 18 | 19 | export function removeChild (node, child) { 20 | node.removeChild(child) 21 | } 22 | 23 | export function appendChild (node, child) { 24 | node.appendChild(child) 25 | } 26 | 27 | export function parentNode (node) { 28 | return node.parentNode 29 | } 30 | 31 | export function nextSibling (node) { 32 | return node.nextSibling 33 | } 34 | 35 | export function tagName (node) { 36 | return node.tagName 37 | } 38 | 39 | export function setTextContent (node, text) { 40 | node.textContent = text 41 | } 42 | 43 | export function setAttribute (node, key, val) { 44 | node.setAttribute(key, val) 45 | } 46 | -------------------------------------------------------------------------------- /examples/2.4.1/examples.css: -------------------------------------------------------------------------------- 1 | * { 2 | margin: 0; 3 | padding: 0; 4 | } 5 | 6 | button { 7 | margin: 0; 8 | padding: 5px; 9 | font-size: 100%; 10 | vertical-align: baseline; 11 | font-family: inherit; 12 | font-weight: inherit; 13 | color: inherit; 14 | -webkit-appearance: none; 15 | appearance: none; 16 | -webkit-font-smoothing: antialiased; 17 | -moz-font-smoothing: antialiased; 18 | font-smoothing: antialiased; 19 | } 20 | 21 | body { 22 | font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif; 23 | line-height: 1.4em; 24 | background: #f5f5f5; 25 | color: #4d4d4d; 26 | -webkit-font-smoothing: antialiased; 27 | -moz-font-smoothing: antialiased; 28 | font-smoothing: antialiased; 29 | font-weight: 300; 30 | } 31 | 32 | .vue-test-container { 33 | min-width: 230px; 34 | max-width: 550px; 35 | margin: 0 auto; 36 | font-size: 21px; 37 | line-height: 32px; 38 | } 39 | 40 | .header { 41 | text-align: center; 42 | padding: 15px 0; 43 | } 44 | .footer { 45 | margin-top: 5px; 46 | padding-top: 5px; 47 | border-top: 1px solid #DDD; 48 | text-align: center; 49 | color: #AAA; 50 | font-size: 14px; 51 | line-height: 21px; 52 | } 53 | .footer a{ 54 | color: #999; 55 | } -------------------------------------------------------------------------------- /examples/2.4.2/examples.css: -------------------------------------------------------------------------------- 1 | * { 2 | margin: 0; 3 | padding: 0; 4 | } 5 | 6 | button { 7 | margin: 0; 8 | padding: 5px; 9 | font-size: 100%; 10 | vertical-align: baseline; 11 | font-family: inherit; 12 | font-weight: inherit; 13 | color: inherit; 14 | -webkit-appearance: none; 15 | appearance: none; 16 | -webkit-font-smoothing: antialiased; 17 | -moz-font-smoothing: antialiased; 18 | font-smoothing: antialiased; 19 | } 20 | 21 | body { 22 | font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif; 23 | line-height: 1.4em; 24 | background: #f5f5f5; 25 | color: #4d4d4d; 26 | -webkit-font-smoothing: antialiased; 27 | -moz-font-smoothing: antialiased; 28 | font-smoothing: antialiased; 29 | font-weight: 300; 30 | } 31 | 32 | .vue-test-container { 33 | min-width: 230px; 34 | max-width: 550px; 35 | margin: 0 auto; 36 | font-size: 21px; 37 | line-height: 32px; 38 | } 39 | 40 | .header { 41 | text-align: center; 42 | padding: 15px 0; 43 | } 44 | .footer { 45 | margin-top: 5px; 46 | padding-top: 5px; 47 | border-top: 1px solid #DDD; 48 | text-align: center; 49 | color: #AAA; 50 | font-size: 14px; 51 | line-height: 21px; 52 | } 53 | .footer a{ 54 | color: #999; 55 | } -------------------------------------------------------------------------------- /examples/1.1/examples.css: -------------------------------------------------------------------------------- 1 | * { 2 | margin: 0; 3 | padding: 0; 4 | } 5 | 6 | button { 7 | margin: 0; 8 | padding: 0; 9 | border: 0; 10 | background: none; 11 | font-size: 100%; 12 | vertical-align: baseline; 13 | font-family: inherit; 14 | font-weight: inherit; 15 | color: inherit; 16 | -webkit-appearance: none; 17 | appearance: none; 18 | -webkit-font-smoothing: antialiased; 19 | -moz-font-smoothing: antialiased; 20 | font-smoothing: antialiased; 21 | } 22 | 23 | body { 24 | font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif; 25 | line-height: 1.4em; 26 | background: #f5f5f5; 27 | color: #4d4d4d; 28 | -webkit-font-smoothing: antialiased; 29 | -moz-font-smoothing: antialiased; 30 | font-smoothing: antialiased; 31 | font-weight: 300; 32 | } 33 | 34 | .vue-test-container { 35 | min-width: 230px; 36 | max-width: 550px; 37 | margin: 0 auto; 38 | font-size: 21px; 39 | line-height: 32px; 40 | } 41 | 42 | .header { 43 | text-align: center; 44 | padding: 15px 0; 45 | } 46 | .footer { 47 | margin-top: 5px; 48 | padding-top: 5px; 49 | border-top: 1px solid #DDD; 50 | text-align: center; 51 | color: #AAA; 52 | font-size: 14px; 53 | line-height: 21px; 54 | } 55 | .footer a{ 56 | color: #999; 57 | } -------------------------------------------------------------------------------- /examples/1.2/examples.css: -------------------------------------------------------------------------------- 1 | * { 2 | margin: 0; 3 | padding: 0; 4 | } 5 | 6 | button { 7 | margin: 0; 8 | padding: 0; 9 | border: 0; 10 | background: none; 11 | font-size: 100%; 12 | vertical-align: baseline; 13 | font-family: inherit; 14 | font-weight: inherit; 15 | color: inherit; 16 | -webkit-appearance: none; 17 | appearance: none; 18 | -webkit-font-smoothing: antialiased; 19 | -moz-font-smoothing: antialiased; 20 | font-smoothing: antialiased; 21 | } 22 | 23 | body { 24 | font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif; 25 | line-height: 1.4em; 26 | background: #f5f5f5; 27 | color: #4d4d4d; 28 | -webkit-font-smoothing: antialiased; 29 | -moz-font-smoothing: antialiased; 30 | font-smoothing: antialiased; 31 | font-weight: 300; 32 | } 33 | 34 | .vue-test-container { 35 | min-width: 230px; 36 | max-width: 550px; 37 | margin: 0 auto; 38 | font-size: 21px; 39 | line-height: 32px; 40 | } 41 | 42 | .header { 43 | text-align: center; 44 | padding: 15px 0; 45 | } 46 | .footer { 47 | margin-top: 5px; 48 | padding-top: 5px; 49 | border-top: 1px solid #DDD; 50 | text-align: center; 51 | color: #AAA; 52 | font-size: 14px; 53 | line-height: 21px; 54 | } 55 | .footer a{ 56 | color: #999; 57 | } -------------------------------------------------------------------------------- /examples/1.3/examples.css: -------------------------------------------------------------------------------- 1 | * { 2 | margin: 0; 3 | padding: 0; 4 | } 5 | 6 | button { 7 | margin: 0; 8 | padding: 0; 9 | border: 0; 10 | background: none; 11 | font-size: 100%; 12 | vertical-align: baseline; 13 | font-family: inherit; 14 | font-weight: inherit; 15 | color: inherit; 16 | -webkit-appearance: none; 17 | appearance: none; 18 | -webkit-font-smoothing: antialiased; 19 | -moz-font-smoothing: antialiased; 20 | font-smoothing: antialiased; 21 | } 22 | 23 | body { 24 | font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif; 25 | line-height: 1.4em; 26 | background: #f5f5f5; 27 | color: #4d4d4d; 28 | -webkit-font-smoothing: antialiased; 29 | -moz-font-smoothing: antialiased; 30 | font-smoothing: antialiased; 31 | font-weight: 300; 32 | } 33 | 34 | .vue-test-container { 35 | min-width: 230px; 36 | max-width: 550px; 37 | margin: 0 auto; 38 | font-size: 21px; 39 | line-height: 32px; 40 | } 41 | 42 | .header { 43 | text-align: center; 44 | padding: 15px 0; 45 | } 46 | .footer { 47 | margin-top: 5px; 48 | padding-top: 5px; 49 | border-top: 1px solid #DDD; 50 | text-align: center; 51 | color: #AAA; 52 | font-size: 14px; 53 | line-height: 21px; 54 | } 55 | .footer a{ 56 | color: #999; 57 | } -------------------------------------------------------------------------------- /examples/2.2.1/examples.css: -------------------------------------------------------------------------------- 1 | * { 2 | margin: 0; 3 | padding: 0; 4 | } 5 | 6 | button { 7 | margin: 0; 8 | padding: 0; 9 | border: 0; 10 | background: none; 11 | font-size: 100%; 12 | vertical-align: baseline; 13 | font-family: inherit; 14 | font-weight: inherit; 15 | color: inherit; 16 | -webkit-appearance: none; 17 | appearance: none; 18 | -webkit-font-smoothing: antialiased; 19 | -moz-font-smoothing: antialiased; 20 | font-smoothing: antialiased; 21 | } 22 | 23 | body { 24 | font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif; 25 | line-height: 1.4em; 26 | background: #f5f5f5; 27 | color: #4d4d4d; 28 | -webkit-font-smoothing: antialiased; 29 | -moz-font-smoothing: antialiased; 30 | font-smoothing: antialiased; 31 | font-weight: 300; 32 | } 33 | 34 | .vue-test-container { 35 | min-width: 230px; 36 | max-width: 550px; 37 | margin: 0 auto; 38 | font-size: 21px; 39 | line-height: 32px; 40 | } 41 | 42 | .header { 43 | text-align: center; 44 | padding: 15px 0; 45 | } 46 | .footer { 47 | margin-top: 5px; 48 | padding-top: 5px; 49 | border-top: 1px solid #DDD; 50 | text-align: center; 51 | color: #AAA; 52 | font-size: 14px; 53 | line-height: 21px; 54 | } 55 | .footer a{ 56 | color: #999; 57 | } -------------------------------------------------------------------------------- /examples/2.2.2.1/examples.css: -------------------------------------------------------------------------------- 1 | * { 2 | margin: 0; 3 | padding: 0; 4 | } 5 | 6 | button { 7 | margin: 0; 8 | padding: 0; 9 | border: 0; 10 | background: none; 11 | font-size: 100%; 12 | vertical-align: baseline; 13 | font-family: inherit; 14 | font-weight: inherit; 15 | color: inherit; 16 | -webkit-appearance: none; 17 | appearance: none; 18 | -webkit-font-smoothing: antialiased; 19 | -moz-font-smoothing: antialiased; 20 | font-smoothing: antialiased; 21 | } 22 | 23 | body { 24 | font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif; 25 | line-height: 1.4em; 26 | background: #f5f5f5; 27 | color: #4d4d4d; 28 | -webkit-font-smoothing: antialiased; 29 | -moz-font-smoothing: antialiased; 30 | font-smoothing: antialiased; 31 | font-weight: 300; 32 | } 33 | 34 | .vue-test-container { 35 | min-width: 230px; 36 | max-width: 550px; 37 | margin: 0 auto; 38 | font-size: 21px; 39 | line-height: 32px; 40 | } 41 | 42 | .header { 43 | text-align: center; 44 | padding: 15px 0; 45 | } 46 | .footer { 47 | margin-top: 5px; 48 | padding-top: 5px; 49 | border-top: 1px solid #DDD; 50 | text-align: center; 51 | color: #AAA; 52 | font-size: 14px; 53 | line-height: 21px; 54 | } 55 | .footer a{ 56 | color: #999; 57 | } -------------------------------------------------------------------------------- /examples/2.3.1/todo/js/app.js: -------------------------------------------------------------------------------- 1 | /*global Vue, todoStorage */ 2 | 3 | (function (exports) { 4 | 5 | 'use strict'; 6 | 7 | todoStorage.save([ 8 | { "title": "item 1", "completed": true }, 9 | { "title": "item 2", "completed": false }, 10 | { "title": "item 3", "completed": true }, 11 | { "title": "item 4", "completed": false } 12 | ]) 13 | 14 | var filters = { 15 | all: function (todos) { 16 | return todos; 17 | }, 18 | active: function (todos) { 19 | return todos.filter(function (todo) { 20 | return !todo.completed; 21 | }); 22 | }, 23 | completed: function (todos) { 24 | return todos.filter(function (todo) { 25 | return todo.completed; 26 | }); 27 | } 28 | }; 29 | 30 | var vm = exports.app = new Vue({ 31 | 32 | template: document.getElementById("tmpl").innerHTML, 33 | 34 | // app initial state 35 | data: { 36 | todos: todoStorage.fetch(), 37 | newTodo: '', 38 | editedTodo: null, 39 | visibility: 'all' 40 | }, 41 | computed: { 42 | filteredTodos: function () { 43 | return filters[this.visibility](this.todos); 44 | }, 45 | remaining: function () { 46 | return filters.active(this.todos).length; 47 | } 48 | }, 49 | }); 50 | 51 | vm.$mount("todoapp") 52 | 53 | })(window); 54 | -------------------------------------------------------------------------------- /examples/2.3.2/todo/js/app.js: -------------------------------------------------------------------------------- 1 | /*global Vue, todoStorage */ 2 | 3 | (function (exports) { 4 | 5 | 'use strict'; 6 | 7 | todoStorage.save([ 8 | { "title": "item 1", "completed": true }, 9 | { "title": "item 2", "completed": false }, 10 | { "title": "item 3", "completed": true }, 11 | { "title": "item 4", "completed": false } 12 | ]) 13 | 14 | var filters = { 15 | all: function (todos) { 16 | return todos; 17 | }, 18 | active: function (todos) { 19 | return todos.filter(function (todo) { 20 | return !todo.completed; 21 | }); 22 | }, 23 | completed: function (todos) { 24 | return todos.filter(function (todo) { 25 | return todo.completed; 26 | }); 27 | } 28 | }; 29 | 30 | var vm = exports.app = new Vue({ 31 | 32 | template: document.getElementById("tmpl").innerHTML, 33 | 34 | // app initial state 35 | data: { 36 | todos: todoStorage.fetch(), 37 | newTodo: '', 38 | editedTodo: null, 39 | visibility: 'all' 40 | }, 41 | computed: { 42 | filteredTodos: function () { 43 | return filters[this.visibility](this.todos); 44 | }, 45 | remaining: function () { 46 | return filters.active(this.todos).length; 47 | } 48 | }, 49 | }); 50 | 51 | vm.$mount("todoapp") 52 | 53 | })(window); 54 | -------------------------------------------------------------------------------- /src/core/vdom/attrs.js: -------------------------------------------------------------------------------- 1 | import { makeMap } from 'shared/util' 2 | 3 | export function updateAttrs (oldVnode, vnode) { 4 | if (!oldVnode.data.attrs && !vnode.data.attrs) { 5 | return 6 | } 7 | let key, cur, old 8 | const elm = vnode.elm 9 | const oldAttrs = oldVnode.data.attrs || {} 10 | let attrs= vnode.data.attrs || {} 11 | 12 | for (key in attrs) { 13 | cur = attrs[key] 14 | old = oldAttrs[key] 15 | if (old !== cur) { 16 | setAttr(elm, key, cur) 17 | } 18 | } 19 | 20 | for (key in oldAttrs) { 21 | if (attrs[key] == null) { 22 | elm.removeAttribute(key) 23 | } 24 | } 25 | } 26 | 27 | 28 | // attributes that should be using props for binding 29 | // 需要用props来绑定的属性 30 | const acceptValue = makeMap('input,textarea,option,select') 31 | export const mustUseProp = (tag, type, attr) => { 32 | return ( 33 | (attr === 'value' && acceptValue(tag)) && type !== 'button' || 34 | (attr === 'selected' && tag === 'option') || 35 | (attr === 'checked' && tag === 'input') || 36 | (attr === 'muted' && tag === 'video') 37 | ) 38 | } 39 | 40 | export const isFalsyAttrValue = (val) => { 41 | return val == null || val === false 42 | } 43 | 44 | function setAttr (el, key, value) { 45 | if (isFalsyAttrValue(value)) { 46 | el.removeAttribute(key) 47 | } else { 48 | el.setAttribute(key, value) 49 | } 50 | } -------------------------------------------------------------------------------- /src/core/observer/array.js: -------------------------------------------------------------------------------- 1 | /* 2 | * not type checking this file because flow doesn't play well with 3 | * dynamically accessing methods on Array prototype 4 | */ 5 | 6 | import { def } from '../util/index' 7 | 8 | const arrayProto = Array.prototype 9 | export const arrayMethods = Object.create(arrayProto) 10 | 11 | /** 12 | * Intercept mutating methods and emit events 13 | */ 14 | ;[ 15 | 'push', 16 | 'pop', 17 | 'shift', 18 | 'unshift', 19 | 'splice', 20 | 'sort', 21 | 'reverse' 22 | ] 23 | .forEach(function (method) { 24 | // cache original method 25 | const original = arrayProto[method] 26 | def(arrayMethods, method, function mutator () { 27 | // avoid leaking arguments: 28 | // http://jsperf.com/closure-with-arguments 29 | let i = arguments.length 30 | const args = new Array(i) 31 | while (i--) { 32 | args[i] = arguments[i] 33 | } 34 | const result = original.apply(this, args) // 调用原方法 35 | const ob = this.__ob__ 36 | let inserted 37 | switch (method) { 38 | case 'push': 39 | inserted = args 40 | break 41 | case 'unshift': 42 | inserted = args 43 | break 44 | case 'splice': 45 | inserted = args.slice(2) 46 | break 47 | } 48 | if (inserted) ob.observeArray(inserted) 49 | // notify change 50 | // set操作要 notify 51 | ob.dep.notify() 52 | return result 53 | }) 54 | }) 55 | -------------------------------------------------------------------------------- /examples/2.4.1/examples1.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | how to learn vue2 (by raphealguo) 6 | 7 | 8 | 9 |
10 |

测试event

11 |
12 |
13 |
14 |
15 | 18 | 19 | Fork me on GitHub 23 | 24 | 25 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /examples/1.3/examples1.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | how to learn vue2 (by raphealguo) 6 | 7 | 8 | 9 |
10 |

ul渲染

11 |
12 |
13 |
14 |
15 | 18 | 19 | Fork me on GitHub 23 | 24 | 25 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /examples/2.2.2.1/examples1.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | how to learn vue2 (by raphealguo) 6 | 7 | 8 | 9 |
10 |

测试v-if v-else-if v-else 渲染

11 |
12 |
13 |
14 |
15 | 18 | 19 | Fork me on GitHub 23 | 24 | 25 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /examples/1.2/examples1.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | how to learn vue2 (by raphealguo) 6 | 7 | 8 | 9 |
10 |

ul渲染

11 |
12 |
13 |
14 |
15 | 18 | 19 | Fork me on GitHub 23 | 24 | 25 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /examples/2.2.1/examples1.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | how to learn vue2 (by raphealguo) 6 | 7 | 8 | 9 |
10 |

测试v-if v-else-if v-else 渲染

11 |
12 |
13 |
14 |
15 | 18 | 19 | Fork me on GitHub 23 | 24 | 25 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /examples/2.4.1/todo/js/app.js: -------------------------------------------------------------------------------- 1 | /*global Vue, todoStorage */ 2 | 3 | (function (exports) { 4 | 5 | 'use strict'; 6 | 7 | var filters = { 8 | all: function (todos) { 9 | return todos; 10 | }, 11 | active: function (todos) { 12 | return todos.filter(function (todo) { 13 | return !todo.completed; 14 | }); 15 | }, 16 | completed: function (todos) { 17 | return todos.filter(function (todo) { 18 | return todo.completed; 19 | }); 20 | } 21 | }; 22 | 23 | var vm = exports.app = new Vue({ 24 | 25 | template: document.getElementById("tmpl").innerHTML, 26 | 27 | // app initial state 28 | data: { 29 | todos: todoStorage.fetch(), 30 | newTodo: '', 31 | editedTodo: null, 32 | visibility: 'all' 33 | }, 34 | computed: { 35 | filteredTodos: function () { 36 | return filters[this.visibility](this.todos); 37 | }, 38 | remaining: function () { 39 | return filters.active(this.todos).length; 40 | } 41 | }, 42 | methods: { 43 | inputTodo: function($event){ 44 | this.newTodo = $event.target.value; 45 | }, 46 | addTodo: function ($event) { 47 | if ($event.which !== 13) { return } 48 | var value = this.newTodo && this.newTodo.trim(); 49 | if (!value) { 50 | return; 51 | } 52 | this.todos.push({ title: value, completed: false }); 53 | this.newTodo = ''; 54 | }, 55 | removeCompleted: function () { 56 | this.todos = filters.active(this.todos); 57 | } 58 | } 59 | }); 60 | 61 | vm.$mount("todoapp") 62 | 63 | vm.$watch("todos", function(todos){ 64 | todoStorage.save(todos); 65 | }, { 66 | deep: true 67 | }) 68 | })(window); 69 | -------------------------------------------------------------------------------- /examples/1.3/examples2.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | how to learn vue2 by raphealguo 6 | 7 | 8 | 9 |
10 |

通过 setData 改变数据

11 |
12 |
13 |
14 |
15 | 18 | 19 | Fork me on GitHub 23 | 24 | 25 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /examples/1.2/examples2.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | how to learn vue2 by raphealguo 6 | 7 | 8 | 9 |
10 |

每一秒数字加1

11 |
12 |
13 |
14 |
15 | 18 | 19 | Fork me on GitHub 23 | 24 | 25 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /examples/1.2/examples3.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | how to learn vue2 by raphealguo 6 | 7 | 8 | 9 |
10 |

每隔一秒插入一行

11 |
12 |
13 |
14 |
15 | 18 | 19 | Fork me on GitHub 23 | 24 | 25 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /examples/2.1/todo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Vue.js • TodoMVC 6 | 7 | 8 | 9 | 10 |
11 |
12 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /examples/2.2.1/todo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Vue.js • TodoMVC 6 | 7 | 8 | 9 | 10 |
11 |
12 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /examples/1.1/examples1.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | how to learn vue2 (by raphealguo) 6 | 7 | 8 | 9 |
10 |

ul渲染

11 |
12 |
13 |
14 |
15 | 18 | 19 | Fork me on GitHub 23 | 24 | 25 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /src/core/observer/dep.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import Watcher from './watcher' 4 | import { remove } from '../util/index' 5 | 6 | let uid = 0 7 | 8 | /** 9 | * A dep is an observable that can have multiple 10 | * directives subscribing to it. 11 | */ 12 | /* 13 | 14 | computed : { 15 | m: function(){ 16 | return this.a + this.b 17 | }, 18 | n: function(){ 19 | return this.a + this.c 20 | }, 21 | x: function(){ 22 | return this.a + this.b + this.c 23 | } 24 | } 25 | 26 | DepA.subs = [WatcherM, WatcherN, WatcherX] 27 | DepB.subs = [WatcherM, WatcherX] 28 | DepC.subs = [WatcherN, WatcherX] 29 | 30 | WatcherM.deps = [DepA, DepB] 31 | WatcherN.deps = [DepA, DepC] 32 | WatcherX.deps = [DepA, DepB, DepC] 33 | 34 | 当getA发生的时候,需要通过 depend 添加WatcherM/WatcherN/WatcherX的依赖deps, WatcherN.subs.push() 35 | 当setA发生的时候,需要通过 notify 广播 DepA.subs,让他们通知对应的watcher 36 | */ 37 | export default class Dep { 38 | /* 39 | static target: ?Watcher; 40 | id: number; 41 | subs: Array; 42 | */ 43 | 44 | constructor () { 45 | this.id = uid++ 46 | this.subs = [] 47 | } 48 | 49 | addSub (sub) { 50 | this.subs.push(sub) 51 | } 52 | 53 | removeSub (sub) { 54 | remove(this.subs, sub) 55 | } 56 | 57 | depend () { 58 | if (Dep.target) { 59 | Dep.target.addDep(this) 60 | } 61 | } 62 | 63 | notify () { 64 | // 广播 65 | const subs = this.subs.slice() 66 | for (let i = 0, l = subs.length; i < l; i++) { 67 | subs[i].update() 68 | } 69 | } 70 | } 71 | 72 | // the current target watcher being evaluated. 73 | // this is globally unique because there could be only one 74 | // watcher being evaluated at any time. 75 | 76 | // js是单线程,所以全局只会有一个watcher在被执行 77 | // 在收集依赖的时候只需要维护一个全局的target堆栈即可 78 | Dep.target = null 79 | const targetStack = [] 80 | 81 | export function pushTarget (_target) { 82 | if (Dep.target) targetStack.push(Dep.target) 83 | Dep.target = _target 84 | } 85 | 86 | export function popTarget () { 87 | Dep.target = targetStack.pop() 88 | } 89 | -------------------------------------------------------------------------------- /examples/2.4.2/examples1.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | how to learn vue2 (by raphealguo) 6 | 7 | 8 | 9 |
10 |

测试event

11 |
12 |
13 |
14 |
15 | 18 | 19 | Fork me on GitHub 23 | 24 | 25 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /src/core/vdom/class.js: -------------------------------------------------------------------------------- 1 | import { isObject } from 'shared/util' 2 | 3 | // 支持class表达式: 4 | /* 5 | 6 |
7 |
8 |
9 | 10 | 支持class :class同时存在 class对应vNode节点的data.staticClass :class对应vNode节点的data.class 11 | */ 12 | export function updateClass (oldVnode, vnode) { 13 | const el = vnode.elm 14 | const data = vnode.data 15 | const oldData = oldVnode.data 16 | if (!data.staticClass && !data.class && 17 | (!oldData || (!oldData.staticClass && !oldData.class))) { 18 | return 19 | } 20 | 21 | let cls = genClassForVnode(vnode) 22 | 23 | // set the class 24 | if (cls !== el._prevClass) { 25 | el.setAttribute('class', cls) 26 | el._prevClass = cls 27 | } 28 | } 29 | 30 | function genClassForVnode (vnode) { 31 | return genClassFromData(vnode.data) 32 | } 33 | 34 | function genClassFromData (data) { 35 | const dynamicClass = data.class //
36 | const staticClass = data.staticClass//
37 | if (staticClass || dynamicClass) { // merge class & :class 38 | return concat(staticClass, stringifyClass(dynamicClass)) 39 | } 40 | /* istanbul ignore next */ 41 | return '' 42 | } 43 | 44 | function concat (a, b) { 45 | return a ? b ? (a + ' ' + b) : a : (b || '') 46 | } 47 | 48 | function stringifyClass (value) { 49 | let res = '' 50 | if (!value) { 51 | return res 52 | } 53 | if (typeof value === 'string') { 54 | return value 55 | } 56 | if (Array.isArray(value)) { 57 | let stringified 58 | for (let i = 0, l = value.length; i < l; i++) { 59 | if (value[i]) { 60 | if ((stringified = stringifyClass(value[i]))) { 61 | res += stringified + ' ' 62 | } 63 | } 64 | } 65 | return res.slice(0, -1) // 去除尾巴的空格 66 | } 67 | if (isObject(value)) { 68 | for (const key in value) { 69 | if (value[key]) res += key + ' ' 70 | } 71 | return res.slice(0, -1) // 去除尾巴的空格 72 | } 73 | return res 74 | } 75 | 76 | -------------------------------------------------------------------------------- /examples/2.2.2/todo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Vue.js • TodoMVC 6 | 7 | 8 | 9 | 10 |
11 |
12 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /examples/1.1/examples2.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | how to learn vue2 by raphealguo 6 | 7 | 8 | 9 |
10 |

每一秒数字加1

11 |
12 |
13 |
14 |
15 | 18 | 19 | Fork me on GitHub 23 | 24 | 25 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /examples/1.1/examples3.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | how to learn vue2 by raphealguo 6 | 7 | 8 | 9 |
10 |

每隔一秒插入一行

11 |
12 |
13 |
14 |
15 | 18 | 19 | Fork me on GitHub 23 | 24 | 25 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /src/shared/util.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Convert a value to a string that is actually rendered. 3 | */ 4 | export function _toString (val) { 5 | return val == null 6 | ? '' 7 | : typeof val === 'object' 8 | ? JSON.stringify(val, null, 2) 9 | : String(val) 10 | } 11 | 12 | 13 | /** 14 | * Make a map and return a function for checking if a key 15 | * is in that map. 16 | */ 17 | export function makeMap (str, expectsLowerCase) { 18 | const map = Object.create(null) 19 | const list = str.split(',') 20 | for (let i = 0; i < list.length; i++) { 21 | map[list[i]] = true 22 | } 23 | return expectsLowerCase 24 | ? val => map[val.toLowerCase()] 25 | : val => map[val] 26 | } 27 | 28 | /** 29 | * Remove an item from an array 30 | */ 31 | export function remove (arr, item) { 32 | if (arr.length) { 33 | const index = arr.indexOf(item) 34 | if (index > -1) { 35 | return arr.splice(index, 1) 36 | } 37 | } 38 | } 39 | 40 | /** 41 | * Check whether the object has the property. 42 | */ 43 | const hasOwnProperty = Object.prototype.hasOwnProperty 44 | export function hasOwn (obj, key) { 45 | return hasOwnProperty.call(obj, key) 46 | } 47 | 48 | /** 49 | * Simple bind, faster than native 50 | */ 51 | export function bind (fn, ctx) { 52 | function boundFn (a) { 53 | const l = arguments.length 54 | return l 55 | ? l > 1 56 | ? fn.apply(ctx, arguments) 57 | : fn.call(ctx, a) 58 | : fn.call(ctx) 59 | } 60 | // record original fn length 61 | boundFn._length = fn.length 62 | return boundFn 63 | } 64 | 65 | /** 66 | * Quick object check - this is primarily used to tell 67 | * Objects from primitive values when we know the value 68 | * is a JSON-compliant type. 69 | */ 70 | export function isObject (obj) { 71 | return obj !== null && typeof obj === 'object' 72 | } 73 | 74 | /** 75 | * Strict object type check. Only returns true 76 | * for plain JavaScript objects. 77 | */ 78 | const toString = Object.prototype.toString 79 | const OBJECT_STRING = '[object Object]' 80 | export function isPlainObject (obj) { 81 | return toString.call(obj) === OBJECT_STRING 82 | } 83 | 84 | /** 85 | * Perform no operation. 86 | */ 87 | export function noop () {} 88 | 89 | /** 90 | * Always return false. 91 | */ 92 | export const no = () => false 93 | -------------------------------------------------------------------------------- /examples/2.3.1/todo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Vue.js • TodoMVC 6 | 7 | 8 | 9 | 10 |
11 |
12 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /examples/2.3.2/todo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Vue.js • TodoMVC 6 | 7 | 8 | 9 | 10 |
11 |
12 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /examples/2.4.1/todo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Vue.js • TodoMVC 6 | 7 | 8 | 9 | 10 |
11 |
12 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /examples/2.5/todo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Vue.js • TodoMVC 6 | 7 | 8 | 9 | 10 |
11 |
12 |

todos

13 | 14 |
15 |
16 | 17 |
    18 |
  • 19 |
    20 | 21 | 22 | 23 |
    24 | 25 |
  • 26 |
27 |
28 |
29 | 30 | {{remaining}} left 31 | 32 | 37 | 40 |
41 |
42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /examples/2.1/todo/node_modules/todomvc-common/base.css: -------------------------------------------------------------------------------- 1 | hr { 2 | margin: 20px 0; 3 | border: 0; 4 | border-top: 1px dashed #c5c5c5; 5 | border-bottom: 1px dashed #f7f7f7; 6 | } 7 | 8 | .learn a { 9 | font-weight: normal; 10 | text-decoration: none; 11 | color: #b83f45; 12 | } 13 | 14 | .learn a:hover { 15 | text-decoration: underline; 16 | color: #787e7e; 17 | } 18 | 19 | .learn h3, 20 | .learn h4, 21 | .learn h5 { 22 | margin: 10px 0; 23 | font-weight: 500; 24 | line-height: 1.2; 25 | color: #000; 26 | } 27 | 28 | .learn h3 { 29 | font-size: 24px; 30 | } 31 | 32 | .learn h4 { 33 | font-size: 18px; 34 | } 35 | 36 | .learn h5 { 37 | margin-bottom: 0; 38 | font-size: 14px; 39 | } 40 | 41 | .learn ul { 42 | padding: 0; 43 | margin: 0 0 30px 25px; 44 | } 45 | 46 | .learn li { 47 | line-height: 20px; 48 | } 49 | 50 | .learn p { 51 | font-size: 15px; 52 | font-weight: 300; 53 | line-height: 1.3; 54 | margin-top: 0; 55 | margin-bottom: 0; 56 | } 57 | 58 | #issue-count { 59 | display: none; 60 | } 61 | 62 | .quote { 63 | border: none; 64 | margin: 20px 0 60px 0; 65 | } 66 | 67 | .quote p { 68 | font-style: italic; 69 | } 70 | 71 | .quote p:before { 72 | content: '“'; 73 | font-size: 50px; 74 | opacity: .15; 75 | position: absolute; 76 | top: -20px; 77 | left: 3px; 78 | } 79 | 80 | .quote p:after { 81 | content: '”'; 82 | font-size: 50px; 83 | opacity: .15; 84 | position: absolute; 85 | bottom: -42px; 86 | right: 3px; 87 | } 88 | 89 | .quote footer { 90 | position: absolute; 91 | bottom: -40px; 92 | right: 0; 93 | } 94 | 95 | .quote footer img { 96 | border-radius: 3px; 97 | } 98 | 99 | .quote footer a { 100 | margin-left: 5px; 101 | vertical-align: middle; 102 | } 103 | 104 | .speech-bubble { 105 | position: relative; 106 | padding: 10px; 107 | background: rgba(0, 0, 0, .04); 108 | border-radius: 5px; 109 | } 110 | 111 | .speech-bubble:after { 112 | content: ''; 113 | position: absolute; 114 | top: 100%; 115 | right: 30px; 116 | border: 13px solid transparent; 117 | border-top-color: rgba(0, 0, 0, .04); 118 | } 119 | 120 | .learn-bar > .learn { 121 | position: absolute; 122 | width: 272px; 123 | top: 8px; 124 | left: -300px; 125 | padding: 10px; 126 | border-radius: 5px; 127 | background-color: rgba(255, 255, 255, .6); 128 | transition-property: left; 129 | transition-duration: 500ms; 130 | } 131 | 132 | @media (min-width: 899px) { 133 | .learn-bar { 134 | width: auto; 135 | padding-left: 300px; 136 | } 137 | 138 | .learn-bar > .learn { 139 | left: 8px; 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /examples/2.2.1/todo/node_modules/todomvc-common/base.css: -------------------------------------------------------------------------------- 1 | hr { 2 | margin: 20px 0; 3 | border: 0; 4 | border-top: 1px dashed #c5c5c5; 5 | border-bottom: 1px dashed #f7f7f7; 6 | } 7 | 8 | .learn a { 9 | font-weight: normal; 10 | text-decoration: none; 11 | color: #b83f45; 12 | } 13 | 14 | .learn a:hover { 15 | text-decoration: underline; 16 | color: #787e7e; 17 | } 18 | 19 | .learn h3, 20 | .learn h4, 21 | .learn h5 { 22 | margin: 10px 0; 23 | font-weight: 500; 24 | line-height: 1.2; 25 | color: #000; 26 | } 27 | 28 | .learn h3 { 29 | font-size: 24px; 30 | } 31 | 32 | .learn h4 { 33 | font-size: 18px; 34 | } 35 | 36 | .learn h5 { 37 | margin-bottom: 0; 38 | font-size: 14px; 39 | } 40 | 41 | .learn ul { 42 | padding: 0; 43 | margin: 0 0 30px 25px; 44 | } 45 | 46 | .learn li { 47 | line-height: 20px; 48 | } 49 | 50 | .learn p { 51 | font-size: 15px; 52 | font-weight: 300; 53 | line-height: 1.3; 54 | margin-top: 0; 55 | margin-bottom: 0; 56 | } 57 | 58 | #issue-count { 59 | display: none; 60 | } 61 | 62 | .quote { 63 | border: none; 64 | margin: 20px 0 60px 0; 65 | } 66 | 67 | .quote p { 68 | font-style: italic; 69 | } 70 | 71 | .quote p:before { 72 | content: '“'; 73 | font-size: 50px; 74 | opacity: .15; 75 | position: absolute; 76 | top: -20px; 77 | left: 3px; 78 | } 79 | 80 | .quote p:after { 81 | content: '”'; 82 | font-size: 50px; 83 | opacity: .15; 84 | position: absolute; 85 | bottom: -42px; 86 | right: 3px; 87 | } 88 | 89 | .quote footer { 90 | position: absolute; 91 | bottom: -40px; 92 | right: 0; 93 | } 94 | 95 | .quote footer img { 96 | border-radius: 3px; 97 | } 98 | 99 | .quote footer a { 100 | margin-left: 5px; 101 | vertical-align: middle; 102 | } 103 | 104 | .speech-bubble { 105 | position: relative; 106 | padding: 10px; 107 | background: rgba(0, 0, 0, .04); 108 | border-radius: 5px; 109 | } 110 | 111 | .speech-bubble:after { 112 | content: ''; 113 | position: absolute; 114 | top: 100%; 115 | right: 30px; 116 | border: 13px solid transparent; 117 | border-top-color: rgba(0, 0, 0, .04); 118 | } 119 | 120 | .learn-bar > .learn { 121 | position: absolute; 122 | width: 272px; 123 | top: 8px; 124 | left: -300px; 125 | padding: 10px; 126 | border-radius: 5px; 127 | background-color: rgba(255, 255, 255, .6); 128 | transition-property: left; 129 | transition-duration: 500ms; 130 | } 131 | 132 | @media (min-width: 899px) { 133 | .learn-bar { 134 | width: auto; 135 | padding-left: 300px; 136 | } 137 | 138 | .learn-bar > .learn { 139 | left: 8px; 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /examples/2.2.2/todo/node_modules/todomvc-common/base.css: -------------------------------------------------------------------------------- 1 | hr { 2 | margin: 20px 0; 3 | border: 0; 4 | border-top: 1px dashed #c5c5c5; 5 | border-bottom: 1px dashed #f7f7f7; 6 | } 7 | 8 | .learn a { 9 | font-weight: normal; 10 | text-decoration: none; 11 | color: #b83f45; 12 | } 13 | 14 | .learn a:hover { 15 | text-decoration: underline; 16 | color: #787e7e; 17 | } 18 | 19 | .learn h3, 20 | .learn h4, 21 | .learn h5 { 22 | margin: 10px 0; 23 | font-weight: 500; 24 | line-height: 1.2; 25 | color: #000; 26 | } 27 | 28 | .learn h3 { 29 | font-size: 24px; 30 | } 31 | 32 | .learn h4 { 33 | font-size: 18px; 34 | } 35 | 36 | .learn h5 { 37 | margin-bottom: 0; 38 | font-size: 14px; 39 | } 40 | 41 | .learn ul { 42 | padding: 0; 43 | margin: 0 0 30px 25px; 44 | } 45 | 46 | .learn li { 47 | line-height: 20px; 48 | } 49 | 50 | .learn p { 51 | font-size: 15px; 52 | font-weight: 300; 53 | line-height: 1.3; 54 | margin-top: 0; 55 | margin-bottom: 0; 56 | } 57 | 58 | #issue-count { 59 | display: none; 60 | } 61 | 62 | .quote { 63 | border: none; 64 | margin: 20px 0 60px 0; 65 | } 66 | 67 | .quote p { 68 | font-style: italic; 69 | } 70 | 71 | .quote p:before { 72 | content: '“'; 73 | font-size: 50px; 74 | opacity: .15; 75 | position: absolute; 76 | top: -20px; 77 | left: 3px; 78 | } 79 | 80 | .quote p:after { 81 | content: '”'; 82 | font-size: 50px; 83 | opacity: .15; 84 | position: absolute; 85 | bottom: -42px; 86 | right: 3px; 87 | } 88 | 89 | .quote footer { 90 | position: absolute; 91 | bottom: -40px; 92 | right: 0; 93 | } 94 | 95 | .quote footer img { 96 | border-radius: 3px; 97 | } 98 | 99 | .quote footer a { 100 | margin-left: 5px; 101 | vertical-align: middle; 102 | } 103 | 104 | .speech-bubble { 105 | position: relative; 106 | padding: 10px; 107 | background: rgba(0, 0, 0, .04); 108 | border-radius: 5px; 109 | } 110 | 111 | .speech-bubble:after { 112 | content: ''; 113 | position: absolute; 114 | top: 100%; 115 | right: 30px; 116 | border: 13px solid transparent; 117 | border-top-color: rgba(0, 0, 0, .04); 118 | } 119 | 120 | .learn-bar > .learn { 121 | position: absolute; 122 | width: 272px; 123 | top: 8px; 124 | left: -300px; 125 | padding: 10px; 126 | border-radius: 5px; 127 | background-color: rgba(255, 255, 255, .6); 128 | transition-property: left; 129 | transition-duration: 500ms; 130 | } 131 | 132 | @media (min-width: 899px) { 133 | .learn-bar { 134 | width: auto; 135 | padding-left: 300px; 136 | } 137 | 138 | .learn-bar > .learn { 139 | left: 8px; 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /examples/2.3.1/todo/node_modules/todomvc-common/base.css: -------------------------------------------------------------------------------- 1 | hr { 2 | margin: 20px 0; 3 | border: 0; 4 | border-top: 1px dashed #c5c5c5; 5 | border-bottom: 1px dashed #f7f7f7; 6 | } 7 | 8 | .learn a { 9 | font-weight: normal; 10 | text-decoration: none; 11 | color: #b83f45; 12 | } 13 | 14 | .learn a:hover { 15 | text-decoration: underline; 16 | color: #787e7e; 17 | } 18 | 19 | .learn h3, 20 | .learn h4, 21 | .learn h5 { 22 | margin: 10px 0; 23 | font-weight: 500; 24 | line-height: 1.2; 25 | color: #000; 26 | } 27 | 28 | .learn h3 { 29 | font-size: 24px; 30 | } 31 | 32 | .learn h4 { 33 | font-size: 18px; 34 | } 35 | 36 | .learn h5 { 37 | margin-bottom: 0; 38 | font-size: 14px; 39 | } 40 | 41 | .learn ul { 42 | padding: 0; 43 | margin: 0 0 30px 25px; 44 | } 45 | 46 | .learn li { 47 | line-height: 20px; 48 | } 49 | 50 | .learn p { 51 | font-size: 15px; 52 | font-weight: 300; 53 | line-height: 1.3; 54 | margin-top: 0; 55 | margin-bottom: 0; 56 | } 57 | 58 | #issue-count { 59 | display: none; 60 | } 61 | 62 | .quote { 63 | border: none; 64 | margin: 20px 0 60px 0; 65 | } 66 | 67 | .quote p { 68 | font-style: italic; 69 | } 70 | 71 | .quote p:before { 72 | content: '“'; 73 | font-size: 50px; 74 | opacity: .15; 75 | position: absolute; 76 | top: -20px; 77 | left: 3px; 78 | } 79 | 80 | .quote p:after { 81 | content: '”'; 82 | font-size: 50px; 83 | opacity: .15; 84 | position: absolute; 85 | bottom: -42px; 86 | right: 3px; 87 | } 88 | 89 | .quote footer { 90 | position: absolute; 91 | bottom: -40px; 92 | right: 0; 93 | } 94 | 95 | .quote footer img { 96 | border-radius: 3px; 97 | } 98 | 99 | .quote footer a { 100 | margin-left: 5px; 101 | vertical-align: middle; 102 | } 103 | 104 | .speech-bubble { 105 | position: relative; 106 | padding: 10px; 107 | background: rgba(0, 0, 0, .04); 108 | border-radius: 5px; 109 | } 110 | 111 | .speech-bubble:after { 112 | content: ''; 113 | position: absolute; 114 | top: 100%; 115 | right: 30px; 116 | border: 13px solid transparent; 117 | border-top-color: rgba(0, 0, 0, .04); 118 | } 119 | 120 | .learn-bar > .learn { 121 | position: absolute; 122 | width: 272px; 123 | top: 8px; 124 | left: -300px; 125 | padding: 10px; 126 | border-radius: 5px; 127 | background-color: rgba(255, 255, 255, .6); 128 | transition-property: left; 129 | transition-duration: 500ms; 130 | } 131 | 132 | @media (min-width: 899px) { 133 | .learn-bar { 134 | width: auto; 135 | padding-left: 300px; 136 | } 137 | 138 | .learn-bar > .learn { 139 | left: 8px; 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /examples/2.3.2/todo/node_modules/todomvc-common/base.css: -------------------------------------------------------------------------------- 1 | hr { 2 | margin: 20px 0; 3 | border: 0; 4 | border-top: 1px dashed #c5c5c5; 5 | border-bottom: 1px dashed #f7f7f7; 6 | } 7 | 8 | .learn a { 9 | font-weight: normal; 10 | text-decoration: none; 11 | color: #b83f45; 12 | } 13 | 14 | .learn a:hover { 15 | text-decoration: underline; 16 | color: #787e7e; 17 | } 18 | 19 | .learn h3, 20 | .learn h4, 21 | .learn h5 { 22 | margin: 10px 0; 23 | font-weight: 500; 24 | line-height: 1.2; 25 | color: #000; 26 | } 27 | 28 | .learn h3 { 29 | font-size: 24px; 30 | } 31 | 32 | .learn h4 { 33 | font-size: 18px; 34 | } 35 | 36 | .learn h5 { 37 | margin-bottom: 0; 38 | font-size: 14px; 39 | } 40 | 41 | .learn ul { 42 | padding: 0; 43 | margin: 0 0 30px 25px; 44 | } 45 | 46 | .learn li { 47 | line-height: 20px; 48 | } 49 | 50 | .learn p { 51 | font-size: 15px; 52 | font-weight: 300; 53 | line-height: 1.3; 54 | margin-top: 0; 55 | margin-bottom: 0; 56 | } 57 | 58 | #issue-count { 59 | display: none; 60 | } 61 | 62 | .quote { 63 | border: none; 64 | margin: 20px 0 60px 0; 65 | } 66 | 67 | .quote p { 68 | font-style: italic; 69 | } 70 | 71 | .quote p:before { 72 | content: '“'; 73 | font-size: 50px; 74 | opacity: .15; 75 | position: absolute; 76 | top: -20px; 77 | left: 3px; 78 | } 79 | 80 | .quote p:after { 81 | content: '”'; 82 | font-size: 50px; 83 | opacity: .15; 84 | position: absolute; 85 | bottom: -42px; 86 | right: 3px; 87 | } 88 | 89 | .quote footer { 90 | position: absolute; 91 | bottom: -40px; 92 | right: 0; 93 | } 94 | 95 | .quote footer img { 96 | border-radius: 3px; 97 | } 98 | 99 | .quote footer a { 100 | margin-left: 5px; 101 | vertical-align: middle; 102 | } 103 | 104 | .speech-bubble { 105 | position: relative; 106 | padding: 10px; 107 | background: rgba(0, 0, 0, .04); 108 | border-radius: 5px; 109 | } 110 | 111 | .speech-bubble:after { 112 | content: ''; 113 | position: absolute; 114 | top: 100%; 115 | right: 30px; 116 | border: 13px solid transparent; 117 | border-top-color: rgba(0, 0, 0, .04); 118 | } 119 | 120 | .learn-bar > .learn { 121 | position: absolute; 122 | width: 272px; 123 | top: 8px; 124 | left: -300px; 125 | padding: 10px; 126 | border-radius: 5px; 127 | background-color: rgba(255, 255, 255, .6); 128 | transition-property: left; 129 | transition-duration: 500ms; 130 | } 131 | 132 | @media (min-width: 899px) { 133 | .learn-bar { 134 | width: auto; 135 | padding-left: 300px; 136 | } 137 | 138 | .learn-bar > .learn { 139 | left: 8px; 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /examples/2.4.1/todo/node_modules/todomvc-common/base.css: -------------------------------------------------------------------------------- 1 | hr { 2 | margin: 20px 0; 3 | border: 0; 4 | border-top: 1px dashed #c5c5c5; 5 | border-bottom: 1px dashed #f7f7f7; 6 | } 7 | 8 | .learn a { 9 | font-weight: normal; 10 | text-decoration: none; 11 | color: #b83f45; 12 | } 13 | 14 | .learn a:hover { 15 | text-decoration: underline; 16 | color: #787e7e; 17 | } 18 | 19 | .learn h3, 20 | .learn h4, 21 | .learn h5 { 22 | margin: 10px 0; 23 | font-weight: 500; 24 | line-height: 1.2; 25 | color: #000; 26 | } 27 | 28 | .learn h3 { 29 | font-size: 24px; 30 | } 31 | 32 | .learn h4 { 33 | font-size: 18px; 34 | } 35 | 36 | .learn h5 { 37 | margin-bottom: 0; 38 | font-size: 14px; 39 | } 40 | 41 | .learn ul { 42 | padding: 0; 43 | margin: 0 0 30px 25px; 44 | } 45 | 46 | .learn li { 47 | line-height: 20px; 48 | } 49 | 50 | .learn p { 51 | font-size: 15px; 52 | font-weight: 300; 53 | line-height: 1.3; 54 | margin-top: 0; 55 | margin-bottom: 0; 56 | } 57 | 58 | #issue-count { 59 | display: none; 60 | } 61 | 62 | .quote { 63 | border: none; 64 | margin: 20px 0 60px 0; 65 | } 66 | 67 | .quote p { 68 | font-style: italic; 69 | } 70 | 71 | .quote p:before { 72 | content: '“'; 73 | font-size: 50px; 74 | opacity: .15; 75 | position: absolute; 76 | top: -20px; 77 | left: 3px; 78 | } 79 | 80 | .quote p:after { 81 | content: '”'; 82 | font-size: 50px; 83 | opacity: .15; 84 | position: absolute; 85 | bottom: -42px; 86 | right: 3px; 87 | } 88 | 89 | .quote footer { 90 | position: absolute; 91 | bottom: -40px; 92 | right: 0; 93 | } 94 | 95 | .quote footer img { 96 | border-radius: 3px; 97 | } 98 | 99 | .quote footer a { 100 | margin-left: 5px; 101 | vertical-align: middle; 102 | } 103 | 104 | .speech-bubble { 105 | position: relative; 106 | padding: 10px; 107 | background: rgba(0, 0, 0, .04); 108 | border-radius: 5px; 109 | } 110 | 111 | .speech-bubble:after { 112 | content: ''; 113 | position: absolute; 114 | top: 100%; 115 | right: 30px; 116 | border: 13px solid transparent; 117 | border-top-color: rgba(0, 0, 0, .04); 118 | } 119 | 120 | .learn-bar > .learn { 121 | position: absolute; 122 | width: 272px; 123 | top: 8px; 124 | left: -300px; 125 | padding: 10px; 126 | border-radius: 5px; 127 | background-color: rgba(255, 255, 255, .6); 128 | transition-property: left; 129 | transition-duration: 500ms; 130 | } 131 | 132 | @media (min-width: 899px) { 133 | .learn-bar { 134 | width: auto; 135 | padding-left: 300px; 136 | } 137 | 138 | .learn-bar > .learn { 139 | left: 8px; 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /examples/2.4.2/todo/node_modules/todomvc-common/base.css: -------------------------------------------------------------------------------- 1 | hr { 2 | margin: 20px 0; 3 | border: 0; 4 | border-top: 1px dashed #c5c5c5; 5 | border-bottom: 1px dashed #f7f7f7; 6 | } 7 | 8 | .learn a { 9 | font-weight: normal; 10 | text-decoration: none; 11 | color: #b83f45; 12 | } 13 | 14 | .learn a:hover { 15 | text-decoration: underline; 16 | color: #787e7e; 17 | } 18 | 19 | .learn h3, 20 | .learn h4, 21 | .learn h5 { 22 | margin: 10px 0; 23 | font-weight: 500; 24 | line-height: 1.2; 25 | color: #000; 26 | } 27 | 28 | .learn h3 { 29 | font-size: 24px; 30 | } 31 | 32 | .learn h4 { 33 | font-size: 18px; 34 | } 35 | 36 | .learn h5 { 37 | margin-bottom: 0; 38 | font-size: 14px; 39 | } 40 | 41 | .learn ul { 42 | padding: 0; 43 | margin: 0 0 30px 25px; 44 | } 45 | 46 | .learn li { 47 | line-height: 20px; 48 | } 49 | 50 | .learn p { 51 | font-size: 15px; 52 | font-weight: 300; 53 | line-height: 1.3; 54 | margin-top: 0; 55 | margin-bottom: 0; 56 | } 57 | 58 | #issue-count { 59 | display: none; 60 | } 61 | 62 | .quote { 63 | border: none; 64 | margin: 20px 0 60px 0; 65 | } 66 | 67 | .quote p { 68 | font-style: italic; 69 | } 70 | 71 | .quote p:before { 72 | content: '“'; 73 | font-size: 50px; 74 | opacity: .15; 75 | position: absolute; 76 | top: -20px; 77 | left: 3px; 78 | } 79 | 80 | .quote p:after { 81 | content: '”'; 82 | font-size: 50px; 83 | opacity: .15; 84 | position: absolute; 85 | bottom: -42px; 86 | right: 3px; 87 | } 88 | 89 | .quote footer { 90 | position: absolute; 91 | bottom: -40px; 92 | right: 0; 93 | } 94 | 95 | .quote footer img { 96 | border-radius: 3px; 97 | } 98 | 99 | .quote footer a { 100 | margin-left: 5px; 101 | vertical-align: middle; 102 | } 103 | 104 | .speech-bubble { 105 | position: relative; 106 | padding: 10px; 107 | background: rgba(0, 0, 0, .04); 108 | border-radius: 5px; 109 | } 110 | 111 | .speech-bubble:after { 112 | content: ''; 113 | position: absolute; 114 | top: 100%; 115 | right: 30px; 116 | border: 13px solid transparent; 117 | border-top-color: rgba(0, 0, 0, .04); 118 | } 119 | 120 | .learn-bar > .learn { 121 | position: absolute; 122 | width: 272px; 123 | top: 8px; 124 | left: -300px; 125 | padding: 10px; 126 | border-radius: 5px; 127 | background-color: rgba(255, 255, 255, .6); 128 | transition-property: left; 129 | transition-duration: 500ms; 130 | } 131 | 132 | @media (min-width: 899px) { 133 | .learn-bar { 134 | width: auto; 135 | padding-left: 300px; 136 | } 137 | 138 | .learn-bar > .learn { 139 | left: 8px; 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /examples/2.5/todo/node_modules/todomvc-common/base.css: -------------------------------------------------------------------------------- 1 | hr { 2 | margin: 20px 0; 3 | border: 0; 4 | border-top: 1px dashed #c5c5c5; 5 | border-bottom: 1px dashed #f7f7f7; 6 | } 7 | 8 | .learn a { 9 | font-weight: normal; 10 | text-decoration: none; 11 | color: #b83f45; 12 | } 13 | 14 | .learn a:hover { 15 | text-decoration: underline; 16 | color: #787e7e; 17 | } 18 | 19 | .learn h3, 20 | .learn h4, 21 | .learn h5 { 22 | margin: 10px 0; 23 | font-weight: 500; 24 | line-height: 1.2; 25 | color: #000; 26 | } 27 | 28 | .learn h3 { 29 | font-size: 24px; 30 | } 31 | 32 | .learn h4 { 33 | font-size: 18px; 34 | } 35 | 36 | .learn h5 { 37 | margin-bottom: 0; 38 | font-size: 14px; 39 | } 40 | 41 | .learn ul { 42 | padding: 0; 43 | margin: 0 0 30px 25px; 44 | } 45 | 46 | .learn li { 47 | line-height: 20px; 48 | } 49 | 50 | .learn p { 51 | font-size: 15px; 52 | font-weight: 300; 53 | line-height: 1.3; 54 | margin-top: 0; 55 | margin-bottom: 0; 56 | } 57 | 58 | #issue-count { 59 | display: none; 60 | } 61 | 62 | .quote { 63 | border: none; 64 | margin: 20px 0 60px 0; 65 | } 66 | 67 | .quote p { 68 | font-style: italic; 69 | } 70 | 71 | .quote p:before { 72 | content: '“'; 73 | font-size: 50px; 74 | opacity: .15; 75 | position: absolute; 76 | top: -20px; 77 | left: 3px; 78 | } 79 | 80 | .quote p:after { 81 | content: '”'; 82 | font-size: 50px; 83 | opacity: .15; 84 | position: absolute; 85 | bottom: -42px; 86 | right: 3px; 87 | } 88 | 89 | .quote footer { 90 | position: absolute; 91 | bottom: -40px; 92 | right: 0; 93 | } 94 | 95 | .quote footer img { 96 | border-radius: 3px; 97 | } 98 | 99 | .quote footer a { 100 | margin-left: 5px; 101 | vertical-align: middle; 102 | } 103 | 104 | .speech-bubble { 105 | position: relative; 106 | padding: 10px; 107 | background: rgba(0, 0, 0, .04); 108 | border-radius: 5px; 109 | } 110 | 111 | .speech-bubble:after { 112 | content: ''; 113 | position: absolute; 114 | top: 100%; 115 | right: 30px; 116 | border: 13px solid transparent; 117 | border-top-color: rgba(0, 0, 0, .04); 118 | } 119 | 120 | .learn-bar > .learn { 121 | position: absolute; 122 | width: 272px; 123 | top: 8px; 124 | left: -300px; 125 | padding: 10px; 126 | border-radius: 5px; 127 | background-color: rgba(255, 255, 255, .6); 128 | transition-property: left; 129 | transition-duration: 500ms; 130 | } 131 | 132 | @media (min-width: 899px) { 133 | .learn-bar { 134 | width: auto; 135 | padding-left: 300px; 136 | } 137 | 138 | .learn-bar > .learn { 139 | left: 8px; 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /examples/2.4.2/todo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Vue.js • TodoMVC 6 | 7 | 8 | 9 | 10 |
11 |
12 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /examples/2.4.2/todo/js/app.js: -------------------------------------------------------------------------------- 1 | /*global Vue, todoStorage */ 2 | 3 | (function (exports) { 4 | 5 | 'use strict'; 6 | 7 | var filters = { 8 | all: function (todos) { 9 | return todos; 10 | }, 11 | active: function (todos) { 12 | return todos.filter(function (todo) { 13 | return !todo.completed; 14 | }); 15 | }, 16 | completed: function (todos) { 17 | return todos.filter(function (todo) { 18 | return todo.completed; 19 | }); 20 | } 21 | }; 22 | 23 | var vm = exports.app = new Vue({ 24 | 25 | template: document.getElementById("tmpl").innerHTML, 26 | 27 | // app initial state 28 | data: { 29 | todos: todoStorage.fetch(), 30 | newTodo: '', 31 | editedTodo: null, 32 | visibility: 'all' 33 | }, 34 | computed: { 35 | filteredTodos: function () { 36 | return filters[this.visibility](this.todos); 37 | }, 38 | remaining: function () { 39 | return filters.active(this.todos).length; 40 | } 41 | }, 42 | methods: { 43 | inputTodo: function($event){ 44 | this.newTodo = $event.target.value; 45 | }, 46 | inputEditTodo: function($event, todo){ 47 | todo.title = $event.target.value; 48 | }, 49 | addTodo: function () { 50 | var value = this.newTodo && this.newTodo.trim(); 51 | if (!value) { 52 | return; 53 | } 54 | this.todos.push({ title: value, completed: false }); 55 | this.newTodo = ''; 56 | }, 57 | 58 | removeTodo: function (todo) { 59 | var todos = this.todos; 60 | if (!todos.length) return 61 | var index = todos.indexOf(todo) 62 | if (index > -1) { 63 | todos.splice(index, 1); 64 | } 65 | }, 66 | 67 | editTodo: function (todo) { 68 | this.beforeEditCache = todo.title; 69 | this.editedTodo = todo; 70 | }, 71 | 72 | changeTodo: function (todo) { 73 | todo.completed = !todo.completed 74 | }, 75 | 76 | doneEdit: function (todo) { 77 | if (!this.editedTodo) { 78 | return; 79 | } 80 | this.editedTodo = null; 81 | todo.title = todo.title.trim(); 82 | if (!todo.title) { 83 | this.removeTodo(todo); 84 | } 85 | }, 86 | 87 | cancelEdit: function (todo) { 88 | this.editedTodo = null; 89 | todo.title = this.beforeEditCache; 90 | }, 91 | 92 | removeCompleted: function () { 93 | this.todos = filters.active(this.todos); 94 | } 95 | } 96 | }); 97 | 98 | vm.$mount("todoapp") 99 | 100 | vm.$watch("todos", function(todos){ 101 | todoStorage.save(todos); 102 | }, { 103 | deep: true 104 | }) 105 | })(window); 106 | -------------------------------------------------------------------------------- /src/core/vdom/events.js: -------------------------------------------------------------------------------- 1 | import { warn } from 'core/util/index' 2 | 3 | 4 | let target 5 | 6 | function add (event, handler, once, capture) { 7 | if (once) { 8 | const oldHandler = handler 9 | const _target = target // save current target element in closure 10 | handler = function (ev) { 11 | const res = arguments.length === 1 12 | ? oldHandler(ev) 13 | : oldHandler.apply(null, arguments) 14 | 15 | if (res !== null) { 16 | // 执行一次之后就remove掉 17 | // 但是有个例外,例如存在keydown等keyCode的修饰符时: 18 | // oldHandler = function($event) { if($event.keyCode != 13) return null; blabla($event); } 19 | // 在没触发过的话真正的handler:blabla前,我们不应该移除监听,于是加多一个null的返回值干扰流程 20 | remove(event, handler, capture, _target) 21 | } 22 | } 23 | } 24 | target.addEventListener(event, handler, capture) 25 | } 26 | 27 | function remove (event, handler, capture, _target) { 28 | (_target || target).removeEventListener(event, handler, capture) 29 | } 30 | 31 | 32 | function createFnInvoker (fns) { 33 | function invoker () { 34 | const fns = invoker.fns 35 | if (Array.isArray(fns)) { 36 | for (let i = 0; i < fns.length; i++) { 37 | fns[i].apply(null, arguments) 38 | } 39 | } else { 40 | // return handler return value for single handlers 41 | return fns.apply(null, arguments) 42 | } 43 | } 44 | invoker.fns = fns 45 | return invoker 46 | } 47 | 48 | // name = "~!click" 其中 ~表示once , !表示capture 49 | const normalizeEvent = (name) => { 50 | const once = name.charAt(0) === '~' // Prefixed last, checked first 51 | name = once ? name.slice(1) : name 52 | const capture = name.charAt(0) === '!' 53 | name = capture ? name.slice(1) : name 54 | return { 55 | name, 56 | once, 57 | capture 58 | } 59 | } 60 | 61 | function updateListeners (on, oldOn) { 62 | let name, cur, old, event 63 | for (name in on) { 64 | cur = on[name] 65 | old = oldOn[name] 66 | event = normalizeEvent(name) 67 | if (!cur) { // v-on:click="clickme" 找不到clickme同名方法定义 68 | warn( 69 | `Invalid handler for event "${event.name}": got ` + String(cur) 70 | ) 71 | } else if (!old) { // 旧vnode没有on此事件 72 | if (!cur.fns) { // 下次 patch 时就不用重新再包装 listenerCb 73 | cur = on[name] = createFnInvoker(cur) 74 | } 75 | add(event.name, cur, event.once, event.capture) 76 | } else if (cur !== old) { // 旧vnode和新vnode都有on同个事件,并且listenerCb指向不同,只要把当前的listenerCb指向cur的即可 77 | old.fns = cur 78 | on[name] = old 79 | } 80 | } 81 | 82 | // 把旧的监听移除掉 83 | for (name in oldOn) { 84 | if (!on[name]) { 85 | event = normalizeEvent(name) 86 | remove(event.name, oldOn[name], event.capture) 87 | } 88 | } 89 | } 90 | 91 | export function updateDOMListeners (oldVnode, vnode) { 92 | if (!oldVnode.data.on && !vnode.data.on) { 93 | return 94 | } 95 | const on = vnode.data.on || {} 96 | const oldOn = oldVnode.data.on || {} 97 | target = vnode.elm 98 | updateListeners(on, oldOn) 99 | } -------------------------------------------------------------------------------- /examples/2.5/todo/js/app.js: -------------------------------------------------------------------------------- 1 | /*global Vue, todoStorage */ 2 | 3 | (function (exports) { 4 | 5 | 'use strict'; 6 | 7 | var filters = { 8 | all: function (todos) { 9 | return todos; 10 | }, 11 | active: function (todos) { 12 | return todos.filter(function (todo) { 13 | return !todo.completed; 14 | }); 15 | }, 16 | completed: function (todos) { 17 | return todos.filter(function (todo) { 18 | return todo.completed; 19 | }); 20 | } 21 | }; 22 | 23 | var vm = exports.app = new Vue({ 24 | 25 | // the root element that will be compiled 26 | el: '.todoapp', 27 | 28 | // app initial state 29 | data: { 30 | todos: todoStorage.fetch(), 31 | newTodo: '', 32 | editedTodo: null, 33 | visibility: 'all' 34 | }, 35 | computed: { 36 | filteredTodos: function () { 37 | return filters[this.visibility](this.todos); 38 | }, 39 | remaining: function () { 40 | return filters.active(this.todos).length; 41 | }, 42 | allDone: { 43 | get: function () { 44 | return this.remaining === 0; 45 | }, 46 | set: function (value) { 47 | this.todos.forEach(function (todo) { 48 | todo.completed = value; 49 | }); 50 | } 51 | } 52 | }, 53 | methods: { 54 | inputTodo: function($event){ 55 | this.newTodo = $event.target.value; 56 | }, 57 | inputEditTodo: function($event, todo){ 58 | todo.title = $event.target.value; 59 | }, 60 | addTodo: function () { 61 | var value = this.newTodo && this.newTodo.trim(); 62 | if (!value) { 63 | return; 64 | } 65 | this.todos.push({ title: value, completed: false }); 66 | this.newTodo = ''; 67 | }, 68 | 69 | removeTodo: function (todo) { 70 | var todos = this.todos; 71 | if (!todos.length) return 72 | var index = todos.indexOf(todo) 73 | if (index > -1) { 74 | todos.splice(index, 1); 75 | } 76 | }, 77 | 78 | editTodo: function (todo) { 79 | this.beforeEditCache = todo.title; 80 | this.editedTodo = todo; 81 | }, 82 | 83 | changeTodo: function (todo) { 84 | todo.completed = !todo.completed 85 | }, 86 | 87 | doneEdit: function (todo) { 88 | if (!this.editedTodo) { 89 | return; 90 | } 91 | this.editedTodo = null; 92 | todo.title = todo.title.trim(); 93 | if (!todo.title) { 94 | this.removeTodo(todo); 95 | } 96 | }, 97 | 98 | cancelEdit: function (todo) { 99 | this.editedTodo = null; 100 | todo.title = this.beforeEditCache; 101 | }, 102 | 103 | removeCompleted: function () { 104 | this.todos = filters.active(this.todos); 105 | } 106 | } 107 | }); 108 | 109 | vm.$watch("todos", function(todos){ 110 | todoStorage.save(todos); 111 | }, { 112 | deep: true 113 | }) 114 | })(window); 115 | -------------------------------------------------------------------------------- /src/core/instance/state.js: -------------------------------------------------------------------------------- 1 | 2 | const sharedPropertyDefinition = { 3 | enumerable: true, 4 | configurable: true, 5 | get: noop, 6 | set: noop 7 | } 8 | 9 | export function proxy (target, sourceKey, key) { 10 | sharedPropertyDefinition.get = function proxyGetter () { 11 | return this[sourceKey][key] 12 | }; 13 | sharedPropertyDefinition.set = function proxySetter (val) { 14 | this[sourceKey][key] = val; 15 | }; 16 | Object.defineProperty(target, key, sharedPropertyDefinition); 17 | } 18 | 19 | export function initState (vm) { 20 | vm._watchers = [] 21 | const opts = vm.$options 22 | 23 | if (opts.methods) initMethods(vm, opts.methods) 24 | 25 | if (opts.data) { 26 | initData(vm) 27 | } else { 28 | observe(vm._data = {}, vm) 29 | } 30 | 31 | if (opts.computed) initComputed(vm, opts.computed) 32 | } 33 | 34 | 35 | function initData (vm) { 36 | let data = vm.$options.data 37 | data = vm._data = data || {} // 把 data 所有属性代理到 vm._data 上 38 | 39 | if (!isPlainObject(data)) { 40 | data = {} 41 | } 42 | const keys = Object.keys(data) 43 | const props = vm.$options.props 44 | let i = keys.length 45 | while (i--) { 46 | if (!isReserved(keys[i])) { // vm._xx vm.$xxx 都是vm的内部/外部方法,所以不能代理到data上 47 | proxy(vm, `_data`, keys[i]) // 把 vm.abc 代理到 vm._data.abc 48 | } 49 | } 50 | observe(data, this) 51 | } 52 | import Watcher from '../observer/watcher' 53 | 54 | import { 55 | set, 56 | del, 57 | observe 58 | } from '../observer/index' 59 | 60 | import { 61 | warn, 62 | hasOwn, 63 | isReserved, 64 | isPlainObject, 65 | bind, 66 | noop 67 | } from '../util/index' 68 | 69 | function initComputed (vm, computed) { 70 | for (const key in computed) { 71 | const userDef = computed[key] 72 | const getter = typeof userDef === 'function' ? userDef : userDef.get 73 | 74 | if (!(key in vm)) { 75 | defineComputed(vm, key, userDef) 76 | } 77 | } 78 | } 79 | 80 | function defineComputed (target, key, userDef) { 81 | if (typeof userDef === 'function') { // computed传入function的话,不可写 82 | sharedPropertyDefinition.get = function () { return userDef.call(target) } 83 | sharedPropertyDefinition.set = noop 84 | } else { 85 | sharedPropertyDefinition.get = userDef.get ? userDef.get : noop 86 | sharedPropertyDefinition.set = userDef.set ? userDef.set : noop 87 | } 88 | Object.defineProperty(target, key, sharedPropertyDefinition) 89 | } 90 | 91 | function initMethods (vm, methods) { 92 | for (const key in methods) { 93 | vm[key] = methods[key] == null ? noop : bind(methods[key], vm) 94 | } 95 | } 96 | 97 | export function stateMixin (Vue) { 98 | Vue.prototype.$set = set 99 | Vue.prototype.$delete = del 100 | 101 | Vue.prototype.$watch = function (expOrFn, cb, options) { 102 | const vm = this 103 | options = options || {} 104 | options.user = true // 标记用户主动监听的Watcher 105 | const watcher = new Watcher(vm, expOrFn, cb, options) 106 | if (options.immediate) { 107 | cb.call(vm, watcher.value) 108 | } 109 | return function unwatchFn () { // 返回取消watch的接口 110 | watcher.teardown() 111 | } 112 | } 113 | } -------------------------------------------------------------------------------- /src/compiler/codegen/events.js: -------------------------------------------------------------------------------- 1 | // v-on:click="function(){}" 2 | // v-on:click="() => {}" 3 | const fnExpRE = /^\s*([\w$_]+|\([^)]*?\))\s*=>|^function\s*\(/ 4 | 5 | // v-on:click="xxx" // xxx为vm的一个方法名字 6 | const simplePathRE = /^\s*[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*|\['.*?']|\[".*?"]|\[\d+]|\[[A-Za-z_$][\w$]*])*\s*$/ 7 | 8 | //else : // v-on:click="console.log(xxx);xxxx;" // 要被包裹成 function($event) { console.log(xxx);xxxx; } 9 | 10 | // keyCode aliases 11 | const keyCodes = { 12 | esc: 27, 13 | tab: 9, 14 | enter: 13, 15 | space: 32, 16 | up: 38, 17 | left: 37, 18 | right: 39, 19 | down: 40, 20 | 'delete': [8, 46] 21 | } 22 | 23 | const genGuard = condition => `if(${condition})return null;` 24 | // 生成的代码就变成 25 | /* 26 | function ($event) { 27 | if ($event.target !== $event.currentTarget) return null; 28 | if ($event.keyCode !== 13) return null; 29 | } 30 | */ 31 | 32 | const modifierCode = { 33 | stop: '$event.stopPropagation();', 34 | prevent: '$event.preventDefault();', 35 | self: genGuard(`$event.target !== $event.currentTarget`), 36 | ctrl: genGuard(`!$event.ctrlKey`), 37 | shift: genGuard(`!$event.shiftKey`), 38 | alt: genGuard(`!$event.altKey`), 39 | meta: genGuard(`!$event.metaKey`), 40 | left: genGuard(`$event.button !== 0`), 41 | middle: genGuard(`$event.button !== 1`), 42 | right: genGuard(`$event.button !== 2`) 43 | } 44 | 45 | export function genHandlers (events) { 46 | let res = 'on:{' 47 | for (const name in events) { 48 | res += `"${name}":${genHandler(name, events[name])},` 49 | } 50 | return res.slice(0, -1) + '}' 51 | } 52 | 53 | // v-on:click="clickme" 54 | // name='click' handler="clickme" 55 | function genHandler (name, handler) { 56 | if (!handler) { 57 | return 'function(){}' 58 | } else if (Array.isArray(handler)) { 59 | return `[${handler.map(handler => genHandler(name, handler)).join(',')}]` 60 | } else if (!handler.modifiers) { // 没有修饰符的话 .stop .prevent .self 61 | //支持:v-on:click="removeTodo(todo)" 和 v-on:click="xx" 62 | return fnExpRE.test(handler.value) || simplePathRE.test(handler.value) 63 | ? handler.value 64 | : `function($event){${handler.value}}` 65 | } else { 66 | let code = '' 67 | const keys = [] 68 | for (const key in handler.modifiers) { 69 | if (modifierCode[key]) { 70 | code += modifierCode[key] 71 | } else { 72 | keys.push(key) 73 | } 74 | } 75 | 76 | /* genKeyFilter(keys) 生成前缀判断条件: 77 | { 78 | if ($event.keyCode !== 13 && _k($event.keyCode,"enter", 13)) return null; 79 | } 80 | */ 81 | if (keys.length) { 82 | code = genKeyFilter(keys) + code 83 | } 84 | const handlerCode = simplePathRE.test(handler.value) 85 | ? handler.value + '($event)' // v-on:click="xxx" // 生成 xxx($event) 86 | : handler.value // v-on:click="console.log(xxx);xxxx;" 87 | return `function($event){${code}${handlerCode}}` 88 | } 89 | } 90 | 91 | function genKeyFilter (keys) { 92 | return `if(${keys.map(genFilterCode).join('&&')})return null;` 93 | } 94 | 95 | function genFilterCode (key) { 96 | const keyVal = parseInt(key, 10) 97 | if (keyVal) { // v-on:keydown.10 98 | return `$event.keyCode!==${keyVal}` 99 | } 100 | // v-on:keydown.enter 101 | const alias = keyCodes[key] 102 | // 103 | return `_k($event.keyCode,${JSON.stringify(key)}${alias ? ',' + JSON.stringify(alias) : ''})` 104 | } -------------------------------------------------------------------------------- /src/core/instance/index.js: -------------------------------------------------------------------------------- 1 | import { initMixin } from './init' 2 | import { stateMixin } from './state' 3 | import { renderMixin } from './render' 4 | import { warn } from '../util/index' 5 | import Watcher from '../observer/watcher' 6 | import patch from 'core/vdom/patch' 7 | import compile from 'compiler/index' 8 | import { 9 | noop 10 | } from '../util/index' 11 | 12 | const idToTemplate = (id) => { 13 | const el = query(id) 14 | return el && el.innerHTML 15 | } 16 | 17 | function Vue (options) { 18 | if (!(this instanceof Vue)) { 19 | warn('Vue is a constructor and should be called with the `new` keyword') 20 | } 21 | this._init(options) 22 | } 23 | 24 | initMixin(Vue) 25 | stateMixin(Vue) 26 | renderMixin(Vue) 27 | 28 | Vue.prototype._update = function () { 29 | const vm = this 30 | const vnode = vm._render() 31 | const prevVnode = vm._vnode 32 | 33 | vm._vnode = vnode 34 | 35 | if (!prevVnode) { 36 | patch(vm.$el, vnode) 37 | } else { 38 | patch(prevVnode, vnode) 39 | } 40 | } 41 | 42 | Vue.prototype.$mount = function (el) { 43 | // vm._vnode = document.getElementById(el) 44 | 45 | el = el ? query(el) : undefined 46 | 47 | const vm = this 48 | const options = vm.$options 49 | let template = options.template 50 | let _render = vm._render 51 | if (!_render) { //还没有render时,要去编译模板 52 | if (template) { // 直接有字符串模板传进来 53 | if (typeof template === 'string') { 54 | if (template.charAt(0) === '#') { // template = "#id" 55 | template = idToTemplate(template) 56 | /* istanbul ignore if */ 57 | if (!template) { 58 | warn( 59 | `Template element not found or is empty: ${options.template}`, 60 | this 61 | ) 62 | } 63 | } 64 | } else if (template.nodeType) { 65 | template = template.innerHTML 66 | } else { 67 | warn('invalid template option:' + template, this) 68 | return this 69 | } 70 | } else if (el) { // 从dom节点里边取 71 | template = getOuterHTML(el) 72 | } 73 | 74 | if (template) { 75 | const compiled = compile(template) 76 | 77 | vm._render = () => { 78 | return compiled.render.call(vm); 79 | } 80 | } 81 | } 82 | 83 | options.template = template 84 | return mountComponent(this, el) 85 | } 86 | 87 | /** 88 | * Query an element selector if it's not an element already. 89 | */ 90 | function query (el) { 91 | if (typeof el === 'string') { 92 | const selected = document.querySelector(el) 93 | if (!selected) { 94 | warn( 95 | 'Cannot find element: ' + el 96 | ) 97 | return document.createElement('div') 98 | } 99 | return selected 100 | } else { 101 | return el 102 | } 103 | } 104 | 105 | function mountComponent (vm, el) { 106 | vm.$el = el 107 | 108 | // 之后只要有 vm.a = "xxx" 的set动作,自然就会触发到整条依赖链的watcher,最后触发updateComponent的调用 109 | let updateComponent = () => { 110 | vm._update() 111 | } 112 | 113 | // vm 作为 root 开始收集依赖 114 | // 通过vm._update()调用,开始收集整个vm组件内部的依赖 115 | vm._watcher = new Watcher(vm, updateComponent, noop) 116 | 117 | return vm 118 | } 119 | 120 | function getOuterHTML (el) { 121 | if (el.outerHTML) { 122 | return el.outerHTML 123 | } else { 124 | const container = document.createElement('div') 125 | container.appendChild(el.cloneNode(true)) 126 | return container.innerHTML 127 | } 128 | } 129 | 130 | 131 | export default Vue 132 | -------------------------------------------------------------------------------- /src/core/observer/watcher.js: -------------------------------------------------------------------------------- 1 | 2 | import Dep, { pushTarget, popTarget } from './dep' 3 | 4 | import { 5 | warn, 6 | remove, 7 | isObject, 8 | parsePath, 9 | _Set as Set 10 | } from '../util/index' 11 | 12 | let uid = 0 13 | 14 | /** 15 | * A watcher parses an expression, collects dependencies, 16 | * and fires callback when the expression value changes. 17 | * This is used for both the $watch() api and directives. 18 | */ 19 | /* 20 | 21 | computed : { 22 | m: function(){ 23 | return this.a + this.b 24 | }, 25 | n: function(){ 26 | return this.a + this.c 27 | }, 28 | x: function(){ 29 | return this.a + this.b + this.c 30 | } 31 | } 32 | 33 | DepA.subs = [WatcherM, WatcherN, WatcherX] 34 | DepB.subs = [WatcherM, WatcherX] 35 | DepC.subs = [WatcherN, WatcherX] 36 | 37 | WatcherM.deps = [DepA, DepB] 38 | WatcherN.deps = [DepA, DepC] 39 | WatcherX.deps = [DepA, DepB, DepC] 40 | */ 41 | export default class Watcher { 42 | /* 43 | vm: Component; 44 | expression: string; 45 | cb: Function; 46 | id: number; 47 | lazy: boolean; 48 | dirty: boolean; 49 | active: boolean; 50 | deps: Array; 51 | newDeps: Array; 52 | depIds: Set; 53 | newDepIds: Set; 54 | getter: Function; 55 | value: any; 56 | */ 57 | 58 | constructor (vm, expOrFn, cb, options) { 59 | this.vm = vm 60 | vm._watchers.push(this) 61 | // options 62 | // https://cn.vuejs.org/v2/api/#vm-watch-expOrFn-callback-options 63 | if (options) { 64 | this.lazy = !!options.lazy //computed是 65 | } else { 66 | this.lazy = false 67 | } 68 | this.cb = cb 69 | this.id = ++uid // uid for batching 70 | this.active = true 71 | this.dirty = this.lazy // for lazy watchers 72 | 73 | // 在收集依赖的时候会使用 newDeps来收集。 74 | // 收集结束的时候会把newDeps覆盖到deps里 75 | this.deps = [] // WatcherM.deps = [DepA, DepB] 76 | this.newDeps = [] 77 | 78 | this.depIds = new Set() // 对应this.dep的所有id set,避免重复添加同个Dep 79 | this.newDepIds = new Set() 80 | 81 | // parse expression for getter 82 | if (typeof expOrFn === 'function') { 83 | this.getter = expOrFn 84 | } else { 85 | // vm.$watch("a.b", function(){ blabla; }) 86 | // "a.b" 的上下文是在 vm对象上,所以需要parsePath返回一个getter函数,在调用getter的时候,上下文绑定vm即可: this.gette.call(vm) 87 | this.getter = parsePath(expOrFn) 88 | if (!this.getter) { 89 | this.getter = function () {} 90 | warn( 91 | `Failed watching path: "${expOrFn}" ` + 92 | 'Watcher only accepts simple dot-delimited paths. ' + 93 | 'For full control, use a function instead.', 94 | vm 95 | ) 96 | } 97 | } 98 | 99 | this.value = this.lazy 100 | ? undefined 101 | : this.get() 102 | } 103 | 104 | /** 105 | * Evaluate the getter, and re-collect dependencies. 106 | */ 107 | get () { 108 | //开始收集依赖 109 | pushTarget(this) 110 | let value 111 | const vm = this.vm 112 | value = this.getter.call(vm, vm) 113 | 114 | //结束收集依赖 115 | popTarget() 116 | 117 | // 在收集依赖的时候会使用 newDeps来收集。 118 | // 收集结束的时候会把newDeps覆盖到deps里 119 | this.cleanupDeps() 120 | return value 121 | } 122 | 123 | /** 124 | * Add a dependency to this directive. 125 | */ 126 | addDep (dep) { 127 | const id = dep.id 128 | if (!this.newDepIds.has(id)) { 129 | this.newDepIds.add(id) 130 | this.newDeps.push(dep) // WatcherM.deps.push(DepA) // WatcherM.deps = [DepA, DepB] 131 | if (!this.depIds.has(id)) { 132 | dep.addSub(this) // DepA.subs.push(WatcherM) // DepA.subs = [WatcherM, WatcherN, WatcherX] 133 | } 134 | } 135 | } 136 | 137 | /** 138 | * Clean up for dependency collection. 139 | */ 140 | // 在收集依赖的时候会使用 newDeps来收集。 141 | // 收集结束的时候会把newDeps覆盖到deps里 142 | cleanupDeps () { 143 | let i = this.deps.length 144 | while (i--) { // 把旧依赖处理好 145 | const dep = this.deps[i] 146 | if (!this.newDepIds.has(dep.id)) { 147 | dep.removeSub(this) 148 | } 149 | } 150 | // 把新依赖 newDeps 更新到 deps 151 | // newDeps 更新成初始状态,方便下次收集依赖 152 | let tmp = this.depIds 153 | this.depIds = this.newDepIds 154 | this.newDepIds = tmp 155 | this.newDepIds.clear() 156 | tmp = this.deps 157 | this.deps = this.newDeps 158 | this.newDeps = tmp 159 | this.newDeps.length = 0 160 | } 161 | 162 | /** 163 | * Subscriber interface. 164 | * Will be called when a dependency changes. 165 | */ 166 | update () { 167 | if (this.lazy) { 168 | this.dirty = true 169 | } else { 170 | this.run() 171 | } 172 | } 173 | 174 | /** 175 | * Scheduler job interface. 176 | * Will be called by the scheduler. 177 | */ 178 | run () { 179 | if (this.active) { 180 | const value = this.get() 181 | if ( 182 | value !== this.value || 183 | isObject(value) 184 | ) { 185 | // set new value 186 | const oldValue = this.value 187 | this.value = value 188 | this.cb.call(this.vm, value, oldValue) 189 | } 190 | } 191 | } 192 | 193 | /** 194 | * Evaluate the value of the watcher. 195 | * This only gets called for lazy watchers. 196 | */ 197 | evaluate () { 198 | this.value = this.get() 199 | this.dirty = false 200 | } 201 | 202 | /** 203 | * Depend on all deps collected by this watcher. 204 | */ 205 | depend () { 206 | let i = this.deps.length 207 | while (i--) { 208 | this.deps[i].depend() 209 | } 210 | } 211 | 212 | /** 213 | * Remove self from all dependencies' subscriber list. 214 | */ 215 | // 销毁watcher之后,把dep也从目标队列删掉 216 | teardown () { 217 | if (this.active) { 218 | let i = this.deps.length 219 | while (i--) { 220 | this.deps[i].removeSub(this) 221 | } 222 | this.active = false 223 | } 224 | } 225 | } 226 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 如何学习Vue2源码 2 | 3 | ## 背景 4 | 5 | 近期我们把微信公众平台管理端的前端框架切成 MVVM 架构,框架层面最终我们选择了 [Vue](https://github.com/vuejs/vue),为了更了解 Vue,阅读Vue源码是必要的。 6 | 7 | 我参考的 Vue 版本是 [2.2.0](https://github.com/vuejs/vue/tree/v2.2.0),整个项目的代码1万2千行,如果不搞清楚原理,直接每一行看下来肯定会漏掉不少细节,或者对作者为什么这样写代码感到莫名其妙。 8 | 9 | 如此庞大的项目要啃下来并非易事,里边涉及到非常多的概念:Virtual Dom; 条件/列表渲染; 自定义组件; 双向绑定; 指令等等。 10 | 11 | 按照经验,编写这么庞大的系统,我们总是从第1行代码开始写起,慢慢写到第1万行,逐步构造出整个系统/框架。 12 | 13 | 所以我也会按照这个思路,从零开始构造出一个完整的Vue框架。 14 | 15 | ## 如何运行 16 | 17 | 从 [how-to-learn-vue2项目](https://github.com/raphealguo/how-to-learn-vue2) 下载各个分支代码 18 | 19 | 使用 webpack 进行打包,源码采用ES6风格编写。 20 | 21 | 构建: `npm install; webpack` 22 | 23 | 统计当前分支源码行数: `npm run line` 24 | 25 | 运行 Demo: 直接使用 Chrome 打开 examples 目录里边的实例代码即可 26 | 27 | ## 如何阅读 28 | 29 | 建议按照下边顺序阅读,同时参考 [官方的教程](https://cn.vuejs.org/v2/guide/index.html) 配合理解。 30 | 31 | 我会把每个章节的 **源码行数** 以及 **对应的分支** 标记出来,方便大家可以看到每次源码变更的行数。 32 | 33 | 1. 第一章 基础概念 34 | 35 | * [1.1 Virtual DOM](https://github.com/raphealguo/how-to-learn-vue2-blob/blob/master/articles/1.1.md) (源码总共 231 行,[查看代码](https://github.com/raphealguo/how-to-learn-vue2/tree/1.1/src)) 36 | 37 | 整个Vue的底层渲染机制是依赖VD的实现,因此先写一个极简的VD算法是非常不错的开头。 38 | 39 | * [1.2 HTML parser](https://github.com/raphealguo/how-to-learn-vue2-blob/blob/master/articles/1.2.md) (源码总共 639 行,[查看代码](https://github.com/raphealguo/how-to-learn-vue2/tree/1.2/src),[查看新增代码](https://github.com/raphealguo/how-to-learn-vue2/compare/1.1...1.2)) 40 | 41 | 每次手工构造一个 VNode 树效率非常低,而且可读性差,因此这一节会构造一个解释器,能把 HTML 字符串转化成 VNode树。 42 | 43 | 还可以阅读一下番外篇: [1.2.1 一个兼容性更佳的HTML parser](https://github.com/raphealguo/how-to-learn-vue2-blob/blob/master/articles/1.2.1.md) 44 | 45 | * [1.3 构建一个最简单的数据绑定的 Vue](https://github.com/raphealguo/how-to-learn-vue2-blob/blob/master/articles/1.3.md) (源码总共 945 行,[查看代码](https://github.com/raphealguo/how-to-learn-vue2/tree/1.3/src),[查看新增代码](https://github.com/raphealguo/how-to-learn-vue2/compare/1.2.1...1.3)) 46 | 47 | 前边2节的代码已经让我们有足够的基础可以构造一个简单的 Vue 类,在这一篇文章会介绍如何在 Vue 模板语法新增语法糖的流程。 48 | 49 | 2. 第二章 Vue雏形 50 | 51 | * [2.1 VNode 的属性 attrs 和 props](https://github.com/raphealguo/how-to-learn-vue2-blob/blob/master/articles/2.1.md) (源码总共 1112 行,[查看代码](https://github.com/raphealguo/how-to-learn-vue2/tree/2.1/src),[查看新增代码](https://github.com/raphealguo/how-to-learn-vue2/compare/1.3...2.1)) 52 | 53 | 在前边我们忽略了 Dom 元素的属性,我们这一节就把这个补齐,同时从这一节开始我们来逐步完善一个 Vue 的 todo 案例。 54 | 55 | * 2.2 控制语句 56 | 57 | * [2.2.1 条件渲染 v-if, v-else-if, v-else](https://github.com/raphealguo/how-to-learn-vue2-blob/blob/master/articles/2.2.1.md) (源码总共 1237 行,[查看代码](https://github.com/raphealguo/how-to-learn-vue2/tree/2.2.1/src),[查看新增代码](https://github.com/raphealguo/how-to-learn-vue2/compare/2.1...2.2.1)) 58 | 59 | 往往我们需要通过控制某个状态显示或者隐藏界面的某部分,这里就需要用到 if else 的控制语句。 60 | 61 | * [2.2.2 列表渲染 v-for](https://github.com/raphealguo/how-to-learn-vue2-blob/blob/master/articles/2.2.2.md) (源码总共 1371 行,[查看代码](https://github.com/raphealguo/how-to-learn-vue2/tree/2.2.2/src),[查看新增代码](https://github.com/raphealguo/how-to-learn-vue2/compare/2.2.1...2.2.2)) 62 | 63 | 这一节我们更新了 todo 的案例,支持 v-for 语法,可以传递一个数组进行列表渲染。 64 | 65 | 想了解如何改善 VNode 的 patch 算法减少列表 DOM 大规模重绘,还可以阅读番外篇: [2.2.2.1 列表渲染 v-for 的 key](https://github.com/raphealguo/how-to-learn-vue2-blob/blob/master/articles/2.2.2.1.md)。 66 | 67 | * 2.3 数据绑定 68 | 69 | * [2.3.1 响应式原理](https://github.com/raphealguo/how-to-learn-vue2-blob/blob/master/articles/2.3.1.md) (源码总共 1547 行,[查看代码](https://github.com/raphealguo/how-to-learn-vue2/tree/2.3.1/src),[查看新增代码](https://github.com/raphealguo/how-to-learn-vue2/compare/2.2.2.1...2.3.1)) 70 | 71 | 在之前的例子中,我们总是通过 vm.setData( { a:1, b:2 /* 需要填写整个完整的 data */} ) 来改变数据,从而引起界面的响应式变化。为了提高开发效率和可读性,我们更希望使用 vm.a = 3 来修改值,从而更新视图。 72 | 73 | * [2.3.2 深度追踪依赖变化](https://github.com/raphealguo/how-to-learn-vue2-blob/blob/master/articles/2.3.2.md) (源码总共 2245 行,[查看代码](https://github.com/raphealguo/how-to-learn-vue2/tree/2.3.2/src),[查看新增代码](https://github.com/raphealguo/how-to-learn-vue2/compare/2.3.1...2.3.2)) 74 | 75 | 在 2.3.1 节中,只要任何数据变化都一定会引起 VNode 树的更新计算,显然不是最高效的,因为界面不一定绑定了所有 vm 的所有属性,那些没被绑定的属性的更新不应该引起整个 vm 的 VNode 树计算,所以我们要追踪整个 VNode 树依赖的变化。 76 | 77 | * 2.4 事件处理器 78 | 79 | * [2.4.1 事件处理](https://github.com/raphealguo/how-to-learn-vue2-blob/blob/master/articles/2.4.1.md) (源码总共 2391 行,[查看代码](https://github.com/raphealguo/how-to-learn-vue2/tree/2.4.1/src),[查看新增代码](https://github.com/raphealguo/how-to-learn-vue2/compare/2.3.2...2.4.1)) 80 | 81 | 前边一直在介绍如何渲染界面,当你需要和界面做交互的时候,就需要涉及到 Dom 的事件处理,所以在这一节,我们也要往之前的模型里边加上监听事件的语法。 82 | 83 | * [2.4.2 完善事件语法以及事件修饰符](https://github.com/raphealguo/how-to-learn-vue2-blob/blob/master/articles/2.4.2.md) (源码总共 2563 行,[查看代码](https://github.com/raphealguo/how-to-learn-vue2/tree/2.4.2/src),[查看新增代码](https://github.com/raphealguo/how-to-learn-vue2/compare/2.4.1...2.4.2)) 84 | 85 | 2.4.1节设计的 v-on 语法仅接受方法名: ```v-on:dblclick="editTodo"``` ,由于语法过于局限,所以没法在触发 editTodo 事件的时候知道当前元素映射的数据,更好的语法应该是 ```v-on:dblclick="editTodo(todo)"``` ,此外这一节也新增点语法糖: Vue 的事件修饰符。 86 | 87 | * 2.5 完成todo案例 (源码总共 2832 行,[查看代码](https://github.com/raphealguo/how-to-learn-vue2/tree/2.5/src),[查看新增代码](https://github.com/raphealguo/how-to-learn-vue2/compare/2.4.2...2.5)) 88 | 89 | 基本的 Vue 雏形就完成了,我们把一些代码重新组织了一下,新增了一点点语法糖([绑定-HTML-Class](https://cn.vuejs.org/v2/guide/class-and-style.html#绑定-HTML-Class)),完善了整个 todo 的案例([查看代码](https://github.com/raphealguo/how-to-learn-vue2/tree/2.5/examples/2.5/todo))。到这一节结束,Vue 的工作原理已经剖析清楚了。 90 | 91 | 3. 第三章 Vue进阶 92 | 93 | * 3.1 生命周期 94 | 95 | * 3.2 自定义组件 96 | 97 | * 3.2.1 Vue.extend 98 | 99 | * 3.2.2 简单的自定义组件 100 | 101 | * 3.2.3 组件的prop 102 | 103 | * 3.2.4 组件的事件与原生事件 104 | 105 | * 3.2.5 slot 106 | 107 | * 3.3 nextTick 108 | 109 | * 3.4 指令 110 | 111 | * 3.4.1 自定义指令,内置 v-show 指令 112 | 113 | * 3.4.2 内置 v-text v-html 指令 114 | 115 | * 3.5 双向绑定 v-model 指令 116 | 117 | ## 附录 118 | 119 | 1. [VNode render](https://github.com/raphealguo/how-to-learn-vue2-blob/blob/master/articles/VNodeRender.md) 120 | 121 | ## 关于我 122 | 123 | 博客:[http://rapheal.sinaapp.com/](http://rapheal.sinaapp.com/) 124 | 125 | 微博:[@raphealguo](http://weibo.com/p/1005051628949221) -------------------------------------------------------------------------------- /src/core/observer/index.js: -------------------------------------------------------------------------------- 1 | import Dep from './dep' 2 | import { arrayMethods } from './array' 3 | import { 4 | def, 5 | isObject, 6 | isPlainObject, 7 | hasProto, 8 | hasOwn, 9 | } from '../util/index' 10 | 11 | const arrayKeys = Object.getOwnPropertyNames(arrayMethods) 12 | 13 | /* 14 | 15 | computed : { 16 | m: function(){ 17 | return this.a + this.b 18 | }, 19 | n: function(){ 20 | return this.a + this.c 21 | }, 22 | x: function(){ 23 | return this.a + this.b + this.c 24 | } 25 | } 26 | 27 | DepA.subs = [WatcherM, WatcherN, WatcherX] 28 | DepB.subs = [WatcherM, WatcherX] 29 | DepC.subs = [WatcherN, WatcherX] 30 | 31 | WatcherM.deps = [DepA, DepB] 32 | WatcherN.deps = [DepA, DepC] 33 | WatcherX.deps = [DepA, DepB, DepC] 34 | 35 | 当getA发生的时候,需要通过 depend 添加WatcherM/WatcherN/WatcherX的依赖deps, WatcherN.subs.push() 36 | 当setA发生的时候,需要通过 notify 广播 DepA.subs,让他们通知对应的watcher 37 | 38 | */ 39 | 40 | export class Observer { 41 | /* 42 | value: any; 43 | dep: Dep; 44 | */ 45 | constructor (value) { 46 | this.value = value 47 | this.dep = new Dep() 48 | def(value, '__ob__', this) // 把当前Observer对象 绑定在value.__ob__上 49 | 50 | // 将value深度遍历,订阅里边所有值的get set 51 | if (Array.isArray(value)) { 52 | // 由于数组原生的push/shift等方法也是写操作 53 | // 需要在这里勾住 54 | const augment = hasProto 55 | ? protoAugment 56 | : copyAugment 57 | augment(value, arrayMethods, arrayKeys) 58 | this.observeArray(value) 59 | } else { 60 | this.walk(value) 61 | } 62 | } 63 | 64 | /** 65 | * Walk through each property and convert them into 66 | * getter/setters. This method should only be called when 67 | * value type is Object. 68 | */ 69 | walk (obj) { 70 | const keys = Object.keys(obj) 71 | for (let i = 0; i < keys.length; i++) { 72 | defineReactive(obj, keys[i], obj[keys[i]]) 73 | } 74 | } 75 | 76 | /** 77 | * Observe a list of Array items. 78 | */ 79 | observeArray (items) { 80 | for (let i = 0, l = items.length; i < l; i++) { 81 | observe(items[i]) 82 | } 83 | } 84 | } 85 | 86 | // helpers 87 | 88 | /** 89 | * Augment an target Object or Array by intercepting 90 | * the prototype chain using __proto__ 91 | */ 92 | function protoAugment (target, src) { 93 | /* eslint-disable no-proto */ 94 | target.__proto__ = src 95 | /* eslint-enable no-proto */ 96 | } 97 | 98 | /** 99 | * Augment an target Object or Array by defining 100 | * hidden properties. 101 | */ 102 | /* istanbul ignore next */ 103 | function copyAugment (target, src, keys) { 104 | for (let i = 0, l = keys.length; i < l; i++) { 105 | const key = keys[i] 106 | def(target, key, src[key]) 107 | } 108 | } 109 | 110 | 111 | export function observe (value) { 112 | if (!isObject(value)) { 113 | return 114 | } 115 | 116 | let ob 117 | if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) { 118 | ob = value.__ob__ 119 | } else if ( 120 | (Array.isArray(value) || isPlainObject(value)) && 121 | !value._isVue // vm对象不作订阅 122 | ) { 123 | ob = new Observer(value) 124 | } 125 | 126 | return ob 127 | } 128 | 129 | export function defineReactive (obj, key, val) { 130 | const dep = new Dep() 131 | 132 | const property = Object.getOwnPropertyDescriptor(obj, key) 133 | if (property && property.configurable === false) { 134 | return 135 | } 136 | 137 | // cater for pre-defined getter/setters 138 | const getter = property && property.get 139 | const setter = property && property.set 140 | 141 | let childOb = observe(val) 142 | /* 143 | 144 | m: function(){ 145 | return this.a + this.b 146 | }, 147 | 148 | 当getA发生的时候,需要通过 depend 添加WatcherM/WatcherN/WatcherX的依赖deps, WatcherN.subs.push() 149 | 当setA发生的时候,需要通过 notify 广播 DepA.subs,让他们通知对应的watcher 150 | */ 151 | Object.defineProperty(obj, key, { 152 | enumerable: true, 153 | configurable: true, 154 | get: function reactiveGetter () { 155 | const value = getter ? getter.call(obj) : val 156 | if (Dep.target) { 157 | // getA发生的时候,Dep.target == DepM 158 | dep.depend() 159 | if (childOb) { 160 | childOb.dep.depend() 161 | } 162 | if (Array.isArray(value)) { 163 | dependArray(value) 164 | } 165 | } 166 | return val 167 | }, 168 | set: function reactiveSetter (newVal) { 169 | const value = val 170 | 171 | if (newVal === value) { 172 | return 173 | } 174 | 175 | // console.log("newVal = ", newVal) 176 | val = newVal 177 | 178 | childOb = observe(newVal) 179 | dep.notify() 180 | 181 | // vm._update at core/instance/index.js 182 | } 183 | }) 184 | } 185 | 186 | 187 | /** 188 | * Set a property on an object. Adds the new property and 189 | * triggers change notification if the property doesn't 190 | * already exist. 191 | */ 192 | export function set (obj, key, val) { 193 | if (Array.isArray(obj)) { 194 | obj.length = Math.max(obj.length, key) 195 | obj.splice(key, 1, val) 196 | return val 197 | } 198 | if (hasOwn(obj, key)) { 199 | obj[key] = val 200 | return 201 | } 202 | const ob = obj.__ob__ 203 | if (!ob) { // 不是订阅对象,直接set了返回 204 | obj[key] = val 205 | return 206 | } 207 | // 递归订阅set进去的value 208 | // ob.value 可以认为就是 obj 209 | defineReactive(ob.value, key, val) 210 | 211 | // set操作要notify deps 212 | ob.dep.notify() 213 | return val 214 | } 215 | 216 | /** 217 | * Delete a property and trigger change if necessary. 218 | */ 219 | export function del (obj, key) { 220 | if (Array.isArray(obj)) { 221 | obj.splice(key, 1) 222 | return 223 | } 224 | const ob = obj.__ob__ 225 | if (!hasOwn(obj, key)) { 226 | return 227 | } 228 | delete obj[key] 229 | if (!ob) { 230 | return 231 | } 232 | ob.dep.notify() 233 | } 234 | 235 | /** 236 | * Collect dependencies on array elements when the array is touched, since 237 | * we cannot intercept array element access like property getters. 238 | */ 239 | function dependArray (value) { 240 | for (let e, i = 0, l = value.length; i < l; i++) { 241 | e = value[i] 242 | e && e.__ob__ && e.__ob__.dep.depend() 243 | if (Array.isArray(e)) { 244 | dependArray(e) 245 | } 246 | } 247 | } 248 | -------------------------------------------------------------------------------- /src/compiler/codegen/index.js: -------------------------------------------------------------------------------- 1 | import { genHandlers } from './events' 2 | import parse from 'compiler/index' 3 | import VNode from 'core/vdom/vnode' 4 | import { warn } from 'core/util/debug' 5 | 6 | /* 7 |
8 | abc{{a}}xxx{{b}}def 9 |
a
10 |
b
11 |
    12 |
  • {{index}} : {{item}}
  • 13 |
14 | 15 | 16 | 17 | 18 |
19 | 20 | 生成函数: 21 | 22 | this == Vue实例 == vm 23 | 24 | function render() { 25 | with (this) { 26 | return _c('div', undefined, [ 27 | _c('span', { 28 | attrs: { name : 'test' } 29 | }, [ 30 | _v("abc" + _s(a) + "xxx" + _s(b) + "def") 31 | ]), 32 | 33 | // v-if 语句 34 | a ? _c('div', undefined, [ _v("a") ]) : _e(), 35 | b ? _c('div', undefined, [ _v("b") ]) : _e(), 36 | 37 | // v-for 38 | _c('ul', 39 | _l( 40 | (list), 41 | function(item, index) { 42 | return _c( 43 | 'li',[ _v(_s(index)+" : "+_s(item)) ] 44 | ) 45 | }) 46 | ) 47 | 48 | // v-click clickme == vm["clickme"].bind(vm) 49 | _c('button', { on:{"click":clickme} }, [_v("click me")]) 50 | 51 | // v-on:click.stop="console.log(1)" 52 | // click 需要产生一个闭包的handler,.stop等修饰符会作为这个handler的前置条件 53 | _c('button', { on:{"click": function($event){ $event.stopPropagation(); console.log(1) }}}, [_v("click me")]) 54 | 55 | // v-on:click.stop="click" 56 | // 这种和上边例子的区别在于,click是一个vm的method名字,需要生成$event参数给他 57 | _c('button', { on:{"click": function($event){ $event.stopPropagation(); click($event) }}}, [_v("click me")]) 58 | 59 | // v-on:keydown.enter.10="click" 60 | // 新增_k方法,用于判断 61 | _c('button',{on:{"keydown": function($event){ 62 | if($event.keyCode!==10 &&_k($event.keyCode,"enter",13)) return null; 63 | click($event) 64 | }}},[_v("click me")]) 65 | 66 | ]) 67 | } 68 | } 69 | */ 70 | export function generate (ast) { 71 | const code = ast ? genElement(ast) : '_c("div")' 72 | 73 | return { 74 | render: ("with(this){return " + code + "}") 75 | } 76 | } 77 | 78 | function genElement (el){ 79 | if (el.for && !el.forProcessed) { // 为了v-for和v-if的优先级:
    ,需要先处理for语句 80 | return genFor(el) 81 | } if (el.if && !el.ifProcessed) { 82 | return genIf(el) 83 | } else { 84 | let code 85 | const children = genChildren(el) || '[]' 86 | const data = genData(el) 87 | 88 | code = `_c('${el.tag}'${ 89 | `,${data}` // data 90 | }${ 91 | children ? `,${children}` : '' // children 92 | })` 93 | 94 | return code 95 | } 96 | } 97 | 98 | function genIf (el) { 99 | el.ifProcessed = true // 标记已经处理过当前这个if节点了,避免递归死循环 100 | return genIfConditions(el.ifConditions.slice()) 101 | } 102 | 103 | function genIfConditions (conditions) { 104 | if (!conditions.length) { 105 | return '_e()' 106 | } 107 | 108 | const condition = conditions.shift() // 因为我们并没有去真正删除 el.ifConditions 队列的元素,所以需要有el.ifProcessed = true来结束递归 109 | if (condition.exp) { 110 | return `(${condition.exp})?${genTernaryExp(condition.block)}:${genIfConditions(conditions)}` 111 | } else { 112 | return `${genTernaryExp(condition.block)}` 113 | } 114 | 115 | function genTernaryExp (el) { 116 | return genElement(el) 117 | } 118 | } 119 | 120 | function genFor (el) { 121 | const exp = el.for 122 | const alias = el.alias 123 | const iterator1 = el.iterator1 ? `,${el.iterator1}` : '' 124 | const iterator2 = el.iterator2 ? `,${el.iterator2}` : '' 125 | 126 | if (!el.key) { // v-for 最好声明key属性 127 | warn( 128 | `<${el.tag} v-for="${alias} in ${exp}">: component lists rendered with ` + 129 | `v-for should have explicit keys. ` + 130 | `See https://vuejs.org/guide/list.html#key for more info.`, 131 | true /* tip */ 132 | ) 133 | } 134 | 135 | // v-for="(item, index) in list" 136 | // alias = item, iterator1 = index 137 | 138 | // v-for="(value, key, index) in object" 139 | // alias = value, iterator1 = key, iterator2 = index 140 | 141 | 142 | // _l(val, render) 143 | // val = list 144 | // render = function (alias, iterator1, iterator2) { return genElement(el) } 145 | el.forProcessed = true // avoid recursion 146 | return `_l((${exp}),` + 147 | `function(${alias}${iterator1}${iterator2}){` + 148 | `return ${genElement(el)}` + 149 | '})' 150 | } 151 | 152 | function genData (el) { 153 | let data = '{' 154 | 155 | // key 156 | if (el.key) { 157 | data += `key:${el.key},` 158 | } 159 | if (el.attrs) { 160 | data += `attrs:{${genProps(el.attrs)}},` 161 | } 162 | // DOM props 163 | if (el.props) { 164 | data += `domProps:{${genProps(el.props)}},` 165 | } 166 | // event handlers 167 | if (el.events) { 168 | data += `${genHandlers(el.events)},` 169 | } 170 | 171 | // class 172 | if (el.staticClass) { 173 | data += `staticClass:${el.staticClass},` 174 | } 175 | if (el.classBinding) { 176 | data += `class:${el.classBinding},` 177 | } 178 | 179 | data = data.replace(/,$/, '') + '}' 180 | 181 | return data 182 | } 183 | 184 | 185 | function genChildren (el) { 186 | const children = el.children 187 | if (children.length) { 188 | const el = children[0] 189 | 190 | // 对v-for的情况做处理 191 | // _c('ul', undefined, [_l(xxx)]) 需要把_l提出来外层 192 | // 还有一些复杂的情况:_c('ul', undefined, [_c('div'), _l(xxx), _c('div')]) 只能在_c里边处理 193 | if (children.length === 1 && el.for) { 194 | return genElement(el) 195 | } 196 | return `[${children.map(genNode).join(',')}]` 197 | } 198 | } 199 | 200 | function genNode (node) { 201 | if (node.type === 1) { 202 | return genElement(node) 203 | } else { 204 | return genText(node) 205 | } 206 | } 207 | 208 | function genText (text) { 209 | return `_v(${text.type === 2 210 | ? text.expression // no need for () because already wrapped in _s() 211 | : JSON.stringify(text.text) 212 | })` 213 | } 214 | 215 | function genProps (props) { 216 | let res = '' 217 | for (let i = 0; i < props.length; i++) { 218 | const prop = props[i] 219 | res += `"${prop.name}":${prop.value},` 220 | } 221 | return res.slice(0, -1) // 去掉尾巴的逗号 222 | } -------------------------------------------------------------------------------- /src/core/vdom/patch.js: -------------------------------------------------------------------------------- 1 | import * as nodeOps from './node-ops' 2 | import VNode from './vnode' 3 | import { updateAttrs } from './attrs' 4 | import { updateClass } from './class' 5 | import { updateDOMProps } from './dom-props' 6 | import { updateDOMListeners } from './events' 7 | 8 | export const emptyNode = new VNode('', {}, []) 9 | 10 | function isUndef (s) { 11 | return s == null 12 | } 13 | 14 | function isDef (s) { 15 | return s != null 16 | } 17 | 18 | function sameVnode (vnode1, vnode2) { 19 | return ( 20 | vnode1.key === vnode2.key && 21 | vnode1.tag === vnode2.tag && 22 | !vnode1.data === !vnode2.data 23 | ) 24 | } 25 | 26 | function createKeyToOldIdx (children, beginIdx, endIdx) { 27 | let i, key 28 | const map = {} 29 | for (i = beginIdx; i <= endIdx; ++i) { 30 | key = children[i].key 31 | if (isDef(key)) map[key] = i 32 | } 33 | return map 34 | } 35 | 36 | function emptyNodeAt (elm) { 37 | return new VNode(nodeOps.tagName(elm).toLowerCase(), {}, [], undefined, elm) 38 | } 39 | 40 | function removeNode (el) { 41 | const parent = nodeOps.parentNode(el) 42 | if (parent) { 43 | nodeOps.removeChild(parent, el) 44 | } 45 | } 46 | 47 | function createElm (vnode, parentElm, refElm) { 48 | const children = vnode.children 49 | const tag = vnode.tag 50 | if (isDef(tag)) { 51 | vnode.elm = nodeOps.createElement(tag) 52 | 53 | createChildren(vnode, children) 54 | 55 | // 属性 56 | updateAttrs(emptyNode, vnode) 57 | updateClass(emptyNode, vnode) 58 | updateDOMProps(emptyNode, vnode) 59 | updateDOMListeners(emptyNode, vnode) 60 | 61 | insert(parentElm, vnode.elm, refElm) 62 | } else { // 文本节点 63 | vnode.elm = nodeOps.createTextNode(vnode.text) 64 | insert(parentElm, vnode.elm, refElm) 65 | } 66 | } 67 | 68 | function insert (parent, elm, ref) { 69 | if (parent) { 70 | if (ref) { 71 | nodeOps.insertBefore(parent, elm, ref) 72 | } else { 73 | nodeOps.appendChild(parent, elm) 74 | } 75 | } 76 | } 77 | 78 | function createChildren (vnode, children) { 79 | if (Array.isArray(children)) { 80 | for (let i = 0; i < children.length; ++i) { 81 | createElm(children[i], vnode.elm, null) 82 | } 83 | } 84 | } 85 | 86 | function addVnodes (parentElm, refElm, vnodes, startIdx, endIdx) { 87 | for (; startIdx <= endIdx; ++startIdx) { 88 | createElm(vnodes[startIdx], parentElm, refElm) 89 | } 90 | } 91 | 92 | function removeVnodes (parentElm, vnodes, startIdx, endIdx) { 93 | for (; startIdx <= endIdx; ++startIdx) { 94 | const ch = vnodes[startIdx] 95 | if (isDef(ch)) { 96 | removeNode(ch.elm) 97 | } 98 | } 99 | } 100 | 101 | function updateChildren (parentElm, oldCh, newCh, removeOnly) { 102 | let oldStartIdx = 0 103 | let newStartIdx = 0 104 | let oldEndIdx = oldCh.length - 1 105 | let oldStartVnode = oldCh[0] 106 | let oldEndVnode = oldCh[oldEndIdx] 107 | let newEndIdx = newCh.length - 1 108 | let newStartVnode = newCh[0] 109 | let newEndVnode = newCh[newEndIdx] 110 | let oldKeyToIdx, idxInOld, elmToMove, refElm 111 | 112 | const canMove = !removeOnly 113 | 114 | while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) { 115 | if (isUndef(oldStartVnode)) { 116 | oldStartVnode = oldCh[++oldStartIdx] 117 | } else if (isUndef(oldEndVnode)) { 118 | oldEndVnode = oldCh[--oldEndIdx] 119 | } else if (sameVnode(oldStartVnode, newStartVnode)) { 120 | patchVnode(oldStartVnode, newStartVnode) 121 | oldStartVnode = oldCh[++oldStartIdx] 122 | newStartVnode = newCh[++newStartIdx] 123 | } else if (sameVnode(oldEndVnode, newEndVnode)) { 124 | patchVnode(oldEndVnode, newEndVnode) 125 | oldEndVnode = oldCh[--oldEndIdx] 126 | newEndVnode = newCh[--newEndIdx] 127 | } else if (sameVnode(oldStartVnode, newEndVnode)) { 128 | patchVnode(oldStartVnode, newEndVnode) 129 | canMove && nodeOps.insertBefore(parentElm, oldStartVnode.elm, nodeOps.nextSibling(oldEndVnode.elm)) 130 | oldStartVnode = oldCh[++oldStartIdx] 131 | newEndVnode = newCh[--newEndIdx] 132 | } else if (sameVnode(oldEndVnode, newStartVnode)) { 133 | patchVnode(oldEndVnode, newStartVnode) 134 | canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm) 135 | oldEndVnode = oldCh[--oldEndIdx] 136 | newStartVnode = newCh[++newStartIdx] 137 | } else { 138 | if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx) 139 | idxInOld = isDef(newStartVnode.key) ? oldKeyToIdx[newStartVnode.key] : null 140 | if (isUndef(idxInOld)) { // 当前元素在旧VNode里边找不到相同的key 141 | createElm(newStartVnode, parentElm, oldStartVnode.elm) 142 | newStartVnode = newCh[++newStartIdx] 143 | } else { 144 | elmToMove = oldCh[idxInOld] // 找到同key的元素 145 | 146 | if (!elmToMove) { 147 | warn( 148 | 'It seems there are duplicate keys that is causing an update error. ' + 149 | 'Make sure each v-for item has a unique key.' 150 | ) 151 | } 152 | if (sameVnode(elmToMove, newStartVnode)) { 153 | patchVnode(elmToMove, newStartVnode) // 先patch这个节点 154 | oldCh[idxInOld] = undefined 155 | canMove && nodeOps.insertBefore(parentElm, newStartVnode.elm, oldStartVnode.elm) // 然后开始移动 156 | newStartVnode = newCh[++newStartIdx] 157 | } else { 158 | // 虽然是同个key,但是标签等不一致。同样不能复用 159 | createElm(newStartVnode, parentElm, oldStartVnode.elm) 160 | newStartVnode = newCh[++newStartIdx] 161 | } 162 | } 163 | } 164 | } 165 | if (oldStartIdx > oldEndIdx) { 166 | refElm = isUndef(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1].elm 167 | addVnodes(parentElm, refElm, newCh, newStartIdx, newEndIdx) 168 | } else if (newStartIdx > newEndIdx) { 169 | removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx) 170 | } 171 | } 172 | 173 | function patchVnode (oldVnode, vnode, removeOnly) { 174 | if (oldVnode === vnode) { 175 | return 176 | } 177 | 178 | const data = vnode.data 179 | const hasData = isDef(data) 180 | const elm = vnode.elm = oldVnode.elm 181 | const oldCh = oldVnode.children 182 | const ch = vnode.children 183 | 184 | // 更新属性 185 | if (hasData) { 186 | updateAttrs(oldVnode, vnode) 187 | updateClass(oldVnode, vnode) 188 | updateDOMProps(oldVnode, vnode) 189 | updateDOMListeners(oldVnode, vnode) 190 | } 191 | 192 | if (isUndef(vnode.text)) { 193 | if (isDef(oldCh) && isDef(ch)) { 194 | if (oldCh !== ch) updateChildren(elm, oldCh, ch, removeOnly) 195 | } else if (isDef(ch)) { 196 | if (isDef(oldVnode.text)) nodeOps.setTextContent(elm, '') 197 | addVnodes(elm, null, ch, 0, ch.length - 1) 198 | } else if (isDef(oldCh)) { 199 | removeVnodes(elm, oldCh, 0, oldCh.length - 1) 200 | } else if (isDef(oldVnode.text)) { 201 | nodeOps.setTextContent(elm, '') 202 | } 203 | } else if (oldVnode.text !== vnode.text) { 204 | nodeOps.setTextContent(elm, vnode.text) 205 | } 206 | } 207 | 208 | export default function patch (oldVnode, vnode) { 209 | let isInitialPatch = false 210 | 211 | const isRealElement = isDef(oldVnode.nodeType) 212 | if (!isRealElement && sameVnode(oldVnode, vnode)) {// 如果两个vnode节点根一致 213 | patchVnode(oldVnode, vnode) 214 | } else { 215 | if (isRealElement) { 216 | oldVnode = emptyNodeAt(oldVnode) 217 | } 218 | //既然到了这里 就说明两个vnode的dom的根节点不一样 219 | //只是拿到原来的dom的容器parentElm,把当前vnode的所有dom生成进去 220 | //然后把以前的oldVnode全部移除掉 221 | const oldElm = oldVnode.elm 222 | const parentElm = nodeOps.parentNode(oldElm) 223 | createElm( 224 | vnode, 225 | parentElm, 226 | nodeOps.nextSibling(oldElm) 227 | ) 228 | 229 | if (parentElm !== null) { 230 | removeVnodes(parentElm, [oldVnode], 0, 0) 231 | } 232 | } 233 | 234 | return vnode.elm 235 | } 236 | -------------------------------------------------------------------------------- /examples/2.1/todo/node_modules/todomvc-common/base.js: -------------------------------------------------------------------------------- 1 | /* global _ */ 2 | (function () { 3 | 'use strict'; 4 | 5 | /* jshint ignore:start */ 6 | // Underscore's Template Module 7 | // Courtesy of underscorejs.org 8 | var _ = (function (_) { 9 | _.defaults = function (object) { 10 | if (!object) { 11 | return object; 12 | } 13 | for (var argsIndex = 1, argsLength = arguments.length; argsIndex < argsLength; argsIndex++) { 14 | var iterable = arguments[argsIndex]; 15 | if (iterable) { 16 | for (var key in iterable) { 17 | if (object[key] == null) { 18 | object[key] = iterable[key]; 19 | } 20 | } 21 | } 22 | } 23 | return object; 24 | } 25 | 26 | // By default, Underscore uses ERB-style template delimiters, change the 27 | // following template settings to use alternative delimiters. 28 | _.templateSettings = { 29 | evaluate : /<%([\s\S]+?)%>/g, 30 | interpolate : /<%=([\s\S]+?)%>/g, 31 | escape : /<%-([\s\S]+?)%>/g 32 | }; 33 | 34 | // When customizing `templateSettings`, if you don't want to define an 35 | // interpolation, evaluation or escaping regex, we need one that is 36 | // guaranteed not to match. 37 | var noMatch = /(.)^/; 38 | 39 | // Certain characters need to be escaped so that they can be put into a 40 | // string literal. 41 | var escapes = { 42 | "'": "'", 43 | '\\': '\\', 44 | '\r': 'r', 45 | '\n': 'n', 46 | '\t': 't', 47 | '\u2028': 'u2028', 48 | '\u2029': 'u2029' 49 | }; 50 | 51 | var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g; 52 | 53 | // JavaScript micro-templating, similar to John Resig's implementation. 54 | // Underscore templating handles arbitrary delimiters, preserves whitespace, 55 | // and correctly escapes quotes within interpolated code. 56 | _.template = function(text, data, settings) { 57 | var render; 58 | settings = _.defaults({}, settings, _.templateSettings); 59 | 60 | // Combine delimiters into one regular expression via alternation. 61 | var matcher = new RegExp([ 62 | (settings.escape || noMatch).source, 63 | (settings.interpolate || noMatch).source, 64 | (settings.evaluate || noMatch).source 65 | ].join('|') + '|$', 'g'); 66 | 67 | // Compile the template source, escaping string literals appropriately. 68 | var index = 0; 69 | var source = "__p+='"; 70 | text.replace(matcher, function(match, escape, interpolate, evaluate, offset) { 71 | source += text.slice(index, offset) 72 | .replace(escaper, function(match) { return '\\' + escapes[match]; }); 73 | 74 | if (escape) { 75 | source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'"; 76 | } 77 | if (interpolate) { 78 | source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'"; 79 | } 80 | if (evaluate) { 81 | source += "';\n" + evaluate + "\n__p+='"; 82 | } 83 | index = offset + match.length; 84 | return match; 85 | }); 86 | source += "';\n"; 87 | 88 | // If a variable is not specified, place data values in local scope. 89 | if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n'; 90 | 91 | source = "var __t,__p='',__j=Array.prototype.join," + 92 | "print=function(){__p+=__j.call(arguments,'');};\n" + 93 | source + "return __p;\n"; 94 | 95 | try { 96 | render = new Function(settings.variable || 'obj', '_', source); 97 | } catch (e) { 98 | e.source = source; 99 | throw e; 100 | } 101 | 102 | if (data) return render(data, _); 103 | var template = function(data) { 104 | return render.call(this, data, _); 105 | }; 106 | 107 | // Provide the compiled function source as a convenience for precompilation. 108 | template.source = 'function(' + (settings.variable || 'obj') + '){\n' + source + '}'; 109 | 110 | return template; 111 | }; 112 | 113 | return _; 114 | })({}); 115 | 116 | if (location.hostname === 'todomvc.com') { 117 | (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ 118 | (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), 119 | m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) 120 | })(window,document,'script','https://www.google-analytics.com/analytics.js','ga'); 121 | ga('create', 'UA-31081062-1', 'auto'); 122 | ga('send', 'pageview'); 123 | } 124 | /* jshint ignore:end */ 125 | 126 | function redirect() { 127 | if (location.hostname === 'tastejs.github.io') { 128 | location.href = location.href.replace('tastejs.github.io/todomvc', 'todomvc.com'); 129 | } 130 | } 131 | 132 | function findRoot() { 133 | var base = location.href.indexOf('examples/'); 134 | return location.href.substr(0, base); 135 | } 136 | 137 | function getFile(file, callback) { 138 | if (!location.host) { 139 | return console.info('Miss the info bar? Run TodoMVC from a server to avoid a cross-origin error.'); 140 | } 141 | 142 | var xhr = new XMLHttpRequest(); 143 | 144 | xhr.open('GET', findRoot() + file, true); 145 | xhr.send(); 146 | 147 | xhr.onload = function () { 148 | if (xhr.status === 200 && callback) { 149 | callback(xhr.responseText); 150 | } 151 | }; 152 | } 153 | 154 | function Learn(learnJSON, config) { 155 | if (!(this instanceof Learn)) { 156 | return new Learn(learnJSON, config); 157 | } 158 | 159 | var template, framework; 160 | 161 | if (typeof learnJSON !== 'object') { 162 | try { 163 | learnJSON = JSON.parse(learnJSON); 164 | } catch (e) { 165 | return; 166 | } 167 | } 168 | 169 | if (config) { 170 | template = config.template; 171 | framework = config.framework; 172 | } 173 | 174 | if (!template && learnJSON.templates) { 175 | template = learnJSON.templates.todomvc; 176 | } 177 | 178 | if (!framework && document.querySelector('[data-framework]')) { 179 | framework = document.querySelector('[data-framework]').dataset.framework; 180 | } 181 | 182 | this.template = template; 183 | 184 | if (learnJSON.backend) { 185 | this.frameworkJSON = learnJSON.backend; 186 | this.frameworkJSON.issueLabel = framework; 187 | this.append({ 188 | backend: true 189 | }); 190 | } else if (learnJSON[framework]) { 191 | this.frameworkJSON = learnJSON[framework]; 192 | this.frameworkJSON.issueLabel = framework; 193 | this.append(); 194 | } 195 | 196 | this.fetchIssueCount(); 197 | } 198 | 199 | Learn.prototype.append = function (opts) { 200 | var aside = document.createElement('aside'); 201 | aside.innerHTML = _.template(this.template, this.frameworkJSON); 202 | aside.className = 'learn'; 203 | 204 | if (opts && opts.backend) { 205 | // Remove demo link 206 | var sourceLinks = aside.querySelector('.source-links'); 207 | var heading = sourceLinks.firstElementChild; 208 | var sourceLink = sourceLinks.lastElementChild; 209 | // Correct link path 210 | var href = sourceLink.getAttribute('href'); 211 | sourceLink.setAttribute('href', href.substr(href.lastIndexOf('http'))); 212 | sourceLinks.innerHTML = heading.outerHTML + sourceLink.outerHTML; 213 | } else { 214 | // Localize demo links 215 | var demoLinks = aside.querySelectorAll('.demo-link'); 216 | Array.prototype.forEach.call(demoLinks, function (demoLink) { 217 | if (demoLink.getAttribute('href').substr(0, 4) !== 'http') { 218 | demoLink.setAttribute('href', findRoot() + demoLink.getAttribute('href')); 219 | } 220 | }); 221 | } 222 | 223 | document.body.className = (document.body.className + ' learn-bar').trim(); 224 | document.body.insertAdjacentHTML('afterBegin', aside.outerHTML); 225 | }; 226 | 227 | Learn.prototype.fetchIssueCount = function () { 228 | var issueLink = document.getElementById('issue-count-link'); 229 | if (issueLink) { 230 | var url = issueLink.href.replace('https://github.com', 'https://api.github.com/repos'); 231 | var xhr = new XMLHttpRequest(); 232 | xhr.open('GET', url, true); 233 | xhr.onload = function (e) { 234 | var parsedResponse = JSON.parse(e.target.responseText); 235 | if (parsedResponse instanceof Array) { 236 | var count = parsedResponse.length; 237 | if (count !== 0) { 238 | issueLink.innerHTML = 'This app has ' + count + ' open issues'; 239 | document.getElementById('issue-count').style.display = 'inline'; 240 | } 241 | } 242 | }; 243 | xhr.send(); 244 | } 245 | }; 246 | 247 | redirect(); 248 | getFile('learn.json', Learn); 249 | })(); 250 | -------------------------------------------------------------------------------- /examples/2.2.1/todo/node_modules/todomvc-common/base.js: -------------------------------------------------------------------------------- 1 | /* global _ */ 2 | (function () { 3 | 'use strict'; 4 | 5 | /* jshint ignore:start */ 6 | // Underscore's Template Module 7 | // Courtesy of underscorejs.org 8 | var _ = (function (_) { 9 | _.defaults = function (object) { 10 | if (!object) { 11 | return object; 12 | } 13 | for (var argsIndex = 1, argsLength = arguments.length; argsIndex < argsLength; argsIndex++) { 14 | var iterable = arguments[argsIndex]; 15 | if (iterable) { 16 | for (var key in iterable) { 17 | if (object[key] == null) { 18 | object[key] = iterable[key]; 19 | } 20 | } 21 | } 22 | } 23 | return object; 24 | } 25 | 26 | // By default, Underscore uses ERB-style template delimiters, change the 27 | // following template settings to use alternative delimiters. 28 | _.templateSettings = { 29 | evaluate : /<%([\s\S]+?)%>/g, 30 | interpolate : /<%=([\s\S]+?)%>/g, 31 | escape : /<%-([\s\S]+?)%>/g 32 | }; 33 | 34 | // When customizing `templateSettings`, if you don't want to define an 35 | // interpolation, evaluation or escaping regex, we need one that is 36 | // guaranteed not to match. 37 | var noMatch = /(.)^/; 38 | 39 | // Certain characters need to be escaped so that they can be put into a 40 | // string literal. 41 | var escapes = { 42 | "'": "'", 43 | '\\': '\\', 44 | '\r': 'r', 45 | '\n': 'n', 46 | '\t': 't', 47 | '\u2028': 'u2028', 48 | '\u2029': 'u2029' 49 | }; 50 | 51 | var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g; 52 | 53 | // JavaScript micro-templating, similar to John Resig's implementation. 54 | // Underscore templating handles arbitrary delimiters, preserves whitespace, 55 | // and correctly escapes quotes within interpolated code. 56 | _.template = function(text, data, settings) { 57 | var render; 58 | settings = _.defaults({}, settings, _.templateSettings); 59 | 60 | // Combine delimiters into one regular expression via alternation. 61 | var matcher = new RegExp([ 62 | (settings.escape || noMatch).source, 63 | (settings.interpolate || noMatch).source, 64 | (settings.evaluate || noMatch).source 65 | ].join('|') + '|$', 'g'); 66 | 67 | // Compile the template source, escaping string literals appropriately. 68 | var index = 0; 69 | var source = "__p+='"; 70 | text.replace(matcher, function(match, escape, interpolate, evaluate, offset) { 71 | source += text.slice(index, offset) 72 | .replace(escaper, function(match) { return '\\' + escapes[match]; }); 73 | 74 | if (escape) { 75 | source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'"; 76 | } 77 | if (interpolate) { 78 | source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'"; 79 | } 80 | if (evaluate) { 81 | source += "';\n" + evaluate + "\n__p+='"; 82 | } 83 | index = offset + match.length; 84 | return match; 85 | }); 86 | source += "';\n"; 87 | 88 | // If a variable is not specified, place data values in local scope. 89 | if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n'; 90 | 91 | source = "var __t,__p='',__j=Array.prototype.join," + 92 | "print=function(){__p+=__j.call(arguments,'');};\n" + 93 | source + "return __p;\n"; 94 | 95 | try { 96 | render = new Function(settings.variable || 'obj', '_', source); 97 | } catch (e) { 98 | e.source = source; 99 | throw e; 100 | } 101 | 102 | if (data) return render(data, _); 103 | var template = function(data) { 104 | return render.call(this, data, _); 105 | }; 106 | 107 | // Provide the compiled function source as a convenience for precompilation. 108 | template.source = 'function(' + (settings.variable || 'obj') + '){\n' + source + '}'; 109 | 110 | return template; 111 | }; 112 | 113 | return _; 114 | })({}); 115 | 116 | if (location.hostname === 'todomvc.com') { 117 | (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ 118 | (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), 119 | m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) 120 | })(window,document,'script','https://www.google-analytics.com/analytics.js','ga'); 121 | ga('create', 'UA-31081062-1', 'auto'); 122 | ga('send', 'pageview'); 123 | } 124 | /* jshint ignore:end */ 125 | 126 | function redirect() { 127 | if (location.hostname === 'tastejs.github.io') { 128 | location.href = location.href.replace('tastejs.github.io/todomvc', 'todomvc.com'); 129 | } 130 | } 131 | 132 | function findRoot() { 133 | var base = location.href.indexOf('examples/'); 134 | return location.href.substr(0, base); 135 | } 136 | 137 | function getFile(file, callback) { 138 | if (!location.host) { 139 | return console.info('Miss the info bar? Run TodoMVC from a server to avoid a cross-origin error.'); 140 | } 141 | 142 | var xhr = new XMLHttpRequest(); 143 | 144 | xhr.open('GET', findRoot() + file, true); 145 | xhr.send(); 146 | 147 | xhr.onload = function () { 148 | if (xhr.status === 200 && callback) { 149 | callback(xhr.responseText); 150 | } 151 | }; 152 | } 153 | 154 | function Learn(learnJSON, config) { 155 | if (!(this instanceof Learn)) { 156 | return new Learn(learnJSON, config); 157 | } 158 | 159 | var template, framework; 160 | 161 | if (typeof learnJSON !== 'object') { 162 | try { 163 | learnJSON = JSON.parse(learnJSON); 164 | } catch (e) { 165 | return; 166 | } 167 | } 168 | 169 | if (config) { 170 | template = config.template; 171 | framework = config.framework; 172 | } 173 | 174 | if (!template && learnJSON.templates) { 175 | template = learnJSON.templates.todomvc; 176 | } 177 | 178 | if (!framework && document.querySelector('[data-framework]')) { 179 | framework = document.querySelector('[data-framework]').dataset.framework; 180 | } 181 | 182 | this.template = template; 183 | 184 | if (learnJSON.backend) { 185 | this.frameworkJSON = learnJSON.backend; 186 | this.frameworkJSON.issueLabel = framework; 187 | this.append({ 188 | backend: true 189 | }); 190 | } else if (learnJSON[framework]) { 191 | this.frameworkJSON = learnJSON[framework]; 192 | this.frameworkJSON.issueLabel = framework; 193 | this.append(); 194 | } 195 | 196 | this.fetchIssueCount(); 197 | } 198 | 199 | Learn.prototype.append = function (opts) { 200 | var aside = document.createElement('aside'); 201 | aside.innerHTML = _.template(this.template, this.frameworkJSON); 202 | aside.className = 'learn'; 203 | 204 | if (opts && opts.backend) { 205 | // Remove demo link 206 | var sourceLinks = aside.querySelector('.source-links'); 207 | var heading = sourceLinks.firstElementChild; 208 | var sourceLink = sourceLinks.lastElementChild; 209 | // Correct link path 210 | var href = sourceLink.getAttribute('href'); 211 | sourceLink.setAttribute('href', href.substr(href.lastIndexOf('http'))); 212 | sourceLinks.innerHTML = heading.outerHTML + sourceLink.outerHTML; 213 | } else { 214 | // Localize demo links 215 | var demoLinks = aside.querySelectorAll('.demo-link'); 216 | Array.prototype.forEach.call(demoLinks, function (demoLink) { 217 | if (demoLink.getAttribute('href').substr(0, 4) !== 'http') { 218 | demoLink.setAttribute('href', findRoot() + demoLink.getAttribute('href')); 219 | } 220 | }); 221 | } 222 | 223 | document.body.className = (document.body.className + ' learn-bar').trim(); 224 | document.body.insertAdjacentHTML('afterBegin', aside.outerHTML); 225 | }; 226 | 227 | Learn.prototype.fetchIssueCount = function () { 228 | var issueLink = document.getElementById('issue-count-link'); 229 | if (issueLink) { 230 | var url = issueLink.href.replace('https://github.com', 'https://api.github.com/repos'); 231 | var xhr = new XMLHttpRequest(); 232 | xhr.open('GET', url, true); 233 | xhr.onload = function (e) { 234 | var parsedResponse = JSON.parse(e.target.responseText); 235 | if (parsedResponse instanceof Array) { 236 | var count = parsedResponse.length; 237 | if (count !== 0) { 238 | issueLink.innerHTML = 'This app has ' + count + ' open issues'; 239 | document.getElementById('issue-count').style.display = 'inline'; 240 | } 241 | } 242 | }; 243 | xhr.send(); 244 | } 245 | }; 246 | 247 | redirect(); 248 | getFile('learn.json', Learn); 249 | })(); 250 | -------------------------------------------------------------------------------- /examples/2.2.2/todo/node_modules/todomvc-common/base.js: -------------------------------------------------------------------------------- 1 | /* global _ */ 2 | (function () { 3 | 'use strict'; 4 | 5 | /* jshint ignore:start */ 6 | // Underscore's Template Module 7 | // Courtesy of underscorejs.org 8 | var _ = (function (_) { 9 | _.defaults = function (object) { 10 | if (!object) { 11 | return object; 12 | } 13 | for (var argsIndex = 1, argsLength = arguments.length; argsIndex < argsLength; argsIndex++) { 14 | var iterable = arguments[argsIndex]; 15 | if (iterable) { 16 | for (var key in iterable) { 17 | if (object[key] == null) { 18 | object[key] = iterable[key]; 19 | } 20 | } 21 | } 22 | } 23 | return object; 24 | } 25 | 26 | // By default, Underscore uses ERB-style template delimiters, change the 27 | // following template settings to use alternative delimiters. 28 | _.templateSettings = { 29 | evaluate : /<%([\s\S]+?)%>/g, 30 | interpolate : /<%=([\s\S]+?)%>/g, 31 | escape : /<%-([\s\S]+?)%>/g 32 | }; 33 | 34 | // When customizing `templateSettings`, if you don't want to define an 35 | // interpolation, evaluation or escaping regex, we need one that is 36 | // guaranteed not to match. 37 | var noMatch = /(.)^/; 38 | 39 | // Certain characters need to be escaped so that they can be put into a 40 | // string literal. 41 | var escapes = { 42 | "'": "'", 43 | '\\': '\\', 44 | '\r': 'r', 45 | '\n': 'n', 46 | '\t': 't', 47 | '\u2028': 'u2028', 48 | '\u2029': 'u2029' 49 | }; 50 | 51 | var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g; 52 | 53 | // JavaScript micro-templating, similar to John Resig's implementation. 54 | // Underscore templating handles arbitrary delimiters, preserves whitespace, 55 | // and correctly escapes quotes within interpolated code. 56 | _.template = function(text, data, settings) { 57 | var render; 58 | settings = _.defaults({}, settings, _.templateSettings); 59 | 60 | // Combine delimiters into one regular expression via alternation. 61 | var matcher = new RegExp([ 62 | (settings.escape || noMatch).source, 63 | (settings.interpolate || noMatch).source, 64 | (settings.evaluate || noMatch).source 65 | ].join('|') + '|$', 'g'); 66 | 67 | // Compile the template source, escaping string literals appropriately. 68 | var index = 0; 69 | var source = "__p+='"; 70 | text.replace(matcher, function(match, escape, interpolate, evaluate, offset) { 71 | source += text.slice(index, offset) 72 | .replace(escaper, function(match) { return '\\' + escapes[match]; }); 73 | 74 | if (escape) { 75 | source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'"; 76 | } 77 | if (interpolate) { 78 | source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'"; 79 | } 80 | if (evaluate) { 81 | source += "';\n" + evaluate + "\n__p+='"; 82 | } 83 | index = offset + match.length; 84 | return match; 85 | }); 86 | source += "';\n"; 87 | 88 | // If a variable is not specified, place data values in local scope. 89 | if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n'; 90 | 91 | source = "var __t,__p='',__j=Array.prototype.join," + 92 | "print=function(){__p+=__j.call(arguments,'');};\n" + 93 | source + "return __p;\n"; 94 | 95 | try { 96 | render = new Function(settings.variable || 'obj', '_', source); 97 | } catch (e) { 98 | e.source = source; 99 | throw e; 100 | } 101 | 102 | if (data) return render(data, _); 103 | var template = function(data) { 104 | return render.call(this, data, _); 105 | }; 106 | 107 | // Provide the compiled function source as a convenience for precompilation. 108 | template.source = 'function(' + (settings.variable || 'obj') + '){\n' + source + '}'; 109 | 110 | return template; 111 | }; 112 | 113 | return _; 114 | })({}); 115 | 116 | if (location.hostname === 'todomvc.com') { 117 | (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ 118 | (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), 119 | m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) 120 | })(window,document,'script','https://www.google-analytics.com/analytics.js','ga'); 121 | ga('create', 'UA-31081062-1', 'auto'); 122 | ga('send', 'pageview'); 123 | } 124 | /* jshint ignore:end */ 125 | 126 | function redirect() { 127 | if (location.hostname === 'tastejs.github.io') { 128 | location.href = location.href.replace('tastejs.github.io/todomvc', 'todomvc.com'); 129 | } 130 | } 131 | 132 | function findRoot() { 133 | var base = location.href.indexOf('examples/'); 134 | return location.href.substr(0, base); 135 | } 136 | 137 | function getFile(file, callback) { 138 | if (!location.host) { 139 | return console.info('Miss the info bar? Run TodoMVC from a server to avoid a cross-origin error.'); 140 | } 141 | 142 | var xhr = new XMLHttpRequest(); 143 | 144 | xhr.open('GET', findRoot() + file, true); 145 | xhr.send(); 146 | 147 | xhr.onload = function () { 148 | if (xhr.status === 200 && callback) { 149 | callback(xhr.responseText); 150 | } 151 | }; 152 | } 153 | 154 | function Learn(learnJSON, config) { 155 | if (!(this instanceof Learn)) { 156 | return new Learn(learnJSON, config); 157 | } 158 | 159 | var template, framework; 160 | 161 | if (typeof learnJSON !== 'object') { 162 | try { 163 | learnJSON = JSON.parse(learnJSON); 164 | } catch (e) { 165 | return; 166 | } 167 | } 168 | 169 | if (config) { 170 | template = config.template; 171 | framework = config.framework; 172 | } 173 | 174 | if (!template && learnJSON.templates) { 175 | template = learnJSON.templates.todomvc; 176 | } 177 | 178 | if (!framework && document.querySelector('[data-framework]')) { 179 | framework = document.querySelector('[data-framework]').dataset.framework; 180 | } 181 | 182 | this.template = template; 183 | 184 | if (learnJSON.backend) { 185 | this.frameworkJSON = learnJSON.backend; 186 | this.frameworkJSON.issueLabel = framework; 187 | this.append({ 188 | backend: true 189 | }); 190 | } else if (learnJSON[framework]) { 191 | this.frameworkJSON = learnJSON[framework]; 192 | this.frameworkJSON.issueLabel = framework; 193 | this.append(); 194 | } 195 | 196 | this.fetchIssueCount(); 197 | } 198 | 199 | Learn.prototype.append = function (opts) { 200 | var aside = document.createElement('aside'); 201 | aside.innerHTML = _.template(this.template, this.frameworkJSON); 202 | aside.className = 'learn'; 203 | 204 | if (opts && opts.backend) { 205 | // Remove demo link 206 | var sourceLinks = aside.querySelector('.source-links'); 207 | var heading = sourceLinks.firstElementChild; 208 | var sourceLink = sourceLinks.lastElementChild; 209 | // Correct link path 210 | var href = sourceLink.getAttribute('href'); 211 | sourceLink.setAttribute('href', href.substr(href.lastIndexOf('http'))); 212 | sourceLinks.innerHTML = heading.outerHTML + sourceLink.outerHTML; 213 | } else { 214 | // Localize demo links 215 | var demoLinks = aside.querySelectorAll('.demo-link'); 216 | Array.prototype.forEach.call(demoLinks, function (demoLink) { 217 | if (demoLink.getAttribute('href').substr(0, 4) !== 'http') { 218 | demoLink.setAttribute('href', findRoot() + demoLink.getAttribute('href')); 219 | } 220 | }); 221 | } 222 | 223 | document.body.className = (document.body.className + ' learn-bar').trim(); 224 | document.body.insertAdjacentHTML('afterBegin', aside.outerHTML); 225 | }; 226 | 227 | Learn.prototype.fetchIssueCount = function () { 228 | var issueLink = document.getElementById('issue-count-link'); 229 | if (issueLink) { 230 | var url = issueLink.href.replace('https://github.com', 'https://api.github.com/repos'); 231 | var xhr = new XMLHttpRequest(); 232 | xhr.open('GET', url, true); 233 | xhr.onload = function (e) { 234 | var parsedResponse = JSON.parse(e.target.responseText); 235 | if (parsedResponse instanceof Array) { 236 | var count = parsedResponse.length; 237 | if (count !== 0) { 238 | issueLink.innerHTML = 'This app has ' + count + ' open issues'; 239 | document.getElementById('issue-count').style.display = 'inline'; 240 | } 241 | } 242 | }; 243 | xhr.send(); 244 | } 245 | }; 246 | 247 | redirect(); 248 | getFile('learn.json', Learn); 249 | })(); 250 | -------------------------------------------------------------------------------- /examples/2.3.1/todo/node_modules/todomvc-common/base.js: -------------------------------------------------------------------------------- 1 | /* global _ */ 2 | (function () { 3 | 'use strict'; 4 | 5 | /* jshint ignore:start */ 6 | // Underscore's Template Module 7 | // Courtesy of underscorejs.org 8 | var _ = (function (_) { 9 | _.defaults = function (object) { 10 | if (!object) { 11 | return object; 12 | } 13 | for (var argsIndex = 1, argsLength = arguments.length; argsIndex < argsLength; argsIndex++) { 14 | var iterable = arguments[argsIndex]; 15 | if (iterable) { 16 | for (var key in iterable) { 17 | if (object[key] == null) { 18 | object[key] = iterable[key]; 19 | } 20 | } 21 | } 22 | } 23 | return object; 24 | } 25 | 26 | // By default, Underscore uses ERB-style template delimiters, change the 27 | // following template settings to use alternative delimiters. 28 | _.templateSettings = { 29 | evaluate : /<%([\s\S]+?)%>/g, 30 | interpolate : /<%=([\s\S]+?)%>/g, 31 | escape : /<%-([\s\S]+?)%>/g 32 | }; 33 | 34 | // When customizing `templateSettings`, if you don't want to define an 35 | // interpolation, evaluation or escaping regex, we need one that is 36 | // guaranteed not to match. 37 | var noMatch = /(.)^/; 38 | 39 | // Certain characters need to be escaped so that they can be put into a 40 | // string literal. 41 | var escapes = { 42 | "'": "'", 43 | '\\': '\\', 44 | '\r': 'r', 45 | '\n': 'n', 46 | '\t': 't', 47 | '\u2028': 'u2028', 48 | '\u2029': 'u2029' 49 | }; 50 | 51 | var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g; 52 | 53 | // JavaScript micro-templating, similar to John Resig's implementation. 54 | // Underscore templating handles arbitrary delimiters, preserves whitespace, 55 | // and correctly escapes quotes within interpolated code. 56 | _.template = function(text, data, settings) { 57 | var render; 58 | settings = _.defaults({}, settings, _.templateSettings); 59 | 60 | // Combine delimiters into one regular expression via alternation. 61 | var matcher = new RegExp([ 62 | (settings.escape || noMatch).source, 63 | (settings.interpolate || noMatch).source, 64 | (settings.evaluate || noMatch).source 65 | ].join('|') + '|$', 'g'); 66 | 67 | // Compile the template source, escaping string literals appropriately. 68 | var index = 0; 69 | var source = "__p+='"; 70 | text.replace(matcher, function(match, escape, interpolate, evaluate, offset) { 71 | source += text.slice(index, offset) 72 | .replace(escaper, function(match) { return '\\' + escapes[match]; }); 73 | 74 | if (escape) { 75 | source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'"; 76 | } 77 | if (interpolate) { 78 | source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'"; 79 | } 80 | if (evaluate) { 81 | source += "';\n" + evaluate + "\n__p+='"; 82 | } 83 | index = offset + match.length; 84 | return match; 85 | }); 86 | source += "';\n"; 87 | 88 | // If a variable is not specified, place data values in local scope. 89 | if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n'; 90 | 91 | source = "var __t,__p='',__j=Array.prototype.join," + 92 | "print=function(){__p+=__j.call(arguments,'');};\n" + 93 | source + "return __p;\n"; 94 | 95 | try { 96 | render = new Function(settings.variable || 'obj', '_', source); 97 | } catch (e) { 98 | e.source = source; 99 | throw e; 100 | } 101 | 102 | if (data) return render(data, _); 103 | var template = function(data) { 104 | return render.call(this, data, _); 105 | }; 106 | 107 | // Provide the compiled function source as a convenience for precompilation. 108 | template.source = 'function(' + (settings.variable || 'obj') + '){\n' + source + '}'; 109 | 110 | return template; 111 | }; 112 | 113 | return _; 114 | })({}); 115 | 116 | if (location.hostname === 'todomvc.com') { 117 | (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ 118 | (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), 119 | m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) 120 | })(window,document,'script','https://www.google-analytics.com/analytics.js','ga'); 121 | ga('create', 'UA-31081062-1', 'auto'); 122 | ga('send', 'pageview'); 123 | } 124 | /* jshint ignore:end */ 125 | 126 | function redirect() { 127 | if (location.hostname === 'tastejs.github.io') { 128 | location.href = location.href.replace('tastejs.github.io/todomvc', 'todomvc.com'); 129 | } 130 | } 131 | 132 | function findRoot() { 133 | var base = location.href.indexOf('examples/'); 134 | return location.href.substr(0, base); 135 | } 136 | 137 | function getFile(file, callback) { 138 | if (!location.host) { 139 | return console.info('Miss the info bar? Run TodoMVC from a server to avoid a cross-origin error.'); 140 | } 141 | 142 | var xhr = new XMLHttpRequest(); 143 | 144 | xhr.open('GET', findRoot() + file, true); 145 | xhr.send(); 146 | 147 | xhr.onload = function () { 148 | if (xhr.status === 200 && callback) { 149 | callback(xhr.responseText); 150 | } 151 | }; 152 | } 153 | 154 | function Learn(learnJSON, config) { 155 | if (!(this instanceof Learn)) { 156 | return new Learn(learnJSON, config); 157 | } 158 | 159 | var template, framework; 160 | 161 | if (typeof learnJSON !== 'object') { 162 | try { 163 | learnJSON = JSON.parse(learnJSON); 164 | } catch (e) { 165 | return; 166 | } 167 | } 168 | 169 | if (config) { 170 | template = config.template; 171 | framework = config.framework; 172 | } 173 | 174 | if (!template && learnJSON.templates) { 175 | template = learnJSON.templates.todomvc; 176 | } 177 | 178 | if (!framework && document.querySelector('[data-framework]')) { 179 | framework = document.querySelector('[data-framework]').dataset.framework; 180 | } 181 | 182 | this.template = template; 183 | 184 | if (learnJSON.backend) { 185 | this.frameworkJSON = learnJSON.backend; 186 | this.frameworkJSON.issueLabel = framework; 187 | this.append({ 188 | backend: true 189 | }); 190 | } else if (learnJSON[framework]) { 191 | this.frameworkJSON = learnJSON[framework]; 192 | this.frameworkJSON.issueLabel = framework; 193 | this.append(); 194 | } 195 | 196 | this.fetchIssueCount(); 197 | } 198 | 199 | Learn.prototype.append = function (opts) { 200 | var aside = document.createElement('aside'); 201 | aside.innerHTML = _.template(this.template, this.frameworkJSON); 202 | aside.className = 'learn'; 203 | 204 | if (opts && opts.backend) { 205 | // Remove demo link 206 | var sourceLinks = aside.querySelector('.source-links'); 207 | var heading = sourceLinks.firstElementChild; 208 | var sourceLink = sourceLinks.lastElementChild; 209 | // Correct link path 210 | var href = sourceLink.getAttribute('href'); 211 | sourceLink.setAttribute('href', href.substr(href.lastIndexOf('http'))); 212 | sourceLinks.innerHTML = heading.outerHTML + sourceLink.outerHTML; 213 | } else { 214 | // Localize demo links 215 | var demoLinks = aside.querySelectorAll('.demo-link'); 216 | Array.prototype.forEach.call(demoLinks, function (demoLink) { 217 | if (demoLink.getAttribute('href').substr(0, 4) !== 'http') { 218 | demoLink.setAttribute('href', findRoot() + demoLink.getAttribute('href')); 219 | } 220 | }); 221 | } 222 | 223 | document.body.className = (document.body.className + ' learn-bar').trim(); 224 | document.body.insertAdjacentHTML('afterBegin', aside.outerHTML); 225 | }; 226 | 227 | Learn.prototype.fetchIssueCount = function () { 228 | var issueLink = document.getElementById('issue-count-link'); 229 | if (issueLink) { 230 | var url = issueLink.href.replace('https://github.com', 'https://api.github.com/repos'); 231 | var xhr = new XMLHttpRequest(); 232 | xhr.open('GET', url, true); 233 | xhr.onload = function (e) { 234 | var parsedResponse = JSON.parse(e.target.responseText); 235 | if (parsedResponse instanceof Array) { 236 | var count = parsedResponse.length; 237 | if (count !== 0) { 238 | issueLink.innerHTML = 'This app has ' + count + ' open issues'; 239 | document.getElementById('issue-count').style.display = 'inline'; 240 | } 241 | } 242 | }; 243 | xhr.send(); 244 | } 245 | }; 246 | 247 | redirect(); 248 | getFile('learn.json', Learn); 249 | })(); 250 | -------------------------------------------------------------------------------- /examples/2.3.2/todo/node_modules/todomvc-common/base.js: -------------------------------------------------------------------------------- 1 | /* global _ */ 2 | (function () { 3 | 'use strict'; 4 | 5 | /* jshint ignore:start */ 6 | // Underscore's Template Module 7 | // Courtesy of underscorejs.org 8 | var _ = (function (_) { 9 | _.defaults = function (object) { 10 | if (!object) { 11 | return object; 12 | } 13 | for (var argsIndex = 1, argsLength = arguments.length; argsIndex < argsLength; argsIndex++) { 14 | var iterable = arguments[argsIndex]; 15 | if (iterable) { 16 | for (var key in iterable) { 17 | if (object[key] == null) { 18 | object[key] = iterable[key]; 19 | } 20 | } 21 | } 22 | } 23 | return object; 24 | } 25 | 26 | // By default, Underscore uses ERB-style template delimiters, change the 27 | // following template settings to use alternative delimiters. 28 | _.templateSettings = { 29 | evaluate : /<%([\s\S]+?)%>/g, 30 | interpolate : /<%=([\s\S]+?)%>/g, 31 | escape : /<%-([\s\S]+?)%>/g 32 | }; 33 | 34 | // When customizing `templateSettings`, if you don't want to define an 35 | // interpolation, evaluation or escaping regex, we need one that is 36 | // guaranteed not to match. 37 | var noMatch = /(.)^/; 38 | 39 | // Certain characters need to be escaped so that they can be put into a 40 | // string literal. 41 | var escapes = { 42 | "'": "'", 43 | '\\': '\\', 44 | '\r': 'r', 45 | '\n': 'n', 46 | '\t': 't', 47 | '\u2028': 'u2028', 48 | '\u2029': 'u2029' 49 | }; 50 | 51 | var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g; 52 | 53 | // JavaScript micro-templating, similar to John Resig's implementation. 54 | // Underscore templating handles arbitrary delimiters, preserves whitespace, 55 | // and correctly escapes quotes within interpolated code. 56 | _.template = function(text, data, settings) { 57 | var render; 58 | settings = _.defaults({}, settings, _.templateSettings); 59 | 60 | // Combine delimiters into one regular expression via alternation. 61 | var matcher = new RegExp([ 62 | (settings.escape || noMatch).source, 63 | (settings.interpolate || noMatch).source, 64 | (settings.evaluate || noMatch).source 65 | ].join('|') + '|$', 'g'); 66 | 67 | // Compile the template source, escaping string literals appropriately. 68 | var index = 0; 69 | var source = "__p+='"; 70 | text.replace(matcher, function(match, escape, interpolate, evaluate, offset) { 71 | source += text.slice(index, offset) 72 | .replace(escaper, function(match) { return '\\' + escapes[match]; }); 73 | 74 | if (escape) { 75 | source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'"; 76 | } 77 | if (interpolate) { 78 | source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'"; 79 | } 80 | if (evaluate) { 81 | source += "';\n" + evaluate + "\n__p+='"; 82 | } 83 | index = offset + match.length; 84 | return match; 85 | }); 86 | source += "';\n"; 87 | 88 | // If a variable is not specified, place data values in local scope. 89 | if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n'; 90 | 91 | source = "var __t,__p='',__j=Array.prototype.join," + 92 | "print=function(){__p+=__j.call(arguments,'');};\n" + 93 | source + "return __p;\n"; 94 | 95 | try { 96 | render = new Function(settings.variable || 'obj', '_', source); 97 | } catch (e) { 98 | e.source = source; 99 | throw e; 100 | } 101 | 102 | if (data) return render(data, _); 103 | var template = function(data) { 104 | return render.call(this, data, _); 105 | }; 106 | 107 | // Provide the compiled function source as a convenience for precompilation. 108 | template.source = 'function(' + (settings.variable || 'obj') + '){\n' + source + '}'; 109 | 110 | return template; 111 | }; 112 | 113 | return _; 114 | })({}); 115 | 116 | if (location.hostname === 'todomvc.com') { 117 | (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ 118 | (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), 119 | m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) 120 | })(window,document,'script','https://www.google-analytics.com/analytics.js','ga'); 121 | ga('create', 'UA-31081062-1', 'auto'); 122 | ga('send', 'pageview'); 123 | } 124 | /* jshint ignore:end */ 125 | 126 | function redirect() { 127 | if (location.hostname === 'tastejs.github.io') { 128 | location.href = location.href.replace('tastejs.github.io/todomvc', 'todomvc.com'); 129 | } 130 | } 131 | 132 | function findRoot() { 133 | var base = location.href.indexOf('examples/'); 134 | return location.href.substr(0, base); 135 | } 136 | 137 | function getFile(file, callback) { 138 | if (!location.host) { 139 | return console.info('Miss the info bar? Run TodoMVC from a server to avoid a cross-origin error.'); 140 | } 141 | 142 | var xhr = new XMLHttpRequest(); 143 | 144 | xhr.open('GET', findRoot() + file, true); 145 | xhr.send(); 146 | 147 | xhr.onload = function () { 148 | if (xhr.status === 200 && callback) { 149 | callback(xhr.responseText); 150 | } 151 | }; 152 | } 153 | 154 | function Learn(learnJSON, config) { 155 | if (!(this instanceof Learn)) { 156 | return new Learn(learnJSON, config); 157 | } 158 | 159 | var template, framework; 160 | 161 | if (typeof learnJSON !== 'object') { 162 | try { 163 | learnJSON = JSON.parse(learnJSON); 164 | } catch (e) { 165 | return; 166 | } 167 | } 168 | 169 | if (config) { 170 | template = config.template; 171 | framework = config.framework; 172 | } 173 | 174 | if (!template && learnJSON.templates) { 175 | template = learnJSON.templates.todomvc; 176 | } 177 | 178 | if (!framework && document.querySelector('[data-framework]')) { 179 | framework = document.querySelector('[data-framework]').dataset.framework; 180 | } 181 | 182 | this.template = template; 183 | 184 | if (learnJSON.backend) { 185 | this.frameworkJSON = learnJSON.backend; 186 | this.frameworkJSON.issueLabel = framework; 187 | this.append({ 188 | backend: true 189 | }); 190 | } else if (learnJSON[framework]) { 191 | this.frameworkJSON = learnJSON[framework]; 192 | this.frameworkJSON.issueLabel = framework; 193 | this.append(); 194 | } 195 | 196 | this.fetchIssueCount(); 197 | } 198 | 199 | Learn.prototype.append = function (opts) { 200 | var aside = document.createElement('aside'); 201 | aside.innerHTML = _.template(this.template, this.frameworkJSON); 202 | aside.className = 'learn'; 203 | 204 | if (opts && opts.backend) { 205 | // Remove demo link 206 | var sourceLinks = aside.querySelector('.source-links'); 207 | var heading = sourceLinks.firstElementChild; 208 | var sourceLink = sourceLinks.lastElementChild; 209 | // Correct link path 210 | var href = sourceLink.getAttribute('href'); 211 | sourceLink.setAttribute('href', href.substr(href.lastIndexOf('http'))); 212 | sourceLinks.innerHTML = heading.outerHTML + sourceLink.outerHTML; 213 | } else { 214 | // Localize demo links 215 | var demoLinks = aside.querySelectorAll('.demo-link'); 216 | Array.prototype.forEach.call(demoLinks, function (demoLink) { 217 | if (demoLink.getAttribute('href').substr(0, 4) !== 'http') { 218 | demoLink.setAttribute('href', findRoot() + demoLink.getAttribute('href')); 219 | } 220 | }); 221 | } 222 | 223 | document.body.className = (document.body.className + ' learn-bar').trim(); 224 | document.body.insertAdjacentHTML('afterBegin', aside.outerHTML); 225 | }; 226 | 227 | Learn.prototype.fetchIssueCount = function () { 228 | var issueLink = document.getElementById('issue-count-link'); 229 | if (issueLink) { 230 | var url = issueLink.href.replace('https://github.com', 'https://api.github.com/repos'); 231 | var xhr = new XMLHttpRequest(); 232 | xhr.open('GET', url, true); 233 | xhr.onload = function (e) { 234 | var parsedResponse = JSON.parse(e.target.responseText); 235 | if (parsedResponse instanceof Array) { 236 | var count = parsedResponse.length; 237 | if (count !== 0) { 238 | issueLink.innerHTML = 'This app has ' + count + ' open issues'; 239 | document.getElementById('issue-count').style.display = 'inline'; 240 | } 241 | } 242 | }; 243 | xhr.send(); 244 | } 245 | }; 246 | 247 | redirect(); 248 | getFile('learn.json', Learn); 249 | })(); 250 | -------------------------------------------------------------------------------- /examples/2.4.1/todo/node_modules/todomvc-common/base.js: -------------------------------------------------------------------------------- 1 | /* global _ */ 2 | (function () { 3 | 'use strict'; 4 | 5 | /* jshint ignore:start */ 6 | // Underscore's Template Module 7 | // Courtesy of underscorejs.org 8 | var _ = (function (_) { 9 | _.defaults = function (object) { 10 | if (!object) { 11 | return object; 12 | } 13 | for (var argsIndex = 1, argsLength = arguments.length; argsIndex < argsLength; argsIndex++) { 14 | var iterable = arguments[argsIndex]; 15 | if (iterable) { 16 | for (var key in iterable) { 17 | if (object[key] == null) { 18 | object[key] = iterable[key]; 19 | } 20 | } 21 | } 22 | } 23 | return object; 24 | } 25 | 26 | // By default, Underscore uses ERB-style template delimiters, change the 27 | // following template settings to use alternative delimiters. 28 | _.templateSettings = { 29 | evaluate : /<%([\s\S]+?)%>/g, 30 | interpolate : /<%=([\s\S]+?)%>/g, 31 | escape : /<%-([\s\S]+?)%>/g 32 | }; 33 | 34 | // When customizing `templateSettings`, if you don't want to define an 35 | // interpolation, evaluation or escaping regex, we need one that is 36 | // guaranteed not to match. 37 | var noMatch = /(.)^/; 38 | 39 | // Certain characters need to be escaped so that they can be put into a 40 | // string literal. 41 | var escapes = { 42 | "'": "'", 43 | '\\': '\\', 44 | '\r': 'r', 45 | '\n': 'n', 46 | '\t': 't', 47 | '\u2028': 'u2028', 48 | '\u2029': 'u2029' 49 | }; 50 | 51 | var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g; 52 | 53 | // JavaScript micro-templating, similar to John Resig's implementation. 54 | // Underscore templating handles arbitrary delimiters, preserves whitespace, 55 | // and correctly escapes quotes within interpolated code. 56 | _.template = function(text, data, settings) { 57 | var render; 58 | settings = _.defaults({}, settings, _.templateSettings); 59 | 60 | // Combine delimiters into one regular expression via alternation. 61 | var matcher = new RegExp([ 62 | (settings.escape || noMatch).source, 63 | (settings.interpolate || noMatch).source, 64 | (settings.evaluate || noMatch).source 65 | ].join('|') + '|$', 'g'); 66 | 67 | // Compile the template source, escaping string literals appropriately. 68 | var index = 0; 69 | var source = "__p+='"; 70 | text.replace(matcher, function(match, escape, interpolate, evaluate, offset) { 71 | source += text.slice(index, offset) 72 | .replace(escaper, function(match) { return '\\' + escapes[match]; }); 73 | 74 | if (escape) { 75 | source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'"; 76 | } 77 | if (interpolate) { 78 | source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'"; 79 | } 80 | if (evaluate) { 81 | source += "';\n" + evaluate + "\n__p+='"; 82 | } 83 | index = offset + match.length; 84 | return match; 85 | }); 86 | source += "';\n"; 87 | 88 | // If a variable is not specified, place data values in local scope. 89 | if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n'; 90 | 91 | source = "var __t,__p='',__j=Array.prototype.join," + 92 | "print=function(){__p+=__j.call(arguments,'');};\n" + 93 | source + "return __p;\n"; 94 | 95 | try { 96 | render = new Function(settings.variable || 'obj', '_', source); 97 | } catch (e) { 98 | e.source = source; 99 | throw e; 100 | } 101 | 102 | if (data) return render(data, _); 103 | var template = function(data) { 104 | return render.call(this, data, _); 105 | }; 106 | 107 | // Provide the compiled function source as a convenience for precompilation. 108 | template.source = 'function(' + (settings.variable || 'obj') + '){\n' + source + '}'; 109 | 110 | return template; 111 | }; 112 | 113 | return _; 114 | })({}); 115 | 116 | if (location.hostname === 'todomvc.com') { 117 | (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ 118 | (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), 119 | m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) 120 | })(window,document,'script','https://www.google-analytics.com/analytics.js','ga'); 121 | ga('create', 'UA-31081062-1', 'auto'); 122 | ga('send', 'pageview'); 123 | } 124 | /* jshint ignore:end */ 125 | 126 | function redirect() { 127 | if (location.hostname === 'tastejs.github.io') { 128 | location.href = location.href.replace('tastejs.github.io/todomvc', 'todomvc.com'); 129 | } 130 | } 131 | 132 | function findRoot() { 133 | var base = location.href.indexOf('examples/'); 134 | return location.href.substr(0, base); 135 | } 136 | 137 | function getFile(file, callback) { 138 | if (!location.host) { 139 | return console.info('Miss the info bar? Run TodoMVC from a server to avoid a cross-origin error.'); 140 | } 141 | 142 | var xhr = new XMLHttpRequest(); 143 | 144 | xhr.open('GET', findRoot() + file, true); 145 | xhr.send(); 146 | 147 | xhr.onload = function () { 148 | if (xhr.status === 200 && callback) { 149 | callback(xhr.responseText); 150 | } 151 | }; 152 | } 153 | 154 | function Learn(learnJSON, config) { 155 | if (!(this instanceof Learn)) { 156 | return new Learn(learnJSON, config); 157 | } 158 | 159 | var template, framework; 160 | 161 | if (typeof learnJSON !== 'object') { 162 | try { 163 | learnJSON = JSON.parse(learnJSON); 164 | } catch (e) { 165 | return; 166 | } 167 | } 168 | 169 | if (config) { 170 | template = config.template; 171 | framework = config.framework; 172 | } 173 | 174 | if (!template && learnJSON.templates) { 175 | template = learnJSON.templates.todomvc; 176 | } 177 | 178 | if (!framework && document.querySelector('[data-framework]')) { 179 | framework = document.querySelector('[data-framework]').dataset.framework; 180 | } 181 | 182 | this.template = template; 183 | 184 | if (learnJSON.backend) { 185 | this.frameworkJSON = learnJSON.backend; 186 | this.frameworkJSON.issueLabel = framework; 187 | this.append({ 188 | backend: true 189 | }); 190 | } else if (learnJSON[framework]) { 191 | this.frameworkJSON = learnJSON[framework]; 192 | this.frameworkJSON.issueLabel = framework; 193 | this.append(); 194 | } 195 | 196 | this.fetchIssueCount(); 197 | } 198 | 199 | Learn.prototype.append = function (opts) { 200 | var aside = document.createElement('aside'); 201 | aside.innerHTML = _.template(this.template, this.frameworkJSON); 202 | aside.className = 'learn'; 203 | 204 | if (opts && opts.backend) { 205 | // Remove demo link 206 | var sourceLinks = aside.querySelector('.source-links'); 207 | var heading = sourceLinks.firstElementChild; 208 | var sourceLink = sourceLinks.lastElementChild; 209 | // Correct link path 210 | var href = sourceLink.getAttribute('href'); 211 | sourceLink.setAttribute('href', href.substr(href.lastIndexOf('http'))); 212 | sourceLinks.innerHTML = heading.outerHTML + sourceLink.outerHTML; 213 | } else { 214 | // Localize demo links 215 | var demoLinks = aside.querySelectorAll('.demo-link'); 216 | Array.prototype.forEach.call(demoLinks, function (demoLink) { 217 | if (demoLink.getAttribute('href').substr(0, 4) !== 'http') { 218 | demoLink.setAttribute('href', findRoot() + demoLink.getAttribute('href')); 219 | } 220 | }); 221 | } 222 | 223 | document.body.className = (document.body.className + ' learn-bar').trim(); 224 | document.body.insertAdjacentHTML('afterBegin', aside.outerHTML); 225 | }; 226 | 227 | Learn.prototype.fetchIssueCount = function () { 228 | var issueLink = document.getElementById('issue-count-link'); 229 | if (issueLink) { 230 | var url = issueLink.href.replace('https://github.com', 'https://api.github.com/repos'); 231 | var xhr = new XMLHttpRequest(); 232 | xhr.open('GET', url, true); 233 | xhr.onload = function (e) { 234 | var parsedResponse = JSON.parse(e.target.responseText); 235 | if (parsedResponse instanceof Array) { 236 | var count = parsedResponse.length; 237 | if (count !== 0) { 238 | issueLink.innerHTML = 'This app has ' + count + ' open issues'; 239 | document.getElementById('issue-count').style.display = 'inline'; 240 | } 241 | } 242 | }; 243 | xhr.send(); 244 | } 245 | }; 246 | 247 | redirect(); 248 | getFile('learn.json', Learn); 249 | })(); 250 | -------------------------------------------------------------------------------- /examples/2.4.2/todo/node_modules/todomvc-common/base.js: -------------------------------------------------------------------------------- 1 | /* global _ */ 2 | (function () { 3 | 'use strict'; 4 | 5 | /* jshint ignore:start */ 6 | // Underscore's Template Module 7 | // Courtesy of underscorejs.org 8 | var _ = (function (_) { 9 | _.defaults = function (object) { 10 | if (!object) { 11 | return object; 12 | } 13 | for (var argsIndex = 1, argsLength = arguments.length; argsIndex < argsLength; argsIndex++) { 14 | var iterable = arguments[argsIndex]; 15 | if (iterable) { 16 | for (var key in iterable) { 17 | if (object[key] == null) { 18 | object[key] = iterable[key]; 19 | } 20 | } 21 | } 22 | } 23 | return object; 24 | } 25 | 26 | // By default, Underscore uses ERB-style template delimiters, change the 27 | // following template settings to use alternative delimiters. 28 | _.templateSettings = { 29 | evaluate : /<%([\s\S]+?)%>/g, 30 | interpolate : /<%=([\s\S]+?)%>/g, 31 | escape : /<%-([\s\S]+?)%>/g 32 | }; 33 | 34 | // When customizing `templateSettings`, if you don't want to define an 35 | // interpolation, evaluation or escaping regex, we need one that is 36 | // guaranteed not to match. 37 | var noMatch = /(.)^/; 38 | 39 | // Certain characters need to be escaped so that they can be put into a 40 | // string literal. 41 | var escapes = { 42 | "'": "'", 43 | '\\': '\\', 44 | '\r': 'r', 45 | '\n': 'n', 46 | '\t': 't', 47 | '\u2028': 'u2028', 48 | '\u2029': 'u2029' 49 | }; 50 | 51 | var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g; 52 | 53 | // JavaScript micro-templating, similar to John Resig's implementation. 54 | // Underscore templating handles arbitrary delimiters, preserves whitespace, 55 | // and correctly escapes quotes within interpolated code. 56 | _.template = function(text, data, settings) { 57 | var render; 58 | settings = _.defaults({}, settings, _.templateSettings); 59 | 60 | // Combine delimiters into one regular expression via alternation. 61 | var matcher = new RegExp([ 62 | (settings.escape || noMatch).source, 63 | (settings.interpolate || noMatch).source, 64 | (settings.evaluate || noMatch).source 65 | ].join('|') + '|$', 'g'); 66 | 67 | // Compile the template source, escaping string literals appropriately. 68 | var index = 0; 69 | var source = "__p+='"; 70 | text.replace(matcher, function(match, escape, interpolate, evaluate, offset) { 71 | source += text.slice(index, offset) 72 | .replace(escaper, function(match) { return '\\' + escapes[match]; }); 73 | 74 | if (escape) { 75 | source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'"; 76 | } 77 | if (interpolate) { 78 | source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'"; 79 | } 80 | if (evaluate) { 81 | source += "';\n" + evaluate + "\n__p+='"; 82 | } 83 | index = offset + match.length; 84 | return match; 85 | }); 86 | source += "';\n"; 87 | 88 | // If a variable is not specified, place data values in local scope. 89 | if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n'; 90 | 91 | source = "var __t,__p='',__j=Array.prototype.join," + 92 | "print=function(){__p+=__j.call(arguments,'');};\n" + 93 | source + "return __p;\n"; 94 | 95 | try { 96 | render = new Function(settings.variable || 'obj', '_', source); 97 | } catch (e) { 98 | e.source = source; 99 | throw e; 100 | } 101 | 102 | if (data) return render(data, _); 103 | var template = function(data) { 104 | return render.call(this, data, _); 105 | }; 106 | 107 | // Provide the compiled function source as a convenience for precompilation. 108 | template.source = 'function(' + (settings.variable || 'obj') + '){\n' + source + '}'; 109 | 110 | return template; 111 | }; 112 | 113 | return _; 114 | })({}); 115 | 116 | if (location.hostname === 'todomvc.com') { 117 | (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ 118 | (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), 119 | m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) 120 | })(window,document,'script','https://www.google-analytics.com/analytics.js','ga'); 121 | ga('create', 'UA-31081062-1', 'auto'); 122 | ga('send', 'pageview'); 123 | } 124 | /* jshint ignore:end */ 125 | 126 | function redirect() { 127 | if (location.hostname === 'tastejs.github.io') { 128 | location.href = location.href.replace('tastejs.github.io/todomvc', 'todomvc.com'); 129 | } 130 | } 131 | 132 | function findRoot() { 133 | var base = location.href.indexOf('examples/'); 134 | return location.href.substr(0, base); 135 | } 136 | 137 | function getFile(file, callback) { 138 | if (!location.host) { 139 | return console.info('Miss the info bar? Run TodoMVC from a server to avoid a cross-origin error.'); 140 | } 141 | 142 | var xhr = new XMLHttpRequest(); 143 | 144 | xhr.open('GET', findRoot() + file, true); 145 | xhr.send(); 146 | 147 | xhr.onload = function () { 148 | if (xhr.status === 200 && callback) { 149 | callback(xhr.responseText); 150 | } 151 | }; 152 | } 153 | 154 | function Learn(learnJSON, config) { 155 | if (!(this instanceof Learn)) { 156 | return new Learn(learnJSON, config); 157 | } 158 | 159 | var template, framework; 160 | 161 | if (typeof learnJSON !== 'object') { 162 | try { 163 | learnJSON = JSON.parse(learnJSON); 164 | } catch (e) { 165 | return; 166 | } 167 | } 168 | 169 | if (config) { 170 | template = config.template; 171 | framework = config.framework; 172 | } 173 | 174 | if (!template && learnJSON.templates) { 175 | template = learnJSON.templates.todomvc; 176 | } 177 | 178 | if (!framework && document.querySelector('[data-framework]')) { 179 | framework = document.querySelector('[data-framework]').dataset.framework; 180 | } 181 | 182 | this.template = template; 183 | 184 | if (learnJSON.backend) { 185 | this.frameworkJSON = learnJSON.backend; 186 | this.frameworkJSON.issueLabel = framework; 187 | this.append({ 188 | backend: true 189 | }); 190 | } else if (learnJSON[framework]) { 191 | this.frameworkJSON = learnJSON[framework]; 192 | this.frameworkJSON.issueLabel = framework; 193 | this.append(); 194 | } 195 | 196 | this.fetchIssueCount(); 197 | } 198 | 199 | Learn.prototype.append = function (opts) { 200 | var aside = document.createElement('aside'); 201 | aside.innerHTML = _.template(this.template, this.frameworkJSON); 202 | aside.className = 'learn'; 203 | 204 | if (opts && opts.backend) { 205 | // Remove demo link 206 | var sourceLinks = aside.querySelector('.source-links'); 207 | var heading = sourceLinks.firstElementChild; 208 | var sourceLink = sourceLinks.lastElementChild; 209 | // Correct link path 210 | var href = sourceLink.getAttribute('href'); 211 | sourceLink.setAttribute('href', href.substr(href.lastIndexOf('http'))); 212 | sourceLinks.innerHTML = heading.outerHTML + sourceLink.outerHTML; 213 | } else { 214 | // Localize demo links 215 | var demoLinks = aside.querySelectorAll('.demo-link'); 216 | Array.prototype.forEach.call(demoLinks, function (demoLink) { 217 | if (demoLink.getAttribute('href').substr(0, 4) !== 'http') { 218 | demoLink.setAttribute('href', findRoot() + demoLink.getAttribute('href')); 219 | } 220 | }); 221 | } 222 | 223 | document.body.className = (document.body.className + ' learn-bar').trim(); 224 | document.body.insertAdjacentHTML('afterBegin', aside.outerHTML); 225 | }; 226 | 227 | Learn.prototype.fetchIssueCount = function () { 228 | var issueLink = document.getElementById('issue-count-link'); 229 | if (issueLink) { 230 | var url = issueLink.href.replace('https://github.com', 'https://api.github.com/repos'); 231 | var xhr = new XMLHttpRequest(); 232 | xhr.open('GET', url, true); 233 | xhr.onload = function (e) { 234 | var parsedResponse = JSON.parse(e.target.responseText); 235 | if (parsedResponse instanceof Array) { 236 | var count = parsedResponse.length; 237 | if (count !== 0) { 238 | issueLink.innerHTML = 'This app has ' + count + ' open issues'; 239 | document.getElementById('issue-count').style.display = 'inline'; 240 | } 241 | } 242 | }; 243 | xhr.send(); 244 | } 245 | }; 246 | 247 | redirect(); 248 | getFile('learn.json', Learn); 249 | })(); 250 | --------------------------------------------------------------------------------