🚩Исходный код Vue — как отслеживать изменения данных

Vue.js

В последнее время я участвовал во многих интервью, и почти в каждом интервью будут задавать вопросы об исходном коде Vue. Я открою здесь серию столбцов, чтобы обобщить опыт в этой области.Если вы считаете, что это полезно для вас, пожалуйста, поставьте лайк и поддержите это.

предисловие

Vue управляется данными для создания представлений, и когда данные меняются, представления также меняются. Чтобы реализовать эту функцию, вы должны сначала иметь возможность отслеживать изменения данных, а затем уведомлять представление о внесении соответствующих изменений при изменении данных. Данные можно разделить на тип объекта и тип массива, и процесс мониторинга отличается.

1. Инициализация данных

Напомним, что в процессе разработки Vue при изменении данных в реквизитах и ​​данных представление также будет меняться соответственно.Вполне возможно, что данные в реквизитах и ​​данных отслеживаются. Как отслеживать данные в свойствах и данных на самом деле превращает данные в свойствах и данных в реагирующие объекты. Если вы хотите изучить, как данные в props и data становятся отзывчивым объектом, начните с его инициализации. Инициализация реквизита и данных находится вthis._initметод выполненinitState(vm)Готово, смотриinitStateметод.

function initState(vm) {
    vm._watchers = [];
    var opts = vm.$options;
    if (opts.props) {
        initProps(vm, opts.props);
    }
    if (opts.methods) {
        initMethods(vm, opts.methods);
    }
    if (opts.data) {
        initData(vm);
    } else {
        observe(vm._data = {}, true /* asRootData */ );
    }
    if (opts.computed) {
        initComputed(vm, opts.computed);
    }
    if (opts.watch && opts.watch !== nativeWatch) {
        initWatch(vm, opts.watch);
    }
}

initStateМетод в основном состоит из реквизита, методов, данных, вычисляемых атрибутов и атрибутов wathcer, которые выполняют инициализацию. Здесь мы сосредоточимся на анализе реквизита и данных, чтобы инициализировать другие свойства мы снова после подробного анализа.

где реквизит выполняетсяinitProps(vm, opts.props)для инициализации данные выполняютсяinitData(vm)для инициализации, посмотритеinitProps,initDataметод, код был упрощен, и некоторые суждения о проверке были удалены.

function initProps(vm, propsOptions) {
    var propsData = vm.$options.propsData || {};
    var props = vm._props = {};
    var isRoot = !vm.$parent;
    if (!isRoot) {
        toggleObserving(false);
    }
    var loop = function(key) {
        keys.push(key);
        var value = validateProp(key, propsOptions, propsData, vm);
        defineReactive(props, key, value);
        if (!(key in vm)) {
            proxy(vm, "_props", key);
        }
    };
    for (var key in propsOptions) loop(key);
    toggleObserving(true);
}
function initData(vm) {
    var data = vm.$options.data;
    data = vm._data = typeof data === 'function' ?
        getData(data, vm) :
        data || {};
    var keys = Object.keys(data);
    var i = keys.length;
    while (i--) {
        proxy(vm, "_data", key);
    }
    observe(data, true);
}

существуетinitPropsПройдите данные реквизита в методе и вызовите обходdefineReactiveМетод превращает соответствующее значение каждой опоры в реактивный объект и вызываетproxyСделайте прокси для значения, соответствующего каждой опоре. существуетinitDataДанные data просматриваются в методе, а вызов выполняется в процессе обходаproxyСделайте прокси для каждого значения в данных. последний звонокobserveОтслеживайте данные ради данных.

1. прокси

Позвольте мне сначала представитьproxyспособ узнать, что делает прокси.

function proxy(target, sourceKey, key) {
    sharedPropertyDefinition.get = function proxyGetter() {
        return this[sourceKey][key]
    };
    sharedPropertyDefinition.set = function proxySetter(val) {
        this[sourceKey][key] = val;
    };
    Object.defineProperty(target, key, sharedPropertyDefinition);
}

вObject.definePropertyМетод определяет новое свойство непосредственно в объекте или изменяет существующее свойство объекта и возвращает объект.Сначала рассмотрим синтаксис.

Object.defineProperty(obj, prop, descriptor)

  • obj объект, свойства которого должны быть определены
  • поддержите имя свойства для определения или изменения
  • descriptor Дескриптор свойства, который необходимо определить или изменить:
  • Получите функцию Getter для свойства, которая называется доступ к собственности.
  • Функция установки заданного свойства, которая вызывается при изменении значения свойства.

proxyРоль метода состоит в том, чтобы проксировать свойства реквизита и данных наvm(this). Вот почему свойства и данные определены таким образом, но могут быть переданы черезthis.aа такжеthis.bДоступ к значениям свойств a и b.

export default{
    props:{
        a: {
            type: String,
            default: ''
        },
    }
    data(){
        return {
            b: 1,
        }
    }
}

proxyРеализация метода очень проста, сначала назначьте свойства реквизита и данных дляvm._propsа такжеvm._dataвключить, а затем выполнитьproxy(vm, "_props", key)а такжеproxy(vm, "_data", key),пройти черезObject.definePropertyисправлятьtarget[key]чтения и записи в парахtarget[sourceKey][key]чтения и письма.

