面接の質問:
1.Vue3 はデータ応答性をどのように実装していますか?
前提の要約:
上記に従って、Vue2 のコア実装原則の 1 つは、データの変更を検出する Object.defineProperty 関数であることがわかります。その欠点は、get がセット内のタイムリーな変更をキャプチャできないため、中間グローバル変数 tep が汚染することなく導入されることです。環境では、最終的にデータの変更を監視するために definReactive 関数をカプセル化しました。
実際の答え: Vue3 には、Ref と Reactive という 2 つのデータ応答性の実装方法があります。単純なデータ、つまり Ref で定義されたデータの場合は、setter と getter のデータをハイジャックすることで実装されています。大きな違いはありません。オブジェクト タイプ リアクティブ オブジェクト タイプ レスポンシブの実装 Vue3 は主にプロキシに依存しており、データ ハイジャック/プロキシを担当します。実際、プロキシは設計の初めに選択されましたが、プロキシは ES6 の新機能であり、より高いバージョン要件があるため、プロキシと Object.defineProperty の違いは、プロキシがソース データを直接操作するのではなく、まずソース データのプロキシ データを生成し、その後ゲッターとソース データを介してソース データを操作することです。 setters (1.1 などのコード) ですが、プロキシはオブジェクト タイプのみをプロキシできます。これは注意が必要な点です。プロキシ データはソース データを直接変更しないため、これにより問題が回避されます。つまり、set はデータを変更し、 get は時間内にキャプチャできません。プロキシのターゲットはソース データであり、get メソッドは Return リターンを渡してソース データにアクセスします。set メソッドでは、target[key] を通じてソース データを変更し、プロンプトに true を返します。プロキシはソース データを渡すことでプロキシとして機能し、get と set でデータの変更を監視し、ソース データを変更します。これが Vue2 です。Vue3 との違いに注意してください。その他の実装構造は Vue2 と似ています。Vue2
function reactive(data) {
//Proxy只能代理对象类型的数据,不是对象类型return回去
if (!isObject(data)) return
return new Proxy(data,{
get(target,key){
//在get操作时,收集依赖
track(target,key)
return target[key]
},
set(target,key,value){
target[key] = value
//在set操作时,触发副作用重新执行
trigger(target,key)
return true
}
})
}
コードスニペット 1.1
1. コレクションの依存関係の詳細な説明:
前提の要約:
上記に続いて、なぜ依存関係を収集する必要があるのかを Vue2 で説明しましたが、具体的にはどのように実装されるのでしょうか。
1. get 時に track 関数を使用して依存関係を収集します。依存関係の収集の具体的な実装は、グローバル変数を設定し、そのグローバル関数に副作用関数を代入し、関数を実行することです。副作用関数では多くの変数が使用されますが、例: state.name, this state.name データにアクセスすると、この時点で get メソッドがトリガーされます。実行後、null を割り当てる必要があります。これは、次の関数に便利で、繰り返しの実行を避けるためにも役立ちます。データの get メソッドがトリガーされると、依存関係の収集が実行されるため、これをクリアしない場合、無関係な依存関係に追加される可能性があり、依存関係がトリガーされたときに追跡依存関係収集関数が実行されます。初めて入力すると、弱いマッピング関係が作成されます。ターゲットはオブジェクトです。たとえば、state->map[name:Set(fn,fn),age:Set(fn,fn)] の場合、ターゲットは次のようになります。対応するキー値が作成されていない場合は、name:Set( fn, fn) などの強力なマッピング関係が作成され、収集された依存関係はキーと値のペアの形式でマップに保存されます。このようなマッピング関係により、トリガーされたときに対応する依存関係を通知できます。このようなマッピング関係により、洗練された管理を実現できます。そうでない場合、オブジェクト内の 1 つの値を変更し、他の値を変更すると get メソッドがトリガーされる可能性があります。ただし、この方法では、各属性を正確にターゲットにし、対応する属性の get メソッドのみをトリガーできます。
function effect(fn){
if (typeof fn !== 'function') return
//记录正在执行的副作用函数
activeEffect = fn
//调用副作用函数
fn()
//重置全局变量
activeEffect = null
}
//收集依赖
function track(target,key) {
//当activeEffect不为null的时候才去添加到桶里面
if (!activeEffect) return
let depMap = bucket.get(target)
if(!depMap){
depMap = new Map()
bucket.set(target,depMap)
}
//设置一个中转
let depSet = depMap.get(key)
if(!depSet){
depSet = new Set()
depMap.set(key,depSet)
}
depSet.add(activeEffect)
}
2. set 関数で依存関係をトリガーする データが変更されると、get で収集された依存関係をループし、内部メソッドをトラバースして、変更されたデータに関連する依存関係を変更します。
//触发依赖
function trigger(target,key){
let depMap = bucket.get(target)
if (!depMap) return
// 从副作用函数桶中依次取出每一个元素(副作用函数)执行
let depSet = depMap.get(key)
if (depSet) {
depSet.forEach((fn)=>fn())
}
}
3. 実際、原理を理解するのはそれほど難しいことではありません。実装アイデアの創意工夫に加えて、依存関係バケットの設計にも重要な困難があります。コード スニペット 3.3 を通じて、いくつかの変更が加えられていることがわかりました。初めて、依存関係バケットのコレクションが配列として設計されるため、依存関係が繰り返し追加される状況に遭遇します。たとえば、同じ副作用関数が複数回出現します。配列自体の設計では削除されないため、重複すると、複数の同一の関数がトリガーされ、データがコレクション モードに変更されます。コレクションの利点は重複削除機能が備わっていることですが、設計上の欠陥は、1 つの属性のみが変更されるとすべての属性に通知されてしまうことです。オブジェクト内の属性。明らかに、これにより不要なオーバーヘッドが発生するため、属性名を設定できるように、データ型がマップに変更されます。名前を変更する場合、年齢などの他の属性には影響しませんが、変更には、state:{name:'zs',age:'18'},state1:{name:'ls ',age:'20'} など、2 つのオブジェクトの同じ属性名が含まれます。このようにして、両方のオブジェクトがname 属性にアクセスするときにトリガーされるため、弱いマッピング関係が導入されます。WeakMap() 構造はオブジェクトを属性名として追加するため、別の問題が回避されます。実際、オブジェクトはマップでも使用できます。属性名として、 Map を使用するとメモリ リークが発生しますが、WeakMap を使用するとこの問題を解決できます。WeakMap と Map の違いについては下部で詳しく説明します。
// 定义一个副作用函数桶,存放所有的副作用函数,每一个元素都是一个副作用函数
// const bucket = []
// const bucket = new Set() //修改数据为集合模式 集合模式自带去重
// const bucket = new Map() //修改结构为[name:Set(fn,fn),age:Set(fn,fn)]
const bucket = new WeakMap() //修改结构为[state -> Map[name:Set(fn,fn),age:Set(fn,fn)],state1 -> Map[],]
// 定义一个全局变量,保存当前正在执行的副作用函数
コードスニペット 3.3
コードを完成させる: 値を割り当てて実行する
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<!-- 抽离方法出去,直接引入即可使用,组件化 -->
<script src="./reactive.js"></script>
</head>
<body>
<div id="app">xiaopang</div>
<script>
//定义一个响应式数据: 触发者
const state = reactive({name:'123',age:18})
const state1 = reactive({name:'666',age:18})
//定义一个副作用函数:响应者
effect(function e1(){
app.innerHTML = state.name
console.log("e1.....",state.name)
})
effect(function e2(){
console.log("e2.....",state1.name)
})
// 当state.name改变时,重新执行对应副作用函数effect
// setTimeout(()=>{
// state.name = 'change'
// },1000)
console.log(bucket)
</script>
</body>
</html>
// 定义一个副作用函数桶,存放所有的副作用函数,每一个元素都是一个副作用函数
// const bucket = []
// const bucket = new Set() //修改数据为集合模式 集合模式自带去重
// const bucket = new Map() //修改结构为[name:Set(fn,fn),age:Set(fn,fn)]
const bucket = new WeakMap() //修改结构为[state -> Map[name:Set(fn,fn),age:Set(fn,fn)],state1 -> Map[],]
// 定义一个全局变量,保存当前正在执行的副作用函数
let activeEffect = null
function isObject(value){
return typeof(value) ==='object' && value !== null
}
function effect(fn){
if (typeof fn !== 'function') return
//记录正在执行的副作用函数
activeEffect = fn
//调用副作用函数
fn()
//重置全局变量
activeEffect = null
}
//收集依赖
function track(target,key) {
//当activeEffect不为null的时候才去添加到桶里面
if (!activeEffect) return
let depMap = bucket.get(target)
if(!depMap){
depMap = new Map()
bucket.set(target,depMap)
}
//设置一个中转
let depSet = depMap.get(key)
if(!depSet){
depSet = new Set()
depMap.set(key,depSet)
}
depSet.add(activeEffect)
}
//触发依赖
function trigger(target,key){
let depMap = bucket.get(target)
if (!depMap) return
// 从副作用函数桶中依次取出每一个元素(副作用函数)执行
let depSet = depMap.get(key)
if (depSet) {
depSet.forEach((fn)=>fn())
}
}
/**
* 创建响应式数据
* @param[object]:普通对象
* @return[proxy]:代理对象
*/
function reactive(data) {
//Proxy只能代理对象类型的数据,不是对象类型return回去
if (!isObject(data)) return
return new Proxy(data,{
get(target,key){
//在get操作时,收集依赖
track(target,key)
return target[key]
},
set(target,key,value){
target[key] = value
//在set操作时,触发副作用重新执行
trigger(target,key)
return true
}
})
}
ES6 の新しい構文:
WeakMap、Map、オブジェクト、配列の違い:
配列もキーと値のペア構造 (キー->値) です -------キーは数値のみです。
配列へのアクセスは arr[2] を介して行われます
オブジェクトもキーと値のペア構造 (キー->値) です -------キーは文字列のみです
オブジェクトへのアクセスは obj['name'] 文字を介して行われます。Obj.name は実際には obj['name'] であり、これは簡略化された記述方法です。
図に示すようにクールな操作がありますが、obj.0 を介してデータを取得することはできず、データにアクセスするには obj['0'] が必要です。
マップ: マッピング テーブル (キー->値) キーには、文字列、数値、オブジェクトなどの任意のタイプのデータを指定できます。
マップはオブジェクトの高度なバージョンとみなすことができます
const map = new Map()
const foo = {foo:1}
map.set(foo,1) // {foo:1} => 1
console.log(map)
WeakMap: 弱映射表 (key只能是一个对象)
const weakmap = new WeakMap()
const bar = {bar:2}
weakmap.set(bar,1)
console.log(weakmap)
//weakmap.set(2,3)
重要な違いの 1 つは次のとおりです (マップ定義によりメモリが発生します)
{
const map = new Map()
;(function(){
const foo = {foo:1} //在IIFE(自执行函数)定义的变量foo,执行完成后会被释放
map.set(foo,1) //由于将foo作为map的key,导致foo占用的空间没有被释放
})()
console.log(map.keys()) //通过调用map.keys方法可以证实说法,依旧能够找到foo这个对象
}
{
const weakmap = new WeakMap()
;(function(){
const foo = {foo:1}
weakmap.set(foo,1) //weakmap建立的是弱引用关系,不会保留foo的空间
})()
//当IIFE执行完后,foo的空间被释放
console.log(weakmap) //查看可知weakmap方法中没有map.keys这样的方法,方法较少
}
メモリ リークとは何かについて簡単に説明します。
環境内で名前が付けられたグローバル変数はメモリ空間を占有します。データが不要になった場合は、data.array 配列を削除するなど、時間内に破棄する必要があります。もちろん、JavaScript にも独自のガベージ コレクション メカニズムがありdelete data.array
ます data.array = null
。これは、マークの削除、参照カウント、手動で変数を空白に設定して変数を破棄することで削除できます。もちろん、クロージャーは変数の使用法の問題を解決する良い方法ですが、変数が時間内に破棄されない場合、変数を削除することはできません。次に使用する必要がある関数によって使用されます。
たとえば、工場では日勤と夜勤という 2 つのシフトがあり、全員が同じ職場を共有することができ、出勤したり、職場で機械を使用したり、出勤したり、勤務したりすることができる、というのが一般的な理解です。ただし、日勤の人が仕事を終えて、仕事後の打刻を忘れた場合、その人はワークステーションで機器を使用しなくても、機器を使用する権利が発生します。 「その人はまだ彼のものです。夜勤中の人は打刻をすることができず、マシンを正常に使用することができません。率直に言って、メモリリークはトイレを占拠して、うんざりもせず、生きていけないようなものです。「人」は、終了後も離れません。仕事は終わりました。
上記の例を補足すると、読んでみて厳密ではないことがわかりましたが、グローバル変数の定義は環境を汚染するだけでなく、セキュリティの問題という非常に重要な点もあります。この変数はグローバル変数として、コードの他の部分にもアクセスおよび変更される可能性があり、これは明らかにセキュリティ上の問題を引き起こし、元の関数が正常に使用されないなどのバグを引き起こすため、望ましくありません。 ①関数メソッドの実行 変数の安全性は保証されず、他のコードによって変更されたりアクセスされる可能性がある ②関数実行後、変数は時間内に破棄されずメモリを占有する
参考記事と画像:
[Jiege Classroom] Vue3.2のソースコード設計と実装対応の原則