JavaScript - прокси-объект ведет себя как примитив - PullRequest
4 голосов
/ 19 марта 2020

Я пытаюсь создать систему, которая «кэширует» вызовы библиотеки до загрузки этой библиотеки.

Это похоже на то, что код настройки Google Analytics делает с _gaq переменная - она ​​инициализируется как массив, который «кэширует» вызовы реальных конечных точек аналитики до тех пор, пока не будет загружена библиотека ga.js. Когда это происходит, он читает _gaq и воспроизводит вызовы.

Мы решили сделать это, потому что наш унаследованный код содержит множество вызовов определенной библиотеки, которая загружается синхронно в <head>. Это значительно увеличивает время до первой содержательной отрисовки, так как много JS оценивается и выполняется.

Однако в коде слишком много мест, которые необходимо изменить (обернуто в 'DOMContentLoaded' слушатель), поэтому мы решили попробовать использовать обходной путь.

Мы решили попробовать использовать Proxy, чтобы перехватывать вызовы методов нашей библиотеки и воспроизводить их, когда все будет готово:

// Original code:
var ourLib = new OurLib({ ... });

// Throughout the site, calls such as:
var res1 = ourLib.doThis();
var res2 = ourLib.getThat(3);

Вот своего рода «упрощенная» версия того, что делает наш новый код:

// New code:
var ourLib = new Proxy({
    calls: [],
}, {
    get(target, prop) {
        if (prop in target) {
            return Reflect.get(...arguments);
        }

        const callref = { prop, args: [], placeholder };
        target.calls.push(callref);

        return function(...args) {
            const placeholder = MakeResultPlaceholder(...);

            callref.args = args;
            callref.placeholder = placeholder;

            return placeholder;
        };
    },
});

// Throughout the site, calls continue as before, except now they're 'stored' in `calls`
var res1 = ourLib.doThis();
var res2 = ourLib.getThat(3);

// Much later, the original lib is loaded, and 
var ourRealLib = new OurLib({ ... });
__playbackCalls(ourLib.calls, ourRealLib);

// Replace the proxy with the real thing
ourLib = ourRealLib;

После запуска выше свойство calls будет выглядеть примерно так:

[
    {
        prop: 'doThis',
        args: [],
        reference: ResultPlaceholder
    },
    {
        prop: 'getThat',
        args: [3],
        reference: ResultPlaceholder
    }
]

Функция __playbackCalls перебирает массив calls и apply каждый метод из ourRealLib с args, хранящимся в каждом объекте.

calls.forEach(({ prop, args, reference }) => {
    reference._value = ourRealLib[prop].apply(ourRealLib, args);
});

Проблема возникает, когда результат вызовов прокси должен быть использован. Прямо сейчас, как вы можете видеть, вызовы возвращают объект placeholder (который сам по себе является другим прокси). Эти заполнители содержат свойство _value, которое заполняется во время «воспроизведения».

Итак, вот вопрос:

  • Допустим, ourLib.getThat() предназначено для возврата number.
  • Во время первого «запуска», из-за всего прокси, res1 будет указывать на placeholder объект: Proxy { _value: undefined }
  • Загружена реальная библиотека, «воспроизведение» заканчивается, ourRealLib.getThat(3) возвращает 23, поэтому res1 будет Proxy { _value: 23 }
  • Могу ли я сделать что-нибудь , чтобы мы могли использовать res1 в качестве числа? Что-то вроде:
console.log(res1 * 2); // 46
...