Vue Internals①astを中心に 1-4mountComponent

#はじめに
前回でgenerateが終わり、compileまでが終わりました。
今回はmountComponentでどのように実際のdomが出来上がっていくかを見ていきたいと思います。

#mount.callから
まずmount.call(this, el, hydrating)のthisはvmで、elはdiv#vue_exampleです。
vm

```
$attrs: (...)
$listeners: (...)
message: (...)
$data: (...)
$props: (...)
_uid: 0
_isVue: true
$options: {components: {…}, directives: {…}, filters: {…}, el: "#vue_example", _base: ƒ, …}
_renderProxy: Proxy {_uid: 0, _isVue: true, $options: {…}, _renderProxy: Proxy, _self: Vue, …}
_self: Vue {_uid: 0, _isVue: true, $options: {…}, _renderProxy: Proxy, _self: Vue, …}
$parent: undefined
$root: Vue {_uid: 0, _isVue: true, $options: {…}, _renderProxy: Proxy, _self: Vue, …}
$children: []
$refs: {}
_watcher: Watcher {vm: Vue, deep: false, user: false, lazy: false, sync: false, …}
_vnode: VNode {tag: "div", data: {…}, children: Array(1), text: undefined, elm: div#vue_example, …}
_staticTrees: null
$vnode: undefined
$slots: {}
$scopedSlots: {}
_c: ƒ (a, b, c, d)
$createElement: ƒ (a, b, c, d)
_watchers: (4) [Watcher, Watcher, Watcher, Watcher]
_data: {__ob__: Observer}
$el: div#vue_example

```
このなかで1-1で作った_data,_renderProxyや、1-3で作ったoptionsのrenderが重要な役割を果たしていきます。

mount.callからmountComponentへ

```js
function mountComponent (
    vm,
    el,
    hydrating
  ) {
    vm.$el = el;
   
    callHook(vm, 'beforeMount');

    var updateComponent;

    updateComponent = function () {
        vm._update(vm._render(), hydrating);
      };
    

    // we set this to vm._watcher inside the watcher's constructor
    // since the watcher's initial patch may call $forceUpdate (e.g. inside child
    // component's mounted hook), which relies on vm._watcher being already defined
    new Watcher(vm, updateComponent, noop, {
      before: function before () {
        if (vm._isMounted && !vm._isDestroyed) {
          callHook(vm, 'beforeUpdate');
        }
      }
    }, true /* isRenderWatcher */);
    hydrating = false;

    // manually mounted instance, call mounted on self
    // mounted is called for render-created child components in its inserted hook
    if (vm.$vnode == null) {
      vm._isMounted = true;
      callHook(vm, 'mounted');
    }
    return vm
  }
```

vm.$elを作り、updateConmonentFunctionを作って、new　Watcherに行く
watcher絡みは詳しくは後でやります。

new　Watcher

```js
var Watcher = function Watcher (
    vm,
    expOrFn,
    cb,
    options,
    isRenderWatcher
  ) {
    this.vm = vm;
    if (isRenderWatcher) {
      vm._watcher = this;
    }
    vm._watchers.push(this);
    
    this.cb = cb;
    this.id = ++uid$2; // uid for batching
    this.active = true;
    this.dirty = this.lazy; // for lazy watchers
    this.deps = [];
    this.newDeps = [];
    this.depIds = new _Set();
    this.newDepIds = new _Set();
    this.expression = expOrFn.toString();
    // parse expression for getter
    if (typeof expOrFn === 'function') {
      this.getter = expOrFn;
    } else {
      this.getter = parsePath(expOrFn);
      if (!this.getter) {
        this.getter = noop;
        warn(
          "Failed watching path: \"" + expOrFn + "\" " +
          'Watcher only accepts simple dot-delimited paths. ' +
          'For full control, use a function instead.',
          vm
        );
      }
    }
    this.value = this.lazy
      ? undefined
      : this.get();
  };
```

やっていることはまずnew　Watch.vmに引数のvmをセットし、vm._watcherにnew　Watchをセットして相互に参照できるようにして、vm,wacthersにnew　Watchをpush
this.expressionに
"function () {
        vm._update(vm._render(), hydrating);
      }"
と関数の文字列を入れて、
this.getterには上の関数そのものを入れる

最後のthis.valueにはthis.get()の返り値が入る