Если даvm.aчтения и записи в парахvm._props.a, который может быть прочитан и записанvm._props.aДоступ к свойствам, определенным в реквизитах, поэтому вы можете передатьvm.aсвойство, определенное в реквизитах.

Точно так же, еслиvm.bчтения и записи в парахvm._data.b, который может быть прочитан и записанvm._data.bДоступ к свойствам, определенным в объекте, возвращаемом функцией данных, поэтому вы можете передатьvm.bДоступ к свойству b, определенному в объекте, возвращаемом функцией данных.

2. определитьреактивный

посмотри сноваdefineReactiveФункция, роль которой состоит в том, чтобы превратить объект в реактивный объект.

function defineReactive(obj, key, val, customSetter, shallow) {
    var dep = new Dep();
    
    var property = Object.getOwnPropertyDescriptor(obj, key);
    if (property && property.configurable === false) {
        return
    }
    
    var getter = property && property.get;
    var setter = property && property.set;
    if ((!getter || setter) && arguments.length === 2) {
        val = obj[key];
    }

    var childOb = !shallow && observe(val);
    Object.defineProperty(obj, key, {
        enumerable: true,
        configurable: true,
        get: function reactiveGetter() {
            var value = getter ? getter.call(obj) : val;
            if (Dep.target) {
                dep.depend();
                if (childOb) {
                    childOb.dep.depend();
                    if (Array.isArray(value)) {
                        dependArray(value);
                    }
                }
            }
            return value
        },
        set: function reactiveSetter(newVal) {
            var value = getter ? getter.call(obj) : val;
            if (newVal === value || (newVal !== newVal && value !== value)) {
                return
            }
            if (customSetter) {
                customSetter();
            }
            if (getter && !setter) {
                return
            }
            if (setter) {
                setter.call(obj, newVal);
            } else {
                val = newVal;
            }
            childOb = !shallow && observe(newVal);
            dep.notify();
        }
    });
}

воплощать в жизньvar dep = new Dep(), создать сборщик подписчиков, что такое сборщик подписчиков здесь не представлено, что такое подписчик, будет представлено в последующих статьях.

передачаObject.getOwnPropertyDescriptorметод получает дескриптор свойства объекта тогда и только тогда, когда дескриптор свойства объектаconfigurableключевое значениеtrueТолько после этого можно изменить дескриптор свойства объекта. Так когдаproperty.configurable === falseвернуться напрямую.

воплощать в жизньvar getter = property && property.getПолучить свойство get дескриптора свойства объекта и кэшировать его в константуgetter. воплощать в жизньvar setter = property && property.setПолучить установленное свойство дескриптора свойства объекта и кэшировать его в константуsetter.

Так как он будет использоваться далееObject.definePropertyМетод определяет атрибут get и атрибут set дескриптора атрибута объекта.Если дескриптор атрибута объекта определил атрибут get и атрибут set, исходные атрибуты get и set не могут быть перезаписаны, поэтому он необходимо сначала кэшировать его.

воплощать в жизнь(!getter || setter) && arguments.length === 2, если условия выполнены, выполнитьval = obj[key], по ключевому ключу перейти к объекту obj для получения соответствующего значения.

вarguments.length === 2Хорошо понятно, что когда всего два параметра, значит, третьего параметра нетval, поэтому выполнитеval = obj[key]Получатьval. Что касается(!getter || setter)Это условие необходимо ввести в сочетании с некоторыми граничными сценариями, которые не будут здесь представлены, но будут представлены далее в этой статье.

воплощать в жизньvar childOb = !shallow && observe(val)позвони сюдаobserveметод, поэтому эта часть логики представлена ​​в следующем разделеobserveметод будет представлен позже.

передачаObject.definePropertyДескриптор свойства объекта определения метода,enumerable: trueсделать объект перечислимым,configurable: trueДелает дескриптор свойства объекта изменяемым.

один определен в свойстве getreactiveGetterфункция, называемая функцией-получателем, в которой она выполняетсяvar value = getter ? getter.call(obj) : val, если исходный дескриптор свойства объекта имеет определенное свойство get и кэшируется в предыдущей логикеgetter, поэтому выполнитеgetter.call(obj)передачаgetterПолучить значение объекта и присвоить егоvalueи вернуться. Ниже приведена остальная часть логики функции, роль которой состоит в том, чтобы собрать подписчики, которые не будут введены первыми, но будут введены в последующую колонку.

if (Dep.target) {
    dep.depend();
    if (childOb) {
        childOb.dep.depend();
        if (Array.isArray(value)) {
            dependArray(value);
        }
    }
}

После того, как дескриптор атрибута объекта определяет атрибут get, всякий раз, когда считывается значение объекта, вызывается функция getter для получения значения.value, чтобы можно было следить за приобретением стоимости этого объекта.

