vue 源码深入学习分析——史上超详细

2017/6/2 15:27:50 第一次复习

vue 框架号称五分钟就能上手,半小时就能精通,这是因为其使用非常简单,就像下面一样:

  1. let vm = new Vue({
  2. el: ‘#app‘,
  3. data: {
  4. a: 1,
  5. b: [1, 2, 3]
  6. }
  7. })

在最开始,我传递了两个选项 el 以及 data ,很简单,官网上也是这样写的。

你肯定注意到了,我使用了 new 操作符。这就很自然的想到,Vue 就是一个构造函数,vm是 Vue构造函数 生成的实例,我们的配置项是传入构造函数的参数,是一个包括 el 属性 和 data属性的对象,事实上在实例化 Vue 时,传入的选项对象可以包含数据、模板、挂载元素、方法、生命周期钩子等选项。全部的选项可以在 vue的官方API 文档中查看。;

那么我们下面就要受好奇心的驱动,来看看 Vue构造函数 是什么样的?

在 \node_modules\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. function Vue (options) {
  8. if (process.env.NODE_ENV !== ‘production‘ &&
  9. !(this instanceof Vue)) {
  10. warn(‘Vue is a constructor and should be called with the `new` keyword‘)
  11. }
  12. this._init(options)
  13. }
  14. initMixin(Vue)
  15. stateMixin(Vue)
  16. eventsMixin(Vue)
  17. lifecycleMixin(Vue)
  18. renderMixin(Vue)
  19. export default V

不用害怕,我带你捋一捋,我们首先关注第8行,我摘抄出来:

  1. function Vue (options) {
  2. if (process.env.NODE_ENV !== ‘production‘ && // 这个 if 判断,是当你不用new操作符来实例化Vue构造函数时,会爆出警告
  3. !(this instanceof Vue)) {
  4. warn(‘Vue is a constructor and should be called with the `new` keyword‘)
  5. }
  6. this._init(options) // 主要就是这一句,
  7. }

发现了吧,Vue 的确是一个构造函数,和你平时使用的 Array, Object 等普普通通的构造函数,没有本质的区别。

在构造函数里面,我们要关心的是 this._init( options ) , 稍微我会详细的来讲,我们先看 \node_modules\vue\src\core\instance\index.js 文件中的第16行~20行:

  1. initMixin(Vue)
  2. stateMixin(Vue)
  3. eventsMixin(Vue)
  4. lifecycleMixin(Vue)
  5. renderMixin(Vue)

上面的代码调用了五个方法,这五个方法都是把Vue构造函数作为参数传入,其目的都是在  Vue .prototype  上挂载方法或属性,这个概念很好理解,我们在js 的原型链继承的学习中,经常把属性和方法丢到构造函数的原型上作为公有的属性和方法。

  1. // initMixin(Vue) src/core/instance/init.js **************************************************
  2. Vue.prototype._init = function (options?: Object) {}
  3. // stateMixin(Vue) src/core/instance/state.js **************************************************
  4. Vue.prototype.$data
  5. Vue.prototype.$set = set
  6. Vue.prototype.$delete = del
  7. Vue.prototype.$watch = function(){}
  8. // renderMixin(Vue) src/core/instance/render.js **************************************************
  9. Vue.prototype.$nextTick = function (fn: Function) {}
  10. Vue.prototype._render = function (): VNode {}
  11. Vue.prototype._s = _toString
  12. Vue.prototype._v = createTextVNode
  13. Vue.prototype._n = toNumber
  14. Vue.prototype._e = createEmptyVNode
  15. Vue.prototype._q = looseEqual
  16. Vue.prototype._i = looseIndexOf
  17. Vue.prototype._m = function(){}
  18. Vue.prototype._o = function(){}
  19. Vue.prototype._f = function resolveFilter (id) {}
  20. Vue.prototype._l = function(){}
  21. Vue.prototype._t = function(){}
  22. Vue.prototype._b = function(){}
  23. Vue.prototype._k = function(){}
  24. // eventsMixin(Vue) src/core/instance/events.js **************************************************
  25. Vue.prototype.$on = function (event: string, fn: Function): Component {}
  26. Vue.prototype.$once = function (event: string, fn: Function): Component {}
  27. Vue.prototype.$off = function (event?: string, fn?: Function): Component {}
  28. Vue.prototype.$emit = function (event: string): Component {}
  29. // lifecycleMixin(Vue) src/core/instance/lifecycle.js **************************************************
  30. Vue.prototype._mount = function(){}
  31. Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {}
  32. Vue.prototype._updateFromParent = function(){}
  33. Vue.prototype.$forceUpdate = function () {}
  34. Vue.prototype.$destroy = function () {}

