Vue Internals①astを中心に　1-1compile前まで

#はじめに
Vueを使ってはいるけど実際どんな操作が行われているかわからない・・・
なので、実際にソースを読んでみることにしました。
静的にソースを読むのではなく、今回はchromeのデバック機能を使って動的に解析していきたいと思います。
動的に見ていく利点としては実際の変数が入った状態で見れるので何をしているかわかりやすい、誤った道筋に飛ばないといった感じです。
![chromeDebug.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/496421/7fa56b29-c65c-18fe-d749-1d2ad14dac22.png)

今回使っていくcodeは上記の画像にある通りのシンプルな奴です、ただシンプルとはいっても処理はとても長いのでやはりソースコードを読むうえでは**気になる処理に絞って想像がつく処理はある程度雑に**読んでいった方がいい気がします。
ただ、記事にするうえで雑にやってしまうと分かりづらい気がするのでこの記事では割と丁寧にやっていきたいと思います。



VueInternalシリーズの大目標としては
①{{message}}はどうやって処理されてる？
②watcherとかcomputedとか
③v-系がどう処理されているか
④diffアルゴリズムについて
の４つを理解することとします。

1.全体の流れでは{{message}}はどうやって処理されてる？の謎が解けます。
2.watcherやらdepやらについてではwatcher周り、
3.v-系の処理を見るではv-系がどう処理されているか、
4.diffアルゴリズムではdiffアルゴリズムについてを目標としてみていきます。

#全体の流れ
![flowOverView.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/496421/8219091f-103d-f5b8-2d0f-1c960c841c23.png)

compile部分がとても長いです。この図の中の大部分がここのast生成部です。
ast部分で

```
"<div id="vue_example">
  {{message}}
</div>"
```
を

```
Object
type: 1
tag: "div"
attrsList: Array(1)
0: {name: "id", value: "vue_example", start: 5, end: 21}
length: 1
__proto__: Array(0)
attrsMap: {id: "vue_example"}
rawAttrsMap:
id: {name: "id", value: "vue_example", start: 5, end: 21}
__proto__: Object
parent: undefined
children: Array(1)
0: {type: 2, expression: ""\n  "+_s(message)+"\n"", tokens: Array(3), text: "↵  {{message}}↵", start: 22, …}
length: 1
__proto__: Array(0)
start: 0
end: 43
plain: false
attrs: Array(1)
0: {name: "id", value: ""vue_example"", dynamic: undefined, start: 5, end: 21}
length: 1
__proto__: Array(0)
__proto__: Object
```
みたいにobjectにして、generateで

```
{render: "with(this){return _c('div',{attrs:{"id":"vue_example"}},[_v("\n  "+_s(message)+"\n")])}"
```
renderFunctionを作り、最後にこの関数を使ってmountComponentでrealDomを作ります。

抑えるところとしてはast生成、generate、最後のmountComponentが重要になっていきます。

# new Vue~compile到達まで

```js
function Vue (options) {
   //optionsの中身は
　　el: "#vue_example"
　　data:
　　　　message: "Hello Vue.js!"//
      
    if (!(this instanceof Vue)
    ) {
      warn('Vue is a constructor and should be called with the `new` keyword');
    }
    this._init(options);
  }
```