один, определенный в свойстве setreactiveSetterфункция, называемая сеттер-функцией, аргументы которойnewValЭто значение, присвоенное объекту, которое здесь называется новым значением. воплощать в жизньvar value = getter ? getter.call(obj) : valПолучить исходное значение объектаvalue.

воплощать в жизньnewVal === value || (newVal !== newVal && value !== value)Сравнивая старое и новое значения, следует отметить, что значение объекта может быть NaN, поэтому используйтеnewVal !== newVal && value !== valueДавайте вынесем приговор. Если старое и новое значения совпадают или NaN, вернитесь напрямую.

customSetterдаdefineReactiveЧетвертый параметр функции — это функция, которая выполняется, если она существует.

Если атрибут get, первоначально определенный в дескрипторе атрибута объекта, имеет значение, а атрибут set не имеет значения, возвращайтесь напрямую. причины будут представлены позже в этой статье.

Если свойство set, первоначально определенное в дескрипторе свойства объекта, имеет значение, выполнитеsetter.call(obj, newVal), передайте новое значение в первоначально определенную функцию установки для выполнения. Если свойство set не имеет значения, присвойте новое значениеval.

Новое значение может быть массивом или объектом, так чтоchildOb = !shallow && observe(newVal).

окончательное исполнениеdep.notify(), который уведомляет подписчиков о выполнении обновлений, что также рассматривается в следующем столбце.

После того, как дескриптор атрибута объекта определяет установленный атрибут, всякий раз, когда значение объекта изменяется, вызывается функция установки, чтобы получитьvalue, чтобы вы могли отслеживать изменения значения этого объекта.

существуетdefineReactiveиспользуется в функцииObject.definePropertyМетод определяет атрибут get и атрибут set дескриптора атрибута объекта, поэтому чтение или изменение объекта можно отслеживать, поэтому объект становится реагирующим объектом.

Также вызовите несколько раз в немobserveфункция, описанная нижеobserveфункция.

3. наблюдать

function observe(value, asRootData) {
    if (!isObject(value) || value instanceof VNode) {
        return
    }
    var ob;
    if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
        ob = value.__ob__;
    } else if (
        shouldObserve &&
        !isServerRendering() &&
        (Array.isArray(value) || isPlainObject(value)) &&
        Object.isExtensible(value) &&
        !value._isVue
    ) {
        ob = new Observer(value);
    }
    if (asRootData && ob) {
        ob.vmCount++;
    }
    return ob
}

observeФункции используются для мониторинга изменений данных. Получает два параметра параметраvalueданные для мониторинга, параметрыasRootDataявляется логическим значением дляtrueУказывает, что отслеживаемые данные являются данными корневого уровня.

воплощать в жизнь!isObject(value) || value instanceof VNode,подобноvalueНе тип объекта или массива или объект, созданный классом VNode, возвращаются напрямую.

Определите переменную OB, выполнитеhasOwn(value, '__ob__') && value.__ob__ instanceof Observer,подобноvalueТам__ob__это свойство, иvalue.__ob__является объектом, созданным классом Observer, это означаетvalueконтролируется непосредственноvalue.__ob__назначить наobИ вернуться, чтобы избежать повторного мониторинга данных.

подобноvalueполдень__ob__Это свойство входит в логику else if. воплощать в жизньshouldObserve && !isServerRendering() && (Array.isArray(value) || isPlainObject(value)) && Object.isExtensible(value) && !value._isVue, вышеуказанные условия выполняются для выполненияob = new Observer(value).

  • shouldObserveусловие эквивалентно переключателю, дляtrueвыполняется, когдаtoggleObservingФункция для управления, потому что в некоторых сценариях нужно контролировать, следует ли отслеживать данные.

    function toggleObserving(value) {
        shouldObserve = value;
    }
    
  • !isServerRendering()условие,isServerRenderingВозвращаемое значение функции является логическим значением, которое используется для определения того, является ли это рендерингом на стороне сервера, если нет, возвращаетfalse, что означает, что условия не выполняются при рендеринге на стороне сервера.

  • (Array.isArray(value) || isPlainObject(value))Условие, которое выполняется только в том случае, если данные являются массивом или чистым объектом.

  • Object.isExtensible(value)условие,Object.isExtensibleМетод используется для определения того, являются ли данные расширяемыми. Условие выполняется только в том случае, если он является расширяемым.Обычный объект является расширяемым по умолчанию, ноObject.freeze()Объект можно сделать нерасширяемым, так что делайте выводы.

  • !value._isVueусловие,valueУсловие выполняется только в том случае, если это не объект экземпляра Vue.

Когда вышеуказанные пять условий выполнены, выполнитеnew Observer(value)И присвоить результат выполнения ob.

еслиvalueданные корневого уровня иobЕсли есть значение, выполнитьob.vmCount++быть знаком, и, наконец, вернутьсяob.

Давайте представим конструктор Observer.

4. Конструктор наблюдателя

В конструкторе Observer тип массива и тип объекта отслеживаются отдельно.

var Observer = function Observer(value) {
    this.value = value;
    this.dep = new Dep();
    this.vmCount = 0;
    def(value, '__ob__', this);
    if (Array.isArray(value)) {
        if (hasProto) {
            protoAugment(value, arrayMethods);
        } else {
            copyAugment(value, arrayMethods, arrayKeys);
        }
        this.observeArray(value);
    } else {
        this.walk(value);
    }
}