经过上面5个方法对Vue构造函数的处理,vm实例上就可以使用这些属性和方法了。其实在其他地方,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. initGlobalAPI(Vue)
  5. Object.defineProperty(Vue.prototype, ‘$isServer‘, { //为 Vue.prototype 添加$isServer属性
  6. get: isServerRendering
  7. })
  8. Vue.version = ‘__VERSION__‘ // 在VUE 身上挂载了 version的静态属性
  9. export default Vue

initGlobalAPI() 的作用是在 Vue 构造函数上挂载静态属性和方法,Vue 在经过 initGlobalAPI 之后,会变成这样:

  1. Vue.config
  2. Vue.util = util
  3. Vue.set = set
  4. Vue.delete = del
  5. Vue.nextTick = util.nextTick
  6. Vue.options = {
  7. components: {
  8. KeepAlive
  9. },
  10. directives: {},
  11. filters: {},
  12. _base: Vue
  13. }
  14. Vue.use
  15. Vue.mixin
  16. Vue.cid = 0
  17. Vue.extend
  18. Vue.component = function(){}
  19. Vue.directive = function(){}
  20. Vue.filter = function(){}
  21. Vue.prototype.$isServer
  22. Vue.version = ‘__VERSION__‘

下一个就是 web-runtime.js 文件了,web-runtime.js 文件主要做了三件事儿:

  1. 1、覆盖 Vue.config 的属性,将其设置为平台特有的一些方法
  2. 2、Vue.options.directives 和 Vue.options.components 安装平台特有的指令和组件
  3. 3、在 Vue.prototype 上定义 __patch__ 和 $mount

经过 web-runtime.js 文件之后,Vue 变成下面这个样子:

  1. // 安装平台特定的utils
  2. Vue.config.isUnknownElement = isUnknownElement
  3. Vue.config.isReservedTag = isReservedTag
  4. Vue.config.getTagNamespace = getTagNamespace
  5. Vue.config.mustUseProp = mustUseProp
  6. // 安装平台特定的 指令 和 组件
  7. Vue.options = {
  8. components: {
  9. KeepAlive,
  10. Transition,
  11. TransitionGroup
  12. },
  13. directives: {
  14. model,
  15. show
  16. },
  17. filters: {},
  18. _base: Vue
  19. }
  20. Vue.prototype.__patch__
  21. Vue.prototype.$mount

这里要注意的是Vue.options 的变化。

最后一个处理 Vue 的文件就是入口文件 web-runtime-with-compiler.js 了,该文件做了两件事:

1、缓存来自 web-runtime.js 文件的 $mount 函数

  1. const mount = Vue.prototype.$mount

2、在 Vue 上挂载 compile

  1. Vue.compile = compileToFunctions

上面 compileToFunctions 函数可以将模板 template 编译为render函数。

至此,我们算是还原了 Vue 构造函数,总结一下:

  1. 1、Vue.prototype 下的属性和方法的挂载主要是在 src/core/instance 目录中的代码处理的
  2. 2、Vue 下的静态属性和方法的挂载主要是在 src/core/global-api 目录下的代码处理的
  3. 3、web-runtime.js 主要是添加web平台特有的配置、组件和指令,web-runtime-with-compiler.js 给Vue的 $mount 方法添加 compiler 编译器,支持 template。

