数据劫持
- 将来我在使用框架的时候(vue), 框架目前都支持一个"数据驱动视图"
- 完成数据驱动视图,需要借助 数据劫持帮助我们完成
- 以原始数据为基础,对数据进行一份复刻
- 复刻出来的数据是不允许修改的,值从原始数据里面获取
- 语法: Object.defineProperty('哪一个对象', '属性','配置项')
- 配置项:
- value: 这个属性对应的值
- writable:该属性是否可以被重写,默认是false不允许被修改
- enumerable: 该属性能否被枚举,默认是false不能被枚举
- get: 是一个函数,叫做getter获取器,可以决定当前属性的值,不能与value 和 writable同时出现
- set: 是一个函数,叫做setter设置器,当你需要修改这个属性的时候,会触发该函数
- 配置项:
const obj = {}
obj.name = '张三'
Object.defineProperty(obj, 'age', {
// value: 18,
// writable: true,
enumerable:true,
get() {
console.log('你现在访问了obj的age属性,然后这函数内可以做很多事')
return 300
},
set(val) {
console.log('你现在想要修改obj的age属性,所以触发了setter函数,你想要设置的值为:', val)
}
})
obj.age = 99
// console.log(obj)
// console.log(obj.age)
数据劫持与渲染页面
<h1 class="box"></h1>
<h1 class="box2"></h1>
<script>
const box = document.querySelector('.box')
const box2 = document.querySelector('.box2')
// 原始对象
const obj = {}
obj.name = '张三'
obj.age = 18
// 将obj的属性,劫持到这个对象中
const res = {}
// 数据劫持
Object.defineProperty(res, 'age', {
enumerable: true,
get() {
// console.log('你现在访问了obj的age属性,然后这函数内可以做很多事')
return obj.age
},
set(val) {
// console.log('你现在想要修改obj的age属性,所以触发了setter函数,你想要设置的值为:', val)
box.innerHTML = `res年龄: ${val}`
obj.age = val
}
})
res.age = 100
console.log(res)
box.innerHTML = `res年龄: ${res.age}`
box2.innerHTML = `obj年龄: ${obj.age}`
</script>
封装数据劫持
<h1 class="box"></h1>
<h1 class="box2"></h1>
<script>
const box = document.querySelector('.box')
const box2 = document.querySelector('.box2')
// 原始对象
const obj = {}
obj.name = '张三'
obj.age = 18
// 如果劫持的属性多了,原本的写法不太方便,代码量比较多,所以封装数据劫持
function observer(origin, callback) {
// 1. 创建一个对象,将origin内部的属性劫持到这个对象内
const target = {}
// 2. 劫持origin上的属性到target中
for(let key in origin) {
// console.log(key)
Object.defineProperty(target, key, {
enumerable: true,
get() {
// console.log('你现在访问了obj的age属性,然后这函数内可以做很多事')
return origin[key]
},
set(val) {
// console.log('你现在想要修改obj的age属性,所以触发了setter函数,你想要设置的值为:', val)
// box.innerHTML = `res年龄: ${val}`
origin[key] = val
callback(target)
}
})
}
// 3. 将劫持后的target返回出去
return target
}
// const newObj = observer(obj, () => {console.log('回调函数执行的时候,会打印我这个字符串')})
const newObj = observer(obj, fn)
// console.log(newObj)
// newObj.age = 999
// newObj.name = '李四'
// 创建一个数据劫持后的对象
function fn(res) {
box.innerHTML = `年龄: ${res.age}; 名字: ${res.name}`
}
newObj.age = 666
newObj.name = '李四'
数据劫持升级
- 基础版数据劫持语法:Object.defineProperty(那个对象,属性,{配置项})
- 升级版数据劫持语法:Object.defineProperties('那个对象', '配置项')
<div class="box1"></div>
<div class="box2"></div>
<script>
const box1 = document.querySelector('.box1')
const box2 = document.querySelector('.box2')
const obj = {}
obj.age = 18
obj.name = '张三'
// 将数据劫持后的对象属性存放在res对象中
const res = {}
// 基础版
/*Object.defineProperties(res, {
age: {
get() {
return obj.age
},
set(val) {
box2.innerHTML = `res对象的age属性: ${val}, name属性: ${res.name}`
obj.age = val
}
},
name: {
get() {
return obj.name
},
set(val) {
box2.innerHTML = `res对象的age属性: ${res.age}, name属性: ${val}`
obj.name = val
}
}
})*/
// 利用循环 优化代码量
for(let key in obj) {
Object.defineProperties(res, {
// 此处的key 我们的需求是当一个变量使用,如果直接写 那么会当成一个字符串,解决方案在key加一个[]包裹起来,当成变量
[key]: {
get() {
return obj[key]
},
set(val) {
obj[key] = val
box2.innerHTML = `res对象的age属性: ${res.age}, name属性: ${res.name}`
}
},
})
}
// console.log(res)
// 首次打开页面的时候,给页面做一个赋值
box1.innerHTML = `obj对象的 age属性: ${obj.age}, name属性: ${obj.name}`
box2.innerHTML = `res对象的 age属性: ${res.age}, name属性: ${res.name}`
// 首次渲染完毕页面后 更改两个对象的属性值
obj.age = 666 //obj的修改不会影响 box1
obj.name = '李四'
res.age = 999 //res的修改会触发set函数,set函数内有一行代码会让box2更新,所以res的修改会让页面重新渲染
res.name = '王五'
<div class="box"></div>
<script>
const box = document.querySelector('.box')
const obj = {}
obj.age = 18
obj.name = '张三'
// 将数据劫持后的对象属性存放在res对象中
// const res = {}
// // 利用循环 优化代码量
// for(let key in obj) {
// Object.defineProperties(res, {
// // 此处的key 我们的需求是当一个变量使用,如果直接写 那么会当成一个字符串,解决方案在key加一个[]包裹起来,当成变量
// [key]: {
// get() {
// return obj[key]
// },
// set(val) {
// obj[key] = val
// box2.innerHTML = `res对象的age属性: ${res.age}, name属性: ${res.name}`
// }
// },
// })
// }
// 升级版:自己劫持自己(全都在原始对象上进行操作)
for(let key in obj) {
Object.defineProperties(obj, {
/**
* 通常我们在处理""自己劫持自己"的时候,不会在对象的原属性上操作,而是复制出来一份一模一样的数据操作
* 为了和原属性相同,所以会在 原本属性前加一个下划线,用来区分
* */
['_' + key]: {
value: obj[key],
writable: true
},
[key]: {
get() {
return obj['_' + key]
},
set(val) {
obj['_' + key] = val
box.innerHTML = `obj对象的 age属性: ${obj.age}, name属性: ${obj.name}`
}
}
})
}
// 首次打开页面的时候,给页面做一个赋值
box.innerHTML = `obj对象的 age属性: ${obj.age}, name属性: ${obj.name}`
// 首次渲染完毕页面后 更改两个对象的属性值
obj.age = 666 //obj的修改不会影响 box1
obj.name = '李四'