```js
 Watcher.prototype.get = function get () {
    pushTarget(this);
    var value;
    var vm = this.vm;
    try {
      value = this.getter.call(vm, vm);
    } catch (e) {
      if (this.user) {
        handleError(e, vm, ("getter for watcher \"" + (this.expression) + "\""));
      } else {
        throw e
      }
    } finally {
      // "touch" every property so they are all tracked as
      // dependencies for deep watching
      if (this.deep) {
        traverse(value);
      }
      popTarget();
      this.cleanupDeps();
    }
    return value
  };
```

this.getter.callはfunction () {
        vm._update(vm._render(), hydrating);
      }で、callなのでthisはvmとなる。

まずはvm._renderへ

```js
Vue.prototype._render = function () {
      var vm = this;
      var ref = vm.$options;
      var render = ref.render;
      var _parentVnode = ref._parentVnode;

      if (_parentVnode) {
        vm.$scopedSlots = normalizeScopedSlots(
          _parentVnode.data.scopedSlots,
          vm.$slots,
          vm.$scopedSlots
        );
      }

      
      vm.$vnode = _parentVnode;
      // render self
      var vnode;
      try {
       
        currentRenderingInstance = vm;
        vnode = render.call(vm._renderProxy, vm.$createElement);
      } catch (e) {
        handleError(e, vm, "render");
        // return error render result,
        // or previous vnode to prevent render error causing blank component
        /* istanbul ignore else */
        if (vm.$options.renderError) {
          try {
            vnode = vm.$options.renderError.call(vm._renderProxy, vm.$createElement, e);
          } catch (e) {
            handleError(e, vm, "renderError");
            vnode = vm._vnode;
          }
        } else {
          vnode = vm._vnode;
        }
      } finally {
        currentRenderingInstance = null;
      }
      // if the returned array contains only a single node, allow it
      if (Array.isArray(vnode) && vnode.length === 1) {
        vnode = vnode[0];
      }
      // return empty vnode in case the render function errored out
      if (!(vnode instanceof VNode)) {
        if (Array.isArray(vnode)) {
          warn(
            'Multiple root nodes returned from render function. Render function ' +
            'should return a single root node.',
            vm
          );
        }
        vnode = createEmptyVNode();
      }
      // set parent
      vnode.parent = _parentVnode;
      return vnode
    };
  }
```
 vm._renderでthisはvmで、renderにはcompileで作ったrender関数

```js
(function anonymous(
) {
with(this){return _c('div',{attrs:{"id":"vue_example"}},[_v("\n  "+_s(message)+"\n")])}
})
```
が入る

そして vnode = render.call(vm._renderProxy, vm.$createElement);
が実行されるとき↑の無名関数のthisはvm._renderProxyとなる、これは1-1のnew　Proxyで作ったもので、hasHandlerが登録されていた。

だが、このwith(this)とは何でしょうか？
例えばobj.a=5、obj.b=7として、

```js
 with(obj){
   console.log(a)//5
   console.log(b)//7
}

with(c=9){
  console.log(c)//9
}
console.log(c)//undefined
```

with(){}で{}内の変数はwith()で宣言していれば使えるようになる、といったものです。
**参考**
http://blog.tojiru.net/article/197591734.html

さて、withの使い方はわかったので、今回のケースではwith(this=vm._renderProxy)でした。
そもそもvm._renderProxyとはvmをProxyでwrapしたもので、?　in　vm._renderProxyとなった時にhasHandlerが起動するのでした。
問題の関数を簡略化すると

```js
with(vm._renderProxy){
  return _c()
}
```
となります。
ここのどこに　?　in　vm._renderProxyがあるのでしょうか？

例として

```js
const handler1 = {
  has (target, key) {
    console.log(target,key);
    if(key in target){
     return true;
    }else{
     return false;
    }
  }
};

const monster1 = {
  _secret: 'easily scared',
  eyeCount: 4
};

const proxy1 = new Proxy(monster1, handler1);
console.log('eyeCount' in proxy1);//true
console.log("aaa" in proxy1);//false
with(proxy1){
  console.log(eyeCount);
}

//withの結果
Object { _secret: "easily scared", eyeCount: 4 } "console"
Object { _secret: "easily scared", eyeCount: 4 } "eyeCount"
 4
```
上記の結果からwithを使うと変数を　"console"　in　proxy1みたいに探しているみたいです。
なのでhasが処理に割り込むということですね。