好了,我们再回过头来看 this._init() 方法,_init() 方法就是Vue调用的第一个方法,然后将我们的参数 options 传了过去。_init() 是在   \node_modules\vue\src\core\instance\init.js 文件中被声明的:

  1. Vue.prototype._init = function (options?: Object) {
  2. const vm: Component = this
  3. // a uid
  4. vm._uid = uid++
  5. let startTag, endTag
  6. /* istanbul ignore if */
  7. if (process.env.NODE_ENV !== ‘production‘ && config.performance && mark) {
  8. startTag = `vue-perf-init:${vm._uid}`
  9. endTag = `vue-perf-end:${vm._uid}`
  10. mark(startTag)
  11. }
  12. // a flag to avoid this being observed
  13. vm._isVue = true
  14. // merge options
  15. if (options && options._isComponent) {
  16. // optimize internal component instantiation
  17. // since dynamic options merging is pretty slow, and none of the
  18. // internal component options needs special treatment.
  19. initInternalComponent(vm, options)
  20. } else { // 大部分情况下是走了这个分支,也是vue第一步要做的事情,使用mergeOptions来合并参数选项
  21. vm.$options = mergeOptions(
  22. resolveConstructorOptions(vm.constructor),
  23. options || {},
  24. vm
  25. )
  26. }
  27. /* istanbul ignore else */
  28. if (process.env.NODE_ENV !== ‘production‘) {
  29. initProxy(vm)
  30. } else {
  31. vm._renderProxy = vm
  32. }
  33. // expose real self
  34. vm._self = vm
  35. initLifecycle(vm)
  36. initEvents(vm)
  37. initRender(vm)
  38. callHook(vm, ‘beforeCreate‘)
  39. initInjections(vm) // resolve injections before data/props
  40. initState(vm)
  41. initProvide(vm) // resolve provide after data/props
  42. callHook(vm, ‘created‘)
  43. /* istanbul ignore if */
  44. if (process.env.NODE_ENV !== ‘production‘ && config.performance && mark) {
  45. vm._name = formatComponentName(vm, false)
  46. mark(endTag)
  47. measure(`${vm._name} init`, startTag, endTag)
  48. }
  49. if (vm.$options.el) {
  50. vm.$mount(vm.$options.el)
  51. }
  52. }

好了,我们一开始不需要关心那么多边边角角,直接从23行代码开始看,因为大部分情况下是走了这条分支,也就是执行了下面的代码:

  1. vm.$options = mergeOptions(
  2. resolveConstructorOptions(vm.constructor),
  3. options || {},
  4. vm
  5. )

这里是执行了 mergeOptions 函数,并将返回值赋值给 vm.$options  属性。 mergeOptions 函数接受三个参数,分别是 resolveContructorOptions方法,  我们调用 vue 构造函数传入的配置对象(如果没有就是空对象),以及 vm 实例 本身。

我们先看 resovleContructorOptions 方法, 传入的参数是 vm.constructor 。 vm.constructor 代表的是啥? const vm: Component = this 人家_init() 函数第一行就定义了,是指向_init() 函数内部的this, _init( ) 函数是 Vue.prototype上的一个方法,所以在其身上调用的时候,this 指向本身 Vue.prototype, 那么 vm.constructor 也就是指向 Vue 构造函数.

  1. export function resolveConstructorOptions (Ctor: Class<Component>) { //ctor 就是 VUE 构造函数
  2. let options = Ctor.options // vue 构造函数身上的 options 属性
  3. if (Ctor.super) { // 判断是否定义了 Vue.super ,这个是用来处理继承的,我们后续再讲
  4. const superOptions = resolveConstructorOptions(Ctor.super)
  5. const cachedSuperOptions = Ctor.superOptions
  6. if (superOptions !== cachedSuperOptions) {
  7. // super option changed,
  8. // need to resolve new options.
  9. Ctor.superOptions = superOptions
  10. // check if there are any late-modified/attached options (#4976)
  11. const modifiedOptions = resolveModifiedOptions(Ctor)
  12. // update base extend options
  13. if (modifiedOptions) {
  14. extend(Ctor.extendOptions, modifiedOptions)
  15. }
  16. options = Ctor.options = mergeOptions(superOptions, Ctor.extendOptions)
  17. if (options.name) {
  18. options.components[options.name] = Ctor
  19. }
  20. }
  21. }
  22. return options
  23. }

