数据响应式原理在面试的时候会经常遇到,今天搞懂了,分享一下。
//使用了观察者模式
//总体思路:
// 1、observe(){} 监听数据的函数
// 2、对象拦截函数
// 3、数组劫持函数
let data = {
username:'Ycenkun',
age:'18',
company:{
name:'杭州'
},
arr:[1,2,3,4,5,6]
}
//数组劫持函数
const proto = Object.create(Array.prototype)//创建一个新对象,使用现有对象为新创建的对象提供__proto__
//新对象proto具有数组原型上所有的方法
const arrMethods = ['push','pop','shift','unshift','join','indexof','slice','splice','reverse','sort']
arrMethods.map((item)=>{
proto[item] = function(){ //假如执行了push方法,那么就相当于执行了这个函数,而且这个proto已经成为data实例的原型方法
Array.prototype.push.call(data.arr,...arguments) //既然重写这个方法,那么就要真正再实现这个方法,即再利用原型上的方法,不过需要改变this指向
render(item,...arguments)
}
})
//需要监听数据的函数 【数据改变会走set(){},但是由于set不能识别对象 所以set函数中也会用到这里】
function observe(data){
// console.log(data)
//判断对象的类型
if(Array.isArray(data)){ //如果是数组,要用数组劫持来处理
// console.log(data)
data.__proto__ = proto //data是一个实例化的数组(我们不让他走自己的原型对象上的方法,
//而是我做一个必要的多此一举,自己控制原型对象的方法,所以触发原型对象的方法的时候,
//我们可以做一些自己想做的事情) data.__proto__就指向数组原型对象,而proto恰好是数组原型对象内的
//所有方法,那么即把数组的方法又全部继承给了data这个数组
} //data的方法是继承于proto 然而proto的方法被重写了
if(Object.prototype.toString.call(data) == '[object Null]'){ //空的不做任何处理
return
}
if(Object.prototype.toString.call(data) == '[object Object]'){ //如果是对象 交给对象拦截处理
// console.log(data)
for(let key in data){
reactive(data,key,data[key]) //调用对象拦截函数处理
}
}
}
//对象拦截函数
function reactive(obj,attr,value){
observe(value) //当初始值是对象的时候,需要重新再走一次监听函数 递归直到剥离出value不是对象而是个值
Object.defineProperty(obj,attr,{
get(){ //获得初始值
return value;
},
set(val){ //获得最新值,数据只要改变就要走到这里重新操作,所以数据一旦改变就会触发
// console.log(`${attr}发生了改变,变成${val}`); //监听到数据改变
// observe(val) //当改变的值是个对象的时候,需要再走一次监听函数,剥离出来最终改变的值
render(attr,val)
}
})
}
function render(who,res){
console.log(`${who}发生了改变,改变的值是${res}`)
}
observe(data)
//node.js环境下测试:
// data.username = 'Cooper';
// data.age = 25;
// data.company={
// name:'山东'
// }
//data.company.name='山东'
// data.arr.push(456)