乐趣区

关于javascript:通过Proxy和Reflect实现vue的响应式原理

vue3 通过 Proxy+Reflect 实现响应式,vue2 通过 defineProperty 来实现

Proxy

Proxy 是什么

Proxy 是 ES6 中减少的类,示意代理。
如果咱们想要监听对象的操作过程,能够先创立一个代理对象,之后所有对于对象的操作,都由代理对象来实现,代理对象能够监听到咱们对于原对象进行了哪些操作。

Proxy 怎么应用

Proxy 是一个类,通过 new 关键字创建对象,传入原对象和解决监听的捕捉器

const user = {name: 'alice'}

const proxy = new Proxy(user, {})

console.log(proxy)
proxy.name = 'kiki'
console.log(proxy.name)
console.log(user.name)

对代理所作的操作,同样会作用于原对象

什么是捕捉器

捕捉器就是用来监听对于对象操作的办法

const user = {
  name: 'alice',
  age: 18
}
const proxy = new Proxy(user, {get: function(target, key){console.log(` 调用 ${key}属性的读取操作 `)
    return target[key]
  },
  set: function(target, key, value){console.log(` 调用 ${key}属性的设置操作 `)
    target[key] = value
  }
})
proxy.name = 'kiki'
console.log(proxy.age)

以上 get、set 是捕捉器中罕用的两种,别离用于对象数据的“读取”操作和“设置”操作

有哪些捕捉器

Proxy 里对应捕捉器与一般对象的操作和定义是一一对应的

  1. handler.getPrototypeOf()
    Object.getPrototypeOf 办法的捕获器。
  2. handler.setPrototypeOf()
    Object.setPrototypeOf 办法的捕获器。
  3. handler.isExtensible()
    Object.isExtensible 办法的捕获器。
  4. handler.preventExtensions()
    Object.preventExtensions 办法的捕获器。
  5. handler.getOwnPropertyDescriptor() Object.getOwnPropertyDescriptor 办法的捕获器。
  6. handler.defineProperty()
    Object.defineProperty 办法的捕获器。
  7. handler.has()
    in 操作符的捕获器。
  8. handler.get()
    属性读取操作的捕获器。
  9. handler.set()
    属性设置操作的捕获器。
  10. handler.deleteProperty()
    delete 操作符的捕获器。
  11. handler.ownKeys()
    Object.getOwnPropertyNames 办法和 Object.getOwnPropertySymbols 办法的捕获器。
  12. handler.apply()
    函数调用操作的捕获器。
  13. handler.construct()
    new 操作符的捕获器。

将这些捕捉器以及对应的对象操作写在了以下示例中

const user = {
  name: "alice",
  age: 18
};
const proxy = new Proxy(user, {get(target, key) {console.log("执行了 get 办法");
    return target[key];
  },
  set(target, key, value) {target[key] = value;
    console.log("执行了 set 办法");
  },
  has(target, key) {return key in target;},
  deleteProperty(target, key){console.log('执行了 delete 的捕捉器')
    delete target[key]
  },
  ownKeys(target){console.log('ownKeys')
    return Object.keys(target)
  },
  defineProperty(target, key, value){console.log('defineProperty', target, key, value)
    return true
  },
  getOwnPropertyDescriptor(target, key){return Object.getOwnPropertyDescriptor(target, key)
  },
  preventExtensions(target){console.log('preventExtensions')
    return Object.preventExtensions(target)
  },
  isExtensible(target){return Object.isExtensible(target)
  },
  getPrototypeOf(target){return Object.getPrototypeOf(target)
  },
  setPrototypeOf(target, prototype){console.log('setPrototypeOf', target, prototype)
    return false
  }
});

console.log(proxy.name);
proxy.name = "kiki";
console.log("name" in proxy);
delete proxy.age
console.log(Object.keys(proxy))
Object.defineProperty(proxy, 'name', {value: 'alice'})
console.log(Object.getOwnPropertyDescriptor(proxy, 'name'))
console.log(Object.preventExtensions(proxy))
console.log(Object.isExtensible(proxy))
console.log(Object.getPrototypeOf(proxy))
Reflect.setPrototypeOf(proxy, {})

通过捕捉器去监听对象的批改、查问、删除等操作