第22行,resolveConstructorOptions 方法直接返回了 Vue.options。也就是说,传递给 mergeOptions 方法的第一个参数其实是 Vue.options。那么,实际上原来的代码就变成了下面这样:

  1. // 这是原来的代码
  2. vm.$options = mergeOptions(
  3. resolveConstructorOptions(vm.constructor),
  4. options || {},
  5. vm
  6. )

  7.  
  8. // 实际上传过去的参数是下面这些
  9. vm.$options = mergeOptions(
  10. // Vue.options
  11. {
  12. components: {
  13. KeepAlive,
  14. Transition,
  15. TransitionGroup
  16. },
  17. directives: {
  18. model,
  19. show
  20. },
  21. filters: {},
  22. _base: Vue
  23. },
  24. // 调用Vue构造函数时传入的参数选项 options
  25. {
  26. el: ‘#app‘,
  27. data: {
  28. a: 1,
  29. b: [1, 2, 3]
  30. }
  31. },
  32. // this
  33. vm
  34. )

为什么要使用 mergeOptions 方法呢? 是为了 合并策略, 对于子组件和父组件如果有相同的属性(option)时要进行合并,相关文章:

http://www.tuicool.com/articles/UbqqAfY

那么我们继续查看 _init() 方法在合并完选项之后,Vue 第二部做的事情就来了:初始化工作与Vue实例对象的设计:

通过initData 看vue的数据响应系统

Vue的数据响应系统包含三个部分: Observer 、 Dep 、 Watcher 。我们还是先看一下 initData 中的代码:

  1. function initData (vm: Component) {
  2. let data = vm.$options.data // 第一步还是要先拿到数据,vm.$options.data 这时候还是通过 mergeOptions 合并处理后的 mergedInstanceDataFn 函数
  3. data = vm._data = typeof data === ‘function‘
  4. ? data.call(vm)
  5. : data || {}
  6. if (!isPlainObject(data)) {
  7. data = {}
  8. process.env.NODE_ENV !== ‘production‘ && warn(
  9. ‘data functions should return an object:\n‘ +
  10. ‘https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function‘,
  11. vm
  12. )
  13. }
  14. // proxy data on instance
  15. const keys = Object.keys(data)
  16. const props = vm.$options.props
  17. let i = keys.length
  18. while (i--) {
  19. if (props && hasOwn(props, keys[i])) {
  20. process.env.NODE_ENV !== ‘production‘ && warn(
  21. `The data property "${keys[i]}" is already declared as a prop. ` +
  22. `Use prop default value instead.`,
  23. vm
  24. )
  25. } else {
  26. proxy(vm, keys[i]) // 目的是在实例对象上对数据进行代理,这样我们就能通过 this.a 来访问 data.a 了
  27. }
  28. }
  29. // observe data
  30. observe(data)
  31. data.__ob__ && data.__ob__.vmCount++
  32. }

上面 proxy 方法非常简单,仅仅是在实例对象上设置与 data 属性同名的访问器属性,然后使用 _data 做数据劫持,如下:

  1. function proxy (vm: Component, key: string) {
  2. if (!isReserved(key)) {
  3. Object.defineProperty(vm, key, { // vm是实例,key是data属性上的属性,
  4. configurable: true,
  5. enumerable: true,
  6. get: function proxyGetter () {
  7. return vm._data[key]
  8. },
  9. set: function proxySetter (val) {
  10. vm._data[key] = val
  11. }
  12. })
  13. }
  14. }