```js
function initMixin (Vue) {
  Vue.prototype._init = function (options) {
    var vm = this;
    // a uid
    vm._uid = uid$3++;

    var startTag, endTag;
    /* istanbul ignore if */
    if (config.performance && mark) {
      startTag = "vue-perf-start:" + (vm._uid);
      endTag = "vue-perf-end:" + (vm._uid);
      mark(startTag);
    }

    // a flag to avoid this being observed
    vm._isVue = true;
    // merge options
    if (options && options._isComponent) {
      // optimize internal component instantiation
      // since dynamic options merging is pretty slow, and none of the
      // internal component options needs special treatment.
      initInternalComponent(vm, options);
    } else {
      vm.$options = mergeOptions(
        resolveConstructorOptions(vm.constructor),
        options || {},
        vm
      );
    }
    /* istanbul ignore else */
    {
      initProxy(vm);
    }
    // expose real self
    vm._self = vm;
    initLifecycle(vm);
    initEvents(vm);
    initRender(vm);
    callHook(vm, 'beforeCreate');
    initInjections(vm); // resolve injections before data/props
    initState(vm);
    initProvide(vm); // resolve provide after data/props
    callHook(vm, 'created');

    /* istanbul ignore if */
    if (config.performance && mark) {
      vm._name = formatComponentName(vm, false);
      mark(endTag);
      measure(("vue " + (vm._name) + " init"), startTag, endTag);
    }

    if (vm.$options.el) {
      vm.$mount(vm.$options.el);
    }
  };
}
```

initでやることはvm=thisにmountで必要な情報を追加してあげることです。
initに入った直後
![init vm=this.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/496421/4e7e6a25-1194-7c5e-c517-ff564ca3ab5d.png)

mountに入る前
![init beforeMount.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/496421/9063d9b6-fe7f-4107-81ed-ced7cd85125d.png)

まずはmergeOptionから見ていきます。
resolveConstructorOptionsはnew　VueにsuperClassがある場合なので今回は何もしません。
mergeOptionの役割はparentとchildがそれぞれobjectなのでそのobjectのkeyを使ってあらかじめ用意したmapであるstratsから関数を取り出して,実行しその返り値をoptionに加えることです。

```js
/**
 * Merge two option objects into a new one.
 * Core utility used in both instantiation and inheritance.
 */
function mergeOptions (
  parent,
  child,
  vm
) {
  
  var options = {};
  var key;
  for (key in parent) {
    mergeField(key);
  }
  for (key in child) {
    if (!hasOwn(parent, key)) {
      mergeField(key);
    }
  }
  function mergeField (key) {
    var strat = strats[key] || defaultStrat;
    options[key] = strat(parent[key], child[key], vm, key);
  }
  return options
}
```
例えばparentのcomponentを例としてみるとmergeFieldのstrats[component]はmergeAssetsなので、
option[component]にはmergeAssetsの返り値が入ります。![mergeOptions example.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/496421/ea737fbc-a189-f1f2-e0bc-abb2abc49222.png)

childのelとdataをkeyとしてstratsから手に入れる関数は特殊なので注意、
starts.elによってoptionにel: "#vue_example"、
strats.dataによってdata: ƒ mergedInstanceDataFn()が追加されます

strats.el
![strats.elの場合はちょっと特殊.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/496421/082223a3-dd09-312e-9025-1a4470b59405.png)
![strats.el2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/496421/b8f445b8-b59d-850b-3911-396f2ab768b2.png)

strats.data
![strats.data1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/496421/c2bc258b-58fb-c5a5-461c-5bdfe1a49ecf.png)
![strats.data2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/496421/f9ee0c68-1889-5acc-de12-d06b2a7a363c.png)

mergedOptionの結果としてel,dataがvmに追加されます。

次にinitProxyを見ます。

```js
 initProxy = function initProxy (vm) {
    if (hasProxy) {
      // determine which proxy handler to use
      const options = vm.$options
      const handlers = options.render && options.render._withStripped
        ? getHandler
        : hasHandler //ここではhasHandler
      vm._renderProxy = new Proxy(vm, handlers)
    } else {
      vm._renderProxy = vm
    }
  }
```

```js

const hasHandler = {
    has (target, key) {
      const has = key in target
      const isAllowed = allowedGlobals(key) ||
        (typeof key === 'string' && key.charAt(0) === '_' && !(key in target.$data))
      if (!has && !isAllowed) {
        if (key in target.$data) warnReservedPrefix(target, key)
        else warnNonPresent(target, key)
      }
      return has || !isAllowed
    }
  }
```
**補足　proxyについて**
proxyはobjectのgetとかsetの振る舞いをhandlerを渡してそのhandlerの通りに動かしたい時に使います。
例えば例として

