【フロントエンドソースコード解析】データレスポンシブの原則

参考:Vueソースコード解析シリーズ講座

シリーズノート:

この章のソースコード: https://gitee.com/szluyu99/vue-source-learn/tree/master/Data_Reactive_Study

コースの目的: Vue2 データ更新の原理を徹底的に理解する

データレスポンシブ

MVVM パターン:

侵入型および非侵入型:

Object.defineProperty()

参照ドキュメント: Object.defineProperty() - JavaScript | MDN (mozilla.org)

Object.defineProperty()これは、オブジェクト プロパティの変更を検出するために JavaScript エンジンによって提供される機能を使用して、データ ハイジャック/データ プロキシに使用されます。

このメソッドは、オブジェクトで直接新しいプロパティを定義するか、オブジェクトの既存のプロパティを変更して、このオブジェクトを返します。

var obj = {
    
    }

Object.defineProperty(obj, 'a', {
    
    
    value: 3, // 值
    writable: false, // 只读
    enumerable: true, // 可枚举
})

console.log(obj.a) // 3
obj.a++ // 只读的,不能修改
console.log(obj.a); // 3

メソッドの getter/setter が機能するには、変数のターンオーバーが必要です。

var obj = {
    
    }
var temp // 临时变量

Object.defineProperty(obj, 'a', {
    
    
    get() {
    
    
        console.log('get a');
        return temp
    }, 
    set(newVal) {
    
    
        console.log('set a',  newVal);
        temp = newVal
    }
})

obj.a = 1
obj.a++ 
console.log(obj.a) // 2

defineReactive関数をカスタマイズしてクロージャーを使用すると、一時変数を設定する必要がなくなります。

export default function defineReactive(obj, key, val) {
    
    
    if (arguments.length == 2) {
    
    
        val = obj[key]
    }
    Object.defineProperty(obj, key, {
    
    
        enumerable: true, // 可枚举
        configurable: true, // 可配置
        get() {
    
    
            console.log('get', key);
            return val
        },
        set(newVal) {
    
    
            console.log('set', key, newVal);
            if (val === newVal) return
            val = newVal
        }
    })
}
let obj = {
    
    }

// 实现了数据响应式
defineReactive(obj, 'a', 1)
console.log(obj.a); // 1
obj.a = 10
console.log(obj.a); // 10

オブジェクトのすべてのプロパティを再帰的に検出する

以下のオブジェクトについては、defineReactive上記のobj.a.m.n監視できないプロパティを使用してください (監視できるのは 1 つのレイヤーのみであり、複数のレイヤーは監視できません)。

let obj = {
    
    
    a: {
    
    
        m: {
    
    
            n: 1
        }
    },
    b: 1
}

望ましい効果: obj のすべてのプロパティをレスポンシブにobserveする

observe(obj)
obj.b++ // 响应式
obj.a.m.n++ // 响应式

プログラムのフローチャート: (再帰の効果は、すべてのレベルの関数間の呼び出しによって実現されます)

observe.js

/**
 * 将 obj 所有属性变为响应式
 */
export default function observe(obj) {
    
    
    if (!obj || typeof obj !== 'object') return
    var ob;
	// 判断是否已经是响应式
    if (typeof obj.__ob__ != 'undefined') {
    
    
        ob = obj.__ob__
    } else {
    
    
        ob = new Observer(obj)
    }
    return ob
}

Observer.js

/**
 * 将一个正常的 object 转换成每个层级的属性都是响应式的 object
 */
export default class Observer {
    
    
    constructor(obj) {
    
    
        console.log('Observer constructor', obj);
        // 构造函数中的 this 不是类本身,而是表示实例
        def(obj, '__ob__', this, false)
        // 将 object 中的属性转换成响应式的属性
        this.walk(obj)
    }
    // 遍历 object 的属性,将其转换成响应式的属性
    walk(obj) {
    
    
        console.log('walk', obj);
        for (let k in obj) {
    
    
            defineReactive(obj, k)
        }
    }
}

/**
 * 对 Object.defineProperty 的封装
 */
export const def = function (obj, key, value, enumerable) {
    
    
    Object.defineProperty(obj, key, {
    
    
        value,
        enumerable,
        writable: true,
        configurable: true
    })
}

defineReactive.js

