1. 数据劫持概念
将来我们在使用框架的时候(vue), 框架目前都支持一个 "数据驱动视图"
完成数据驱动视图 需要借助 数据劫持帮助我们完成
以原始数据为基础, 对数据进行一份复刻, 复刻出来数据是不允许修改的, 值从原始数据里面获取
基础版数据劫持语法: Object.defineProperty(那个对象, 属性, {配置项})
升级版数据劫持语法: Object.defineProperties('那个对象', '配置项')
配置项:
value: 这个属性对应的值
writable: 该属性是否可以被重写, 默认是 false 不允许被修改
enumerable: 该属性是否可以被枚举, 默认是 false 不能被枚举到
get: 是一个函数, 叫做 getter 获取器, 可以决定当前属性的值, 不能与 value writable 同时出现
set: 是一个函数, 叫做 setter 设置器, 当你需要修改这个属性的时候, 会触发该函数
1)初级案例 Object.defineProperty
<body>
<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
const res = {}
Object.defineProperty(res, 'age', {
enumerable: true,
get() {
return obj.age
},
set(val) {
box.innerHTML = `res 年龄: ${val}`
obj.age = val
}
})
res.age = 999
box.innerHTML = `res 年龄: ${res.age}`
box2.innerHTML = `obj 年龄: ${obj.age}`
res.age = 777
</script>
</body>
2)封装数据劫持 Object.defineProperty
将来工作中: observer这个函数由框架提供, 我们直接使用即可
为什么要封装函数: 如果劫持的属性多了, 原本的写法不太方便, 代码量比较多, 所以封装数据劫持
<body>
<h1 class="box"></h1>
<h1 class="box2"></h1>
<script>
function observer(origin, callback) {
const target = {}
for (let key in origin) {
Object.defineProperty(target, key, {
enumerable: true,
get() {
return origin[key]
},
set(val) {
origin[key] = val
callback(target)
}
})
}
return target
}
function fn(res) {
box.innerHTML = `年龄: ${res.age}; 名字: ${res.name}`
}
const box = document.querySelector('.box')
const box2 = document.querySelector('.box2')
const obj = {}
obj.name = '张三'
obj.age = 18
const newObj = observer(obj, fn)
newObj.age = 666
newObj.name = '李四'
</script>
</body>
3) 数据劫持升级 Object.defineProperties
<body>
<h1 class="box1"></h1>
<h1 class="box2"></h1>
<script>
const box1 = document.querySelector('.box1')
const box2 = document.querySelector('.box2')
// 原始对象
const obj = {}
obj.age = 18
obj.name = '张三'
基础版
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
}
},
})
// 利用循环 将上面的代码块 优化
// 此处的 key 我们的需求是当一个变量使用, 如果直接写 那么会当成一个字符串, 解决方案在 key 加一个 [] 包裹起来,
当成一个变量
const res = {}
for (let key in obj) {
Object.defineProperties(res, {
[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 = '王五'
</script>
</body>
4)数据劫持升级2 自己劫持自己
/**
* 通常我们在处理 "自己劫持自己" 的时候, 不会在对象的原属性上操作, 而是复制出来一份一模一样数据操作
* 为了和原属姓名相同, 所以会在 原本的属性名前 加一个下划线, 用来区分 给 obj添加_obj和_age属性,用来存储数据
*/
<body>
<h1 class="box"></h1>
<script>
const box = document.querySelector('.box')
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
</script>
</body>
2. 数据代理
* 是 官方给的一个名字, 有部分程序员还是习惯性的叫做 数据劫持
* proxy 是 ES6以后官方推出的 是一个内置构造函数
<script>
const obj = {
name: '张三',
age: 18
}
const res = new Proxy(obj, {
get(target, property) {
return target[property]
},
set(target, property, val) {
target[property] = val
console.log(`修改 形参target 的 ${property} 属性, 修改的值为 ${val}`)
}
})
obj.abc = 'qwer'
console.log(res.abc)
console.log(res.age)
console.log(res.name)
console.log(obj.abc)
console.log(obj.age)
console.log(obj.name)
res.age = 66
res.add
console.log(res.add)
</script>
3. 回调函数
* 本质上就是一个普通函数
* 一个函数A以实参的形式传递到 函数B中。 在函数B中, 以形参的方式调用函数A
* 此时 函数A就可以称为 函数B 的回调函数。回调函数的使用场景为异步代码的解决方案
function A() {
console.log('我是函数A')
}
function B(callback) {
callback()
}
B(A)
function fn(callback = () => { }) {
console.log('班长, 帮我买瓶水')
setTimeout(() => {
console.log('班长, 买到水了')
callback()
}, 3000)
}
function jinnang() {
console.log('在帮买一箱水')
}
fn(jinnang)
function fn(A, B) {
const timer = Math.ceil(Math.random() * 3000)
console.log('班长, 去帮我买瓶水')
setTimeout(() => {
if (timer > 2500) {
console.log('买水失败, 用时: ', timer)
B()
} else {
console.log('买水成功, 用时: ', timer)
A()
}
}, timer)
}
fn(
() => {
console.log('谢谢班长, 我和你开玩笑的, 退了吧!')
},
() => {
console.log('买不到就别回来了')
}
)