以上捕捉器中只有 apply 和 constructor 是属于函数对象的

function foo(){}

const proxy = new Proxy(foo, {apply: function(target, thisArg, args){console.log('执行 proxy 的 apply 办法', target, thisArg, args)
    return target.apply(thisArg, args)
  },
  construct: function(target, argArray){console.log('执行 proxy 的 construct 办法',target, argArray)
    return new target()}
})

proxy.apply({}, [1,2])
new proxy('alice', 18)

apply 办法执行 apply 捕捉器,new 操作执行 constructor 捕捉器

Reflect

Reflect 是什么

Reflect 也是 ES6 新增的一个 API,示意反射。它是一个对象,提供了很多操作对象的办法,相似于 Object 中的办法,比方 Reflect.getPrototypeOf 和 Object.getPrototypeOf。

晚期操作对象的办法都是定义在 Object 上,但 Object 作为构造函数,间接放在它身上并不适合,所以新增 Reflect 对象来对立操作,并且转换了对象中 in、delete 这样的操作符

Reflect 中有哪些办法

Reflect 中的办法与 Proxy 中是一一对应的

  1. Reflect.apply(target, thisArgument, argumentsList)
    对一个函数进行调用操作,同时能够传入一个数组作为调用参数。和 Function.prototype.apply() 性能相似。
  2. Reflect.construct(target, argumentsList[, newTarget])
    对构造函数进行 new 操作,相当于执行 new target(…args)。
  3. Reflect.defineProperty(target, propertyKey, attributes)和 Object.defineProperty() 相似。如果设置胜利就会返回 true
  4. Reflect.deleteProperty(target, propertyKey)
    作为函数的 delete 操作符,相当于执行 delete target[name]。
  5. Reflect.get(target, propertyKey[, receiver])
    获取对象身上某个属性的值,相似于 target[name]。
  6. Reflect.getOwnPropertyDescriptor(target, propertyKey)
    相似于 Object.getOwnPropertyDescriptor()。如果对象中存在该属性,则返回对应的属性描述符,  否则返回 undefined.
  7. Reflect.getPrototypeOf(target)
    相似于 Object.getPrototypeOf()。
  8. Reflect.has(target, propertyKey)
    判断一个对象是否存在某个属性,和 in 运算符 的性能完全相同。
  9. Reflect.isExtensible(target)
    相似于 Object.isExtensible().
  10. Reflect.ownKeys(target)
    返回一个蕴含所有本身属性(不蕴含继承属性)的数组。(相似于 Object.keys(), 但不会受 enumerable 影响).
  11. Reflect.preventExtensions(target)
    相似于 Object.preventExtensions()。返回一个 Boolean。
  12. Reflect.set(target, propertyKey, value[, receiver])
    将值调配给属性的函数。返回一个 Boolean,如果更新胜利,则返回 true。
  13. Reflect.setPrototypeOf(target, prototype)
    设置对象原型的函数. 返回一个 Boolean,如果更新胜利,则返回 true。

将之前通过 Proxy 设置代理的对象操作全都变为 Reflect

const user = {
  name: "alice",
  age: 18
};
const proxy = new Proxy(user, {get(target, key) {console.log("执行了 get 办法");
    return Reflect.get(target, key)
  },
  set(target, key, value) {Reflect.set(target, key, value)
    console.log("执行了 set 办法");
  },
  has(target, key) {return Reflect.has(target, key)
  },
  deleteProperty(target, key){Reflect.deleteProperty(target, key)
  },
  ownKeys(target){console.log('ownKeys')
    return Reflect.ownKeys(target)
  },
  defineProperty(target, key, value){console.log('defineProperty', target, key, value)
    return true
  },
  getOwnPropertyDescriptor(target, key){return Reflect.getOwnPropertyDescriptor(target, key)
  },
  preventExtensions(target){console.log('preventExtensions')
    return Reflect.preventExtensions(target)
  },
  isExtensible(target){return Reflect.isExtensible(target)
  },
  getPrototypeOf(target){return Reflect.getPrototypeOf(target)
  },
  setPrototypeOf(target, prototype){console.log('setPrototypeOf', target, prototype)
    return false
  }
});
console.log(proxy.name);
proxy.name = "kiki";
console.log(Reflect.has(proxy, 'name'));
delete proxy.age
console.log(Reflect.ownKeys(proxy))
Reflect.defineProperty(proxy, 'name', {value: 'alice'})
console.log(Reflect.getOwnPropertyDescriptor(proxy, 'name'))
console.log(Reflect.preventExtensions(proxy))
console.log(Reflect.isExtensible(proxy))
console.log(Reflect.getPrototypeOf(proxy))
Reflect.setPrototypeOf(proxy, {})

