├── README.md ├── docs ├── VNode节点.MarkDown ├── VirtualDOM与diff(Vue实现).MarkDown ├── Vue.js异步更新DOM策略及nextTick.MarkDown ├── Vuex源码解析.MarkDown ├── Vue事件机制.MarkDown ├── Vue组件间通信.MarkDown ├── 从template到DOM(Vue.js源码角度看内部运行机制).MarkDown ├── 从源码角度再看数据绑定.MarkDown ├── 依赖收集.MarkDown ├── 响应式原理.MarkDown ├── 聊聊Vue的template编译.MarkDown ├── 聊聊keep-alive组件的使用及其实现原理.MarkDown └── 说说element组件库broadcast与dispatch.MarkDown ├── images ├── AST1.png ├── AST2.png ├── VNode.png ├── VNode.sketch ├── VNode2.png ├── VNode2.sketch ├── diff1.png ├── diff1.sketch ├── diff10.png ├── diff10.sketch ├── diff2.png ├── diff2.sketch ├── diff3.png ├── diff3.sketch ├── diff4.png ├── diff4.sketch ├── diff5.png ├── diff5.sketch ├── diff6.png ├── diff6.sketch ├── diff7.png ├── diff7.sketch ├── diff8.png ├── diff8.sketch ├── diff9.png ├── diff9.sketch └── youxuan.png ├── vue-router-src ├── components │ ├── link.js │ └── view.js ├── create-matcher.js ├── create-route-map.js ├── history │ ├── abstract.js │ ├── base.js │ ├── hash.js │ └── html5.js ├── index.js ├── install.js └── util │ ├── async.js │ ├── dom.js │ ├── location.js │ ├── params.js │ ├── path.js │ ├── push-state.js │ ├── query.js │ ├── resolve-components.js │ ├── route.js │ ├── scroll.js │ └── warn.js ├── vue-src ├── compiler │ ├── codegen │ │ ├── events.js │ │ └── index.js │ ├── directives │ │ ├── bind.js │ │ ├── index.js │ │ └── model.js │ ├── error-detector.js │ ├── helpers.js │ ├── index.js │ ├── optimizer.js │ └── parser │ │ ├── entity-decoder.js │ │ ├── filter-parser.js │ │ ├── html-parser.js │ │ ├── index.js │ │ └── text-parser.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-object-props.js │ │ │ ├── check-keycodes.js │ │ │ ├── render-list.js │ │ │ ├── render-slot.js │ │ │ ├── render-static.js │ │ │ ├── resolve-filter.js │ │ │ └── resolve-slots.js │ │ ├── render.js │ │ └── state.js │ ├── observer │ │ ├── array.js │ │ ├── dep.js │ │ ├── index.js │ │ ├── scheduler.js │ │ └── watcher.js │ ├── util │ │ ├── debug.js │ │ ├── env.js │ │ ├── error.js │ │ ├── index.js │ │ ├── lang.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 │ │ ├── merge-hook.js │ │ ├── normalize-children.js │ │ ├── resolve-async-component.js │ │ └── update-listeners.js │ │ ├── modules │ │ ├── directives.js │ │ ├── index.js │ │ └── ref.js │ │ ├── patch.js │ │ └── vnode.js ├── platforms │ ├── web │ │ ├── compiler.js │ │ ├── compiler │ │ │ ├── directives │ │ │ │ ├── html.js │ │ │ │ ├── index.js │ │ │ │ ├── model.js │ │ │ │ └── text.js │ │ │ ├── index.js │ │ │ ├── modules │ │ │ │ ├── class.js │ │ │ │ ├── index.js │ │ │ │ └── style.js │ │ │ └── util.js │ │ ├── runtime-with-compiler.js │ │ ├── runtime.js │ │ ├── runtime │ │ │ ├── class-util.js │ │ │ ├── components │ │ │ │ ├── index.js │ │ │ │ ├── transition-group.js │ │ │ │ └── transition.js │ │ │ ├── directives │ │ │ │ ├── index.js │ │ │ │ ├── model.js │ │ │ │ └── show.js │ │ │ ├── index.js │ │ │ ├── modules │ │ │ │ ├── attrs.js │ │ │ │ ├── class.js │ │ │ │ ├── dom-props.js │ │ │ │ ├── events.js │ │ │ │ ├── index.js │ │ │ │ ├── style.js │ │ │ │ └── transition.js │ │ │ ├── node-ops.js │ │ │ ├── patch.js │ │ │ └── transition-util.js │ │ ├── server-renderer.js │ │ ├── server │ │ │ ├── directives │ │ │ │ ├── index.js │ │ │ │ └── show.js │ │ │ ├── modules │ │ │ │ ├── attrs.js │ │ │ │ ├── class.js │ │ │ │ ├── dom-props.js │ │ │ │ ├── index.js │ │ │ │ └── style.js │ │ │ └── util.js │ │ └── util │ │ │ ├── attrs.js │ │ │ ├── class.js │ │ │ ├── compat.js │ │ │ ├── element.js │ │ │ ├── index.js │ │ │ └── style.js │ └── weex │ │ ├── compiler.js │ │ ├── compiler │ │ ├── directives │ │ │ ├── index.js │ │ │ └── model.js │ │ ├── index.js │ │ └── modules │ │ │ ├── append.js │ │ │ ├── class.js │ │ │ ├── index.js │ │ │ ├── props.js │ │ │ └── style.js │ │ ├── framework.js │ │ ├── runtime-factory.js │ │ ├── runtime │ │ ├── components │ │ │ ├── index.js │ │ │ ├── transition-group.js │ │ │ └── transition.js │ │ ├── directives │ │ │ └── index.js │ │ ├── index.js │ │ ├── modules │ │ │ ├── attrs.js │ │ │ ├── class.js │ │ │ ├── events.js │ │ │ ├── index.js │ │ │ ├── style.js │ │ │ └── transition.js │ │ ├── node-ops.js │ │ ├── patch.js │ │ └── text-node.js │ │ └── util │ │ └── index.js ├── server │ ├── bundle-renderer │ │ ├── create-bundle-renderer.js │ │ ├── create-bundle-runner.js │ │ └── source-map-support.js │ ├── create-renderer.js │ ├── render-context.js │ ├── render-stream.js │ ├── render.js │ ├── template-renderer │ │ ├── create-async-file-mapper.js │ │ ├── index.js │ │ ├── parse-template.js │ │ └── template-stream.js │ ├── util.js │ ├── webpack-plugin │ │ ├── client.js │ │ ├── server.js │ │ └── util.js │ └── write.js ├── sfc │ └── parser.js └── shared │ ├── constants.js │ └── util.js └── vuex-src ├── helpers.js ├── index.esm.js ├── index.js ├── mixin.js ├── module ├── module-collection.js └── module.js ├── plugins ├── devtool.js └── logger.js ├── store.js └── util.js /README.md: -------------------------------------------------------------------------------- 1 | # learnVue 2 | 3 | ## 介绍 4 | 5 | Vue.js源码分析,记录了个人学习Vue.js源码的过程中的一些心得以及收获。以及对于Vue框架,周边库的一些个人见解。 6 | 7 | 在学习的过程中我为Vue.js(2.3.0)、Vuex(2.4.0)、Vue-router(3.0.1)加上了注释,分别在文件夹[vue-src](./vue-src)、[vuex-src](./vuex-src)以及[vue-router-src](./vue-router-src)中,希望可以帮助有需要的同学更好地学习理解Vue.js及周边库的源码。 8 | 9 | 感谢[尤大](https://github.com/yyx990803)提高生产力。 10 | 11 | 本项目希望对Vue.js做更进一步的探索与学习,Vue.js基础内容请参考Vue.js官网,[https://cn.vuejs.org/v2/guide/](https://cn.vuejs.org/v2/guide/)。 12 | 可能会有理解存在偏差的地方,欢迎提issue指出,共同学习,共同进步。 13 | 14 | --- 15 | 16 | ## 目录 17 | 18 | ### 源码相关 19 | 20 | [Vue.js响应式原理](./docs/响应式原理.MarkDown) 21 | 22 | [Vue.js依赖收集](./docs/依赖收集.MarkDown) 23 | 24 | [从Vue.js源码角度再看数据绑定](./docs/从源码角度再看数据绑定.MarkDown) 25 | 26 | [Vue.js事件机制](./docs/Vue事件机制.MarkDown) 27 | 28 | [VNode节点(Vue.js实现)](./docs/VNode节点.MarkDown) 29 | 30 | [Virtual DOM与diff(Vue.js实现)](./docs/VirtualDOM与diff(Vue实现).MarkDown) 31 | 32 | [聊聊Vue.js的template编译](./docs/聊聊Vue的template编译.MarkDown) 33 | 34 | [Vue.js异步更新DOM策略及nextTick](./docs/Vue.js异步更新DOM策略及nextTick.MarkDown) 35 | 36 | [从template到DOM(Vue.js源码角度看内部运行机制)](./docs/从template到DOM(Vue.js源码角度看内部运行机制).MarkDown) 37 | 38 | [Vuex源码解析](./docs/Vuex源码解析.MarkDown) 39 | 40 | [聊聊keep-alive组件的使用及其实现原理](./docs/聊聊keep-alive组件的使用及其实现原理.MarkDown) 41 | 42 | ### 随笔杂谈 43 | 44 | [Vue组件间通信](./docs/Vue组件间通信.MarkDown) 45 | 46 | [说说element组件库broadcast与dispatch](./docs/说说element组件库broadcast与dispatch.MarkDown) 47 | 48 | --- 49 | 50 | ## 对于新手同学 51 | 52 | 由于以上内容都是针对 Vue.js 源码进行讲解了,可能有一些不太熟悉源码的同学读起来感觉晦涩难懂。 53 | 54 | 笔者撰写的[《剖析 Vue.js 内部运行机制》](https://juejin.im/book/5a36661851882538e2259c0f)或许可以帮到你。 55 | 56 | ## 获取更多前端领域优质技术博文 57 | 58 | 扫码或微信搜索“前端技术优选”,长期分享前端及Node.js领域优质技术博文,欢迎关注。 59 | 60 |
61 | 62 | ## 与更多技术同行交流 63 | 64 |
65 | 66 | ## 关于作者 67 | 68 | 作者: 染陌 69 | 70 | Email:answershuto@gmail.com 71 | 72 | Github: [https://github.com/answershuto](https://github.com/answershuto) 73 | 74 | 知乎:[https://www.zhihu.com/people/cao-yang-49/activities](https://www.zhihu.com/people/cao-yang-49/activities) 75 | 76 | 掘金:[https://juejin.im/user/58f87ae844d9040069ca7507](https://juejin.im/user/58f87ae844d9040069ca7507) 77 | 78 | 对内容有任何疑问,欢迎联系我。 -------------------------------------------------------------------------------- /docs/依赖收集.MarkDown: -------------------------------------------------------------------------------- 1 | ## 为什么要依赖收集 2 | 3 | 先看下面这段代码 4 | 5 | ```javascript 6 | new Vue({ 7 | template: 8 | `
9 | text1: {{text1}} 10 | text2: {{text2}} 11 |
`, 12 | data: { 13 | text1: 'text1', 14 | text2: 'text2', 15 | text3: 'text3' 16 | } 17 | }); 18 | ``` 19 | 20 | 按照之前[《响应式原理》](https://github.com/answershuto/learnVue/blob/master/docs/%E5%93%8D%E5%BA%94%E5%BC%8F%E5%8E%9F%E7%90%86.MarkDown)中的方法进行绑定则会出现一个问题——text3在实际模板中并没有被用到,然而当text3的数据被修改(this.text3 = 'test')的时候,同样会触发text3的setter导致重新执行渲染,这显然不正确。 21 | 22 | ## 先说说Dep 23 | 24 | 当对data上的对象进行修改值的时候会触发它的setter,那么取值的时候自然就会触发getter事件,所以我们只要在最开始进行一次render,那么所有被渲染所依赖的data中的数据就会被getter收集到Dep的subs中去。在对data中的数据进行修改的时候setter只会触发Dep的subs的函数。 25 | 26 | 定义一个依赖收集类Dep。 27 | 28 | ```javascript 29 | class Dep { 30 | constructor () { 31 | this.subs = []; 32 | } 33 | 34 | addSub (sub: Watcher) { 35 | this.subs.push(sub) 36 | } 37 | 38 | removeSub (sub: Watcher) { 39 | remove(this.subs, sub) 40 | } 41 | /*Github:https://github.com/answershuto*/ 42 | notify () { 43 | // stabilize the subscriber list first 44 | const subs = this.subs.slice() 45 | for (let i = 0, l = subs.length; i < l; i++) { 46 | subs[i].update() 47 | } 48 | } 49 | } 50 | function remove (arr, item) { 51 | if (arr.length) { 52 | const index = arr.indexOf(item) 53 | if (index > -1) { 54 | return arr.splice(index, 1) 55 | } 56 | } 57 | ``` 58 | 59 | ## Watcher 60 | 61 | 订阅者,当依赖收集的时候会addSub到sub中,在修改data中数据的时候会触发dep对象的notify,通知所有Watcher对象去修改对应视图。 62 | 63 | ```javascript 64 | class Watcher { 65 | constructor (vm, expOrFn, cb, options) { 66 | this.cb = cb; 67 | this.vm = vm; 68 | 69 | /*在这里将观察者本身赋值给全局的target,只有被target标记过的才会进行依赖收集*/ 70 | Dep.target = this; 71 | /*Github:https://github.com/answershuto*/ 72 | /*触发渲染操作进行依赖收集*/ 73 | this.cb.call(this.vm); 74 | } 75 | 76 | update () { 77 | this.cb.call(this.vm); 78 | } 79 | } 80 | ``` 81 | 82 | ## 开始依赖收集 83 | 84 | ```javascript 85 | class Vue { 86 | constructor(options) { 87 | this._data = options.data; 88 | observer(this._data, options.render); 89 | let watcher = new Watcher(this, ); 90 | } 91 | } 92 | 93 | function defineReactive (obj, key, val, cb) { 94 | /*在闭包内存储一个Dep对象*/ 95 | const dep = new Dep(); 96 | 97 | Object.defineProperty(obj, key, { 98 | enumerable: true, 99 | configurable: true, 100 | get: ()=>{ 101 | if (Dep.target) { 102 | /*Watcher对象存在全局的Dep.target中*/ 103 | dep.addSub(Dep.target); 104 | } 105 | }, 106 | set:newVal=> { 107 | /*只有之前addSub中的函数才会触发*/ 108 | dep.notify(); 109 | } 110 | }) 111 | } 112 | 113 | Dep.target = null; 114 | ``` 115 | 116 | 将观察者Watcher实例赋值给全局的Dep.target,然后触发render操作只有被Dep.target标记过的才会进行依赖收集。有Dep.target的对象会将Watcher的实例push到subs中,在对象被修改触发setter操作的时候dep会调用subs中的Watcher实例的update方法进行渲染。 117 | -------------------------------------------------------------------------------- /docs/响应式原理.MarkDown: -------------------------------------------------------------------------------- 1 | ## 关于Vue.js 2 | 3 | Vue.js是一款MVVM框架,上手快速简单易用,通过响应式在修改数据的时候更新视图。Vue.js的响应式原理依赖于[Object.defineProperty](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty),尤大大在[Vue.js文档](https://cn.vuejs.org/v2/guide/reactivity.html#如何追踪变化)中就已经提到过,这也是Vue.js不支持IE8 以及更低版本浏览器的原因。Vue通过设定对象属性的 setter/getter 方法来监听数据的变化,通过getter进行依赖收集,而每个setter方法就是一个观察者,在数据变更的时候通知订阅者更新视图。 4 | 5 | 6 | ## 将数据data变成可观察(observable)的 7 | 8 | 那么Vue是如何将所有data下面的所有属性变成可观察的(observable)呢? 9 | 10 | ```javascript 11 | function observe(value, cb) { 12 | Object.keys(value).forEach((key) => defineReactive(value, key, value[key] , cb)) 13 | } 14 | 15 | function defineReactive (obj, key, val, cb) { 16 | Object.defineProperty(obj, key, { 17 | enumerable: true, 18 | configurable: true, 19 | get: ()=>{ 20 | /*....依赖收集等....*/ 21 | /*Github:https://github.com/answershuto*/ 22 | return val 23 | }, 24 | set:newVal=> { 25 | val = newVal; 26 | cb();/*订阅者收到消息的回调*/ 27 | } 28 | }) 29 | } 30 | 31 | class Vue { 32 | constructor(options) { 33 | this._data = options.data; 34 | observe(this._data, options.render) 35 | } 36 | } 37 | 38 | let app = new Vue({ 39 | el: '#app', 40 | data: { 41 | text: 'text', 42 | text2: 'text2' 43 | }, 44 | render(){ 45 | console.log("render"); 46 | } 47 | }) 48 | ``` 49 | 50 | 为了便于理解,首先考虑一种最简单的情况,不考虑数组等情况,代码如上所示。在[initData](https://github.com/vuejs/vue/blob/dev/src/core/instance/state.js#L107)中会调用[observe](https://github.com/vuejs/vue/blob/dev/src/core/observer/index.js#L106)这个函数将Vue的数据设置成observable的。当_data数据发生改变的时候就会触发set,对订阅者进行回调(在这里是render)。 51 | 52 | 那么问题来了,需要对app._data.text操作才会触发set。为了偷懒,我们需要一种方便的方法通过app.text直接设置就能触发set对视图进行重绘。那么就需要用到代理。 53 | 54 | ## 代理 55 | 56 | 我们可以在Vue的构造函数constructor中为data执行一个代理[proxy](https://github.com/vuejs/vue/blob/dev/src/core/instance/state.js#L33)。这样我们就把data上面的属性代理到了vm实例上。 57 | 58 | ```javascript 59 | _proxy.call(this, options.data);/*构造函数中*/ 60 | 61 | /*代理*/ 62 | function _proxy (data) { 63 | const that = this; 64 | Object.keys(data).forEach(key => { 65 | Object.defineProperty(that, key, { 66 | configurable: true, 67 | enumerable: true, 68 | get: function proxyGetter () { 69 | return that._data[key]; 70 | }, 71 | set: function proxySetter (val) { 72 | that._data[key] = val; 73 | } 74 | }) 75 | }); 76 | } 77 | ``` 78 | 79 | 我们就可以用app.text代替app._data.text了。 80 | -------------------------------------------------------------------------------- /docs/说说element组件库broadcast与dispatch.MarkDown: -------------------------------------------------------------------------------- 1 | 周所周知,Vue在2.0版本中去除了$broadcast方法以及$dispatch方法,最近在学习饿了么的[Element](https://github.com/ElemeFE/element)时重新实现了这两种方法,并以minix的方式引入。 2 | 3 | 看一下[源代码](https://github.com/ElemeFE/element/blob/dev/src/mixins/emitter.js) 4 | 5 | ```javascript 6 | function broadcast(componentName, eventName, params) { 7 | /*遍历当前节点下的所有子组件*/ 8 | this.$children.forEach(child => { 9 | /*获取子组件名称*/ 10 | var name = child.$options.componentName; 11 | 12 | if (name === componentName) { 13 | /*如果是我们需要广播到的子组件的时候调用$emit触发所需事件,在子组件中用$on监听*/ 14 | child.$emit.apply(child, [eventName].concat(params)); 15 | } else { 16 | /*非所需子组件则递归遍历深层次子组件*/ 17 | broadcast.apply(child, [componentName, eventName].concat([params])); 18 | } 19 | }); 20 | } 21 | export default { 22 | methods: { 23 | /*对多级父组件进行事件派发*/ 24 | dispatch(componentName, eventName, params) { 25 | /*获取父组件,如果以及是根组件,则是$root*/ 26 | var parent = this.$parent || this.$root; 27 | /*获取父节点的组件名*/ 28 | var name = parent.$options.componentName; 29 | 30 | while (parent && (!name || name !== componentName)) { 31 | /*当父组件不是所需组件时继续向上寻找*/ 32 | parent = parent.$parent; 33 | 34 | if (parent) { 35 | name = parent.$options.componentName; 36 | } 37 | } 38 | /*找到所需组件后调用$emit触发当前事件*/ 39 | if (parent) { 40 | parent.$emit.apply(parent, [eventName].concat(params)); 41 | } 42 | }, 43 | /* 44 | 向所有子组件进行事件广播。 45 | 这里包了一层,为了修改broadcast的this对象为当前Vue实例 46 | */ 47 | broadcast(componentName, eventName, params) { 48 | broadcast.call(this, componentName, eventName, params); 49 | } 50 | } 51 | }; 52 | 53 | ``` 54 | 55 | 其实这里的broadcast与dispatch实现了一个定向的多层级父子组件间的事件广播及事件派发功能。完成多层级分发对应事件的组件间通信功能。 56 | 57 | broadcast通过递归遍历子组件找到所需组件广播事件,而dispatch则逐级向上查找对应父组件派发事件。 58 | 59 | broadcast需要三个参数,componentName(组件名),eventName(事件名称)以及params(数据)。根据componentName深度遍历子组件找到对应组件emit事件eventName。 60 | 61 | dispatch同样道理,需要三个参数,componentName(组件名),eventName(事件名称)以及params(数据)。根据componentName向上级一直寻找对应父组件,找到以后emit事件eventName。 62 | -------------------------------------------------------------------------------- /images/AST1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YSGStudyHards/learnVue/e1bc0332a179bb7cf0a761cdf14af5ff7b87428f/images/AST1.png -------------------------------------------------------------------------------- /images/AST2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YSGStudyHards/learnVue/e1bc0332a179bb7cf0a761cdf14af5ff7b87428f/images/AST2.png -------------------------------------------------------------------------------- /images/VNode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YSGStudyHards/learnVue/e1bc0332a179bb7cf0a761cdf14af5ff7b87428f/images/VNode.png -------------------------------------------------------------------------------- /images/VNode.sketch: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YSGStudyHards/learnVue/e1bc0332a179bb7cf0a761cdf14af5ff7b87428f/images/VNode.sketch -------------------------------------------------------------------------------- /images/VNode2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YSGStudyHards/learnVue/e1bc0332a179bb7cf0a761cdf14af5ff7b87428f/images/VNode2.png -------------------------------------------------------------------------------- /images/VNode2.sketch: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YSGStudyHards/learnVue/e1bc0332a179bb7cf0a761cdf14af5ff7b87428f/images/VNode2.sketch -------------------------------------------------------------------------------- /images/diff1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YSGStudyHards/learnVue/e1bc0332a179bb7cf0a761cdf14af5ff7b87428f/images/diff1.png -------------------------------------------------------------------------------- /images/diff1.sketch: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YSGStudyHards/learnVue/e1bc0332a179bb7cf0a761cdf14af5ff7b87428f/images/diff1.sketch -------------------------------------------------------------------------------- /images/diff10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YSGStudyHards/learnVue/e1bc0332a179bb7cf0a761cdf14af5ff7b87428f/images/diff10.png -------------------------------------------------------------------------------- /images/diff10.sketch: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YSGStudyHards/learnVue/e1bc0332a179bb7cf0a761cdf14af5ff7b87428f/images/diff10.sketch -------------------------------------------------------------------------------- /images/diff2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YSGStudyHards/learnVue/e1bc0332a179bb7cf0a761cdf14af5ff7b87428f/images/diff2.png -------------------------------------------------------------------------------- /images/diff2.sketch: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YSGStudyHards/learnVue/e1bc0332a179bb7cf0a761cdf14af5ff7b87428f/images/diff2.sketch -------------------------------------------------------------------------------- /images/diff3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YSGStudyHards/learnVue/e1bc0332a179bb7cf0a761cdf14af5ff7b87428f/images/diff3.png -------------------------------------------------------------------------------- /images/diff3.sketch: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YSGStudyHards/learnVue/e1bc0332a179bb7cf0a761cdf14af5ff7b87428f/images/diff3.sketch -------------------------------------------------------------------------------- /images/diff4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YSGStudyHards/learnVue/e1bc0332a179bb7cf0a761cdf14af5ff7b87428f/images/diff4.png -------------------------------------------------------------------------------- /images/diff4.sketch: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YSGStudyHards/learnVue/e1bc0332a179bb7cf0a761cdf14af5ff7b87428f/images/diff4.sketch -------------------------------------------------------------------------------- /images/diff5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YSGStudyHards/learnVue/e1bc0332a179bb7cf0a761cdf14af5ff7b87428f/images/diff5.png -------------------------------------------------------------------------------- /images/diff5.sketch: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YSGStudyHards/learnVue/e1bc0332a179bb7cf0a761cdf14af5ff7b87428f/images/diff5.sketch -------------------------------------------------------------------------------- /images/diff6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YSGStudyHards/learnVue/e1bc0332a179bb7cf0a761cdf14af5ff7b87428f/images/diff6.png -------------------------------------------------------------------------------- /images/diff6.sketch: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YSGStudyHards/learnVue/e1bc0332a179bb7cf0a761cdf14af5ff7b87428f/images/diff6.sketch -------------------------------------------------------------------------------- /images/diff7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YSGStudyHards/learnVue/e1bc0332a179bb7cf0a761cdf14af5ff7b87428f/images/diff7.png -------------------------------------------------------------------------------- /images/diff7.sketch: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YSGStudyHards/learnVue/e1bc0332a179bb7cf0a761cdf14af5ff7b87428f/images/diff7.sketch -------------------------------------------------------------------------------- /images/diff8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YSGStudyHards/learnVue/e1bc0332a179bb7cf0a761cdf14af5ff7b87428f/images/diff8.png -------------------------------------------------------------------------------- /images/diff8.sketch: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YSGStudyHards/learnVue/e1bc0332a179bb7cf0a761cdf14af5ff7b87428f/images/diff8.sketch -------------------------------------------------------------------------------- /images/diff9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YSGStudyHards/learnVue/e1bc0332a179bb7cf0a761cdf14af5ff7b87428f/images/diff9.png -------------------------------------------------------------------------------- /images/diff9.sketch: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YSGStudyHards/learnVue/e1bc0332a179bb7cf0a761cdf14af5ff7b87428f/images/diff9.sketch -------------------------------------------------------------------------------- /images/youxuan.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YSGStudyHards/learnVue/e1bc0332a179bb7cf0a761cdf14af5ff7b87428f/images/youxuan.png -------------------------------------------------------------------------------- /vue-router-src/history/abstract.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import type Router from '../index' 4 | import { History } from './base' 5 | 6 | export class AbstractHistory extends History { 7 | index: number; 8 | stack: Array; 9 | 10 | constructor (router: Router, base: ?string) { 11 | super(router, base) 12 | this.stack = [] 13 | this.index = -1 14 | } 15 | 16 | push (location: RawLocation, onComplete?: Function, onAbort?: Function) { 17 | this.transitionTo(location, route => { 18 | this.stack = this.stack.slice(0, this.index + 1).concat(route) 19 | this.index++ 20 | onComplete && onComplete(route) 21 | }, onAbort) 22 | } 23 | 24 | replace (location: RawLocation, onComplete?: Function, onAbort?: Function) { 25 | this.transitionTo(location, route => { 26 | this.stack = this.stack.slice(0, this.index).concat(route) 27 | onComplete && onComplete(route) 28 | }, onAbort) 29 | } 30 | 31 | go (n: number) { 32 | const targetIndex = this.index + n 33 | if (targetIndex < 0 || targetIndex >= this.stack.length) { 34 | return 35 | } 36 | const route = this.stack[targetIndex] 37 | this.confirmTransition(route, () => { 38 | this.index = targetIndex 39 | this.updateRoute(route) 40 | }) 41 | } 42 | 43 | getCurrentLocation () { 44 | const current = this.stack[this.stack.length - 1] 45 | return current ? current.fullPath : '/' 46 | } 47 | 48 | ensureURL () { 49 | // noop 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /vue-router-src/history/html5.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import type Router from '../index' 4 | import { History } from './base' 5 | import { cleanPath } from '../util/path' 6 | import { START } from '../util/route' 7 | import { setupScroll, handleScroll } from '../util/scroll' 8 | import { pushState, replaceState, supportsPushState } from '../util/push-state' 9 | 10 | export class HTML5History extends History { 11 | constructor (router: Router, base: ?string) { 12 | super(router, base) 13 | 14 | const expectScroll = router.options.scrollBehavior 15 | const supportsScroll = supportsPushState && expectScroll 16 | 17 | if (supportsScroll) { 18 | setupScroll() 19 | } 20 | 21 | const initLocation = getLocation(this.base) 22 | window.addEventListener('popstate', e => { 23 | const current = this.current 24 | 25 | // Avoiding first `popstate` event dispatched in some browsers but first 26 | // history route not updated since async guard at the same time. 27 | const location = getLocation(this.base) 28 | if (this.current === START && location === initLocation) { 29 | return 30 | } 31 | 32 | this.transitionTo(location, route => { 33 | if (supportsScroll) { 34 | handleScroll(router, route, current, true) 35 | } 36 | }) 37 | }) 38 | } 39 | 40 | go (n: number) { 41 | window.history.go(n) 42 | } 43 | 44 | push (location: RawLocation, onComplete?: Function, onAbort?: Function) { 45 | const { current: fromRoute } = this 46 | this.transitionTo(location, route => { 47 | pushState(cleanPath(this.base + route.fullPath)) 48 | handleScroll(this.router, route, fromRoute, false) 49 | onComplete && onComplete(route) 50 | }, onAbort) 51 | } 52 | 53 | replace (location: RawLocation, onComplete?: Function, onAbort?: Function) { 54 | const { current: fromRoute } = this 55 | this.transitionTo(location, route => { 56 | replaceState(cleanPath(this.base + route.fullPath)) 57 | handleScroll(this.router, route, fromRoute, false) 58 | onComplete && onComplete(route) 59 | }, onAbort) 60 | } 61 | 62 | ensureURL (push?: boolean) { 63 | if (getLocation(this.base) !== this.current.fullPath) { 64 | const current = cleanPath(this.base + this.current.fullPath) 65 | push ? pushState(current) : replaceState(current) 66 | } 67 | } 68 | 69 | getCurrentLocation (): string { 70 | return getLocation(this.base) 71 | } 72 | } 73 | 74 | export function getLocation (base: string): string { 75 | let path = window.location.pathname 76 | if (base && path.indexOf(base) === 0) { 77 | path = path.slice(base.length) 78 | } 79 | return (path || '/') + window.location.search + window.location.hash 80 | } 81 | -------------------------------------------------------------------------------- /vue-router-src/install.js: -------------------------------------------------------------------------------- 1 | import View from './components/view' 2 | import Link from './components/link' 3 | 4 | export let _Vue 5 | 6 | /* Vue.use安装插件时候需要暴露的install方法 */ 7 | export function install (Vue) { 8 | 9 | /* 判断是否已安装过 */ 10 | if (install.installed && _Vue === Vue) return 11 | install.installed = true 12 | 13 | /* 保存Vue实例 */ 14 | _Vue = Vue 15 | 16 | /* 判断是否已定义 */ 17 | const isDef = v => v !== undefined 18 | 19 | /* 通过registerRouteInstance方法注册router实例 */ 20 | const registerInstance = (vm, callVal) => { 21 | let i = vm.$options._parentVnode 22 | if (isDef(i) && isDef(i = i.data) && isDef(i = i.registerRouteInstance)) { 23 | i(vm, callVal) 24 | } 25 | } 26 | 27 | /* 混淆进Vue实例,在boforeCreate与destroyed钩子上混淆 */ 28 | Vue.mixin({ 29 | /* boforeCreate钩子 */ 30 | beforeCreate () { 31 | if (isDef(this.$options.router)) { 32 | /* 在option上面存在router则代表是根组件 */ 33 | /* 保存跟组件vm */ 34 | this._routerRoot = this 35 | /* 保存router */ 36 | this._router = this.$options.router 37 | /* VueRouter对象的init方法 */ 38 | this._router.init(this) 39 | /* Vue内部方法,为对象defineProperty上在变化时通知的属性 */ 40 | Vue.util.defineReactive(this, '_route', this._router.history.current) 41 | } else { 42 | /* 非根组件则直接从父组件中获取 */ 43 | this._routerRoot = (this.$parent && this.$parent._routerRoot) || this 44 | } 45 | /* 通过registerRouteInstance方法注册router实例 */ 46 | registerInstance(this, this) 47 | }, 48 | destroyed () { 49 | registerInstance(this) 50 | } 51 | }) 52 | 53 | /* 在Vue的prototype上面绑定$router,这样可以在任意Vue对象中使用this.$router访问,同时经过Object.defineProperty,访问this.$router即访问this._routerRoot._router */ 54 | Object.defineProperty(Vue.prototype, '$router', { 55 | get () { return this._routerRoot._router } 56 | }) 57 | 58 | /* 以上同理,访问this.$route即访问this._routerRoot._route */ 59 | Object.defineProperty(Vue.prototype, '$route', { 60 | get () { return this._routerRoot._route } 61 | }) 62 | 63 | /* 注册touter-view以及router-link组件 */ 64 | Vue.component('RouterView', View) 65 | Vue.component('RouterLink', Link) 66 | 67 | /* 该对象保存了两个option合并的规则 */ 68 | const strats = Vue.config.optionMergeStrategies 69 | // use the same hook merging strategy for route hooks 70 | strats.beforeRouteEnter = strats.beforeRouteLeave = strats.beforeRouteUpdate = strats.created 71 | } 72 | -------------------------------------------------------------------------------- /vue-router-src/util/async.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | export function runQueue (queue: Array, fn: Function, cb: Function) { 4 | const step = index => { 5 | if (index >= queue.length) { 6 | cb() 7 | } else { 8 | if (queue[index]) { 9 | fn(queue[index], () => { 10 | step(index + 1) 11 | }) 12 | } else { 13 | step(index + 1) 14 | } 15 | } 16 | } 17 | step(0) 18 | } 19 | -------------------------------------------------------------------------------- /vue-router-src/util/dom.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | export const inBrowser = typeof window !== 'undefined' 4 | -------------------------------------------------------------------------------- /vue-router-src/util/location.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import type VueRouter from '../index' 4 | import { parsePath, resolvePath } from './path' 5 | import { resolveQuery } from './query' 6 | import { fillParams } from './params' 7 | import { warn } from './warn' 8 | 9 | export function normalizeLocation ( 10 | raw: RawLocation, 11 | current: ?Route, 12 | append: ?boolean, 13 | router: ?VueRouter 14 | ): Location { 15 | let next: Location = typeof raw === 'string' ? { path: raw } : raw 16 | // named target 17 | if (next.name || next._normalized) { 18 | return next 19 | } 20 | 21 | // relative params 22 | if (!next.path && next.params && current) { 23 | next = assign({}, next) 24 | next._normalized = true 25 | const params: any = assign(assign({}, current.params), next.params) 26 | if (current.name) { 27 | next.name = current.name 28 | next.params = params 29 | } else if (current.matched.length) { 30 | const rawPath = current.matched[current.matched.length - 1].path 31 | next.path = fillParams(rawPath, params, `path ${current.path}`) 32 | } else if (process.env.NODE_ENV !== 'production') { 33 | warn(false, `relative params navigation requires a current route.`) 34 | } 35 | return next 36 | } 37 | 38 | const parsedPath = parsePath(next.path || '') 39 | const basePath = (current && current.path) || '/' 40 | const path = parsedPath.path 41 | ? resolvePath(parsedPath.path, basePath, append || next.append) 42 | : basePath 43 | 44 | const query = resolveQuery( 45 | parsedPath.query, 46 | next.query, 47 | router && router.options.parseQuery 48 | ) 49 | 50 | let hash = next.hash || parsedPath.hash 51 | if (hash && hash.charAt(0) !== '#') { 52 | hash = `#${hash}` 53 | } 54 | 55 | return { 56 | _normalized: true, 57 | path, 58 | query, 59 | hash 60 | } 61 | } 62 | 63 | function assign (a, b) { 64 | for (const key in b) { 65 | a[key] = b[key] 66 | } 67 | return a 68 | } 69 | -------------------------------------------------------------------------------- /vue-router-src/util/params.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { warn } from './warn' 4 | import Regexp from 'path-to-regexp' 5 | 6 | // $flow-disable-line 7 | const regexpCompileCache: { 8 | [key: string]: Function 9 | } = Object.create(null) 10 | 11 | export function fillParams ( 12 | path: string, 13 | params: ?Object, 14 | routeMsg: string 15 | ): string { 16 | try { 17 | const filler = 18 | regexpCompileCache[path] || 19 | (regexpCompileCache[path] = Regexp.compile(path)) 20 | return filler(params || {}, { pretty: true }) 21 | } catch (e) { 22 | if (process.env.NODE_ENV !== 'production') { 23 | warn(false, `missing param for ${routeMsg}: ${e.message}`) 24 | } 25 | return '' 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /vue-router-src/util/path.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | export function resolvePath ( 4 | relative: string, 5 | base: string, 6 | append?: boolean 7 | ): string { 8 | const firstChar = relative.charAt(0) 9 | if (firstChar === '/') { 10 | return relative 11 | } 12 | 13 | if (firstChar === '?' || firstChar === '#') { 14 | return base + relative 15 | } 16 | 17 | const stack = base.split('/') 18 | 19 | // remove trailing segment if: 20 | // - not appending 21 | // - appending to trailing slash (last segment is empty) 22 | if (!append || !stack[stack.length - 1]) { 23 | stack.pop() 24 | } 25 | 26 | // resolve relative path 27 | const segments = relative.replace(/^\//, '').split('/') 28 | for (let i = 0; i < segments.length; i++) { 29 | const segment = segments[i] 30 | if (segment === '..') { 31 | stack.pop() 32 | } else if (segment !== '.') { 33 | stack.push(segment) 34 | } 35 | } 36 | 37 | // ensure leading slash 38 | if (stack[0] !== '') { 39 | stack.unshift('') 40 | } 41 | 42 | return stack.join('/') 43 | } 44 | 45 | export function parsePath (path: string): { 46 | path: string; 47 | query: string; 48 | hash: string; 49 | } { 50 | let hash = '' 51 | let query = '' 52 | 53 | const hashIndex = path.indexOf('#') 54 | if (hashIndex >= 0) { 55 | hash = path.slice(hashIndex) 56 | path = path.slice(0, hashIndex) 57 | } 58 | 59 | const queryIndex = path.indexOf('?') 60 | if (queryIndex >= 0) { 61 | query = path.slice(queryIndex + 1) 62 | path = path.slice(0, queryIndex) 63 | } 64 | 65 | return { 66 | path, 67 | query, 68 | hash 69 | } 70 | } 71 | 72 | export function cleanPath (path: string): string { 73 | return path.replace(/\/\//g, '/') 74 | } 75 | -------------------------------------------------------------------------------- /vue-router-src/util/push-state.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { inBrowser } from './dom' 4 | import { saveScrollPosition } from './scroll' 5 | 6 | export const supportsPushState = inBrowser && (function () { 7 | const ua = window.navigator.userAgent 8 | 9 | if ( 10 | (ua.indexOf('Android 2.') !== -1 || ua.indexOf('Android 4.0') !== -1) && 11 | ua.indexOf('Mobile Safari') !== -1 && 12 | ua.indexOf('Chrome') === -1 && 13 | ua.indexOf('Windows Phone') === -1 14 | ) { 15 | return false 16 | } 17 | 18 | return window.history && 'pushState' in window.history 19 | })() 20 | 21 | // use User Timing api (if present) for more accurate key precision 22 | const Time = inBrowser && window.performance && window.performance.now 23 | ? window.performance 24 | : Date 25 | 26 | let _key: string = genKey() 27 | 28 | function genKey (): string { 29 | return Time.now().toFixed(3) 30 | } 31 | 32 | export function getStateKey () { 33 | return _key 34 | } 35 | 36 | export function setStateKey (key: string) { 37 | _key = key 38 | } 39 | 40 | export function pushState (url?: string, replace?: boolean) { 41 | saveScrollPosition() 42 | // try...catch the pushState call to get around Safari 43 | // DOM Exception 18 where it limits to 100 pushState calls 44 | const history = window.history 45 | try { 46 | if (replace) { 47 | history.replaceState({ key: _key }, '', url) 48 | } else { 49 | _key = genKey() 50 | history.pushState({ key: _key }, '', url) 51 | } 52 | } catch (e) { 53 | window.location[replace ? 'replace' : 'assign'](url) 54 | } 55 | } 56 | 57 | export function replaceState (url?: string) { 58 | pushState(url, true) 59 | } 60 | -------------------------------------------------------------------------------- /vue-router-src/util/query.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { warn } from './warn' 4 | 5 | const encodeReserveRE = /[!'()*]/g 6 | const encodeReserveReplacer = c => '%' + c.charCodeAt(0).toString(16) 7 | const commaRE = /%2C/g 8 | 9 | // fixed encodeURIComponent which is more conformant to RFC3986: 10 | // - escapes [!'()*] 11 | // - preserve commas 12 | const encode = str => encodeURIComponent(str) 13 | .replace(encodeReserveRE, encodeReserveReplacer) 14 | .replace(commaRE, ',') 15 | 16 | const decode = decodeURIComponent 17 | 18 | export function resolveQuery ( 19 | query: ?string, 20 | extraQuery: Dictionary = {}, 21 | _parseQuery: ?Function 22 | ): Dictionary { 23 | const parse = _parseQuery || parseQuery 24 | let parsedQuery 25 | try { 26 | parsedQuery = parse(query || '') 27 | } catch (e) { 28 | process.env.NODE_ENV !== 'production' && warn(false, e.message) 29 | parsedQuery = {} 30 | } 31 | for (const key in extraQuery) { 32 | parsedQuery[key] = extraQuery[key] 33 | } 34 | return parsedQuery 35 | } 36 | 37 | function parseQuery (query: string): Dictionary { 38 | const res = {} 39 | 40 | query = query.trim().replace(/^(\?|#|&)/, '') 41 | 42 | if (!query) { 43 | return res 44 | } 45 | 46 | query.split('&').forEach(param => { 47 | const parts = param.replace(/\+/g, ' ').split('=') 48 | const key = decode(parts.shift()) 49 | const val = parts.length > 0 50 | ? decode(parts.join('=')) 51 | : null 52 | 53 | if (res[key] === undefined) { 54 | res[key] = val 55 | } else if (Array.isArray(res[key])) { 56 | res[key].push(val) 57 | } else { 58 | res[key] = [res[key], val] 59 | } 60 | }) 61 | 62 | return res 63 | } 64 | 65 | export function stringifyQuery (obj: Dictionary): string { 66 | const res = obj ? Object.keys(obj).map(key => { 67 | const val = obj[key] 68 | 69 | if (val === undefined) { 70 | return '' 71 | } 72 | 73 | if (val === null) { 74 | return encode(key) 75 | } 76 | 77 | if (Array.isArray(val)) { 78 | const result = [] 79 | val.forEach(val2 => { 80 | if (val2 === undefined) { 81 | return 82 | } 83 | if (val2 === null) { 84 | result.push(encode(key)) 85 | } else { 86 | result.push(encode(key) + '=' + encode(val2)) 87 | } 88 | }) 89 | return result.join('&') 90 | } 91 | 92 | return encode(key) + '=' + encode(val) 93 | }).filter(x => x.length > 0).join('&') : null 94 | return res ? `?${res}` : '' 95 | } 96 | -------------------------------------------------------------------------------- /vue-router-src/util/resolve-components.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { _Vue } from '../install' 4 | import { warn, isError } from './warn' 5 | 6 | export function resolveAsyncComponents (matched: Array): Function { 7 | return (to, from, next) => { 8 | let hasAsync = false 9 | let pending = 0 10 | let error = null 11 | 12 | flatMapComponents(matched, (def, _, match, key) => { 13 | // if it's a function and doesn't have cid attached, 14 | // assume it's an async component resolve function. 15 | // we are not using Vue's default async resolving mechanism because 16 | // we want to halt the navigation until the incoming component has been 17 | // resolved. 18 | if (typeof def === 'function' && def.cid === undefined) { 19 | hasAsync = true 20 | pending++ 21 | 22 | const resolve = once(resolvedDef => { 23 | if (isESModule(resolvedDef)) { 24 | resolvedDef = resolvedDef.default 25 | } 26 | // save resolved on async factory in case it's used elsewhere 27 | def.resolved = typeof resolvedDef === 'function' 28 | ? resolvedDef 29 | : _Vue.extend(resolvedDef) 30 | match.components[key] = resolvedDef 31 | pending-- 32 | if (pending <= 0) { 33 | next() 34 | } 35 | }) 36 | 37 | const reject = once(reason => { 38 | const msg = `Failed to resolve async component ${key}: ${reason}` 39 | process.env.NODE_ENV !== 'production' && warn(false, msg) 40 | if (!error) { 41 | error = isError(reason) 42 | ? reason 43 | : new Error(msg) 44 | next(error) 45 | } 46 | }) 47 | 48 | let res 49 | try { 50 | res = def(resolve, reject) 51 | } catch (e) { 52 | reject(e) 53 | } 54 | if (res) { 55 | if (typeof res.then === 'function') { 56 | res.then(resolve, reject) 57 | } else { 58 | // new syntax in Vue 2.3 59 | const comp = res.component 60 | if (comp && typeof comp.then === 'function') { 61 | comp.then(resolve, reject) 62 | } 63 | } 64 | } 65 | } 66 | }) 67 | 68 | if (!hasAsync) next() 69 | } 70 | } 71 | 72 | export function flatMapComponents ( 73 | matched: Array, 74 | fn: Function 75 | ): Array { 76 | return flatten(matched.map(m => { 77 | return Object.keys(m.components).map(key => fn( 78 | m.components[key], 79 | m.instances[key], 80 | m, key 81 | )) 82 | })) 83 | } 84 | 85 | export function flatten (arr: Array): Array { 86 | return Array.prototype.concat.apply([], arr) 87 | } 88 | 89 | const hasSymbol = 90 | typeof Symbol === 'function' && 91 | typeof Symbol.toStringTag === 'symbol' 92 | 93 | function isESModule (obj) { 94 | return obj.__esModule || (hasSymbol && obj[Symbol.toStringTag] === 'Module') 95 | } 96 | 97 | // in Webpack 2, require.ensure now also returns a Promise 98 | // so the resolve/reject functions may get called an extra time 99 | // if the user uses an arrow function shorthand that happens to 100 | // return that Promise. 101 | function once (fn) { 102 | let called = false 103 | return function (...args) { 104 | if (called) return 105 | called = true 106 | return fn.apply(this, args) 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /vue-router-src/util/warn.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | export function assert (condition: any, message: string) { 4 | if (!condition) { 5 | throw new Error(`[vue-router] ${message}`) 6 | } 7 | } 8 | 9 | export function warn (condition: any, message: string) { 10 | if (process.env.NODE_ENV !== 'production' && !condition) { 11 | typeof console !== 'undefined' && console.warn(`[vue-router] ${message}`) 12 | } 13 | } 14 | 15 | export function isError (err: any): boolean { 16 | return Object.prototype.toString.call(err).indexOf('Error') > -1 17 | } 18 | -------------------------------------------------------------------------------- /vue-src/compiler/directives/bind.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | /*Github:https://github.com/answershuto*/ 3 | export default function bind (el: ASTElement, dir: ASTDirective) { 4 | el.wrapData = (code: string) => { 5 | return `_b(${code},'${el.tag}',${dir.value}${ 6 | dir.modifiers && dir.modifiers.prop ? ',true' : '' 7 | })` 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /vue-src/compiler/directives/index.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import bind from './bind' 4 | import { noop } from 'shared/util' 5 | /*Github:https://github.com/answershuto*/ 6 | export default { 7 | bind, 8 | cloak: noop 9 | } 10 | -------------------------------------------------------------------------------- /vue-src/compiler/directives/model.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | /** 4 | * Cross-platform code generation for component v-model 5 | */ 6 | export function genComponentModel ( 7 | el: ASTElement, 8 | value: string, 9 | modifiers: ?ASTModifiers 10 | ): ?boolean { 11 | const { number, trim } = modifiers || {} 12 | 13 | const baseValueExpression = '$$v' 14 | let valueExpression = baseValueExpression 15 | if (trim) { 16 | valueExpression = 17 | `(typeof ${baseValueExpression} === 'string'` + 18 | `? ${baseValueExpression}.trim()` + 19 | `: ${baseValueExpression})` 20 | } 21 | if (number) { 22 | valueExpression = `_n(${valueExpression})` 23 | } 24 | const assignment = genAssignmentCode(value, valueExpression) 25 | 26 | el.model = { 27 | value: `(${value})`, 28 | expression: `"${value}"`, 29 | callback: `function (${baseValueExpression}) {${assignment}}` 30 | } 31 | } 32 | /*Github:https://github.com/answershuto*/ 33 | /** 34 | * Cross-platform codegen helper for generating v-model value assignment code. 35 | */ 36 | export function genAssignmentCode ( 37 | value: string, 38 | assignment: string 39 | ): string { 40 | const modelRs = parseModel(value) 41 | if (modelRs.idx === null) { 42 | return `${value}=${assignment}` 43 | } else { 44 | return `var $$exp = ${modelRs.exp}, $$idx = ${modelRs.idx};` + 45 | `if (!Array.isArray($$exp)){` + 46 | `${value}=${assignment}}` + 47 | `else{$$exp.splice($$idx, 1, ${assignment})}` 48 | } 49 | } 50 | 51 | /** 52 | * parse directive model to do the array update transform. a[idx] = val => $$a.splice($$idx, 1, val) 53 | * 54 | * for loop possible cases: 55 | * 56 | * - test 57 | * - test[idx] 58 | * - test[test1[idx]] 59 | * - test["a"][idx] 60 | * - xxx.test[a[a].test1[idx]] 61 | * - test.xxx.a["asa"][test1[idx]] 62 | * 63 | */ 64 | 65 | let len, str, chr, index, expressionPos, expressionEndPos 66 | 67 | export function parseModel (val: string): Object { 68 | str = val 69 | len = str.length 70 | index = expressionPos = expressionEndPos = 0 71 | 72 | if (val.indexOf('[') < 0 || val.lastIndexOf(']') < len - 1) { 73 | return { 74 | exp: val, 75 | idx: null 76 | } 77 | } 78 | 79 | while (!eof()) { 80 | chr = next() 81 | /* istanbul ignore if */ 82 | if (isStringStart(chr)) { 83 | parseString(chr) 84 | } else if (chr === 0x5B) { 85 | parseBracket(chr) 86 | } 87 | } 88 | 89 | return { 90 | exp: val.substring(0, expressionPos), 91 | idx: val.substring(expressionPos + 1, expressionEndPos) 92 | } 93 | } 94 | 95 | function next (): number { 96 | return str.charCodeAt(++index) 97 | } 98 | 99 | function eof (): boolean { 100 | return index >= len 101 | } 102 | 103 | function isStringStart (chr: number): boolean { 104 | return chr === 0x22 || chr === 0x27 105 | } 106 | 107 | function parseBracket (chr: number): void { 108 | let inBracket = 1 109 | expressionPos = index 110 | while (!eof()) { 111 | chr = next() 112 | if (isStringStart(chr)) { 113 | parseString(chr) 114 | continue 115 | } 116 | if (chr === 0x5B) inBracket++ 117 | if (chr === 0x5D) inBracket-- 118 | if (inBracket === 0) { 119 | expressionEndPos = index 120 | break 121 | } 122 | } 123 | } 124 | 125 | function parseString (chr: number): void { 126 | const stringQuote = chr 127 | while (!eof()) { 128 | chr = next() 129 | if (chr === stringQuote) { 130 | break 131 | } 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /vue-src/compiler/error-detector.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { dirRE, onRE } from './parser/index' 4 | 5 | // these keywords should not appear inside expressions, but operators like 6 | // typeof, instanceof and in are allowed 7 | const prohibitedKeywordRE = new RegExp('\\b' + ( 8 | 'do,if,for,let,new,try,var,case,else,with,await,break,catch,class,const,' + 9 | 'super,throw,while,yield,delete,export,import,return,switch,default,' + 10 | 'extends,finally,continue,debugger,function,arguments' 11 | ).split(',').join('\\b|\\b') + '\\b') 12 | 13 | // these unary operators should not be used as property/method names 14 | const unaryOperatorsRE = new RegExp('\\b' + ( 15 | 'delete,typeof,void' 16 | ).split(',').join('\\s*\\([^\\)]*\\)|\\b') + '\\s*\\([^\\)]*\\)') 17 | 18 | // check valid identifier for v-for 19 | const identRE = /[A-Za-z_$][\w$]*/ 20 | 21 | // strip strings in expressions 22 | const stripStringRE = /'(?:[^'\\]|\\.)*'|"(?:[^"\\]|\\.)*"|`(?:[^`\\]|\\.)*\$\{|\}(?:[^`\\]|\\.)*`|`(?:[^`\\]|\\.)*`/g 23 | 24 | // detect problematic expressions in a template 25 | export function detectErrors (ast: ?ASTNode): Array { 26 | const errors: Array = [] 27 | if (ast) { 28 | checkNode(ast, errors) 29 | } 30 | return errors 31 | } 32 | 33 | function checkNode (node: ASTNode, errors: Array) { 34 | if (node.type === 1) { 35 | for (const name in node.attrsMap) { 36 | if (dirRE.test(name)) { 37 | const value = node.attrsMap[name] 38 | if (value) { 39 | if (name === 'v-for') { 40 | checkFor(node, `v-for="${value}"`, errors) 41 | } else if (onRE.test(name)) { 42 | checkEvent(value, `${name}="${value}"`, errors) 43 | } else { 44 | checkExpression(value, `${name}="${value}"`, errors) 45 | } 46 | } 47 | } 48 | } 49 | if (node.children) { 50 | for (let i = 0; i < node.children.length; i++) { 51 | checkNode(node.children[i], errors) 52 | } 53 | } 54 | } else if (node.type === 2) { 55 | checkExpression(node.expression, node.text, errors) 56 | } 57 | } 58 | 59 | function checkEvent (exp: string, text: string, errors: Array) { 60 | const stipped = exp.replace(stripStringRE, '') 61 | const keywordMatch: any = stipped.match(unaryOperatorsRE) 62 | if (keywordMatch && stipped.charAt(keywordMatch.index - 1) !== '$') { 63 | errors.push( 64 | `avoid using JavaScript unary operator as property name: ` + 65 | `"${keywordMatch[0]}" in expression ${text.trim()}` 66 | ) 67 | } 68 | checkExpression(exp, text, errors) 69 | } 70 | 71 | function checkFor (node: ASTElement, text: string, errors: Array) { 72 | checkExpression(node.for || '', text, errors) 73 | checkIdentifier(node.alias, 'v-for alias', text, errors) 74 | checkIdentifier(node.iterator1, 'v-for iterator', text, errors) 75 | checkIdentifier(node.iterator2, 'v-for iterator', text, errors) 76 | } 77 | 78 | function checkIdentifier (ident: ?string, type: string, text: string, errors: Array) { 79 | if (typeof ident === 'string' && !identRE.test(ident)) { 80 | errors.push(`invalid ${type} "${ident}" in expression: ${text.trim()}`) 81 | } 82 | } 83 | 84 | function checkExpression (exp: string, text: string, errors: Array) { 85 | try { 86 | new Function(`return ${exp}`) 87 | } catch (e) { 88 | const keywordMatch = exp.replace(stripStringRE, '').match(prohibitedKeywordRE) 89 | if (keywordMatch) { 90 | errors.push( 91 | `avoid using JavaScript keyword as property name: ` + 92 | `"${keywordMatch[0]}" in expression ${text.trim()}` 93 | ) 94 | } else { 95 | errors.push(`invalid expression: ${text.trim()}`) 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /vue-src/compiler/parser/entity-decoder.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | let decoder 4 | 5 | export function decode (html: string): string { 6 | decoder = decoder || document.createElement('div') 7 | decoder.innerHTML = html 8 | return decoder.textContent 9 | } 10 | -------------------------------------------------------------------------------- /vue-src/compiler/parser/filter-parser.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | const validDivisionCharRE = /[\w).+\-_$\]]/ 4 | 5 | /*解析过滤器*/ 6 | export function parseFilters (exp: string): string { 7 | let inSingle = false 8 | let inDouble = false 9 | let inTemplateString = false 10 | let inRegex = false 11 | let curly = 0 12 | let square = 0 13 | let paren = 0 14 | let lastFilterIndex = 0 15 | let c, prev, i, expression, filters 16 | 17 | for (i = 0; i < exp.length; i++) { 18 | prev = c 19 | c = exp.charCodeAt(i) 20 | if (inSingle) { 21 | // ' 单引号 22 | if (c === 0x27 && prev !== 0x5C) inSingle = false 23 | } else if (inDouble) { 24 | // " 双引号 25 | if (c === 0x22 && prev !== 0x5C) inDouble = false 26 | } else if (inTemplateString) { 27 | // ` 模板字符串 28 | if (c === 0x60 && prev !== 0x5C) inTemplateString = false 29 | } else if (inRegex) { 30 | // / 正则 31 | if (c === 0x2f && prev !== 0x5C) inRegex = false 32 | } else if ( 33 | // | 管道 34 | c === 0x7C && // pipe 35 | exp.charCodeAt(i + 1) !== 0x7C && 36 | exp.charCodeAt(i - 1) !== 0x7C && 37 | !curly && !square && !paren 38 | ) { 39 | if (expression === undefined) { 40 | // first filter, end of expression 41 | lastFilterIndex = i + 1 42 | expression = exp.slice(0, i).trim() 43 | } else { 44 | pushFilter() 45 | } 46 | } else { 47 | switch (c) { 48 | case 0x22: inDouble = true; break // " 49 | case 0x27: inSingle = true; break // ' 50 | case 0x60: inTemplateString = true; break // ` 51 | case 0x28: paren++; break // ( 52 | case 0x29: paren--; break // ) 53 | case 0x5B: square++; break // [ 54 | case 0x5D: square--; break // ] 55 | case 0x7B: curly++; break // { 56 | case 0x7D: curly--; break // } 57 | } 58 | if (c === 0x2f) { // / 59 | let j = i - 1 60 | let p 61 | // find first non-whitespace prev char 62 | for (; j >= 0; j--) { 63 | p = exp.charAt(j) 64 | if (p !== ' ') break 65 | } 66 | if (!p || !validDivisionCharRE.test(p)) { 67 | inRegex = true 68 | } 69 | } 70 | } 71 | } 72 | 73 | if (expression === undefined) { 74 | expression = exp.slice(0, i).trim() 75 | } else if (lastFilterIndex !== 0) { 76 | pushFilter() 77 | } 78 | 79 | function pushFilter () { 80 | (filters || (filters = [])).push(exp.slice(lastFilterIndex, i).trim()) 81 | lastFilterIndex = i + 1 82 | } 83 | 84 | if (filters) { 85 | for (i = 0; i < filters.length; i++) { 86 | expression = wrapFilter(expression, filters[i]) 87 | } 88 | } 89 | 90 | return expression 91 | } 92 | 93 | function wrapFilter (exp: string, filter: string): string { 94 | const i = filter.indexOf('(') 95 | if (i < 0) { 96 | // _f: resolveFilter 97 | return `_f("${filter}")(${exp})` 98 | } else { 99 | const name = filter.slice(0, i) 100 | const args = filter.slice(i + 1) 101 | return `_f("${name}")(${exp},${args}` 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /vue-src/compiler/parser/text-parser.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { cached } from 'shared/util' 4 | import { parseFilters } from './filter-parser' 5 | 6 | const defaultTagRE = /\{\{((?:.|\n)+?)\}\}/g 7 | const regexEscapeRE = /[-.*+?^${}()|[\]\/\\]/g 8 | 9 | const buildRegex = cached(delimiters => { 10 | const open = delimiters[0].replace(regexEscapeRE, '\\$&') 11 | const close = delimiters[1].replace(regexEscapeRE, '\\$&') 12 | return new RegExp(open + '((?:.|\\n)+?)' + close, 'g') 13 | }) 14 | /*Github:https://github.com/answershuto*/ 15 | export function parseText ( 16 | text: string, 17 | delimiters?: [string, string] 18 | ): string | void { 19 | const tagRE = delimiters ? buildRegex(delimiters) : defaultTagRE 20 | if (!tagRE.test(text)) { 21 | return 22 | } 23 | const tokens = [] 24 | let lastIndex = tagRE.lastIndex = 0 25 | let match, index 26 | while ((match = tagRE.exec(text))) { 27 | index = match.index 28 | // push text token 29 | if (index > lastIndex) { 30 | tokens.push(JSON.stringify(text.slice(lastIndex, index))) 31 | } 32 | // tag token 33 | const exp = parseFilters(match[1].trim()) 34 | tokens.push(`_s(${exp})`) 35 | lastIndex = index + match[0].length 36 | } 37 | if (lastIndex < text.length) { 38 | tokens.push(JSON.stringify(text.slice(lastIndex))) 39 | } 40 | return tokens.join('+') 41 | } 42 | -------------------------------------------------------------------------------- /vue-src/core/components/index.js: -------------------------------------------------------------------------------- 1 | import KeepAlive from './keep-alive' 2 | 3 | export default { 4 | KeepAlive 5 | } 6 | -------------------------------------------------------------------------------- /vue-src/core/components/keep-alive.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { isRegExp } from 'shared/util' 4 | import { getFirstComponentChild } from 'core/vdom/helpers/index' 5 | 6 | type VNodeCache = { [key: string]: ?VNode }; 7 | 8 | const patternTypes: Array = [String, RegExp] 9 | 10 | /* 获取组件名称 */ 11 | function getComponentName (opts: ?VNodeComponentOptions): ?string { 12 | return opts && (opts.Ctor.options.name || opts.tag) 13 | } 14 | 15 | /* 检测name是否匹配 */ 16 | function matches (pattern: string | RegExp, name: string): boolean { 17 | if (typeof pattern === 'string') { 18 | /* 字符串情况,如a,b,c */ 19 | return pattern.split(',').indexOf(name) > -1 20 | } else if (isRegExp(pattern)) { 21 | /* 正则 */ 22 | return pattern.test(name) 23 | } 24 | /* istanbul ignore next */ 25 | return false 26 | } 27 | 28 | /* 修正cache */ 29 | function pruneCache (cache: VNodeCache, current: VNode, filter: Function) { 30 | for (const key in cache) { 31 | /* 取出cache中的vnode */ 32 | const cachedNode: ?VNode = cache[key] 33 | if (cachedNode) { 34 | const name: ?string = getComponentName(cachedNode.componentOptions) 35 | /* name不符合filter条件的,同时不是目前渲染的vnode时,销毁vnode对应的组件实例(Vue实例),并从cache中移除 */ 36 | if (name && !filter(name)) { 37 | if (cachedNode !== current) { 38 | pruneCacheEntry(cachedNode) 39 | } 40 | cache[key] = null 41 | } 42 | } 43 | } 44 | } 45 | 46 | /* 销毁vnode对应的组件实例(Vue实例) */ 47 | function pruneCacheEntry (vnode: ?VNode) { 48 | if (vnode) { 49 | vnode.componentInstance.$destroy() 50 | } 51 | } 52 | 53 | /* keep-alive组件 */ 54 | export default { 55 | name: 'keep-alive', 56 | /* 抽象组件 */ 57 | abstract: true, 58 | 59 | props: { 60 | include: patternTypes, 61 | exclude: patternTypes 62 | }, 63 | 64 | created () { 65 | /* 缓存对象 */ 66 | this.cache = Object.create(null) 67 | }, 68 | 69 | /* destroyed钩子中销毁所有cache中的组件实例 */ 70 | destroyed () { 71 | for (const key in this.cache) { 72 | pruneCacheEntry(this.cache[key]) 73 | } 74 | }, 75 | 76 | watch: { 77 | /* 监视include以及exclude,在被修改的时候对cache进行修正 */ 78 | include (val: string | RegExp) { 79 | pruneCache(this.cache, this._vnode, name => matches(val, name)) 80 | }, 81 | exclude (val: string | RegExp) { 82 | pruneCache(this.cache, this._vnode, name => !matches(val, name)) 83 | } 84 | }, 85 | 86 | render () { 87 | /* 得到slot插槽中的第一个组件 */ 88 | const vnode: VNode = getFirstComponentChild(this.$slots.default) 89 | 90 | const componentOptions: ?VNodeComponentOptions = vnode && vnode.componentOptions 91 | if (componentOptions) { 92 | // check pattern 93 | /* 获取组件名称,优先获取组件的name字段,否则是组件的tag */ 94 | const name: ?string = getComponentName(componentOptions) 95 | /* name不在inlcude中或者在exlude中则直接返回vnode(没有取缓存) */ 96 | if (name && ( 97 | (this.include && !matches(this.include, name)) || 98 | (this.exclude && matches(this.exclude, name)) 99 | )) { 100 | return vnode 101 | } 102 | const key: ?string = vnode.key == null 103 | // same constructor may get registered as different local components 104 | // so cid alone is not enough (#3269) 105 | ? componentOptions.Ctor.cid + (componentOptions.tag ? `::${componentOptions.tag}` : '') 106 | : vnode.key 107 | /* 如果已经做过缓存了则直接从缓存中获取组件实例给vnode,还未缓存过则进行缓存 */ 108 | if (this.cache[key]) { 109 | vnode.componentInstance = this.cache[key].componentInstance 110 | } else { 111 | this.cache[key] = vnode 112 | } 113 | /* keepAlive标记位 */ 114 | vnode.data.keepAlive = true 115 | } 116 | return vnode 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /vue-src/core/config.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { 4 | no, 5 | noop, 6 | identity 7 | } from 'shared/util' 8 | 9 | import { LIFECYCLE_HOOKS } from 'shared/constants' 10 | 11 | export type Config = { 12 | // user 13 | optionMergeStrategies: { [key: string]: Function }; 14 | silent: boolean; 15 | productionTip: boolean; 16 | performance: boolean; 17 | devtools: boolean; 18 | errorHandler: ?(err: Error, vm: Component, info: string) => void; 19 | ignoredElements: Array; 20 | keyCodes: { [key: string]: number | Array }; 21 | 22 | // platform 23 | isReservedTag: (x?: string) => boolean; 24 | isReservedAttr: (x?: string) => boolean; 25 | parsePlatformTagName: (x: string) => string; 26 | isUnknownElement: (x?: string) => boolean; 27 | getTagNamespace: (x?: string) => string | void; 28 | mustUseProp: (tag: string, type: ?string, name: string) => boolean; 29 | 30 | // legacy 31 | _lifecycleHooks: Array; 32 | }; 33 | 34 | export default ({ 35 | /** 36 | * Option merge strategies (used in core/util/options) 37 | */ 38 | optionMergeStrategies: Object.create(null), 39 | 40 | /** 41 | * Whether to suppress warnings. 42 | */ 43 | silent: false, 44 | 45 | /** 46 | * Show production mode tip message on boot? 47 | */ 48 | productionTip: process.env.NODE_ENV !== 'production', 49 | 50 | /** 51 | * Whether to enable devtools 52 | */ 53 | devtools: process.env.NODE_ENV !== 'production', 54 | 55 | /** 56 | * Whether to record perf 57 | */ 58 | performance: false, 59 | 60 | /** 61 | * Error handler for watcher errors 62 | */ 63 | errorHandler: null, 64 | 65 | /** 66 | * Ignore certain custom elements 67 | */ 68 | ignoredElements: [], 69 | 70 | /** 71 | * Custom user key aliases for v-on 72 | */ 73 | keyCodes: Object.create(null), 74 | 75 | /** 76 | * Check if a tag is reserved so that it cannot be registered as a 77 | * component. This is platform-dependent and may be overwritten. 78 | */ 79 | isReservedTag: no, 80 | 81 | /** 82 | * Check if an attribute is reserved so that it cannot be used as a component 83 | * prop. This is platform-dependent and may be overwritten. 84 | */ 85 | isReservedAttr: no, 86 | 87 | /** 88 | * Check if a tag is an unknown element. 89 | * Platform-dependent. 90 | */ 91 | isUnknownElement: no, 92 | 93 | /** 94 | * Get the namespace of an element 95 | */ 96 | getTagNamespace: noop, 97 | 98 | /** 99 | * Parse the real tag name for the specific platform. 100 | */ 101 | parsePlatformTagName: identity, 102 | 103 | /** 104 | * Check if an attribute must be bound using property, e.g. value 105 | * Platform-dependent. 106 | */ 107 | mustUseProp: no, 108 | 109 | /** 110 | * Exposed for legacy reasons 111 | */ 112 | _lifecycleHooks: LIFECYCLE_HOOKS 113 | }: Config) 114 | -------------------------------------------------------------------------------- /vue-src/core/global-api/assets.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import config from '../config' 4 | import { ASSET_TYPES } from 'shared/constants' 5 | import { warn, isPlainObject } from '../util/index' 6 | 7 | export function initAssetRegisters (Vue: GlobalAPI) { 8 | /** 9 | * Create asset registration methods. 10 | */ 11 | ASSET_TYPES.forEach(type => { 12 | Vue[type] = function ( 13 | id: string, 14 | definition: Function | Object 15 | ): Function | Object | void { 16 | if (!definition) { 17 | return this.options[type + 's'][id] 18 | } else { 19 | /* istanbul ignore if */ 20 | if (process.env.NODE_ENV !== 'production') { 21 | if (type === 'component' && config.isReservedTag(id)) { 22 | warn( 23 | 'Do not use built-in or reserved HTML elements as component ' + 24 | 'id: ' + id 25 | ) 26 | } 27 | } 28 | if (type === 'component' && isPlainObject(definition)) { 29 | definition.name = definition.name || id 30 | definition = this.options._base.extend(definition) 31 | } 32 | if (type === 'directive' && typeof definition === 'function') { 33 | definition = { bind: definition, update: definition } 34 | } 35 | this.options[type + 's'][id] = definition 36 | return definition 37 | } 38 | } 39 | }) 40 | } 41 | -------------------------------------------------------------------------------- /vue-src/core/global-api/index.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import config from '../config' 4 | import { initUse } from './use' 5 | import { initMixin } from './mixin' 6 | import { initExtend } from './extend' 7 | import { initAssetRegisters } from './assets' 8 | import { set, del } from '../observer/index' 9 | import { ASSET_TYPES } from 'shared/constants' 10 | import builtInComponents from '../components/index' 11 | 12 | import { 13 | warn, 14 | extend, 15 | nextTick, 16 | mergeOptions, 17 | defineReactive 18 | } from '../util/index' 19 | 20 | export function initGlobalAPI (Vue: GlobalAPI) { 21 | // config 22 | const configDef = {} 23 | configDef.get = () => config 24 | if (process.env.NODE_ENV !== 'production') { 25 | configDef.set = () => { 26 | warn( 27 | 'Do not replace the Vue.config object, set individual fields instead.' 28 | ) 29 | } 30 | } 31 | Object.defineProperty(Vue, 'config', configDef) 32 | 33 | // exposed util methods. 34 | // NOTE: these are not considered part of the public API - avoid relying on 35 | // them unless you are aware of the risk. 36 | Vue.util = { 37 | warn, 38 | extend, 39 | mergeOptions, 40 | defineReactive 41 | } 42 | 43 | Vue.set = set 44 | Vue.delete = del 45 | Vue.nextTick = nextTick 46 | 47 | Vue.options = Object.create(null) 48 | ASSET_TYPES.forEach(type => { 49 | Vue.options[type + 's'] = Object.create(null) 50 | }) 51 | 52 | // this is used to identify the "base" constructor to extend all plain-object 53 | // components with in Weex's multi-instance scenarios. 54 | /*_base被用来标识基本构造函数(也就是Vue),以便在多场景下添加组件扩展*/ 55 | Vue.options._base = Vue 56 | 57 | extend(Vue.options.components, builtInComponents) 58 | 59 | initUse(Vue) 60 | initMixin(Vue) 61 | initExtend(Vue) 62 | initAssetRegisters(Vue) 63 | } 64 | -------------------------------------------------------------------------------- /vue-src/core/global-api/mixin.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { mergeOptions } from '../util/index' 4 | 5 | /*初始化mixin*/ 6 | export function initMixin (Vue: GlobalAPI) { 7 | /*https://cn.vuejs.org/v2/api/#Vue-mixin*/ 8 | Vue.mixin = function (mixin: Object) { 9 | /*mergeOptions合并optiuons*/ 10 | this.options = mergeOptions(this.options, mixin) 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /vue-src/core/global-api/use.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { toArray } from '../util/index' 4 | 5 | /*初始化use*/ 6 | export function initUse (Vue: GlobalAPI) { 7 | /*https://cn.vuejs.org/v2/api/#Vue-use*/ 8 | Vue.use = function (plugin: Function | Object) { 9 | /* istanbul ignore if */ 10 | /*标识位检测该插件是否已经被安装*/ 11 | if (plugin.installed) { 12 | return 13 | } 14 | // additional parameters 15 | const args = toArray(arguments, 1) 16 | /*a*/ 17 | args.unshift(this) 18 | if (typeof plugin.install === 'function') { 19 | /*install执行插件安装*/ 20 | plugin.install.apply(plugin, args) 21 | } else if (typeof plugin === 'function') { 22 | plugin.apply(null, args) 23 | } 24 | plugin.installed = true 25 | return this 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /vue-src/core/index.js: -------------------------------------------------------------------------------- 1 | import Vue from './instance/index' 2 | import { initGlobalAPI } from './global-api/index' 3 | import { isServerRendering } from 'core/util/env' 4 | 5 | initGlobalAPI(Vue) 6 | 7 | Object.defineProperty(Vue.prototype, '$isServer', { 8 | get: isServerRendering 9 | }) 10 | 11 | Vue.version = '__VERSION__' 12 | 13 | export default Vue 14 | -------------------------------------------------------------------------------- /vue-src/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 | /*Github:https://github.com/answershuto*/ 8 | function Vue (options) { 9 | if (process.env.NODE_ENV !== 'production' && 10 | !(this instanceof Vue)) { 11 | warn('Vue is a constructor and should be called with the `new` keyword') 12 | } 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 | -------------------------------------------------------------------------------- /vue-src/core/instance/inject.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | /*Github:https://github.com/answershuto*/ 3 | import { hasSymbol } from 'core/util/env' 4 | import { warn } from '../util/index' 5 | import { defineReactive } from '../observer/index' 6 | /*Github:https://github.com/answershuto*/ 7 | export function initProvide (vm: Component) { 8 | const provide = vm.$options.provide 9 | if (provide) { 10 | vm._provided = typeof provide === 'function' 11 | ? provide.call(vm) 12 | : provide 13 | } 14 | } 15 | 16 | export function initInjections (vm: Component) { 17 | const result = resolveInject(vm.$options.inject, vm) 18 | if (result) { 19 | Object.keys(result).forEach(key => { 20 | /* istanbul ignore else */ 21 | /*为对象defineProperty上在变化时通知的属性*/ 22 | if (process.env.NODE_ENV !== 'production') { 23 | defineReactive(vm, key, result[key], () => { 24 | warn( 25 | `Avoid mutating an injected value directly since the changes will be ` + 26 | `overwritten whenever the provided component re-renders. ` + 27 | `injection being mutated: "${key}"`, 28 | vm 29 | ) 30 | }) 31 | } else { 32 | defineReactive(vm, key, result[key]) 33 | } 34 | }) 35 | } 36 | } 37 | 38 | export function resolveInject (inject: any, vm: Component): ?Object { 39 | if (inject) { 40 | // inject is :any because flow is not smart enough to figure out cached 41 | // isArray here 42 | const isArray = Array.isArray(inject) 43 | const result = Object.create(null) 44 | const keys = isArray 45 | ? inject 46 | : hasSymbol 47 | ? Reflect.ownKeys(inject) 48 | : Object.keys(inject) 49 | 50 | for (let i = 0; i < keys.length; i++) { 51 | const key = keys[i] 52 | const provideKey = isArray ? key : inject[key] 53 | let source = vm 54 | while (source) { 55 | if (source._provided && provideKey in source._provided) { 56 | result[key] = source._provided[provideKey] 57 | break 58 | } 59 | source = source.$parent 60 | } 61 | } 62 | return result 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /vue-src/core/instance/proxy.js: -------------------------------------------------------------------------------- 1 | /* not type checking this file because flow doesn't play well with Proxy */ 2 | /*Github:https://github.com/answershuto*/ 3 | import config from 'core/config' 4 | import { warn, makeMap } from '../util/index' 5 | /*Github:https://github.com/answershuto*/ 6 | let initProxy 7 | 8 | if (process.env.NODE_ENV !== 'production') { 9 | const allowedGlobals = makeMap( 10 | 'Infinity,undefined,NaN,isFinite,isNaN,' + 11 | 'parseFloat,parseInt,decodeURI,decodeURIComponent,encodeURI,encodeURIComponent,' + 12 | 'Math,Number,Date,Array,Object,Boolean,String,RegExp,Map,Set,JSON,Intl,' + 13 | 'require' // for Webpack/Browserify 14 | ) 15 | 16 | const warnNonPresent = (target, key) => { 17 | warn( 18 | `Property or method "${key}" is not defined on the instance but ` + 19 | `referenced during render. Make sure to declare reactive data ` + 20 | `properties in the data option.`, 21 | target 22 | ) 23 | } 24 | 25 | const hasProxy = 26 | typeof Proxy !== 'undefined' && 27 | Proxy.toString().match(/native code/) 28 | 29 | if (hasProxy) { 30 | const isBuiltInModifier = makeMap('stop,prevent,self,ctrl,shift,alt,meta') 31 | /*为config.keyCodes设置一个代理,在set赋值的时候先从isBuiltInModifier里检查,不存在再赋值*/ 32 | config.keyCodes = new Proxy(config.keyCodes, { 33 | set (target, key, value) { 34 | if (isBuiltInModifier(key)) { 35 | warn(`Avoid overwriting built-in modifier in config.keyCodes: .${key}`) 36 | return false 37 | } else { 38 | target[key] = value 39 | return true 40 | } 41 | } 42 | }) 43 | } 44 | 45 | const hasHandler = { 46 | has (target, key) { 47 | const has = key in target 48 | const isAllowed = allowedGlobals(key) || key.charAt(0) === '_' 49 | if (!has && !isAllowed) { 50 | warnNonPresent(target, key) 51 | } 52 | return has || !isAllowed 53 | } 54 | } 55 | 56 | const getHandler = { 57 | get (target, key) { 58 | if (typeof key === 'string' && !(key in target)) { 59 | warnNonPresent(target, key) 60 | } 61 | return target[key] 62 | } 63 | } 64 | 65 | initProxy = function initProxy (vm) { 66 | if (hasProxy) { 67 | // determine which proxy handler to use 68 | const options = vm.$options 69 | const handlers = options.render && options.render._withStripped 70 | ? getHandler 71 | : hasHandler 72 | vm._renderProxy = new Proxy(vm, handlers) 73 | } else { 74 | vm._renderProxy = vm 75 | } 76 | } 77 | } 78 | 79 | export { initProxy } 80 | -------------------------------------------------------------------------------- /vue-src/core/instance/render-helpers/bind-object-props.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import config from 'core/config' 4 | import { isObject, warn, toObject } from 'core/util/index' 5 | 6 | /** 7 | * Runtime helper for merging v-bind="object" into a VNode's data. 8 | */ 9 | /*合并v-bind指令到VNode中*/ 10 | export function bindObjectProps ( 11 | data: any, 12 | tag: string, 13 | value: any, 14 | asProp?: boolean 15 | ): VNodeData { 16 | if (value) { 17 | if (!isObject(value)) { 18 | /*v-bind必须提供一个Object或者Array作为参数*/ 19 | process.env.NODE_ENV !== 'production' && warn( 20 | 'v-bind without argument expects an Object or Array value', 21 | this 22 | ) 23 | } else { 24 | if (Array.isArray(value)) { 25 | /*合并Array数组中的每一个对象到一个新的Object中*/ 26 | value = toObject(value) 27 | } 28 | let hash 29 | for (const key in value) { 30 | if (key === 'class' || key === 'style') { 31 | hash = data 32 | } else { 33 | const type = data.attrs && data.attrs.type 34 | hash = asProp || config.mustUseProp(tag, type, key) 35 | ? data.domProps || (data.domProps = {}) 36 | : data.attrs || (data.attrs = {}) 37 | } 38 | if (!(key in hash)) { 39 | hash[key] = value[key] 40 | } 41 | } 42 | } 43 | } 44 | return data 45 | } 46 | -------------------------------------------------------------------------------- /vue-src/core/instance/render-helpers/check-keycodes.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import config from 'core/config' 4 | 5 | /** 6 | * Runtime helper for checking keyCodes from config. 7 | */ 8 | /*从config配置中检查eventKeyCode是否存在*/ 9 | export function checkKeyCodes ( 10 | eventKeyCode: number, 11 | key: string, 12 | builtInAlias: number | Array | void 13 | ): boolean { 14 | const keyCodes = config.keyCodes[key] || builtInAlias 15 | if (Array.isArray(keyCodes)) { 16 | return keyCodes.indexOf(eventKeyCode) === -1 17 | } else { 18 | return keyCodes !== eventKeyCode 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /vue-src/core/instance/render-helpers/render-list.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { isObject } from 'core/util/index' 4 | 5 | /** 6 | * Runtime helper for rendering v-for lists. 7 | */ 8 | /*处理v-for列表渲染*/ 9 | export function renderList ( 10 | val: any, 11 | render: () => VNode 12 | ): ?Array { 13 | /*根据类型循环render*/ 14 | let ret: ?Array, i, l, keys, key 15 | if (Array.isArray(val) || typeof val === 'string') { 16 | ret = new Array(val.length) 17 | for (i = 0, l = val.length; i < l; i++) { 18 | ret[i] = render(val[i], i) 19 | } 20 | } else if (typeof val === 'number') { 21 | ret = new Array(val) 22 | for (i = 0; i < val; i++) { 23 | ret[i] = render(i + 1, i) 24 | } 25 | } else if (isObject(val)) { 26 | keys = Object.keys(val) 27 | ret = new Array(keys.length) 28 | for (i = 0, l = keys.length; i < l; i++) { 29 | key = keys[i] 30 | ret[i] = render(val[key], key, i) 31 | } 32 | } 33 | return ret 34 | } 35 | -------------------------------------------------------------------------------- /vue-src/core/instance/render-helpers/render-slot.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { extend, warn } from 'core/util/index' 4 | 5 | /** 6 | * Runtime helper for rendering 7 | */ 8 | /*处理slot的渲染*/ 9 | export function renderSlot ( 10 | name: string, 11 | fallback: ?Array, 12 | props: ?Object, 13 | bindObject: ?Object 14 | ): ?Array { 15 | const scopedSlotFn = this.$scopedSlots[name] 16 | if (scopedSlotFn) { // scoped slot 17 | props = props || {} 18 | if (bindObject) { 19 | extend(props, bindObject) 20 | } 21 | return scopedSlotFn(props) || fallback 22 | } else { 23 | const slotNodes = this.$slots[name] 24 | // warn duplicate slot usage 25 | if (slotNodes && process.env.NODE_ENV !== 'production') { 26 | slotNodes._rendered && warn( 27 | `Duplicate presence of slot "${name}" found in the same render tree ` + 28 | `- this will likely cause render errors.`, 29 | this 30 | ) 31 | slotNodes._rendered = true 32 | } 33 | return slotNodes || fallback 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /vue-src/core/instance/render-helpers/render-static.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { cloneVNode, cloneVNodes } from 'core/vdom/vnode' 4 | 5 | /** 6 | * Runtime helper for rendering static trees. 7 | */ 8 | /*处理static树的渲染*/ 9 | export function renderStatic ( 10 | index: number, 11 | isInFor?: boolean 12 | ): VNode | Array { 13 | /*从_staticTrees中取出tree,如果已经被渲染则会存在*/ 14 | let tree = this._staticTrees[index] 15 | // if has already-rendered static tree and not inside v-for, 16 | // we can reuse the same tree by doing a shallow clone. 17 | /*如果已经被渲染的static tree并且它不是在v-for中,我们能够通过浅拷贝来重用同一棵树*/ 18 | if (tree && !isInFor) { 19 | return Array.isArray(tree) 20 | ? cloneVNodes(tree) 21 | : cloneVNode(tree) 22 | } 23 | // otherwise, render a fresh tree. 24 | /*否则渲染一刻新的树,同时存储在_staticTrees中,供上面检测是否已经渲染过*/ 25 | tree = this._staticTrees[index] = 26 | this.$options.staticRenderFns[index].call(this._renderProxy) 27 | markStatic(tree, `__static__${index}`, false) 28 | return tree 29 | } 30 | 31 | /** 32 | * Runtime helper for v-once. 33 | * Effectively it means marking the node as static with a unique key. 34 | */ 35 | /*处理v-once的渲染函数*/ 36 | export function markOnce ( 37 | tree: VNode | Array, 38 | index: number, 39 | key: string 40 | ) { 41 | markStatic(tree, `__once__${index}${key ? `_${key}` : ``}`, true) 42 | return tree 43 | } 44 | 45 | function markStatic ( 46 | tree: VNode | Array, 47 | key: string, 48 | isOnce: boolean 49 | ) { 50 | /*处理static节点*/ 51 | if (Array.isArray(tree)) { 52 | for (let i = 0; i < tree.length; i++) { 53 | if (tree[i] && typeof tree[i] !== 'string') { 54 | markStaticNode(tree[i], `${key}_${i}`, isOnce) 55 | } 56 | } 57 | } else { 58 | markStaticNode(tree, key, isOnce) 59 | } 60 | } 61 | 62 | /*处理static节点*/ 63 | function markStaticNode (node, key, isOnce) { 64 | node.isStatic = true 65 | node.key = key 66 | node.isOnce = isOnce 67 | } 68 | -------------------------------------------------------------------------------- /vue-src/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 | /*处理filters*/ 9 | export function resolveFilter (id: string): Function { 10 | return resolveAsset(this.$options, 'filters', id, true) || identity 11 | } 12 | -------------------------------------------------------------------------------- /vue-src/core/instance/render-helpers/resolve-slots.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | /** 4 | * Runtime helper for resolving raw children VNodes into a slot object. 5 | */ 6 | export function resolveSlots ( 7 | children: ?Array, 8 | context: ?Component 9 | ): { [key: string]: Array } { 10 | const slots = {} 11 | if (!children) { 12 | return slots 13 | } 14 | const defaultSlot = [] 15 | for (let i = 0, l = children.length; i < l; i++) { 16 | const child = children[i] 17 | // named slots should only be respected if the vnode was rendered in the 18 | // same context. 19 | if ((child.context === context || child.functionalContext === context) && 20 | child.data && child.data.slot != null) { 21 | const name = child.data.slot 22 | const slot = (slots[name] || (slots[name] = [])) 23 | if (child.tag === 'template') { 24 | slot.push.apply(slot, child.children) 25 | } else { 26 | slot.push(child) 27 | } 28 | } else { 29 | defaultSlot.push(child) 30 | } 31 | } 32 | // ignore whitespace 33 | if (!defaultSlot.every(isWhitespace)) { 34 | slots.default = defaultSlot 35 | } 36 | return slots 37 | } 38 | 39 | function isWhitespace (node: VNode): boolean { 40 | return node.isComment || node.text === ' ' 41 | } 42 | 43 | /*处理ScopedSlots*/ 44 | export function resolveScopedSlots ( 45 | fns: Array<[string, Function]> 46 | ): { [key: string]: Function } { 47 | const res = {} 48 | for (let i = 0; i < fns.length; i++) { 49 | res[fns[i][0]] = fns[i][1] 50 | } 51 | return res 52 | } 53 | -------------------------------------------------------------------------------- /vue-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 | /*Github:https://github.com/answershuto*/ 6 | import { def } from '../util/index' 7 | 8 | /*取得原生数组的原型*/ 9 | const arrayProto = Array.prototype 10 | /*创建一个新的数组对象,修改该对象上的数组的七个方法,防止污染原生数组方法*/ 11 | export const arrayMethods = Object.create(arrayProto) 12 | 13 | /** 14 | * Intercept mutating methods and emit events 15 | */ 16 | /*这里重写了数组的这些方法,在保证不污染原生数组原型的情况下重写数组的这些方法,截获数组的成员发生的变化,执行原生数组操作的同时dep通知关联的所有观察者进行响应式处理*/ 17 | ;[ 18 | 'push', 19 | 'pop', 20 | 'shift', 21 | 'unshift', 22 | 'splice', 23 | 'sort', 24 | 'reverse' 25 | ] 26 | .forEach(function (method) { 27 | // cache original method 28 | /*将数组的原生方法缓存起来,后面要调用*/ 29 | const original = arrayProto[method] 30 | def(arrayMethods, method, function mutator () { 31 | // avoid leaking arguments: 32 | // http://jsperf.com/closure-with-arguments 33 | let i = arguments.length 34 | const args = new Array(i) 35 | while (i--) { 36 | args[i] = arguments[i] 37 | } 38 | /*调用原生的数组方法*/ 39 | const result = original.apply(this, args) 40 | 41 | /*数组新插入的元素需要重新进行observe才能响应式*/ 42 | const ob = this.__ob__ 43 | let inserted 44 | switch (method) { 45 | case 'push': 46 | inserted = args 47 | break 48 | case 'unshift': 49 | inserted = args 50 | break 51 | case 'splice': 52 | inserted = args.slice(2) 53 | break 54 | } 55 | if (inserted) ob.observeArray(inserted) 56 | 57 | // notify change 58 | /*dep通知所有注册的观察者进行响应式处理*/ 59 | ob.dep.notify() 60 | return result 61 | }) 62 | }) 63 | -------------------------------------------------------------------------------- /vue-src/core/observer/dep.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import type Watcher from './watcher' 4 | import { remove } from '../util/index' 5 | 6 | let uid = 0 7 | /*Github:https://github.com/answershuto*/ 8 | /** 9 | * A dep is an observable that can have multiple 10 | * directives subscribing to it. 11 | */ 12 | export default class Dep { 13 | static target: ?Watcher; 14 | id: number; 15 | subs: Array; 16 | 17 | constructor () { 18 | this.id = uid++ 19 | this.subs = [] 20 | } 21 | 22 | /*添加一个观察者对象*/ 23 | addSub (sub: Watcher) { 24 | this.subs.push(sub) 25 | } 26 | 27 | /*移除一个观察者对象*/ 28 | removeSub (sub: Watcher) { 29 | remove(this.subs, sub) 30 | } 31 | 32 | /*依赖收集,当存在Dep.target的时候添加观察者对象*/ 33 | depend () { 34 | if (Dep.target) { 35 | Dep.target.addDep(this) 36 | } 37 | } 38 | 39 | /*通知所有订阅者*/ 40 | notify () { 41 | // stabilize the subscriber list first 42 | const subs = this.subs.slice() 43 | for (let i = 0, l = subs.length; i < l; i++) { 44 | subs[i].update() 45 | } 46 | } 47 | } 48 | 49 | // the current target watcher being evaluated. 50 | // this is globally unique because there could be only one 51 | // watcher being evaluated at any time. 52 | /*依赖收集完需要将Dep.target设为null,防止后面重复添加依赖。*/ 53 | Dep.target = null 54 | const targetStack = [] 55 | 56 | /*将watcher观察者实例设置给Dep.target,用以依赖收集。同时将该实例存入target栈中*/ 57 | export function pushTarget (_target: Watcher) { 58 | if (Dep.target) targetStack.push(Dep.target) 59 | Dep.target = _target 60 | } 61 | 62 | /*将观察者实例从target栈中取出并设置给Dep.target*/ 63 | export function popTarget () { 64 | Dep.target = targetStack.pop() 65 | } 66 | -------------------------------------------------------------------------------- /vue-src/core/util/debug.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import config from '../config' 4 | import { noop } from 'shared/util' 5 | 6 | export let warn = noop 7 | export let tip = noop 8 | export let formatComponentName: Function = (null: any) // work around flow check 9 | 10 | if (process.env.NODE_ENV !== 'production') { 11 | const hasConsole = typeof console !== 'undefined' 12 | const classifyRE = /(?:^|[-_])(\w)/g 13 | const classify = str => str 14 | .replace(classifyRE, c => c.toUpperCase()) 15 | .replace(/[-_]/g, '') 16 | 17 | warn = (msg, vm) => { 18 | if (hasConsole && (!config.silent)) { 19 | console.error(`[Vue warn]: ${msg}` + ( 20 | vm ? generateComponentTrace(vm) : '' 21 | )) 22 | } 23 | } 24 | 25 | tip = (msg, vm) => { 26 | if (hasConsole && (!config.silent)) { 27 | console.warn(`[Vue tip]: ${msg}` + ( 28 | vm ? generateComponentTrace(vm) : '' 29 | )) 30 | } 31 | } 32 | 33 | /*格式化组件名*/ 34 | formatComponentName = (vm, includeFile) => { 35 | if (vm.$root === vm) { 36 | return '' 37 | } 38 | let name = typeof vm === 'string' 39 | ? vm 40 | : typeof vm === 'function' && vm.options 41 | ? vm.options.name 42 | : vm._isVue 43 | ? vm.$options.name || vm.$options._componentTag 44 | : vm.name 45 | 46 | const file = vm._isVue && vm.$options.__file 47 | if (!name && file) { 48 | const match = file.match(/([^/\\]+)\.vue$/) 49 | name = match && match[1] 50 | } 51 | 52 | return ( 53 | (name ? `<${classify(name)}>` : ``) + 54 | (file && includeFile !== false ? ` at ${file}` : '') 55 | ) 56 | } 57 | 58 | const repeat = (str, n) => { 59 | let res = '' 60 | while (n) { 61 | if (n % 2 === 1) res += str 62 | if (n > 1) str += str 63 | n >>= 1 64 | } 65 | return res 66 | } 67 | 68 | const generateComponentTrace = vm => { 69 | if (vm._isVue && vm.$parent) { 70 | const tree = [] 71 | let currentRecursiveSequence = 0 72 | while (vm) { 73 | if (tree.length > 0) { 74 | const last = tree[tree.length - 1] 75 | if (last.constructor === vm.constructor) { 76 | currentRecursiveSequence++ 77 | vm = vm.$parent 78 | continue 79 | } else if (currentRecursiveSequence > 0) { 80 | tree[tree.length - 1] = [last, currentRecursiveSequence] 81 | currentRecursiveSequence = 0 82 | } 83 | } 84 | tree.push(vm) 85 | vm = vm.$parent 86 | } 87 | return '\n\nfound in\n\n' + tree 88 | .map((vm, i) => `${ 89 | i === 0 ? '---> ' : repeat(' ', 5 + i * 2) 90 | }${ 91 | Array.isArray(vm) 92 | ? `${formatComponentName(vm[0])}... (${vm[1]} recursive calls)` 93 | : formatComponentName(vm) 94 | }`) 95 | .join('\n') 96 | } else { 97 | return `\n\n(found in ${formatComponentName(vm)})` 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /vue-src/core/util/error.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import config from '../config' 4 | import { warn } from './debug' 5 | import { inBrowser } from './env' 6 | 7 | export function handleError (err: Error, vm: any, info: string) { 8 | if (config.errorHandler) { 9 | config.errorHandler.call(null, err, vm, info) 10 | } else { 11 | if (process.env.NODE_ENV !== 'production') { 12 | warn(`Error in ${info}: "${err.toString()}"`, vm) 13 | } 14 | /* istanbul ignore else */ 15 | if (inBrowser && typeof console !== 'undefined') { 16 | console.error(err) 17 | } else { 18 | throw err 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /vue-src/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 { defineReactive } from '../observer/index' 11 | -------------------------------------------------------------------------------- /vue-src/core/util/lang.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | export const emptyObject = Object.freeze({}) 4 | 5 | /** 6 | * Check if a string starts with $ or _ 7 | */ 8 | export function isReserved (str: string): boolean { 9 | const c = (str + '').charCodeAt(0) 10 | return c === 0x24 || c === 0x5F 11 | } 12 | 13 | /** 14 | * Define a property. 15 | */ 16 | export function def (obj: Object, key: string, val: any, enumerable?: boolean) { 17 | Object.defineProperty(obj, key, { 18 | value: val, 19 | enumerable: !!enumerable, 20 | writable: true, 21 | configurable: true 22 | }) 23 | } 24 | 25 | /** 26 | * Parse simple path. 27 | */ 28 | const bailRE = /[^\w.$]/ 29 | export function parsePath (path: string): any { 30 | if (bailRE.test(path)) { 31 | return 32 | } 33 | const segments = path.split('.') 34 | return function (obj) { 35 | for (let i = 0; i < segments.length; i++) { 36 | if (!obj) return 37 | obj = obj[segments[i]] 38 | } 39 | return obj 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /vue-src/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 | -------------------------------------------------------------------------------- /vue-src/core/vdom/create-functional-component.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import VNode from './vnode' 4 | import { createElement } from './create-element' 5 | import { resolveInject } from '../instance/inject' 6 | import { resolveSlots } from '../instance/render-helpers/resolve-slots' 7 | 8 | import { 9 | isDef, 10 | camelize, 11 | validateProp 12 | } from '../util/index' 13 | 14 | export function createFunctionalComponent ( 15 | Ctor: Class, 16 | propsData: ?Object, 17 | data: VNodeData, 18 | context: Component, 19 | children: ?Array 20 | ): VNode | void { 21 | const props = {} 22 | const propOptions = Ctor.options.props 23 | if (isDef(propOptions)) { 24 | for (const key in propOptions) { 25 | props[key] = validateProp(key, propOptions, propsData || {}) 26 | } 27 | } else { 28 | if (isDef(data.attrs)) mergeProps(props, data.attrs) 29 | if (isDef(data.props)) mergeProps(props, data.props) 30 | } 31 | // ensure the createElement function in functional components 32 | // gets a unique context - this is necessary for correct named slot check 33 | const _context = Object.create(context) 34 | const h = (a, b, c, d) => createElement(_context, a, b, c, d, true) 35 | const vnode = Ctor.options.render.call(null, h, { 36 | data, 37 | props, 38 | children, 39 | parent: context, 40 | listeners: data.on || {}, 41 | injections: resolveInject(Ctor.options.inject, context), 42 | slots: () => resolveSlots(children, context) 43 | }) 44 | if (vnode instanceof VNode) { 45 | vnode.functionalContext = context 46 | if (data.slot) { 47 | (vnode.data || (vnode.data = {})).slot = data.slot 48 | } 49 | } 50 | return vnode 51 | } 52 | 53 | function mergeProps (to, from) { 54 | for (const key in from) { 55 | to[camelize(key)] = from[key] 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /vue-src/core/vdom/helpers/extract-props.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { 4 | tip, 5 | hasOwn, 6 | isDef, 7 | isUndef, 8 | hyphenate, 9 | formatComponentName 10 | } from 'core/util/index' 11 | 12 | export function extractPropsFromVNodeData ( 13 | data: VNodeData, 14 | Ctor: Class, 15 | tag?: string 16 | ): ?Object { 17 | // we are only extracting raw values here. 18 | // validation and default values are handled in the child 19 | // component itself. 20 | const propOptions = Ctor.options.props 21 | if (isUndef(propOptions)) { 22 | return 23 | } 24 | const res = {} 25 | const { attrs, props } = data 26 | if (isDef(attrs) || isDef(props)) { 27 | for (const key in propOptions) { 28 | const altKey = hyphenate(key) 29 | if (process.env.NODE_ENV !== 'production') { 30 | const keyInLowerCase = key.toLowerCase() 31 | if ( 32 | key !== keyInLowerCase && 33 | attrs && hasOwn(attrs, keyInLowerCase) 34 | ) { 35 | tip( 36 | `Prop "${keyInLowerCase}" is passed to component ` + 37 | `${formatComponentName(tag || Ctor)}, but the declared prop name is` + 38 | ` "${key}". ` + 39 | `Note that HTML attributes are case-insensitive and camelCased ` + 40 | `props need to use their kebab-case equivalents when using in-DOM ` + 41 | `templates. You should probably use "${altKey}" instead of "${key}".` 42 | ) 43 | } 44 | } 45 | checkProp(res, props, key, altKey, true) || 46 | checkProp(res, attrs, key, altKey, false) 47 | } 48 | } 49 | return res 50 | } 51 | 52 | function checkProp ( 53 | res: Object, 54 | hash: ?Object, 55 | key: string, 56 | altKey: string, 57 | preserve: boolean 58 | ): boolean { 59 | if (isDef(hash)) { 60 | if (hasOwn(hash, key)) { 61 | res[key] = hash[key] 62 | if (!preserve) { 63 | delete hash[key] 64 | } 65 | return true 66 | } else if (hasOwn(hash, altKey)) { 67 | res[key] = hash[altKey] 68 | if (!preserve) { 69 | delete hash[altKey] 70 | } 71 | return true 72 | } 73 | } 74 | return false 75 | } 76 | -------------------------------------------------------------------------------- /vue-src/core/vdom/helpers/get-first-component-child.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { isDef } from 'shared/util' 4 | 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)) { 11 | return c 12 | } 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /vue-src/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 | -------------------------------------------------------------------------------- /vue-src/core/vdom/helpers/merge-hook.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { createFnInvoker } from './update-listeners' 4 | import { remove, isDef, isUndef, isTrue } from 'shared/util' 5 | 6 | export function mergeVNodeHook (def: Object, hookKey: string, hook: Function) { 7 | let invoker 8 | const oldHook = def[hookKey] 9 | 10 | function wrappedHook () { 11 | hook.apply(this, arguments) 12 | // important: remove merged hook to ensure it's called only once 13 | // and prevent memory leak 14 | remove(invoker.fns, wrappedHook) 15 | } 16 | 17 | if (isUndef(oldHook)) { 18 | // no existing hook 19 | invoker = createFnInvoker([wrappedHook]) 20 | } else { 21 | /* istanbul ignore if */ 22 | if (isDef(oldHook.fns) && isTrue(oldHook.merged)) { 23 | // already a merged invoker 24 | invoker = oldHook 25 | invoker.fns.push(wrappedHook) 26 | } else { 27 | // existing plain hook 28 | invoker = createFnInvoker([oldHook, wrappedHook]) 29 | } 30 | } 31 | 32 | invoker.merged = true 33 | def[hookKey] = invoker 34 | } 35 | -------------------------------------------------------------------------------- /vue-src/core/vdom/helpers/normalize-children.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import VNode, { createTextVNode } from 'core/vdom/vnode' 4 | import { isDef, isUndef, isPrimitive } from 'shared/util' 5 | 6 | // The template compiler attempts to minimize the need for normalization by 7 | // statically analyzing the template at compile time. 8 | // 9 | // For plain HTML markup, normalization can be completely skipped because the 10 | // generated render function is guaranteed to return Array. There are 11 | // two cases where extra normalization is needed: 12 | 13 | // 1. When the children contains components - because a functional component 14 | // may return an Array instead of a single root. In this case, just a simple 15 | // normalization is needed - if any child is an Array, we flatten the whole 16 | // thing with Array.prototype.concat. It is guaranteed to be only 1-level deep 17 | // because functional components already normalize their own children. 18 | export function simpleNormalizeChildren (children: any) { 19 | for (let i = 0; i < children.length; i++) { 20 | if (Array.isArray(children[i])) { 21 | return Array.prototype.concat.apply([], children) 22 | } 23 | } 24 | return children 25 | } 26 | 27 | // 2. When the children contains constructs that always generated nested Arrays, 28 | // e.g.