一、数据劫持
将来我们在使用框架的时候(vue),框架目前都支持一个“数据驱动视图”
完成数据驱动视图需要介入数据劫持帮助我们完成
数据劫持:以原始数据为基础,对数据进行一份复刻
复刻出来的数据是不允许修改的,值从原始数据里面获取
语法:Object.defineProperty('哪一个对象', '属性', '配置项')
配置项以对象的形式书写
配置项:
value:这个属性对应的值
writable:该属性是否可以被重写,默认是false,不允许被修改
enumerable:该属性是否可以被枚举,默认是false不能被枚举到(枚举——遍历)
get:是一个函数,叫做 getter 获取器,可以决定当前属性的值,不能与 value writable 同时出现
set:是一个函数,叫做setter设置器,当你需要修改这个属性的时候,会触发该函数
const obj = {}
obj.name = '张三'
Object.defineProperty(obj, 'age', {
enumerable: true,
get(){
console.log('你现在访问了obj的age属性,然后这个函数内可以做很多事')
return 300
},
set(val){
console.log('你现在要修改obj的age属性,所以触发了setter函数,你想要设置的值为:', val)
}
})
obj.age = 99
console.log(obj.age)
二、数据劫持与渲染页面
HTML
<h1 class = "box"></h1>
<h1 class = "box2"></h1>
JS
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 = 88
console.log(res)
box.innerHTML = `res年龄:${res.age}`
box2.innerHTML = `obj年龄:${obj.age}`
三、封装函数劫持
// 这个函数由框架提供,我们直接使用即可
//如果劫持的属性多了,原本的写法不太方便,代码量比较多,所以封装数据劫持
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)
console.log(newObj)
function fn(res) {
box.innerHTML = `obj 年龄:${res.age};名字:${res.name}`
}
newObj.age = 999
newObj.name = '李四'
四、数据劫持升级
础版数据劫持语法:
Object.defineProperty(哪个对象, 属性, {配置项})
升级版数据劫持语法:
Object.defineProperties('哪个对象', '配置项')
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}, res对象的name属性:${res.name} `
// obj.age = val
// }
// },
// name: {
// get() {
// return obj.name
// },
// set(val) {
// box2.innerHTML = `res对象的age属性:${res.age}, res对象的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}, res对象的name属性:${res.name} `
}
}
})
}
// 首次打开页面的时候,给页面做一个赋值
box1.innerHTML = `obj对象的age属性:${obj.age}, obj对象的name属性:${obj.name} `
box2.innerHTML = `res对象的age属性:${res.age}, res对象的name属性:${res.name} `
// 首次渲染完毕页面后,更改两个对象的属性值
obj.age = 666 //obj的修改不会影响box1
obj.name = '李四'
// console.log(res)
res.age = 999 //res的修改会触发set函数,set函数内有一行代码会让box2更新,所以res的修改会让页面重新渲染
res.name = '王五'
五、数据劫持升级(自己劫持自己)
const box = document.querySelector('.box')
// 原始对象
const obj = {}
obj.age = 18
obj.name = '张三'
// const res = {} // 将数据劫持后的对象属性存放在res对象中
// for (let key in obj) {
// Object.defineProperties(res, {
// [key]: {
// get() {
// return obj[key]
// },
// set(val) {
// obj[key] = val
// box.innerHTML = `res对象的age属性:${res.age}, res对象的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}, obj对象的name属性:${obj.name} `
}
}
})
}
// 首次打开页面的时候,给页面做一个赋值
// box.innerHTML = `obj对象的age属性:${obj.age}, obj对象的name属性:${obj.name} `
// 首次渲染完毕页面后,更改两个对象的属性值
obj.age = 666 //obj的修改不会影响box1
obj.name = '李四'
六、数据代理
数据代理是官方给的一个名字,有不部分程序员还是习惯性的叫做数据劫持
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.age)
// console.log(res.name)
// console.log(res.abc)
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(success, failure) {
const timer = Math.ceil(Math.random() * 3000)
console.log('班长,去帮我买瓶水')
setTimeout(() => {
if (timer > 2500) {
console.log('买水失败,用时:超时', timer)
failure()
} else {
console.log('买水成功,用时:', timer);
success()
}
}, timer)
}
fn(
()=>{console.log('谢谢班长,我和你开玩笑的,退了吧!');},
()=>{console.log('买不到就别回来了');}
)
八、回调地狱
-
这不是我们写代码的时候出现的某个漏洞
-
只是我们在利用回调函数解决问题的时候,代码量多了之后的一个视觉体验
-
回调地狱的代码不利于我们去维护或者管理,所以后续再处理异步任务的时候,我们需要一些更简洁的方法
-
此时出现了一个东西叫做
promise,他也是一个异步代码的解决方案
// 调用fn函数,班长就会去买水
function fn(success, failure) {
const timer = Math.ceil(Math.random() * 3000)
console.log('班长,去帮我买瓶水')
setTimeout(() => {
if (timer > 2000) {
console.log('买水失败,用时:超时', timer)
failure()
} else {
console.log('买水成功,用时:', timer);
success()
}
}, timer)
}
/**
* 需求:
* 在班长买水失败后,让他再次去买水(重新调用fn函数)
*
* 新需求:如果班长第二次也失败了,让他继续去买水
*
* 新需求:第一次买水成功的时候,让班长再去买一箱饮料
*/
fn(
() => {
fn(
() => { console.log('班长买完水后,又买了一箱饮料'); },
() => { console.log('班长就买了一瓶水,他不愿意给你们买饮料'); }
)
},
() => {
fn(
() => { console.log('班长第二次买水成功了') },
() => {
fn(
() => { console.log('班长第三次买水成功了'); },
() => { console.log('班长第三次买水又失败了,确实不争气'); }
)
}
)
}
)