序文
「データバインディング」の鍵は、データの変更を監視することです。Vueデータの双方向バインディングは、パブリッシャー/サブスクライバーモデルと組み合わせたデータハイジャックによって実現されます。実際、ES5のObject.definePropertyメソッドは、主にオブジェクトのプロパティを追加または変更する操作を乗っ取って、ビューを更新するために使用されます。
vue3.0はObject.defineProperty()メソッドをプロキシに置き換えると聞きました。そのため、事前にいくつかの使用法を理解する必要があります。プロキシは、オブジェクトのプロパティではなく、オブジェクト全体を直接ハイジャックできます。ハイジャックする方法はたくさんあります。そして、ハイジャック後、最終的には新しいオブジェクトに戻ります。したがって、比較的言えば、この方法は非常に使いやすいです。しかし、互換性はあまり良くありません。
一、defineProperty
ES5はObject.definePropertyメソッドを提供します。このメソッドは、オブジェクトに新しいプロパティを定義したり、オブジェクトの既存のプロパティを変更してオブジェクトを返したりすることができます。
[1]文法
Object.defineProperty(obj, prop, descriptor)
パラメータ:
obj:必須、ターゲットオブジェクト
prop:必須、定義または変更するプロパティの名前
記述子:必須、定義または変更する属性の記述子
戻り値:
関数に渡されたオブジェクト、つまり最初のパラメーターobj
[2]記述子パラメーター分析
関数の3番目のパラメーター記述子で表される属性記述子には、データ記述子とアクセス記述子の2つの形式があります。
データ記述:オブジェクトの属性を変更または定義する場合、属性にいくつかの特性を追加します。データ記述の属性はオプションです。
- value:プロパティに対応する値。どのタイプの値でもかまいません。デフォルトは未定義です。
- 書き込み可能:属性の値を書き換えることができるかどうか。trueに設定すると、オーバーライドできます。falseに設定すると、オーバーライドできません。デフォルトはfalseです
- enumerable:この属性を列挙できるかどうか(for ... inまたはObject.keys()を使用)。列挙するにはtrueに設定し、列挙しない場合はfalseに設定します。デフォルトはfalseです
- 構成可能:ターゲット属性を削除できるかどうか、または属性(書き込み可能、構成可能、列挙可能)を再度変更できるかどうか。trueに設定すると、機能を削除またはリセットできます。falseに設定すると、機能を削除またはリセットできません。デフォルトはfalseです。この属性には2つの役割があります。1。削除を使用してターゲット属性を削除できるかどうか2.ターゲット属性を再度設定できるかどうか
アクセスの説明:アクセサーを使用して属性のプロパティを説明する場合、次のプロパティを設定できます。
- get:プロパティのgetter関数。getterがない場合、未定義です。このプロパティにアクセスすると、この関数が呼び出されます。実行中にパラメーターは渡されませんが、thisオブジェクトは渡されます(継承のため、これは必ずしもプロパティを定義するオブジェクトではありません)。この関数の戻り値は、属性の値として使用されます。デフォルトは未定義です。
- set:プロパティのセッター関数。セッターがない場合は未定義です。属性値が変更されると、この関数が呼び出されます。このメソッドは、パラメーター(つまり、割り当てられる新しい値)を受け入れ、割り当て時にこのオブジェクトを渡します。デフォルトは未定義です。
[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... 张三
2、プロキシ
プロキシオブジェクトは、基本的な操作(属性のルックアップ、割り当て、列挙、関数呼び出しなど)のカスタム動作を定義するために使用されます。
実際、プロキシオブジェクトは、ターゲットオブジェクトの操作の前にインターセプトを提供し、フィルタリングおよび書き換えを行うことができます。外部操作、および特定の変更これらの操作のデフォルトの動作。オブジェクト自体を直接操作することはできませんが、操作されたオブジェクトのプロキシオブジェクトを介して間接的にオブジェクトを操作し、目的を達成します。
[1]文法
const p = new Proxy(target, handler)
【2】パラメータ
target:プロキシによってラップされるターゲットオブジェクト(ネイティブ配列、関数、または別のプロキシを含む、任意のタイプのオブジェクトにすることができます)
ハンドラー:これもオブジェクトであり、その属性は、操作が実行されたときのエージェントの動作、つまりカスタム動作を定義する関数です。
[3]ハンドラーメソッド
ハンドラーオブジェクトは、特定のプロパティのバッチを保持するプレースホルダーオブジェクトです。プロキシのさまざまなトラップが含まれており、すべてのトラップはオプションです。キャッチャーが定義されていない場合、ソースオブジェクトのデフォルトの動作が保持されます。
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... 张三
3、definePropertyとプロキシの比較
Object.definePropertyはオブジェクトのプロパティのみを乗っ取ることができ、Proxyは直接プロキシオブジェクトです。
Object.definePropertyはプロパティのみをハイジャックできるため、オブジェクトの各プロパティをトラバースする必要があります。プロパティ値もオブジェクトである場合は、深いトラバースが必要です。また、プロキシはオブジェクトを直接プロキシします。トラバーサル操作は必要ありません。Object.definePropertyには、新しいプロパティを手動で監視する必要があります。
Object.definePropertyはオブジェクトのプロパティをハイジャックするため、プロパティを追加するときは、オブジェクトを再トラバースして(プロパティを変更しても、セッターは自動的にトリガーされません)、Object.definePropertyを使用して新しいプロパティをハイジャックする必要があります。
このため、vueを使用してデータ内の配列またはオブジェクトに属性を追加する場合は、vm。$ setを使用して、追加された属性も応答するようにする必要があります。definePropertyは元のオブジェクトを汚染します(主な違い)
プロキシプロキシをobに、元のオブジェクトobに変更を加えずに新しいプロキシオブジェクトを返しますが、definepropertyはメタオブジェクトを変更してメタのプロパティを変更します。オブジェクト、プロキシはメタオブジェクトプロキシ専用であり、新しいプロキシオブジェクトを提供します。
4つ目は、双方向のデータバインディングを実現することです。
[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で「フロントエンドコレクション 」を検索し て初めて読むと、[ビデオ] [ブック]に返信して、200Gのビデオ資料と30のPDFブック資料を受け取ることができます。