# 阅读vue源码经验

  1. 为啥beforeCreate之前拿不到data

因为callHook('beforeCreate')在initState之前执行

  1. $on,$off,$emit的实现

class Event {
  constructor() {
      this._events = {}
  }
  $on(event, fn) {
      if(Array.isArray(event)) {
          for(let i = 0,l = event.length; i < l ; i++) {
              this.$on(event[i],fn);
          }
      } else {
          (this._events[event] || (this._events[event] = [])).push(fn)
      }
      return this
  }
  $once(event, fn) {
      const self = this
      function on() {
          this.$off(event, on)
          fn.apply(self, arguments)
      }
      on.fn = fn;
      this.$on(event,on)
      return this
  }
  $emit(event) {
      const cbs = this._events[event]
      const args = Array.prototype.slice.call(arguments,1)
      if(cbs) {
         for(var i = 0, l = cbs.length; i < l ;i++ ){
            cbs[i](args)
         }
      }
      return this
  }
  $off(event,fn){
    //   没有参数,取消所有事件
    if(!arguments.length) {
        this._events = {}
        return this
    }
    // 多个事件名,同一个事件
    // $on(['say','he'], function(){ console.log(1)})
    if(Array.isArray(event) ){
        for(var i$1 = 0, l = event.length; i$1 < l; i$1 ++) {
            this.$off(event[i$1], fn)
        }
        return this
    }
    //   没有找到事件
    var cbs = this._events[event];
    if (!cbs) {
        return this
    }
    // 
    if (!fn) {
        this._events[event] = null;
        return this
    }

    var cb;
    var i = cbs.length;
    while (i--) {
        cb = cbs[i];
        if (cb === fn || cb.fn === fn) {
            cbs.splice(i, 1);
            break
        }
    }
    return this

  }
}


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
  1. diff算法
  2. 父子组件挂载顺序
    父---beforeCreate
    父---created
    父---beforeMount
    子---beforeCreate
    子---created
    子---beforeMount
    孙---beforeCreate
    孙---created
    孙---beforeMount
    孙---mounted
    子---mounted
    父---mounted
    父---beforeDestroy
    子---beforeDestroy
    孙---beforeDestroy
    孙---destroyed
    子---destroyed
    父---destroyed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
  1. render函数到底是啥
<div id="app">
   <span ref="span" @click="setName"> {{name}}</span>
</div>

<script>
    (function anonymous() {
        with(this){
            return _c(
                'div',
                {
                    attrs:{
                        "id":"app"
                    }
                },
                [_c(
                    'span',
                    {
                        ref:"span",
                        on:{
                            "click":setName
                        }
                    },
                    [_v(" " + _s(name))])
                    ]
            )
        }
    })
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
  1. 一个组件一个Watcher实例
function mountComponent() {
     new Watcher(vm, updateComponent, noop, {
      before: function before () {
        if (vm._isMounted && !vm._isDestroyed) {
          callHook(vm, 'beforeUpdate');
        }
      }
    }, true /* isRenderWatcher */);
}

1
2
3
4
5
6
7
8
9
10
  1. 依赖收集是何时进行的 执行render函数,触发依赖收集
(function anonymous() {
    with(this){return _c('div',[_v(_s(name)+" "),_c('router-view')],1)}
})
1
2
3
  1. v-model的实现

这个HTML代码<div id="app"><input type="text" v-model="myname"></div>经过下面编译后

addProp(el, 'value', ("(" + value + ")"));
addHandler(el, event, code, null, true);
genDefaultModel (vue2.6.12.js:7571)
model (vue2.6.12.js:7445)
genDirectives (vue2.6.12.js:11298)
genData$2 (vue2.6.12.js:11206)
genElement (vue2.6.12.js:11075)
genNode (vue2.6.12.js:11484)
(anonymous) (vue2.6.12.js:11447)
genChildren (vue2.6.12.js:11447)
genElement (vue2.6.12.js:11078)
generate (vue2.6.12.js:11043)
baseCompile (vue2.6.12.js:11943)
compile (vue2.6.12.js:11914)
compileToFunctions (vue2.6.12.js:11797)
Vue.$mount (vue2.6.12.js:12027)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