воплощать в жизньthis.value = value, поместите данные для мониторингаvalueНазначается экземпляру объекта класса Observer.

воплощать в жизньthis.dep = new Dep(), создайте сборщик подписчиков и назначьте его экземпляру объекта класса Observer. Что такое сборщик подписчиков и что такое подписчик здесь не будет представлено, о чем будет рассказано в последующих статьях.

воплощать в жизньthis.vmCount = 0,ПучокvmCountНазначается экземпляру объекта класса Observer.

Таким образом, объект экземпляра класса Observer имеет три свойства экземпляра:value,dep,vmCount.

воплощать в жизньdef(value, '__ob__', this), добавить к данным свой собственный объект-экземплярvalueиз__ob__атрибут, делатьvalueиз__ob__Некоторые объекты экземпляра и методы экземпляра класса Observer сохраняются в свойстве, которое будет часто использоваться в последующей логике. Если другой объект имеет__ob__свойство, это означает, что объект находится под наблюдением.defметод правильныйObject.definePropertyинкапсуляция методов. Это используетconsole.logПри печати данных данных вы найдете еще один__ob__причина атрибута.

function def(obj, key, val, enumerable) {
    Object.defineProperty(obj, key, {
        value: val,
        enumerable: !!enumerable,
        writable: true,
        configurable: true
    });
}

воплощать в жизньif (Array.isArray(value))судитьvalueЯвляется ли это типом массива, если не выполнитьthis.walk(value), если выполняется следующий код.

if (hasProto) {
    protoAugment(value, arrayMethods);
} else {
    copyAugment(value, arrayMethods, arrayKeys);
}
this.observeArray(value);

Видно, что методы обработки данных типа массива и мониторинга данных типа объекта в Vue различны.Ниже будут представлены соответствующие методы обработки.

Во-вторых, отслеживать данные типа объекта

В конструкторе Observer для данных объектного типа выполнитеthis.walk(value)контролировать. посмотриthis.walkМетод экземпляра.

Observer.prototype.walk = function walk(obj) {
    var keys = Object.keys(obj);
    for (var i = 0; i < keys.length; i++) {
        defineReactive(obj, keys[i]);
    }
};

воплощать в жизньvar keys = Object.keys(obj)Получить коллекцию данных типа "ключ-значение" типа объекта и присвоить ее константеkeys.

траверсkeysвыполнить в немdefineReactive(obj, keys[i]),defineReactiveБольшая часть логики этой функции была представлена ​​выше, вinitPropsметод,defineReactiveФункция получает три параметра, и здесьdefineReactiveФункция получает только два параметра, поэтому будет выполнен следующий код

if ((!getter || setter) && arguments.length === 2) {
    val = obj[key];
}

arguments.length === 2Это условие легко понять. Так зачем установить(!getter || setter)это условие. вgetterа такжеsetterЭто значение атрибутов get и set атрибута дескриптора объекта, значение по умолчанию не определено и имеет значение только после искусственного определения.

затем выполнитvar childOb = !shallow && observe(val),подобноvalявляется объектом или массивом данных, вobserveфункция будет выполнятьсяnew Observer(value),существуетObserverбудет вызываться в конструктореdefineReactiveфункция, вdefineReactiveфункция вызоветobserveфункция, которая формирует рекурсивный вызов, гарантирующий, что независимо от сложности структуры данных будут отслеживаться все ее подсвойства.

Предположим, что свойство get свойства дескриптора объекта имеет значение, равноеgetterЗначение есть, в данный момент оно не будет выполненоval = obj[key], то есть,valне определено, затем выполнитьvar childOb = !shallow && observe(val)Он будет возвращен напрямую, и для подсвойств этого объекта не будет выполняться глубокий мониторинг обхода.

Почему свойство set свойства дескриптора объекта имеет значение, т.е.setterЕсли есть значение, оно будет выполнено в это времяval = obj[key], затем выполнитеvar childOb = !shallow && observe(val)Выполните глубокий мониторинг обхода подсвойств объектов. Потому что при присвоении значения объектным данным будет вызвана функция setter, в которой он и будет выполнятьсяchildOb = !shallow && observe(newVal)Прислушивайтесь к новым значениям. Если старое значение не отслеживается первым, его можно отслеживать после присвоения данных данным, что приводит к несогласованному поведению до и после монитора.

На этом можно сделать вывод: во Vue, если объект настраивает атрибут get на атрибут дескриптора, но не определяет атрибут set, то подсвойства этого объекта не будут отслеживаться.

