├── .gitignore ├── .vscode └── settings.json ├── code ├── 1.Vue2剥丝抽茧-响应式系统 │ ├── data.js │ ├── dep.js │ ├── reactive.js │ └── watcher.js ├── 10.Vue2剥丝抽茧-响应式系统之nextTick │ ├── array.js │ ├── data.js │ ├── dep.js │ ├── env.js │ ├── next-tick.js │ ├── reactive.js │ ├── scheduler.js │ ├── util.js │ └── watcher.js ├── 11.Vue2剥丝抽茧-响应式系统之watch │ ├── array.js │ ├── data.js │ ├── dep.js │ ├── env.js │ ├── next-tick.js │ ├── reactive.js │ ├── scheduler.js │ ├── state.js │ ├── util.js │ └── watcher.js ├── 12.Vue2剥丝抽茧-响应式系统之watch2 │ ├── array.js │ ├── data.js │ ├── dep.js │ ├── env.js │ ├── next-tick.js │ ├── reactive.js │ ├── scheduler.js │ ├── state.js │ ├── traverse.js │ ├── util.js │ └── watcher.js ├── 13.Vue2剥丝抽茧-响应式系统之computed │ ├── array.js │ ├── data.js │ ├── dep.js │ ├── env.js │ ├── next-tick.js │ ├── reactive.js │ ├── scheduler.js │ ├── state.js │ ├── traverse.js │ ├── util.js │ └── watcher.js ├── 15.Vue2剥丝抽茧-虚拟dom │ ├── create-element.js │ ├── main copy.js │ ├── main.js │ ├── node-ops.js │ ├── normalize-children.js │ ├── patch.js │ ├── util.js │ └── vnode.js ├── 16.Vue2剥丝抽茧-虚拟dom之绑定事件 │ ├── create-element.js │ ├── main copy.js │ ├── main.js │ ├── modules │ │ ├── events.js │ │ └── index.js │ ├── node-ops.js │ ├── normalize-children.js │ ├── patch.js │ ├── update-listeners.js │ ├── util.js │ └── vnode.js ├── 17.Vue2剥丝抽茧-虚拟dom之更新 │ ├── create-element.js │ ├── main copy.js │ ├── main.js │ ├── modules │ │ ├── events.js │ │ └── index.js │ ├── node-ops.js │ ├── normalize-children.js │ ├── observer │ │ ├── array.js │ │ ├── data.js │ │ ├── dep.js │ │ ├── env.js │ │ ├── next-tick.js │ │ ├── reactive.js │ │ ├── scheduler.js │ │ ├── state.js │ │ ├── traverse.js │ │ ├── util.js │ │ └── watcher.js │ ├── patch.js │ ├── update-listeners.js │ ├── util.js │ └── vnode.js ├── 18.Vue2剥丝抽茧-虚拟dom之移动 │ ├── create-element.js │ ├── main copy.js │ ├── main.js │ ├── modules │ │ ├── events.js │ │ └── index.js │ ├── node-ops.js │ ├── normalize-children.js │ ├── observer │ │ ├── array.js │ │ ├── data.js │ │ ├── dep.js │ │ ├── env.js │ │ ├── next-tick.js │ │ ├── reactive.js │ │ ├── scheduler.js │ │ ├── state.js │ │ ├── traverse.js │ │ ├── util.js │ │ └── watcher.js │ ├── patch.js │ ├── update-listeners.js │ ├── util.js │ └── vnode.js ├── 19.Vue2剥丝抽茧-虚拟dom之移动优化 │ ├── create-element.js │ ├── main copy.js │ ├── main.js │ ├── modules │ │ ├── events.js │ │ └── index.js │ ├── node-ops.js │ ├── normalize-children.js │ ├── observer │ │ ├── array.js │ │ ├── data.js │ │ ├── dep.js │ │ ├── env.js │ │ ├── next-tick.js │ │ ├── reactive.js │ │ ├── scheduler.js │ │ ├── state.js │ │ ├── traverse.js │ │ ├── util.js │ │ └── watcher.js │ ├── patch.js │ ├── update-listeners.js │ ├── util.js │ └── vnode.js ├── 2.Vue2剥丝抽茧-响应式系统之分支切换 │ ├── data.js │ ├── dep.js │ ├── reactive.js │ ├── util.js │ └── watcher.js ├── 20.Vue2剥丝抽茧-虚拟dom之增删 │ ├── create-element.js │ ├── main copy.js │ ├── main.js │ ├── mainAddChildren.js │ ├── modules │ │ ├── events.js │ │ └── index.js │ ├── node-ops.js │ ├── normalize-children.js │ ├── observer │ │ ├── array.js │ │ ├── data.js │ │ ├── dep.js │ │ ├── env.js │ │ ├── next-tick.js │ │ ├── reactive.js │ │ ├── scheduler.js │ │ ├── state.js │ │ ├── traverse.js │ │ ├── util.js │ │ └── watcher.js │ ├── patch.js │ ├── update-listeners.js │ ├── util.js │ └── vnode.js ├── 21.Vue2剥丝抽茧-虚拟dom之组件(废) │ ├── config.js │ ├── create-component.js │ ├── create-element.js │ ├── extract-props.js │ ├── helpers │ │ ├── extract-props.js │ │ ├── get-first-component-child.js │ │ ├── index.js │ │ ├── is-async-placeholder.js │ │ ├── merge-hook.js │ │ ├── normalize-children.js │ │ ├── normalize-scoped-slots.js │ │ ├── resolve-async-component.js │ │ └── update-listeners.js │ ├── main copy.js │ ├── main.js │ ├── mainAddChildren.js │ ├── modules │ │ ├── events.js │ │ └── index.js │ ├── node-ops.js │ ├── normalize-children.js │ ├── observer │ │ ├── array.js │ │ ├── data.js │ │ ├── dep.js │ │ ├── env.js │ │ ├── next-tick.js │ │ ├── reactive.js │ │ ├── scheduler.js │ │ ├── state.js │ │ ├── traverse.js │ │ ├── util.js │ │ └── watcher.js │ ├── patch.js │ ├── ref.js │ ├── update-listeners.js │ ├── util.js │ ├── vnode.js │ └── webutil.js ├── 22.Vue2剥丝抽茧-虚拟dom之组件 │ ├── src │ │ ├── core │ │ │ ├── config.js │ │ │ ├── global-api │ │ │ │ ├── extend.js │ │ │ │ └── index.js │ │ │ ├── index.js │ │ │ ├── instance │ │ │ │ ├── index.js │ │ │ │ ├── init.js │ │ │ │ ├── lifecycle.js │ │ │ │ ├── render.js │ │ │ │ └── state.js │ │ │ ├── observer │ │ │ │ ├── array.js │ │ │ │ ├── dep.js │ │ │ │ ├── index.js │ │ │ │ ├── scheduler.js │ │ │ │ ├── traverse.js │ │ │ │ └── watcher.js │ │ │ ├── util │ │ │ │ ├── env.js │ │ │ │ ├── index.js │ │ │ │ ├── lang.js │ │ │ │ ├── next-tick.js │ │ │ │ └── options.js │ │ │ └── vdom │ │ │ │ ├── create-component.js │ │ │ │ ├── create-element.js │ │ │ │ ├── helpers │ │ │ │ ├── extract-props.js │ │ │ │ ├── index.js │ │ │ │ ├── normalize-children.js │ │ │ │ └── update-listeners.js │ │ │ │ ├── patch.js │ │ │ │ └── vnode.js │ │ ├── platforms │ │ │ └── web │ │ │ │ ├── entry-runtime.js │ │ │ │ ├── modules │ │ │ │ ├── events.js │ │ │ │ └── index.js │ │ │ │ ├── runtime │ │ │ │ ├── index.js │ │ │ │ ├── node-ops.js │ │ │ │ └── patch.js │ │ │ │ └── util │ │ │ │ ├── element.js │ │ │ │ └── index.js │ │ └── shared │ │ │ ├── constants.js │ │ │ └── util.js │ └── vueliang1.js ├── 23.Vue2剥丝抽茧-模版编译之分词 │ ├── automata.js │ └── main.js ├── 24.Vue2剥丝抽茧-模版编译之生成AST │ ├── automata.js │ └── main.js ├── 25.Vue2剥丝抽茧-模版编译之静态render │ ├── element.js │ ├── generate.js │ ├── main.js │ └── optimize.js ├── 3.Vue2剥丝抽茧-响应式系统之嵌套 │ ├── data.js │ ├── dep.js │ ├── reactive.js │ ├── util.js │ └── watcher.js ├── 4-1 │ ├── data.js │ ├── dep.js │ ├── reactive.js │ ├── scheduler.js │ ├── util.js │ └── watcher.js ├── 4.Vue2剥丝抽茧-响应式系统完善 │ ├── data.js │ ├── dep.js │ ├── reactive.js │ ├── util.js │ └── watcher.js ├── 5.Vue2剥丝抽茧-响应式系统之深度响应 │ ├── data.js │ ├── dep.js │ ├── reactive.js │ ├── util.js │ └── watcher.js ├── 6.Vue2剥丝抽茧-响应式系统之数组 │ ├── array.js │ ├── data.js │ ├── dep.js │ ├── reactive.js │ ├── util.js │ └── watcher.js ├── 7.Vue2剥丝抽茧-响应式系统之数组2 │ ├── array.js │ ├── data.js │ ├── dep.js │ ├── reactive.js │ ├── util.js │ └── watcher.js ├── 8.Vue2剥丝抽茧-响应式系统之set和delete │ ├── array.js │ ├── data.js │ ├── dep.js │ ├── reactive.js │ ├── util.js │ └── watcher.js ├── 9.Vue2剥丝抽茧-响应式系统之异步队列 │ ├── array.js │ ├── data.js │ ├── dep.js │ ├── reactive.js │ ├── scheduler.js │ ├── util.js │ └── watcher.js ├── VueLiang0 │ ├── src │ │ ├── core │ │ │ ├── global-api │ │ │ │ └── index.js │ │ │ ├── index.js │ │ │ ├── instance │ │ │ │ ├── index.js │ │ │ │ ├── init.js │ │ │ │ ├── lifecycle.js │ │ │ │ ├── render.js │ │ │ │ └── state.js │ │ │ ├── observer │ │ │ │ ├── array.js │ │ │ │ ├── dep.js │ │ │ │ ├── index.js │ │ │ │ ├── scheduler.js │ │ │ │ ├── traverse.js │ │ │ │ └── watcher.js │ │ │ └── util │ │ │ │ ├── env.js │ │ │ │ ├── index.js │ │ │ │ ├── lang.js │ │ │ │ └── next-tick.js │ │ └── shared │ │ │ └── util.js │ └── vueliang0.js ├── VueLiang1 │ ├── src │ │ ├── core │ │ │ ├── global-api │ │ │ │ └── index.js │ │ │ ├── index.js │ │ │ ├── instance │ │ │ │ ├── index.js │ │ │ │ ├── init.js │ │ │ │ ├── lifecycle.js │ │ │ │ ├── render.js │ │ │ │ └── state.js │ │ │ ├── observer │ │ │ │ ├── array.js │ │ │ │ ├── dep.js │ │ │ │ ├── index.js │ │ │ │ ├── scheduler.js │ │ │ │ ├── traverse.js │ │ │ │ └── watcher.js │ │ │ ├── util │ │ │ │ ├── env.js │ │ │ │ ├── index.js │ │ │ │ ├── lang.js │ │ │ │ └── next-tick.js │ │ │ └── vdom │ │ │ │ ├── create-element.js │ │ │ │ ├── helpers │ │ │ │ ├── index.js │ │ │ │ ├── normalize-children.js │ │ │ │ └── update-listeners.js │ │ │ │ ├── patch.js │ │ │ │ └── vnode.js │ │ ├── platforms │ │ │ └── web │ │ │ │ ├── entry-runtime.js │ │ │ │ ├── modules │ │ │ │ ├── events.js │ │ │ │ └── index.js │ │ │ │ └── runtime │ │ │ │ ├── index.js │ │ │ │ ├── node-ops.js │ │ │ │ └── patch.js │ │ └── shared │ │ │ └── util.js │ └── vueliang1.js ├── core │ ├── components │ │ ├── index.js │ │ └── keep-alive.js │ ├── config.js │ ├── global-api │ │ ├── assets.js │ │ ├── extend.js │ │ ├── index.js │ │ ├── mixin.js │ │ └── use.js │ ├── index.js │ ├── instance │ │ ├── events.js │ │ ├── index.js │ │ ├── init.js │ │ ├── inject.js │ │ ├── lifecycle.js │ │ ├── proxy.js │ │ ├── render-helpers │ │ │ ├── bind-dynamic-keys.js │ │ │ ├── bind-object-listeners.js │ │ │ ├── bind-object-props.js │ │ │ ├── check-keycodes.js │ │ │ ├── index.js │ │ │ ├── render-list.js │ │ │ ├── render-slot.js │ │ │ ├── render-static.js │ │ │ ├── resolve-filter.js │ │ │ ├── resolve-scoped-slots.js │ │ │ └── resolve-slots.js │ │ ├── render.js │ │ └── state.js │ ├── observer │ │ ├── array.js │ │ ├── dep.js │ │ ├── index.js │ │ ├── scheduler.js │ │ ├── traverse.js │ │ └── watcher.js │ ├── util │ │ ├── debug.js │ │ ├── env.js │ │ ├── error.js │ │ ├── index.js │ │ ├── lang.js │ │ ├── next-tick.js │ │ ├── options.js │ │ ├── perf.js │ │ └── props.js │ └── vdom │ │ ├── create-component.js │ │ ├── create-element.js │ │ ├── create-functional-component.js │ │ ├── helpers │ │ ├── extract-props.js │ │ ├── get-first-component-child.js │ │ ├── index.js │ │ ├── is-async-placeholder.js │ │ ├── merge-hook.js │ │ ├── normalize-children.js │ │ ├── normalize-scoped-slots.js │ │ ├── resolve-async-component.js │ │ └── update-listeners.js │ │ ├── modules │ │ ├── directives.js │ │ ├── index.js │ │ └── ref.js │ │ ├── patch.js │ │ └── vnode.js ├── dist │ ├── bundle.js │ └── index.html ├── package-lock.json ├── package.json └── webpack.config.js ├── docs ├── .vuepress │ └── config.js ├── README.md └── posts │ ├── Vue2剥丝抽茧-VueLiang0.md │ ├── Vue2剥丝抽茧-VueLiang1.md │ ├── Vue2剥丝抽茧-响应式系统.md │ ├── Vue2剥丝抽茧-响应式系统之computed.md │ ├── Vue2剥丝抽茧-响应式系统之nextTick.md │ ├── Vue2剥丝抽茧-响应式系统之set和delete.md │ ├── Vue2剥丝抽茧-响应式系统之watch.md │ ├── Vue2剥丝抽茧-响应式系统之watch2.md │ ├── Vue2剥丝抽茧-响应式系统之分支切换.md │ ├── Vue2剥丝抽茧-响应式系统之嵌套.md │ ├── Vue2剥丝抽茧-响应式系统之异步队列.md │ ├── Vue2剥丝抽茧-响应式系统之数组.md │ ├── Vue2剥丝抽茧-响应式系统之数组2.md │ ├── Vue2剥丝抽茧-响应式系统之深度响应.md │ ├── Vue2剥丝抽茧-响应式系统完善.md │ ├── Vue2剥丝抽茧-模版编译之分词.md │ ├── Vue2剥丝抽茧-模版编译之生成AST.md │ ├── Vue2剥丝抽茧-模版编译之静态render.md │ ├── Vue2剥丝抽茧-虚拟dom之增删.md │ ├── Vue2剥丝抽茧-虚拟dom之更新.md │ ├── Vue2剥丝抽茧-虚拟dom之移动.md │ ├── Vue2剥丝抽茧-虚拟dom之移动优化.md │ ├── Vue2剥丝抽茧-虚拟dom之组件.md │ ├── Vue2剥丝抽茧-虚拟dom之绑定事件.md │ └── Vue2剥丝抽茧-虚拟dom简介.md ├── package-lock.json └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | docs/.vuepress/dist 3 | **/.DS_Store -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.defaultFormatter": "esbenp.prettier-vscode", 3 | "eslint.validate": ["javascript", "javascriptreact", "vue", "typescript", "typescriptreact"], 4 | "editor.codeActionsOnSave": { 5 | "source.fixAll.eslint": true 6 | }, 7 | "editor.formatOnSave": true 8 | } 9 | -------------------------------------------------------------------------------- /code/1.Vue2剥丝抽茧-响应式系统/data.js: -------------------------------------------------------------------------------- 1 | // https://vue.windliang.wang/posts/Vue2%E5%89%A5%E4%B8%9D%E6%8A%BD%E8%8C%A7-%E5%93%8D%E5%BA%94%E5%BC%8F%E7%B3%BB%E7%BB%9F.html 2 | import { observe } from "./reactive"; 3 | import Watcher from "./watcher"; 4 | const data = { 5 | text: "hello, world", 6 | }; 7 | observe(data); 8 | 9 | const updateComponent = () => { 10 | console.log("收到", data.text); 11 | }; 12 | 13 | new Watcher(updateComponent); 14 | 15 | data.text = "hello, liang"; 16 | -------------------------------------------------------------------------------- /code/1.Vue2剥丝抽茧-响应式系统/dep.js: -------------------------------------------------------------------------------- 1 | export default class Dep { 2 | static target; //当前在执行的函数 3 | subs; // 依赖的函数 4 | constructor() { 5 | this.subs = []; // 保存所有需要执行的函数 6 | } 7 | 8 | addSub(sub) { 9 | this.subs.push(sub); 10 | } 11 | 12 | depend() { 13 | if (Dep.target) { 14 | // 委托给 Dep.target 去调用 addSub 15 | Dep.target.addDep(this); 16 | } 17 | } 18 | 19 | notify() { 20 | for (let i = 0, l = this.subs.length; i < l; i++) { 21 | this.subs[i].update(); 22 | } 23 | } 24 | } 25 | 26 | Dep.target = null; // 静态变量,全局唯一 27 | -------------------------------------------------------------------------------- /code/1.Vue2剥丝抽茧-响应式系统/watcher.js: -------------------------------------------------------------------------------- 1 | import Dep from "./dep"; 2 | export default class Watcher { 3 | constructor(Fn) { 4 | this.getter = Fn; 5 | this.get(); 6 | } 7 | 8 | /** 9 | * Evaluate the getter, and re-collect dependencies. 10 | */ 11 | get() { 12 | Dep.target = this; // 保存包装了当前正在执行的函数的 Watcher 13 | let value; 14 | try { 15 | value = this.getter.call(); 16 | } catch (e) { 17 | throw e; 18 | } 19 | return value; 20 | } 21 | 22 | /** 23 | * Add a dependency to this directive. 24 | */ 25 | addDep(dep) { 26 | // 当前正在执行的函数的 Watcher 保存到 dep 中的 subs 中 27 | dep.addSub(this); 28 | } 29 | 30 | /** 31 | * Subscriber interface. 32 | * Will be called when a dependency changes. 33 | */ 34 | update() { 35 | this.run(); 36 | } 37 | 38 | /** 39 | * Scheduler job interface. 40 | * Will be called by the scheduler. 41 | */ 42 | run() { 43 | this.get(); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /code/10.Vue2剥丝抽茧-响应式系统之nextTick/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"; 7 | 8 | const arrayProto = Array.prototype; 9 | export const arrayMethods = Object.create(arrayProto); 10 | 11 | const methodsToPatch = [ 12 | "push", 13 | "pop", 14 | "shift", 15 | "unshift", 16 | "splice", 17 | "sort", 18 | "reverse", 19 | ]; 20 | 21 | /** 22 | * Intercept mutating methods and emit events 23 | */ 24 | methodsToPatch.forEach(function (method) { 25 | // cache original method 26 | const original = arrayProto[method]; 27 | def(arrayMethods, method, function mutator(...args) { 28 | const result = original.apply(this, args); 29 | const ob = this.__ob__; 30 | /******新增 *************************/ 31 | let inserted; // 加添加的元素拿到 32 | switch (method) { 33 | case "push": 34 | case "unshift": 35 | inserted = args; 36 | break; 37 | case "splice": 38 | inserted = args.slice(2); 39 | break; 40 | } 41 | if (inserted) ob.observeArray(inserted); 42 | /************************************/ 43 | // notify change 44 | ob.dep.notify(); 45 | return result; 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /code/10.Vue2剥丝抽茧-响应式系统之nextTick/dep.js: -------------------------------------------------------------------------------- 1 | import { remove } from "./util"; 2 | 3 | let uid = 0; 4 | 5 | export default class Dep { 6 | static target; //当前在执行的函数 7 | subs; // 依赖的函数 8 | id; // Dep 对象标识 9 | constructor() { 10 | this.id = uid++; 11 | this.subs = []; // 保存所有需要执行的函数 12 | } 13 | 14 | addSub(sub) { 15 | this.subs.push(sub); 16 | } 17 | 18 | removeSub(sub) { 19 | remove(this.subs, sub); 20 | } 21 | depend() { 22 | if (Dep.target) { 23 | // 委托给 Dep.target 去调用 addSub 24 | Dep.target.addDep(this); 25 | } 26 | } 27 | 28 | notify() { 29 | // stabilize the subscriber list first 30 | const subs = this.subs.slice(); 31 | for (let i = 0, l = subs.length; i < l; i++) { 32 | subs[i].update(); 33 | } 34 | } 35 | } 36 | 37 | Dep.target = null; // 静态变量,全局唯一 38 | 39 | // The current target watcher being evaluated. 40 | // This is globally unique because only one watcher 41 | // can be evaluated at a time. 42 | const targetStack = []; 43 | 44 | export function pushTarget(target) { 45 | targetStack.push(target); 46 | Dep.target = target; 47 | } 48 | 49 | export function popTarget() { 50 | targetStack.pop(); 51 | Dep.target = targetStack[targetStack.length - 1]; 52 | } 53 | -------------------------------------------------------------------------------- /code/10.Vue2剥丝抽茧-响应式系统之nextTick/env.js: -------------------------------------------------------------------------------- 1 | /* istanbul ignore next */ 2 | export function isNative(Ctor) { 3 | return typeof Ctor === "function" && /native code/.test(Ctor.toString()); 4 | } 5 | -------------------------------------------------------------------------------- /code/10.Vue2剥丝抽茧-响应式系统之nextTick/next-tick.js: -------------------------------------------------------------------------------- 1 | import { isNative } from "./env"; 2 | 3 | const callbacks = []; 4 | let pending = false; 5 | 6 | function flushCallbacks() { 7 | pending = false; 8 | const copies = callbacks.slice(0); 9 | callbacks.length = 0; 10 | for (let i = 0; i < copies.length; i++) { 11 | copies[i](); 12 | } 13 | } 14 | 15 | let timerFunc; 16 | 17 | if (typeof Promise !== "undefined" && isNative(Promise)) { 18 | const p = Promise.resolve(); 19 | timerFunc = () => { 20 | p.then(flushCallbacks); 21 | }; 22 | } else { 23 | // Fallback to setTimeout. 24 | timerFunc = () => { 25 | setTimeout(flushCallbacks, 0); 26 | }; 27 | } 28 | 29 | export function nextTick(cb, ctx) { 30 | let _resolve; 31 | callbacks.push(() => { 32 | if (cb) { 33 | cb.call(ctx); 34 | } else if (_resolve) { 35 | _resolve(ctx); 36 | } 37 | }); 38 | if (!pending) { 39 | pending = true; 40 | timerFunc(); // 只执行一次 41 | } 42 | // $flow-disable-line 43 | if (!cb && typeof Promise !== "undefined") { 44 | return new Promise((resolve) => { 45 | _resolve = resolve; 46 | }); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /code/10.Vue2剥丝抽茧-响应式系统之nextTick/scheduler.js: -------------------------------------------------------------------------------- 1 | import { nextTick } from "./next-tick"; 2 | const queue = []; 3 | let has = {}; 4 | let waiting = false; 5 | let flushing = false; 6 | let index = 0; 7 | 8 | /** 9 | * Reset the scheduler's state. 10 | */ 11 | function resetSchedulerState() { 12 | index = queue.length = 0; 13 | has = {}; 14 | waiting = flushing = false; 15 | } 16 | 17 | /** 18 | * Flush both queues and run the watchers. 19 | */ 20 | function flushSchedulerQueue() { 21 | flushing = true; 22 | let watcher, id; 23 | // do not cache length because more watchers might be pushed 24 | // as we run existing watchers 25 | for (index = 0; index < queue.length; index++) { 26 | watcher = queue[index]; 27 | if (watcher.before) { 28 | watcher.before(); 29 | } 30 | id = watcher.id; 31 | has[id] = null; 32 | watcher.run(); 33 | } 34 | 35 | resetSchedulerState(); 36 | } 37 | 38 | /** 39 | * Push a watcher into the watcher queue. 40 | * Jobs with duplicate IDs will be skipped unless it's 41 | * pushed when the queue is being flushed. 42 | */ 43 | export function queueWatcher(watcher) { 44 | const id = watcher.id; 45 | if (has[id] == null) { 46 | has[id] = true; 47 | queue.push(watcher); 48 | // queue the flush 49 | if (!waiting) { 50 | waiting = true; 51 | nextTick(flushSchedulerQueue); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /code/10.Vue2剥丝抽茧-响应式系统之nextTick/util.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Remove an item from an array. 3 | */ 4 | export function remove(arr, item) { 5 | if (arr.length) { 6 | const index = arr.indexOf(item); 7 | if (index > -1) { 8 | return arr.splice(index, 1); 9 | } 10 | } 11 | } 12 | 13 | /** 14 | * Quick object check - this is primarily used to tell 15 | * Objects from primitive values when we know the value 16 | * is a JSON-compliant type. 17 | */ 18 | export function isObject(obj) { 19 | return obj !== null && typeof obj === "object"; 20 | } 21 | 22 | /** 23 | * Define a property. 24 | */ 25 | export function def(obj, key, val, enumerable) { 26 | Object.defineProperty(obj, key, { 27 | value: val, 28 | enumerable: !!enumerable, 29 | writable: true, 30 | configurable: true, 31 | }); 32 | } 33 | 34 | // can we use __proto__? 35 | export const hasProto = "__proto__" in {}; 36 | 37 | /** 38 | * Check if val is a valid array index. 39 | */ 40 | export function isValidArrayIndex(val) { 41 | const n = parseFloat(String(val)); 42 | return n >= 0 && Math.floor(n) === n && isFinite(val); 43 | } 44 | 45 | /** 46 | * Check whether an object has the property. 47 | */ 48 | const hasOwnProperty = Object.prototype.hasOwnProperty; 49 | export function hasOwn(obj, key) { 50 | return hasOwnProperty.call(obj, key); 51 | } 52 | -------------------------------------------------------------------------------- /code/11.Vue2剥丝抽茧-响应式系统之watch/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"; 7 | 8 | const arrayProto = Array.prototype; 9 | export const arrayMethods = Object.create(arrayProto); 10 | 11 | const methodsToPatch = [ 12 | "push", 13 | "pop", 14 | "shift", 15 | "unshift", 16 | "splice", 17 | "sort", 18 | "reverse", 19 | ]; 20 | 21 | /** 22 | * Intercept mutating methods and emit events 23 | */ 24 | methodsToPatch.forEach(function (method) { 25 | // cache original method 26 | const original = arrayProto[method]; 27 | def(arrayMethods, method, function mutator(...args) { 28 | const result = original.apply(this, args); 29 | const ob = this.__ob__; 30 | /******新增 *************************/ 31 | let inserted; // 加添加的元素拿到 32 | switch (method) { 33 | case "push": 34 | case "unshift": 35 | inserted = args; 36 | break; 37 | case "splice": 38 | inserted = args.slice(2); 39 | break; 40 | } 41 | if (inserted) ob.observeArray(inserted); 42 | /************************************/ 43 | // notify change 44 | ob.dep.notify(); 45 | return result; 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /code/11.Vue2剥丝抽茧-响应式系统之watch/data.js: -------------------------------------------------------------------------------- 1 | // https://vue.windliang.wang/posts/Vue2%E5%89%A5%E4%B8%9D%E6%8A%BD%E8%8C%A7-%E5%93%8D%E5%BA%94%E5%BC%8F%E7%B3%BB%E7%BB%9F%E4%B9%8Bwatch.html 2 | import { observe } from "./reactive"; 3 | import { initWatch } from "./state"; 4 | const options = { 5 | data: { 6 | first: { 7 | text: "hello", 8 | }, 9 | title: "liang", 10 | }, 11 | watch: { 12 | "first.text": [ 13 | function (newVal, oldVal) { 14 | console.log("收到变化", newVal, oldVal); 15 | }, 16 | function (newVal, oldVal) { 17 | console.log("收到变化2", newVal, oldVal); 18 | }, 19 | ], 20 | title(newVal, oldVal) { 21 | console.log("收到变化", newVal, oldVal); 22 | }, 23 | }, 24 | }; 25 | observe(options.data); 26 | initWatch(options.data, options.watch); 27 | 28 | options.data.first.text = "changeText"; 29 | 30 | options.data.title = "changeTitle"; 31 | -------------------------------------------------------------------------------- /code/11.Vue2剥丝抽茧-响应式系统之watch/dep.js: -------------------------------------------------------------------------------- 1 | import { remove } from "./util"; 2 | 3 | let uid = 0; 4 | 5 | export default class Dep { 6 | static target; //当前在执行的函数 7 | subs; // 依赖的函数 8 | id; // Dep 对象标识 9 | constructor() { 10 | this.id = uid++; 11 | this.subs = []; // 保存所有需要执行的函数 12 | } 13 | 14 | addSub(sub) { 15 | this.subs.push(sub); 16 | } 17 | 18 | removeSub(sub) { 19 | remove(this.subs, sub); 20 | } 21 | depend() { 22 | if (Dep.target) { 23 | // 委托给 Dep.target 去调用 addSub 24 | Dep.target.addDep(this); 25 | } 26 | } 27 | 28 | notify() { 29 | // stabilize the subscriber list first 30 | const subs = this.subs.slice(); 31 | for (let i = 0, l = subs.length; i < l; i++) { 32 | subs[i].update(); 33 | } 34 | } 35 | } 36 | 37 | Dep.target = null; // 静态变量,全局唯一 38 | 39 | // The current target watcher being evaluated. 40 | // This is globally unique because only one watcher 41 | // can be evaluated at a time. 42 | const targetStack = []; 43 | 44 | export function pushTarget(target) { 45 | targetStack.push(target); 46 | Dep.target = target; 47 | } 48 | 49 | export function popTarget() { 50 | targetStack.pop(); 51 | Dep.target = targetStack[targetStack.length - 1]; 52 | } 53 | -------------------------------------------------------------------------------- /code/11.Vue2剥丝抽茧-响应式系统之watch/env.js: -------------------------------------------------------------------------------- 1 | /* istanbul ignore next */ 2 | export function isNative(Ctor) { 3 | return typeof Ctor === "function" && /native code/.test(Ctor.toString()); 4 | } 5 | -------------------------------------------------------------------------------- /code/11.Vue2剥丝抽茧-响应式系统之watch/next-tick.js: -------------------------------------------------------------------------------- 1 | import { isNative } from "./env"; 2 | 3 | const callbacks = []; 4 | let pending = false; 5 | 6 | function flushCallbacks() { 7 | pending = false; 8 | const copies = callbacks.slice(0); 9 | callbacks.length = 0; 10 | for (let i = 0; i < copies.length; i++) { 11 | copies[i](); 12 | } 13 | } 14 | 15 | let timerFunc; 16 | 17 | if (typeof Promise !== "undefined" && isNative(Promise)) { 18 | const p = Promise.resolve(); 19 | timerFunc = () => { 20 | p.then(flushCallbacks); 21 | }; 22 | } else { 23 | // Fallback to setTimeout. 24 | timerFunc = () => { 25 | setTimeout(flushCallbacks, 0); 26 | }; 27 | } 28 | 29 | export function nextTick(cb, ctx) { 30 | let _resolve; 31 | callbacks.push(() => { 32 | if (cb) { 33 | cb.call(ctx); 34 | } else if (_resolve) { 35 | _resolve(ctx); 36 | } 37 | }); 38 | if (!pending) { 39 | pending = true; 40 | timerFunc(); // 只执行一次 41 | } 42 | // $flow-disable-line 43 | if (!cb && typeof Promise !== "undefined") { 44 | return new Promise((resolve) => { 45 | _resolve = resolve; 46 | }); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /code/11.Vue2剥丝抽茧-响应式系统之watch/scheduler.js: -------------------------------------------------------------------------------- 1 | import { nextTick } from "./next-tick"; 2 | const queue = []; 3 | let has = {}; 4 | let waiting = false; 5 | let flushing = false; 6 | let index = 0; 7 | 8 | /** 9 | * Reset the scheduler's state. 10 | */ 11 | function resetSchedulerState() { 12 | index = queue.length = 0; 13 | has = {}; 14 | waiting = flushing = false; 15 | } 16 | 17 | /** 18 | * Flush both queues and run the watchers. 19 | */ 20 | function flushSchedulerQueue() { 21 | flushing = true; 22 | let watcher, id; 23 | // do not cache length because more watchers might be pushed 24 | // as we run existing watchers 25 | for (index = 0; index < queue.length; index++) { 26 | watcher = queue[index]; 27 | if (watcher.before) { 28 | watcher.before(); 29 | } 30 | id = watcher.id; 31 | has[id] = null; 32 | watcher.run(); 33 | } 34 | 35 | resetSchedulerState(); 36 | } 37 | 38 | /** 39 | * Push a watcher into the watcher queue. 40 | * Jobs with duplicate IDs will be skipped unless it's 41 | * pushed when the queue is being flushed. 42 | */ 43 | export function queueWatcher(watcher) { 44 | const id = watcher.id; 45 | if (has[id] == null) { 46 | has[id] = true; 47 | queue.push(watcher); 48 | // queue the flush 49 | if (!waiting) { 50 | waiting = true; 51 | nextTick(flushSchedulerQueue); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /code/11.Vue2剥丝抽茧-响应式系统之watch/state.js: -------------------------------------------------------------------------------- 1 | import Watcher from "./watcher"; 2 | import { pushTarget, popTarget } from "./dep"; 3 | 4 | export function initWatch(data, watch) { 5 | for (const key in watch) { 6 | const handler = watch[key]; 7 | if (Array.isArray(handler)) { 8 | for (let i = 0; i < handler.length; i++) { 9 | createWatcher(data, key, handler[i]); 10 | } 11 | } else { 12 | createWatcher(data, key, handler); 13 | } 14 | } 15 | } 16 | 17 | function createWatcher(data, expOrFn, handler) { 18 | return $watch(data, expOrFn, handler); 19 | } 20 | 21 | function $watch(data, expOrFn, handler) { 22 | const watcher = new Watcher(data, expOrFn, handler); 23 | // if (options.immediate) { 24 | // pushTarget(); 25 | // watcher.value 26 | // ? handler.apply(data, [watcher.value]) 27 | // : handler.call(data); 28 | // popTarget(); 29 | // } 30 | return function unwatchFn() { 31 | watcher.teardown(); 32 | }; 33 | } 34 | -------------------------------------------------------------------------------- /code/12.Vue2剥丝抽茧-响应式系统之watch2/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"; 7 | 8 | const arrayProto = Array.prototype; 9 | export const arrayMethods = Object.create(arrayProto); 10 | 11 | const methodsToPatch = [ 12 | "push", 13 | "pop", 14 | "shift", 15 | "unshift", 16 | "splice", 17 | "sort", 18 | "reverse", 19 | ]; 20 | 21 | /** 22 | * Intercept mutating methods and emit events 23 | */ 24 | methodsToPatch.forEach(function (method) { 25 | // cache original method 26 | const original = arrayProto[method]; 27 | def(arrayMethods, method, function mutator(...args) { 28 | const result = original.apply(this, args); 29 | const ob = this.__ob__; 30 | /******新增 *************************/ 31 | let inserted; // 加添加的元素拿到 32 | switch (method) { 33 | case "push": 34 | case "unshift": 35 | inserted = args; 36 | break; 37 | case "splice": 38 | inserted = args.slice(2); 39 | break; 40 | } 41 | if (inserted) ob.observeArray(inserted); 42 | /************************************/ 43 | // notify change 44 | ob.dep.notify(); 45 | return result; 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /code/12.Vue2剥丝抽茧-响应式系统之watch2/data.js: -------------------------------------------------------------------------------- 1 | // https://vue.windliang.wang/posts/Vue2%E5%89%A5%E4%B8%9D%E6%8A%BD%E8%8C%A7-%E5%93%8D%E5%BA%94%E5%BC%8F%E7%B3%BB%E7%BB%9F%E4%B9%8Bwatch2.html 2 | import { observe } from "./reactive"; 3 | import { initWatch } from "./state"; 4 | const options = { 5 | data: { 6 | info: { 7 | name: { 8 | firstName: "wind", 9 | secondName: "liang", 10 | }, 11 | }, 12 | }, 13 | watch: { 14 | "info.name": { 15 | handler(newVal, oldVal) { 16 | console.log("收到变化", newVal, oldVal); 17 | }, 18 | deep: true, 19 | }, 20 | }, 21 | }; 22 | observe(options.data); 23 | initWatch(options.data, options.watch); 24 | 25 | options.data.info = { 26 | name: { 27 | firstName: "wind2", 28 | secondName: "liang2", 29 | }, 30 | }; 31 | 32 | // setTimeout(() => { 33 | // options.data.info.name = { 34 | // firstName: "wind2", 35 | // secondName: "liang2", 36 | // }; 37 | // }, 0); 38 | 39 | options.data.info.name = { 40 | firstName: "wind2", 41 | secondName: "liang2", 42 | }; 43 | 44 | setTimeout(() => { 45 | options.data.info.name.firstName = "wind3"; 46 | }, 0); 47 | -------------------------------------------------------------------------------- /code/12.Vue2剥丝抽茧-响应式系统之watch2/dep.js: -------------------------------------------------------------------------------- 1 | import { remove } from "./util"; 2 | 3 | let uid = 0; 4 | 5 | export default class Dep { 6 | static target; //当前在执行的函数 7 | subs; // 依赖的函数 8 | id; // Dep 对象标识 9 | constructor() { 10 | this.id = uid++; 11 | this.subs = []; // 保存所有需要执行的函数 12 | } 13 | 14 | addSub(sub) { 15 | this.subs.push(sub); 16 | } 17 | 18 | removeSub(sub) { 19 | remove(this.subs, sub); 20 | } 21 | depend() { 22 | if (Dep.target) { 23 | // 委托给 Dep.target 去调用 addSub 24 | Dep.target.addDep(this); 25 | } 26 | } 27 | 28 | notify() { 29 | // stabilize the subscriber list first 30 | const subs = this.subs.slice(); 31 | for (let i = 0, l = subs.length; i < l; i++) { 32 | subs[i].update(); 33 | } 34 | } 35 | } 36 | 37 | Dep.target = null; // 静态变量,全局唯一 38 | 39 | // The current target watcher being evaluated. 40 | // This is globally unique because only one watcher 41 | // can be evaluated at a time. 42 | const targetStack = []; 43 | 44 | export function pushTarget(target) { 45 | targetStack.push(target); 46 | Dep.target = target; 47 | } 48 | 49 | export function popTarget() { 50 | targetStack.pop(); 51 | Dep.target = targetStack[targetStack.length - 1]; 52 | } 53 | -------------------------------------------------------------------------------- /code/12.Vue2剥丝抽茧-响应式系统之watch2/env.js: -------------------------------------------------------------------------------- 1 | /* istanbul ignore next */ 2 | export function isNative(Ctor) { 3 | return typeof Ctor === "function" && /native code/.test(Ctor.toString()); 4 | } 5 | -------------------------------------------------------------------------------- /code/12.Vue2剥丝抽茧-响应式系统之watch2/next-tick.js: -------------------------------------------------------------------------------- 1 | import { isNative } from "./env"; 2 | 3 | const callbacks = []; 4 | let pending = false; 5 | 6 | function flushCallbacks() { 7 | pending = false; 8 | const copies = callbacks.slice(0); 9 | callbacks.length = 0; 10 | for (let i = 0; i < copies.length; i++) { 11 | copies[i](); 12 | } 13 | } 14 | 15 | let timerFunc; 16 | 17 | if (typeof Promise !== "undefined" && isNative(Promise)) { 18 | const p = Promise.resolve(); 19 | timerFunc = () => { 20 | p.then(flushCallbacks); 21 | }; 22 | } else { 23 | // Fallback to setTimeout. 24 | timerFunc = () => { 25 | setTimeout(flushCallbacks, 0); 26 | }; 27 | } 28 | 29 | export function nextTick(cb, ctx) { 30 | let _resolve; 31 | callbacks.push(() => { 32 | if (cb) { 33 | cb.call(ctx); 34 | } else if (_resolve) { 35 | _resolve(ctx); 36 | } 37 | }); 38 | if (!pending) { 39 | pending = true; 40 | timerFunc(); // 只执行一次 41 | } 42 | // $flow-disable-line 43 | if (!cb && typeof Promise !== "undefined") { 44 | return new Promise((resolve) => { 45 | _resolve = resolve; 46 | }); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /code/12.Vue2剥丝抽茧-响应式系统之watch2/scheduler.js: -------------------------------------------------------------------------------- 1 | import { nextTick } from "./next-tick"; 2 | const queue = []; 3 | let has = {}; 4 | let waiting = false; 5 | let flushing = false; 6 | let index = 0; 7 | 8 | /** 9 | * Reset the scheduler's state. 10 | */ 11 | function resetSchedulerState() { 12 | index = queue.length = 0; 13 | has = {}; 14 | waiting = flushing = false; 15 | } 16 | 17 | /** 18 | * Flush both queues and run the watchers. 19 | */ 20 | function flushSchedulerQueue() { 21 | flushing = true; 22 | let watcher, id; 23 | // do not cache length because more watchers might be pushed 24 | // as we run existing watchers 25 | for (index = 0; index < queue.length; index++) { 26 | watcher = queue[index]; 27 | if (watcher.before) { 28 | watcher.before(); 29 | } 30 | id = watcher.id; 31 | has[id] = null; 32 | watcher.run(); 33 | } 34 | 35 | resetSchedulerState(); 36 | } 37 | 38 | /** 39 | * Push a watcher into the watcher queue. 40 | * Jobs with duplicate IDs will be skipped unless it's 41 | * pushed when the queue is being flushed. 42 | */ 43 | export function queueWatcher(watcher) { 44 | const id = watcher.id; 45 | if (has[id] == null) { 46 | has[id] = true; 47 | queue.push(watcher); 48 | // queue the flush 49 | if (!waiting) { 50 | waiting = true; 51 | nextTick(flushSchedulerQueue); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /code/12.Vue2剥丝抽茧-响应式系统之watch2/state.js: -------------------------------------------------------------------------------- 1 | import Watcher from "./watcher"; 2 | import { isPlainObject } from "./util"; 3 | 4 | export function initWatch(data, watch) { 5 | for (const key in watch) { 6 | const handler = watch[key]; 7 | if (Array.isArray(handler)) { 8 | for (let i = 0; i < handler.length; i++) { 9 | createWatcher(data, key, handler[i]); 10 | } 11 | } else { 12 | createWatcher(data, key, handler); 13 | } 14 | } 15 | } 16 | 17 | function createWatcher(data, expOrFn, handler, options) { 18 | if (isPlainObject(handler)) { 19 | options = handler; 20 | handler = handler.handler; 21 | } 22 | return $watch(data, expOrFn, handler, options); 23 | } 24 | 25 | function $watch(data, expOrFn, handler, options) { 26 | const watcher = new Watcher(data, expOrFn, handler, options); 27 | if (options.immediate) { 28 | handler.call(data, watcher.value); 29 | } 30 | return function unwatchFn() { 31 | watcher.teardown(); 32 | }; 33 | } 34 | -------------------------------------------------------------------------------- /code/12.Vue2剥丝抽茧-响应式系统之watch2/traverse.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { isObject } from "./util"; 4 | 5 | const seenObjects = new Set(); 6 | 7 | /** 8 | * Recursively traverse an object to evoke all converted 9 | * getters, so that every nested property inside the object 10 | * is collected as a "deep" dependency. 11 | */ 12 | export function traverse(val) { 13 | _traverse(val, seenObjects); 14 | seenObjects.clear(); 15 | } 16 | 17 | function _traverse(val, seen) { 18 | let i, keys; 19 | const isA = Array.isArray(val); 20 | if ((!isA && !isObject(val)) || Object.isFrozen(val)) { 21 | return; 22 | } 23 | if (val.__ob__) { 24 | const depId = val.__ob__.dep.id; 25 | if (seen.has(depId)) { 26 | return; 27 | } 28 | seen.add(depId); 29 | } 30 | if (isA) { 31 | i = val.length; 32 | while (i--) _traverse(val[i], seen); 33 | } else { 34 | keys = Object.keys(val); 35 | i = keys.length; 36 | while (i--) _traverse(val[keys[i]], seen); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /code/13.Vue2剥丝抽茧-响应式系统之computed/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"; 7 | 8 | const arrayProto = Array.prototype; 9 | export const arrayMethods = Object.create(arrayProto); 10 | 11 | const methodsToPatch = [ 12 | "push", 13 | "pop", 14 | "shift", 15 | "unshift", 16 | "splice", 17 | "sort", 18 | "reverse", 19 | ]; 20 | 21 | /** 22 | * Intercept mutating methods and emit events 23 | */ 24 | methodsToPatch.forEach(function (method) { 25 | // cache original method 26 | const original = arrayProto[method]; 27 | def(arrayMethods, method, function mutator(...args) { 28 | const result = original.apply(this, args); 29 | const ob = this.__ob__; 30 | /******新增 *************************/ 31 | let inserted; // 加添加的元素拿到 32 | switch (method) { 33 | case "push": 34 | case "unshift": 35 | inserted = args; 36 | break; 37 | case "splice": 38 | inserted = args.slice(2); 39 | break; 40 | } 41 | if (inserted) ob.observeArray(inserted); 42 | /************************************/ 43 | // notify change 44 | ob.dep.notify(); 45 | return result; 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /code/13.Vue2剥丝抽茧-响应式系统之computed/dep.js: -------------------------------------------------------------------------------- 1 | import { remove } from "./util"; 2 | 3 | let uid = 0; 4 | 5 | export default class Dep { 6 | static target; //当前在执行的函数 7 | subs; // 依赖的函数 8 | id; // Dep 对象标识 9 | constructor() { 10 | this.id = uid++; 11 | this.subs = []; // 保存所有需要执行的函数 12 | } 13 | 14 | addSub(sub) { 15 | this.subs.push(sub); 16 | } 17 | 18 | removeSub(sub) { 19 | remove(this.subs, sub); 20 | } 21 | depend() { 22 | if (Dep.target) { 23 | // 委托给 Dep.target 去调用 addSub 24 | Dep.target.addDep(this); 25 | } 26 | } 27 | 28 | notify() { 29 | // stabilize the subscriber list first 30 | const subs = this.subs.slice(); 31 | for (let i = 0, l = subs.length; i < l; i++) { 32 | subs[i].update(); 33 | } 34 | } 35 | } 36 | 37 | Dep.target = null; // 静态变量,全局唯一 38 | 39 | // The current target watcher being evaluated. 40 | // This is globally unique because only one watcher 41 | // can be evaluated at a time. 42 | const targetStack = []; 43 | 44 | export function pushTarget(target) { 45 | targetStack.push(target); 46 | Dep.target = target; 47 | } 48 | 49 | export function popTarget() { 50 | targetStack.pop(); 51 | Dep.target = targetStack[targetStack.length - 1]; 52 | } 53 | -------------------------------------------------------------------------------- /code/13.Vue2剥丝抽茧-响应式系统之computed/env.js: -------------------------------------------------------------------------------- 1 | /* istanbul ignore next */ 2 | export function isNative(Ctor) { 3 | return typeof Ctor === "function" && /native code/.test(Ctor.toString()); 4 | } 5 | -------------------------------------------------------------------------------- /code/13.Vue2剥丝抽茧-响应式系统之computed/next-tick.js: -------------------------------------------------------------------------------- 1 | import { isNative } from "./env"; 2 | 3 | const callbacks = []; 4 | let pending = false; 5 | 6 | function flushCallbacks() { 7 | pending = false; 8 | const copies = callbacks.slice(0); 9 | callbacks.length = 0; 10 | for (let i = 0; i < copies.length; i++) { 11 | copies[i](); 12 | } 13 | } 14 | 15 | let timerFunc; 16 | 17 | if (typeof Promise !== "undefined" && isNative(Promise)) { 18 | const p = Promise.resolve(); 19 | timerFunc = () => { 20 | p.then(flushCallbacks); 21 | }; 22 | } else { 23 | // Fallback to setTimeout. 24 | timerFunc = () => { 25 | setTimeout(flushCallbacks, 0); 26 | }; 27 | } 28 | 29 | export function nextTick(cb, ctx) { 30 | let _resolve; 31 | callbacks.push(() => { 32 | if (cb) { 33 | cb.call(ctx); 34 | } else if (_resolve) { 35 | _resolve(ctx); 36 | } 37 | }); 38 | if (!pending) { 39 | pending = true; 40 | timerFunc(); // 只执行一次 41 | } 42 | // $flow-disable-line 43 | if (!cb && typeof Promise !== "undefined") { 44 | return new Promise((resolve) => { 45 | _resolve = resolve; 46 | }); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /code/13.Vue2剥丝抽茧-响应式系统之computed/scheduler.js: -------------------------------------------------------------------------------- 1 | import { nextTick } from "./next-tick"; 2 | const queue = []; 3 | let has = {}; 4 | let waiting = false; 5 | let flushing = false; 6 | let index = 0; 7 | 8 | /** 9 | * Reset the scheduler's state. 10 | */ 11 | function resetSchedulerState() { 12 | index = queue.length = 0; 13 | has = {}; 14 | waiting = flushing = false; 15 | } 16 | 17 | /** 18 | * Flush both queues and run the watchers. 19 | */ 20 | function flushSchedulerQueue() { 21 | flushing = true; 22 | let watcher, id; 23 | // do not cache length because more watchers might be pushed 24 | // as we run existing watchers 25 | for (index = 0; index < queue.length; index++) { 26 | watcher = queue[index]; 27 | if (watcher.before) { 28 | watcher.before(); 29 | } 30 | id = watcher.id; 31 | has[id] = null; 32 | watcher.run(); 33 | } 34 | 35 | resetSchedulerState(); 36 | } 37 | 38 | /** 39 | * Push a watcher into the watcher queue. 40 | * Jobs with duplicate IDs will be skipped unless it's 41 | * pushed when the queue is being flushed. 42 | */ 43 | export function queueWatcher(watcher) { 44 | const id = watcher.id; 45 | if (has[id] == null) { 46 | has[id] = true; 47 | queue.push(watcher); 48 | // queue the flush 49 | if (!waiting) { 50 | waiting = true; 51 | nextTick(flushSchedulerQueue); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /code/13.Vue2剥丝抽茧-响应式系统之computed/traverse.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { isObject } from "./util"; 4 | 5 | const seenObjects = new Set(); 6 | 7 | /** 8 | * Recursively traverse an object to evoke all converted 9 | * getters, so that every nested property inside the object 10 | * is collected as a "deep" dependency. 11 | */ 12 | export function traverse(val) { 13 | _traverse(val, seenObjects); 14 | seenObjects.clear(); 15 | } 16 | 17 | function _traverse(val, seen) { 18 | let i, keys; 19 | const isA = Array.isArray(val); 20 | if ((!isA && !isObject(val)) || Object.isFrozen(val)) { 21 | return; 22 | } 23 | if (val.__ob__) { 24 | const depId = val.__ob__.dep.id; 25 | if (seen.has(depId)) { 26 | return; 27 | } 28 | seen.add(depId); 29 | } 30 | if (isA) { 31 | i = val.length; 32 | while (i--) _traverse(val[i], seen); 33 | } else { 34 | keys = Object.keys(val); 35 | i = keys.length; 36 | while (i--) _traverse(val[keys[i]], seen); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /code/15.Vue2剥丝抽茧-虚拟dom/create-element.js: -------------------------------------------------------------------------------- 1 | import VNode, { createEmptyVNode } from "./vnode"; 2 | import { normalizeChildren } from "./normalize-children"; 3 | // wrapper function for providing a more flexible interface 4 | // without getting yelled at by flow 5 | export function createElement(tag, children) { 6 | return _createElement(tag, children); 7 | } 8 | 9 | export function _createElement(tag, children) { 10 | if (!tag) { 11 | // in case of component :is set to falsy value 12 | return createEmptyVNode(); 13 | } 14 | children = normalizeChildren(children); 15 | let vnode = new VNode(tag, null, children); 16 | return vnode; 17 | } 18 | -------------------------------------------------------------------------------- /code/15.Vue2剥丝抽茧-虚拟dom/main copy.js: -------------------------------------------------------------------------------- 1 | import * as nodeOps from "./node-ops"; 2 | const vnode = { tag: "div", children: [{ text: "windliang" }] }; 3 | const children = vnode.children; 4 | const tag = vnode.tag; 5 | vnode.elm = nodeOps.createElement(tag); 6 | 7 | const childVNode = children[0]; 8 | const childEle = nodeOps.createTextNode(childVNode.text); 9 | 10 | nodeOps.appendChild(vnode.elm, childEle); 11 | nodeOps.appendChild(document.body, vnode.elm); 12 | -------------------------------------------------------------------------------- /code/15.Vue2剥丝抽茧-虚拟dom/main.js: -------------------------------------------------------------------------------- 1 | import * as nodeOps from "./node-ops"; 2 | import { createPatchFunction } from "./patch"; 3 | import { createElement } from "./create-element"; 4 | 5 | const options = { 6 | el: "#root", 7 | data: { 8 | text: "hello,liang", 9 | text2: "2", 10 | }, 11 | render(createElement) { 12 | const test = createElement("div", [ 13 | this.text, 14 | createElement("div", this.text2), 15 | ]); 16 | return test; 17 | }, 18 | }; 19 | 20 | const _render = function () { 21 | const vnode = options.render.call(options.data, createElement); 22 | return vnode; 23 | }; 24 | 25 | const $el = document.querySelector(options.el); 26 | 27 | const __patch__ = createPatchFunction({ nodeOps }); 28 | 29 | function _update(vnode) { 30 | __patch__($el, vnode); 31 | } 32 | 33 | _update(_render()); 34 | -------------------------------------------------------------------------------- /code/15.Vue2剥丝抽茧-虚拟dom/node-ops.js: -------------------------------------------------------------------------------- 1 | export function createElement(tagName) { 2 | const elm = document.createElement(tagName); 3 | return elm; 4 | } 5 | 6 | export function createTextNode(text) { 7 | return document.createTextNode(text); 8 | } 9 | 10 | export function insertBefore(parentNode, newNode, referenceNode) { 11 | parentNode.insertBefore(newNode, referenceNode); 12 | } 13 | 14 | export function removeChild(node, child) { 15 | node.removeChild(child); 16 | } 17 | 18 | export function appendChild(node, child) { 19 | node.appendChild(child); 20 | } 21 | 22 | export function parentNode(node) { 23 | return node.parentNode; 24 | } 25 | 26 | export function nextSibling(node) { 27 | return node.nextSibling; 28 | } 29 | 30 | export function tagName(node) { 31 | return node.tagName; 32 | } 33 | 34 | export function setTextContent(node, text) { 35 | node.textContent = text; 36 | } 37 | -------------------------------------------------------------------------------- /code/15.Vue2剥丝抽茧-虚拟dom/normalize-children.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { createTextVNode } from "./vnode"; 4 | import { isUndef, isPrimitive } from "./util"; 5 | 6 | export function normalizeChildren(children) { 7 | return isPrimitive(children) 8 | ? [createTextVNode(children)] 9 | : Array.isArray(children) 10 | ? normalizeArrayChildren(children) 11 | : undefined; 12 | } 13 | 14 | function normalizeArrayChildren(children) { 15 | const res = []; 16 | let i, c; 17 | for (i = 0; i < children.length; i++) { 18 | c = children[i]; 19 | if (isUndef(c) || typeof c === "boolean") continue; 20 | if (isPrimitive(c)) { 21 | if (c !== "") 22 | // convert primitive to vnode 23 | res.push(createTextVNode(c)); 24 | // 省略了很多 if else 25 | } else { 26 | // 走到这里说明当前 c 已经是一个 vnode 节点了 27 | res.push(c); 28 | } 29 | } 30 | return res; 31 | } 32 | -------------------------------------------------------------------------------- /code/15.Vue2剥丝抽茧-虚拟dom/util.js: -------------------------------------------------------------------------------- 1 | // These helpers produce better VM code in JS engines due to their 2 | // explicitness and function inlining. 3 | export function isUndef(v) { 4 | return v === undefined || v === null; 5 | } 6 | 7 | export function isDef(v) { 8 | return v !== undefined && v !== null; 9 | } 10 | 11 | /** 12 | * Check if value is primitive. 13 | */ 14 | export function isPrimitive(value) { 15 | return ( 16 | typeof value === "string" || 17 | typeof value === "number" || 18 | typeof value === "symbol" || 19 | typeof value === "boolean" 20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /code/16.Vue2剥丝抽茧-虚拟dom之绑定事件/create-element.js: -------------------------------------------------------------------------------- 1 | import VNode, { createEmptyVNode } from "./vnode"; 2 | import { normalizeChildren } from "./normalize-children"; 3 | // wrapper function for providing a more flexible interface 4 | // without getting yelled at by flow 5 | export function createElement(tag, data, children) { 6 | return _createElement(tag, data, children); 7 | } 8 | 9 | export function _createElement(tag, data, children) { 10 | if (!tag) { 11 | // in case of component :is set to falsy value 12 | return createEmptyVNode(); 13 | } 14 | children = normalizeChildren(children); 15 | let vnode = new VNode(tag, data, children); 16 | return vnode; 17 | } 18 | -------------------------------------------------------------------------------- /code/16.Vue2剥丝抽茧-虚拟dom之绑定事件/main copy.js: -------------------------------------------------------------------------------- 1 | import * as nodeOps from "./node-ops"; 2 | const vnode = { tag: "div", children: [{ text: "windliang" }] }; 3 | const children = vnode.children; 4 | const tag = vnode.tag; 5 | vnode.elm = nodeOps.createElement(tag); 6 | 7 | const childVNode = children[0]; 8 | const childEle = nodeOps.createTextNode(childVNode.text); 9 | 10 | nodeOps.appendChild(vnode.elm, childEle); 11 | nodeOps.appendChild(document.body, vnode.elm); 12 | -------------------------------------------------------------------------------- /code/16.Vue2剥丝抽茧-虚拟dom之绑定事件/main.js: -------------------------------------------------------------------------------- 1 | import * as nodeOps from "./node-ops"; 2 | import modules from "./modules"; 3 | import { createPatchFunction } from "./patch"; 4 | import { createElement } from "./create-element"; 5 | 6 | const options = { 7 | el: "#root", 8 | data: { 9 | text: "hello,liang", 10 | text2: "2", 11 | }, 12 | render(createElement) { 13 | const test = createElement( 14 | "div", 15 | { 16 | on: { 17 | click: () => console.log(1), 18 | dblclick: () => console.log(2), 19 | }, 20 | }, 21 | [this.text, createElement("div", this.text2)] 22 | ); 23 | return test; 24 | }, 25 | }; 26 | 27 | const _render = function () { 28 | const vnode = options.render.call(options.data, createElement); 29 | return vnode; 30 | }; 31 | 32 | const $el = document.querySelector(options.el); 33 | 34 | const __patch__ = createPatchFunction({ nodeOps, modules }); 35 | 36 | function _update(vnode) { 37 | __patch__($el, vnode); 38 | } 39 | 40 | _update(_render()); 41 | -------------------------------------------------------------------------------- /code/16.Vue2剥丝抽茧-虚拟dom之绑定事件/modules/index.js: -------------------------------------------------------------------------------- 1 | import events from "./events"; 2 | 3 | export default [events]; 4 | -------------------------------------------------------------------------------- /code/16.Vue2剥丝抽茧-虚拟dom之绑定事件/node-ops.js: -------------------------------------------------------------------------------- 1 | export function createElement(tagName) { 2 | const elm = document.createElement(tagName); 3 | return elm; 4 | } 5 | 6 | export function createTextNode(text) { 7 | return document.createTextNode(text); 8 | } 9 | 10 | export function insertBefore(parentNode, newNode, referenceNode) { 11 | parentNode.insertBefore(newNode, referenceNode); 12 | } 13 | 14 | export function removeChild(node, child) { 15 | node.removeChild(child); 16 | } 17 | 18 | export function appendChild(node, child) { 19 | node.appendChild(child); 20 | } 21 | 22 | export function parentNode(node) { 23 | return node.parentNode; 24 | } 25 | 26 | export function nextSibling(node) { 27 | return node.nextSibling; 28 | } 29 | 30 | export function tagName(node) { 31 | return node.tagName; 32 | } 33 | 34 | export function setTextContent(node, text) { 35 | node.textContent = text; 36 | } 37 | -------------------------------------------------------------------------------- /code/16.Vue2剥丝抽茧-虚拟dom之绑定事件/normalize-children.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { createTextVNode } from "./vnode"; 4 | import { isUndef, isPrimitive } from "./util"; 5 | 6 | export function normalizeChildren(children) { 7 | return isPrimitive(children) 8 | ? [createTextVNode(children)] 9 | : Array.isArray(children) 10 | ? normalizeArrayChildren(children) 11 | : undefined; 12 | } 13 | 14 | function normalizeArrayChildren(children) { 15 | const res = []; 16 | let i, c; 17 | for (i = 0; i < children.length; i++) { 18 | c = children[i]; 19 | if (isUndef(c) || typeof c === "boolean") continue; 20 | if (isPrimitive(c)) { 21 | if (c !== "") 22 | // convert primitive to vnode 23 | res.push(createTextVNode(c)); 24 | // 省略了很多 if else 25 | } else { 26 | // 走到这里说明当前 c 已经是一个 vnode 节点了 27 | res.push(c); 28 | } 29 | } 30 | return res; 31 | } 32 | -------------------------------------------------------------------------------- /code/17.Vue2剥丝抽茧-虚拟dom之更新/create-element.js: -------------------------------------------------------------------------------- 1 | import VNode, { createEmptyVNode } from "./vnode"; 2 | import { normalizeChildren } from "./normalize-children"; 3 | import { isPrimitive } from "./util"; 4 | // wrapper function for providing a more flexible interface 5 | // without getting yelled at by flow 6 | export function createElement(tag, data, children) { 7 | return _createElement(tag, data, children); 8 | } 9 | 10 | export function _createElement(tag, data, children) { 11 | if (!tag) { 12 | // in case of component :is set to falsy value 13 | return createEmptyVNode(); 14 | } 15 | if (Array.isArray(data) || isPrimitive(data)) { 16 | children = data; 17 | data = undefined; 18 | } 19 | children = normalizeChildren(children); 20 | let vnode = new VNode(tag, data, children); 21 | return vnode; 22 | } 23 | -------------------------------------------------------------------------------- /code/17.Vue2剥丝抽茧-虚拟dom之更新/main copy.js: -------------------------------------------------------------------------------- 1 | import * as nodeOps from "./node-ops"; 2 | const vnode = { tag: "div", children: [{ text: "windliang" }] }; 3 | const children = vnode.children; 4 | const tag = vnode.tag; 5 | vnode.elm = nodeOps.createElement(tag); 6 | 7 | const childVNode = children[0]; 8 | const childEle = nodeOps.createTextNode(childVNode.text); 9 | 10 | nodeOps.appendChild(vnode.elm, childEle); 11 | nodeOps.appendChild(document.body, vnode.elm); 12 | -------------------------------------------------------------------------------- /code/17.Vue2剥丝抽茧-虚拟dom之更新/modules/index.js: -------------------------------------------------------------------------------- 1 | import events from "./events"; 2 | 3 | export default [events]; 4 | -------------------------------------------------------------------------------- /code/17.Vue2剥丝抽茧-虚拟dom之更新/node-ops.js: -------------------------------------------------------------------------------- 1 | export function createElement(tagName) { 2 | const elm = document.createElement(tagName); 3 | return elm; 4 | } 5 | 6 | export function createTextNode(text) { 7 | return document.createTextNode(text); 8 | } 9 | 10 | export function insertBefore(parentNode, newNode, referenceNode) { 11 | parentNode.insertBefore(newNode, referenceNode); 12 | } 13 | 14 | export function removeChild(node, child) { 15 | node.removeChild(child); 16 | } 17 | 18 | export function appendChild(node, child) { 19 | node.appendChild(child); 20 | } 21 | 22 | export function parentNode(node) { 23 | return node.parentNode; 24 | } 25 | 26 | export function nextSibling(node) { 27 | return node.nextSibling; 28 | } 29 | 30 | export function tagName(node) { 31 | return node.tagName; 32 | } 33 | 34 | export function setTextContent(node, text) { 35 | node.textContent = text; 36 | } 37 | -------------------------------------------------------------------------------- /code/17.Vue2剥丝抽茧-虚拟dom之更新/normalize-children.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { createTextVNode } from "./vnode"; 4 | import { isUndef, isPrimitive } from "./util"; 5 | 6 | export function normalizeChildren(children) { 7 | return isPrimitive(children) 8 | ? [createTextVNode(children)] 9 | : Array.isArray(children) 10 | ? normalizeArrayChildren(children) 11 | : undefined; 12 | } 13 | 14 | function normalizeArrayChildren(children) { 15 | const res = []; 16 | let i, c; 17 | for (i = 0; i < children.length; i++) { 18 | c = children[i]; 19 | if (isUndef(c) || typeof c === "boolean") continue; 20 | if (isPrimitive(c)) { 21 | if (c !== "") 22 | // convert primitive to vnode 23 | res.push(createTextVNode(c)); 24 | // 省略了很多 if else 25 | } else { 26 | // 走到这里说明当前 c 已经是一个 vnode 节点了 27 | res.push(c); 28 | } 29 | } 30 | return res; 31 | } 32 | -------------------------------------------------------------------------------- /code/17.Vue2剥丝抽茧-虚拟dom之更新/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"; 7 | 8 | const arrayProto = Array.prototype; 9 | export const arrayMethods = Object.create(arrayProto); 10 | 11 | const methodsToPatch = [ 12 | "push", 13 | "pop", 14 | "shift", 15 | "unshift", 16 | "splice", 17 | "sort", 18 | "reverse", 19 | ]; 20 | 21 | /** 22 | * Intercept mutating methods and emit events 23 | */ 24 | methodsToPatch.forEach(function (method) { 25 | // cache original method 26 | const original = arrayProto[method]; 27 | def(arrayMethods, method, function mutator(...args) { 28 | const result = original.apply(this, args); 29 | const ob = this.__ob__; 30 | /******新增 *************************/ 31 | let inserted; // 加添加的元素拿到 32 | switch (method) { 33 | case "push": 34 | case "unshift": 35 | inserted = args; 36 | break; 37 | case "splice": 38 | inserted = args.slice(2); 39 | break; 40 | } 41 | if (inserted) ob.observeArray(inserted); 42 | /************************************/ 43 | // notify change 44 | ob.dep.notify(); 45 | return result; 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /code/17.Vue2剥丝抽茧-虚拟dom之更新/observer/dep.js: -------------------------------------------------------------------------------- 1 | import { remove } from "./util"; 2 | 3 | let uid = 0; 4 | 5 | export default class Dep { 6 | static target; //当前在执行的函数 7 | subs; // 依赖的函数 8 | id; // Dep 对象标识 9 | constructor() { 10 | this.id = uid++; 11 | this.subs = []; // 保存所有需要执行的函数 12 | } 13 | 14 | addSub(sub) { 15 | this.subs.push(sub); 16 | } 17 | 18 | removeSub(sub) { 19 | remove(this.subs, sub); 20 | } 21 | depend() { 22 | if (Dep.target) { 23 | // 委托给 Dep.target 去调用 addSub 24 | Dep.target.addDep(this); 25 | } 26 | } 27 | 28 | notify() { 29 | // stabilize the subscriber list first 30 | const subs = this.subs.slice(); 31 | for (let i = 0, l = subs.length; i < l; i++) { 32 | subs[i].update(); 33 | } 34 | } 35 | } 36 | 37 | Dep.target = null; // 静态变量,全局唯一 38 | 39 | // The current target watcher being evaluated. 40 | // This is globally unique because only one watcher 41 | // can be evaluated at a time. 42 | const targetStack = []; 43 | 44 | export function pushTarget(target) { 45 | targetStack.push(target); 46 | Dep.target = target; 47 | } 48 | 49 | export function popTarget() { 50 | targetStack.pop(); 51 | Dep.target = targetStack[targetStack.length - 1]; 52 | } 53 | -------------------------------------------------------------------------------- /code/17.Vue2剥丝抽茧-虚拟dom之更新/observer/env.js: -------------------------------------------------------------------------------- 1 | /* istanbul ignore next */ 2 | export function isNative(Ctor) { 3 | return typeof Ctor === "function" && /native code/.test(Ctor.toString()); 4 | } 5 | -------------------------------------------------------------------------------- /code/17.Vue2剥丝抽茧-虚拟dom之更新/observer/next-tick.js: -------------------------------------------------------------------------------- 1 | import { isNative } from "./env"; 2 | 3 | const callbacks = []; 4 | let pending = false; 5 | 6 | function flushCallbacks() { 7 | pending = false; 8 | const copies = callbacks.slice(0); 9 | callbacks.length = 0; 10 | for (let i = 0; i < copies.length; i++) { 11 | copies[i](); 12 | } 13 | } 14 | 15 | let timerFunc; 16 | 17 | if (typeof Promise !== "undefined" && isNative(Promise)) { 18 | const p = Promise.resolve(); 19 | timerFunc = () => { 20 | p.then(flushCallbacks); 21 | }; 22 | } else { 23 | // Fallback to setTimeout. 24 | timerFunc = () => { 25 | setTimeout(flushCallbacks, 0); 26 | }; 27 | } 28 | 29 | export function nextTick(cb, ctx) { 30 | let _resolve; 31 | callbacks.push(() => { 32 | if (cb) { 33 | cb.call(ctx); 34 | } else if (_resolve) { 35 | _resolve(ctx); 36 | } 37 | }); 38 | if (!pending) { 39 | pending = true; 40 | timerFunc(); // 只执行一次 41 | } 42 | // $flow-disable-line 43 | if (!cb && typeof Promise !== "undefined") { 44 | return new Promise((resolve) => { 45 | _resolve = resolve; 46 | }); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /code/17.Vue2剥丝抽茧-虚拟dom之更新/observer/scheduler.js: -------------------------------------------------------------------------------- 1 | import { nextTick } from "./next-tick"; 2 | const queue = []; 3 | let has = {}; 4 | let waiting = false; 5 | let flushing = false; 6 | let index = 0; 7 | 8 | /** 9 | * Reset the scheduler's state. 10 | */ 11 | function resetSchedulerState() { 12 | index = queue.length = 0; 13 | has = {}; 14 | waiting = flushing = false; 15 | } 16 | 17 | /** 18 | * Flush both queues and run the watchers. 19 | */ 20 | function flushSchedulerQueue() { 21 | flushing = true; 22 | let watcher, id; 23 | // do not cache length because more watchers might be pushed 24 | // as we run existing watchers 25 | for (index = 0; index < queue.length; index++) { 26 | watcher = queue[index]; 27 | if (watcher.before) { 28 | watcher.before(); 29 | } 30 | id = watcher.id; 31 | has[id] = null; 32 | watcher.run(); 33 | } 34 | 35 | resetSchedulerState(); 36 | } 37 | 38 | /** 39 | * Push a watcher into the watcher queue. 40 | * Jobs with duplicate IDs will be skipped unless it's 41 | * pushed when the queue is being flushed. 42 | */ 43 | export function queueWatcher(watcher) { 44 | const id = watcher.id; 45 | if (has[id] == null) { 46 | has[id] = true; 47 | queue.push(watcher); 48 | // queue the flush 49 | if (!waiting) { 50 | waiting = true; 51 | nextTick(flushSchedulerQueue); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /code/17.Vue2剥丝抽茧-虚拟dom之更新/observer/traverse.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { isObject } from "./util"; 4 | 5 | const seenObjects = new Set(); 6 | 7 | /** 8 | * Recursively traverse an object to evoke all converted 9 | * getters, so that every nested property inside the object 10 | * is collected as a "deep" dependency. 11 | */ 12 | export function traverse(val) { 13 | _traverse(val, seenObjects); 14 | seenObjects.clear(); 15 | } 16 | 17 | function _traverse(val, seen) { 18 | let i, keys; 19 | const isA = Array.isArray(val); 20 | if ((!isA && !isObject(val)) || Object.isFrozen(val)) { 21 | return; 22 | } 23 | if (val.__ob__) { 24 | const depId = val.__ob__.dep.id; 25 | if (seen.has(depId)) { 26 | return; 27 | } 28 | seen.add(depId); 29 | } 30 | if (isA) { 31 | i = val.length; 32 | while (i--) _traverse(val[i], seen); 33 | } else { 34 | keys = Object.keys(val); 35 | i = keys.length; 36 | while (i--) _traverse(val[keys[i]], seen); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /code/18.Vue2剥丝抽茧-虚拟dom之移动/create-element.js: -------------------------------------------------------------------------------- 1 | import VNode, { createEmptyVNode } from "./vnode"; 2 | import { normalizeChildren } from "./normalize-children"; 3 | import { isPrimitive } from "./util"; 4 | // wrapper function for providing a more flexible interface 5 | // without getting yelled at by flow 6 | export function createElement(tag, data, children) { 7 | return _createElement(tag, data, children); 8 | } 9 | 10 | export function _createElement(tag, data, children) { 11 | if (!tag) { 12 | // in case of component :is set to falsy value 13 | return createEmptyVNode(); 14 | } 15 | if (Array.isArray(data) || isPrimitive(data)) { 16 | children = data; 17 | data = undefined; 18 | } 19 | children = normalizeChildren(children); 20 | let vnode = new VNode(tag, data, children); 21 | return vnode; 22 | } 23 | -------------------------------------------------------------------------------- /code/18.Vue2剥丝抽茧-虚拟dom之移动/main copy.js: -------------------------------------------------------------------------------- 1 | import * as nodeOps from "./node-ops"; 2 | const vnode = { tag: "div", children: [{ text: "windliang" }] }; 3 | const children = vnode.children; 4 | const tag = vnode.tag; 5 | vnode.elm = nodeOps.createElement(tag); 6 | 7 | const childVNode = children[0]; 8 | const childEle = nodeOps.createTextNode(childVNode.text); 9 | 10 | nodeOps.appendChild(vnode.elm, childEle); 11 | nodeOps.appendChild(document.body, vnode.elm); 12 | -------------------------------------------------------------------------------- /code/18.Vue2剥丝抽茧-虚拟dom之移动/modules/index.js: -------------------------------------------------------------------------------- 1 | import events from "./events"; 2 | 3 | export default [events]; 4 | -------------------------------------------------------------------------------- /code/18.Vue2剥丝抽茧-虚拟dom之移动/node-ops.js: -------------------------------------------------------------------------------- 1 | export function createElement(tagName) { 2 | const elm = document.createElement(tagName); 3 | return elm; 4 | } 5 | 6 | export function createTextNode(text) { 7 | return document.createTextNode(text); 8 | } 9 | 10 | export function insertBefore(parentNode, newNode, referenceNode) { 11 | parentNode.insertBefore(newNode, referenceNode); 12 | } 13 | 14 | export function removeChild(node, child) { 15 | node.removeChild(child); 16 | } 17 | 18 | export function appendChild(node, child) { 19 | node.appendChild(child); 20 | } 21 | 22 | export function parentNode(node) { 23 | return node.parentNode; 24 | } 25 | 26 | export function nextSibling(node) { 27 | return node.nextSibling; 28 | } 29 | 30 | export function tagName(node) { 31 | return node.tagName; 32 | } 33 | 34 | export function setTextContent(node, text) { 35 | node.textContent = text; 36 | } 37 | -------------------------------------------------------------------------------- /code/18.Vue2剥丝抽茧-虚拟dom之移动/normalize-children.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { createTextVNode } from "./vnode"; 4 | import { isUndef, isPrimitive } from "./util"; 5 | 6 | export function normalizeChildren(children) { 7 | return isPrimitive(children) 8 | ? [createTextVNode(children)] 9 | : Array.isArray(children) 10 | ? normalizeArrayChildren(children) 11 | : undefined; 12 | } 13 | 14 | function normalizeArrayChildren(children) { 15 | const res = []; 16 | let i, c; 17 | for (i = 0; i < children.length; i++) { 18 | c = children[i]; 19 | if (isUndef(c) || typeof c === "boolean") continue; 20 | if (isPrimitive(c)) { 21 | if (c !== "") 22 | // convert primitive to vnode 23 | res.push(createTextVNode(c)); 24 | // 省略了很多 if else 25 | } else { 26 | // 走到这里说明当前 c 已经是一个 vnode 节点了 27 | res.push(c); 28 | } 29 | } 30 | return res; 31 | } 32 | -------------------------------------------------------------------------------- /code/18.Vue2剥丝抽茧-虚拟dom之移动/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"; 7 | 8 | const arrayProto = Array.prototype; 9 | export const arrayMethods = Object.create(arrayProto); 10 | 11 | const methodsToPatch = [ 12 | "push", 13 | "pop", 14 | "shift", 15 | "unshift", 16 | "splice", 17 | "sort", 18 | "reverse", 19 | ]; 20 | 21 | /** 22 | * Intercept mutating methods and emit events 23 | */ 24 | methodsToPatch.forEach(function (method) { 25 | // cache original method 26 | const original = arrayProto[method]; 27 | def(arrayMethods, method, function mutator(...args) { 28 | const result = original.apply(this, args); 29 | const ob = this.__ob__; 30 | /******新增 *************************/ 31 | let inserted; // 加添加的元素拿到 32 | switch (method) { 33 | case "push": 34 | case "unshift": 35 | inserted = args; 36 | break; 37 | case "splice": 38 | inserted = args.slice(2); 39 | break; 40 | } 41 | if (inserted) ob.observeArray(inserted); 42 | /************************************/ 43 | // notify change 44 | ob.dep.notify(); 45 | return result; 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /code/18.Vue2剥丝抽茧-虚拟dom之移动/observer/dep.js: -------------------------------------------------------------------------------- 1 | import { remove } from "./util"; 2 | 3 | let uid = 0; 4 | 5 | export default class Dep { 6 | static target; //当前在执行的函数 7 | subs; // 依赖的函数 8 | id; // Dep 对象标识 9 | constructor() { 10 | this.id = uid++; 11 | this.subs = []; // 保存所有需要执行的函数 12 | } 13 | 14 | addSub(sub) { 15 | this.subs.push(sub); 16 | } 17 | 18 | removeSub(sub) { 19 | remove(this.subs, sub); 20 | } 21 | depend() { 22 | if (Dep.target) { 23 | // 委托给 Dep.target 去调用 addSub 24 | Dep.target.addDep(this); 25 | } 26 | } 27 | 28 | notify() { 29 | // stabilize the subscriber list first 30 | const subs = this.subs.slice(); 31 | for (let i = 0, l = subs.length; i < l; i++) { 32 | subs[i].update(); 33 | } 34 | } 35 | } 36 | 37 | Dep.target = null; // 静态变量,全局唯一 38 | 39 | // The current target watcher being evaluated. 40 | // This is globally unique because only one watcher 41 | // can be evaluated at a time. 42 | const targetStack = []; 43 | 44 | export function pushTarget(target) { 45 | targetStack.push(target); 46 | Dep.target = target; 47 | } 48 | 49 | export function popTarget() { 50 | targetStack.pop(); 51 | Dep.target = targetStack[targetStack.length - 1]; 52 | } 53 | -------------------------------------------------------------------------------- /code/18.Vue2剥丝抽茧-虚拟dom之移动/observer/env.js: -------------------------------------------------------------------------------- 1 | /* istanbul ignore next */ 2 | export function isNative(Ctor) { 3 | return typeof Ctor === "function" && /native code/.test(Ctor.toString()); 4 | } 5 | -------------------------------------------------------------------------------- /code/18.Vue2剥丝抽茧-虚拟dom之移动/observer/next-tick.js: -------------------------------------------------------------------------------- 1 | import { isNative } from "./env"; 2 | 3 | const callbacks = []; 4 | let pending = false; 5 | 6 | function flushCallbacks() { 7 | pending = false; 8 | const copies = callbacks.slice(0); 9 | callbacks.length = 0; 10 | for (let i = 0; i < copies.length; i++) { 11 | copies[i](); 12 | } 13 | } 14 | 15 | let timerFunc; 16 | 17 | if (typeof Promise !== "undefined" && isNative(Promise)) { 18 | const p = Promise.resolve(); 19 | timerFunc = () => { 20 | p.then(flushCallbacks); 21 | }; 22 | } else { 23 | // Fallback to setTimeout. 24 | timerFunc = () => { 25 | setTimeout(flushCallbacks, 0); 26 | }; 27 | } 28 | 29 | export function nextTick(cb, ctx) { 30 | let _resolve; 31 | callbacks.push(() => { 32 | if (cb) { 33 | cb.call(ctx); 34 | } else if (_resolve) { 35 | _resolve(ctx); 36 | } 37 | }); 38 | if (!pending) { 39 | pending = true; 40 | timerFunc(); // 只执行一次 41 | } 42 | // $flow-disable-line 43 | if (!cb && typeof Promise !== "undefined") { 44 | return new Promise((resolve) => { 45 | _resolve = resolve; 46 | }); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /code/18.Vue2剥丝抽茧-虚拟dom之移动/observer/scheduler.js: -------------------------------------------------------------------------------- 1 | import { nextTick } from "./next-tick"; 2 | const queue = []; 3 | let has = {}; 4 | let waiting = false; 5 | let flushing = false; 6 | let index = 0; 7 | 8 | /** 9 | * Reset the scheduler's state. 10 | */ 11 | function resetSchedulerState() { 12 | index = queue.length = 0; 13 | has = {}; 14 | waiting = flushing = false; 15 | } 16 | 17 | /** 18 | * Flush both queues and run the watchers. 19 | */ 20 | function flushSchedulerQueue() { 21 | flushing = true; 22 | let watcher, id; 23 | // do not cache length because more watchers might be pushed 24 | // as we run existing watchers 25 | for (index = 0; index < queue.length; index++) { 26 | watcher = queue[index]; 27 | if (watcher.before) { 28 | watcher.before(); 29 | } 30 | id = watcher.id; 31 | has[id] = null; 32 | watcher.run(); 33 | } 34 | 35 | resetSchedulerState(); 36 | } 37 | 38 | /** 39 | * Push a watcher into the watcher queue. 40 | * Jobs with duplicate IDs will be skipped unless it's 41 | * pushed when the queue is being flushed. 42 | */ 43 | export function queueWatcher(watcher) { 44 | const id = watcher.id; 45 | if (has[id] == null) { 46 | has[id] = true; 47 | queue.push(watcher); 48 | // queue the flush 49 | if (!waiting) { 50 | waiting = true; 51 | nextTick(flushSchedulerQueue); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /code/18.Vue2剥丝抽茧-虚拟dom之移动/observer/traverse.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { isObject } from "./util"; 4 | 5 | const seenObjects = new Set(); 6 | 7 | /** 8 | * Recursively traverse an object to evoke all converted 9 | * getters, so that every nested property inside the object 10 | * is collected as a "deep" dependency. 11 | */ 12 | export function traverse(val) { 13 | _traverse(val, seenObjects); 14 | seenObjects.clear(); 15 | } 16 | 17 | function _traverse(val, seen) { 18 | let i, keys; 19 | const isA = Array.isArray(val); 20 | if ((!isA && !isObject(val)) || Object.isFrozen(val)) { 21 | return; 22 | } 23 | if (val.__ob__) { 24 | const depId = val.__ob__.dep.id; 25 | if (seen.has(depId)) { 26 | return; 27 | } 28 | seen.add(depId); 29 | } 30 | if (isA) { 31 | i = val.length; 32 | while (i--) _traverse(val[i], seen); 33 | } else { 34 | keys = Object.keys(val); 35 | i = keys.length; 36 | while (i--) _traverse(val[keys[i]], seen); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /code/19.Vue2剥丝抽茧-虚拟dom之移动优化/create-element.js: -------------------------------------------------------------------------------- 1 | import VNode, { createEmptyVNode } from "./vnode"; 2 | import { normalizeChildren } from "./normalize-children"; 3 | import { isPrimitive } from "./util"; 4 | // wrapper function for providing a more flexible interface 5 | // without getting yelled at by flow 6 | export function createElement(tag, data, children) { 7 | return _createElement(tag, data, children); 8 | } 9 | 10 | export function _createElement(tag, data, children) { 11 | if (!tag) { 12 | // in case of component :is set to falsy value 13 | return createEmptyVNode(); 14 | } 15 | if (Array.isArray(data) || isPrimitive(data)) { 16 | children = data; 17 | data = undefined; 18 | } 19 | children = normalizeChildren(children); 20 | let vnode = new VNode(tag, data, children); 21 | return vnode; 22 | } 23 | -------------------------------------------------------------------------------- /code/19.Vue2剥丝抽茧-虚拟dom之移动优化/main copy.js: -------------------------------------------------------------------------------- 1 | import * as nodeOps from "./node-ops"; 2 | const vnode = { tag: "div", children: [{ text: "windliang" }] }; 3 | const children = vnode.children; 4 | const tag = vnode.tag; 5 | vnode.elm = nodeOps.createElement(tag); 6 | 7 | const childVNode = children[0]; 8 | const childEle = nodeOps.createTextNode(childVNode.text); 9 | 10 | nodeOps.appendChild(vnode.elm, childEle); 11 | nodeOps.appendChild(document.body, vnode.elm); 12 | -------------------------------------------------------------------------------- /code/19.Vue2剥丝抽茧-虚拟dom之移动优化/modules/index.js: -------------------------------------------------------------------------------- 1 | import events from "./events"; 2 | 3 | export default [events]; 4 | -------------------------------------------------------------------------------- /code/19.Vue2剥丝抽茧-虚拟dom之移动优化/node-ops.js: -------------------------------------------------------------------------------- 1 | export function createElement(tagName) { 2 | const elm = document.createElement(tagName); 3 | return elm; 4 | } 5 | 6 | export function createTextNode(text) { 7 | return document.createTextNode(text); 8 | } 9 | 10 | export function insertBefore(parentNode, newNode, referenceNode) { 11 | parentNode.insertBefore(newNode, referenceNode); 12 | } 13 | 14 | export function removeChild(node, child) { 15 | node.removeChild(child); 16 | } 17 | 18 | export function appendChild(node, child) { 19 | node.appendChild(child); 20 | } 21 | 22 | export function parentNode(node) { 23 | return node.parentNode; 24 | } 25 | 26 | export function nextSibling(node) { 27 | return node.nextSibling; 28 | } 29 | 30 | export function tagName(node) { 31 | return node.tagName; 32 | } 33 | 34 | export function setTextContent(node, text) { 35 | node.textContent = text; 36 | } 37 | -------------------------------------------------------------------------------- /code/19.Vue2剥丝抽茧-虚拟dom之移动优化/normalize-children.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { createTextVNode } from "./vnode"; 4 | import { isUndef, isPrimitive } from "./util"; 5 | 6 | export function normalizeChildren(children) { 7 | return isPrimitive(children) 8 | ? [createTextVNode(children)] 9 | : Array.isArray(children) 10 | ? normalizeArrayChildren(children) 11 | : undefined; 12 | } 13 | 14 | function normalizeArrayChildren(children) { 15 | const res = []; 16 | let i, c; 17 | for (i = 0; i < children.length; i++) { 18 | c = children[i]; 19 | if (isUndef(c) || typeof c === "boolean") continue; 20 | if (isPrimitive(c)) { 21 | if (c !== "") 22 | // convert primitive to vnode 23 | res.push(createTextVNode(c)); 24 | // 省略了很多 if else 25 | } else { 26 | // 走到这里说明当前 c 已经是一个 vnode 节点了 27 | res.push(c); 28 | } 29 | } 30 | return res; 31 | } 32 | -------------------------------------------------------------------------------- /code/19.Vue2剥丝抽茧-虚拟dom之移动优化/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"; 7 | 8 | const arrayProto = Array.prototype; 9 | export const arrayMethods = Object.create(arrayProto); 10 | 11 | const methodsToPatch = [ 12 | "push", 13 | "pop", 14 | "shift", 15 | "unshift", 16 | "splice", 17 | "sort", 18 | "reverse", 19 | ]; 20 | 21 | /** 22 | * Intercept mutating methods and emit events 23 | */ 24 | methodsToPatch.forEach(function (method) { 25 | // cache original method 26 | const original = arrayProto[method]; 27 | def(arrayMethods, method, function mutator(...args) { 28 | const result = original.apply(this, args); 29 | const ob = this.__ob__; 30 | /******新增 *************************/ 31 | let inserted; // 加添加的元素拿到 32 | switch (method) { 33 | case "push": 34 | case "unshift": 35 | inserted = args; 36 | break; 37 | case "splice": 38 | inserted = args.slice(2); 39 | break; 40 | } 41 | if (inserted) ob.observeArray(inserted); 42 | /************************************/ 43 | // notify change 44 | ob.dep.notify(); 45 | return result; 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /code/19.Vue2剥丝抽茧-虚拟dom之移动优化/observer/dep.js: -------------------------------------------------------------------------------- 1 | import { remove } from "./util"; 2 | 3 | let uid = 0; 4 | 5 | export default class Dep { 6 | static target; //当前在执行的函数 7 | subs; // 依赖的函数 8 | id; // Dep 对象标识 9 | constructor() { 10 | this.id = uid++; 11 | this.subs = []; // 保存所有需要执行的函数 12 | } 13 | 14 | addSub(sub) { 15 | this.subs.push(sub); 16 | } 17 | 18 | removeSub(sub) { 19 | remove(this.subs, sub); 20 | } 21 | depend() { 22 | if (Dep.target) { 23 | // 委托给 Dep.target 去调用 addSub 24 | Dep.target.addDep(this); 25 | } 26 | } 27 | 28 | notify() { 29 | // stabilize the subscriber list first 30 | const subs = this.subs.slice(); 31 | for (let i = 0, l = subs.length; i < l; i++) { 32 | subs[i].update(); 33 | } 34 | } 35 | } 36 | 37 | Dep.target = null; // 静态变量,全局唯一 38 | 39 | // The current target watcher being evaluated. 40 | // This is globally unique because only one watcher 41 | // can be evaluated at a time. 42 | const targetStack = []; 43 | 44 | export function pushTarget(target) { 45 | targetStack.push(target); 46 | Dep.target = target; 47 | } 48 | 49 | export function popTarget() { 50 | targetStack.pop(); 51 | Dep.target = targetStack[targetStack.length - 1]; 52 | } 53 | -------------------------------------------------------------------------------- /code/19.Vue2剥丝抽茧-虚拟dom之移动优化/observer/env.js: -------------------------------------------------------------------------------- 1 | /* istanbul ignore next */ 2 | export function isNative(Ctor) { 3 | return typeof Ctor === "function" && /native code/.test(Ctor.toString()); 4 | } 5 | -------------------------------------------------------------------------------- /code/19.Vue2剥丝抽茧-虚拟dom之移动优化/observer/next-tick.js: -------------------------------------------------------------------------------- 1 | import { isNative } from "./env"; 2 | 3 | const callbacks = []; 4 | let pending = false; 5 | 6 | function flushCallbacks() { 7 | pending = false; 8 | const copies = callbacks.slice(0); 9 | callbacks.length = 0; 10 | for (let i = 0; i < copies.length; i++) { 11 | copies[i](); 12 | } 13 | } 14 | 15 | let timerFunc; 16 | 17 | if (typeof Promise !== "undefined" && isNative(Promise)) { 18 | const p = Promise.resolve(); 19 | timerFunc = () => { 20 | p.then(flushCallbacks); 21 | }; 22 | } else { 23 | // Fallback to setTimeout. 24 | timerFunc = () => { 25 | setTimeout(flushCallbacks, 0); 26 | }; 27 | } 28 | 29 | export function nextTick(cb, ctx) { 30 | let _resolve; 31 | callbacks.push(() => { 32 | if (cb) { 33 | cb.call(ctx); 34 | } else if (_resolve) { 35 | _resolve(ctx); 36 | } 37 | }); 38 | if (!pending) { 39 | pending = true; 40 | timerFunc(); // 只执行一次 41 | } 42 | // $flow-disable-line 43 | if (!cb && typeof Promise !== "undefined") { 44 | return new Promise((resolve) => { 45 | _resolve = resolve; 46 | }); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /code/19.Vue2剥丝抽茧-虚拟dom之移动优化/observer/scheduler.js: -------------------------------------------------------------------------------- 1 | import { nextTick } from "./next-tick"; 2 | const queue = []; 3 | let has = {}; 4 | let waiting = false; 5 | let flushing = false; 6 | let index = 0; 7 | 8 | /** 9 | * Reset the scheduler's state. 10 | */ 11 | function resetSchedulerState() { 12 | index = queue.length = 0; 13 | has = {}; 14 | waiting = flushing = false; 15 | } 16 | 17 | /** 18 | * Flush both queues and run the watchers. 19 | */ 20 | function flushSchedulerQueue() { 21 | flushing = true; 22 | let watcher, id; 23 | // do not cache length because more watchers might be pushed 24 | // as we run existing watchers 25 | for (index = 0; index < queue.length; index++) { 26 | watcher = queue[index]; 27 | if (watcher.before) { 28 | watcher.before(); 29 | } 30 | id = watcher.id; 31 | has[id] = null; 32 | watcher.run(); 33 | } 34 | 35 | resetSchedulerState(); 36 | } 37 | 38 | /** 39 | * Push a watcher into the watcher queue. 40 | * Jobs with duplicate IDs will be skipped unless it's 41 | * pushed when the queue is being flushed. 42 | */ 43 | export function queueWatcher(watcher) { 44 | const id = watcher.id; 45 | if (has[id] == null) { 46 | has[id] = true; 47 | queue.push(watcher); 48 | // queue the flush 49 | if (!waiting) { 50 | waiting = true; 51 | nextTick(flushSchedulerQueue); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /code/19.Vue2剥丝抽茧-虚拟dom之移动优化/observer/traverse.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { isObject } from "./util"; 4 | 5 | const seenObjects = new Set(); 6 | 7 | /** 8 | * Recursively traverse an object to evoke all converted 9 | * getters, so that every nested property inside the object 10 | * is collected as a "deep" dependency. 11 | */ 12 | export function traverse(val) { 13 | _traverse(val, seenObjects); 14 | seenObjects.clear(); 15 | } 16 | 17 | function _traverse(val, seen) { 18 | let i, keys; 19 | const isA = Array.isArray(val); 20 | if ((!isA && !isObject(val)) || Object.isFrozen(val)) { 21 | return; 22 | } 23 | if (val.__ob__) { 24 | const depId = val.__ob__.dep.id; 25 | if (seen.has(depId)) { 26 | return; 27 | } 28 | seen.add(depId); 29 | } 30 | if (isA) { 31 | i = val.length; 32 | while (i--) _traverse(val[i], seen); 33 | } else { 34 | keys = Object.keys(val); 35 | i = keys.length; 36 | while (i--) _traverse(val[keys[i]], seen); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /code/2.Vue2剥丝抽茧-响应式系统之分支切换/data.js: -------------------------------------------------------------------------------- 1 | // https://vue.windliang.wang/posts/Vue2%E5%89%A5%E4%B8%9D%E6%8A%BD%E8%8C%A7-%E5%93%8D%E5%BA%94%E5%BC%8F%E7%B3%BB%E7%BB%9F%E4%B9%8B%E5%88%86%E6%94%AF%E5%88%87%E6%8D%A2.html 2 | import { observe } from "./reactive"; 3 | import Watcher from "./watcher"; 4 | const data = { 5 | text: "hello, world", 6 | ok: true, 7 | }; 8 | observe(data); 9 | 10 | const updateComponent = () => { 11 | console.log("收到", data.ok ? data.text : "not"); 12 | }; 13 | 14 | new Watcher(updateComponent); // updateComponent 执行一次函数,输出 hello, world 15 | 16 | data.ok = false; // updateComponent 执行一次函数,输出 not 17 | 18 | data.text = "hello, liang"; // updateComponent 会执行吗? 19 | -------------------------------------------------------------------------------- /code/2.Vue2剥丝抽茧-响应式系统之分支切换/dep.js: -------------------------------------------------------------------------------- 1 | import { remove } from "./util"; 2 | 3 | let uid = 0; 4 | 5 | export default class Dep { 6 | static target; //当前在执行的函数 7 | subs; // 依赖的函数 8 | id; // Dep 对象标识 9 | constructor() { 10 | this.id = uid++; 11 | this.subs = []; // 保存所有需要执行的函数 12 | } 13 | 14 | addSub(sub) { 15 | this.subs.push(sub); 16 | } 17 | 18 | removeSub(sub) { 19 | remove(this.subs, sub); 20 | } 21 | depend() { 22 | if (Dep.target) { 23 | // 委托给 Dep.target 去调用 addSub 24 | Dep.target.addDep(this); 25 | } 26 | } 27 | 28 | notify() { 29 | for (let i = 0, l = this.subs.length; i < l; i++) { 30 | this.subs[i].update(); 31 | } 32 | } 33 | } 34 | 35 | Dep.target = null; // 静态变量,全局唯一 36 | -------------------------------------------------------------------------------- /code/2.Vue2剥丝抽茧-响应式系统之分支切换/util.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Remove an item from an array. 3 | */ 4 | export function remove(arr, item) { 5 | if (arr.length) { 6 | const index = arr.indexOf(item); 7 | if (index > -1) { 8 | return arr.splice(index, 1); 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /code/20.Vue2剥丝抽茧-虚拟dom之增删/create-element.js: -------------------------------------------------------------------------------- 1 | import VNode, { createEmptyVNode } from "./vnode"; 2 | import { normalizeChildren } from "./normalize-children"; 3 | import { isPrimitive } from "./util"; 4 | // wrapper function for providing a more flexible interface 5 | // without getting yelled at by flow 6 | export function createElement(tag, data, children) { 7 | return _createElement(tag, data, children); 8 | } 9 | 10 | export function _createElement(tag, data, children) { 11 | if (!tag) { 12 | // in case of component :is set to falsy value 13 | return createEmptyVNode(); 14 | } 15 | if (Array.isArray(data) || isPrimitive(data)) { 16 | children = data; 17 | data = undefined; 18 | } 19 | children = normalizeChildren(children); 20 | let vnode = new VNode(tag, data, children); 21 | return vnode; 22 | } 23 | -------------------------------------------------------------------------------- /code/20.Vue2剥丝抽茧-虚拟dom之增删/main copy.js: -------------------------------------------------------------------------------- 1 | import * as nodeOps from "./node-ops"; 2 | const vnode = { tag: "div", children: [{ text: "windliang" }] }; 3 | const children = vnode.children; 4 | const tag = vnode.tag; 5 | vnode.elm = nodeOps.createElement(tag); 6 | 7 | const childVNode = children[0]; 8 | const childEle = nodeOps.createTextNode(childVNode.text); 9 | 10 | nodeOps.appendChild(vnode.elm, childEle); 11 | nodeOps.appendChild(document.body, vnode.elm); 12 | -------------------------------------------------------------------------------- /code/20.Vue2剥丝抽茧-虚拟dom之增删/modules/index.js: -------------------------------------------------------------------------------- 1 | import events from "./events"; 2 | 3 | export default [events]; 4 | -------------------------------------------------------------------------------- /code/20.Vue2剥丝抽茧-虚拟dom之增删/node-ops.js: -------------------------------------------------------------------------------- 1 | export function createElement(tagName) { 2 | const elm = document.createElement(tagName); 3 | return elm; 4 | } 5 | 6 | export function createTextNode(text) { 7 | return document.createTextNode(text); 8 | } 9 | 10 | export function insertBefore(parentNode, newNode, referenceNode) { 11 | parentNode.insertBefore(newNode, referenceNode); 12 | } 13 | 14 | export function removeChild(node, child) { 15 | node.removeChild(child); 16 | } 17 | 18 | export function appendChild(node, child) { 19 | node.appendChild(child); 20 | } 21 | 22 | export function parentNode(node) { 23 | return node.parentNode; 24 | } 25 | 26 | export function nextSibling(node) { 27 | return node.nextSibling; 28 | } 29 | 30 | export function tagName(node) { 31 | return node.tagName; 32 | } 33 | 34 | export function setTextContent(node, text) { 35 | node.textContent = text; 36 | } 37 | -------------------------------------------------------------------------------- /code/20.Vue2剥丝抽茧-虚拟dom之增删/normalize-children.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { createTextVNode } from "./vnode"; 4 | import { isUndef, isPrimitive } from "./util"; 5 | 6 | export function normalizeChildren(children) { 7 | return isPrimitive(children) 8 | ? [createTextVNode(children)] 9 | : Array.isArray(children) 10 | ? normalizeArrayChildren(children) 11 | : undefined; 12 | } 13 | 14 | function normalizeArrayChildren(children) { 15 | const res = []; 16 | let i, c; 17 | for (i = 0; i < children.length; i++) { 18 | c = children[i]; 19 | if (isUndef(c) || typeof c === "boolean") continue; 20 | if (isPrimitive(c)) { 21 | if (c !== "") 22 | // convert primitive to vnode 23 | res.push(createTextVNode(c)); 24 | // 省略了很多 if else 25 | } else { 26 | // 走到这里说明当前 c 已经是一个 vnode 节点了 27 | res.push(c); 28 | } 29 | } 30 | return res; 31 | } 32 | -------------------------------------------------------------------------------- /code/20.Vue2剥丝抽茧-虚拟dom之增删/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"; 7 | 8 | const arrayProto = Array.prototype; 9 | export const arrayMethods = Object.create(arrayProto); 10 | 11 | const methodsToPatch = [ 12 | "push", 13 | "pop", 14 | "shift", 15 | "unshift", 16 | "splice", 17 | "sort", 18 | "reverse", 19 | ]; 20 | 21 | /** 22 | * Intercept mutating methods and emit events 23 | */ 24 | methodsToPatch.forEach(function (method) { 25 | // cache original method 26 | const original = arrayProto[method]; 27 | def(arrayMethods, method, function mutator(...args) { 28 | const result = original.apply(this, args); 29 | const ob = this.__ob__; 30 | /******新增 *************************/ 31 | let inserted; // 加添加的元素拿到 32 | switch (method) { 33 | case "push": 34 | case "unshift": 35 | inserted = args; 36 | break; 37 | case "splice": 38 | inserted = args.slice(2); 39 | break; 40 | } 41 | if (inserted) ob.observeArray(inserted); 42 | /************************************/ 43 | // notify change 44 | ob.dep.notify(); 45 | return result; 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /code/20.Vue2剥丝抽茧-虚拟dom之增删/observer/dep.js: -------------------------------------------------------------------------------- 1 | import { remove } from "./util"; 2 | 3 | let uid = 0; 4 | 5 | export default class Dep { 6 | static target; //当前在执行的函数 7 | subs; // 依赖的函数 8 | id; // Dep 对象标识 9 | constructor() { 10 | this.id = uid++; 11 | this.subs = []; // 保存所有需要执行的函数 12 | } 13 | 14 | addSub(sub) { 15 | this.subs.push(sub); 16 | } 17 | 18 | removeSub(sub) { 19 | remove(this.subs, sub); 20 | } 21 | depend() { 22 | if (Dep.target) { 23 | // 委托给 Dep.target 去调用 addSub 24 | Dep.target.addDep(this); 25 | } 26 | } 27 | 28 | notify() { 29 | // stabilize the subscriber list first 30 | const subs = this.subs.slice(); 31 | for (let i = 0, l = subs.length; i < l; i++) { 32 | subs[i].update(); 33 | } 34 | } 35 | } 36 | 37 | Dep.target = null; // 静态变量,全局唯一 38 | 39 | // The current target watcher being evaluated. 40 | // This is globally unique because only one watcher 41 | // can be evaluated at a time. 42 | const targetStack = []; 43 | 44 | export function pushTarget(target) { 45 | targetStack.push(target); 46 | Dep.target = target; 47 | } 48 | 49 | export function popTarget() { 50 | targetStack.pop(); 51 | Dep.target = targetStack[targetStack.length - 1]; 52 | } 53 | -------------------------------------------------------------------------------- /code/20.Vue2剥丝抽茧-虚拟dom之增删/observer/env.js: -------------------------------------------------------------------------------- 1 | /* istanbul ignore next */ 2 | export function isNative(Ctor) { 3 | return typeof Ctor === "function" && /native code/.test(Ctor.toString()); 4 | } 5 | -------------------------------------------------------------------------------- /code/20.Vue2剥丝抽茧-虚拟dom之增删/observer/next-tick.js: -------------------------------------------------------------------------------- 1 | import { isNative } from "./env"; 2 | 3 | const callbacks = []; 4 | let pending = false; 5 | 6 | function flushCallbacks() { 7 | pending = false; 8 | const copies = callbacks.slice(0); 9 | callbacks.length = 0; 10 | for (let i = 0; i < copies.length; i++) { 11 | copies[i](); 12 | } 13 | } 14 | 15 | let timerFunc; 16 | 17 | if (typeof Promise !== "undefined" && isNative(Promise)) { 18 | const p = Promise.resolve(); 19 | timerFunc = () => { 20 | p.then(flushCallbacks); 21 | }; 22 | } else { 23 | // Fallback to setTimeout. 24 | timerFunc = () => { 25 | setTimeout(flushCallbacks, 0); 26 | }; 27 | } 28 | 29 | export function nextTick(cb, ctx) { 30 | let _resolve; 31 | callbacks.push(() => { 32 | if (cb) { 33 | cb.call(ctx); 34 | } else if (_resolve) { 35 | _resolve(ctx); 36 | } 37 | }); 38 | if (!pending) { 39 | pending = true; 40 | timerFunc(); // 只执行一次 41 | } 42 | // $flow-disable-line 43 | if (!cb && typeof Promise !== "undefined") { 44 | return new Promise((resolve) => { 45 | _resolve = resolve; 46 | }); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /code/20.Vue2剥丝抽茧-虚拟dom之增删/observer/scheduler.js: -------------------------------------------------------------------------------- 1 | import { nextTick } from "./next-tick"; 2 | const queue = []; 3 | let has = {}; 4 | let waiting = false; 5 | let flushing = false; 6 | let index = 0; 7 | 8 | /** 9 | * Reset the scheduler's state. 10 | */ 11 | function resetSchedulerState() { 12 | index = queue.length = 0; 13 | has = {}; 14 | waiting = flushing = false; 15 | } 16 | 17 | /** 18 | * Flush both queues and run the watchers. 19 | */ 20 | function flushSchedulerQueue() { 21 | flushing = true; 22 | let watcher, id; 23 | // do not cache length because more watchers might be pushed 24 | // as we run existing watchers 25 | for (index = 0; index < queue.length; index++) { 26 | watcher = queue[index]; 27 | if (watcher.before) { 28 | watcher.before(); 29 | } 30 | id = watcher.id; 31 | has[id] = null; 32 | watcher.run(); 33 | } 34 | 35 | resetSchedulerState(); 36 | } 37 | 38 | /** 39 | * Push a watcher into the watcher queue. 40 | * Jobs with duplicate IDs will be skipped unless it's 41 | * pushed when the queue is being flushed. 42 | */ 43 | export function queueWatcher(watcher) { 44 | const id = watcher.id; 45 | if (has[id] == null) { 46 | has[id] = true; 47 | queue.push(watcher); 48 | // queue the flush 49 | if (!waiting) { 50 | waiting = true; 51 | nextTick(flushSchedulerQueue); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /code/20.Vue2剥丝抽茧-虚拟dom之增删/observer/traverse.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { isObject } from "./util"; 4 | 5 | const seenObjects = new Set(); 6 | 7 | /** 8 | * Recursively traverse an object to evoke all converted 9 | * getters, so that every nested property inside the object 10 | * is collected as a "deep" dependency. 11 | */ 12 | export function traverse(val) { 13 | _traverse(val, seenObjects); 14 | seenObjects.clear(); 15 | } 16 | 17 | function _traverse(val, seen) { 18 | let i, keys; 19 | const isA = Array.isArray(val); 20 | if ((!isA && !isObject(val)) || Object.isFrozen(val)) { 21 | return; 22 | } 23 | if (val.__ob__) { 24 | const depId = val.__ob__.dep.id; 25 | if (seen.has(depId)) { 26 | return; 27 | } 28 | seen.add(depId); 29 | } 30 | if (isA) { 31 | i = val.length; 32 | while (i--) _traverse(val[i], seen); 33 | } else { 34 | keys = Object.keys(val); 35 | i = keys.length; 36 | while (i--) _traverse(val[keys[i]], seen); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /code/21.Vue2剥丝抽茧-虚拟dom之组件(废)/config.js: -------------------------------------------------------------------------------- 1 | import { isReservedTag } from "./webutil"; 2 | 3 | export default { 4 | /** 5 | * Check if a tag is reserved so that it cannot be registered as a 6 | * component. This is platform-dependent and may be overwritten. 7 | */ 8 | isReservedTag, 9 | }; 10 | -------------------------------------------------------------------------------- /code/21.Vue2剥丝抽茧-虚拟dom之组件(废)/create-component.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import VNode from "./vnode"; 4 | import { extractPropsFromVNodeData } from "./extract-props"; 5 | 6 | export function createComponent(Ctor, data, context, children, tag) { 7 | if (isUndef(Ctor)) { 8 | return; 9 | } 10 | data = data || {}; 11 | 12 | // extract props 13 | const propsData = extractPropsFromVNodeData(data, Ctor, tag); 14 | 15 | // extract listeners, since these needs to be treated as 16 | // child component listeners instead of DOM listeners 17 | const listeners = data.on; 18 | 19 | // replace with listeners with .native modifier 20 | // so it gets processed during parent component patch. 21 | data.on = data.nativeOn; 22 | 23 | // return a placeholder vnode 24 | const name = Ctor.options.name || tag; 25 | const vnode = new VNode( 26 | `vue-component-${Ctor.cid}${name ? `-${name}` : ""}`, 27 | data, 28 | undefined, 29 | undefined, 30 | undefined, 31 | context, 32 | { Ctor, propsData, listeners, tag, children }, 33 | asyncFactory 34 | ); 35 | return vnode; 36 | } 37 | -------------------------------------------------------------------------------- /code/21.Vue2剥丝抽茧-虚拟dom之组件(废)/create-element.js: -------------------------------------------------------------------------------- 1 | import VNode, { createEmptyVNode } from "./vnode"; 2 | import { normalizeChildren } from "./normalize-children"; 3 | import { isPrimitive, resolveAsset, isDef } from "./util"; 4 | import config from "./config"; 5 | 6 | // wrapper function for providing a more flexible interface 7 | // without getting yelled at by flow 8 | export function createElement(tag, data, children) { 9 | return _createElement(tag, data, children); 10 | } 11 | 12 | export function _createElement(tag, data, children) { 13 | if (!tag) { 14 | // in case of component :is set to falsy value 15 | return createEmptyVNode(); 16 | } 17 | if (Array.isArray(data) || isPrimitive(data)) { 18 | children = data; 19 | data = undefined; 20 | } 21 | children = normalizeChildren(children); 22 | if (typeof tag === "string") { 23 | let Ctor; 24 | if (config.isReservedTag(tag)) { 25 | vnode = new VNode(tag, data, children); 26 | } else if ( 27 | (!data || !data.pre) && 28 | isDef((Ctor = resolveAsset(context.$options, "components", tag))) 29 | ) { 30 | // component 31 | vnode = createComponent(Ctor, data, context, children, tag); 32 | } 33 | } 34 | return vnode; 35 | } 36 | -------------------------------------------------------------------------------- /code/21.Vue2剥丝抽茧-虚拟dom之组件(废)/extract-props.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { hasOwn, isDef, isUndef, hyphenate } from "./util"; 4 | 5 | export function extractPropsFromVNodeData(data, Ctor, tag) { 6 | // we are only extracting raw values here. 7 | // validation and default values are handled in the child 8 | // component itself. 9 | const propOptions = Ctor.options.props; 10 | if (isUndef(propOptions)) { 11 | return; 12 | } 13 | const res = {}; 14 | const { attrs, props } = data; 15 | if (isDef(attrs) || isDef(props)) { 16 | for (const key in propOptions) { 17 | const altKey = hyphenate(key); 18 | checkProp(res, props, key, altKey, true) || 19 | checkProp(res, attrs, key, altKey, false); 20 | } 21 | } 22 | return res; 23 | } 24 | 25 | function checkProp(res, hash, key, altKey, preserve) { 26 | if (isDef(hash)) { 27 | if (hasOwn(hash, key)) { 28 | res[key] = hash[key]; 29 | if (!preserve) { 30 | delete hash[key]; 31 | } 32 | return true; 33 | } else if (hasOwn(hash, altKey)) { 34 | res[key] = hash[altKey]; 35 | if (!preserve) { 36 | delete hash[altKey]; 37 | } 38 | return true; 39 | } 40 | } 41 | return false; 42 | } 43 | -------------------------------------------------------------------------------- /code/21.Vue2剥丝抽茧-虚拟dom之组件(废)/helpers/get-first-component-child.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { isDef } from 'shared/util' 4 | import { isAsyncPlaceholder } from './is-async-placeholder' 5 | 6 | export function getFirstComponentChild (children: ?Array): ?VNode { 7 | if (Array.isArray(children)) { 8 | for (let i = 0; i < children.length; i++) { 9 | const c = children[i] 10 | if (isDef(c) && (isDef(c.componentOptions) || isAsyncPlaceholder(c))) { 11 | return c 12 | } 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /code/21.Vue2剥丝抽茧-虚拟dom之组件(废)/helpers/index.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | export * from './merge-hook' 4 | export * from './extract-props' 5 | export * from './update-listeners' 6 | export * from './normalize-children' 7 | export * from './resolve-async-component' 8 | export * from './get-first-component-child' 9 | export * from './is-async-placeholder' 10 | -------------------------------------------------------------------------------- /code/21.Vue2剥丝抽茧-虚拟dom之组件(废)/helpers/is-async-placeholder.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | export function isAsyncPlaceholder (node: VNode): boolean { 4 | return node.isComment && node.asyncFactory 5 | } 6 | -------------------------------------------------------------------------------- /code/21.Vue2剥丝抽茧-虚拟dom之组件(废)/helpers/merge-hook.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import VNode from '../vnode' 4 | import { createFnInvoker } from './update-listeners' 5 | import { remove, isDef, isUndef, isTrue } from 'shared/util' 6 | 7 | export function mergeVNodeHook (def: Object, hookKey: string, hook: Function) { 8 | if (def instanceof VNode) { 9 | def = def.data.hook || (def.data.hook = {}) 10 | } 11 | let invoker 12 | const oldHook = def[hookKey] 13 | 14 | function wrappedHook () { 15 | hook.apply(this, arguments) 16 | // important: remove merged hook to ensure it's called only once 17 | // and prevent memory leak 18 | remove(invoker.fns, wrappedHook) 19 | } 20 | 21 | if (isUndef(oldHook)) { 22 | // no existing hook 23 | invoker = createFnInvoker([wrappedHook]) 24 | } else { 25 | /* istanbul ignore if */ 26 | if (isDef(oldHook.fns) && isTrue(oldHook.merged)) { 27 | // already a merged invoker 28 | invoker = oldHook 29 | invoker.fns.push(wrappedHook) 30 | } else { 31 | // existing plain hook 32 | invoker = createFnInvoker([oldHook, wrappedHook]) 33 | } 34 | } 35 | 36 | invoker.merged = true 37 | def[hookKey] = invoker 38 | } 39 | -------------------------------------------------------------------------------- /code/21.Vue2剥丝抽茧-虚拟dom之组件(废)/main copy.js: -------------------------------------------------------------------------------- 1 | import * as nodeOps from "./node-ops"; 2 | const vnode = { tag: "div", children: [{ text: "windliang" }] }; 3 | const children = vnode.children; 4 | const tag = vnode.tag; 5 | vnode.elm = nodeOps.createElement(tag); 6 | 7 | const childVNode = children[0]; 8 | const childEle = nodeOps.createTextNode(childVNode.text); 9 | 10 | nodeOps.appendChild(vnode.elm, childEle); 11 | nodeOps.appendChild(document.body, vnode.elm); 12 | -------------------------------------------------------------------------------- /code/21.Vue2剥丝抽茧-虚拟dom之组件(废)/modules/index.js: -------------------------------------------------------------------------------- 1 | import events from "./events"; 2 | 3 | export default [events]; 4 | -------------------------------------------------------------------------------- /code/21.Vue2剥丝抽茧-虚拟dom之组件(废)/node-ops.js: -------------------------------------------------------------------------------- 1 | export function createElement(tagName) { 2 | const elm = document.createElement(tagName); 3 | return elm; 4 | } 5 | 6 | export function createTextNode(text) { 7 | return document.createTextNode(text); 8 | } 9 | 10 | export function insertBefore(parentNode, newNode, referenceNode) { 11 | parentNode.insertBefore(newNode, referenceNode); 12 | } 13 | 14 | export function removeChild(node, child) { 15 | node.removeChild(child); 16 | } 17 | 18 | export function appendChild(node, child) { 19 | node.appendChild(child); 20 | } 21 | 22 | export function parentNode(node) { 23 | return node.parentNode; 24 | } 25 | 26 | export function nextSibling(node) { 27 | return node.nextSibling; 28 | } 29 | 30 | export function tagName(node) { 31 | return node.tagName; 32 | } 33 | 34 | export function setTextContent(node, text) { 35 | node.textContent = text; 36 | } 37 | -------------------------------------------------------------------------------- /code/21.Vue2剥丝抽茧-虚拟dom之组件(废)/normalize-children.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { createTextVNode } from "./vnode"; 4 | import { isUndef, isPrimitive } from "./util"; 5 | 6 | export function normalizeChildren(children) { 7 | return isPrimitive(children) 8 | ? [createTextVNode(children)] 9 | : Array.isArray(children) 10 | ? normalizeArrayChildren(children) 11 | : undefined; 12 | } 13 | 14 | function normalizeArrayChildren(children) { 15 | const res = []; 16 | let i, c; 17 | for (i = 0; i < children.length; i++) { 18 | c = children[i]; 19 | if (isUndef(c) || typeof c === "boolean") continue; 20 | if (isPrimitive(c)) { 21 | if (c !== "") 22 | // convert primitive to vnode 23 | res.push(createTextVNode(c)); 24 | // 省略了很多 if else 25 | } else { 26 | // 走到这里说明当前 c 已经是一个 vnode 节点了 27 | res.push(c); 28 | } 29 | } 30 | return res; 31 | } 32 | -------------------------------------------------------------------------------- /code/21.Vue2剥丝抽茧-虚拟dom之组件(废)/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"; 7 | 8 | const arrayProto = Array.prototype; 9 | export const arrayMethods = Object.create(arrayProto); 10 | 11 | const methodsToPatch = [ 12 | "push", 13 | "pop", 14 | "shift", 15 | "unshift", 16 | "splice", 17 | "sort", 18 | "reverse", 19 | ]; 20 | 21 | /** 22 | * Intercept mutating methods and emit events 23 | */ 24 | methodsToPatch.forEach(function (method) { 25 | // cache original method 26 | const original = arrayProto[method]; 27 | def(arrayMethods, method, function mutator(...args) { 28 | const result = original.apply(this, args); 29 | const ob = this.__ob__; 30 | /******新增 *************************/ 31 | let inserted; // 加添加的元素拿到 32 | switch (method) { 33 | case "push": 34 | case "unshift": 35 | inserted = args; 36 | break; 37 | case "splice": 38 | inserted = args.slice(2); 39 | break; 40 | } 41 | if (inserted) ob.observeArray(inserted); 42 | /************************************/ 43 | // notify change 44 | ob.dep.notify(); 45 | return result; 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /code/21.Vue2剥丝抽茧-虚拟dom之组件(废)/observer/dep.js: -------------------------------------------------------------------------------- 1 | import { remove } from "./util"; 2 | 3 | let uid = 0; 4 | 5 | export default class Dep { 6 | static target; //当前在执行的函数 7 | subs; // 依赖的函数 8 | id; // Dep 对象标识 9 | constructor() { 10 | this.id = uid++; 11 | this.subs = []; // 保存所有需要执行的函数 12 | } 13 | 14 | addSub(sub) { 15 | this.subs.push(sub); 16 | } 17 | 18 | removeSub(sub) { 19 | remove(this.subs, sub); 20 | } 21 | depend() { 22 | if (Dep.target) { 23 | // 委托给 Dep.target 去调用 addSub 24 | Dep.target.addDep(this); 25 | } 26 | } 27 | 28 | notify() { 29 | // stabilize the subscriber list first 30 | const subs = this.subs.slice(); 31 | for (let i = 0, l = subs.length; i < l; i++) { 32 | subs[i].update(); 33 | } 34 | } 35 | } 36 | 37 | Dep.target = null; // 静态变量,全局唯一 38 | 39 | // The current target watcher being evaluated. 40 | // This is globally unique because only one watcher 41 | // can be evaluated at a time. 42 | const targetStack = []; 43 | 44 | export function pushTarget(target) { 45 | targetStack.push(target); 46 | Dep.target = target; 47 | } 48 | 49 | export function popTarget() { 50 | targetStack.pop(); 51 | Dep.target = targetStack[targetStack.length - 1]; 52 | } 53 | -------------------------------------------------------------------------------- /code/21.Vue2剥丝抽茧-虚拟dom之组件(废)/observer/env.js: -------------------------------------------------------------------------------- 1 | /* istanbul ignore next */ 2 | export function isNative(Ctor) { 3 | return typeof Ctor === "function" && /native code/.test(Ctor.toString()); 4 | } 5 | -------------------------------------------------------------------------------- /code/21.Vue2剥丝抽茧-虚拟dom之组件(废)/observer/next-tick.js: -------------------------------------------------------------------------------- 1 | import { isNative } from "./env"; 2 | 3 | const callbacks = []; 4 | let pending = false; 5 | 6 | function flushCallbacks() { 7 | pending = false; 8 | const copies = callbacks.slice(0); 9 | callbacks.length = 0; 10 | for (let i = 0; i < copies.length; i++) { 11 | copies[i](); 12 | } 13 | } 14 | 15 | let timerFunc; 16 | 17 | if (typeof Promise !== "undefined" && isNative(Promise)) { 18 | const p = Promise.resolve(); 19 | timerFunc = () => { 20 | p.then(flushCallbacks); 21 | }; 22 | } else { 23 | // Fallback to setTimeout. 24 | timerFunc = () => { 25 | setTimeout(flushCallbacks, 0); 26 | }; 27 | } 28 | 29 | export function nextTick(cb, ctx) { 30 | let _resolve; 31 | callbacks.push(() => { 32 | if (cb) { 33 | cb.call(ctx); 34 | } else if (_resolve) { 35 | _resolve(ctx); 36 | } 37 | }); 38 | if (!pending) { 39 | pending = true; 40 | timerFunc(); // 只执行一次 41 | } 42 | // $flow-disable-line 43 | if (!cb && typeof Promise !== "undefined") { 44 | return new Promise((resolve) => { 45 | _resolve = resolve; 46 | }); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /code/21.Vue2剥丝抽茧-虚拟dom之组件(废)/observer/scheduler.js: -------------------------------------------------------------------------------- 1 | import { nextTick } from "./next-tick"; 2 | const queue = []; 3 | let has = {}; 4 | let waiting = false; 5 | let flushing = false; 6 | let index = 0; 7 | 8 | /** 9 | * Reset the scheduler's state. 10 | */ 11 | function resetSchedulerState() { 12 | index = queue.length = 0; 13 | has = {}; 14 | waiting = flushing = false; 15 | } 16 | 17 | /** 18 | * Flush both queues and run the watchers. 19 | */ 20 | function flushSchedulerQueue() { 21 | flushing = true; 22 | let watcher, id; 23 | // do not cache length because more watchers might be pushed 24 | // as we run existing watchers 25 | for (index = 0; index < queue.length; index++) { 26 | watcher = queue[index]; 27 | if (watcher.before) { 28 | watcher.before(); 29 | } 30 | id = watcher.id; 31 | has[id] = null; 32 | watcher.run(); 33 | } 34 | 35 | resetSchedulerState(); 36 | } 37 | 38 | /** 39 | * Push a watcher into the watcher queue. 40 | * Jobs with duplicate IDs will be skipped unless it's 41 | * pushed when the queue is being flushed. 42 | */ 43 | export function queueWatcher(watcher) { 44 | const id = watcher.id; 45 | if (has[id] == null) { 46 | has[id] = true; 47 | queue.push(watcher); 48 | // queue the flush 49 | if (!waiting) { 50 | waiting = true; 51 | nextTick(flushSchedulerQueue); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /code/21.Vue2剥丝抽茧-虚拟dom之组件(废)/observer/traverse.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { isObject } from "./util"; 4 | 5 | const seenObjects = new Set(); 6 | 7 | /** 8 | * Recursively traverse an object to evoke all converted 9 | * getters, so that every nested property inside the object 10 | * is collected as a "deep" dependency. 11 | */ 12 | export function traverse(val) { 13 | _traverse(val, seenObjects); 14 | seenObjects.clear(); 15 | } 16 | 17 | function _traverse(val, seen) { 18 | let i, keys; 19 | const isA = Array.isArray(val); 20 | if ((!isA && !isObject(val)) || Object.isFrozen(val)) { 21 | return; 22 | } 23 | if (val.__ob__) { 24 | const depId = val.__ob__.dep.id; 25 | if (seen.has(depId)) { 26 | return; 27 | } 28 | seen.add(depId); 29 | } 30 | if (isA) { 31 | i = val.length; 32 | while (i--) _traverse(val[i], seen); 33 | } else { 34 | keys = Object.keys(val); 35 | i = keys.length; 36 | while (i--) _traverse(val[keys[i]], seen); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /code/21.Vue2剥丝抽茧-虚拟dom之组件(废)/ref.js: -------------------------------------------------------------------------------- 1 | import { remove, isDef } from "./util"; 2 | 3 | export function registerRef(vnode, isRemoval) { 4 | const key = vnode.data.ref; 5 | if (!isDef(key)) return; 6 | 7 | const vm = vnode.context; 8 | const ref = vnode.componentInstance || vnode.elm; 9 | const refs = vm.$refs; 10 | if (isRemoval) { 11 | if (Array.isArray(refs[key])) { 12 | remove(refs[key], ref); 13 | } else if (refs[key] === ref) { 14 | refs[key] = undefined; 15 | } 16 | } else { 17 | if (vnode.data.refInFor) { 18 | if (!Array.isArray(refs[key])) { 19 | refs[key] = [ref]; 20 | } else if (refs[key].indexOf(ref) < 0) { 21 | // $flow-disable-line 22 | refs[key].push(ref); 23 | } 24 | } else { 25 | refs[key] = ref; 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /code/22.Vue2剥丝抽茧-虚拟dom之组件/src/core/global-api/index.js: -------------------------------------------------------------------------------- 1 | import { set, del } from "../observer/index"; 2 | import config from "../config"; 3 | import { initExtend } from "./extend"; 4 | 5 | import { nextTick } from "../util/index"; 6 | 7 | export function initGlobalAPI(Vue) { 8 | // config 9 | const configDef = {}; 10 | configDef.get = () => config; 11 | if (process.env.NODE_ENV !== "production") { 12 | configDef.set = () => { 13 | console.warn( 14 | "Do not replace the Vue.config object, set individual fields instead." 15 | ); 16 | }; 17 | } 18 | Vue.options = Object.create(null); 19 | Object.defineProperty(Vue, "config", configDef); 20 | // this is used to identify the "base" constructor to extend all plain-object 21 | // components with in Weex's multi-instance scenarios. 22 | Vue.options._base = Vue; 23 | Vue.set = set; 24 | Vue.delete = del; 25 | Vue.nextTick = nextTick; 26 | 27 | initExtend(Vue); 28 | } 29 | -------------------------------------------------------------------------------- /code/22.Vue2剥丝抽茧-虚拟dom之组件/src/core/index.js: -------------------------------------------------------------------------------- 1 | import Vue from './instance/index' 2 | import { initGlobalAPI } from './global-api/index' 3 | 4 | initGlobalAPI(Vue) 5 | 6 | export default Vue 7 | -------------------------------------------------------------------------------- /code/22.Vue2剥丝抽茧-虚拟dom之组件/src/core/instance/index.js: -------------------------------------------------------------------------------- 1 | import { initMixin } from "./init"; 2 | import { stateMixin } from "./state"; 3 | import { lifecycleMixin } from "./lifecycle"; 4 | import { renderMixin } from "./render"; 5 | 6 | function Vue(options) { 7 | this._init(options); 8 | } 9 | 10 | initMixin(Vue); 11 | stateMixin(Vue); 12 | lifecycleMixin(Vue); 13 | renderMixin(Vue); 14 | 15 | export default Vue; 16 | -------------------------------------------------------------------------------- /code/22.Vue2剥丝抽茧-虚拟dom之组件/src/core/instance/render.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { nextTick } from "../util/index"; 4 | import { createElement } from "../vdom/create-element"; 5 | 6 | export function initRender(vm) { 7 | const options = vm.$options; 8 | vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false); 9 | // normalization is always applied for the public version, used in 10 | // user-written render functions. 11 | vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true); 12 | } 13 | 14 | export function renderMixin(Vue) { 15 | Vue.prototype.$nextTick = function (fn) { 16 | return nextTick(fn, this); 17 | }; 18 | 19 | Vue.prototype._render = function () { 20 | const vm = this; 21 | const { render } = vm.$options; 22 | 23 | return render.call(vm._renderProxy, vm.$createElement); 24 | }; 25 | } 26 | -------------------------------------------------------------------------------- /code/22.Vue2剥丝抽茧-虚拟dom之组件/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"; 7 | 8 | const arrayProto = Array.prototype; 9 | export const arrayMethods = Object.create(arrayProto); 10 | 11 | const methodsToPatch = [ 12 | "push", 13 | "pop", 14 | "shift", 15 | "unshift", 16 | "splice", 17 | "sort", 18 | "reverse", 19 | ]; 20 | 21 | /** 22 | * Intercept mutating methods and emit events 23 | */ 24 | methodsToPatch.forEach(function (method) { 25 | // cache original method 26 | const original = arrayProto[method]; 27 | def(arrayMethods, method, function mutator(...args) { 28 | const result = original.apply(this, args); 29 | const ob = this.__ob__; 30 | let inserted; // 加添加的元素拿到 31 | switch (method) { 32 | case "push": 33 | case "unshift": 34 | inserted = args; 35 | break; 36 | case "splice": 37 | inserted = args.slice(2); 38 | break; 39 | } 40 | if (inserted) ob.observeArray(inserted); 41 | // notify change 42 | ob.dep.notify(); 43 | return result; 44 | }); 45 | }); 46 | -------------------------------------------------------------------------------- /code/22.Vue2剥丝抽茧-虚拟dom之组件/src/core/observer/dep.js: -------------------------------------------------------------------------------- 1 | import { remove } from "../util"; 2 | 3 | let uid = 0; 4 | 5 | export default class Dep { 6 | static target; //当前在执行的函数 7 | subs; // 依赖的函数 8 | id; // Dep 对象标识 9 | constructor() { 10 | this.id = uid++; 11 | this.subs = []; // 保存所有需要执行的函数 12 | } 13 | 14 | addSub(sub) { 15 | this.subs.push(sub); 16 | } 17 | 18 | removeSub(sub) { 19 | remove(this.subs, sub); 20 | } 21 | depend() { 22 | if (Dep.target) { 23 | // 委托给 Dep.target 去调用 addSub 24 | Dep.target.addDep(this); 25 | } 26 | } 27 | 28 | notify() { 29 | // stabilize the subscriber list first 30 | const subs = this.subs.slice(); 31 | for (let i = 0, l = subs.length; i < l; i++) { 32 | subs[i].update(); 33 | } 34 | } 35 | } 36 | 37 | Dep.target = null; // 静态变量,全局唯一 38 | 39 | // The current target watcher being evaluated. 40 | // This is globally unique because only one watcher 41 | // can be evaluated at a time. 42 | const targetStack = []; 43 | 44 | export function pushTarget(target) { 45 | targetStack.push(target); 46 | Dep.target = target; 47 | } 48 | 49 | export function popTarget() { 50 | targetStack.pop(); 51 | Dep.target = targetStack[targetStack.length - 1]; 52 | } 53 | -------------------------------------------------------------------------------- /code/22.Vue2剥丝抽茧-虚拟dom之组件/src/core/observer/traverse.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { isObject } from "../util"; 4 | 5 | const seenObjects = new Set(); 6 | 7 | /** 8 | * Recursively traverse an object to evoke all converted 9 | * getters, so that every nested property inside the object 10 | * is collected as a "deep" dependency. 11 | */ 12 | export function traverse(val) { 13 | _traverse(val, seenObjects); 14 | seenObjects.clear(); 15 | } 16 | 17 | function _traverse(val, seen) { 18 | let i, keys; 19 | const isA = Array.isArray(val); 20 | if ((!isA && !isObject(val)) || Object.isFrozen(val)) { 21 | return; 22 | } 23 | if (val.__ob__) { 24 | const depId = val.__ob__.dep.id; 25 | if (seen.has(depId)) { 26 | return; 27 | } 28 | seen.add(depId); 29 | } 30 | if (isA) { 31 | i = val.length; 32 | while (i--) _traverse(val[i], seen); 33 | } else { 34 | keys = Object.keys(val); 35 | i = keys.length; 36 | while (i--) _traverse(val[keys[i]], seen); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /code/22.Vue2剥丝抽茧-虚拟dom之组件/src/core/util/env.js: -------------------------------------------------------------------------------- 1 | // Browser environment sniffing 2 | export const inBrowser = typeof window !== "undefined"; 3 | 4 | /* istanbul ignore next */ 5 | export function isNative(Ctor) { 6 | return typeof Ctor === "function" && /native code/.test(Ctor.toString()); 7 | } 8 | 9 | // can we use __proto__? 10 | export const hasProto = "__proto__" in {}; 11 | 12 | export let supportsPassive = false; 13 | if (inBrowser) { 14 | try { 15 | const opts = {}; 16 | Object.defineProperty(opts, "passive", { 17 | get() { 18 | /* istanbul ignore next */ 19 | supportsPassive = true; 20 | }, 21 | }); // https://github.com/facebook/flow/issues/285 22 | window.addEventListener("test-passive", null, opts); 23 | } catch (e) {} 24 | } 25 | -------------------------------------------------------------------------------- /code/22.Vue2剥丝抽茧-虚拟dom之组件/src/core/util/index.js: -------------------------------------------------------------------------------- 1 | export * from "../../shared/util"; 2 | export * from "./lang"; 3 | export * from "./env"; 4 | export * from "./next-tick"; 5 | export * from "./options"; 6 | -------------------------------------------------------------------------------- /code/22.Vue2剥丝抽茧-虚拟dom之组件/src/core/util/lang.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | /** 4 | * unicode letters used for parsing html tags, component names and property paths. 5 | * using https://www.w3.org/TR/html53/semantics-scripting.html#potentialcustomelementname 6 | * skipping \u10000-\uEFFFF due to it freezing up PhantomJS 7 | */ 8 | export const unicodeRegExp = 9 | /a-zA-Z\u00B7\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u037D\u037F-\u1FFF\u200C-\u200D\u203F-\u2040\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD/; 10 | 11 | /** 12 | * Check if a string starts with $ or _ 13 | */ 14 | export function isReserved(str) { 15 | const c = (str + "").charCodeAt(0); 16 | return c === 0x24 || c === 0x5f; 17 | } 18 | 19 | /** 20 | * Define a property. 21 | */ 22 | export function def(obj, key, val, enumerable) { 23 | Object.defineProperty(obj, key, { 24 | value: val, 25 | enumerable: !!enumerable, 26 | writable: true, 27 | configurable: true, 28 | }); 29 | } 30 | 31 | /** 32 | * Parse simple path. 33 | */ 34 | const bailRE = new RegExp(`[^${unicodeRegExp.source}.$_\\d]`); 35 | export function parsePath(path) { 36 | if (bailRE.test(path)) { 37 | return; 38 | } 39 | const segments = path.split("."); 40 | return function (obj) { 41 | for (let i = 0; i < segments.length; i++) { 42 | if (!obj) return; 43 | obj = obj[segments[i]]; 44 | } 45 | return obj; 46 | }; 47 | } 48 | -------------------------------------------------------------------------------- /code/22.Vue2剥丝抽茧-虚拟dom之组件/src/core/util/next-tick.js: -------------------------------------------------------------------------------- 1 | import { isNative } from "./env"; 2 | 3 | const callbacks = []; 4 | let pending = false; 5 | 6 | function flushCallbacks() { 7 | pending = false; 8 | const copies = callbacks.slice(0); 9 | callbacks.length = 0; 10 | for (let i = 0; i < copies.length; i++) { 11 | copies[i](); 12 | } 13 | } 14 | 15 | let timerFunc; 16 | 17 | if (typeof Promise !== "undefined" && isNative(Promise)) { 18 | const p = Promise.resolve(); 19 | timerFunc = () => { 20 | p.then(flushCallbacks); 21 | }; 22 | } else { 23 | // Fallback to setTimeout. 24 | timerFunc = () => { 25 | setTimeout(flushCallbacks, 0); 26 | }; 27 | } 28 | 29 | export function nextTick(cb, ctx) { 30 | let _resolve; 31 | callbacks.push(() => { 32 | if (cb) { 33 | cb.call(ctx); 34 | } else if (_resolve) { 35 | _resolve(ctx); 36 | } 37 | }); 38 | if (!pending) { 39 | pending = true; 40 | timerFunc(); // 只执行一次 41 | } 42 | // $flow-disable-line 43 | if (!cb && typeof Promise !== "undefined") { 44 | return new Promise((resolve) => { 45 | _resolve = resolve; 46 | }); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /code/22.Vue2剥丝抽茧-虚拟dom之组件/src/core/vdom/create-element.js: -------------------------------------------------------------------------------- 1 | import config from "../config"; 2 | import VNode, { createEmptyVNode } from "./vnode"; 3 | import { normalizeChildren } from "./helpers/index"; 4 | import { isDef, isPrimitive, resolveAsset } from "../util/index"; 5 | import { createComponent } from "./create-component"; 6 | 7 | // wrapper function for providing a more flexible interface 8 | // without getting yelled at by flow 9 | export function createElement(context, tag, data, children) { 10 | return _createElement(context, tag, data, children); 11 | } 12 | 13 | export function _createElement(context, tag, data, children) { 14 | if (!tag) { 15 | // in case of component :is set to falsy value 16 | return createEmptyVNode(); 17 | } 18 | if (Array.isArray(data) || isPrimitive(data)) { 19 | children = data; 20 | data = undefined; 21 | } 22 | children = normalizeChildren(children); 23 | let vnode; 24 | let Ctor; 25 | if (config.isReservedTag(tag)) { 26 | vnode = new VNode(tag, data, children, undefined, undefined, context); 27 | } else if ( 28 | isDef((Ctor = resolveAsset(context.$options, "components", tag))) 29 | ) { 30 | // component 31 | vnode = createComponent(Ctor, data, context, children, tag); 32 | } 33 | return vnode; 34 | } 35 | -------------------------------------------------------------------------------- /code/22.Vue2剥丝抽茧-虚拟dom之组件/src/core/vdom/helpers/extract-props.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { hasOwn, isDef, isUndef, hyphenate } from "@/core/util/index"; 4 | 5 | export function extractPropsFromVNodeData(data, Ctor, tag) { 6 | // we are only extracting raw values here. 7 | // validation and default values are handled in the child 8 | // component itself. 9 | const propOptions = Ctor.options.props; 10 | if (isUndef(propOptions)) { 11 | return; 12 | } 13 | const res = {}; 14 | const { attrs, props } = data; 15 | if (isDef(attrs) || isDef(props)) { 16 | for (const key in propOptions) { 17 | const altKey = hyphenate(key); 18 | checkProp(res, props, key, altKey, true) || 19 | checkProp(res, attrs, key, altKey, false); 20 | } 21 | } 22 | return res; 23 | } 24 | 25 | function checkProp(res, hash, key, altKey, preserve) { 26 | if (isDef(hash)) { 27 | if (hasOwn(hash, key)) { 28 | res[key] = hash[key]; 29 | if (!preserve) { 30 | delete hash[key]; 31 | } 32 | return true; 33 | } else if (hasOwn(hash, altKey)) { 34 | res[key] = hash[altKey]; 35 | if (!preserve) { 36 | delete hash[altKey]; 37 | } 38 | return true; 39 | } 40 | } 41 | return false; 42 | } 43 | -------------------------------------------------------------------------------- /code/22.Vue2剥丝抽茧-虚拟dom之组件/src/core/vdom/helpers/index.js: -------------------------------------------------------------------------------- 1 | export * from "./normalize-children"; 2 | export * from "./update-listeners"; 3 | export * from "./extract-props"; 4 | -------------------------------------------------------------------------------- /code/22.Vue2剥丝抽茧-虚拟dom之组件/src/core/vdom/helpers/normalize-children.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { createTextVNode } from "@/core/vdom/vnode"; 4 | import { isUndef, isPrimitive } from "@/shared/util"; 5 | 6 | export function normalizeChildren(children) { 7 | return isPrimitive(children) 8 | ? [createTextVNode(children)] 9 | : Array.isArray(children) 10 | ? normalizeArrayChildren(children) 11 | : undefined; 12 | } 13 | 14 | function normalizeArrayChildren(children) { 15 | const res = []; 16 | let i, c; 17 | for (i = 0; i < children.length; i++) { 18 | c = children[i]; 19 | if (isUndef(c) || typeof c === "boolean") continue; 20 | if (isPrimitive(c)) { 21 | if (c !== "") 22 | // convert primitive to vnode 23 | res.push(createTextVNode(c)); 24 | // 省略了很多 if else 25 | } else { 26 | // 走到这里说明当前 c 已经是一个 vnode 节点了 27 | res.push(c); 28 | } 29 | } 30 | return res; 31 | } 32 | -------------------------------------------------------------------------------- /code/22.Vue2剥丝抽茧-虚拟dom之组件/src/platforms/web/entry-runtime.js: -------------------------------------------------------------------------------- 1 | import Vue from "./runtime/index"; 2 | 3 | export default Vue; 4 | -------------------------------------------------------------------------------- /code/22.Vue2剥丝抽茧-虚拟dom之组件/src/platforms/web/modules/index.js: -------------------------------------------------------------------------------- 1 | import events from "./events"; 2 | 3 | export default [events]; 4 | -------------------------------------------------------------------------------- /code/22.Vue2剥丝抽茧-虚拟dom之组件/src/platforms/web/runtime/index.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import Vue from "@/core/index"; 4 | import { noop } from "@/shared/util"; 5 | import { mountComponent } from "@/core/instance/lifecycle"; 6 | import { inBrowser } from "@/core/util/index"; 7 | 8 | import { patch } from "./patch"; 9 | import { 10 | isReservedTag, 11 | getTagNamespace, 12 | isUnknownElement, 13 | } from "../util/index"; 14 | 15 | // install platform patch function 16 | Vue.prototype.__patch__ = inBrowser ? patch : noop; 17 | // install platform specific utils 18 | // Vue.config 在 /src/core/global-api/index.js 初始化,这里进行覆盖 19 | Vue.config.isReservedTag = isReservedTag; 20 | Vue.config.getTagNamespace = getTagNamespace; 21 | Vue.config.isUnknownElement = isUnknownElement; 22 | 23 | // public mount method 24 | Vue.prototype.$mount = function (el) { 25 | el = el && document.querySelector(el); 26 | return mountComponent(this, el); 27 | }; 28 | 29 | export default Vue; 30 | -------------------------------------------------------------------------------- /code/22.Vue2剥丝抽茧-虚拟dom之组件/src/platforms/web/runtime/node-ops.js: -------------------------------------------------------------------------------- 1 | export function createElement(tagName) { 2 | const elm = document.createElement(tagName); 3 | return elm; 4 | } 5 | 6 | export function createTextNode(text) { 7 | return document.createTextNode(text); 8 | } 9 | 10 | export function insertBefore(parentNode, newNode, referenceNode) { 11 | parentNode.insertBefore(newNode, referenceNode); 12 | } 13 | 14 | export function removeChild(node, child) { 15 | node.removeChild(child); 16 | } 17 | 18 | export function appendChild(node, child) { 19 | node.appendChild(child); 20 | } 21 | 22 | export function parentNode(node) { 23 | return node.parentNode; 24 | } 25 | 26 | export function nextSibling(node) { 27 | return node.nextSibling; 28 | } 29 | 30 | export function tagName(node) { 31 | return node.tagName; 32 | } 33 | 34 | export function setTextContent(node, text) { 35 | node.textContent = text; 36 | } 37 | -------------------------------------------------------------------------------- /code/22.Vue2剥丝抽茧-虚拟dom之组件/src/platforms/web/runtime/patch.js: -------------------------------------------------------------------------------- 1 | import * as nodeOps from "./node-ops"; 2 | import { createPatchFunction } from "@/core/vdom/patch"; 3 | import platformModules from "../modules/index"; 4 | 5 | export const patch = createPatchFunction({ nodeOps, modules: platformModules }); 6 | -------------------------------------------------------------------------------- /code/22.Vue2剥丝抽茧-虚拟dom之组件/src/platforms/web/util/index.js: -------------------------------------------------------------------------------- 1 | export * from "./element"; 2 | -------------------------------------------------------------------------------- /code/22.Vue2剥丝抽茧-虚拟dom之组件/src/shared/constants.js: -------------------------------------------------------------------------------- 1 | export const ASSET_TYPES = ["component", "directive", "filter"]; 2 | export const LIFECYCLE_HOOKS = [ 3 | "beforeCreate", 4 | "created", 5 | "beforeMount", 6 | "mounted", 7 | "beforeUpdate", 8 | "updated", 9 | "beforeDestroy", 10 | "destroyed", 11 | "activated", 12 | "deactivated", 13 | "errorCaptured", 14 | "serverPrefetch", 15 | ]; 16 | -------------------------------------------------------------------------------- /code/22.Vue2剥丝抽茧-虚拟dom之组件/vueliang1.js: -------------------------------------------------------------------------------- 1 | import Vue from "./src/platforms/web/entry-runtime"; 2 | 3 | const Hello = { 4 | props: { 5 | title: String, 6 | }, 7 | data() { 8 | return { 9 | text: "component world", 10 | }; 11 | }, 12 | methods: { 13 | click() { 14 | this.text = ",component world"; 15 | }, 16 | }, 17 | render(h) { 18 | return h( 19 | "div", 20 | { 21 | on: { 22 | click: this.click, 23 | }, 24 | }, 25 | [this.title, this.text] 26 | ); 27 | }, 28 | }; 29 | new Vue({ 30 | el: "#root", 31 | data() { 32 | return { 33 | text: "world", 34 | title: "hello", 35 | }; 36 | }, 37 | components: { Hello }, 38 | methods: { 39 | click() { 40 | this.title = "hello2"; 41 | // this.text = "hello2"; 42 | }, 43 | }, 44 | render(createElement) { 45 | const test = createElement( 46 | "div", 47 | { 48 | on: { 49 | // click: this.click, 50 | }, 51 | }, 52 | [ 53 | createElement("Hello", { props: { title: this.title } }), 54 | this.text, 55 | ] 56 | ); 57 | return test; 58 | }, 59 | }); 60 | -------------------------------------------------------------------------------- /code/3.Vue2剥丝抽茧-响应式系统之嵌套/data.js: -------------------------------------------------------------------------------- 1 | // https://vue.windliang.wang/posts/Vue2%E5%89%A5%E4%B8%9D%E6%8A%BD%E8%8C%A7-%E5%93%8D%E5%BA%94%E5%BC%8F%E7%B3%BB%E7%BB%9F%E4%B9%8B%E5%B5%8C%E5%A5%97.html 2 | import { observe } from "./reactive"; 3 | import Watcher from "./watcher"; 4 | const data = { 5 | text: "hello, world", 6 | inner: "内部", 7 | }; 8 | observe(data); 9 | 10 | const updateMyComponent = () => { 11 | console.log("子组件收到:", data.inner); 12 | }; 13 | 14 | const updateParentComponent = () => { 15 | new Watcher(updateMyComponent); 16 | console.log("父组件收到:", data.text); 17 | }; 18 | 19 | new Watcher(updateParentComponent); 20 | 21 | data.text = "hello, liang"; 22 | -------------------------------------------------------------------------------- /code/3.Vue2剥丝抽茧-响应式系统之嵌套/dep.js: -------------------------------------------------------------------------------- 1 | import { remove } from "./util"; 2 | 3 | let uid = 0; 4 | 5 | export default class Dep { 6 | static target; //当前在执行的函数 7 | subs; // 依赖的函数 8 | id; // Dep 对象标识 9 | constructor() { 10 | this.id = uid++; 11 | this.subs = []; // 保存所有需要执行的函数 12 | } 13 | 14 | addSub(sub) { 15 | this.subs.push(sub); 16 | } 17 | 18 | removeSub(sub) { 19 | remove(this.subs, sub); 20 | } 21 | depend() { 22 | if (Dep.target) { 23 | // 委托给 Dep.target 去调用 addSub 24 | Dep.target.addDep(this); 25 | } 26 | } 27 | 28 | notify() { 29 | for (let i = 0, l = this.subs.length; i < l; i++) { 30 | this.subs[i].update(); 31 | } 32 | } 33 | } 34 | 35 | Dep.target = null; // 静态变量,全局唯一 36 | 37 | // The current target watcher being evaluated. 38 | // This is globally unique because only one watcher 39 | // can be evaluated at a time. 40 | const targetStack = []; 41 | 42 | export function pushTarget(target) { 43 | targetStack.push(target); 44 | Dep.target = target; 45 | } 46 | 47 | export function popTarget() { 48 | targetStack.pop(); 49 | Dep.target = targetStack[targetStack.length - 1]; 50 | } 51 | -------------------------------------------------------------------------------- /code/3.Vue2剥丝抽茧-响应式系统之嵌套/util.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Remove an item from an array. 3 | */ 4 | export function remove(arr, item) { 5 | if (arr.length) { 6 | const index = arr.indexOf(item); 7 | if (index > -1) { 8 | return arr.splice(index, 1); 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /code/4-1/data.js: -------------------------------------------------------------------------------- 1 | import { observe } from "./reactive"; 2 | import Watcher from "./watcher"; 3 | const data = { 4 | text: "hello, world", 5 | }; 6 | observe(data); 7 | 8 | const updateComponent = () => { 9 | const temp = data.text + "liang"; 10 | data.text = temp; 11 | }; 12 | 13 | new Watcher(updateComponent); 14 | -------------------------------------------------------------------------------- /code/4-1/dep.js: -------------------------------------------------------------------------------- 1 | import { remove } from "./util"; 2 | 3 | let uid = 0; 4 | 5 | export default class Dep { 6 | static target; //当前在执行的函数 7 | subs; // 依赖的函数 8 | id; // Dep 对象标识 9 | constructor() { 10 | this.id = uid++; 11 | this.subs = []; // 保存所有需要执行的函数 12 | } 13 | 14 | addSub(sub) { 15 | this.subs.push(sub); 16 | } 17 | 18 | removeSub(sub) { 19 | remove(this.subs, sub); 20 | } 21 | depend() { 22 | if (Dep.target) { 23 | // 委托给 Dep.target 去调用 addSub 24 | Dep.target.addDep(this); 25 | } 26 | } 27 | 28 | notify() { 29 | for (let i = 0, l = this.subs.length; i < l; i++) { 30 | this.subs[i].update(); 31 | } 32 | } 33 | } 34 | 35 | Dep.target = null; // 静态变量,全局唯一 36 | 37 | // The current target watcher being evaluated. 38 | // This is globally unique because only one watcher 39 | // can be evaluated at a time. 40 | const targetStack = []; 41 | 42 | export function pushTarget(target) { 43 | targetStack.push(target); 44 | Dep.target = target; 45 | } 46 | 47 | export function popTarget() { 48 | targetStack.pop(); 49 | Dep.target = targetStack[targetStack.length - 1]; 50 | } 51 | -------------------------------------------------------------------------------- /code/4-1/scheduler.js: -------------------------------------------------------------------------------- 1 | const queue = []; 2 | let has = {}; 3 | let index = 0; 4 | 5 | /** 6 | * Flush both queues and run the watchers. 7 | */ 8 | function flushSchedulerQueue() { 9 | let watcher, id; 10 | // do not cache length because more watchers might be pushed 11 | // as we run existing watchers 12 | for (index = 0; index < queue.length; index++) { 13 | watcher = queue[index]; 14 | id = watcher.id; 15 | has[id] = null; 16 | watcher.run(); 17 | } 18 | } 19 | 20 | /** 21 | * Push a watcher into the watcher queue. 22 | * Jobs with duplicate IDs will be skipped unless it's 23 | * pushed when the queue is being flushed. 24 | */ 25 | export function queueWatcher(watcher) { 26 | const id = watcher.id; 27 | if (has[id] == null) { 28 | has[id] = true; 29 | queue.push(watcher); 30 | flushSchedulerQueue(); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /code/4-1/util.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Remove an item from an array. 3 | */ 4 | export function remove(arr, item) { 5 | if (arr.length) { 6 | const index = arr.indexOf(item); 7 | if (index > -1) { 8 | return arr.splice(index, 1); 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /code/4.Vue2剥丝抽茧-响应式系统完善/data.js: -------------------------------------------------------------------------------- 1 | // https://vue.windliang.wang/posts/Vue2%E5%89%A5%E4%B8%9D%E6%8A%BD%E8%8C%A7-%E5%93%8D%E5%BA%94%E5%BC%8F%E7%B3%BB%E7%BB%9F%E5%AE%8C%E5%96%84.html 2 | import { observe } from "./reactive"; 3 | import Watcher from "./watcher"; 4 | const data = { 5 | text: "hello, world", 6 | }; 7 | observe(data); 8 | let show = true; 9 | const updateComponent = () => { 10 | if (show) { 11 | console.log(data.text); 12 | show = false; 13 | } 14 | }; 15 | 16 | new Watcher(updateComponent); 17 | 18 | new Watcher(() => console.log("依赖", data.text)); 19 | 20 | data.text = "123"; 21 | -------------------------------------------------------------------------------- /code/4.Vue2剥丝抽茧-响应式系统完善/dep.js: -------------------------------------------------------------------------------- 1 | import { remove } from "./util"; 2 | 3 | let uid = 0; 4 | 5 | export default class Dep { 6 | static target; //当前在执行的函数 7 | subs; // 依赖的函数 8 | id; // Dep 对象标识 9 | constructor() { 10 | this.id = uid++; 11 | this.subs = []; // 保存所有需要执行的函数 12 | } 13 | 14 | addSub(sub) { 15 | this.subs.push(sub); 16 | } 17 | 18 | removeSub(sub) { 19 | remove(this.subs, sub); 20 | } 21 | depend() { 22 | if (Dep.target) { 23 | // 委托给 Dep.target 去调用 addSub 24 | Dep.target.addDep(this); 25 | } 26 | } 27 | 28 | notify() { 29 | // stabilize the subscriber list first 30 | const subs = this.subs.slice(); 31 | for (let i = 0, l = subs.length; i < l; i++) { 32 | subs[i].update(); 33 | } 34 | } 35 | } 36 | 37 | Dep.target = null; // 静态变量,全局唯一 38 | 39 | // The current target watcher being evaluated. 40 | // This is globally unique because only one watcher 41 | // can be evaluated at a time. 42 | const targetStack = []; 43 | 44 | export function pushTarget(target) { 45 | targetStack.push(target); 46 | Dep.target = target; 47 | } 48 | 49 | export function popTarget() { 50 | targetStack.pop(); 51 | Dep.target = targetStack[targetStack.length - 1]; 52 | } 53 | -------------------------------------------------------------------------------- /code/4.Vue2剥丝抽茧-响应式系统完善/util.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Remove an item from an array. 3 | */ 4 | export function remove(arr, item) { 5 | if (arr.length) { 6 | const index = arr.indexOf(item); 7 | if (index > -1) { 8 | return arr.splice(index, 1); 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /code/5.Vue2剥丝抽茧-响应式系统之深度响应/data.js: -------------------------------------------------------------------------------- 1 | // https://vue.windliang.wang/posts/Vue2%E5%89%A5%E4%B8%9D%E6%8A%BD%E8%8C%A7-%E5%93%8D%E5%BA%94%E5%BC%8F%E7%B3%BB%E7%BB%9F%E4%B9%8B%E6%B7%B1%E5%BA%A6%E5%93%8D%E5%BA%94.html 2 | import { observe } from "./reactive"; 3 | import Watcher from "./watcher"; 4 | const data = { 5 | text: { 6 | innerText: { 7 | childText: "hello", 8 | }, 9 | }, 10 | }; 11 | observe(data); 12 | const updateComponent = () => { 13 | console.log(data.text.innerText.childText); 14 | }; 15 | 16 | new Watcher(updateComponent); 17 | 18 | data.text.innerText.childText = "liang"; 19 | data.text = { 20 | innerText: { 21 | childText: "liang2", 22 | }, 23 | }; 24 | 25 | data.text.innerText.childText = "liang3"; 26 | -------------------------------------------------------------------------------- /code/5.Vue2剥丝抽茧-响应式系统之深度响应/dep.js: -------------------------------------------------------------------------------- 1 | import { remove } from "./util"; 2 | 3 | let uid = 0; 4 | 5 | export default class Dep { 6 | static target; //当前在执行的函数 7 | subs; // 依赖的函数 8 | id; // Dep 对象标识 9 | constructor() { 10 | this.id = uid++; 11 | this.subs = []; // 保存所有需要执行的函数 12 | } 13 | 14 | addSub(sub) { 15 | this.subs.push(sub); 16 | } 17 | 18 | removeSub(sub) { 19 | remove(this.subs, sub); 20 | } 21 | depend() { 22 | if (Dep.target) { 23 | // 委托给 Dep.target 去调用 addSub 24 | Dep.target.addDep(this); 25 | } 26 | } 27 | 28 | notify() { 29 | // stabilize the subscriber list first 30 | const subs = this.subs.slice(); 31 | for (let i = 0, l = subs.length; i < l; i++) { 32 | subs[i].update(); 33 | } 34 | } 35 | } 36 | 37 | Dep.target = null; // 静态变量,全局唯一 38 | 39 | // The current target watcher being evaluated. 40 | // This is globally unique because only one watcher 41 | // can be evaluated at a time. 42 | const targetStack = []; 43 | 44 | export function pushTarget(target) { 45 | targetStack.push(target); 46 | Dep.target = target; 47 | } 48 | 49 | export function popTarget() { 50 | targetStack.pop(); 51 | Dep.target = targetStack[targetStack.length - 1]; 52 | } 53 | -------------------------------------------------------------------------------- /code/5.Vue2剥丝抽茧-响应式系统之深度响应/util.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Remove an item from an array. 3 | */ 4 | export function remove(arr, item) { 5 | if (arr.length) { 6 | const index = arr.indexOf(item); 7 | if (index > -1) { 8 | return arr.splice(index, 1); 9 | } 10 | } 11 | } 12 | 13 | /** 14 | * Quick object check - this is primarily used to tell 15 | * Objects from primitive values when we know the value 16 | * is a JSON-compliant type. 17 | */ 18 | export function isObject(obj) { 19 | return obj !== null && typeof obj === "object"; 20 | } 21 | -------------------------------------------------------------------------------- /code/6.Vue2剥丝抽茧-响应式系统之数组/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"; 7 | 8 | const arrayProto = Array.prototype; 9 | export const arrayMethods = Object.create(arrayProto); 10 | 11 | const methodsToPatch = [ 12 | "push", 13 | "pop", 14 | "shift", 15 | "unshift", 16 | "splice", 17 | "sort", 18 | "reverse", 19 | ]; 20 | 21 | /** 22 | * Intercept mutating methods and emit events 23 | */ 24 | methodsToPatch.forEach(function (method) { 25 | // cache original method 26 | const original = arrayProto[method]; 27 | def(arrayMethods, method, function mutator(...args) { 28 | const result = original.apply(this, args); 29 | /*****************这里相当于调用了对象 set 需要通知 watcher ************************/ 30 | const ob = this.__ob__; 31 | // notify change 32 | ob.dep.notify(); 33 | /**************************************************************************** */ 34 | return result; 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /code/6.Vue2剥丝抽茧-响应式系统之数组/data.js: -------------------------------------------------------------------------------- 1 | // https://vue.windliang.wang/posts/Vue2%E5%89%A5%E4%B8%9D%E6%8A%BD%E8%8C%A7-%E5%93%8D%E5%BA%94%E5%BC%8F%E7%B3%BB%E7%BB%9F%E4%B9%8B%E6%95%B0%E7%BB%84.html 2 | import { observe } from "./reactive"; 3 | import Watcher from "./watcher"; 4 | const data = { 5 | list: ["hello"], 6 | }; 7 | observe(data); 8 | const updateComponent = () => { 9 | for (const item of data.list) { 10 | console.log(item); 11 | } 12 | }; 13 | 14 | new Watcher(updateComponent); 15 | // data.list = ["hello", "liang"]; 16 | 17 | data.list = ["hello", "liang"]; 18 | 19 | data.list.push("liang"); 20 | -------------------------------------------------------------------------------- /code/6.Vue2剥丝抽茧-响应式系统之数组/dep.js: -------------------------------------------------------------------------------- 1 | import { remove } from "./util"; 2 | 3 | let uid = 0; 4 | 5 | export default class Dep { 6 | static target; //当前在执行的函数 7 | subs; // 依赖的函数 8 | id; // Dep 对象标识 9 | constructor() { 10 | this.id = uid++; 11 | this.subs = []; // 保存所有需要执行的函数 12 | } 13 | 14 | addSub(sub) { 15 | this.subs.push(sub); 16 | } 17 | 18 | removeSub(sub) { 19 | remove(this.subs, sub); 20 | } 21 | depend() { 22 | if (Dep.target) { 23 | // 委托给 Dep.target 去调用 addSub 24 | Dep.target.addDep(this); 25 | } 26 | } 27 | 28 | notify() { 29 | // stabilize the subscriber list first 30 | const subs = this.subs.slice(); 31 | for (let i = 0, l = subs.length; i < l; i++) { 32 | subs[i].update(); 33 | } 34 | } 35 | } 36 | 37 | Dep.target = null; // 静态变量,全局唯一 38 | 39 | // The current target watcher being evaluated. 40 | // This is globally unique because only one watcher 41 | // can be evaluated at a time. 42 | const targetStack = []; 43 | 44 | export function pushTarget(target) { 45 | targetStack.push(target); 46 | Dep.target = target; 47 | } 48 | 49 | export function popTarget() { 50 | targetStack.pop(); 51 | Dep.target = targetStack[targetStack.length - 1]; 52 | } 53 | -------------------------------------------------------------------------------- /code/6.Vue2剥丝抽茧-响应式系统之数组/util.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Remove an item from an array. 3 | */ 4 | export function remove(arr, item) { 5 | if (arr.length) { 6 | const index = arr.indexOf(item); 7 | if (index > -1) { 8 | return arr.splice(index, 1); 9 | } 10 | } 11 | } 12 | 13 | /** 14 | * Quick object check - this is primarily used to tell 15 | * Objects from primitive values when we know the value 16 | * is a JSON-compliant type. 17 | */ 18 | export function isObject(obj) { 19 | return obj !== null && typeof obj === "object"; 20 | } 21 | 22 | /** 23 | * Define a property. 24 | */ 25 | export function def(obj, key, val, enumerable) { 26 | Object.defineProperty(obj, key, { 27 | value: val, 28 | enumerable: !!enumerable, 29 | writable: true, 30 | configurable: true, 31 | }); 32 | } 33 | 34 | // can we use __proto__? 35 | export const hasProto = "__proto__" in {}; 36 | -------------------------------------------------------------------------------- /code/7.Vue2剥丝抽茧-响应式系统之数组2/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"; 7 | 8 | const arrayProto = Array.prototype; 9 | export const arrayMethods = Object.create(arrayProto); 10 | 11 | const methodsToPatch = [ 12 | "push", 13 | "pop", 14 | "shift", 15 | "unshift", 16 | "splice", 17 | "sort", 18 | "reverse", 19 | ]; 20 | 21 | /** 22 | * Intercept mutating methods and emit events 23 | */ 24 | methodsToPatch.forEach(function (method) { 25 | // cache original method 26 | const original = arrayProto[method]; 27 | def(arrayMethods, method, function mutator(...args) { 28 | const result = original.apply(this, args); 29 | const ob = this.__ob__; 30 | /******新增 *************************/ 31 | let inserted; // 加添加的元素拿到 32 | switch (method) { 33 | case "push": 34 | case "unshift": 35 | inserted = args; 36 | break; 37 | case "splice": 38 | inserted = args.slice(2); 39 | break; 40 | } 41 | if (inserted) ob.observeArray(inserted); 42 | /************************************/ 43 | // notify change 44 | ob.dep.notify(); 45 | return result; 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /code/7.Vue2剥丝抽茧-响应式系统之数组2/dep.js: -------------------------------------------------------------------------------- 1 | import { remove } from "./util"; 2 | 3 | let uid = 0; 4 | 5 | export default class Dep { 6 | static target; //当前在执行的函数 7 | subs; // 依赖的函数 8 | id; // Dep 对象标识 9 | constructor() { 10 | this.id = uid++; 11 | this.subs = []; // 保存所有需要执行的函数 12 | } 13 | 14 | addSub(sub) { 15 | this.subs.push(sub); 16 | } 17 | 18 | removeSub(sub) { 19 | remove(this.subs, sub); 20 | } 21 | depend() { 22 | if (Dep.target) { 23 | // 委托给 Dep.target 去调用 addSub 24 | Dep.target.addDep(this); 25 | } 26 | } 27 | 28 | notify() { 29 | // stabilize the subscriber list first 30 | const subs = this.subs.slice(); 31 | for (let i = 0, l = subs.length; i < l; i++) { 32 | subs[i].update(); 33 | } 34 | } 35 | } 36 | 37 | Dep.target = null; // 静态变量,全局唯一 38 | 39 | // The current target watcher being evaluated. 40 | // This is globally unique because only one watcher 41 | // can be evaluated at a time. 42 | const targetStack = []; 43 | 44 | export function pushTarget(target) { 45 | targetStack.push(target); 46 | Dep.target = target; 47 | } 48 | 49 | export function popTarget() { 50 | targetStack.pop(); 51 | Dep.target = targetStack[targetStack.length - 1]; 52 | } 53 | -------------------------------------------------------------------------------- /code/7.Vue2剥丝抽茧-响应式系统之数组2/util.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Remove an item from an array. 3 | */ 4 | export function remove(arr, item) { 5 | if (arr.length) { 6 | const index = arr.indexOf(item); 7 | if (index > -1) { 8 | return arr.splice(index, 1); 9 | } 10 | } 11 | } 12 | 13 | /** 14 | * Quick object check - this is primarily used to tell 15 | * Objects from primitive values when we know the value 16 | * is a JSON-compliant type. 17 | */ 18 | export function isObject(obj) { 19 | return obj !== null && typeof obj === "object"; 20 | } 21 | 22 | /** 23 | * Define a property. 24 | */ 25 | export function def(obj, key, val, enumerable) { 26 | Object.defineProperty(obj, key, { 27 | value: val, 28 | enumerable: !!enumerable, 29 | writable: true, 30 | configurable: true, 31 | }); 32 | } 33 | 34 | // can we use __proto__? 35 | export const hasProto = "__proto__" in {}; 36 | -------------------------------------------------------------------------------- /code/8.Vue2剥丝抽茧-响应式系统之set和delete/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"; 7 | 8 | const arrayProto = Array.prototype; 9 | export const arrayMethods = Object.create(arrayProto); 10 | 11 | const methodsToPatch = [ 12 | "push", 13 | "pop", 14 | "shift", 15 | "unshift", 16 | "splice", 17 | "sort", 18 | "reverse", 19 | ]; 20 | 21 | /** 22 | * Intercept mutating methods and emit events 23 | */ 24 | methodsToPatch.forEach(function (method) { 25 | // cache original method 26 | const original = arrayProto[method]; 27 | def(arrayMethods, method, function mutator(...args) { 28 | const result = original.apply(this, args); 29 | const ob = this.__ob__; 30 | /******新增 *************************/ 31 | let inserted; // 加添加的元素拿到 32 | switch (method) { 33 | case "push": 34 | case "unshift": 35 | inserted = args; 36 | break; 37 | case "splice": 38 | inserted = args.slice(2); 39 | break; 40 | } 41 | if (inserted) ob.observeArray(inserted); 42 | /************************************/ 43 | // notify change 44 | ob.dep.notify(); 45 | return result; 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /code/8.Vue2剥丝抽茧-响应式系统之set和delete/data.js: -------------------------------------------------------------------------------- 1 | // https://vue.windliang.wang/posts/Vue2%E5%89%A5%E4%B8%9D%E6%8A%BD%E8%8C%A7-%E5%93%8D%E5%BA%94%E5%BC%8F%E7%B3%BB%E7%BB%9F%E4%B9%8Bset%E5%92%8Cdelete.html 2 | import { observe, set, del } from "./reactive"; 3 | import Watcher from "./watcher"; 4 | // const data = { 5 | // list: [1, 2], 6 | // }; 7 | // observe(data); 8 | // const updateComponent = () => { 9 | // console.log(data.list); 10 | // }; 11 | 12 | // new Watcher(updateComponent); 13 | 14 | // list[0] = 3; 15 | // data.list.splice(0, 1, 3); 16 | // set(data.list, 0, 4); 17 | // del(data.list, 0); 18 | // const data = { 19 | // obj: { 20 | // a: 1, 21 | // b: 2, 22 | // }, 23 | // }; 24 | // observe(data); 25 | // const updateComponent = () => { 26 | // const c = data.obj.c ? data.obj.c : 0; 27 | // console.log(data.obj.a + data.obj.b + c); 28 | // }; 29 | 30 | // new Watcher(updateComponent); 31 | 32 | // data.obj.c = 3; 33 | 34 | const data = { 35 | obj: { 36 | a: 1, 37 | b: 2, 38 | }, 39 | }; 40 | observe(data); 41 | const updateComponent = () => { 42 | const c = data.obj.c ? data.obj.c : 0; 43 | console.log(data.obj.a + data.obj.b + c); 44 | }; 45 | 46 | const ob = new Watcher(updateComponent); 47 | 48 | set(data.obj, "c", 3); 49 | 50 | data.obj.c = 5; 51 | 52 | del(data.obj, "a"); 53 | -------------------------------------------------------------------------------- /code/8.Vue2剥丝抽茧-响应式系统之set和delete/dep.js: -------------------------------------------------------------------------------- 1 | import { remove } from "./util"; 2 | 3 | let uid = 0; 4 | 5 | export default class Dep { 6 | static target; //当前在执行的函数 7 | subs; // 依赖的函数 8 | id; // Dep 对象标识 9 | constructor() { 10 | this.id = uid++; 11 | this.subs = []; // 保存所有需要执行的函数 12 | } 13 | 14 | addSub(sub) { 15 | this.subs.push(sub); 16 | } 17 | 18 | removeSub(sub) { 19 | remove(this.subs, sub); 20 | } 21 | depend() { 22 | if (Dep.target) { 23 | // 委托给 Dep.target 去调用 addSub 24 | Dep.target.addDep(this); 25 | } 26 | } 27 | 28 | notify() { 29 | // stabilize the subscriber list first 30 | const subs = this.subs.slice(); 31 | for (let i = 0, l = subs.length; i < l; i++) { 32 | subs[i].update(); 33 | } 34 | } 35 | } 36 | 37 | Dep.target = null; // 静态变量,全局唯一 38 | 39 | // The current target watcher being evaluated. 40 | // This is globally unique because only one watcher 41 | // can be evaluated at a time. 42 | const targetStack = []; 43 | 44 | export function pushTarget(target) { 45 | targetStack.push(target); 46 | Dep.target = target; 47 | } 48 | 49 | export function popTarget() { 50 | targetStack.pop(); 51 | Dep.target = targetStack[targetStack.length - 1]; 52 | } 53 | -------------------------------------------------------------------------------- /code/8.Vue2剥丝抽茧-响应式系统之set和delete/util.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Remove an item from an array. 3 | */ 4 | export function remove(arr, item) { 5 | if (arr.length) { 6 | const index = arr.indexOf(item); 7 | if (index > -1) { 8 | return arr.splice(index, 1); 9 | } 10 | } 11 | } 12 | 13 | /** 14 | * Quick object check - this is primarily used to tell 15 | * Objects from primitive values when we know the value 16 | * is a JSON-compliant type. 17 | */ 18 | export function isObject(obj) { 19 | return obj !== null && typeof obj === "object"; 20 | } 21 | 22 | /** 23 | * Define a property. 24 | */ 25 | export function def(obj, key, val, enumerable) { 26 | Object.defineProperty(obj, key, { 27 | value: val, 28 | enumerable: !!enumerable, 29 | writable: true, 30 | configurable: true, 31 | }); 32 | } 33 | 34 | // can we use __proto__? 35 | export const hasProto = "__proto__" in {}; 36 | 37 | /** 38 | * Check if val is a valid array index. 39 | */ 40 | export function isValidArrayIndex(val) { 41 | const n = parseFloat(String(val)); 42 | return n >= 0 && Math.floor(n) === n && isFinite(val); 43 | } 44 | 45 | /** 46 | * Check whether an object has the property. 47 | */ 48 | const hasOwnProperty = Object.prototype.hasOwnProperty; 49 | export function hasOwn(obj, key) { 50 | return hasOwnProperty.call(obj, key); 51 | } 52 | -------------------------------------------------------------------------------- /code/9.Vue2剥丝抽茧-响应式系统之异步队列/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"; 7 | 8 | const arrayProto = Array.prototype; 9 | export const arrayMethods = Object.create(arrayProto); 10 | 11 | const methodsToPatch = [ 12 | "push", 13 | "pop", 14 | "shift", 15 | "unshift", 16 | "splice", 17 | "sort", 18 | "reverse", 19 | ]; 20 | 21 | /** 22 | * Intercept mutating methods and emit events 23 | */ 24 | methodsToPatch.forEach(function (method) { 25 | // cache original method 26 | const original = arrayProto[method]; 27 | def(arrayMethods, method, function mutator(...args) { 28 | const result = original.apply(this, args); 29 | const ob = this.__ob__; 30 | /******新增 *************************/ 31 | let inserted; // 加添加的元素拿到 32 | switch (method) { 33 | case "push": 34 | case "unshift": 35 | inserted = args; 36 | break; 37 | case "splice": 38 | inserted = args.slice(2); 39 | break; 40 | } 41 | if (inserted) ob.observeArray(inserted); 42 | /************************************/ 43 | // notify change 44 | ob.dep.notify(); 45 | return result; 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /code/9.Vue2剥丝抽茧-响应式系统之异步队列/data.js: -------------------------------------------------------------------------------- 1 | // https://vue.windliang.wang/posts/Vue2%E5%89%A5%E4%B8%9D%E6%8A%BD%E8%8C%A7-%E5%93%8D%E5%BA%94%E5%BC%8F%E7%B3%BB%E7%BB%9F%E4%B9%8B%E5%BC%82%E6%AD%A5%E9%98%9F%E5%88%97.html 2 | import { observe } from "./reactive"; 3 | import Watcher from "./watcher"; 4 | 5 | const data = { 6 | a: 1, 7 | b: 2, 8 | c: 3, 9 | }; 10 | observe(data); 11 | const updateComponent = () => { 12 | console.log(data.a + data.b); 13 | }; 14 | 15 | new Watcher(updateComponent); 16 | 17 | const updateComponent2 = () => { 18 | console.log(data.c); 19 | }; 20 | new Watcher(updateComponent2); 21 | 22 | data.a = 2; 23 | data.a = 3; 24 | data.b = 4; 25 | 26 | data.c = 5; 27 | -------------------------------------------------------------------------------- /code/9.Vue2剥丝抽茧-响应式系统之异步队列/dep.js: -------------------------------------------------------------------------------- 1 | import { remove } from "./util"; 2 | 3 | let uid = 0; 4 | 5 | export default class Dep { 6 | static target; //当前在执行的函数 7 | subs; // 依赖的函数 8 | id; // Dep 对象标识 9 | constructor() { 10 | this.id = uid++; 11 | this.subs = []; // 保存所有需要执行的函数 12 | } 13 | 14 | addSub(sub) { 15 | this.subs.push(sub); 16 | } 17 | 18 | removeSub(sub) { 19 | remove(this.subs, sub); 20 | } 21 | depend() { 22 | if (Dep.target) { 23 | // 委托给 Dep.target 去调用 addSub 24 | Dep.target.addDep(this); 25 | } 26 | } 27 | 28 | notify() { 29 | // stabilize the subscriber list first 30 | const subs = this.subs.slice(); 31 | for (let i = 0, l = subs.length; i < l; i++) { 32 | subs[i].update(); 33 | } 34 | } 35 | } 36 | 37 | Dep.target = null; // 静态变量,全局唯一 38 | 39 | // The current target watcher being evaluated. 40 | // This is globally unique because only one watcher 41 | // can be evaluated at a time. 42 | const targetStack = []; 43 | 44 | export function pushTarget(target) { 45 | targetStack.push(target); 46 | Dep.target = target; 47 | } 48 | 49 | export function popTarget() { 50 | targetStack.pop(); 51 | Dep.target = targetStack[targetStack.length - 1]; 52 | } 53 | -------------------------------------------------------------------------------- /code/9.Vue2剥丝抽茧-响应式系统之异步队列/scheduler.js: -------------------------------------------------------------------------------- 1 | const queue = []; 2 | let has = {}; 3 | let waiting = false; 4 | let flushing = false; 5 | let index = 0; 6 | 7 | /** 8 | * Reset the scheduler's state. 9 | */ 10 | function resetSchedulerState() { 11 | index = queue.length = 0; 12 | has = {}; 13 | waiting = flushing = false; 14 | } 15 | 16 | /** 17 | * Flush both queues and run the watchers. 18 | */ 19 | function flushSchedulerQueue() { 20 | flushing = true; 21 | let watcher, id; 22 | // do not cache length because more watchers might be pushed 23 | // as we run existing watchers 24 | for (index = 0; index < queue.length; index++) { 25 | watcher = queue[index]; 26 | if (watcher.before) { 27 | watcher.before(); 28 | } 29 | id = watcher.id; 30 | has[id] = null; 31 | watcher.run(); 32 | } 33 | 34 | resetSchedulerState(); 35 | } 36 | 37 | /** 38 | * Push a watcher into the watcher queue. 39 | * Jobs with duplicate IDs will be skipped unless it's 40 | * pushed when the queue is being flushed. 41 | */ 42 | export function queueWatcher(watcher) { 43 | const id = watcher.id; 44 | if (has[id] == null) { 45 | has[id] = true; 46 | queue.push(watcher); 47 | // queue the flush 48 | if (!waiting) { 49 | waiting = true; 50 | setTimeout(flushSchedulerQueue, 0); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /code/9.Vue2剥丝抽茧-响应式系统之异步队列/util.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Remove an item from an array. 3 | */ 4 | export function remove(arr, item) { 5 | if (arr.length) { 6 | const index = arr.indexOf(item); 7 | if (index > -1) { 8 | return arr.splice(index, 1); 9 | } 10 | } 11 | } 12 | 13 | /** 14 | * Quick object check - this is primarily used to tell 15 | * Objects from primitive values when we know the value 16 | * is a JSON-compliant type. 17 | */ 18 | export function isObject(obj) { 19 | return obj !== null && typeof obj === "object"; 20 | } 21 | 22 | /** 23 | * Define a property. 24 | */ 25 | export function def(obj, key, val, enumerable) { 26 | Object.defineProperty(obj, key, { 27 | value: val, 28 | enumerable: !!enumerable, 29 | writable: true, 30 | configurable: true, 31 | }); 32 | } 33 | 34 | // can we use __proto__? 35 | export const hasProto = "__proto__" in {}; 36 | 37 | /** 38 | * Check if val is a valid array index. 39 | */ 40 | export function isValidArrayIndex(val) { 41 | const n = parseFloat(String(val)); 42 | return n >= 0 && Math.floor(n) === n && isFinite(val); 43 | } 44 | 45 | /** 46 | * Check whether an object has the property. 47 | */ 48 | const hasOwnProperty = Object.prototype.hasOwnProperty; 49 | export function hasOwn(obj, key) { 50 | return hasOwnProperty.call(obj, key); 51 | } 52 | -------------------------------------------------------------------------------- /code/VueLiang0/src/core/global-api/index.js: -------------------------------------------------------------------------------- 1 | import { set, del } from '../observer/index' 2 | 3 | import { 4 | nextTick, 5 | } from '../util/index' 6 | 7 | export function initGlobalAPI (Vue) { 8 | Vue.set = set 9 | Vue.delete = del 10 | Vue.nextTick = nextTick 11 | } 12 | -------------------------------------------------------------------------------- /code/VueLiang0/src/core/index.js: -------------------------------------------------------------------------------- 1 | import Vue from './instance/index' 2 | import { initGlobalAPI } from './global-api/index' 3 | 4 | initGlobalAPI(Vue) 5 | 6 | export default Vue 7 | -------------------------------------------------------------------------------- /code/VueLiang0/src/core/instance/index.js: -------------------------------------------------------------------------------- 1 | import { initMixin } from "./init"; 2 | import { stateMixin } from "./state"; 3 | import { lifecycleMixin } from "./lifecycle"; 4 | import { renderMixin } from "./render"; 5 | 6 | function Vue(options) { 7 | this._init(options); 8 | } 9 | 10 | initMixin(Vue); 11 | stateMixin(Vue); 12 | lifecycleMixin(Vue); 13 | renderMixin(Vue); 14 | 15 | export default Vue; 16 | -------------------------------------------------------------------------------- /code/VueLiang0/src/core/instance/init.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { initState } from "./state"; 4 | 5 | export function initMixin(Vue) { 6 | Vue.prototype._init = function (options) { 7 | const vm = this; 8 | vm.$options = options; 9 | vm._renderProxy = vm; 10 | initState(vm); 11 | if (vm.$options.el) { 12 | vm.$mount(vm.$options.el); 13 | } 14 | }; 15 | } 16 | -------------------------------------------------------------------------------- /code/VueLiang0/src/core/instance/lifecycle.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import Watcher from "../observer/watcher"; 4 | 5 | import { noop } from "../util/index"; 6 | 7 | export function lifecycleMixin(Vue) { 8 | /**** vue2 源码中 $mount 在另一个位置,这里临时放到这里 */ 9 | Vue.prototype.$mount = function (el) { 10 | el = el && document.querySelector(el); 11 | return mountComponent(this, el); 12 | }; 13 | /*******************************/ 14 | Vue.prototype._update = function (dom) { 15 | const vm = this; 16 | /*****这里仅仅是把 dom 更新,vue2 源码中这里会进行虚拟 dom 的处理 */ 17 | if (vm.$el.children[0]) { 18 | vm.$el.removeChild(vm.$el.children[0]); 19 | } 20 | vm.$el.appendChild(dom); 21 | /*******************************/ 22 | }; 23 | } 24 | 25 | export function mountComponent(vm, el) { 26 | vm.$el = el; 27 | let updateComponent; 28 | updateComponent = () => { 29 | vm._update(vm._render()); 30 | }; 31 | // we set this to vm._watcher inside the watcher's constructor 32 | // since the watcher's initial patch may call $forceUpdate (e.g. inside child 33 | // component's mounted hook), which relies on vm._watcher being already defined 34 | new Watcher(vm, updateComponent, noop /* isRenderWatcher */); 35 | return vm; 36 | } 37 | -------------------------------------------------------------------------------- /code/VueLiang0/src/core/instance/render.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { nextTick } from "../util/index"; 4 | 5 | export function renderMixin(Vue) { 6 | Vue.prototype.$nextTick = function (fn) { 7 | return nextTick(fn, this); 8 | }; 9 | 10 | Vue.prototype._render = function () { 11 | const vm = this; 12 | const { render } = vm.$options; 13 | 14 | return render.call(vm._renderProxy); 15 | }; 16 | } 17 | -------------------------------------------------------------------------------- /code/VueLiang0/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"; 7 | 8 | const arrayProto = Array.prototype; 9 | export const arrayMethods = Object.create(arrayProto); 10 | 11 | const methodsToPatch = [ 12 | "push", 13 | "pop", 14 | "shift", 15 | "unshift", 16 | "splice", 17 | "sort", 18 | "reverse", 19 | ]; 20 | 21 | /** 22 | * Intercept mutating methods and emit events 23 | */ 24 | methodsToPatch.forEach(function (method) { 25 | // cache original method 26 | const original = arrayProto[method]; 27 | def(arrayMethods, method, function mutator(...args) { 28 | const result = original.apply(this, args); 29 | const ob = this.__ob__; 30 | let inserted; // 加添加的元素拿到 31 | switch (method) { 32 | case "push": 33 | case "unshift": 34 | inserted = args; 35 | break; 36 | case "splice": 37 | inserted = args.slice(2); 38 | break; 39 | } 40 | if (inserted) ob.observeArray(inserted); 41 | // notify change 42 | ob.dep.notify(); 43 | return result; 44 | }); 45 | }); 46 | -------------------------------------------------------------------------------- /code/VueLiang0/src/core/observer/dep.js: -------------------------------------------------------------------------------- 1 | import { remove } from "../util"; 2 | 3 | let uid = 0; 4 | 5 | export default class Dep { 6 | static target; //当前在执行的函数 7 | subs; // 依赖的函数 8 | id; // Dep 对象标识 9 | constructor() { 10 | this.id = uid++; 11 | this.subs = []; // 保存所有需要执行的函数 12 | } 13 | 14 | addSub(sub) { 15 | this.subs.push(sub); 16 | } 17 | 18 | removeSub(sub) { 19 | remove(this.subs, sub); 20 | } 21 | depend() { 22 | if (Dep.target) { 23 | // 委托给 Dep.target 去调用 addSub 24 | Dep.target.addDep(this); 25 | } 26 | } 27 | 28 | notify() { 29 | // stabilize the subscriber list first 30 | const subs = this.subs.slice(); 31 | for (let i = 0, l = subs.length; i < l; i++) { 32 | subs[i].update(); 33 | } 34 | } 35 | } 36 | 37 | Dep.target = null; // 静态变量,全局唯一 38 | 39 | // The current target watcher being evaluated. 40 | // This is globally unique because only one watcher 41 | // can be evaluated at a time. 42 | const targetStack = []; 43 | 44 | export function pushTarget(target) { 45 | targetStack.push(target); 46 | Dep.target = target; 47 | } 48 | 49 | export function popTarget() { 50 | targetStack.pop(); 51 | Dep.target = targetStack[targetStack.length - 1]; 52 | } 53 | -------------------------------------------------------------------------------- /code/VueLiang0/src/core/observer/scheduler.js: -------------------------------------------------------------------------------- 1 | import { nextTick } from "../util/next-tick"; 2 | const queue = []; 3 | let has = {}; 4 | let waiting = false; 5 | let flushing = false; 6 | let index = 0; 7 | 8 | /** 9 | * Reset the scheduler's state. 10 | */ 11 | function resetSchedulerState() { 12 | index = queue.length = 0; 13 | has = {}; 14 | waiting = flushing = false; 15 | } 16 | 17 | /** 18 | * Flush both queues and run the watchers. 19 | */ 20 | function flushSchedulerQueue() { 21 | flushing = true; 22 | let watcher, id; 23 | // do not cache length because more watchers might be pushed 24 | // as we run existing watchers 25 | for (index = 0; index < queue.length; index++) { 26 | watcher = queue[index]; 27 | if (watcher.before) { 28 | watcher.before(); 29 | } 30 | id = watcher.id; 31 | has[id] = null; 32 | watcher.run(); 33 | } 34 | 35 | resetSchedulerState(); 36 | } 37 | 38 | /** 39 | * Push a watcher into the watcher queue. 40 | * Jobs with duplicate IDs will be skipped unless it's 41 | * pushed when the queue is being flushed. 42 | */ 43 | export function queueWatcher(watcher) { 44 | const id = watcher.id; 45 | if (has[id] == null) { 46 | has[id] = true; 47 | queue.push(watcher); 48 | // queue the flush 49 | if (!waiting) { 50 | waiting = true; 51 | nextTick(flushSchedulerQueue); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /code/VueLiang0/src/core/observer/traverse.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { isObject } from "../util"; 4 | 5 | const seenObjects = new Set(); 6 | 7 | /** 8 | * Recursively traverse an object to evoke all converted 9 | * getters, so that every nested property inside the object 10 | * is collected as a "deep" dependency. 11 | */ 12 | export function traverse(val) { 13 | _traverse(val, seenObjects); 14 | seenObjects.clear(); 15 | } 16 | 17 | function _traverse(val, seen) { 18 | let i, keys; 19 | const isA = Array.isArray(val); 20 | if ((!isA && !isObject(val)) || Object.isFrozen(val)) { 21 | return; 22 | } 23 | if (val.__ob__) { 24 | const depId = val.__ob__.dep.id; 25 | if (seen.has(depId)) { 26 | return; 27 | } 28 | seen.add(depId); 29 | } 30 | if (isA) { 31 | i = val.length; 32 | while (i--) _traverse(val[i], seen); 33 | } else { 34 | keys = Object.keys(val); 35 | i = keys.length; 36 | while (i--) _traverse(val[keys[i]], seen); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /code/VueLiang0/src/core/util/env.js: -------------------------------------------------------------------------------- 1 | /* istanbul ignore next */ 2 | export function isNative(Ctor) { 3 | return typeof Ctor === "function" && /native code/.test(Ctor.toString()); 4 | } 5 | 6 | // can we use __proto__? 7 | export const hasProto = "__proto__" in {}; 8 | -------------------------------------------------------------------------------- /code/VueLiang0/src/core/util/index.js: -------------------------------------------------------------------------------- 1 | export * from "../../shared/util"; 2 | export * from "./lang"; 3 | export * from "./env"; 4 | export * from "./next-tick"; 5 | -------------------------------------------------------------------------------- /code/VueLiang0/src/core/util/lang.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | /** 4 | * unicode letters used for parsing html tags, component names and property paths. 5 | * using https://www.w3.org/TR/html53/semantics-scripting.html#potentialcustomelementname 6 | * skipping \u10000-\uEFFFF due to it freezing up PhantomJS 7 | */ 8 | export const unicodeRegExp = 9 | /a-zA-Z\u00B7\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u037D\u037F-\u1FFF\u200C-\u200D\u203F-\u2040\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD/; 10 | 11 | /** 12 | * Check if a string starts with $ or _ 13 | */ 14 | export function isReserved(str) { 15 | const c = (str + "").charCodeAt(0); 16 | return c === 0x24 || c === 0x5f; 17 | } 18 | 19 | /** 20 | * Define a property. 21 | */ 22 | export function def(obj, key, val, enumerable) { 23 | Object.defineProperty(obj, key, { 24 | value: val, 25 | enumerable: !!enumerable, 26 | writable: true, 27 | configurable: true, 28 | }); 29 | } 30 | 31 | /** 32 | * Parse simple path. 33 | */ 34 | const bailRE = new RegExp(`[^${unicodeRegExp.source}.$_\\d]`); 35 | export function parsePath(path) { 36 | if (bailRE.test(path)) { 37 | return; 38 | } 39 | const segments = path.split("."); 40 | return function (obj) { 41 | for (let i = 0; i < segments.length; i++) { 42 | if (!obj) return; 43 | obj = obj[segments[i]]; 44 | } 45 | return obj; 46 | }; 47 | } 48 | -------------------------------------------------------------------------------- /code/VueLiang0/src/core/util/next-tick.js: -------------------------------------------------------------------------------- 1 | import { isNative } from "./env"; 2 | 3 | const callbacks = []; 4 | let pending = false; 5 | 6 | function flushCallbacks() { 7 | pending = false; 8 | const copies = callbacks.slice(0); 9 | callbacks.length = 0; 10 | for (let i = 0; i < copies.length; i++) { 11 | copies[i](); 12 | } 13 | } 14 | 15 | let timerFunc; 16 | 17 | if (typeof Promise !== "undefined" && isNative(Promise)) { 18 | const p = Promise.resolve(); 19 | timerFunc = () => { 20 | p.then(flushCallbacks); 21 | }; 22 | } else { 23 | // Fallback to setTimeout. 24 | timerFunc = () => { 25 | setTimeout(flushCallbacks, 0); 26 | }; 27 | } 28 | 29 | export function nextTick(cb, ctx) { 30 | let _resolve; 31 | callbacks.push(() => { 32 | if (cb) { 33 | cb.call(ctx); 34 | } else if (_resolve) { 35 | _resolve(ctx); 36 | } 37 | }); 38 | if (!pending) { 39 | pending = true; 40 | timerFunc(); // 只执行一次 41 | } 42 | // $flow-disable-line 43 | if (!cb && typeof Promise !== "undefined") { 44 | return new Promise((resolve) => { 45 | _resolve = resolve; 46 | }); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /code/VueLiang0/vueliang0.js: -------------------------------------------------------------------------------- 1 | import Vue from "./src/core/index"; 2 | 3 | new Vue({ 4 | el: "#root", 5 | data() { 6 | return { 7 | test: 1, 8 | name: "data:liang", 9 | }; 10 | }, 11 | watch: { 12 | test(newVal, oldVal) { 13 | console.log(newVal, oldVal); 14 | }, 15 | }, 16 | computed: { 17 | text() { 18 | return "computed:hello:" + this.name; 19 | }, 20 | }, 21 | methods: { 22 | hello() { 23 | return "调用methods:hello"; 24 | }, 25 | click() { 26 | this.test = 3; 27 | this.name = "wind"; 28 | }, 29 | }, 30 | render() { 31 | const node = document.createElement("div"); 32 | 33 | const dataNode = document.createElement("div"); 34 | dataNode.innerText = this.test; 35 | node.append(dataNode); 36 | 37 | const computedNode = document.createElement("div"); 38 | computedNode.innerText = this.text; 39 | node.append(computedNode); 40 | 41 | const methodsNode = document.createElement("div"); 42 | methodsNode.innerText = this.hello(); 43 | node.append(methodsNode); 44 | 45 | node.addEventListener("click", this.click); 46 | return node; 47 | }, 48 | }); 49 | -------------------------------------------------------------------------------- /code/VueLiang1/src/core/global-api/index.js: -------------------------------------------------------------------------------- 1 | import { set, del } from '../observer/index' 2 | 3 | import { 4 | nextTick, 5 | } from '../util/index' 6 | 7 | export function initGlobalAPI (Vue) { 8 | Vue.set = set 9 | Vue.delete = del 10 | Vue.nextTick = nextTick 11 | } 12 | -------------------------------------------------------------------------------- /code/VueLiang1/src/core/index.js: -------------------------------------------------------------------------------- 1 | import Vue from './instance/index' 2 | import { initGlobalAPI } from './global-api/index' 3 | 4 | initGlobalAPI(Vue) 5 | 6 | export default Vue 7 | -------------------------------------------------------------------------------- /code/VueLiang1/src/core/instance/index.js: -------------------------------------------------------------------------------- 1 | import { initMixin } from "./init"; 2 | import { stateMixin } from "./state"; 3 | import { lifecycleMixin } from "./lifecycle"; 4 | import { renderMixin } from "./render"; 5 | 6 | function Vue(options) { 7 | this._init(options); 8 | } 9 | 10 | initMixin(Vue); 11 | stateMixin(Vue); 12 | lifecycleMixin(Vue); 13 | renderMixin(Vue); 14 | 15 | export default Vue; 16 | -------------------------------------------------------------------------------- /code/VueLiang1/src/core/instance/init.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { initState } from "./state"; 4 | import { initRender } from "./render"; 5 | 6 | export function initMixin(Vue) { 7 | Vue.prototype._init = function (options) { 8 | const vm = this; 9 | vm.$options = options; 10 | vm._renderProxy = vm; 11 | initRender(vm); 12 | initState(vm); 13 | if (vm.$options.el) { 14 | vm.$mount(vm.$options.el); 15 | } 16 | }; 17 | } 18 | -------------------------------------------------------------------------------- /code/VueLiang1/src/core/instance/lifecycle.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import Watcher from "../observer/watcher"; 4 | 5 | import { noop } from "../util/index"; 6 | 7 | export function lifecycleMixin(Vue) { 8 | /**** vue2 源码中 $mount 在另一个位置,这里临时放到这里 */ 9 | 10 | /*******************************/ 11 | Vue.prototype._update = function (vnode) { 12 | const vm = this; 13 | const prevVnode = vm._vnode; 14 | vm._vnode = vnode; 15 | // Vue.prototype.__patch__ is injected in entry points 16 | // based on the rendering backend used. 17 | if (!prevVnode) { 18 | // initial render 19 | vm.$el = vm.__patch__(vm.$el, vnode); 20 | } else { 21 | // updates 22 | vm.$el = vm.__patch__(prevVnode, vnode); 23 | } 24 | }; 25 | } 26 | 27 | export function mountComponent(vm, el) { 28 | vm.$el = el; 29 | let updateComponent; 30 | updateComponent = () => { 31 | vm._update(vm._render()); 32 | }; 33 | // we set this to vm._watcher inside the watcher's constructor 34 | // since the watcher's initial patch may call $forceUpdate (e.g. inside child 35 | // component's mounted hook), which relies on vm._watcher being already defined 36 | new Watcher(vm, updateComponent, noop /* isRenderWatcher */); 37 | return vm; 38 | } 39 | -------------------------------------------------------------------------------- /code/VueLiang1/src/core/instance/render.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { nextTick } from "../util/index"; 4 | import { createElement } from "../vdom/create-element"; 5 | 6 | export function initRender(vm) { 7 | const options = vm.$options; 8 | vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false); 9 | // normalization is always applied for the public version, used in 10 | // user-written render functions. 11 | vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true); 12 | } 13 | 14 | export function renderMixin(Vue) { 15 | Vue.prototype.$nextTick = function (fn) { 16 | return nextTick(fn, this); 17 | }; 18 | 19 | Vue.prototype._render = function () { 20 | const vm = this; 21 | const { render } = vm.$options; 22 | 23 | return render.call(vm._renderProxy, vm.$createElement); 24 | }; 25 | } 26 | -------------------------------------------------------------------------------- /code/VueLiang1/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"; 7 | 8 | const arrayProto = Array.prototype; 9 | export const arrayMethods = Object.create(arrayProto); 10 | 11 | const methodsToPatch = [ 12 | "push", 13 | "pop", 14 | "shift", 15 | "unshift", 16 | "splice", 17 | "sort", 18 | "reverse", 19 | ]; 20 | 21 | /** 22 | * Intercept mutating methods and emit events 23 | */ 24 | methodsToPatch.forEach(function (method) { 25 | // cache original method 26 | const original = arrayProto[method]; 27 | def(arrayMethods, method, function mutator(...args) { 28 | const result = original.apply(this, args); 29 | const ob = this.__ob__; 30 | let inserted; // 加添加的元素拿到 31 | switch (method) { 32 | case "push": 33 | case "unshift": 34 | inserted = args; 35 | break; 36 | case "splice": 37 | inserted = args.slice(2); 38 | break; 39 | } 40 | if (inserted) ob.observeArray(inserted); 41 | // notify change 42 | ob.dep.notify(); 43 | return result; 44 | }); 45 | }); 46 | -------------------------------------------------------------------------------- /code/VueLiang1/src/core/observer/dep.js: -------------------------------------------------------------------------------- 1 | import { remove } from "../util"; 2 | 3 | let uid = 0; 4 | 5 | export default class Dep { 6 | static target; //当前在执行的函数 7 | subs; // 依赖的函数 8 | id; // Dep 对象标识 9 | constructor() { 10 | this.id = uid++; 11 | this.subs = []; // 保存所有需要执行的函数 12 | } 13 | 14 | addSub(sub) { 15 | this.subs.push(sub); 16 | } 17 | 18 | removeSub(sub) { 19 | remove(this.subs, sub); 20 | } 21 | depend() { 22 | if (Dep.target) { 23 | // 委托给 Dep.target 去调用 addSub 24 | Dep.target.addDep(this); 25 | } 26 | } 27 | 28 | notify() { 29 | // stabilize the subscriber list first 30 | const subs = this.subs.slice(); 31 | for (let i = 0, l = subs.length; i < l; i++) { 32 | subs[i].update(); 33 | } 34 | } 35 | } 36 | 37 | Dep.target = null; // 静态变量,全局唯一 38 | 39 | // The current target watcher being evaluated. 40 | // This is globally unique because only one watcher 41 | // can be evaluated at a time. 42 | const targetStack = []; 43 | 44 | export function pushTarget(target) { 45 | targetStack.push(target); 46 | Dep.target = target; 47 | } 48 | 49 | export function popTarget() { 50 | targetStack.pop(); 51 | Dep.target = targetStack[targetStack.length - 1]; 52 | } 53 | -------------------------------------------------------------------------------- /code/VueLiang1/src/core/observer/traverse.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { isObject } from "../util"; 4 | 5 | const seenObjects = new Set(); 6 | 7 | /** 8 | * Recursively traverse an object to evoke all converted 9 | * getters, so that every nested property inside the object 10 | * is collected as a "deep" dependency. 11 | */ 12 | export function traverse(val) { 13 | _traverse(val, seenObjects); 14 | seenObjects.clear(); 15 | } 16 | 17 | function _traverse(val, seen) { 18 | let i, keys; 19 | const isA = Array.isArray(val); 20 | if ((!isA && !isObject(val)) || Object.isFrozen(val)) { 21 | return; 22 | } 23 | if (val.__ob__) { 24 | const depId = val.__ob__.dep.id; 25 | if (seen.has(depId)) { 26 | return; 27 | } 28 | seen.add(depId); 29 | } 30 | if (isA) { 31 | i = val.length; 32 | while (i--) _traverse(val[i], seen); 33 | } else { 34 | keys = Object.keys(val); 35 | i = keys.length; 36 | while (i--) _traverse(val[keys[i]], seen); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /code/VueLiang1/src/core/util/env.js: -------------------------------------------------------------------------------- 1 | // Browser environment sniffing 2 | export const inBrowser = typeof window !== "undefined"; 3 | 4 | /* istanbul ignore next */ 5 | export function isNative(Ctor) { 6 | return typeof Ctor === "function" && /native code/.test(Ctor.toString()); 7 | } 8 | 9 | // can we use __proto__? 10 | export const hasProto = "__proto__" in {}; 11 | 12 | export let supportsPassive = false; 13 | if (inBrowser) { 14 | try { 15 | const opts = {}; 16 | Object.defineProperty(opts, "passive", { 17 | get() { 18 | /* istanbul ignore next */ 19 | supportsPassive = true; 20 | }, 21 | }); // https://github.com/facebook/flow/issues/285 22 | window.addEventListener("test-passive", null, opts); 23 | } catch (e) {} 24 | } 25 | -------------------------------------------------------------------------------- /code/VueLiang1/src/core/util/index.js: -------------------------------------------------------------------------------- 1 | export * from "../../shared/util"; 2 | export * from "./lang"; 3 | export * from "./env"; 4 | export * from "./next-tick"; 5 | -------------------------------------------------------------------------------- /code/VueLiang1/src/core/util/lang.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | /** 4 | * unicode letters used for parsing html tags, component names and property paths. 5 | * using https://www.w3.org/TR/html53/semantics-scripting.html#potentialcustomelementname 6 | * skipping \u10000-\uEFFFF due to it freezing up PhantomJS 7 | */ 8 | export const unicodeRegExp = 9 | /a-zA-Z\u00B7\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u037D\u037F-\u1FFF\u200C-\u200D\u203F-\u2040\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD/; 10 | 11 | /** 12 | * Check if a string starts with $ or _ 13 | */ 14 | export function isReserved(str) { 15 | const c = (str + "").charCodeAt(0); 16 | return c === 0x24 || c === 0x5f; 17 | } 18 | 19 | /** 20 | * Define a property. 21 | */ 22 | export function def(obj, key, val, enumerable) { 23 | Object.defineProperty(obj, key, { 24 | value: val, 25 | enumerable: !!enumerable, 26 | writable: true, 27 | configurable: true, 28 | }); 29 | } 30 | 31 | /** 32 | * Parse simple path. 33 | */ 34 | const bailRE = new RegExp(`[^${unicodeRegExp.source}.$_\\d]`); 35 | export function parsePath(path) { 36 | if (bailRE.test(path)) { 37 | return; 38 | } 39 | const segments = path.split("."); 40 | return function (obj) { 41 | for (let i = 0; i < segments.length; i++) { 42 | if (!obj) return; 43 | obj = obj[segments[i]]; 44 | } 45 | return obj; 46 | }; 47 | } 48 | -------------------------------------------------------------------------------- /code/VueLiang1/src/core/util/next-tick.js: -------------------------------------------------------------------------------- 1 | import { isNative } from "./env"; 2 | 3 | const callbacks = []; 4 | let pending = false; 5 | 6 | function flushCallbacks() { 7 | pending = false; 8 | const copies = callbacks.slice(0); 9 | callbacks.length = 0; 10 | for (let i = 0; i < copies.length; i++) { 11 | copies[i](); 12 | } 13 | } 14 | 15 | let timerFunc; 16 | 17 | if (typeof Promise !== "undefined" && isNative(Promise)) { 18 | const p = Promise.resolve(); 19 | timerFunc = () => { 20 | p.then(flushCallbacks); 21 | }; 22 | } else { 23 | // Fallback to setTimeout. 24 | timerFunc = () => { 25 | setTimeout(flushCallbacks, 0); 26 | }; 27 | } 28 | 29 | export function nextTick(cb, ctx) { 30 | let _resolve; 31 | callbacks.push(() => { 32 | if (cb) { 33 | cb.call(ctx); 34 | } else if (_resolve) { 35 | _resolve(ctx); 36 | } 37 | }); 38 | if (!pending) { 39 | pending = true; 40 | timerFunc(); // 只执行一次 41 | } 42 | // $flow-disable-line 43 | if (!cb && typeof Promise !== "undefined") { 44 | return new Promise((resolve) => { 45 | _resolve = resolve; 46 | }); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /code/VueLiang1/src/core/vdom/create-element.js: -------------------------------------------------------------------------------- 1 | import VNode, { createEmptyVNode } from "./vnode"; 2 | import { normalizeChildren } from "./helpers/index"; 3 | import { isPrimitive } from "../util/index"; 4 | // wrapper function for providing a more flexible interface 5 | // without getting yelled at by flow 6 | export function createElement(context, tag, data, children) { 7 | return _createElement(context, tag, data, children); 8 | } 9 | 10 | export function _createElement(context, tag, data, children) { 11 | if (!tag) { 12 | // in case of component :is set to falsy value 13 | return createEmptyVNode(); 14 | } 15 | if (Array.isArray(data) || isPrimitive(data)) { 16 | children = data; 17 | data = undefined; 18 | } 19 | children = normalizeChildren(children); 20 | let vnode = new VNode(tag, data, children, undefined, undefined, context); 21 | return vnode; 22 | } 23 | -------------------------------------------------------------------------------- /code/VueLiang1/src/core/vdom/helpers/index.js: -------------------------------------------------------------------------------- 1 | export * from "./normalize-children"; 2 | export * from "./update-listeners"; 3 | -------------------------------------------------------------------------------- /code/VueLiang1/src/core/vdom/helpers/normalize-children.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { createTextVNode } from "@/core/vdom/vnode"; 4 | import { isUndef, isPrimitive } from "@/shared/util"; 5 | 6 | export function normalizeChildren(children) { 7 | return isPrimitive(children) 8 | ? [createTextVNode(children)] 9 | : Array.isArray(children) 10 | ? normalizeArrayChildren(children) 11 | : undefined; 12 | } 13 | 14 | function normalizeArrayChildren(children) { 15 | const res = []; 16 | let i, c; 17 | for (i = 0; i < children.length; i++) { 18 | c = children[i]; 19 | if (isUndef(c) || typeof c === "boolean") continue; 20 | if (isPrimitive(c)) { 21 | if (c !== "") 22 | // convert primitive to vnode 23 | res.push(createTextVNode(c)); 24 | // 省略了很多 if else 25 | } else { 26 | // 走到这里说明当前 c 已经是一个 vnode 节点了 27 | res.push(c); 28 | } 29 | } 30 | return res; 31 | } 32 | -------------------------------------------------------------------------------- /code/VueLiang1/src/platforms/web/entry-runtime.js: -------------------------------------------------------------------------------- 1 | import Vue from "./runtime/index"; 2 | 3 | export default Vue; 4 | -------------------------------------------------------------------------------- /code/VueLiang1/src/platforms/web/modules/index.js: -------------------------------------------------------------------------------- 1 | import events from "./events"; 2 | 3 | export default [events]; 4 | -------------------------------------------------------------------------------- /code/VueLiang1/src/platforms/web/runtime/index.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import Vue from "@/core/index"; 4 | import { noop } from "@/shared/util"; 5 | import { mountComponent } from "@/core/instance/lifecycle"; 6 | import { inBrowser } from "@/core/util/index"; 7 | 8 | import { patch } from "./patch"; 9 | // install platform patch function 10 | Vue.prototype.__patch__ = inBrowser ? patch : noop; 11 | 12 | // public mount method 13 | Vue.prototype.$mount = function (el) { 14 | el = el && document.querySelector(el); 15 | return mountComponent(this, el); 16 | }; 17 | 18 | export default Vue; 19 | -------------------------------------------------------------------------------- /code/VueLiang1/src/platforms/web/runtime/node-ops.js: -------------------------------------------------------------------------------- 1 | export function createElement(tagName) { 2 | const elm = document.createElement(tagName); 3 | return elm; 4 | } 5 | 6 | export function createTextNode(text) { 7 | return document.createTextNode(text); 8 | } 9 | 10 | export function insertBefore(parentNode, newNode, referenceNode) { 11 | parentNode.insertBefore(newNode, referenceNode); 12 | } 13 | 14 | export function removeChild(node, child) { 15 | node.removeChild(child); 16 | } 17 | 18 | export function appendChild(node, child) { 19 | node.appendChild(child); 20 | } 21 | 22 | export function parentNode(node) { 23 | return node.parentNode; 24 | } 25 | 26 | export function nextSibling(node) { 27 | return node.nextSibling; 28 | } 29 | 30 | export function tagName(node) { 31 | return node.tagName; 32 | } 33 | 34 | export function setTextContent(node, text) { 35 | node.textContent = text; 36 | } 37 | -------------------------------------------------------------------------------- /code/VueLiang1/src/platforms/web/runtime/patch.js: -------------------------------------------------------------------------------- 1 | import * as nodeOps from "./node-ops"; 2 | import { createPatchFunction } from "@/core/vdom/patch"; 3 | import platformModules from "../modules/index"; 4 | 5 | export const patch = createPatchFunction({ nodeOps, modules: platformModules }); 6 | -------------------------------------------------------------------------------- /code/VueLiang1/vueliang1.js: -------------------------------------------------------------------------------- 1 | import Vue from "./src/platforms/web/entry-runtime"; 2 | 3 | new Vue({ 4 | el: "#root", 5 | data() { 6 | return { 7 | test: 1, 8 | name: "data:liang", 9 | }; 10 | }, 11 | watch: { 12 | test(newVal, oldVal) { 13 | console.log(newVal, oldVal); 14 | }, 15 | }, 16 | computed: { 17 | text() { 18 | return "computed:hello:" + this.name; 19 | }, 20 | }, 21 | methods: { 22 | hello() { 23 | console.log("调用methods:hello"); 24 | return "调用methods:hello"; 25 | }, 26 | click() { 27 | this.test = 3; 28 | this.name = "wind"; 29 | }, 30 | }, 31 | render(createElement) { 32 | const test = createElement( 33 | "div", 34 | { 35 | on: { 36 | click: () => this.click(), 37 | dblclick: () => this.hello(), 38 | }, 39 | }, 40 | [this.text, createElement("div", this.test)] 41 | ); 42 | return test; 43 | }, 44 | }); 45 | -------------------------------------------------------------------------------- /code/core/components/index.js: -------------------------------------------------------------------------------- 1 | import KeepAlive from './keep-alive' 2 | 3 | export default { 4 | KeepAlive 5 | } 6 | -------------------------------------------------------------------------------- /code/core/global-api/assets.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { ASSET_TYPES } from 'shared/constants' 4 | import { isPlainObject, validateComponentName } from '../util/index' 5 | 6 | export function initAssetRegisters (Vue: GlobalAPI) { 7 | /** 8 | * Create asset registration methods. 9 | */ 10 | ASSET_TYPES.forEach(type => { 11 | Vue[type] = function ( 12 | id: string, 13 | definition: Function | Object 14 | ): Function | Object | void { 15 | if (!definition) { 16 | return this.options[type + 's'][id] 17 | } else { 18 | /* istanbul ignore if */ 19 | if (process.env.NODE_ENV !== 'production' && type === 'component') { 20 | validateComponentName(id) 21 | } 22 | if (type === 'component' && isPlainObject(definition)) { 23 | definition.name = definition.name || id 24 | definition = this.options._base.extend(definition) 25 | } 26 | if (type === 'directive' && typeof definition === 'function') { 27 | definition = { bind: definition, update: definition } 28 | } 29 | this.options[type + 's'][id] = definition 30 | return definition 31 | } 32 | } 33 | }) 34 | } 35 | -------------------------------------------------------------------------------- /code/core/global-api/mixin.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { mergeOptions } from '../util/index' 4 | 5 | export function initMixin (Vue: GlobalAPI) { 6 | Vue.mixin = function (mixin: Object) { 7 | this.options = mergeOptions(this.options, mixin) 8 | return this 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /code/core/global-api/use.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { toArray } from '../util/index' 4 | 5 | export function initUse (Vue: GlobalAPI) { 6 | Vue.use = function (plugin: Function | Object) { 7 | const installedPlugins = (this._installedPlugins || (this._installedPlugins = [])) 8 | if (installedPlugins.indexOf(plugin) > -1) { 9 | return this 10 | } 11 | 12 | // additional parameters 13 | const args = toArray(arguments, 1) 14 | args.unshift(this) 15 | if (typeof plugin.install === 'function') { 16 | plugin.install.apply(plugin, args) 17 | } else if (typeof plugin === 'function') { 18 | plugin.apply(null, args) 19 | } 20 | installedPlugins.push(plugin) 21 | return this 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /code/core/index.js: -------------------------------------------------------------------------------- 1 | import Vue from './instance/index' 2 | import { initGlobalAPI } from './global-api/index' 3 | import { isServerRendering } from 'core/util/env' 4 | import { FunctionalRenderContext } from 'core/vdom/create-functional-component' 5 | 6 | initGlobalAPI(Vue) 7 | 8 | Object.defineProperty(Vue.prototype, '$isServer', { 9 | get: isServerRendering 10 | }) 11 | 12 | Object.defineProperty(Vue.prototype, '$ssrContext', { 13 | get () { 14 | /* istanbul ignore next */ 15 | return this.$vnode && this.$vnode.ssrContext 16 | } 17 | }) 18 | 19 | // expose FunctionalRenderContext for ssr runtime helper installation 20 | Object.defineProperty(Vue, 'FunctionalRenderContext', { 21 | value: FunctionalRenderContext 22 | }) 23 | 24 | Vue.version = '__VERSION__' 25 | 26 | export default Vue 27 | -------------------------------------------------------------------------------- /code/core/instance/index.js: -------------------------------------------------------------------------------- 1 | import { initMixin } from './init' 2 | import { stateMixin } from './state' 3 | import { renderMixin } from './render' 4 | import { eventsMixin } from './events' 5 | import { lifecycleMixin } from './lifecycle' 6 | import { warn } from '../util/index' 7 | 8 | function Vue (options) { 9 | if (process.env.NODE_ENV !== 'production' && 10 | !(this instanceof Vue) 11 | ) { 12 | warn('Vue is a constructor and should be called with the `new` keyword') 13 | } 14 | this._init(options) 15 | } 16 | 17 | initMixin(Vue) 18 | stateMixin(Vue) 19 | eventsMixin(Vue) 20 | lifecycleMixin(Vue) 21 | renderMixin(Vue) 22 | 23 | export default Vue 24 | -------------------------------------------------------------------------------- /code/core/instance/render-helpers/bind-dynamic-keys.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | // helper to process dynamic keys for dynamic arguments in v-bind and v-on. 4 | // For example, the following template: 5 | // 6 | //
7 | // 8 | // compiles to the following: 9 | // 10 | // _c('div', { attrs: bindDynamicKeys({ "id": "app" }, [key, value]) }) 11 | 12 | import { warn } from 'core/util/debug' 13 | 14 | export function bindDynamicKeys (baseObj: Object, values: Array): Object { 15 | for (let i = 0; i < values.length; i += 2) { 16 | const key = values[i] 17 | if (typeof key === 'string' && key) { 18 | baseObj[values[i]] = values[i + 1] 19 | } else if (process.env.NODE_ENV !== 'production' && key !== '' && key !== null) { 20 | // null is a special value for explicitly removing a binding 21 | warn( 22 | `Invalid value for dynamic directive argument (expected string or null): ${key}`, 23 | this 24 | ) 25 | } 26 | } 27 | return baseObj 28 | } 29 | 30 | // helper to dynamically append modifier runtime markers to event names. 31 | // ensure only append when value is already string, otherwise it will be cast 32 | // to string and cause the type check to miss. 33 | export function prependModifier (value: any, symbol: string): any { 34 | return typeof value === 'string' ? symbol + value : value 35 | } 36 | -------------------------------------------------------------------------------- /code/core/instance/render-helpers/bind-object-listeners.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { warn, extend, isPlainObject } from 'core/util/index' 4 | 5 | export function bindObjectListeners (data: any, value: any): VNodeData { 6 | if (value) { 7 | if (!isPlainObject(value)) { 8 | process.env.NODE_ENV !== 'production' && warn( 9 | 'v-on without argument expects an Object value', 10 | this 11 | ) 12 | } else { 13 | const on = data.on = data.on ? extend({}, data.on) : {} 14 | for (const key in value) { 15 | const existing = on[key] 16 | const ours = value[key] 17 | on[key] = existing ? [].concat(existing, ours) : ours 18 | } 19 | } 20 | } 21 | return data 22 | } 23 | -------------------------------------------------------------------------------- /code/core/instance/render-helpers/check-keycodes.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import config from 'core/config' 4 | import { hyphenate } from 'shared/util' 5 | 6 | function isKeyNotMatch (expect: T | Array, actual: T): boolean { 7 | if (Array.isArray(expect)) { 8 | return expect.indexOf(actual) === -1 9 | } else { 10 | return expect !== actual 11 | } 12 | } 13 | 14 | /** 15 | * Runtime helper for checking keyCodes from config. 16 | * exposed as Vue.prototype._k 17 | * passing in eventKeyName as last argument separately for backwards compat 18 | */ 19 | export function checkKeyCodes ( 20 | eventKeyCode: number, 21 | key: string, 22 | builtInKeyCode?: number | Array, 23 | eventKeyName?: string, 24 | builtInKeyName?: string | Array 25 | ): ?boolean { 26 | const mappedKeyCode = config.keyCodes[key] || builtInKeyCode 27 | if (builtInKeyName && eventKeyName && !config.keyCodes[key]) { 28 | return isKeyNotMatch(builtInKeyName, eventKeyName) 29 | } else if (mappedKeyCode) { 30 | return isKeyNotMatch(mappedKeyCode, eventKeyCode) 31 | } else if (eventKeyName) { 32 | return hyphenate(eventKeyName) !== key 33 | } 34 | return eventKeyCode === undefined 35 | } 36 | -------------------------------------------------------------------------------- /code/core/instance/render-helpers/index.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { toNumber, toString, looseEqual, looseIndexOf } from 'shared/util' 4 | import { createTextVNode, createEmptyVNode } from 'core/vdom/vnode' 5 | import { renderList } from './render-list' 6 | import { renderSlot } from './render-slot' 7 | import { resolveFilter } from './resolve-filter' 8 | import { checkKeyCodes } from './check-keycodes' 9 | import { bindObjectProps } from './bind-object-props' 10 | import { renderStatic, markOnce } from './render-static' 11 | import { bindObjectListeners } from './bind-object-listeners' 12 | import { resolveScopedSlots } from './resolve-scoped-slots' 13 | import { bindDynamicKeys, prependModifier } from './bind-dynamic-keys' 14 | 15 | export function installRenderHelpers (target: any) { 16 | target._o = markOnce 17 | target._n = toNumber 18 | target._s = toString 19 | target._l = renderList 20 | target._t = renderSlot 21 | target._q = looseEqual 22 | target._i = looseIndexOf 23 | target._m = renderStatic 24 | target._f = resolveFilter 25 | target._k = checkKeyCodes 26 | target._b = bindObjectProps 27 | target._v = createTextVNode 28 | target._e = createEmptyVNode 29 | target._u = resolveScopedSlots 30 | target._g = bindObjectListeners 31 | target._d = bindDynamicKeys 32 | target._p = prependModifier 33 | } 34 | -------------------------------------------------------------------------------- /code/core/instance/render-helpers/render-list.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { isObject, isDef, hasSymbol } from 'core/util/index' 4 | 5 | /** 6 | * Runtime helper for rendering v-for lists. 7 | */ 8 | export function renderList ( 9 | val: any, 10 | render: ( 11 | val: any, 12 | keyOrIndex: string | number, 13 | index?: number 14 | ) => VNode 15 | ): ?Array { 16 | let ret: ?Array, i, l, keys, key 17 | if (Array.isArray(val) || typeof val === 'string') { 18 | ret = new Array(val.length) 19 | for (i = 0, l = val.length; i < l; i++) { 20 | ret[i] = render(val[i], i) 21 | } 22 | } else if (typeof val === 'number') { 23 | ret = new Array(val) 24 | for (i = 0; i < val; i++) { 25 | ret[i] = render(i + 1, i) 26 | } 27 | } else if (isObject(val)) { 28 | if (hasSymbol && val[Symbol.iterator]) { 29 | ret = [] 30 | const iterator: Iterator = val[Symbol.iterator]() 31 | let result = iterator.next() 32 | while (!result.done) { 33 | ret.push(render(result.value, ret.length)) 34 | result = iterator.next() 35 | } 36 | } else { 37 | keys = Object.keys(val) 38 | ret = new Array(keys.length) 39 | for (i = 0, l = keys.length; i < l; i++) { 40 | key = keys[i] 41 | ret[i] = render(val[key], key, i) 42 | } 43 | } 44 | } 45 | if (!isDef(ret)) { 46 | ret = [] 47 | } 48 | (ret: any)._isVList = true 49 | return ret 50 | } 51 | -------------------------------------------------------------------------------- /code/core/instance/render-helpers/render-slot.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { extend, warn, isObject } from 'core/util/index' 4 | 5 | /** 6 | * Runtime helper for rendering 7 | */ 8 | export function renderSlot ( 9 | name: string, 10 | fallbackRender: ?((() => Array) | Array), 11 | props: ?Object, 12 | bindObject: ?Object 13 | ): ?Array { 14 | const scopedSlotFn = this.$scopedSlots[name] 15 | let nodes 16 | if (scopedSlotFn) { 17 | // scoped slot 18 | props = props || {} 19 | if (bindObject) { 20 | if (process.env.NODE_ENV !== 'production' && !isObject(bindObject)) { 21 | warn('slot v-bind without argument expects an Object', this) 22 | } 23 | props = extend(extend({}, bindObject), props) 24 | } 25 | nodes = 26 | scopedSlotFn(props) || 27 | (typeof fallbackRender === 'function' ? fallbackRender() : fallbackRender) 28 | } else { 29 | nodes = 30 | this.$slots[name] || 31 | (typeof fallbackRender === 'function' ? fallbackRender() : fallbackRender) 32 | } 33 | 34 | const target = props && props.slot 35 | if (target) { 36 | return this.$createElement('template', { slot: target }, nodes) 37 | } else { 38 | return nodes 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /code/core/instance/render-helpers/resolve-filter.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { identity, resolveAsset } from 'core/util/index' 4 | 5 | /** 6 | * Runtime helper for resolving filters 7 | */ 8 | export function resolveFilter (id: string): Function { 9 | return resolveAsset(this.$options, 'filters', id, true) || identity 10 | } 11 | -------------------------------------------------------------------------------- /code/core/instance/render-helpers/resolve-scoped-slots.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | export function resolveScopedSlots ( 4 | fns: ScopedSlotsData, // see flow/vnode 5 | res?: Object, 6 | // the following are added in 2.6 7 | hasDynamicKeys?: boolean, 8 | contentHashKey?: number 9 | ): { [key: string]: Function, $stable: boolean } { 10 | res = res || { $stable: !hasDynamicKeys } 11 | for (let i = 0; i < fns.length; i++) { 12 | const slot = fns[i] 13 | if (Array.isArray(slot)) { 14 | resolveScopedSlots(slot, res, hasDynamicKeys) 15 | } else if (slot) { 16 | // marker for reverse proxying v-slot without scope on this.$slots 17 | if (slot.proxy) { 18 | slot.fn.proxy = true 19 | } 20 | res[slot.key] = slot.fn 21 | } 22 | } 23 | if (contentHashKey) { 24 | (res: any).$key = contentHashKey 25 | } 26 | return res 27 | } 28 | -------------------------------------------------------------------------------- /code/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 | const methodsToPatch = [ 12 | 'push', 13 | 'pop', 14 | 'shift', 15 | 'unshift', 16 | 'splice', 17 | 'sort', 18 | 'reverse' 19 | ] 20 | 21 | /** 22 | * Intercept mutating methods and emit events 23 | */ 24 | methodsToPatch.forEach(function (method) { 25 | // cache original method 26 | const original = arrayProto[method] 27 | def(arrayMethods, method, function mutator (...args) { 28 | const result = original.apply(this, args) 29 | const ob = this.__ob__ 30 | let inserted 31 | switch (method) { 32 | case 'push': 33 | case 'unshift': 34 | inserted = args 35 | break 36 | case 'splice': 37 | inserted = args.slice(2) 38 | break 39 | } 40 | if (inserted) ob.observeArray(inserted) 41 | // notify change 42 | ob.dep.notify() 43 | return result 44 | }) 45 | }) 46 | -------------------------------------------------------------------------------- /code/core/observer/traverse.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { _Set as Set, isObject } from '../util/index' 4 | import type { SimpleSet } from '../util/index' 5 | import VNode from '../vdom/vnode' 6 | 7 | const seenObjects = new Set() 8 | 9 | /** 10 | * Recursively traverse an object to evoke all converted 11 | * getters, so that every nested property inside the object 12 | * is collected as a "deep" dependency. 13 | */ 14 | export function traverse (val: any) { 15 | _traverse(val, seenObjects) 16 | seenObjects.clear() 17 | } 18 | 19 | function _traverse (val: any, seen: SimpleSet) { 20 | let i, keys 21 | const isA = Array.isArray(val) 22 | if ((!isA && !isObject(val)) || Object.isFrozen(val) || val instanceof VNode) { 23 | return 24 | } 25 | if (val.__ob__) { 26 | const depId = val.__ob__.dep.id 27 | if (seen.has(depId)) { 28 | return 29 | } 30 | seen.add(depId) 31 | } 32 | if (isA) { 33 | i = val.length 34 | while (i--) _traverse(val[i], seen) 35 | } else { 36 | keys = Object.keys(val) 37 | i = keys.length 38 | while (i--) _traverse(val[keys[i]], seen) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /code/core/util/index.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | export * from 'shared/util' 4 | export * from './lang' 5 | export * from './env' 6 | export * from './options' 7 | export * from './debug' 8 | export * from './props' 9 | export * from './error' 10 | export * from './next-tick' 11 | export { defineReactive } from '../observer/index' 12 | -------------------------------------------------------------------------------- /code/core/util/lang.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | /** 4 | * unicode letters used for parsing html tags, component names and property paths. 5 | * using https://www.w3.org/TR/html53/semantics-scripting.html#potentialcustomelementname 6 | * skipping \u10000-\uEFFFF due to it freezing up PhantomJS 7 | */ 8 | export const unicodeRegExp = /a-zA-Z\u00B7\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u037D\u037F-\u1FFF\u200C-\u200D\u203F-\u2040\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD/ 9 | 10 | /** 11 | * Check if a string starts with $ or _ 12 | */ 13 | export function isReserved (str: string): boolean { 14 | const c = (str + '').charCodeAt(0) 15 | return c === 0x24 || c === 0x5F 16 | } 17 | 18 | /** 19 | * Define a property. 20 | */ 21 | export function def (obj: Object, key: string, val: any, enumerable?: boolean) { 22 | Object.defineProperty(obj, key, { 23 | value: val, 24 | enumerable: !!enumerable, 25 | writable: true, 26 | configurable: true 27 | }) 28 | } 29 | 30 | /** 31 | * Parse simple path. 32 | */ 33 | const bailRE = new RegExp(`[^${unicodeRegExp.source}.$_\\d]`) 34 | export function parsePath (path: string): any { 35 | if (bailRE.test(path)) { 36 | return 37 | } 38 | const segments = path.split('.') 39 | return function (obj) { 40 | for (let i = 0; i < segments.length; i++) { 41 | if (!obj) return 42 | obj = obj[segments[i]] 43 | } 44 | return obj 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /code/core/util/perf.js: -------------------------------------------------------------------------------- 1 | import { inBrowser } from './env' 2 | 3 | export let mark 4 | export let measure 5 | 6 | if (process.env.NODE_ENV !== 'production') { 7 | const perf = inBrowser && window.performance 8 | /* istanbul ignore if */ 9 | if ( 10 | perf && 11 | perf.mark && 12 | perf.measure && 13 | perf.clearMarks && 14 | perf.clearMeasures 15 | ) { 16 | mark = tag => perf.mark(tag) 17 | measure = (name, startTag, endTag) => { 18 | perf.measure(name, startTag, endTag) 19 | perf.clearMarks(startTag) 20 | perf.clearMarks(endTag) 21 | // perf.clearMeasures(name) 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /code/core/vdom/helpers/get-first-component-child.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { isDef } from 'shared/util' 4 | import { isAsyncPlaceholder } from './is-async-placeholder' 5 | 6 | export function getFirstComponentChild (children: ?Array): ?VNode { 7 | if (Array.isArray(children)) { 8 | for (let i = 0; i < children.length; i++) { 9 | const c = children[i] 10 | if (isDef(c) && (isDef(c.componentOptions) || isAsyncPlaceholder(c))) { 11 | return c 12 | } 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /code/core/vdom/helpers/index.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | export * from './merge-hook' 4 | export * from './extract-props' 5 | export * from './update-listeners' 6 | export * from './normalize-children' 7 | export * from './resolve-async-component' 8 | export * from './get-first-component-child' 9 | export * from './is-async-placeholder' 10 | -------------------------------------------------------------------------------- /code/core/vdom/helpers/is-async-placeholder.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | export function isAsyncPlaceholder (node: VNode): boolean { 4 | return node.isComment && node.asyncFactory 5 | } 6 | -------------------------------------------------------------------------------- /code/core/vdom/helpers/merge-hook.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import VNode from '../vnode' 4 | import { createFnInvoker } from './update-listeners' 5 | import { remove, isDef, isUndef, isTrue } from 'shared/util' 6 | 7 | export function mergeVNodeHook (def: Object, hookKey: string, hook: Function) { 8 | if (def instanceof VNode) { 9 | def = def.data.hook || (def.data.hook = {}) 10 | } 11 | let invoker 12 | const oldHook = def[hookKey] 13 | 14 | function wrappedHook () { 15 | hook.apply(this, arguments) 16 | // important: remove merged hook to ensure it's called only once 17 | // and prevent memory leak 18 | remove(invoker.fns, wrappedHook) 19 | } 20 | 21 | if (isUndef(oldHook)) { 22 | // no existing hook 23 | invoker = createFnInvoker([wrappedHook]) 24 | } else { 25 | /* istanbul ignore if */ 26 | if (isDef(oldHook.fns) && isTrue(oldHook.merged)) { 27 | // already a merged invoker 28 | invoker = oldHook 29 | invoker.fns.push(wrappedHook) 30 | } else { 31 | // existing plain hook 32 | invoker = createFnInvoker([oldHook, wrappedHook]) 33 | } 34 | } 35 | 36 | invoker.merged = true 37 | def[hookKey] = invoker 38 | } 39 | -------------------------------------------------------------------------------- /code/core/vdom/modules/index.js: -------------------------------------------------------------------------------- 1 | import directives from './directives' 2 | import ref from './ref' 3 | 4 | export default [ 5 | ref, 6 | directives 7 | ] 8 | -------------------------------------------------------------------------------- /code/core/vdom/modules/ref.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { remove, isDef } from 'shared/util' 4 | 5 | export default { 6 | create (_: any, vnode: VNodeWithData) { 7 | registerRef(vnode) 8 | }, 9 | update (oldVnode: VNodeWithData, vnode: VNodeWithData) { 10 | if (oldVnode.data.ref !== vnode.data.ref) { 11 | registerRef(oldVnode, true) 12 | registerRef(vnode) 13 | } 14 | }, 15 | destroy (vnode: VNodeWithData) { 16 | registerRef(vnode, true) 17 | } 18 | } 19 | 20 | export function registerRef (vnode: VNodeWithData, isRemoval: ?boolean) { 21 | const key = vnode.data.ref 22 | if (!isDef(key)) return 23 | 24 | const vm = vnode.context 25 | const ref = vnode.componentInstance || vnode.elm 26 | const refs = vm.$refs 27 | if (isRemoval) { 28 | if (Array.isArray(refs[key])) { 29 | remove(refs[key], ref) 30 | } else if (refs[key] === ref) { 31 | refs[key] = undefined 32 | } 33 | } else { 34 | if (vnode.data.refInFor) { 35 | if (!Array.isArray(refs[key])) { 36 | refs[key] = [ref] 37 | } else if (refs[key].indexOf(ref) < 0) { 38 | // $flow-disable-line 39 | refs[key].push(ref) 40 | } 41 | } else { 42 | refs[key] = ref 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /code/dist/bundle.js: -------------------------------------------------------------------------------- 1 | (()=>{"use strict";class t{static target;subs;constructor(){this.subs=[]}addSub(t){this.subs.push(t)}depend(){t.target&&t.target.addDep(this)}notify(){for(let t=0,e=this.subs.length;t{console.log("收到",s.text)})),s.text="hello, liang"})(); -------------------------------------------------------------------------------- /code/dist/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /code/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue2-learn", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "dev": "webpack-dev-server --mode development --open", 8 | "build": "webpack --mode production" 9 | }, 10 | "keywords": [ 11 | "vue2源码" 12 | ], 13 | "author": "windliang", 14 | "license": "ISC", 15 | "devDependencies": { 16 | "webpack": "^5.70.0", 17 | "webpack-cli": "^4.9.2", 18 | "webpack-dev-server": "^4.7.4" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /code/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const currentPath = "25.Vue2剥丝抽茧-模版编译之静态render"; 3 | module.exports = { 4 | entry: `./${currentPath}/main.js`, 5 | resolve: { 6 | extensions: [".js", ".json"], 7 | alias: { 8 | "@": path.join(__dirname, `./${currentPath}/src`), 9 | }, 10 | }, 11 | output: { 12 | path: path.resolve(__dirname, "./dist"), 13 | filename: "bundle.js", 14 | }, 15 | devServer: { 16 | static: path.resolve(__dirname, "./dist"), 17 | }, 18 | }; 19 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | Vue2 剥丝抽茧,源码详细分析,持续更新中... 2 | 3 | 在线阅读:[vue.windliang.wang/](https://vue.windliang.wang/) 4 | 5 | 每篇文章都有对应的源码,详见 [github](https://github.com/wind-liang/vue2) 的 `code` 目录。项目 `clone` 后,进入到 `code` 目录,执行 `npm i` 和 `npm run dev` 即可跑示例程序。更改 `code/webpack.config.js` 文件的 `entry` 字段可以运行不同程序。 6 | 7 | 对应源码怎么跑也可以参考视频 [bilibili](https://www.bilibili.com/video/BV17a411M788?share_source=copy_web) 8 | 9 | 如果觉得不错的话,感谢给一个 [Star](https://github.com/wind-liang/vue2) 10 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue2", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "license": "MIT", 6 | "devDependencies": { 7 | "@qcyblm/vuepress-theme-vpx": "^1.0.7", 8 | "vuepress": "^1.9.7", 9 | "vuepress-plugin-baidu-tongji-analytics": "^1.0.2" 10 | }, 11 | "scripts": { 12 | "dev": "vuepress dev docs", 13 | "build": "vuepress build docs" 14 | }, 15 | "dependencies": { 16 | "@renovamen/vuepress-plugin-katex": "^0.2.0" 17 | } 18 | } 19 | --------------------------------------------------------------------------------