なので今回は_c　in　vm._renderProxyとなります。
今回はhasのチェックは_cとか_vとか全部通るので素直に_cとかが何をしているかだけ追っていけば大丈夫です。
では戻ってrender関数を見ていきましょう。

```js
(function anonymous(
) {
with(this){return _c('div',{attrs:{"id":"vue_example"}},[_v("\n  "+_s(message)+"\n")])}
})
```
ここのmessageを取得するときにvm.messageはproxyでvm._data.messageとなることを思い出し、さらにvm._data.messageはObservableという仕組みになっていました。
なのでvm._data.messageの値を取得するときはObject.definePropertyで付けたgetが起動します。

![obdefinemess.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/496421/56e4e08c-da83-b5eb-4744-d4627e2de1ab.png)
dep絡みはまた後でやります。

messageの値を取得した後_s(message)へ,_sはvm._proto_にあるto_String関数のことです。
![_s=toString.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/496421/f1dc2f2c-22b2-fe36-fd84-e3f46d1c3c42.png)
_vはcreateTextVNode関数で、
![_v=createTextVNode.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/496421/2c0fa443-a79b-6007-f546-ec2c0b110f1d.png)
new　VNode
![new VNode.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/496421/0f3dbf88-3c8d-80fc-cb81-aacb94a89385.png)

無名関数に戻って次は_c=createElement
![_c=createElement.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/496421/5c4c4499-7584-0182-72da-82ade1c2f0fb.png)

ここでcontext=vm、tag=div、data=attrs: {id: "vue_example"}、children=_vで作ったVNode

```js
function createElement (
    context,
    tag,
    data,
    children,
    normalizationType,
    alwaysNormalize
  ) {
    if (Array.isArray(data) || isPrimitive(data)) {
      normalizationType = children;
      children = data;
      data = undefined;
    }
    if (isTrue(alwaysNormalize)) {
      normalizationType = ALWAYS_NORMALIZE;
    }
    return _createElement(context, tag, data, children, normalizationType)
  }
```

```js
function _createElement (
    context,
    tag,
    data,
    children,
    normalizationType
  ) {
    
    var vnode, ns;
    if (typeof tag === 'string') {
      var Ctor;
      ns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag);
      if (config.isReservedTag(tag)) {
        // platform built-in elements
        if (isDef(data) && isDef(data.nativeOn)) {
          warn(
            ("The .native modifier for v-on is only valid on components but it was used on <" + tag + ">."),
            context
          );
        }
        //今回はここ！
        vnode = new VNode(
          config.parsePlatformTagName(tag), data, children,
          undefined, undefined, context
        );
      }
    } else {
      // direct component options / constructor
      vnode = createComponent(tag, data, context, children);
    }
    if (Array.isArray(vnode)) {
      return vnode
    } else if (isDef(vnode)) {
      if (isDef(ns)) { applyNS(vnode, ns); }
      if (isDef(data)) { registerDeepBindings(data); }
      return vnode
    } else {
      return createEmptyVNode()
    }
  }
```
_createElementではchildrenのvNodeとattrとtagを使ってvNodeをつくる
作られたvNodeはcontextにvm、dataにattrなど必要な情報が入っている

```
tag: "div"
data: {attrs: {…}}
children: [VNode]
context: Vue {_uid: 0, _isVue: true, $options: {…}, _renderProxy: Proxy, _self: Vue, …}
```
この作られたvNodeがvm._renderの返り値で、watcherのgetterから呼び出したvm._updateへ
```
 updateComponent = function () {
        vm._update(vm._render(), hydrating);
      };
```

vm._updateでは初回と二度目以降で_patch_の引数が違う、ここでvNodeのdiffをやるんですが、その詳細はdiffアルゴリズム編で

今回は初回の動きを見ていきます。
![vm._update.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/496421/cf3b5b71-9111-4e0c-f76c-b52626635a14.png)
初回は_patch_でoldVnodeがdiv#vue_exampleでvnodeがさっきつくったやつ