Подводя итог, отслеживайте данные типа объектаvalueпроцесс, первыйvalueПередать как параметрobserve(value)функция, в которой выполняетсяnew Observer(value), а затем в конструкторе Observer вызовитеthis.walkметод экземпляра, вthis.walkиспользуется в методеObject.keys()Получатьvalueнабор ключейkeys, затем повторяетсяkeysвыполнить в немdefineReactive(value, keys[i]),существуетdefineReactiveв функцииvalueОпределите свойства get и set в собственном дескрипторе свойств для мониторинга, а затем передайтеvalue[keys[i]]Получатьvalueкаждый податрибутval,еслиvalобъект или массив будет выполнятьсяobserve(val)контролировать дочерние свойстваval, повторяя процесс запуска, формируя таким образом рекурсивный вызов, так что данныеvalueБудет прослушиваться либо он сам, либо все его подсвойства.

3. Мониторинг данных типа массива

В конструкторе Observer для данных типа массива выполните следующую логику для мониторинга.

if (hasProto) {
    protoAugment(value, arrayMethods);
} else {
    copyAugment(value, arrayMethods, arrayKeys);
}
this.observeArray(value);

Давайте посмотрим на логику if выше.this.observeArrayМетод экземпляра.

Observer.prototype.observeArray = function observeArray (items) {
    for (var i = 0, l = items.length; i < l; i++) {
        observe(items[i]);
    }
}

Только подумайте, почему бы не вызвать прямо в обходеdefineReactiveфункция для превращения данных в реактивный объект для прослушивания, но для вызоваobserveфункция. Это связано с тем, что элементами массива могут быть объекты, массивы и т. д. Процесс мониторинга данных для типов массивов и типов объектов в Vue отличается.defineReactiveФункция состоит в том, чтобы напрямую преобразовать данные типа объекта в реагирующий объект для мониторинга, только когдаobserveРазличие проводится в функции.

воплощать в жизньif (hasProto)hasProtoтак определеноvar hasProto = '__proto__' in {}, переменная в объекте, чтобы определить, является ли переменная свойством объекта.

посмотриprotoAugmentфункция иcopyAugmentфункция.

function protoAugment(target, src) {
    target.__proto__ = src;
}
function copyAugment(target, src, keys) {
    for (var i = 0, l = keys.length; i < l; i++) {
        var key = keys[i];
        def(target, key, src[key]);
    }
}

существуетprotoAugmentпараметр в функцииsrcприсвоить параметруtargetиз__proto__. объект__proto__Значением свойства является соответствующий объект-прототип.В JS массив также является объектом. ТакprotoAugmentФункция функции состоит в том, чтобы взять параметрtargetОбъект-прототип изменен на параметрsrc. но__proto__Это свойство не поддерживается в некоторых версиях браузеров, таких как IE9, поэтому используйте'__proto__' in {}Сделайте оценку совместимости.

Если браузер не поддерживает__proto__это свойство, затем позвонитеcopyAugmentфункция, в которойdefметод, укажите параметрыtargetЗначение в объекте-прототипе изменений,defМетод был описан выше.

воплощать в жизньprotoAugment(value, arrayMethods)valueпредставляет собой массив,valueОбъект-прототип изменен наarrayMethods, то почему объект-прототип массива должен быть изменен наarrayMethods, посмотри сначалаarrayMethodsкак это определяется.

var arrayProto = Array.prototype;
var arrayMethods = Object.create(arrayProto);
var methodsToPatch = [
    'push',
    'pop',
    'shift',
    'unshift',
    'splice',
    'sort',
    'reverse'
];
methodsToPatch.forEach(function(method) {
    var original = arrayProto[method];
    def(arrayMethods, method, function mutator() {
        var args = [],
            len = arguments.length;
        while (len--) args[len] = arguments[len];
        var result = original.apply(this, args);
        var ob = this.__ob__;
        var inserted;
        switch (method) {
            case 'push':
            case 'unshift':
                inserted = args;
                break
            case 'splice':
                inserted = args.slice(2);
                break
        }
        if (inserted) {
            ob.observeArray(inserted);
        }
        ob.dep.notify();
        return result
    });
});
var arrayKeys = Object.getOwnPropertyNames(arrayMethods);

воплощать в жизньvar arrayProto = Array.prototypeПолучите прототип объекта массива и назначьте егоarrayProto. воплощать в жизньvar arrayMethods = Object.create(arrayProto)создать новый объектarrayMethods, который содержит прототип объекта массива. ПеременнаяmethodsToPatchНекоторые распространенные методы экземпляра массива определены в .methodsToPatchвызыватьdefМодификация функцийarrayMethodsИ дальшеmethodsToPatchодноименный метод экземпляра в , это называется перехватом функции.

Только представьте, почему Vue перехватывает методы экземпляров массивов. Потому чтоObject.definePropertyМетод не работает с массивом, а атрибуты get и set не могут быть определены для массива, поэтому массив нельзя отслеживать как объект, поэтому необходимо перехватить метод экземпляра массива и отслеживать изменения данные типа массива в переопределенном методе экземпляра Давайте посмотрим, как переопределить метод экземпляра массива.

воплощать в жизньvar original = arrayProto[method]Кэшировать исходный метод экземпляра массива в константуoriginal, а затем позвонитеdefфункция, вdefТретья функция параметров, передаваемых в примере массива метода повторной обработки.

В методе экземпляра переопределенного массива выполните

var args = [],len = arguments.length;
while (len--) args[len] = arguments[len];