实现的成果是完全一致的

Reflect 的 receiver

Reflect 中在进行 get/set 捕捉器操作的时候,还有一个入参是 receiver,指的是代理对象,用于扭转 this 指向

const user = {
  _name: 'alice',
  get name(){return this._name},
  set name(value){this._name = value}
}
const proxy = new Proxy(user, {get: function(target, key, receiver){console.log('get 操作', key)
    return Reflect.get(target, key, receiver)
  },
  set: function(target, key, receiver){console.log('set 操作', key)
    return Reflect.set(target, key, receiver)
  },
})
console.log(proxy.name)
proxy.name = 'kiki'

(1) 如果没有 receiver,那么当批改 name 属性时,objProxy 先执行 key 为 name 时的 get 操作
(2) 而后代理到 obj 里的 get 办法,读取 this 的_name 属性,此时的 this 是 obj,会间接批改
obj._name,不会再通过 objProxy
(3) 减少了 receiver 之后,执行 obj 的 get 办法,读取 this 的_name 属性,此时 this 是 proxy
对象,所以会再次到 get 的捕捉器中

set 操作同理

Reflect 中的 constructor

用于扭转 this 的指向

function Person(){}
function Student(name, age){
  this.name = name;
  this.age = age
}
const student = Reflect.construct(Student, ['aclie', 18], Person)

console.log(student)
console.log(student.__proto__ === Person.prototype)

此时创立的 student 对象尽管领有 Student 的属性和办法,然而它的 this 指向 Person

vue 的响应式

通过以下步骤一步步实现响应式

1、定义一个数组收集所有的依赖

  • 定义全局变量 reactiveFns 用来保留所有的函数
  • 定义方法 watchFn,入参为函数,代码体为将入参保留进全局变量中
  • 批改对象的值,遍历全局变量,执行每一个函数
let user = {name: "alice",};
let reactiveFns = [];
function watchFn(fn) {reactiveFns.push(fn);
}
watchFn(function () {console.log("哈哈哈");
});
watchFn(function () {console.log("hello world");
});
function foo(){console.log('一般函数')
}
user.name = "kiki";
reactiveFns.forEach((fn) => {fn();
});

此时通过响应式函数 watchFn 将所有须要执行的函数收集进了数组中,而后当变量的值发生变化时,手动遍历执行所有的函数

2. 收集依赖类的封装

以上只有一个数组来收集对象的执行函数,真实情况下,不止一个对象须要对操作状态进行监听,须要监听多个对象就能够应用类。

  • 定义 Depend 类,给每个实例对象提供 addDepend 办法用于增加绑定的办法,notify 办法用于执行该实例的 reactiveFns 属性上增加的所有办法
  • 封装响应式函数 watchFn,函数里调用实例对象的 appDepend 办法
  • 批改对象的值,调用实例对象的 notify 办法
class Depend {constructor() {this.reactiveFns = [];
  }
  addDepend(fn) {this.reactiveFns.push(fn);
  }
  notify() {this.reactiveFns.forEach((fn) => {fn();
    });
  }
}
let user = {
  name: "alice",
  age: 18,
};
const depend = new Depend();
function watchFn(fn) {depend.addDepend(fn);
}
watchFn(function(){console.log('啦啦啦啦啦啦')
})
watchFn(function(){console.log('hello hello')
})
function foo(){console.log('foo')
}
user.name = 'kiki'
depend.notify()

将收集操作和顺次执行函数的办法都定义在类中

3. 主动监听对象的变动

以上依然是咱们本人手动调用执行函数的办法,以下主动监听

  • 在通过类收集依赖的根底上,减少 Proxy 来定义对象,Reflect 执行对象的办法
  • 在 set 办法中执行实例对象 depend 的 notify 办法
  • 批改代理 proxy 属性的值
