手写vue(上) defineProperty
通过Object.defineProperty函数可以对对象属性的设置和获取进行拦截
定义函数defineReactive
该函数传入对象,属性,值;对传入的对象的属性操作进行拦截。
每次获取此属性前打印get xxx
,每次对此属性设置值前打印set xxx 新值
function defineReactive(obj, key, val){
// 对传入obj进行访问拦截,注意:对数组无效
Object.defineProperty(obj, key, {
// val被引用,形成闭包不被释放,保存当前状态
// 暴露在函数作用域之外,下面用obj.xxx通过get方法在全局作用域能访问到
get(){
console.log('get '+key);
return val
},
set(newVal){
if(newVal !== val){
console.log('set ' + key + ': ' + newVal);
val = newVal
}
}
})
}
调用
const obj = {}
defineReactive(obj, 'foo', 'bar')
obj.foo // get foo
obj.foo = 'foobar' // set foo: foobar
在对属性set时进行一些界面更新
set(newVal){
if(newVal !== val){
console.log('set ' + key + ': ' + newVal);
val = newVal
// 更新函数
update()
}
}
function update(){
document.body.innerText = obj.foo
}
使用
defineReactive(obj, 'foo', 'bar')
// 页面加载显示时间
obj.foo = new Date().toLocaleTimeString()
// 每隔一秒后时间更新
setInterval(() => {
obj.foo = new Date().toLocaleTimeString()
},1000)
递归拦截,set拦截
假如对象多层嵌套,上面无法拦截
const obj = { foo: 'bar', baz: 'bar', foobar: { a: 1 } }
定义observe函数,对循环循环拦截
function observe(obj){
if(typeof obj !== 'object' || obj == null) return
Object.keys(obj).forEach(key => {
defineReactive(obj, key, obj[key])
})
}
对象嵌套的情况要递归拦截,如果传入的newVal依然是obj,要对其做响应式处理
function defineReactive(obj, key, val){
// 递归
observe(val)
// 对传入obj进行访问拦截
Object.defineProperty(obj, key, {
// val被引用,形成闭包不被释放,保存当前状态
get(){
console.log('get '+key);
return val
},
set(newVal){
if(newVal !== val){
console.log('set ' + key + ': ' + newVal);
// 如果传入的newVal依然是obj,要对其做响应式处理
observe(newVal)
val = newVal
}
}
})
}
当添加新属性,直接赋值无法拦截,需要经过defineProperty再拦截
function set(obj, key, val){
defineReactive(obj, key, val)
}
使用
const obj = { foo: 'bar', baz: 'bar', foobar: { a: 1 } }
// 遍历响应化处理
observe(obj)
obj.foo // get foo
obj.foo = 'foobar' // set foo: foobar
obj.baz // get baz
obj.baz = 'foobarbaz' // set baz: foobarbaz
obj.foobar.a = 10 // 先get foobar 后set a: 10
// 添加新属性
// obj.dong = 'dong'
set(obj, 'dong', 'dong')
obj.dong // get dong
Object.defineProperty没法拦截数组
替换数组原型中7个方法
push、pop、unshift、shift、reverse、splice、sort
// 数组响应式
// 1.替换数组原型中7个方法
const orginalProto = Array.prototype
// 备份一份,修改备份
const arrayProto = Object.create(orginalProto);
['push', 'pop', 'shift', 'unshift'].forEach(method => {
arrayProto[method] = function(){
// 原始操作
orginalProto[method].apply(this, arguments)
// 覆盖操作,通知更新
console.log('数组执行 '+ method +' 操作');
}
});
function defineReactive(obj, key, val){
// 递归
observe(val)
// 对传入obj进行访问拦截
Object.defineProperty(obj, key, {
// val被引用,形成闭包不被释放,保存当前状态
get(){
console.log('get '+key);
return val
},
set(newVal){
if(newVal !== val){
console.log('set ' + key + ': ' + newVal);
// 如果传入的newVal依然是obj,要对其做响应式处理
observe(newVal)
val = newVal
}
}
})
}
function observe(obj){
if(typeof obj !== 'object' || obj == null) return
// 判断传入obj类型
if(Array.isArray(obj)){
// 覆盖原型,替换7个变更操作
obj.__proto__ = arrayProto
// 对数组内部元素执行响应化
const keys = Object.keys(obj)
for(let i = 0; i< obj.length; i++){
observe(obj[i])
}
}else{
Object.keys(obj).forEach(key => {
defineReactive(obj, key, obj[key])
})
}
}
function set(obj, key, val){
defineReactive(obj, key, val)
}
// 响应式
const obj = { foo: 'bar', baz: 'bar', foobar: { a: 1 }, arr: [] }
// 遍历响应化处理
observe(obj)
obj.foo
obj.foo = 'foobar'
obj.baz
obj.baz = 'foobarbaz'
obj.foobar.a = 10
// 添加新属性
// obj.dong = 'dong'
set(obj, 'dong', 'dong')
obj.dong
obj.arr.push(1)
1
1
1
1
1
1
1
1
1
1