做完数据的代理,就正式进入响应系统:

  1. observe(data)

我们说过,数据响应系统主要包含三部分: Observer  、Dep、Watcher,

我们首先思考,我们应该如何观察一个数据对象的变化?

vue.js和avalon.js 都是通过 Object.definedProperty() 方法来实现的, 下面我们主要来介绍一下这个方法为什么可以实现对对象属性改变的监听。

Object.defineProperty ( )有三个参数, 三个参数都需要,分别是对象,属性,属性的属性

  1. var o = {};
  2. Object.definedProperty(o, ‘a‘, {
  3. value: ‘b‘
  4. })

属性的属性有下面这些:

  1. configurable:true | false,
  2. enumerable:true | false,
  3. value:任意类型的值,
  4. writable:true | false

writable

该属性的值是否可以修改;如果设置为false,则不能被修改,修改不会报错,只是默默的不修改;

  1. var obj = {}
  2. //第一种情况:writable设置为false,不能重写。
  3. Object.defineProperty(obj,"newKey",{
  4. value:"hello",
  5. writable:false
  6. });
  7. //更改newKey的值
  8. obj.newKey = "change value";
  9. console.log( obj.newKey ); //hello
  10. //第二种情况:writable设置为true,可以重写
  11. Object.defineProperty(obj,"newKey",{
  12. value:"hello",
  13. writable:true
  14. });
  15. //更改newKey的值
  16. obj.newKey = "change value";
  17. console.log( obj.newKey ); //change value

enumerable

是否该属性可以被for……in 或者Object.keys( ) 枚举

  1. var obj = {}
  2. //第一种情况:enumerable设置为false,不能被枚举。
  3. Object.defineProperty(obj,"newKey",{
  4. value:"hello",
  5. writable:false,
  6. enumerable:false
  7. });
  8. //枚举对象的属性
  9. for( var attr in obj ){
  10. console.log( attr );
  11. }
  12. //第二种情况:enumerable设置为true,可以被枚举。
  13. Object.defineProperty(obj,"newKey",{
  14. value:"hello",
  15. writable:false,
  16. enumerable:true
  17. });
  18. //枚举对象的属性
  19. for( var attr in obj ){
  20. console.log( attr ); //newKey
  21. }

configurable

是否可以删除目标属性或是否可以再次修改属性的特性(writable, configurable, enumerable)。设置为true可以被删除或可以重新设置特性;设置为false,不能被可以被删除或不可以重新设置特性。默认为false。

这个属性起到两个作用:

1、 目标属性是否可以使用delete删除

2、目标属性是否可以再次设置特性

  1. //-----------------测试目标属性是否能被删除------------------------
  2. var obj = {}
  3. //第一种情况:configurable设置为false,不能被删除。
  4. Object.defineProperty(obj,"newKey",{
  5. value:"hello",
  6. writable:false,
  7. enumerable:false,
  8. configurable:false
  9. });
  10. //删除属性
  11. delete obj.newKey; //可以用delete 关键字来删除某一个对象上的属性
  12. console.log( obj.newKey ); //hello
  13. //第二种情况:configurable设置为true,可以被删除。
  14. Object.defineProperty(obj,"newKey",{
  15. value:"hello",
  16. writable:false,
  17. enumerable:false,
  18. configurable:true
  19. });
  20. //删除属性
  21. delete obj.newKey;
  22. console.log( obj.newKey ); //undefined
  23. //-----------------测试是否可以再次修改特性------------------------
  24. var obj = {}
  25. //第一种情况:configurable设置为false,不能再次修改特性。
  26. Object.defineProperty(obj,"newKey",{
  27. value:"hello",
  28. writable:false,
  29. enumerable:false,
  30. configurable:false
  31. });
  32. //重新修改特性
  33. Object.defineProperty(obj,"newKey",{
  34. value:"hello",
  35. writable:true,
  36. enumerable:true,
  37. configurable:true
  38. });
  39. console.log( obj.newKey ); //报错:Uncaught TypeError: Cannot redefine property: newKey
  40. //第二种情况:configurable设置为true,可以再次修改特性。
  41. Object.defineProperty(obj,"newKey",{
  42. value:"hello",
  43. writable:false,
  44. enumerable:false,
  45. configurable:true
  46. });
  47. //重新修改特性
  48. Object.defineProperty(obj,"newKey",{
  49. value:"hello",
  50. writable:true,
  51. enumerable:true,
  52. configurable:true
  53. });
  54. console.log( obj.newKey ); //hello