class Depend {constructor() {this.reactiveFns = [];
  }
  addDepend(fn) {this.reactiveFns.push(fn);
  }
  notify() {this.reactiveFns.forEach((fn) => {fn();
    });
  }
}
let user = {
  name: "alice",
  age: 18,
};
const depend = new Depend();
function watchFn(fn) {depend.addDepend(fn);
}
const proxy = new Proxy(user, {get: function (target, key, receiver) {return Reflect.get(target, key, receiver);
  },
  set: function (target, key, value, receiver) {Reflect.set(target, key, value, receiver);
    depend.notify();},
});
watchFn(function () {console.log("name 变动执行的函数");
});
watchFn(function () {console.log("age 变动执行的函数");
});
function foo() {console.log("foo");
}
proxy.name = "kiki";
proxy.age = 20;

此时的问题是,批改了对象的任一属性,所有的函数都会调用,没有依照一一对应的关系来保留对象的属性和对应的函数

4、收集依赖的治理

  • 定义 weakMap 用来治理对象,[对象名, map],定义 map 保留 [对象的属性名, 实例对象 depend]
  • 定义获取 depend 实例对象的办法 getDepend,先从 weakMap 中获取 map,如果没有就 new 一个,再从 map 中获取 depend 对象,如果没有再 new 一个
  • 在 set 办法中获取 depend 对象,调用 notify 办法
class Depend {constructor() {this.reactiveFns = [];
  }
  addDepend(fn) {this.reactiveFns.push(fn);
  }
  notify() {this.reactiveFns.forEach((fn) => {fn();
    });
  }
}
let user = {
  name: "alice",
  age: 18,
};
const depend = new Depend();
function watchFn(fn) {depend.addDepend(fn);
}
const weakMap = new WeakMap()
function getDepend(obj, key){let map = weakMap.get(obj)
  if(!map){map = new Map()
    weakMap.set(obj, map)
  }
  let depend = map.get(key)
  if(!depend){depend = new Depend()
    map.set(key, depend)
  }
  return depend
}
const proxy = new Proxy(user, {get: function (target, key, receiver) {return Reflect.get(target, key, receiver);
  },
  set: function (target, key, value, receiver) {Reflect.set(target, key, value, receiver);
    const depend = getDepend(target, key)
    depend.notify();},
});
watchFn(function () {console.log("name 变动执行的函数");
});
watchFn(function () {console.log("age 变动执行的函数");
});
function foo() {console.log("foo");
}
proxy.name = "kiki";
proxy.age = 20;

此时 proxy 对应的 depend 是没有值的,所以此时没有任何打印的数据

5、正确治理收集的依赖

  • 全局定义变量 activeReactiveFn 指向 watchFn 传入的办法,执行传入的办法,再将 activeReactiveFn 指向 null
  • watchFn 传入时便会被执行一次,用于代码对象的 get 办法中收集依赖
  • Depend 类中应用 addDepend 办法无需传参,间接应用全局的 activeReactiveFn
  • get 办法中通过 getDepend 获取 depend,并应用 addDepend 办法,收集依赖
class Depend {constructor() {this.reactiveFns = [];
  }
  addDepend(fn) {this.reactiveFns.push(fn);
  }
  notify() {this.reactiveFns.forEach((fn) => {fn();
    });
  }
}
let user = {
  name: "alice",
  age: 18,
};
let activeReactiveFn = null;
function watchFn(fn) {
  activeReactiveFn = fn;
  fn();
  activeReactiveFn = null;
}
const weakMap = new WeakMap();
function getDepend(obj, key) {let map = weakMap.get(obj);
  if (!map) {map = new Map();
    weakMap.set(obj, map);
  }
  let depend = map.get(key);
  if (!depend) {depend = new Depend();
    map.set(key, depend);
  }
  return depend;
}
const proxy = new Proxy(user, {get: function (target, key, receiver) {const depend = getDepend(target, key)
    depend.addDepend(activeReactiveFn)
    return Reflect.get(target, key, receiver);
  },
  set: function (target, key, value, receiver) {Reflect.set(target, key, value, receiver);
    const depend = getDepend(target, key);
    depend.notify();},
});
watchFn(function () {console.log(proxy.name, "name 变动执行的函数");
  console.log(proxy.name, "name 变动执行的函数 again");
});
watchFn(function () {console.log(proxy.age, "age 变动执行的函数");
});
function foo() {console.log("foo");
}
proxy.name = "kiki";