最终生成的代码如下

(function anonymous() {
    with(this) {
        return _c(
            'div', {
                attrs: {
                    "id": "app"
                }
            }, [_c('input', {
                directives: [{
                    name: "model",
                    rawName: "v-model",
                    value: (myname),
                    expression: "myname"
                }],
                attrs: {
                    "type": "text"
                },
                domProps: {
                    "value": (myname)
                },
                on: {
                    "input": function ($event) {
                        if ($event.target.composing) return;
                        myname = $event.target.value
                    }
                }
            })])
    }
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

所有说 v-model<input :value="myname" type="text" @input="myname=$event">

  1. 数组的哪几个方法为啥能触发视图更新
  var methodsToPatch = [
    'push',
    'pop',
    'shift',
    'unshift',
    'splice',
    'sort',
    'reverse'
  ];

  methodsToPatch.forEach(function (method) {
    // cache original method
    var original = arrayProto[method];
    def(arrayMethods, method, function mutator () {
      var args = [], len = arguments.length;
      while ( len-- ) args[ len ] = arguments[ len ];

      var result = original.apply(this, args);
      var ob = this.__ob__;
      var inserted;
      switch (method) {
        case 'push':
        case 'unshift':
          inserted = args;
          break
        case 'splice':
          inserted = args.slice(2);
          break
      }
      if (inserted) { ob.observeArray(inserted); }
      // notify change
      ob.dep.notify();
      return result
    });
  });
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
  1. 事件修饰符once的实现 <div id="app"><input :value="myname" type="text" @input.once="change"> </div> 经过下面编译过程
add$1 (vue2.6.12.js:7653)

createOnceHandler(event.name, cur, event.capture); (vue2.6.12.js:7603)
updateListeners (vue2.6.12.js:2282) 
updateDOMListeners (vue2.6.12.js:7683)
invokeCreateHooks (vue2.6.12.js:6153)
createElm (vue2.6.12.js:6040)
createChildren (vue2.6.12.js:6137)
createElm (vue2.6.12.js:6038)
patch (vue2.6.12.js:6610)
Vue._update (vue2.6.12.js:4011)
updateComponent (vue2.6.12.js:4134)
get (vue2.6.12.js:4557)
Watcher (vue2.6.12.js:4545)
mountComponent (vue2.6.12.js:4141)
Vue.$mount (vue2.6.12.js:9146)
Vue.$mount (vue2.6.12.js:12046)
(anonymous) (v-model的实现.html:29)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

input.once事件编译为


handler = original._wrapper = function (e) {
    if (
        // no bubbling, should always fire.
        // this is just a safety net in case event.timeStamp is unreliable in
        // certain weird environments...
        e.target === e.currentTarget ||
        // event is fired after handler attachment
        e.timeStamp >= attachedTimestamp ||
        // bail for environments that have buggy event.timeStamp implementations
        // #9462 iOS 9 bug: event.timeStamp is 0 after history.pushState
        // #9681 QtWebEngine event.timeStamp is negative value
        e.timeStamp <= 0 ||
        // #9448 bail if event is fired in another document in a multi-page
        // electron/nw.js app, since event.timeStamp will be using a different
        // starting reference
        e.target.ownerDocument !== document
    ) {
        return original.apply(this, arguments)
    }
    };
}

target$1.addEventListener(
      name,
      handler,
      supportsPassive
        ? { capture: capture, passive: passive }
        : capture
    );

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
  • 执行input事件 触发handler事件
  • handler触发 original.apply(this, arguments)
  • 触发onceHandler事件
 function onceHandler () {
      var res = handler.apply(null, arguments);
      if (res !== null) {
        remove$2(event, onceHandler, capture, _target);
      }
    }
1
2
3
4
5
6
  • 该onceHandler事件执行后,把当前DOM绑定的事件进行移除,从而实现执行一次的效果
  1. 事件修饰符.sync的实现
会被编译为 

v-bind="title"

this.$emit('update:tilte','xxx')

@update="title=$event"
1
2
3
4
5
6
7
  1. nextTick的实现



1. <span ref="span" @click="setName"> {{name}}</span> 点击触发setName
2. 调用对应事件,对应事件
1
2
3
4
5

setName() { this.name=Math.random() console.log('. :>> ', this.$el.textContent);

        this.$nextTick(() => {
            console.log('thinextTicks. :>> ', this.$el.textContent);
        })
    }

3. 触发 this.$nextTick(() => { console.log('thinextTicks. :>> ', this.$el.textContent);})
4. 触发 Vue.prototype.$nextTick = function (fn) { return nextTick(fn, this)  };
5. 触发 function nextTick (cb, ctx) {}
        
     
1
2
3
4
5
6
  1. Vue.set 和 this.$set

this.$set(this,'name',100) 通过对新值进行defineReactive()后再触发 ob.dep.notify() 触发视图更新

  1. 计算属性的依赖收集

代码实例

<div id="app">{{pName}}</div>
<script>
  var vm = new Vue({
    el:'#app',
    data() {
        return {
            name:'2'
        }
    },
    computed:{
        pName() {
            return this.name + 'ppppp'
        }
    },
    created() {
        setTimeout(() => {
            this.name = 4
        }, 2000)
    },
})

</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

实例化过程


initComputed (vm, {
        pName() {
            return this.name + 'ppppp'
        }
})


  var watchers = vm._computedWatchers = Object.create(null);
  watchers[key] = new Watcher(
    vm,
    getter || noop,
    noop,
    computedWatcherOptions
  );


  defineComputed(vm, key, userDef);



Object.defineProperty(target, key, sharedPropertyDefinition);

// userDef = pName() {
//             return this.name + 'ppppp'
//         }
sharedPropertyDefinition.get = createGetterInvoker(userDef)

  function createComputedGetter (key) {
    return function computedGetter () {
      var watcher = this._computedWatchers && this._computedWatchers[key];
      if (watcher) {
        if (watcher.dirty) {
          watcher.evaluate();
        }
        if (Dep.target) {
          watcher.depend();
        }
        return watcher.value
      }
    }
  }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42

依赖收集调用栈


 Object.defineProperty(vm, 'pName', {
    configurable: true,
    enumerable: true,
    get:computedGetter
 });


//  reactiveGetter (vue.js:1050)    // 
//  proxyGetter (vue.js:4627)        // 触发对name 的读取
//  pName (computed.html:17)        // 执行  pName() {return this.name + 'ppppp'} 函数
//  get (vue.js:4478)              // this.getter.call(vm, vm);
//  evaluate (vue.js:4583)         // 获取pName的值
//  computedGetter (vue.js:4832)   // 触发computedGetter
//  (anonymous) (VM452:3)          // 调用render函数, 执行 this.pName
//  Vue._render (vue.js:3551)      // 
//  updateComponent (vue.js:4067)  // 
//  get (vue.js:4478)                
//  Watcher (vue.js:4467)           // 实例化组件实例的watcher
//  mountComponent (vue.js:4074)    // 挂载组件
//  Vue.$mount (vue.js:9061)       // 开始挂载组件
//  Vue.$mount (vue.js:11961)      // 生成render函数
//  Vue._init (vue.js:5012)       // 初始化实例
//  Vue (vue.js:5078)
//  (anonymous) (computed.html:8) // 实例化Vue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

# Vuex经验

  1. Vue.use(Vuex)

  2. Vuex.install(Vue)

  3. applyMixin(Vue)

  4. Vue.mixin({ beforeCreate: vuexInit })

  function vuexInit () {
    var options = this.$options;
    // store injection
    // 根组件注入
    if (options.store) {
      this.$store = typeof options.store === 'function'
        ? options.store()
        : options.store;
    } else if (options.parent && options.parent.$store) {
        获取父组件的store实例
      this.$store = options.parent.$store;
    }
  }
1
2
3
4
5
6
7
8
9
10
11
12
13
  1. this.$store = options.store || options.parent.$store;

  2. new Vue({ el: '#app', store })

  3. 触发 beforeCreate, 如果没有store就执行挂载,否则从父组件继承store

  4. 渲染使用了Store里的数据的组件

  5. 触发组件的计算属性

 Vue.component('a-a' ,{
    template: '<div><h1>我是A{{count}}</h1> <button @click="add()"> 加</button></div>',
    computed: {
        count () {
            return this.$store.state.count
        }
    },
    methods: {
        add() {
            this.$store.commit('add', 1000)
        }
    },
})
1
2
3
4
5
6
7
8
9
10
11
12
13
  1. 触发Store的 this.$store.state
  2. 触发
  prototypeAccessors$1.state.get = function () {
    return this._vm._data.$$state
  };
1
2
3
  1. 触发 Object.defineProperty(obj, '$$state', {get() {}})
  2. 进一步读取this.$store.state.count触发 childOb.dep.depend();
  3. 进行依赖收集
  4. 通过commit 触发state变更,触发computed
  store._vm = new Vue({
    data: {
      $$state: state
    },
    computed: computed
  });
1
2
3
4
5
6

# VueRouter笔记

  1. Vue.use(VueRouter) - 全局混入beforeCreate钩子

          Vue.mixin({
            beforeCreate () {
                if (isDef(this.$options.router)) {
                    this._routerRoot = this
                    this._router = this.$options.router
                    this._router.init(this)
                    Vue.util.defineReactive(this, '_route', this._router.history.current)
                } else {
                    this._routerRoot = (this.$parent && this.$parent._routerRoot) || this
                }
                registerInstance(this, this)
            },
            destroyed () {
                registerInstance(this)
            }
        })
    
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
  2. var router = new VueRouter()

  3. 执行根new Vue的时候,执行混入的 beforeCreate钩子,并做四件事

    • 把当前应用的根组件的_routerRoot设置为根组件的实例
    • 把当前应用的根组件的_router设置为用户传入的router实例
    • 调用router.init初始化router
    • 设置当前应用的根组件的_route为响应式
new Vue({
    el:'#app',
    router
})
1
2
3
4
  1. this._router.init(this)

  2. 执行Vue.util.defineReactive(this, '_route', this._router.history.current)

  3. 调用render函数

(function anonymous() {
    with(this){return _c('div',[_v(_s(name)+" "),_c('router-view')],1)}
})
1
2
3
  1. 渲染router-view 组件

  2. 调用RouterView组件的render方法

  3. 获取var route = parent.$route;,触发上面第5步defineReactive$$1定义的_route的get函数

  4. 调用get方法做依赖收集,此时的Dep.target是根Vue的实例

  5. 当调用this.$router.push会触发transitionTo事件,从而触发第5步响应式_route的改变,从而触发dep.notify();

  6. 触发subs[i].update()

  7. 触发 queueWatcher(this);

# debug记录




// render (router.js:324)
// createFunctionalComponent (vue2.6.10.js:3065)
// createComponent (vue2.6.10.js:3238)
// _createElement (vue2.6.10.js:3422)
// createElement (vue2.6.10.js:3360)
// vm._c (vue2.6.10.js:3491)
// (anonymous) (VM963:3)
// Vue._render (vue2.6.10.js:3545)
// updateComponent (vue2.6.10.js:4061)
// get (vue2.6.10.js:4472)
// Watcher (vue2.6.10.js:4461)
// mountComponent (vue2.6.10.js:4068)
// Vue.$mount (vue2.6.10.js:9038)
// Vue.$mount (vue2.6.10.js:11923)
// Vue._init (vue2.6.10.js:5006)
// Vue (vue2.6.10.js:5072)
// (anonymous) (15路由debug.html:29)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21