1.数据劫持
/**
* 数据劫持
* 将来我们在使用框架的时候(vue), 框架目前都支持一个 "数据驱动视图"
*
* 完成数据驱动视图 需要借助 数据劫持帮助我们完成
* 以原始数据为基础, 对数据进行一份复刻, 复刻出来数据是不允许修改的, 值从原始数据里面获取
*
* 语法: Object.defineProperty('哪一个对象', '属性', '配置项')
* 配置项:
* value: 这个属性对应的值
* writable: 该属性是否可以被重写, 默认是 false 不允许被修改
* enumerable: 该属性是否可以被枚举, 默认是 false 不能被枚举到
* configurable :
* get: 是一个函数, 叫做 getter 获取器, 可以决定当前属性的值, 不能与 value writable 同时出现
* set: 是一个函数, 叫做 setter 设置器, 当你需要修改这个属性的时候, 会触发该函数
*/
Object.defineProperty()
语法
Object.defineProperty(obj, prop, descriptor)
参数
-
obj 要定义属性的对象。
-
prop 要定义或修改的属性的名称或 Symbol 。
-
descriptor 要定义或修改的属性描述符。 返回值 被传递给函数的对象。
基础版
<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 = 99
box.innerHTML = `res 年龄: ${res.age}`
box2.innerHTML = `obj 年龄: ${obj.age}`
obj.age = 666
res.age = 777
// box2.innerHTML = `obj 年龄: ${obj.age}`
// obj.age = 777
// box2.innerHTML = `obj 年龄: ${obj.age}`
</script>
封装版 循环加封装
// 将来工作中: 这个函数由框架提供, 我们直接使用即可
// 为什么要封装函数: 如果劫持的属性多了, 原本的写法不太方便, 代码量比较多, 所以封装数据劫持
function observer(origin, callback) {
// 1. 创建一个对象, 将 origin 内部的属性劫持到这个对象内
const target = {}
// 2. 劫持 origin 上的属性到 target 中
for (let key in origin) {
Object.defineProperty(target, key, {
enumerable: true,
get() {
// console.log('你现在访问了 obj 的 age 属性, 然后这函数内可以做很多事')
return origin[key]
},
set(val) {
// console.log('你现在想要修改 obj 的 age 属性, 所以触发了 setter 函数, 你要想要设置的值为: ', val)
origin[key] = val
callback(target)
}
})
}
// 3. 将劫持后的 target 返回出去
return target
}
const box = document.querySelector('.box')
const box2 = document.querySelector('.box2')
// 原始对象
const obj = {}
obj.name = '张三'
obj.age = 18
// 创建一个数据劫持后的对象
const newObj = observer(obj, fn)
//回调函数
function fn(res) {
box.innerHTML = `年龄: ${res.age}; 名字: ${res.name}`
}
newObj.age = 666
newObj.name = '李四'
Object.defineProperties()
可以对多条属性进行修改
基础班
const obj = {}
obj.age = 18
obj.name = '张三'
// 基础版
const res = {} // 将数据劫持后的对象属性存放在 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
}
},
})
利用循环 优化代码量
const obj = {}
obj.name = '张三'
obj.age = 18
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}`
}
},
})
}
// 首次打开页面的时候, 给页面做一个赋值
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 = '王五'
升级 自己劫持自己
// 原始对象
const obj = {}
obj.age = 18
obj.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.name = '李四'
2.事件代理Proxy
const obj = {
name:'张三',
age:18
}
const res = new Proxy(obj,{
get(target,prokey){
//第一个形参 就是你要代理的这个对象,在当前案例中就是obj
//第二个形参:Jiushi该对象内部的某一个属性,自动分配
return target[prokey]
},
set(target,prokey,val){
//val 传参
target[prokey] = val
console.log(`形参的值${prokey},修改的值为${val}`)
}
})
res.age = 66
//在代理i完成后给原始对象新增一个属性,此时代理对象以然能够访问到(proxy 独有的功能)
obj.abc = 'qwer'
console.log(res.abc)