/**
 * 将数据变为响应式
 */
export default function defineReactive(obj, key, val) {
    
    
    console.log('defineReactive', key);
    if (arguments.length == 2) {
    
    
        val = obj[key]
    }

    // 子元素要进行 observe,至此形成递归
    // 这个递归是多个函数、类循环调用
    let childOb = observe(val)

    Object.defineProperty(obj, key, {
    
    
        enumerable: true, // 可枚举
        configurable: true, // 可配置
        get() {
    
    
            // console.log('get', key);
            return val
        },
        set(newVal) {
    
    
            console.log('set', key, newVal);
            if (val === newVal) return
            val = newVal
            // 当设置了新值,这个新值也要被 observe
            childOb = observe(newVal)
        }
    })
}

効果:

let obj = {
    
    
    a: {
    
    
        m: {
    
    
            n: 1
        }
    },
    b: 1
}

observe(obj)
obj.b++ // 响应式
obj.a.m.n++ // 响应式

配列の応答原理

配列オブジェクトの場合、上記の実装はpush、 、popおよびその他の要素変更メソッドを監視できません。

let obj = {
    
    
	c: [1, 2, 3, 4]
}

7つのメソッドをオーバーライド: pushpopshiftunshiftsplicesortreverse

array.js

// 数组的原型
const arrayPrototype = Array.prototype

// 以 Array.prototype 为原型创建 arrayMethods 对象,并暴露
export const arrayMethods = Object.create(arrayPrototype)

// 要被改写的 7 个数组方法
const methodsNeedChange = [
    'push',
    'pop',
    'shift',
    'unshift',
    'splice',
    'sort',
    'reverse'
]

// 数组方法实际是自己写的,自己写的再调用真实的数组方法,中间可以拦截数据
methodsNeedChange.forEach(methodName => {
    
    
    // 备份原来的方法
    const originMethod = arrayPrototype[methodName]
    // 给原型定义新的方法
    def(arrayMethods, methodName, function () {
    
    
        console.log('arrayMethods', methodName);

        // 执行原来的函数
        const result = originMethod.apply(this, arguments)
        // arguments 是伪数组对象,转成数组对象
        const args = [...arguments]

        // 把数组身上的 __ob__ 取出来
        const ob = this.__ob__

        // 有三种方法 push / unshift / splice 可以插入新项
        // 要将插入的新项也变为 observe 的
        let inserted = []

        switch (methodName) {
    
    
            case 'push':
            case 'unshift':
                inserted = args
                break;
            case 'splice':
                // splice(下标, 数量, 插入的新项)
                inserted = args.slice(2)
                break;
        }

        // 判断有没有要插入的新项,让新项也变为响应的
        if (inserted) ob.observeArray(inserted)

        return result
    }, false)
})

Observer.jsObserver を構築するようにコードを変更します。

/**
 * 将一个正常的 object 转换成每个层级的属性都是响应式的 object
 */
export default class Observer {
    
    
    constructor(obj) {
    
    
        console.log('Observer constructor', obj);
        // 构造函数中的 this 不是类本身,而是表示实例
        def(obj, '__ob__', this, false)
        // 将 object 中的属性转换成响应式的属性
        if (Array.isArray(obj)) {
    
    
            // 将数组的原型指向 arrayMethods
            Object.setPrototypeOf(obj, arrayMethods)
            // 让数组变的 observe
            this.observeArray(obj)
        } else {
    
    
            this.walk(obj)
        }
    }
    // 遍历 object 的属性,将其转换成响应式的属性
    walk(obj) {
    
    
        console.log('walk');
        for (let k in obj) {
    
    
            defineReactive(obj, k)
        }
    }
    // 数组的特殊遍历
    observeArray(arr) {
    
    
        for (let i = 0, l = arr.length; i < l; i++) {
    
    
            // 逐项进行 observe
            observe(arr[i])
        }
    }
}

効果:

let obj = {
    
    
    c: [1, 2, 3, 4]
}

observe(obj)
obj.c.push(55, 66, 77)
obj.c.splice(1, 1, [1, 2])
console.log(obj.c);

依存関係のコレクション