```js
let obj = { name:"stonelike",value:1};
const handler = {
 has(target,key){// key　in　target
    const has = key in target;
    return has;
 }
}
let proxy = new Proxy(obj,handler);//objをwrapしたproxyができる
//今回はhandlerにhasを指定しているので　?　in　proxyみたいにinを使ったときにhandlerが起動します。
//example
console.log(name in proxy);//true,objにnameがあるので1
console.log(adadad in proxy)l//false

```
なので、vmに追加される_renderProxyは ? in vm._renderProxyとなったときにhasが起動してチェックが入ります、これはmountCOmponentで使われるのでまだまだ先ですが・・・
**補足終わり**

initに戻って、initLifeCycleとinitEventは特に大したことはやっていないので飛ばしてinitRenderを見ていきます。

```js
function initRender (vm) {
  vm._vnode = null; // the root of the child tree
  vm._staticTrees = null; // v-once cached trees
  var options = vm.$options;

  vm._c = function (a, b, c, d) { return createElement(vm, a, b, c, d, false); };
  
  vm.$createElement = function (a, b, c, d) { return createElement(vm, a, b, c, d, true); };

 /* istanbul ignore else */
    {
      defineReactive$$1(vm, '$attrs', parentData && parentData.attrs || emptyObject, function () {
        !isUpdatingChildComponent && warn("$attrs is readonly.", vm);
      }, true);
      defineReactive$$1(vm, '$listeners', options._parentListeners || emptyObject, function () {
        !isUpdatingChildComponent && warn("$listeners is readonly.", vm);
      }, true);
    }
}
```
ここのvm._cはmountComponentで使います。

そして重要なのはdefineReactive$$1です。やりたいことはproxyと同じくgetとかsetのcustomなんですが、それとともにwatcherと連携させるようにしたいので、例えば$attrにsetするとき、customSetの中でdep.notify()が発火,そしてwatchが起動してre-renderするようにしてあげます。こうして名前の通り、Reactiveとしています。watcherとdepの関係など詳しくはwatcherの章でやります。
![defineReactive1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/496421/95ca3bf6-4208-833e-b084-134859cdb158.png)

definePropertyですが、objectにpropertyを追加し、そのpropertyに対してproxyでやった通りにget,setなどをcustomするときに使います、$attrをObservableにしている感じですね。
ここではvmに$attrなどを追加しています。

**definePropertyの参考**
https://qiita.com/saka_pon/items/42f30cf4983822240398

callHookはbeforeCreateとかを呼ぶためのものです。今回は何も書いていないので飛ばします。
initInjectionも飛ばして、initStateへ、ここでvmにdataがsetされるのでここから先で初めてdataにアクセス可能となります。

```js

function initState (vm) {
  vm._watchers = [];
  var opts = vm.$options;
  if (opts.props) { initProps(vm, opts.props); }
  if (opts.methods) { initMethods(vm, opts.methods); }
  if (opts.data) {
    initData(vm);
  } else {
    observe(vm._data = {}, true /* asRootData */);
  }
  if (opts.computed) { initComputed(vm, opts.computed); }
  if (opts.watch && opts.watch !== nativeWatch) {
    initWatch(vm, opts.watch);
  }
}
```

ここでvar data = vm.$options.data;はstrats.dataで手に入れたmergedInstanceDataFnで、getDataではこの関数が実行されてdata,vm_dataに返り値が入ります。
data = vm._data = {message: "Hello Vue.js!"}

getData以降は
一つはdataのkey、ここではmessageのみに対してvmからproxyを使ってショートカットを作ります。
vm._data.messageではなく、vm.messageでも値がとれる、といった感じです。![proxy.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/496421/c723eb89-7dd1-e827-e7a9-1483fefcab22.png)


二つ目は$attrと同じようにdataをobservableにします。
これはmessageが"aaaa"みたいになったら{{message}}の部分も"aaaa"になってほしいからです。



