머리말
"데이터 바인딩"의 핵심은 데이터 변경 사항을 모니터링하는 것입니다. Vue 데이터 양방향 바인딩은 게시자-구독자 모델과 결합 된 데이터 하이재킹을 통해 이루어집니다. 실제로 ES5의 Object.defineProperty 메서드는 주로 개체의 속성을 추가하거나 수정하여 뷰를 업데이트하는 작업을 가로채는 데 사용됩니다.
vue3.0이 Object.defineProperty () 메서드를 프록시로 대체한다고 들었습니다. 따라서 몇 가지 사용법을 미리 이해해야합니다. 프록시는 개체의 속성이 아닌 전체 개체를 직접 가로 챌 수 있으며이를 가로채는 방법은 여러 가지가 있습니다. 그리고 결국 하이재킹 후 새 개체로 돌아갑니다. 따라서 상대적으로 말하면이 방법은 사용하기 매우 쉽습니다. 그러나 호환성은 그리 좋지 않습니다.
一 、 defineProperty
ES5는 객체에 대한 새 속성을 정의하거나 객체의 기존 속성을 수정하고 객체를 반환 할 수있는 Object.defineProperty 메서드를 제공합니다.
[1] 문법
Object.defineProperty(obj, prop, descriptor)
매개 변수 :
obj : 필수, 대상 개체
prop : 필수, 정의하거나 수정할 속성의 이름
설명자 : 필수, 정의하거나 수정할 속성의 설명자
반환 값 :
함수에 전달 된 객체, 즉 첫 번째 매개 변수 obj
[2] 설명자 매개 변수 분석
함수의 세 번째 매개 변수 설명자가 나타내는 속성 설명자는 데이터 설명자와 액세스 설명 자의 두 가지 형식을 갖습니다.
데이터 설명 : 개체의 속성을 수정하거나 정의 할 때 속성에 몇 가지 특성을 추가하면 데이터 설명의 속성은 선택 사항입니다.
- value : 모든 유형의 값이 될 수있는 속성에 해당하는 값, 기본값은 정의되지 않음
- 쓰기 가능 : 속성 값을 다시 쓸 수 있는지 여부입니다. true로 설정하면 무시할 수 있습니다. false로 설정하면 무시할 수 없습니다. 기본값은 거짓입니다.
- enumerable : 이 속성을 열거 할 수 있는지 여부 (for ... in 또는 Object.keys () 사용). 열거하려면 true로 설정하고 열거하지 않으려면 false로 설정합니다. 기본값은 거짓입니다.
- configurable : 대상 속성을 삭제할 수 있는지 또는 속성 (쓰기 가능, 구성 가능, 열거 가능)을 다시 수정할 수 있는지 여부입니다. true로 설정하면 기능을 삭제하거나 재설정 할 수 있으며 false로 설정하면 기능을 삭제하거나 재설정 할 수 없습니다. 기본값은 거짓입니다. 이 속성은 두 가지 역할을합니다. 1. 삭제를 사용하여 대상 속성을 삭제할 수 있는지 여부 2. 대상 속성을 다시 설정할 수 있는지 여부
액세스 설명 : 접근자를 사용하여 특성의 속성을 설명하는 경우 다음 속성을 설정할 수 있습니다.
- get : 속성의 getter 함수로, getter가 없으면 정의되지 않습니다. 이 속성에 액세스하면이 함수가 호출됩니다. 실행 중에 매개 변수가 전달되지 않지만이 객체는 전달됩니다 (상속성 때문에 여기에서 속성을 정의하는 객체 일 필요는 없습니다). 이 함수의 반환 값은 속성 값으로 사용됩니다. 기본값은 정의되지 않습니다.
- set : 속성의 setter 함수로 setter가 없으면 정의되지 않습니다. 속성 값이 수정되면이 함수가 호출됩니다. 이 메서드는 매개 변수 (즉, 할당 할 새 값)를 받아들이고 할당시 this 개체를 전달합니다. 기본값은 정의되지 않습니다.
[3] 예
- 값
let obj = {}
// 不设置value属性
Object.defineProperty(obj, "name", {});
console.log(obj.name); // undefined
// 设置value属性
Object.defineProperty(obj, "name", {
value: "Demi"
});
console.log(obj.name); // Demi
- 쓰기 가능
let obj = {}
// writable设置为false,不能重写
Object.defineProperty(obj, "name", {
value: "Demi",
writable: false
});
//更改name的值(更改失败)
obj.name = "张三";
console.log(obj.name); // Demi
// writable设置为true,可以重写
Object.defineProperty(obj, "name", {
value: "Demi",
writable: true
});
//更改name的值
obj.name = "张三";
console.log(obj.name); // 张三
- 열거 할 수있는
let obj = {}
// enumerable设置为false,不能被枚举。
Object.defineProperty(obj, "name", {
value: "Demi",
writable: false,
enumerable: false
});
// 枚举对象的属性
for (let attr in obj) {
console.log(attr);
}
// enumerable设置为true,可以被枚举。
Object.defineProperty(obj, "age", {
value: 18,
writable: false,
enumerable: true
});
// 枚举对象的属性
for (let attr in obj) {
console.log(attr); //age
}
- 구성 가능
//-----------------测试目标属性是否能被删除------------------------//
let obj = {}
// configurable设置为false,不能被删除。
Object.defineProperty(obj, "name", {
value: "Demi",
writable: false,
enumerable: false,
configurable: false
});
// 删除属性
delete obj.name;
console.log(obj.name); // Demi
// configurable设置为true,可以被删除。
Object.defineProperty(obj, "age", {
value: 19,
writable: false,
enumerable: false,
configurable: true
});
// 删除属性
delete obj.age;
console.log(obj.age); // undefined
//-----------------测试是否可以再次修改特性------------------------//
let obj2 = {}
// configurable设置为false,不能再次修改特性。
Object.defineProperty(obj2, "name", {
value: "dingFY",
writable: false,
enumerable: false,
configurable: false
});
//重新修改特性
Object.defineProperty(obj2, "name", {
value: "张三",
writable: true,
enumerable: true,
configurable: true
});
console.log(obj2.name); // 报错:Uncaught TypeError: Cannot redefine property: name
// configurable设置为true,可以再次修改特性。
Object.defineProperty(obj2, "age", {
value: 18,
writable: false,
enumerable: false,
configurable: true
});
// 重新修改特性
Object.defineProperty(obj2, "age", {
value: 20,
writable: true,
enumerable: true,
configurable: true
});
console.log(obj2.age); // 20
- 설정하고 얻을
let obj = {
name: 'Demi'
};
Object.defineProperty(obj, "name", {
get: function () {
//当获取值的时候触发的函数
console.log('get...')
},
set: function (newValue) {
//当设置值的时候触发的函数,设置的新值通过参数value拿到
console.log('set...', newValue)
}
});
//获取值
obj.name // get...
//设置值
obj.name = '张三'; // set... 张三
두, 프록시
Proxy 객체는 기본 작업 (속성 조회, 할당, 열거, 함수 호출 등)의 사용자 정의 동작을 정의하는 데 사용됩니다.
실제로 대상 객체의 작업 전에 가로 채기를 제공하며이를 필터링하고 다시 작성할 수 있습니다. 외부 작업, 특정 동작 수정 이러한 작업의 기본 동작은 객체 자체를 직접 조작 할 수없고 원하는 목적을 달성하기 위해 조작 된 객체의 프록시 객체를 통해 간접적으로 객체를 조작합니다 ~
[1] 문법
const p = new Proxy(target, handler)
【2】 모수
target : 프록시에 의해 래핑 될 대상 개체 (기본 배열, 함수 또는 다른 프록시를 포함한 모든 유형의 개체 일 수 있음)
handler : 또한 객체이며 해당 속성은 작업이 수행 될 때 에이전트의 동작, 즉 사용자 지정 동작을 정의하는 함수입니다.
[3] 핸들러 방식
핸들러 개체는 특정 속성의 일괄 처리를 보유하는 자리 표시 자 개체입니다. Proxy의 다양한 트랩이 포함되어 있으며 모든 트랩은 선택 사항입니다. 캐처가 정의되지 않은 경우 소스 개체의 기본 동작이 유지됩니다.
handler.getPrototypeOf () | Object.getPrototypeOf 메소드의 캐처입니다. |
handler.setPrototypeOf () | Object.setPrototypeOf 메서드의 포수입니다. |
handler.isExtensible () | Object.isExtensible 메서드의 캐처입니다. |
handler.preventExtensions () | Object.preventExtensions 메서드의 캐처입니다. |
handler.getOwnPropertyDescriptor () |
Object.getOwnPropertyDescriptor 메소드의 캐처입니다. |
handler.defineProperty () | Object.defineProperty 메서드의 캐처입니다. |
handler.has () | in 연산자의 포수입니다. |
handler.get () | 속성 읽기 작업의 캐처입니다. |
handler.set () | 속성 설정 작업을위한 캐처입니다. |
handler.deleteProperty () | 삭제 연산자의 캐처입니다. |
handler.ownKeys () | Object.getOwnPropertyNames 메서드 및 Object.getOwnPropertySymbols 메서드의 캐처입니다. |
handler.apply () | 함수 호출 작업을위한 캐처입니다. |
handler.construct () | 새로운 대원을위한 포수. |
[4] 예
let obj = {
name: 'name',
age: 18
}
let p = new Proxy(obj, {
get: function (target, property, receiver) {
console.log('get...')
},
set: function (target, property, value, receiver) {
console.log('set...', value)
}
})
p.name // get...
p = {
name: 'dingFY',
age: 20
}
// p.name = '张三' // set... 张三
셋, defineProperty 및 프록시 비교
Object.defineProperty는 개체의 속성 만 하이재킹 할 수 있으며 Proxy는 직접 프록시 개체입니다.
Object.defineProperty는 속성 만 하이 잭 할 수 있으므로 객체의 각 속성을 트래버스해야하며 속성 값도 객체 인 경우 심층 탐색이 필요합니다. 프록시는 객체를 직접 프록시하므로 순회 작업이 필요하지 않습니다.Object.defineProperty에는 새 속성에 대한 수동 관찰이 필요합니다.
Object.defineProperty는 개체의 속성을 가로 채기 때문에 속성을 추가 할 때 개체를 다시 탐색 한 다음 (속성을 변경해도 setter가 자동으로 트리거되지 않음) Object.defineProperty를 사용하여 새 속성을 가로 채야합니다.
vue를 사용하여 데이터의 배열 또는 개체에 속성을 추가 할 때 vm. $ set을 사용하여 추가 된 속성도 응답하는지 확인해야하기 때문입니다.defineProperty는 원래 객체를 오염시킵니다. (주요 차이점)
프록시 프록시는 ob로, 원래 객체 ob를 변경하지 않고 새 프록시 객체를 반환합니다. 반면 defineproperty는 메타 객체를 수정하고 메타 객체의 속성을 수정하는 것입니다. 오브젝트, 프록시는 메타 오브젝트 프록시 전용이며 새 프록시 오브젝트를 제공합니다.
넷, 단순히 양방향 데이터 바인딩을 실현
[1] 새 myVue.js 파일을 만들고 myVue 클래스를 만듭니다.
class myVue extends EventTarget {
constructor(options) {
super();
this.$options = options;
this.compile();
this.observe(this.$options.data);
}
// 数据劫持
observe(data) {
let keys = Object.keys(data);
// 遍历循环data数据,给每个属性增加数据劫持
keys.forEach(key => {
this.defineReact(data, key, data[key]);
})
}
// 利用defineProperty 进行数据劫持
defineReact(data, key, value) {
let _this = this;
Object.defineProperty(data, key, {
configurable: true,
enumerable: true,
get() {
return value;
},
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) {
// 如果是标签 需要跟进元素attribute 属性区分v-html 和 v-model
let attrs = node.attributes;
[...attrs].forEach(attr => {
let attrName = attr.name;
let attrValue = attr.value;
if (attrName.indexOf("v-") === 0) {
attrName = attrName.substr(2);
// 如果是 html 直接替换为将节点的innerHTML替换成data数据
if (attrName === "html") {
node.innerHTML = this.$options.data[attrValue];
} else if (attrName === "model") {
// 如果是 model 需要将input的value值替换成data数据
node.value = this.$options.data[attrValue];
// 监听input数据变化,改变data值
node.addEventListener("input", e => {
this.$options.data[attrValue] = e.target.value;
})
}
}
})
if (node.childNodes.length > 0) {
this.compileNode(node);
}
} else if (node.nodeType === 3) {
// 如果是文本节点, 直接利用正则匹配到文本节点的内容,替换成data的内容
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 => {
let oldValue = this.$options.data[$1];
let reg = new RegExp(oldValue);
node.textContent = node.textContent.replace(reg, e.detail);
})
}
}
})
}
}
[2] html 파일에 myVue.js를 도입하고 예제 생성
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<script src="./mvvm.js" type="text/javascript"></script>
<title>Document</title>
</head>
<body>
<div id="app">
<div>我的名字叫:{
{name}}</div>
<div v-html="htmlData"></div>
<input v-model="modelData" /> {
{modelData}}
</div>
</body>
<script>
let vm = new myVue({
el: "#app",
data: {
name: "Demi",
htmlData: "html数据",
modelData: "input的数据"
}
})
</script>
</html>
【3】 효과
기사는 매주 지속적으로 업데이트되며 WeChat에서 " Front-end Collection "을 검색 하여 처음으로 읽고, [ 비디오 ] [ 도서 ]에 답장하여 200G 동영상 자료와 30 개의 PDF 도서 자료를받을 수 있습니다.