一旦使用 Object.defineProperty 给对象添加属性,那么如果不设置属性的特性,那么configurable、enumerable、writable这些值都为默认的false

存取器描述:get set

不能 同时设置访问器 (get 和 set) 和 wriable 或 value,否则会错,就是说想用(get 和 set),就不能用(wriable 或 value中的任何一个)

注意:get set是加在对象属性上面的,不是对象上面的;赋值或者修改该对象属性,会分别触发get 和 set 方法;

正规用法:

  1. var o = {}; // 不能是O.name=" dudu "了
  2. var val = ‘dudu‘; // o 对象上的属性是其他人家的一个变量
  3. Object.definedProperty(o,‘name‘,{ // Object.definedProperty( ) 方法通过定set get 方法,强行给拉郎配
  4. get:function(){ return val }; //get: return val 把人家变量给返回了,就是人家的人了
  5. set;function(value){ val = value } //set: val = value 把人家变量赋值为传进来的参数,就是人间人了
  6. })

实验性代码:

  1. var O = {};
  2. Object.definedProperty(o,"name",{
  3. set:function(){console.log(‘set‘)}; //在获取对象该属性的时候触发,
  4. get:function(){console.log(‘get‘)}; // 在设置对象该属性的时候触发 , 并不会真正的设置;因为冲突了value,默认是falue
  5. })

所以,你看到这里,基本上就能够明白,通过Object.defineProperty()来重写对象的get, set 方法,就可以在对象属性被访问和修改的时候获知 ,从而触发响应的回调函数,但是同一个数据属性,很可能有多个 watcher 来订阅的 ,所触发的回调函数可能有很多,不可能都写在 get set 里面,我们更希望更通过这样的方式:

  1. var data = {
  2. a: 1,
  3. b: {
  4. c: 2
  5. }
  6. }
  7. observer(data) // 在这里遍历改写了get,set
  8. new Watch(‘a‘, () => {
  9. alert(9)
  10. })
  11. new Watch(‘a‘, () => {
  12. alert(90)
  13. })
  14. new Watch(‘b.c‘, () => {
  15. alert(80)
  16. })

现在的问题是, Watch 构造函数要怎么写?

在 Watch 构造函数里面,我们已经可以获取到 data,当我们访问的时候,就会触发 data 的改写的get 方法:

  1. class Watch {
  2. constructor (exp, fn) {
  3. // ……
  4. data[exp] // 触发了data 身上的get 方法
  5. }
  6. }

当我们每实例化一个 Watch来订阅data上的a属性  , data.a 上的get 方法就会被触发一次, data.a 就多了一个订阅器。那么问题来了,这么多的订阅器watcher,我们肯定希望放在一个数组上进行管理,同时我们还希望有,向数组中 push 新的订阅器watcher的方法,  逐个触发数组中各个watcher的方法等等。这样,我们的data 上的每一个属性,它都有一个数组来放订阅器,都有相应的方法来操作这个数组。根据面向对象中的思想,我们可以把这个数组和操作数组的方法放进一个对象中, 这个对象就叫dep吧 :

  1. dep {
  2. subs: [watcher1,watcher2,watcher3], // subs 属性是一个数组,用来维护众多订阅器
  3. addSubs: function(){ this.subs.push( …… ) },
  4. notify: function() {
  5. for(let i = 0; i< this.subs.length; i++){
  6. this.subs[i].fn()
  7. }
  8. }
  9. }

