Vueの中核でのデータハイジャック
Angular、Regular、Vue、Reactなどはデータバインディングを実装でき、手動でDOM操作を実行する必要がなくなり、実装の原則は基本的に脏检查
OR数据劫持
です。この記事はVueフレームワークから始まり、データハイジャックを実現するためのObject.definePropertyを学習します。
Object.defineProperty(obj、prop、descriptor)の使用法の紹介:
-
パラメータ
obj:ターゲットオブジェクト
prop:定義する必要のあるプロパティまたはメソッドの名前
記述子:ターゲット属性の特性
-
定義に利用できる機能のリスト
value:属性の値
書き込み可能:falseの場合、属性の値を書き換えることはできません。
get:ターゲット属性にアクセスすると、このメソッドがコールバックされ、このメソッドの結果がユーザーに返されます。
set:ターゲット属性が割り当てられると、このメソッドはコールバックされます。
構成可能:falseの場合、ターゲット属性を削除したり、次の属性(書き込み可能、構成可能、列挙可能)を変更しようとした場合は無効になります。
列挙可能:for ... inループでトラバースできるか、Object.keysにリストできるか
データハイジャックとは
上記のObject.definePropertyの紹介を通じて、オブジェクトのプロパティにアクセスまたは設定すると、対応する関数がトリガーされ、プロパティの値がこの関数に返されるか、設定されることを見つけるのは難しくありません。その場合、関数をトリガーするときに実行したいこと、つまり「ハイジャック」操作を確実に実行できます。
実はVueにあり通过Object.defineProperty来劫持对象属性的setter和getter操作,并“种下”一个监听器,当数据发生变化的时候发出通知
ます。簡単な例を挙げましょう。
var data = {
name:'lhl'
}
Object.keys(data).forEach(function(key){
Object.defineProperty(data,key,{
enumerable:true, // 是否能在for...in循环中遍历出来或在Object.keys中列举出来。
configurable:true, // false,不可修改、删除目标属性或修改属性性以下特性
get:function(){
console.log('get');
},
set:function(){
console.log('监听到数据发生了变化');
}
})
});
data.name //控制台会打印出 “get”
data.name = 'hxx' //控制台会打印出 "监听到数据发生了变化"
上記の例からわかるように、オブジェクトプロパティの設定と読み取りを完全に制御できます。
Vueでは、作成者は多くの場所でObject.definePropertyメソッドを巧みに使用しています。
Vueの原則:
1.オブジェクトプロパティの変更を監視します
これは、Vueがデータバインディングをノックするための正面玄関である必要があります。各オブジェクトのプロパティを監視することでサブスクライバー部門に追加し、データが変更されたときに通知を送信します。関連するソースコードは次のとおりです(作成者はES6 + flowを使用して記述し、コードはsrc / core / observer / index.jsモジュールにあります)。
export function defineReactive (
obj: Object,
key: string,
val: any,
customSetter?: Function
) {
const dep = new Dep()//创建订阅对象
const property = Object.getOwnPropertyDescriptor(obj, key)//获取obj对象的key属性的描述
//属性的描述特性里面如果configurable为false则属性的任何修改将无效
if (property && property.configurable === false) {
return
}
// cater for pre-defined getter/setters
const getter = property && property.get
const setter = property && property.set
let childOb = observe(val)//创建一个观察者对象
Object.defineProperty(obj, key, {
enumerable: true,//可枚举
configurable: true,//可修改
get: function reactiveGetter () {
const value = getter ? getter.call(obj) : val//先调用默认的get方法取值
//这里就劫持了get方法,也是作者一个巧妙设计,在创建watcher实例的时候,通过调用对象的get方法往订阅器dep上添加这个创建的watcher实例
if (Dep.target) {
dep.depend()
if (childOb) {
childOb.dep.depend()
}
if (Array.isArray(value)) {
dependArray(value)
}
}
return value//返回属性值
},
set: function reactiveSetter (newVal) {
const value = getter ? getter.call(obj) : val//先取旧值
if (newVal === value) {
return
}
//这个是用来判断生产环境的,可以无视
if (process.env.NODE_ENV !== 'production' && customSetter) {
customSetter()
}
if (setter) {
setter.call(obj, newVal)
} else {
val = newVal
}
childOb = observe(newVal)//继续监听新的属性值
dep.notify()//这个是真正劫持的目的,要对订阅者发通知了
}
})
}
上記はオブジェクトプロパティの変更を監視するVueです。問題は、データを渡すことが多い場合、それはオブジェクトではなく、おそらく配列である可能性があり、方法がないということです。答えは明らかにそうではありません。次に、作成者が配列の変更を監視する方法を見てみましょう。
アレイの変更を監視する
最初にこのソースコードを見てみましょう:
const arrayProto = Array.prototype//原生Array的原型
export const arrayMethods = Object.create(arrayProto)
;[
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse'
]
.forEach(function (method) {
const original = arrayProto[method]//缓存元素数组原型
//这里重写了数组的几个原型方法
def(arrayMethods, method, function mutator () {
//这里备份一份参数应该是从性能方面的考虑
let i = arguments.length
const args = new Array(i)
while (i--) {
args[i] = arguments[i]
}
const result = original.apply(this, args)//原始方法求值
const ob = this.__ob__//这里this.__ob__指向的是数据的Observer
let inserted
switch (method) {
case 'push':
inserted = args
break
case 'unshift':
inserted = args
break
case 'splice':
inserted = args.slice(2)
break
}
if (inserted) ob.observeArray(inserted)
// notify change
ob.dep.notify()
return result
})
})
...
//定义属性
function def (obj, key, val, enumerable) {
Object.defineProperty(obj, key, {
value: val,
enumerable: !!enumerable,
writable: true,
configurable: true
});
}
上記のコードは、主に配列自体のプロトタイプメソッドを継承し、ハイジャックの変更を行って通知することができます。オブザーバーのデータステージで、Vueはそれが配列であるかどうかを判断し、配列のプロトタイプを変更します。この場合、ハイジャックプロセス中に、配列に対する後続の操作を制御できます。Vueのアイデアを組み合わせて、理解を深めるために小さなデモを作成します。
var arrayMethod = Object.create(Array.prototype);
['push','shift'].forEach(function(method){
Object.defineProperty(arrayMethod,method,{
value:function(){
var i = arguments.length
var args = new Array(i)
while (i--) {
args[i] = arguments[i]
}
var original = Array.prototype[method];
var result = original.apply(this,args);
console.log("已经控制了,哈哈");
return result;
},
enumerable: true,
writable: true,
configurable: true
})
})
var bar = [1,2];
bar.__proto__ = arrayMethod;
bar.push(3);//控制台会打印出 “已经控制了,哈哈”;并且bar里面已经成功的添加了成员 ‘3’
プロセス全体は問題ないようです。Vueは完璧だったようです。実際、Vueはまだデータ項目と配列の長さの変更を検出できません。たとえば、次の呼び出しを実行します。
vm.items[index] = "xxx";
vm.items.length = 100;
この呼び出しメソッドは避けようとします。本当に必要な場合、作成者は$ set演算の実装も支援しますが、ここでは紹介しません。
オブジェクト属性プロキシを実装する
通常の状況では、次のようにVueオブジェクトをインスタンス化します。
var VM = new Vue({
data:{
name:'lhl'
},
el:'#id'
})
データを操作するとき、VM.data.name = 'hxx'が正しいのは当然ですが、作成者はこれが十分に簡潔ではないと感じているため、VM.name = 'hxx'の可能性はプロキシによって実現されます。関連するコードは次のとおりです。
function proxy (vm, key) {
if (!isReserved(key)) {
Object.defineProperty(vm, key, {
configurable: true,
enumerable: true,
get: function proxyGetter () {
return vm._data[key]
},
set: function proxySetter (val) {
vm._data[key] = val;
}
});
}
}
一見、VM.nameを操作しているように見えますが、実際には、Object.defineProperty()のgetメソッドとsetメソッドをハイジャックすることで実現されています。
まとめ
Vueフレームワークは、Object.defineProperty()メソッドをうまく利用して、データの双方向バインディングを実現し、モジュール間の適切なデカップリングも実現します。
研究ノートの記録と同様に、次の記事を参照してください:https://my.oschina.net/pengpengpengone/blog/1837257