前言
上一篇文章中了解了整个Moon构造函数的逻辑处理,本篇文章主要是去详细分析Moon是如何处理Methods、computed以及它是如何实现双向绑定的,vue.js是通过ES5的defineProperty来实现的,看看二者有何不同。
具体分析
还是通过简单的实例来分析methods、computed等,具体实例如下:
<div id="app">
<p>{{getMsg}}</p>
</div>
<script src="./moon.js"></script>
<script>
new Moon({
el: '#app',
data: {
msg: 'hello world'
},
// 计算属性
computed: {
getMsg: {
get() {
let msg = this.get('msg');
或者
this.$data.msg直接获取
return `js ${msg}`
}
}
},
methods: {
outMsg() {
console.log(this.$data.msg);
}
}
});
首先明确几点:
- 在Js中应用data中的数据必须是this.$data.变量的形式或者通过Moon的get方法
- computed的使用必须写成get、set形式
methods
首先分析Methods,看看Moon是如何处理的,相关代码如下:
var initMethods = function (instance, methods) {
var initMethod = function (methodName, method) {
// 从此处可以看到2点信息:
// 1:methods中定义的方法都会被添加到$data(数据中心)
// 2:使用apply改变原先methods中的this
instance.$data[methodName] = function () {
return method.apply(instance, arguments);
};
};
// 遍历methods
for (var method in methods) {
initMethod(method, methods[method]);
}
};
Moon中methods的调用形式有两种:
- 直接调用,this.$data.方法
- 使用Moon中提供的方式callMethod的形式:Moon实例.callMethod(方法,参数)
computed
计算属性的处理就不会像methods那么简单了,计算属性涉及到双向绑定以及缓存,具体代码如下:
var initComputed = function (instance, computed) {
var setComputedProperty = function (prop) {
// 获取observer对象,观察者
var observer = instance.$observer;
// 调用observe方法,为每一个计算属性创建一个clear清除缓存的函数
observer.observe(prop);
// 使用Object.defineProperty将计算属性定义到$data中并监听变化
Object.defineProperty(instance.$data, prop, {
get: function () {
var cache = null;
// 查找缓存中是否存在指定名称的computed,存在就取缓存中的计算属性
if (observer.cache[prop] === undefined) {
// 将observer.target就是当前计算属性名称
observer.target = prop;
// 调用computed中指定的计算属性,并将结果者赋给cache
cache = computed[prop].get.call(instance);
observer.target = null;
// 保存当前计算结果到oberver.cache对象中
observer.cache[prop] = cache;
} else {
cache = observer.cache[prop];
}
return cache;
},
set: noop
});
var setter = null;
// computed中的计算属性中set方法都在observer.setter中
if ((setter = computed[prop].set) !== undefined) {
observer.setters[prop] = setter;
}
};
// 遍历computed
for (var propName in computed) {
setComputedProperty(propName);
}
};
从上面中可以得到计算属性也是被添加到$data中,计算属性的缓存是通过Observer中的cache属性来处理
双向绑定
Vue是基于数据劫持 + 发布者-订阅者模式实现双向绑定的,你可以使用this.name = name这样的简单形式实现视图更新。
Moon中并没有提供这种形式,它采用的是提供专门的set和get方法主动去触发$data中的数据的响应式变更。
具体看看set、get方法的实现:
- set
Moon.prototype.set = function (key, val) {
// 获取observer观察者对象
var observer = this.$observer;
// resolveKeyPath处理获取指定的属性名,更新$data中的值
var base = resolveKeyPath(this, this.$data, key, val);
var setter = null;
// 检查是否是计算属性,是则调用计算属性的set方法
if ((setter = observer.setters[base]) !== undefined) {
setter.call(this, val);
}
// 通知,清除包含指定属性的所有计算属性的cache(缓存)
observer.notify(base, val);
// 异步队列更新DOM视图
// 实际上是重新调用Moon实例的build方法并执行updated生命周期函数
queueBuild(this);
};
queueBuid
var queueBuild = function (instance) {
if (instance.$queued === false &&
instance.$destroyed === false) {
instance.$queued = true;
setTimeout(function () {
// 实例的build方法,更新DOM
instance.build();
// updated生命周期函数
callHook(instance, 'updated');
instance.$queued = false;
}, 0);
}
};
- get
Moon.prototype.get = function (key) {
// 获取
var observer = this.$observer;
var target = null;
// observer.target表示计算属性的名称(这里主要是用于计算属性初始化过程中的处理逻辑)
if ((target = observer.target) !== null) {
// 构建map,map中存储每一个属性所在的所有计算属性Map
if (observer.map[key] === undefined) {
// key就是$data中的属性,target就是依赖key的计算属性
observer.map[key] = [target];
} else if (observer.map[key].indexOf(target) === -1) {
observer.map[key].push(target);
}
}
return this.$data[key];
};
从上面的实现逻辑中可以知道Moon中双向绑定实际的处理如下:
- 主动调用set方法,实现$data或计算属性的值变化
- get方法获取 d a t a 中数据,而在该方法中核心点是建立计算属性中的 data中数据,而在该方法中核心点是建立计算属性中的 data中数据,而在该方法中核心点是建立计算属性中的data依赖
只要你在computed中调用 d a t a 中变量,就会在 g e t 中建立 data中变量,就会在get中建立 data中变量,就会在get中建立data与computed之间的Map联系,这就是Moon是如何收集computed的方式。
总结
- Moon中对于Methods以及Computed的处理,都添加到data数据中心
- Moon中计算属性采用了Object.defineProperty,普通data中的变量没有采用
- d a t a 中变量通过 M o o n 实例提供的 s e t 来实现数据响应的,使用 g e t 来获取 data中变量通过Moon实例提供的set来实现数据响应的,使用get来获取 data中变量通过Moon实例提供的set来实现数据响应的,使用get来获取data中的数据。
- 计算属性中通过get来建立$data中变量与其的联系,使用Observer观察者对象的map来保存
- 通过set更新 d a t a 中数据之后会重新渲染,再次渲染时拿到的就是更新后的 data中数据之后会重新渲染,再次渲染时拿到的就是更新后的 data中数据之后会重新渲染,再次渲染时拿到的就是更新后的data,即双向绑定