определить переменнуюargs, а затем присвоить параметры при вызове метода экземпляра массива переменнойargs, затем выполнитеvar result = original.apply(this, args), передать параметрыoriginalВыполняется исходный метод экземпляра массива, а результат выполнения присваивается константеresult.

воплощать в жизньvar ob = this.__ob__, получить объект, созданный классом Observer, принадлежащий массиву, за которым нужно следить, и присвоить его константеob.

определить переменнуюinsertedДля кэширования набора новых элементов массива, поскольку новые элементы не отслеживались, их необходимо обработать. Поскольку позиции параметров, представляющие вновь добавленные элементы в разных методах экземпляра массива, различны, напримерpushа такжеunshiftметод, параметрами которого являются все вновь добавленные элементы, иspliceТолько третий параметр метода является новым элементом, поэтому для работы с ним используйте следующую логику.

switch (method) {
    case 'push':
    case 'unshift':
        inserted = args;
        break
    case 'splice':
        inserted = args.slice(2);
        break
}

Если новый элемент установленinsertedСуществовать, потому что вновь добавленные элементы могут существовать в массивах или объектах, поэтому выполнитеob.observeArray(inserted)отслеживать новые элементы.

когда переменная вызова массиваmethodsToPatchКогда метод экземпляра в , выполняетсяob.dep.notify()Инициировать обновления подписчиков.

Наконец, верните результат выполнения исходного метода экземпляра массива.result. Примеры методов для обеспечения согласованности массива и переопределения исходной функции метода экземпляра.

Если браузер поддерживает__proto__это свойство, затем выполнитеprotoAugment(value, arrayMethods),ПучокvalueОбъект-прототип заменяется наarrayMethods.

Если браузер не поддерживает__proto__это свойство, затем выполнитеcopyAugment(value, arrayMethods, arrayKeys),arrayKeysдляarrayMethodsсбор здоровья, обходarrayKeysвызыватьdefфункция положитьvalueобъект-прототип, за которым следуетarrayMethodsОдноименный метод экземпляра переопределен в .

Подводя итог, потому чтоObject.definePropertyЭтот метод не работает с массивом, а свойства get и set не могут быть определены для массива, поэтому массив нельзя отслеживать как объект, поэтому Vue определяет некоторые распространенные методы экземпляра массива, такие какpush,pop,shift,unshift,splice,sort,reverse, а затем выполнить перехват функции в методе экземпляра с тем же именем в объекте-прототипе массива и сохранить функцию исходного метода экземпляра, чтобы, когда массив использует эти методы экземпляра, его можно было отслеживать, что эквивалентно чтобы превратить массив в отзывчивый объект. Это также есть в официальной документации Vue, используяpush,pop,shift,unshift,splice,sort,reverseПричина, по которой эти методы экземпляра обновляют массив, можно только прослушать.

4. Граничные сценарии данных мониторинга

1. Мониторинг граничной сцены данных типа массива

В предыдущем разделе только использованиеpush,pop,shift,unshift,splice,sort,reverseЭти методы экземпляра массива, переопределенные внутри Vue, можно отслеживать, только манипулируя массивом. На самом деле эти методы экземпляра массива изменят исходный массив, который называется методом изменения. Существуют также некоторые методы экземпляра массива, такие какfilter,concatа такжеslice, эти методы не изменяют исходный массив, а возвращают новый массив, который называется методом замены. Затем используйте эти методы замены для работы с массивом, будет ли он контролироваться? Во-первых, давайте посмотрим, как определяются данные типа массива в разработке Vue.

data(){
    return {
        a: [1,2]
    }
}

вaЭти данные являются значением в объекте данных и будут отслеживаться.Это легко понять.aЗначение[1,2], будет использовать метод экземпляра массива перехвата для мониторинга этого массива, а поскольку элементы этого массива не являются ни объектами, ни массивами, он не будет отслеживать его элементы. Итак, теперь возникает вопрос, если[1,2]Если этот массив заменить новым массивом, будет ли он отслеживаться?

посмотреть пример

let obj = {
    a: [1, 2]
}
let b = obj.a;
Object.defineProperty(obj, 'a', {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter() {
        return b
    },
    set: function reactiveSetter(newVal) {
        console.log(newVal)
        console.log('set')
        b = newVal
    }
})
obj.a.push(2)
obj.a = [3, 4]

который выводит на консоль[3, 4],set, поэтому, когда значение объекта является массивом, его массив заменяется новым массивом, который будет прослушиваться. такfilter,concatа такжеsliceЭти альтернативные методы управления массивами будут прослушиваться.

Кроме того, два метода, которые могут изменить массив, не будут отслеживаться, например,

При использовании индексов массива для прямой установки элемента массива, например:this.items[indexOfItem] = newValueНапример, при изменении длины массива:this.items.length = newLength

Второй случай прост в обращении, используйте его напрямуюthis.items.splice(newLength)решать. В первом случае используйтеthis.$set(this.items, indexOfItem, newValue)решать,this.$setэто метод экземпляра, который является глобальным методомVue.setпсевдоним .