```js
 function patch (oldVnode, vnode, hydrating, removeOnly) {
          if (isRealElement) {
            
            oldVnode = emptyNodeAt(oldVnode);
          

          // replacing existing element
          var oldElm = oldVnode.elm;
          var parentElm = nodeOps.parentNode(oldElm);

          // create new node
          createElm(
            vnode,
            insertedVnodeQueue,
            // extremely rare edge case: do not insert if old element is in a
            // leaving transition. Only happens when combining transition +
            // keep-alive + HOCs. (#4590)
            oldElm._leaveCb ? null : parentElm,
            nodeOps.nextSibling(oldElm)
          );

          // update parent placeholder node element, recursively
          if (isDef(vnode.parent)) {
            var ancestor = vnode.parent;
            var patchable = isPatchable(vnode);
            while (ancestor) {
              for (var i = 0; i < cbs.destroy.length; ++i) {
                cbs.destroy[i](ancestor);
              }
              ancestor.elm = vnode.elm;
              if (patchable) {
                for (var i$1 = 0; i$1 < cbs.create.length; ++i$1) {
                  cbs.create[i$1](emptyNode, ancestor);
                }
                // #6513
                // invoke insert hooks that may have been merged by create hooks.
                // e.g. for directives that uses the "inserted" hook.
                var insert = ancestor.data.hook.insert;
                if (insert.merged) {
                  // start at index 1 to avoid re-invoking component mounted hook
                  for (var i$2 = 1; i$2 < insert.fns.length; i$2++) {
                    insert.fns[i$2]();
                  }
                }
              } else {
                registerRef(ancestor);
              }
              ancestor = ancestor.parent;
            }
          }

          // destroy old node
          if (isDef(parentElm)) {
            removeVnodes([oldVnode], 0, 0);
          } else if (isDef(oldVnode.tag)) {
            invokeDestroyHook(oldVnode);
          }
        }
      }

      invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch);
      return vnode.elm
    }
  }
```

```js
 function emptyNodeAt (elm) {
      return new VNode(nodeOps.tagName(elm).toLowerCase(), {}, [], undefined, elm)
    }
```
emptyNodeAtでoldVNodeにtag: "div"、elm:div#vue_exampleのVNodeを入れる

nodeOpsは

```
createElement: ƒ createElement$1(tagName, vnode)
createElementNS: ƒ createElementNS(namespace, tagName)
createTextNode: ƒ createTextNode(text)
createComment: ƒ createComment(text)
insertBefore: ƒ insertBefore(parentNode, newNode, referenceNode)
removeChild: ƒ removeChild(node, child)
appendChild: ƒ appendChild(node, child)
parentNode: ƒ parentNode(node)
nextSibling: ƒ nextSibling(node)
tagName: ƒ tagName(node)
setTextContent: ƒ setTextContent(node, text)
setStyleScope: ƒ setStyleScope(node, scopeId)
```
となっていて、document.createElementなどのwrapper？

```js
function parentNode (node) {
    return node.parentNode
  }
```

```js
 function nextSibling (node) {
    return node.nextSibling
  }
```
createElmへ
![createElmでdocument.createElement.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/496421/e84c4386-d111-9934-f12d-a4fe197adcc3.png)
ここのvnode.elm＝createElement()で実際のDOMが作られる(childrenはまだ)

![createElement$1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/496421/90ddbee1-61ce-9ba0-cd87-85617082aa78.png)

setScopeではよくvueを使ってるとdata-v-509622e6みたいになっていたりすると思うんですけど、ここでsetされます。今回はcss使っていないので飛ばします。

createElm続き
![createElm2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/496421/20c8c1bb-104c-ba81-b432-e9622f485924.png)
ここのcreateChildrenでvnode.elmにchildrenをset

createChildren→createElmで、今回はcreateTextNode

```js
 function createElm (
      vnode,
      insertedVnodeQueue,
      parentElm,
      refElm,
      nested,
      ownerArray,
      index
    ) {
      var data = vnode.data;
      var children = vnode.children;
      var tag = vnode.tag;
      if (isDef(tag)) {
        {
      } else if (isTrue(vnode.isComment)) {
    
      } else {
        //今回はここ！insertでparentElm(div)に↵  Hello Vue.js!↵を入れる
        vnode.elm = nodeOps.createTextNode(vnode.text);
        insert(parentElm, vnode.elm, refElm);
      }
    }
```
```js

  function createTextNode (text) {
    return document.createTextNode(text)
  }
```
![insert11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/496421/95926306-8592-cb87-7b70-2c2f91df34e7.png)

