数据模型仅仅是普通的 JavaScript
对象,可是对这些对象进行操做时,却能影响对应视图,简而言之,就是你动我也动
。 它的核心实现就是「响应式系统」,核心内容为Object.defineProperty 使用方法以下:编程
/* obj: 目标对象 prop: 目标对象的属性名 descriptor: 描述符 return value 传入对象 */
Object.defineProperty(obj, prop, descriptor)
复制代码
descriptor的一些属性数组
observer
(可观察到你动了)首先定义一个假的函数来模拟更新app
function updateView(val) {
/* 伪装是视图 */
console.log("我动了");
}
复制代码
而后咱们定义一个 defineReactive
,这个方法经过 Object.defineProperty
来实现对对象的「响应式」化,通过 defineReactive
处理之后,咱们的 target 的 key 属性在「读」的时候会触发 get
方法,而在该属性被「写」的时候则会触发 set
方法。函数
function defineReactive (target, key, val) {
Object.defineProperty(obj, key, {
get() {
return val;
},
set(newVal) {
if (newVal === val) return;
updateView(newVal);
}
});
}
复制代码
这样貌似ok了,可是没人让他动起来,咱们再封装一层observer
,这个函数传入一个 value(须要「响应式」化的对象),经过遍历全部属性的方式对该对象的每个属性都经过 defineReactive 处理。测试
function observer (target) {
if (!target || (typeof target !== 'object')) {
return;
}
Object.keys(target).forEach((key) => {
defineReactive(target, key, target[key]);
});
}
复制代码
最后为了好看点,封装一个Vueui
class Vue {
constructor(options) {
this._data = options.data;
observer(this._data);
}
}
// 测试
let o = new Vue({
data: {
test: "I am test."
}
});
o._data.test = "hello,world."; /* 我动了 */
复制代码
嵌套对象
就很容易发现问题,不动啦,原理也很简单,指向同一内存,能够类比深浅拷贝那么咱们要进行递归调用
this
function defineReactive(target, key, value){
observer(value); // 递归 我就将这个对象 继续拦截
Object.defineProperty(target,key,{
get(){
return value
},
set(newValue){
if(newValue !== value){ // 不一样值才更新
// o_data.age = {n:200};o _data.age.n = 300;这种状况就须要从新观察
observer(newValue)
updateView();
value = newValue
}
}
});
}
复制代码
改写observer
, 为了让不改变原数组
,巧妙运用切片编程spa
let oldArrayPrototype = Array.prototype;
let proto = Object.create(oldArrayPrototype); // 继承
['push','shift','unshift'].forEach(method=>{
proto[method] = function(){ //函数劫持 把函数进行重写 内部 继续调用老的方法
updateView(); // 切片编程
oldArrayPrototype[method].call(this, ...arguments)
// oldArrayPrototype[method].apply(this, arguments)
}
});
function observer(target){
if(typeof target !== 'object' || target == null){
return target;
}
if(Array.isArray(target)){ // 拦截数组 给数组的方法进行了重写
Object.setPrototypeOf(target,proto); // 写个循环 赋予给target
// target.__proto__ = proto;
for(let i = 0; i< target.length ;i++){
observer(target[i]);
}
}else{
Object.keys(target).forEach((key) => {
defineReactive(target, key, target[key]);
});
}
}
复制代码
let oldArrayPrototype = Array.prototype;
let proto = Object.create(oldArrayPrototype); // 继承
['push','shift','unshift'].forEach(method=>{
proto[method] = function(){ //函数劫持 把函数进行重写 内部 继续调用老的方法
updateView(); // 切片编程
oldArrayPrototype[method].call(this,...arguments)
}
});
function observer(target){
if(typeof target !== 'object' || target == null){
return target;
}
if(Array.isArray(target)){ // 拦截数组 给数组的方法进行了重写
Object.setPrototypeOf(target,proto); // 写个循环 赋予给target
// target.__proto__ = proto;
for(let i = 0; i< target.length ;i++){
observer(target[i]);
}
}else{
Object.keys(target).forEach((key) => {
defineReactive(target, key, target[key]);
});
}
}
function defineReactive(target,key,value){
observer(value); // 递归 我就将这个对象 继续拦截
Object.defineProperty(target,key,{
get(){ // get 中会进行依赖收集
return value
},
set(newValue){
if(newValue !== value){
// data.age = {n:200}; data.age.n = 300;这种状况就须要从新观察
observer(newValue)
updateView()
value = newValue
}
}
});
}
function updateView(){
console.log('我动啦')
}
class Vue {
constructor(options) {
this._data = options.data;
observer(this._data);
}
}
// 测试
let o = new Vue({
data: {
test: "I am test."
}
});
o._data.test = "hello,world."; /* 我动了 */
复制代码