2. Граничная сцена данных типа объекта мониторинга

Добавление и удаление свойств данных типа объекта нельзя отслеживать в Vue. Причина очень проста, свойства объекта используются только в первую очередь.Object.definePropertyМетод добавляет атрибуты get и set дескриптора атрибута для отслеживания, и новые добавленные атрибуты не должны использоваться в первую очередь.Object.definePropertyметод, поэтому его нельзя контролировать. Удаленное свойство не может отслеживаться с помощью установленного свойства, поэтому его нельзя отслеживать. Есть два способа решить вопрос о добавлении свойств объекта:

Создайте новый объект со свойствами исходного объекта и вновь добавляемыми свойствами, а затем назначьте их исходному объекту, напримерthis.someObject = Object.assign({}, this.someObject, { a: 1, b: 2 }). использоватьthis.$set(this.someObject,'b',2)решать,this.$setэто метод экземпляра, который является глобальным методомVue.setпсевдоним .

Для удаления свойств объекта вы можете использоватьthis.$delete(this.someObject,'b')решать,this.$deleteэто метод экземпляра, который является глобальным методомVue.deleteпсевдоним .

3. Внутренняя логика Vue.set

Vue.setвinitGlobalAPIопределяется в функции.initGlobalAPIФункция выполняется сразу после определения конструктора Vue.

function initGlobalAPI(Vue) {
    Vue.set = set;
}
initGlobalAPI(Vue);

вVue.setдаsetназначение функций, посмотритеsetфункция.

function set(target, key, val) {
    if (isUndef(target) || isPrimitive(target)) {
        warn(("Cannot set reactive property on undefined, null, or primitive value: " + ((target))));
    }
    if (Array.isArray(target) && isValidArrayIndex(key)) {
        target.length = Math.max(target.length, key);
        target.splice(key, 1, val);
        return val
    }
    if (key in target && !(key in Object.prototype)) {
        target[key] = val;
        return val
    }
    var ob = (target).__ob__;
    if (target._isVue || (ob && ob.vmCount)) {
        warn(
            'Avoid adding reactive properties to a Vue instance or its root $data ' +
            'at runtime - declare it upfront in the data option.'
        );
        return val
    }
    if (!ob) {
        target[key] = val;
        return val
    }
    defineReactive(ob.value, key, val);
    ob.dep.notify();
    return val
}

воплощать в жизньif (isUndef(target) || isPrimitive(target))Параметры оценкиtargetЭто undefined, null, string, boolean, number. Если в консоли отображается предупреждение, свойства не могут быть установлены для данных неопределенного, нулевого или базового типа, гдеisPrimitiveКод метода выглядит следующим образом.

function isPrimitive(value) {
    return (
        typeof value === 'string' ||
        typeof value === 'number' ||
        typeof value === 'symbol' ||
        typeof value === 'boolean'
    )
}

воплощать в жизньif (Array.isArray(target) && isValidArrayIndex(key))Параметры оценкиtargetЯвляется ли это массивом, если да, то параметрkeyДолжен быть индексом массива, используемым для оценки параметровkeyнеправильный индекс массива,

function isValidArrayIndex(val) {
    var n = parseFloat(String(val));
    return n >= 0 && Math.floor(n) === n && isFinite(val)
}

Индекс массива должен быть целым числом больше нуля, а не бесконечностью. существуетisValidArrayIndexфункция в первую очередьparseFloatПараметрыval,потому чтоparseFloatПолученный параметр имеет строковый формат, поэтому используйтеStringобрабатывать параметрыval. Очень умное использование здесьMath.floor(n) === nсудить о параметрахvalЯвляется ли это целым числом и, наконец, используйтеisFiniteПараметры оценкиvalне бесконечен.

если параметрkeyявляется правильным индексом массива, выполните следующую логику

target.length = Math.max(target.length, key);
target.splice(key, 1, val);

В настоящее времяtargetэто массив, вот умное приложениеspliceЭтот метод экземпляра массива реализует функцию добавления элемента массива через индекс массива, и в то же времяspliceЭтот метод экземпляра массива был взломан в Vue, поэтому он будет отслеживаться.

Так зачем перенастраиватьtargetдлина. Потому чтоspliceВ методе есть изъян, который проиллюстрирован ниже на примере.

let a = [1,2]
a[3]=3;
console.log(a)
let b = [1,2]
b.splice(3,1,3)
console.log(b)

После выполнения смотрим в консоль и распечатываем их соответственно[1, 2, empty, 3]а также[1, 2, 3], а затем посмотрите на следующие столбцы

let a = [1,2]
a[2]=3;
console.log(a)
let b = [1,2]
b.splice(2,1,3)
console.log(b)

После выполнения смотрим в консоль и распечатываем их соответственно[1, 2, 3]а также[1, 2, 3], проиллюстрироватьspliceПараметры в методах экземпляраkeyПока длина массива превышена, в конец массива будут добавлены только нужные элементы массива.