```js
function initData (vm) {
  var data = vm.$options.data;
  data = vm._data = typeof data === 'function'
    ? getData(data, vm)
    : data || {};
  if (!isPlainObject(data)) {
    data = {};
    process.env.NODE_ENV !== 'production' && warn(
      'data functions should return an object:\n' +
      'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',
      vm
    );
  }
  // proxy data on instance
  var keys = Object.keys(data);
  var props = vm.$options.props;
  var methods = vm.$options.methods;
  var i = keys.length;
  while (i--) {
    var key = keys[i];
    if (process.env.NODE_ENV !== 'production') {
      if (methods && hasOwn(methods, key)) {
        warn(
          ("Method \"" + key + "\" has already been defined as a data property."),
          vm
        );
      }
    }
    if (props && hasOwn(props, key)) {
      process.env.NODE_ENV !== 'production' && warn(
        "The data property \"" + key + "\" is already declared as a prop. " +
        "Use prop default value instead.",
        vm
      );
    } else if (!isReserved(key)) {
      proxy(vm, "_data", key);
    }
  }
  // observe data
  observe(data, true /* asRootData */);
}
```
![mergedInstanceData.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/496421/aad2d464-60ea-e38d-5cb5-00b58f1b0d9a.png)

![proxy.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/496421/b20307ed-4b0e-6c7c-0780-6f42634f34b8.png)

observeを見ていきます。
![observe1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/496421/294fe8ba-8cea-81ea-9071-b7af4e13e975.png)

new Observer
defによってvalueに_ob_を作り、walkでvalueのkey一つ一つをObservableにしていきます。

![Observer1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/496421/3e70fde0-dc32-9294-d0d2-b6d601d76932.png)

```js

function def (obj, key, val, enumerable) {
    Object.defineProperty(obj, key, {
      value: val,
      enumerable: !!enumerable,
      writable: true,
      configurable: true
    });
  }
```

```js
Observer.prototype.walk = function walk (obj) {
  var keys = Object.keys(obj);
  for (var i = 0; i < keys.length; i++) {
    defineReactive$$1(obj, keys[i]);
  }
};
```
複雑だったのでinitStateでやったことをまとめると、結論から言えば今回の例でいえばmessageをObservable化している。
まず、data = vm._data = {message: "Hello Vue.js!"}とした。
次にvm.messageからvm._data.messageにアクセスできるようになり、
observeでdata、つまりvm._dataのkey一つ一つをwalkでObservableにした。
ここで、vm.messageを変更しようとすると、vm._data.messageにアクセスすることになり、これはObservableになっているので、Reactiveになっている。

initStateにもどると、残りにinitComputedとinitWatchがあるが、今回は使わないので飛ばして
initに戻り、initProvideも大したことはやっていないのでmountへ進む


mount
ここでelは#vue_example
query(el)でelに本当のdomが入る,そしてgetOuterHTML(el)で

```template="<div id="vue_example">
  　　　　　{{message}}
　　　　　</div>"```
となる


```js
Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component {
  el = el && query(el)


  const options = this.$options

  template = getOuterHTML(el)
    
  

      const { render, staticRenderFns } = compileToFunctions(template, {
        outputSourceRange: process.env.NODE_ENV !== 'production',
        shouldDecodeNewlines,
        shouldDecodeNewlinesForHref,
        delimiters: options.delimiters,
        comments: options.comments
      }, this)
      options.render = render
      options.staticRenderFns = staticRenderFns

      
    }
  }
  return mount.call(this, el, hydrating)
}
```

```js
export function query (el: string | Element): Element {
  if (typeof el === 'string') {
    const selected = document.querySelector(el)
    if (!selected) {
      process.env.NODE_ENV !== 'production' && warn(
        'Cannot find element: ' + el
      )
      return document.createElement('div')
    }
    return selected
  } else {
    return el
  }
}
```
![template.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/496421/dec9fb03-cc4c-e55c-cfa3-fcf5c93c0380.png)

やっとcompile前にたどり着いたので次からcompileを見ていきたいと思います。