依存とは?データが必要な場合、それは依存関係と呼ばれます

  • Vue 1.x では、依存関係はきめ細かく、データに使用されるDOMは依存しています。
  • Vue 2.x では、依存関係は中粒度であり、データを使用するコンポーネント依存関係です。
  • ゲッターで依存関係を収集し、セッターで依存関係をトリガーします

Dep クラスと Watcher クラス:

  • Dep クラスは、依存関係コレクションのコードをカプセル化し、依存関係の管理に専念しています。各 Observer インスタンスには、そのメンバーに Dep のインスタンスがあります。
  • Watcher は仲介役であり、データが変更されると、Watcher を介して転送され、コンポーネントに通知されます

  • Dependency is Watcher. Watcher によってトリガーされた getter のみが依存関係を収集します. どの Watcher が getter をトリガーすると、どの Watcher が Dep に収集されますか?
  • Dep uses publish-subscribe mode. データが変更されると、依存関係リストが循環し、すべてのウォッチャーに一度通知されます。
  • コード実装の工夫: ウォッチャーは、指定された場所にグローバルに設定し、データを読み取ります. データが読み取られるため、このデータのゲッターがトリガーされます. getter では、現在データを読み込んでいる Watcher を取得し、この Watcher を Dep に収集できます。

参照記事: Vue の詳細なレスポンシブ原則

効果:

let obj = {
    
    
    a: 1,
    b: {
    
    
        m: {
    
    
            n: 1
        }
    },
    c: [1, 2, 3, 4]
}

observe(obj)

// 监控依赖
new Watcher(obj, 'b.m.n', val => {
    
    
    console.log('Watcher 在监控 b.m.n', val)
}) 

obj.b.m.n++ 
console.log(obj)

Dep.js

/**
 * 全局唯一的 依赖收集器
 */
export default class Dep {
    
    
    constructor() {
    
    
        // console.log('Dep constructor'); 
        this.id = uid++

        // 用数组存储自己的订阅者,这个数组中存放 Watcher 实例
        this.subs = [] // subscribers
    }
    // 添加订阅
    addSub(sub) {
    
    
        this.subs.push(sub)
    }
    // 添加依赖
    depend() {
    
    
        // Dep.target 就是自己指定的全局的位置(window.targte 也可以,全局唯一即可)
        if (Dep.target) {
    
    
            this.addSub(Dep.target)
        }
    }
    // 通知更新
    notify() {
    
    
        console.log('Dep notify'); 
        // 浅克隆一份
        const subs = this.subs.slice()
        for (let i = 0, l = subs.length; i < l; i++) {
    
    
            subs[i].update()
        }
    }
}

Watcher.js

var uid = 0

export default class Watcher {
    
    
    // 监听 target 对象的 expression 属性,执行 callback 回调
    constructor(target, expression, callback) {
    
    
        // console.log('Watcher constructor');
        this.id = uid++
        this.target = target
        this.getter = parsePath(expression) // 解析 expression 为一个函数
        this.callback = callback
        this.value = this.get()
    }
    update() {
    
    
        // console.log('Watcher update');
        this.run()
    }
    get() {
    
    
        // 进入依赖收集阶段,让全局 Dep.tartget 设置为 Watcher 本身
        Dep.target = this

        const obj = this.target
        // 只要没找到,就一直找
        let value
        try {
    
    
            value = this.getter(obj)
        } finally {
    
    
            Dep.target = null
        }

        return value
    }
    run() {
    
    
        this.getAndInvoke(this.callback)
    }
    getAndInvoke(cb) {
    
    
        const value = this.get()

        if (value !== this.value || typeof value == 'object') {
    
    
            const oldValue = this.value
            this.value = value
            cb.call(this.target, value, oldValue)
        }
    }
}

// 返回一个可以解析 "a.b.c" 格式的函数
// let fn = parsePath('a.b.c')
// fn({ a: { b: { c: 1 } } })
function parsePath(str) {
    
    
    var segments = str.split('.');
    return obj => {
    
    
        for (let i = 0; i < segments.length; i++) {
    
    
            if (!obj) return
            obj = obj[segments[i]];
        }
        return obj
    }
}

ファイルコードの残りの部分は省略されています...

完全なソース コード リファレンス: https://gitee.com/szluyu99/vue-source-learn/tree/master/Data_Reactive_Study

おすすめ

転載: blog.csdn.net/weixin_43734095/article/details/125488301