ImSheet 开源图床工具
ImSheet 一款简约直观的图床工具,无服务端轻松管理你的个人 images 资产,遵从TIM开源协议,源码已公开。
ImSheet 一款简约直观的图床工具,无服务端轻松管理你的个人 images 资产,遵从TIM开源协议,源码已公开。
这里的函数defineReactive用来对Object.defineProperty进行封装。
从函数的名字可以看出,其作用是定义一个响应式数据。
也就是在这个函数中进行变化追踪,封装后只需要传递data、key和val就行了。
封装好之后,每当从data的key中读取数据时,get函数被触发;每当往data的key中设置数据时,set函数被触发。
1 | function defineReactive(data, key, val) { |
总结起来,其实就一句话,在getter中收集依赖,在setter中触发依赖。
1 | <template> |
1 | function defineReactive (data, key, val) { |
1 | export default class Dep { |
在上面的代码中,我们要通知用到数据的地方,而使用这个数据的地方有很多,而且类型还不一样,既有可能是模板,也有可能是用户写的一个watch,这时需要抽象出一个能集中处理这些情况的类。然后,我们在依赖收集阶段只收集这个封装好的类的实例进来,通知也只通知它一个。接着,它再负责通知其他地方。所以,我们要抽象的这个东西需要先起一个好听的名字。嗯,就叫它Watcher吧。现在就可以回答上面的问题了,收集谁?Watcher!
Watcher是一个中介的角色,数据发生变化时通知它,然后它再通知其他地方。
关于Watcher,先看一个经典的使用方式:
1 | // keypath 观测 data.key, 此处为 data.a.b.c |
这段代码表示当data.a.b.c属性发生变化时,触发第二个参数中的函数。
思考一下,怎么实现这个功能呢?好像只要把这个watcher实例添加到data.a.b.c属性的Dep中就行了。然后,当data.a.b.c的值发生变化时,通知Watcher。
接着,Watcher再执行参数中的这个回调函数。
1 | export default class Watcher { |
这段代码可以把自己主动添加到data.a.b.c的Dep中去,是不是很神奇?因为我在get方法中先把window.target设置成了this,也就是当前watcher实例,然后再读一下data.a.b.c的值,这肯定会触发getter。
触发了getter,就会触发收集依赖的逻辑。
而关于收集依赖,上面已经介绍了,会从window.target中读取一个依赖并添加到Dep中。
这就导致,只要先在window.target赋一个this,然后再读一下值,去触发getter,就可以把this主动添加到keypath的Dep中。
有没有很神奇的感觉啊?依赖注入到Dep中后,每当data.a.b.c的值发生变化时,就会让依赖列表中所有的依赖循环触发update方法,也就是Watcher中的update方法。而update方法会执行参数中的回调函数,将value和oldValue传到参数中。
所以,其实不管是用户执行的vm.$watch(‘a.b.c’, (value, oldValue) => {}),还是模板中用到的data,都是通过Watcher来通知自己是否需要发生变化。
1 | /** |
Object.defineProperty(obj, prop, descriptor) obj
要在其上定义属性的对象。
prop
要定义或修改的属性的名称。
descriptorenumerable:当且仅当该属性为 true 时,该属性才能够出现在对象的枚举属性中。默认为 false。configurable:当且仅当该属性的为 true 时,该属性描述符才能够被改变,同时该属性也能从对应的对象上被删除。默认为 false。
Array.slice(begin, end)slice()方法返回一个新的数组对象,这一对象是一个由 begin 和 end(不包括end)决定的原数组的浅拷贝。原始数组不会被改变
提取规则为 Array[ begin, end )
begin 可选
从该索引处开始提取原数组中的元素(从0开始)。
如果该参数为负数,则表示从原数组中的倒数第几个元素开始提取,slice(-2)表示提取原数组中的倒数第二个元素到最后一个元素(包含最后一个元素)。
如果省略 begin,则 slice 从索引 0 开始。
end 可选
在该索引处结束提取原数组元素(从0开始)。slice会提取原数组中索引从 begin 到 end 的所有元素(包含begin,但不包含end)。
1 | var fruits = ['Banana', 'Orange', 'Lemon', 'Apple', 'Mango']; |
Array.splice(start[, deleteCount[, item1[, item2[, ...]]]])splice() 方法通过删除或替换现有元素或者原地添加新的元素来修改数组,并以数组形式返回被修改的内容。
此方法会改变原数组
start
指定修改的开始位置(从0计数)。如果超出了数组的长度,则从数组末尾开始添加内容;如果是负值,则表示从数组末位开始的第几位(从-1计数,这意味着-n是倒数第n个元素并且等价于array.length-n);如果负数的绝对值大于数组的长度,则表示开始位置为第0位。
deleteCount 可选
整数,表示要移除的数组元素的个数。
如果 deleteCount 大于 start 之后的元素的总数,则从 start 后面的元素都将被删除(含第 start 位)。
如果 deleteCount 被省略了,或者它的值大于等于array.length - start(也就是说,如果它大于或者等于start之后的所有元素的数量),那么start之后数组的所有元素都会被删除。
如果 deleteCount 是 0 或者负数,则不移除元素。这种情况下,至少应添加一个新元素。
item1, item2, … 可选
要添加进数组的元素,从start 位置开始。如果不指定,则 splice() 将只删除数组元素。
fun.call(thisArg, arg1, arg2, ...)call()方法使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数。
注意:该方法的语法和作用与 apply() 方法类似,只有一个区别,就是 call() 方法接受的是一个参数列表,而 apply() 方法接受的是一个包含多个参数的数组。
thisArg
在 fun 函数运行时指定的 this 值。需要注意的是,指定的 this 值并不一定是该函数执行时真正的 this 值,如果这个函数在非严格模式下运行,则指定为 null 和 undefined 的 this 值会自动指向全局对象(浏览器中就是 window 对象),同时值为原始值(数字,字符串,布尔值)的 this 会指向该原始值的自动包装对象。
arg1, arg2, …
指定的参数列表。
regexObj.test(str)test() 方法执行一个检索,用来查看正则表达式与指定的字符串是否匹配。返回 true 或 false。