此时曾经可能依据属性值的变动而执行对应的函数了,但同一个函数会执行两次

6、重构

  • 当函数中调用两次 proxy 的属性时,会将同一个函数增加到数组中两次,所以将 reactiveFn 数据结构由数组变成 set
  • 定义 reactive 函数用来收集解决须要代理及响应式的对象
let activeReactiveFn = null;
class Depend {constructor() {this.reactiveFns = new Set();
  }
  addDepend() {if (activeReactiveFn) {this.reactiveFns.add(activeReactiveFn);
    }
  }
  notify() {this.reactiveFns.forEach((fn) => {fn();
    });
  }
}
function watchFn(fn) {
  activeReactiveFn = fn;
  fn();
  activeReactiveFn = null;
}
const weakMap = new WeakMap();
function getDepend(obj, key) {let map = weakMap.get(obj);
  if (!map) {map = new Map();
    weakMap.set(obj, map);
  }
  let depend = map.get(key);
  if (!depend) {depend = new Depend();
    map.set(key, depend);
  }
  return depend;
}
function reactive(obj){
  return new Proxy(obj, {get: function (target, key, receiver) {const depend = getDepend(target, key);
      depend.addDepend();
      return Reflect.get(target, key, receiver);
    },
    set: function (target, key, value, receiver) {Reflect.set(target, key, value, receiver);
      const depend = getDepend(target, key);
      depend.notify();},
  });
}
const user = reactive({
  name: "alice",
  age: 18,
})
const info = reactive({message: 'hello'})
watchFn(function () {console.log(user.name, "name 变动执行的函数");
  console.log(user.name, "name 变动执行的函数 again");
});
watchFn(function () {console.log(user.age, "age 变动执行的函数");
});
watchFn(function(){console.log(info.message, "message 发生变化了")
})
function foo() {console.log("foo");
}
user.name = "kiki";
user.age = 20;
info.message = 'ooo'

此时已实现 vue3 的响应式~

vue2 的实现原理

vue2 的实现就是将 Proxy 和 Reflect 替换成了 Object.defineProperty 和 Object 自身的一些办法

let activeReactiveFn = null;
class Depend {constructor() {this.reactiveFns = new Set();
  }
  addDepend() {if (activeReactiveFn) {this.reactiveFns.add(activeReactiveFn);
    }
  }
  notify() {this.reactiveFns.forEach((fn) => {fn();
    });
  }
}
function watchFn(fn) {
  activeReactiveFn = fn;
  fn();
  activeReactiveFn = null;
}
const weakMap = new WeakMap();
function getDepend(obj, key) {let map = weakMap.get(obj);
  if (!map) {map = new Map();
    weakMap.set(obj, map);
  }
  let depend = map.get(key);
  if (!depend) {depend = new Depend();
    map.set(key, depend);
  }
  return depend;
}
function reactive(obj){Object.keys(obj).forEach(key=>{let value = obj[key]
    Object.defineProperty(obj, key, {get: function () {const depend = getDepend(obj, key);
        depend.addDepend();
        return value
      },
      set: function (newValue) {
        value = newValue
        const depend = getDepend(obj, key);
        depend.notify();},
    })
  })
  return obj
}
const user = reactive({
  name: "alice",
  age: 18,
})
const info = reactive({message: 'hello'})
watchFn(function () {console.log(user.name, "name 变动执行的函数");
  console.log(user.name, "name 变动执行的函数 again");
});
watchFn(function () {console.log(user.age, "age 变动执行的函数");
});
watchFn(function(){console.log(info.message, "message 发生变化了")
})
function foo() {console.log("foo");
}
user.name = "kiki";
user.age = 20;
info.message = 'ooo'

和下面实现的成果是统一的

以上就是通过 Proxy 和 Reflect 实现 vue 的响应式原理,对于 js 高级,还有很多须要开发者把握的中央,能够看看我写的其余博文,继续更新中~

退出移动版