let uid = 0 // 用于储存订阅者并发布消息
class Dep {
constructor() {
this.id = uid++ // 设置id 用于区分新的watcher和只改变属性值后新产生的watcher
this.subs = [] // 储存订阅者的数组
}
depend () { // 触发target上的watcher的addDep的方法,参数为dep的实例本身
Dep.target.addDep(this)
}
addSub () { // 添加订阅者
this.subs.push(sub)
}
notify () { // 通知所有订阅者(watcher),触发订阅者的相应的处理逻辑
this.subs.forEach(sub => sub.update())
}
}
Dep.target = null // 为Dep类设置一个静态属性,默认为null,工作时指向当前的watcher
// 监听者
class Observe { // 监听属性值变化
constructor (value) {
this.value = value
this.walk(value)
}
walk(value) { // 便利属性值 并监听
Object.keys(value).forEach(key =>this.convert(key,value[key]))
}
convert() { // 执行监听的具体方法
defineReative(this.value, key , val)
}
}
function defineReative (obj,key,val) {
const dep = new Dep()
let childOb = observe(val) // 为当前属性值添加监听
Object.defineProperty(obj,key,{
enumerable:true,
configurable:true,
get: () => {
// 如果dep类存在target属性 将其添加到subs数组里面
// target指向一个Watcher实例,每个Watcher都是一个订阅者
// Watcher实例在实例化过程中,会读取data中的某个属性,从而触发当前get方法
if(Dep.target){
dep.depend()
}
return val
},
set: newVal => {
if(val === newVal) return
val = newVal
childOb = observe(newVal) // 对新的val进行监听
dep.notify() // 通知订阅者属性发生了改变
}
})
}
function observe (value) {
if(!value || typeof value !== 'object') { // 当值不存在,或者不是复杂数据类型时,不再需要继续深入监听
return
}
return new Observe(value)
}
class Watch {
constructor(vm,expOrFn,cb){
this.depIds = {} // hash储存订阅者的id 避免重复订阅
this.vm = vm // 被订阅者的数据一定来自vue的实例
this.cb = cb // 数据更新之后做的事情
this.expOrFn = expOrFn // 被订阅的数据
this.val = this.get() // 维护更新之前的数据
}
update() { // 暴露接口 用于在订阅的数据更新之时 由新的订阅者(Dep)调用
this.run()
}
addDep() {
if(!this.depIds.hasOwnPropety(dep.id)) { // 如果在depIds的hash之中没有找到当前id 可以判断为新的watcher,由此可以添加到dep的数组中储存 避免同id多次储存
dep.addSub(this)
this.depIds[dep.id] = dep
}
}
run () {
const val = this.get()
if(val !== this.val) {
this.val = val
this.cb.call(this.vm,val)
}
}
get () {
Dep.target = this // 当前订阅者(Watcher)读取被订阅数据的更新后的值 通知订阅者管理员收集订阅者
const val = this.vm._data[this.expOrFn]
Dep.target = null // 置空订阅者 为下一个Watcher使用
return val
}
}
class vue {
constructor(options = {}) {
this.$options = options // 简化$options的处理
let data = (this._data = this.$options.data) // 简化Data的处理
Object.key(data).forEach(key => this._proxy(key)) // 将data最外层的属性代理到vue实例上
observe(data) // 监听数据
}
$watch(expOrFn,cb) { // 对外暴露订阅者接口 内部主要在指令中使用订阅者
new Watcher(this, expOrFn,cb)
}
_proxy(key){
Object.defineProperty(this,key,{
configurable:true,
enumerable:true,
get:() => this._data[key],
set:val => {
this._data[key] = val
}
})
}
}
// Object.difineProterty的缺陷 数组触发的渲染函数 无法实现监听 vm.items[indexOfItem] = newValue这种是无法检测的。
// Object.defineProperty的第二个缺陷,只能劫持对象的属性,因此我们需要对每个对象的每个属性进行遍历,如果属性值也是对象那么需要深度遍历,显然能劫持一个完整的对象是更好的选择。
// proxy 实现双向绑定的特点 可以直接监听对象 而并非属性
const input = document.getElementById('input')
const p = document.getElementById('p')
const obj = {}
const newObj = new Proxy(obj,{
get:function(target,key,receiver){
return Reflect.get(target,key,receiver)
},
set:function(target,key,value,receiver){
if(key === 'text'){
input.value = value;
p.innerHTML = value;
}
return Reflect.set(target,key,value,receiver)
}
})
input.addEventListener('keyup',function(e){
newObj.text = e.target.value
})
// proxy可以直接监听数据的变化 对数组进行操作的时候 会触发对应方法名和length的变化
const list = document.getElementById('list')
const btn = document.getElementById('btn')
const Render = {
init: function (arr) {
const fargment = document.createDocumentFragment()
for(let i = 0; i<arr.length; i++){
const li = document.createElement('li')
li.textContent = arr[i]
fargment.appendChild(li)
}
list.appendChild(fargment)
},
change:function (val) {
const li = document.createElement('li')
li.textContent = val
list.appendChild(li)
}
}
const arr = [1,2,3,4] // 创建数组
const newArr = new Proxy(arr,{
get:function(target,value,receiver){
return Reflect.get(target,value,receiver)
},
set:function(target,key,val,receiver){
if(key !=='length') {
Render.change(value)
}
return Reflect.set(target,key,val,receiver)
}
})
window.onload = function (arr) {
Render.init(arr)
}
btn.addEventListener('click',function(){
newArr.push(6)
})
// Proxy有多达13种拦截方法,不限于apply、ownKeys、deleteProperty、has等等是Object.defineProperty不具备的。
// Proxy返回的是一个新对象,我们可以只操作新的对象达到目的,而Object.defineProperty只能遍历对象属性直接修改。