1.基调
根据《认知天性》的介绍,如果你想让自己有创造力并且记忆深刻的话,在做题的时候就不要去看标准答案,应该给自己一定的困难,在自己弄出一些东西之后再去比对答案,或许你会弄出一些奇奇怪怪的东西,但这确实可能成为你未来灵感的源泉。根据《学习的方法》,不要迷信权威,他因为各种原因必然会犯各种错误,这是无法避免的,不要觉得一个很成熟的东西有很多大牛弄过了,他就是完美无缺的,或许里面充满了各种妥协呢。
2.什么是VUE?
在我的理解中,vue是一个响应式的框架,如果我的页面上的东西不需要变动,那么写html和css就好了,没有必要去折腾什么框架,但是如果页面要变化,昨天油价9元一升,今天就10元了,要改起来就麻烦了,那么有没有办法我给他传多少他油价就显示多少,就像雇一个员工放块白板写上今日油价:9元,明天变了把白板字擦了换了,就行了,我的加油站没有必要拆了重建,这就是vue的作用了。
3.变化侦测
首先,我们雇一个员工,他要做的是油价变了就喊一声告诉所有人,然后要会改白板上的字,于是用上了js里的Object.defineProperty()
let oil = {}
let val = 9
Object.defineProperty(oil,'price',{
get(){
console.log("告诉你今日油价")
return val
},
set(newVal){
console.log("油价更新了")
val = newVal
}
})
整合一下形成一个Observe类
export class Observer {
constructor(value) {
this.value = value
this.walk(value)//遍历一遍,把对象转成响应式
}
walk(obj) {
const keys = Object.keys(obj)
for(let i=0; i<keys.length; i++) {
defineReactive(obj, keys[i])
}
}
}
//val参数代表对象的某个key的值
function defineReactive(obj,key,val) {
//如果只有两个参数,val默认 == obj[key]
if(arguments.length === 2) {
val = obj[key]
}
if(typeof(val) === 'object') {
new Observer(val)
}
Object.defineProperty(obj, key, {
enumerable: true,//表示是否可以通过delete删除并重新定义,改为访问器属性
configurable: true,//表示是否可以通过for-in迭代
get(){
console.log(`${key}被读取了`)
return val
},
set(newVal){
if(val === newVal) return
console.log(`${key}被修改了`)
val = newVal
}
})
}
好了,现在对象可以观测了,里面还有很多需要考虑的细节,但这并不是现在需要考虑的问题,把全貌看完再细化细节可能更好。我们的目标是看到最高处,现在拿简易木架搭了第一层阶梯,我们应该做的是继续搭简易阶梯上去,没必要把每一层都浇上钢筋水泥,那太慢了,等看到最高处的风景,想给每一级搭大理石还是金属装饰那都没问题。
4.收集依赖
当数据改变了之后,页面怎么知道到的哪些地方需要修改呢?我们可以做一个目录,哪个地方用了这个数据,就告诉这个地方改一下,引用一句话:在getter中收集依赖,在setter中通知依赖更新。可以用一个数组来做目录,有东西调用了get方法就在目录中加上是哪个对象调用的,set了一个新值就把目录中各个依赖的对象更新一下。下面构建一个Dep类,我们为每一个对象都弄一个Dep,里面有依赖数组和添加,删除,更新依赖的方法。
export default class Dep {
constructor() {
this.subs = []
}
depend() {
if(window.target) {
this.addSub(window.target)
}
}
//添加依赖的方法
addSub(sub) {
this.subs.push(sub)
console.log("添加了依赖,现在的依赖有:",this.subs)
}
//移除依赖
removeSub(sub) {
remove(this.subs, sub)
}
//在源码中remove函数被单独放在了一个util文件夹内,但这里我们先不考虑
remove(arr,item) {
if(arr.length) {
const index = arr.indexOf(item)
if(index > -1) {
return arr.splice(index, 1)
}
}
}
notify() {
const subs = this.subs.slice()
for(let i=0; i<subs.length; i++) {
subs[i].update()//具体如何更新的下面说
}
}
}
好了,接下来更新一下observer类
export class Observer {
constructor(value) {
this.value = value
this.walk(value)//遍历一遍,把对象转成响应式
}
walk(obj) {
const keys = Object.keys(obj)
for(let i=0; i<keys.length; i++) {
defineReactive(obj, keys[i])
}
}
}
//val参数代表对象的某个key的值
function defineReactive(obj,key,val) {
//如果只有两个参数,val默认 == obj[key]
if(arguments.length === 2) {
val = obj[key]
}
if(typeof(val) === 'object') {
new Observer(val)
}
const dep = new Dep()//实例化Dep类,新建一个Dep类型的dep实例
Object.defineProperty(obj, key, {
enumerable: true,//表示是否可以通过delete删除并重新定义,改为访问器属性
configurable: true,//表示是否可以通过for-in迭代
get(){
console.log(`${key}被读取了`)
//收集依赖
dep.depend()
return val
},
set(newVal){
if(val === newVal) return
console.log(`${key}被修改了`)
val = newVal
//被set了新值就通知依赖更新
dep.notify()
}
})
}
5.更新依赖
好了,现在对象变成响应式的了,而且建了依赖数组,一旦有变动就会通知依赖更新,那么依赖是怎么更新的呢?于是我们弄了一个Watcher类,谁用到了数据,我们就给它实例化一个watcher,里面有更新的方法。
export class Watcher {
constructor(vm,expOrFn,cb) {
this.vm = vm
this.cb = cb
this.getter = parsePath(expOrFn) //获取路径,在源码里单独放在工具类util里
//Watcher被实例化的时候就调用get()
this.value = this.get()
}
get() {
//this是一个实例化后的watcher对象,这里先把this保存下来,换成Dep.target也可以
window.target = this
const vm = this.vm
let value = this.getter.call(vm, vm)
window.target = undefined
return value
}
update() {
const oldValue = this.value
this.value = this.get()
this.cb.call(this.vm, this.value, oldValue)
},
parsePath (path) {
const bailRE = /[^\w.$]/
if (bailRE.test(path)) {
return
}
const segments = path.split('.')
return function (obj) {
for (let i = 0; i < segments.length; i++) {
if (!obj) return
obj = obj[segments[i]]
}
return obj
}
}
}
好了,简易的Object的变化侦测就完成了。
参考资料:
github.com/vuejs/vue/t…
vue-js.com/learn-vue/
segmentfault.com/a/119000004…
ustbhuangyi.github.io/vue-analysi…