目录
1. 数据劫持
1-1. 术语解释
访问或者修改对象的某个属性时,在访问和修改属性值时,除了执行基本的数据获取和修改操作以外,还基于数据的操作行为,以数据为基础去执行额外的操作;
当前最经典的数据劫持应用就是数据渲染,各大前端框架的核心功能都是基于数据渲染来实现。
1-2. 通过vue实现数据劫持的例子
<div id="app">
{{message}}
<div v-html="htmlDate"></div>
<input v-model="modelData" /> {{modelData}}
</div>
<script>
let vm = new Vue({
el: "#app",
data: {
message: "测试数据",
htmlDate: "html数据",
modelData: "双绑数据"
}
})
setTimeout(() => {
vm.message = "修改数据"
}, 1000)
</script>
2. defineProperty实现数据劫持
- 当数据不存在时,get会创建数据
let o = {};
Object.defineProperty(o, "message", {
get() {
console.log("get...");
return "测试数据";
},
set(newValue) {
console.log("set...", newValue);
}
})
console.log(o);
>{}
message: "测试数据"
>get message: ƒ get()
>set message: ƒ set(newValue)
>__proto__: Object
>get...
2-1. configurable属性——是否允许对对象进行配置
// configurable: false
let o = {};
Object.defineProperty(o, "message", {
configurable: false,
get() {
console.log("get...");
return "测试数据";
},
set(newValue) {
console.log("set...", newValue);
}
})
delete o.message;
console.log(o);
>{}
message: "测试数据"
>get message: ƒ get()
>set message: ƒ set(newValue)
>__proto__: Object
>get...
// configurable: true
let o = {};
Object.defineProperty(o, "message", {
configurable: true,
get() {
console.log("get...");
return "测试数据";
},
set(newValue) {
console.log("set...", newValue);
}
})
delete o.message;
console.log(o);
>{}
>__proto__: Object
2-2. enumerable属性——是否允许对对象进行枚举
// enumerable: false
let o = {};
Object.defineProperty(o, "message", {
enumerable: false,
get() {
console.log("get...");
return "测试数据";
},
set(newValue) {
console.log("set...", newValue);
}
})
for(let i in o){
console.log(i)
}
>【无输出】
// enumerable: true
let o = {};
Object.defineProperty(o, "message", {
enumerable: true,
get() {
console.log("get...");
return "测试数据";
},
set(newValue) {
console.log("set...", newValue);
}
})
for(let i in o){
console.log(i)
}
>message
3. 通过defineProperty实现vue数据劫持的过程
3-1. 数据渲染视图
<body>
<div id="app">
111{{message}}111
<div>
111
<div>
{{message}}
</div>
</div>
<div v-html="htmlData"></div>
<input v-model="modelData" /> {{modelData}}
</div>
</body>
<script>
let m = new myVue({
el: "#app",
data: {
message: "测试数据",
htmlData: "html数据",
modelData: "绑定的数据"
}
})
</script>
class myVue {
constructor(options) {
this.$options = options;
this.compile();
}
compile() {
let el = document.querySelector(this.$options.el);
this.compileNode(el);
}
compileNode(el) {
let childNodes = el.childNodes;
childNodes.forEach(node => {
if (node.nodeType === 1) {
// 便签
let attrs = node.attributes;
[...attrs].forEach(attr => {
let attrName = attr.name;
let attrValue = attr.value;
if (attrName.indexOf("v-" === 0)) {
attrName = attrName.substr(2);
if (attrName === "html") {
node.innerHTML = this.$options.data[attrValue];
} else if (attrName === "model") {
node.value = this.$options.data[attrValue];
}
}
})
if (node.childNodes.length > 0) {
this.compileNode(node);
}
} else if (node.nodeType === 3) {
// 文本节点
let reg = /\{\{\s*(\S+)\s*\}\}/g;
let textContent = node.textContent;
if (reg.test(textContent)) {
let $1 = RegExp.$1;
node.textContent = node.textContent.replace(reg, this.$options.data[$1]);
}
}
});
}
}
3-2. 数据劫持的实现
observe(data) {
let keys = Object.keys(data);
console.log(keys);
keys.forEach(key => {
this.defineReact(data, key, data[key]);
})
}
defineReact(data, key, value) {
Object.defineProperty(data, key, {
configurable: true,
enumerable: true,
get() {
console.log("get...");
return value;
},
set(newValue) {
console.log("set...", newValue);
}
})
}
3-3. 数据驱动视图更新
- 事件绑定
this.addEventListener($1, e => {
//重新渲染视图
let oldValue = this.$options.data[$1];
let reg = new RegExp(oldValue);
node.textContent = node.textContent.replace(reg, e.detail);
})
- 事件触发
let event = new CustomEvent(key, {
detail: newValue
});
_this.dispatchEvent(event);
3-4. 双向绑定
- 通过数据劫持监听数据data[key]
set(newValue) {
console.log("set...", newValue);
let event = new CustomEvent(key, {
detail: newValue
});
_this.dispatchEvent(event);
value = newValue;
}
- 为每个key绑定其数据视图更新方法
this.addEventListener($1, e => {
//重新渲染视图
let oldValue = this.$options.data[$1];
let reg = new RegExp(oldValue);
node.textContent = node.textContent.replace(reg, e.detail);
})
- 监听输入框的值并改变data[key]
if (attrName === "model") {
node.value = this.$options.data[attrValue];
node.addEventListener("input", e => {
this.$options.data[attrValue] = e.target.value;
})
- 一旦输入框的值发生改变,就会改变data[key]中的值;defineProperty中的set()会监听到key对应的数据的变化;
从而调用key所绑定的重新渲染视图的方法而改变视图中的数据;
3-5. 完整代码
<body>
<div id="app">
111{{message}}111
<div>
111
<div>
{{message}}
</div>
</div>
<div v-html="htmlData"></div>
<input v-model="modelData" /> {{modelData}}
</div>
</body>
<script>
let m = new myVue({
el: "#app",
data: {
message: "测试数据",
htmlData: "html数据",
modelData: "绑定的数据"
}
})
console.log(m.$options.data);
setTimeout(() => {
m.$options.data.message = "修改的数据";
}, 1000)
</script>
class myVue extends EventTarget {
constructor(options) {
super();
this.$options = options;
this.compile();
this.observe(this.$options.data)
}
observe(data) {
let keys = Object.keys(data);
keys.forEach(key => {
this.defineReact(data, key, data[key]);
})
}
defineReact(data, key, value) {
let _this = this;
Object.defineProperty(data, key, {
configurable: true,
enumerable: true,
get() {
console.log("get...", value);
return value;
},
set(newValue) {
console.log("set...", newValue);
let event = new CustomEvent(key, {
detail: newValue
});
_this.dispatchEvent(event);
value = newValue;
}
})
}
compile() {
let el = document.querySelector(this.$options.el);
this.compileNode(el);
}
compileNode(el) {
let childNodes = el.childNodes;
childNodes.forEach(node => {
if (node.nodeType === 1) {
// 便签
let attrs = node.attributes;
[...attrs].forEach(attr => {
let attrName = attr.name;
let attrValue = attr.value;
if (attrName.indexOf("v-" === 0)) {
attrName = attrName.substr(2);
if (attrName === "html") {
node.innerHTML = this.$options.data[attrValue];
} else if (attrName === "model") {
node.value = this.$options.data[attrValue];
node.addEventListener("input", e => {
this.$options.data[attrValue] = e.target.value;
})
}
}
})
if (node.childNodes.length > 0) {
this.compileNode(node);
}
} else if (node.nodeType === 3) {
// 文本节点
let reg = /\{\{\s*(\S+)\s*\}\}/g;
let textContent = node.textContent;
if (reg.test(textContent)) {
let $1 = RegExp.$1;
node.textContent = node.textContent.replace(reg, this.$options.data[$1]);
this.addEventListener($1, e => {
//重新渲染视图
console.log("渲染视图的key:",$1)
let oldValue = this.$options.data[$1];
let reg = new RegExp(oldValue);
node.textContent = node.textContent.replace(reg, e.detail);
})
}
}
});
}
}
4. Proxy实现数据劫持
let obj = {
name: "张三",
age: 20
}
let newObj = new Proxy(obj,{
get(target,key){
console.log("get...",target[key]);
return target[key];
},
set(target,key,newValue){
console.log("set...",target[key],"为",newValue);
target[key] = newValue;
return true;
}
})
newObj.name;
>get... 张三
newObj.name = "李四";
>set... 张三 为 李四
4-1. 基于Proxy数据劫持对3.中代码的修改
- 将3.中的observe()和defineReact()简化为observe() — 简化了循环操作
observe(data){
this.$options.data = new Proxy(data,{
get(target,key){
return target[key];
},
set(target,key,newValue){
target[key] = newValue;
return true;
}
})
}
- 存在不能二次渲染视图的问题,因此需要绑定事件—在set()中添加绑定事件
set(target, key, newValue) {
let event = new CustomEvent(key, {
detail: newValue
});
_this.dispatchEvent(event);
target[key] = newValue;
return true;
}
4-2. 修改后的完整代码
class myVue extends EventTarget {
constructor(options) {
super();
this.$options = options;
this.compile();
this.observe(this.$options.data)
}
observe(data) {
let _this = this;
this.$options.data = new Proxy(data, {
get(target, key) {
return target[key];
},
set(target, key, newValue) {
let event = new CustomEvent(key, {
detail: newValue
});
_this.dispatchEvent(event);
target[key] = newValue;
return true;
}
})
}
compile() {
let el = document.querySelector(this.$options.el);
this.compileNode(el);
}
compileNode(el) {
let childNodes = el.childNodes;
childNodes.forEach(node => {
if (node.nodeType === 1) {
// 便签
let attrs = node.attributes;
[...attrs].forEach(attr => {
let attrName = attr.name;
let attrValue = attr.value;
if (attrName.indexOf("v-" === 0)) {
attrName = attrName.substr(2);
if (attrName === "html") {
node.innerHTML = this.$options.data[attrValue];
} else if (attrName === "model") {
node.value = this.$options.data[attrValue];
node.addEventListener("input", e => {
this.$options.data[attrValue] = e.target.value;
})
}
}
})
if (node.childNodes.length > 0) {
this.compileNode(node);
}
} else if (node.nodeType === 3) {
// 文本节点
let reg = /\{\{\s*(\S+)\s*\}\}/g;
let textContent = node.textContent;
if (reg.test(textContent)) {
let $1 = RegExp.$1;
node.textContent = node.textContent.replace(reg, this.$options.data[$1]);
this.addEventListener($1, e => {
//重新渲染视图
console.log("渲染视图的key:", $1)
let oldValue = this.$options.data[$1];
let reg = new RegExp(oldValue);
node.textContent = node.textContent.replace(reg, e.detail);
})
}
}
});
}
}