dep 对象我们希望用构造函数来生成,这样会比较方便:

  1. class Dep {
  2. constructor () {
  3. this.subs = []
  4. }
  5. addSub () {
  6. this.subs.push(……)
  7. }
  8. notify () {
  9. for(let i = 0; i < this.subs.length; i++){
  10. this.subs[i].fn()
  11. }
  12. }
  13. }

接下来,我们要在每一个data 属性上生成一个dep实例对象:

  1. function defineReactive (data, key, val) { // 这个函数就是用来重写对象属性的get set 方法
  2. observer(val) // 递归的调用从而遍历
  3. let dep = new Dep() // 在这里实例化一个dep实例
  4. Object.defineProperty(data, key, {
  5. enumerable: true,
  6. configurable: true,
  7. get: function () {
  8. dep.addSub() //每当有订阅者订阅,我就新增一个
  9. return val
  10. },
  11. set: function (newVal) {
  12. if(val === newVal){
  13. return
  14. }
  15. observer(newVal)
  16. dep.notify() // 新增
  17. }
  18. })
  19. }

等等,在第8行,执行 dep.addSub , 我怎么知道是要push 进去哪个 watcher 呢? 我们需要改写一下 watch 的构造函数:

  1. Dep.target = null //类似于全局变量的一个东西,用来放 这次实例化的watcher
  2. function pushTarget(watch){
  3. Dep.target = watch
  4. }
  5. class Watch {
  6. constructor (exp, fn) {
  7. this.exp = exp
  8. this.fn = fn
  9. pushTarget(this) // 让Dep.target赋值为本次实例化的实例
  10. data[exp] //紧接着就触发get 方法
  11. }
  12. }

被触发的get 方法在下面:

  1. get: function () {
  2. dep.addSub() //好吧,我又被触发了一次,
  3. return val
  4. },

dep.addSub() 方法的庐山真面目:

  1. class Dep {
  2. constructor () {
  3. this.subs = []
  4. }
  5. addSub () {
  6. this.subs.push(Dep.target)
  7. }
  8. notify () {
  9. for(let i = 0; i < this.subs.length; i++){
  10. this.subs[i].fn()
  11. }
  12. }
  13. }

null

时间: 2024-11-11 00:23:54

vue 源码深入学习分析——史上超详细的相关文章

vue源码构建代码分析

这是xue源码学习记录,如有错误请指出,谢谢!相互学习相互进步. vue源码目录为 vue ├── src #vue源码 ├── flow #flow定义的数据类型库(vue通过flow来检测数据类型是否正确) ├── examples #demo ├── scripts #vue构建命令 ├── ... vue内部代码模块比较清晰,这边主要分析scripts内部代码,讲解vue是如何进行构建的.首先你必须要懂一些rollup,vue内部是通过rollup来进行构建的,rollup是一款js的构

vue源码分析:响应式之依赖收集