createElmに戻ってinvokeCreateHooks→updateAttrs→setAttrs→baseSetAttr→el.setAttributeでvNode.elmにattrsを追加

invokeCreateHooksのcsb.createは

```
create: Array(8)
0: ƒ updateAttrs(oldVnode, vnode)
1: ƒ updateClass(oldVnode, vnode)
2: ƒ updateDOMListeners(oldVnode, vnode)
3: ƒ updateDOMProps(oldVnode, vnode)
4: ƒ updateStyle(oldVnode, vnode)
5: ƒ _enter(_, vnode)
6: ƒ create(_, vnode)
7: ƒ updateDirectives(oldVnode, vnode)
```
となっていて、今回はattrsだけだけど、実際はinvokeCreateHooksでstyleとかdirectiveなどもvNode.elmに追加する,
_enterはtransition関連,createはref

```js
function invokeCreateHooks (vnode, insertedVnodeQueue) {
      for (var i$1 = 0; i$1 < cbs.create.length; ++i$1) {
        cbs.create[i$1](emptyNode, vnode);
      }
      i = vnode.data.hook; // Reuse variable
      if (isDef(i)) {
        if (isDef(i.create)) { i.create(emptyNode, vnode); }
        if (isDef(i.insert)) { insertedVnodeQueue.push(vnode); }
      }
    }
```
![updateAttr.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/496421/1cbf520c-a621-8a91-02d3-ca053959d517.png)
![setAttr.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/496421/25dbccdc-ce5f-c842-fb1b-5eeb4d3a7e1a.png)
![baseSetAttr.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/496421/3914b288-b447-2a76-cb19-fff6ee758ea7.png)

createElmに戻ってparentElmに今まで作ってきたvnode.elmをinsertします。
ここでparentElmはparentElm = nodeOps.parentNode(oldElm)で、oldElmはdiv#vue_exampleのことでした。
なのでparentElmはbodyのことでした。
![insertBody.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/496421/02fab125-4499-ef54-8075-8a5b4a307f00.png)
ここでinsertBeforeをするとbodyにはもともとのdiv#vue_example({{message}})と今作ったvNode(Hello　Vue.js!)が一時的に共存することとなります。

![dualBody.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/496421/ca95bf1c-191f-8f7d-9bc0-7cc56d9e6bc3.png)

ここまででcreateElmがおわりpatchに戻ります。ここからいらなくなってもともとのdiv#vue_exampleを取り除いていきます.

```js
function patch (oldVnode, vnode, hydrating, removeOnly) {
          
           
          // destroy old node
          if (isDef(parentElm)) {
            removeVnodes([oldVnode], 0, 0);
          } else if (isDef(oldVnode.tag)) {
            invokeDestroyHook(oldVnode);
          }
        }
      }

      invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch);
      return vnode.elm
    }
  }
```

removeVNodes→removeAndInVokeRemoveHook→removeNode
![removeVNodess.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/496421/938f9b94-f817-abf9-6901-d03049903735.png)


![removeNode1111.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/496421/cdcda80b-b09c-250b-5698-3d2761c6b68a.png)
removeNodeでbodyからdiv#vue_exampleを取り除きます。
![afterRemoveNode.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/496421/104feaf4-fbbc-df60-985c-042af6d76694.png)
removeVNodesに戻って、invokeDestroyHookではrefやdirectiveを取り除いています。今回は関係ないので飛ばし

Vue._updateに戻ってvm.\$el=vnode.elm=新しく作ったdiv#vue_exampleとなります。
vue.$el._Vue_ = vmとして、$elからVueInstanceが取り出せるように

これで長々としていたWatcherのthis.value=this.get()が終わります。get()の返り値はundefinedです。
ここまでWatcherでやってきたことをまとめると、
まずwatcherとvmをwatcher.vm=vm、vm._watcher=wactherで相互参照できるようにしました。
そこからwatcher.get()でvnode.elmを作って、vm.\$elを元々古いdiv#vue_exampleを指していたのを新しく作ったdiv#vue_exampleにしました。
watcherとvmは相互参照できるので、watcher側からでもきちんと新しいvm.\$elとなります。

ここまでで全体の流れは終わりです。
次回以降はwatcherやdepを見ていきたいと思います。