数据劫持
以原始数据为基础,对数据进行一份复刻,复刻出来的数据是不允许被修改的,值从原始数据里面获取。
- 语法:Object.defineProperty('哪一个对象','属性','{配置项}')。
- 配置项:
- value:这个属性对应的值。
- writable:该属性是否可以被重写,默认是false不允许被修改。
- enumerable:该属性是否可以被枚举,默认是false,不能被枚举到。
- get:是一个函数,叫做getter 获取器,可以决定当前该属性的值,不能与 value writable 同时出现。
- set:是一个函数,叫做setter 设置器,当你需要修改这个属性的时候,会触发该函数。
const obj = {}
obj.name = '张三'
Object.defineProperty(obj,name,{
enumerable:true,
get(){
console.log('你现在访问了 obj 的 age 属性, 然后这函数内可以做很多事')
return 66
}
set () {
console.log('你现在想要修改 obj 的 age 属性, 所以触发了 setter 函数, 你要想要设置的值为: ', val)
}
})
obj.age = 88
console.log(obj.age)
数据劫持与渲染页面
<h1 class="box"></h1>
<h1 class="box2"></h1>
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
//封装数据劫持的函数
function observer(origin,callback){
const targer = {}
for(let key in origin){
Object.defineProperty(target,key,{
enumerable:true,
get(){
return origin[key]
},
set(val){
origin[key] = val
callback(target)
}
})
}
return target
}
const box = document.querySelector('.box')
const box2 = document.querySelector('.box2')
const obj = {}
obj.name = '张三'
obj.age = 18
const newObj = new observer(obj,fn)
function fn (){
box.innerHTML = `年龄: ${res.age}; 名字: ${res.name}`
}
newObj.name = '王武'
newObj.age = 88
数据劫持升级
- 基础版数据劫持语法: Object.defineProperty(那个对象, 属性, {配置项})。
- 升级版数据劫持语法: Object.defineProperties('那个对象', '配置项')。
const box1 = document.querySelector('.box1')
const box2 = document.querySelector('.box2')
// 原始对象
const obj = {}
obj.age = 18
obj.name = '张三'
//利用循环 优化代码量
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.name = '李四'
obj.age = 666
res.name = '王五'
res.age = 888
//数据劫持升级版
<h1 class="box"></h1>
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 = '李四'
数据代理
是 官方给的一个名字, 有部分程序员还是习惯性的叫做 数据劫持
- proxy 是 ES6以后官方推出的 是一个内置构造函数
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}')
}
})
// 在代理完成后给原始对象新加一个属性, 此时代理对象依然能够访问到 (Proxy 独有的功能)
obj.abc = 'qwer'
console.log(res.abc)
res.age =888
回调函数
回调函数 本质上就是一个普通函数
- 一个函数A以实参的形式传递到函数B中,在函数B中,以形参的方式调用函数A,此时函数A就可以称为函数B的回调函数,回调函数的使用场景为异步代码的解决方案
// 网络请求模拟
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 他也是一个异步代码的解决方案
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 的状态为 失败')
})