今天记录下vue源码的响应式的源码学习,第一步,我们想下,为什么为们写一个data.然后里边的数据变化就能映射到dom的变化上.这里用到了Object.defineProperty(obj,key, {}),如果你还不了解建议去jsmdn上仔细了解下,这个函数的目的就是检测你得赋值和取值的过程用来定义自己的行为,比如obj[key]会拿到配置里边的get函数定义,obj[key] = 'xxx'会调用配置中的set.那么vue也是一样的,在get中做了依赖收集(也就是说哪些dom用到了我定义的这

[Vue源码分析] v-model实现原理

最近小组有个关于vue源码分析的分享会,提前准备一下- 前言:我们都知道使用v-model可以实现数据的双向绑定,及实现数据的变化驱动dom的更新,dom的更新影响数据的变化.那么v-model是怎么实现这一原理的呢?接下来探索一下这部分的源码. 前期准备①:vue2.5.2源码(用于阅读.查看关联等)②:建立vue demo,创建包含v-model指令的实例(用于debugger)以下为demo: genDirectives在模板的编译阶段, v-model跟其他指令一样,会被解析到 el.d

Vue源码学习(一)

Vue构建 在scripts/build.js 中,从配置文件scripts/config.js读取配置,再通过命令行参数对构建配置做过滤,这样就可以构建出不同用途(平台:web/weex)的 Vue.js 了. 在scripts/config.js通过resolve查找相关的打包文件. 把自定义的build对象专程rollup所规定的格式,用rollup进行打包. 大专栏  Vue源码学习(一)"headerlink" title="Vue在源码是什么?">

大白话Vue源码系列(01):万事开头难

阅读目录 Vue 的源码目录结构 预备知识 先捡软的捏 Angular 是 Google 亲儿子,React 是 Facebook 小正太,那咱为啥偏偏选择了 Vue 下手,一句话,Vue 是咱见过的最对脾气的 MVVM 框架.之前也使用过 knockout,angular,react 这些框架,但都没有让咱产生 follow 的冲动.直到见到 Vue,简直是一见钟情啊. Vue 的官方文档已经对 Vue 如何使用提供了最好的教程,建议 Vue 新手直接去官网学习,而不要在网上找些质量参差不齐的

深入vue - 源码目录及构建过程分析

 公众号原文链接:深入vue - 源码目录及构建过程分析   喜欢本文可以扫描下方二维码关注我的公众号 「前端小苑」 ?“ 本文主要梳理一下vue代码的目录,以及vue代码构建流程,旨在对vue源码整体有一个认知,有助于后续对源码的阅读.” 一.目录结构 上图是对vue的代码的所有目录进行的梳理,其中源码位于src目录下,下面对src下的目录进行介绍. compiler 该目录是编译相关的代码,即将 template 模板转化成 render 函数的代码. vue 提供了 render 函数,r

对vue源码之缓存的研究--------------引用

探索vue源码之缓存篇 一.从链表说起 首先我们来看一下链表的定义: 链表(Linked list)是一种常见的基础数据结构,是一种线性表,但是并不会按线性的顺序存储数据,而是在每一个节点里存到下一个节点的指针(Pointer) 其中的双向链表是我们今天的主角: 双向链表也叫双链表.双向链表中不仅有指向后一个节点的指针,还有指向前一个节点的指针.这样可以从任何一个节点访问前一个节点,当然也可以访问后一个节点,以至整个链表.一般是在需要大批量的另外储存数据在链表中的位置的时候用. 图示如下 想象一

[Vue源码]一起来学Vue双向绑定原理-数据劫持和发布订阅

有一段时间没有更新技术博文了,因为这段时间埋下头来看Vue源码了.本文我们一起通过学习双向绑定原理来分析Vue源码.预计接下来会围绕Vue源码来整理一些文章,如下. 一起来学Vue双向绑定原理-数据劫持和发布订阅 一起来学Vue模板编译原理(一)-Template生成AST 一起来学Vue模板编译原理(二)-AST生成Render字符串 一起来学Vue虚拟DOM解析-Virtual Dom实现和Dom-diff算法 这些文章统一放在我的git仓库:https://github.com/yzsun

[Vue源码]一起来学Vue模板编译原理(一)-Template生成AST

本文我们一起通过学习Vue模板编译原理(一)-Template生成AST来分析Vue源码.预计接下来会围绕Vue源码来整理一些文章,如下. 一起来学Vue双向绑定原理-数据劫持和发布订阅 一起来学Vue模板编译原理(一)-Template生成AST 一起来学Vue模板编译原理(二)-AST生成Render字符串 一起来学Vue虚拟DOM解析-Virtual Dom实现和Dom-diff算法 这些文章统一放在我的git仓库:https://github.com/yzsunlei/javascrip