数据劫持
1. 数据劫持
- 将来我们在使用框架的时候(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
2. 数据劫持与渲染页面
<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 = 999
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>
3. 封装数据劫持
// 将来工作中: 这个函数由框架提供, 我们直接使用即可
// 为什么要封装函数: 如果劫持的属性多了, 原本的写法不太方便, 代码量比较多, 所以封装数据劫持
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 = '李四'
4. 数据劫持升级
数据劫持升级
- 基础版数据劫持语法: Object.defineProperty(那个对象, 属性, {配置项})
- 升级版数据劫持语法: Object.defineProperties('那个对象', '配置项')
const box1 = document.querySelector('.box1')
const box2 = document.querySelector('.box2')
// 原始对象
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 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 = '王五'
5.数据劫持升级2
<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
obj.name = '李四'
</script>
6. 数据代理
- 是 官方给的一个名字, 有部分程序员还是习惯性的叫做 数据劫持
- proxy 是 ES6以后官方推出的 是一个内置构造函数
const obj = {
name: '张三',
age: 18
}
// new Proxy 第一个参数 要代理的对象, 第二个参数 一些配置项, 最后会返回一个代理后的对象, 我们需要使用一个变量去接收
const res = new Proxy(obj, {
get(target, property) {
/**
* 第一个形参: 就是你要代理的这个对象, 在当前案例中指的就是 obj
* 第二个形参: 就是该对象内部的某一个属性, 自动分配
*/
return target[property]
},
set(target, property, val) {
target[property] = val
console.log(`你现在想要修改 形参target 的 ${property} 属性, 修改的值为 ${val}, 除此之外你还可以做很多事`)
}
})
// 在代理完成后给原始对象新加一个属性, 此时代理对象依然能够访问到 (Proxy 独有的功能)
obj.abc = 'qwer'
console.log(res.abc)
// console.log(res.age)
// console.log(res.name)
res.age = 66
res.name = '李四'
回调函数
回调函数
本质上就是一个普通函数,一个函数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(chenggong, shibai) {
const timer = Math.ceil(Math.random() * 3000)
console.log('班长, 去帮我买瓶水')
setTimeout(() => {
if (timer > 2500) {
console.log('买水失败, 用时: ', timer)
shibai()
} else {
console.log('买水成功, 用时: ', timer)
chenggong()
}
}, timer)
}
fn(
() => { console.log('谢谢班长, 我和你开玩笑的, 退了吧!') },
() => { console.log('买不到就别回来了') }
)
回调地狱
这不是我们写代码的时候出现的某个漏洞,只是我们在利用回调函数解决问题的时候, 代码量多了之后的一个视觉体验
回调地狱的代码不利于我们去维护或者管理, 所以后续再处理异步任务的时候,我们需要一些更加简洁的方法
此时出现了一个东西 叫做 promise 他也是一个异步代码的解决方案
// 调用 fn 函数 班长就回去 买水
function fn(chenggong, shibai) {
const timer = Math.ceil(Math.random() * 3000)
console.log('班长, 去帮我买瓶水')
setTimeout(() => {
if (timer > 2900) {
console.log('买水失败, 用时: ', timer)
shibai()
} else {
console.log('买水成功, 用时: ', timer)
chenggong()
}
}, timer)
}
/**
* 需求:
* 在班长买水失败后, 让他再次去买水(重新调用 fn 函数)
*
* 新需求: 如果班长 第二次也失败了, 让他继续去买水
*
* 新需求: 第一次买水成功的时候, 让班长再去买一箱饮料
*/
fn(
() => {
fn(
() => { console.log('班长买完水后, 又买了一箱饮料') },
() => { console.log('班长就买了一瓶水, 他不愿意给你们买饮料') }
)
},
() => {
fn(
() => { console.log('班长第二次买水 成功了') },
() => {
fn(
() => { console.log('班长第三次买水 成功了') },
() => { console.log('班长第三次买水 又失败了, 确实不争气') }
)
}
)
}
)
promise
-
作用: 一种新的异步代码封装方案, 用来代替 回调函数的
-
promise 的三个状态
- => 持续: pending
- => 成功: fulfilled
- => 失败: rejected
-
promise 只会在持续状态转换为 成功,或者 从持续状态转换为 失败
-
promise 的基本语法: promise 是 JS 内置的一个构造函数
const p = new Promise() -
new Promise 得到的对象 我们叫做 promise 对象
-
promise 对象上 有一些方法
- => then 方法 (会在 promise 状态成功的时候 执行)
- => catch 方法 (会在 promise 状态失败的时候 执行)
const p = new Promise(function (resolve, reject) {
/**
* 形参名无所谓
* 第一个形参: 内部的值是一个函数, 调用之后可以将当前这个 promise 的状态设置为 成功
* 第二个形参: 内部的值是一个函数, 调用之后可以将当前这个 promise 的状态设置为 失败
*/
// 书写我们的异步代码
const timer = Math.ceil(Math.random() * 3000)
console.log('班长, 去帮我买瓶水')
setTimeout(() => {
if (timer > 1500) {
console.log('买水失败, 用时: ', timer)
// shibai()
reject()
} else {
console.log('买水成功, 用时: ', timer)
// chenggong()
resolve()
}
}, timer)
})
// console.log('打印 变量 p: ', p)
p.then(() => {
console.log('如果我这行内容打印, 说明 promise 的状态为 成功')
})
p.catch(() => {
console.log('如果我这行内容打印, 说明 promise 的状态为 失败')
})