背景
id
, class
も振らずにどうやってレンダリングしてるんだろ?と思い、どんな実装になってるのか調べてみた。jQuery
などでDOM
操作してた感覚からすると不思議だったので。
Vueのバージョンは2.5.17
になります。
<!DOCTYPE html>
<script src="./vue2.5.17.js"></script>
<div id="app"></div>
<script>
const vm = new Vue({
template: '<p>{{ msg }}</p>',
data: { msg: 'hello world!' }
}).$mount('#app')
</script>
このように実装してみて、defineReactive
にブレークポイント設定してみると以下の順に処理されてることがわかった。
Vue
function Vue (options) {
// options = {template: "<p>{{ msg }}</p>", data: {…}}
...
this._init(options);
}
_init
Vue.prototype._init = function (options) {
// vm = Vue {_uid: 0, _isVue: true, $options: {…}, _renderProxy: Proxy, _self: Vue, …}
var vm = this;
...
initState(vm);
...
};
initState
function initState (vm) {
...
// opts = {components: {…}, directives: {…}, filters: {…}, _base: ƒ, template: "<p>{{ msg }}</p>", …}
var opts = vm.$options;
...
if (opts.data) {
initData(vm);
}
...
}
initData
function initData (vm) {
// data = {msg: "hello world!", __ob__: Observer}
var data = vm.$options.data;
data = vm._data = typeof data === 'function'
? getData(data, vm)
: data || {};
...
observe(data, true /* asRootData */);
}
observe
function observe (value, asRootData) {
...
var ob;
if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
ob = value.__ob__;
} else if (
shouldObserve &&
!isServerRendering() &&
(Array.isArray(value) || isPlainObject(value)) &&
Object.isExtensible(value) &&
!value._isVue
) {
// value = {msg: "hello world!", __ob__: Observer}
ob = new Observer(value);
}
...
return ob
}
Observer
var Observer = function Observer (value) {
this.value = value;
...
if (Array.isArray(value)) {
...
} else {
// value = {msg: "hello world!", __ob__: Observer}
this.walk(value);
}
};
walk
Observer.prototype.walk = function walk (obj) {
// obj = {msg: "hello world!", __ob__: Observer}
var keys = Object.keys(obj); // keys = ["msg"]
for (var i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i]);
}
};
defineReactive
function defineReactive (
obj, // obj = {msg: "hello world!", __ob__: Observer}
key, // key = "msg"
val,
customSetter,
shallow
) {
var dep = new Dep(); // dep = Dep {id: 3, subs: Array(0)}
// property = {value: "hello world!", writable: true, enumerable: true, configurable: true}, obj = {msg: "hello world!", __ob__: Observer}, key = "msg"
var property = Object.getOwnPropertyDescriptor(obj, key);
if (property && property.configurable === false) {
return
}
// cater for pre-defined getter/setters
var getter = property && property.get;
if (!getter && arguments.length === 2) {
val = obj[key];
}
var setter = property && property.set;
var childOb = !shallow && observe(val);
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
var value = getter ? getter.call(obj) : val;
if (Dep.target) {
dep.depend();
if (childOb) {
childOb.dep.depend();
if (Array.isArray(value)) {
dependArray(value);
}
}
}
return value
},
set: function reactiveSetter (newVal) {
var value = getter ? getter.call(obj) : val;
/* eslint-disable no-self-compare */
if (newVal === value || (newVal !== newVal && value !== value)) {
return
}
/* eslint-enable no-self-compare */
if ("development" !== 'production' && customSetter) {
customSetter();
}
if (setter) {
setter.call(obj, newVal);
} else {
val = newVal;
}
childOb = !shallow && observe(newVal);
dep.notify();
}
});
}
defineReactive
でmsg
にゲッター・セッターを持たせてるようです。
そこで、
vm.msg = 'こんにちは世界!'
とすると、defineReactive
で設定したセッターが反応して
defineReactive
dep.notify();
によりウォッチャに通知が行くみたいです。そこでウォッチャがレンダリング関数を呼び出してレンダリングされるようですが、結局このレンダリング関数を読み解かないとid
、class
なしでレンダリングできてる不思議は解決しなさそうだなと思ったところで力尽きました。
この記事は千 Advent Calendar 2018の23日目の予定でしたが、過ぎた上に調べきれてなくすみません。
近々続きを書きたいと思います。