Чтобы избежать этого недостатка, выполнитеtarget.length = Math.max(target.length, key),когдаkeyСравниватьtarget.lengthбольшой, положитьkeyназначить наtarget.lengthСначала увеличьте длину массива, чтобы убедиться, что он проходитspliceДобавление элемента массива аналогично добавлению элемента массива через индекс массива.

воплощать в жизньif (key in target && !(key in Object.prototype)), параметр оценкиkeyЭто параметрtargetсвойства , а не свойства его объекта-прототипа.

Если так, тоtarget[key]Был отслежен, прямо поставил параметрvalназначить наtarget[key]Вот и все.

воплощать в жизньif (target._isVue || (ob && ob.vmCount)),использоватьtarget._isVueсудить о параметрахtargetЭто объект экземпляра Vue.

использоватьob && ob.vmCountсудить о параметрахtargetявляется корневым объектом данных (то есть объектом, возвращаемым опцией данных), гдеob как параметрtarget.__ob__,__ob__ Это созданный объект класса Observer.В конструкторе Observer только данные являются корневыми данными, это дастvmCountНазначение экземпляра объекта. Если в консоли отображается предупреждение, обратите внимание на параметрыtargetНе может быть экземпляром Vue или корневым объектом данных экземпляра Vue.

воплощать в жизньif (!ob)Параметры оценкиtargetМониторится ли он, если нет, то необходимо мониторить его субатрибуты и выполнятьtarget[key] = valПросто назначьте его напрямую. Если да, выполнитеdefineReactive(ob.value, key, val)Определите свойства get и set в свойствах дескриптора новых свойств для прослушивания новых свойств, гдеob.valueЭто отзывчивый объект, которым становится цель параметра, если параметр используется напрямую.target, что приводит к параметруtargetНи сам объект, ни его подсвойства не могут быть отслежены. воплощать в жизньob.dep.notify(), так как параметрtargetКогда добавляется новый атрибут, он сам изменяется, поэтому запускает обновление своих подписчиков. Наконец возвращает только что добавленное значение `val .

4. Внутренняя логика Vue.delete

Vue.deleteвinitGlobalAPIопределяется в функции.initGlobalAPIФункция выполняется сразу после определения конструктора Vue.

function initGlobalAPI(Vue) {
    Vue.delete = del;
}
initGlobalAPI(Vue);

вVue.deleteдаdelназначение функций, посмотритеdelфункция.

function del(target, key) {
    if (isUndef(target) || isPrimitive(target)) {
        warn(("Cannot delete reactive property on undefined, null, or primitive value: " + ((target))));
    }
    if (Array.isArray(target) && isValidArrayIndex(key)) {
        target.splice(key, 1);
        return
    }
    var ob = (target).__ob__;
    if (target._isVue || (ob && ob.vmCount)) {
        warn(
            'Avoid deleting properties on a Vue instance or its root $data ' +
            '- just set it to null.'
        );
        return
    }
    if (!hasOwn(target, key)) {
        return
    }
    delete target[key];
    if (!ob) {
        return
    }
    ob.dep.notify();
}

часть логической суммыsetФункции точно такие же и были представлены выше. Введем другую логику.

когда параметрtargetКогда это массив, и параметрkeyЧтобы индексировать правильный массив, выполнитеtarget.splice(key, 1), вот умное приложениеspliceЭтот метод экземпляра массива реализует функцию удаления элемента в массиве, и в то же времяspliceЭтот метод экземпляра массива был взломан в Vue, поэтому он будет отслеживаться.

воплощать в жизньif (!hasOwn(target, key)), параметр оценкиkeyЭто параметрtargetсвойство, если нет, вернуть напрямую.

Если нет, выполнитеdelete target[key]Удалить это свойство объекта.

воплощать в жизньvar ob = (target).__ob__; if(!ob)Параметры оценкиtargetБудет ли контролироваться, если нет, вернуться напрямую.

Если да, выполнитеob.dep.notify(), параметр триггераtargetАбонент обновляет себя.

V. Резюме

Причина списания возможности отслеживать изменения данных в Vue заключается в использованииObject.definePropertyМетод определяет атрибуты get и set в атрибуте дескриптора данных типа объекта.Значения атрибута являются функциями получения и установки соответственно.Функция получения запускается при чтении данных типа объекта, а установка срабатывает при изменении данных объектного типа, так что вы можете отслеживать данные объектного типа. и потому, чтоObject.definePropertyМетод не работает для данных типа массива, тогда данные типа массива отслеживаются путем перехвата метода экземпляра массива. Кроме тогоObject.definePropertyЭтот метод несовместим с браузерами IE8 и ниже, поэтому Vue.js несовместим с браузерами IE8 и ниже.

Вызывается при инициализации данныхobserveФункция начинает отслеживать данные, в конструкторе Observer данные типа массива и типа объекта отслеживаются с разной логикой.defineReactiveиспользовать по назначениюObject.definePropertyМетод превращает объект в отзывчивый объект для обеспечения мониторинга и повторного использования.observeфункция, конструктор наблюдателя,defineReactiveФункции вызывают друг друга для формирования рекурсивного вызова, который гарантирует, что независимо от того, насколько сложен объект, его подсвойства могут быть тщательно пройдены и отслежены.