一、 数据驱动视图
- 多次渲染页面,多的时候,比较麻烦和繁琐
const box = document.querySelector('.box')
const obj = {
name: 'QF666',
age: 18
}
box.innerHTML = `名字: ${obj.name}; 年龄: ${obj.age}`;
obj.age = 99;
box.innerHTML = `名字: ${obj.name}; 年龄: ${obj.age}`;
obj.name = 'QF999';
box.innerHTML = `名字: ${obj.name}; 年龄: ${obj.age}`;
console.log(obj);
二、数据劫持
- 将原始数据,劫持出一份一摸一样, 听起来有点像浅拷贝
- 劫持出来的数据, 默认是不可以修改的
语法: Object.defineProperty(那个对象, '对象的key', {配置项})
配置项:
1. value 访问这个值 之后, 得到结果
2. writable 决定当前这个属性能否被修改, 默认是 false
3. enumerable 决定当前这个属性能否被枚举, 决定当前这个属性能否被遍历到
4. getter 是一个函数, 是一个获取器, 当访问这个属性时, 会执行这个函数
getter不能和value、writable一起使用
5. setter 是一个函数, 是一个设置器, 当设置这个属性是, 会执行这个函数
1、初级版
const obj = {}
obj.name = 'QF666'
console.log(obj)
// Object.defineProperty(obj, str, {配置项})
Object.defineProperty(obj, 'age', {
// value: 'QF999',
// writable: true,
enumerable: true,
get() {
// console.log('你当前访问了这个age属性, 触发了get函数')
return 'qwerty'
},
set(val) {
console.log('你当前想要修改这个age属性, 修改的值是: ', val)
}
})
obj.age = 999
console.log(obj.age)
2、升级版
<div id="box"></div>
<script>
// 实现数据劫持
const box = document.querySelector('#box')
const obj = {
name: 'QF666',
age: 18
}
console.log('原始对象obj:', obj)
const res = {}
Object.defineProperty(res, 'name', {
get() {
return obj.name
},
set(val) {
// console.log('你想要修改这个属性的值, 新值为: ', val)
obj.name = val
box.innerHTML = `名字: ${res.name}; 年龄: ${res.age}`
}
})
Object.defineProperty(res, 'age', {
get() {
return obj.age
},
set(val) {
// console.log('你想要修改这个属性的值, 新值为: ', val)
obj.age = val
box.innerHTML = `名字: ${res.name}; 年龄: ${res.age}`
}
})
box.innerHTML = `名字: ${res.name}; 年龄: ${res.age}`
res.age = 100;
</script>
三、封装数据劫持
<input type="text" name="" id="inp">
<div id="box"></div>
函数的封装 :
const box = document.querySelector('#box')
const obj = {
name: 'QF666',
age: 18
}
function observer(origin, callback) {
const target = {}
for (let k in origin) {
Object.defineProperty(target, k, {
get() {
return origin[k]
},
set(val) {
// console.log('你现在想要修改的key是' , k, '修改的值为', val)
origin[k] = val
callback(target)
}
})
}
callback(target)
return target
}
调用的
function fn(res) {
box.innerHTML = `名字: ${res.name}; 年龄: ${res.age}`
}
const app = observer(obj, fn)
document.querySelector('#inp').oninput = function (e) {
app.age = e.target.value
}
四、封装数据劫持+渲染
- 模拟一个其他人写的代码, 框架的源码
- 把需要渲染的代码, 都放到HTML 中, 然后由我这段代码帮助我们去渲染
<div id="app">
<h1> {{msg}} </h1>
<h1> {{ name }} </h1>
<h1> {{ age }} </h1>
<h1> {{ abc }} </h1>
</div>
封装的
<script>
function observer(options) {
// 1. 验证root有没有传递
if (options.root === undefined) {
// console.log('您没有传递root属性, 请重新传递')
// 手动返回一个错误
throw new Error('您没有传递root属性, 请重新传递')
}
// 2. 验证root能否正确获取到节点
const rootHtml = document.querySelector(options.root)
if (rootHtml == null) {
// 手动返回一个错误
throw new Error('您传递 root 的属性, 有问题, 请检查后重新传递')
}
// 3. 验证是否传递data
if (options.data == undefined) {
throw new Error('您没有传递 data 属性, 请重新传递')
}
// 4. 验证data是否为一个对象
if (options.data.constructor !== Object) {
throw new Error('您传递的 data 不是一个对象, 请重新传递')
}
// 5. 数据劫持
// 存储一份最初的html文本
const rootHtmlStr = rootHtml.innerHTML
// 这里存储最开始的具有{{}}的HTML文本,
// 如果不写, 第一次渲染完毕, HTML中就没有花括号, 第二次渲染正则没有办法正确匹配
const _data = {}
for (let k in options.data) {
Object.defineProperty(_data, k, {
get() {
return options.data[k]
},
set(val) {
options.data[k] = val
// 5.2 数据更新后, 重新渲染页面
randers(rootHtml, _data, rootHtmlStr)
}
})
}
// 5.1 首次执行, 渲染页面
randers(rootHtml, _data, rootHtmlStr)
// 6. 返回一个劫持后的数据
return _data
}
function randers(root, _data, str) {
// 准备一个正则
const reg = /{{ *(\w+) *}}/g
// 匹配到节点中所有的花括号, 存放在一个数组中
const res = str.match(reg)
// 遍历数组, 拿到每一个花括号
res.forEach(item => {
// 拿到花括号内的文本, 因为这是对象中的 key
const key = reg.exec(str)[1]
// 将原本花括号与内部文本, 全部替换为对象中实际的值
str = str.replace(/{{ *(\w+) *}}/, _data[key])
})
// 重新将修改完毕的字符串, 渲染到页面
root.innerHTML = str
}
</script>
调用的
<script>
// 我们后续使用, 就在这里
const app = observer({
root: '#app',
data: {
msg: '你好',
name: 'QF001',
age: 18,
abc: '醒醒, 别睡了, 说你呢, 还睡'
}
})
</script>
五、数据劫持升级
<div id="root"></div>
1、基本写法
const obj = {
name: 'QF001',
age: 18
}
const res = {}
Object.defineProperty(res, 'name', {
get() {
return obj.name;
},
set(val) {
obj.name = val;
}
})
obj.newName = 'QF999';
console.log(obj);
document.querySelector('#root').innerHTML = `name: ${obj.name}`;
2、升级版
语法: Object.defineProperties(到那个对象, {
属性1: 配置项,
属性2: 配置项
})
const obj = {
name: 'QF001',
age: 18
}
const res = {}
Object.defineProperties(res, {
name: {
get() {
return obj.name
},
set(val) {
obj.name = val
}
},
age: {
get() {
return obj.age
},
set(val) {
obj.age = val
}
}
})
obj.newName = 'QF999';
console.log(obj);
document.querySelector('#root').innerHTML = `name: ${obj.name}`;
3、升级版(plus)
const obj = {
name: 'QF001',
age: 18
}
const res = {};
// 升级版(plus)
for (let k in obj) {
// k === 'name' 2. k === age
console.log(k);
Object.defineProperties(res, {
// 对象内部直接写 k 会帮当成字符串, 所以可以写成 [k], 将他识别为变量
[k]: {
get() {
return obj[k];
},
set(val) {
obj[k] = val;
}
},
})
}
obj.newName = 'QF999';
console.log(obj);
document.querySelector('#root').innerHTML = `name: ${obj.name}`;
3、升级版(super plus), 自己劫持自己
const obj = {
name: 'QF001',
age: 18
}
for (let k in obj) {
Object.defineProperties(obj, {
['_' + k]: {
// 我在我这个对象内把所有属性复制一份, 放在自己这个对象内部
value: obj[k],
writable: true
},
[k]: {
get () {
return obj['_' + k]
},
set(val) {
obj['_' + k] = val
document.querySelector('#root').innerHTML = `name: ${obj.name}`
}
}
})
}
obj.newName = 'QF999'
console.log(obj)
document.querySelector('#root').innerHTML = `name: ${obj.name}`