Как бороться с побочными эффектами в коде встряхивания дерева? - PullRequest
0 голосов
/ 26 февраля 2019

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

В одном измои модули, я получаю доступ к глобальному конструктору Audio и использую его, чтобы определить, какие аудиофайлы может воспроизводить браузер (аналогично тому, как Modernizr делает это ).Всякий раз, когда я пытаюсь пошатнуть дерево, мой элемент Audio и все ссылки на него не удаляются, даже если я не импортирую модуль в мой файл.

let audio = new Audio(); // or document.createElement('audio')
let canPlay = {
  ogg: audio.canPlayType('audio/ogg; codecs="vorbis"').replace(/^no$/, '');
  mp3: audio.canPlayType('audio/mpeg; codecs="mp3"').replace(/^no$/, '');
  // ...
};

Я понимаю этот кодкоторые содержат побочные эффекты, не могут быть устранены, но я не могу найти, как бороться с неизбежными побочными эффектами.Я не могу просто не получить доступ к глобальному объекту, чтобы создать элемент audio, необходимый для обнаружения поддержки функций.Итак, как мне обращаться с доступом к глобальным функциям / объектам браузера (что я часто и делаю в этой библиотеке) таким образом, чтобы обеспечить удобство сотрясения деревьев и, тем не менее, позволить мне исключить код?

Ответы [ 2 ]

0 голосов
/ 06 марта 2019

Вы можете реализовать модуль, чтобы предоставить вам аналогичную схему использования, предложенную вашим вопросом, используя audio() для доступа к аудиообъекту и canPlay, без вызова функции.Это можно сделать, запустив конструктор Audio в функции, как предложил Асад, и затем вызывая эту функцию каждый раз, когда вы хотите получить к ней доступ.Для canPlay мы можем использовать Proxy , позволяющий выполнять индексацию массива под капотом как функцию.

Давайте предположим, что мы создаем файл audio.js:

let audio = () => new Audio();
let canPlay = new Proxy({}, {
    get: (target, name) => {
        switch(name) {
            case 'ogg':
                return audio().canPlayType('audio/ogg; codecs="vorbis"').replace(/^no$/, '');
            case 'mp3':
                return audio().canPlayType('audio/mpeg; codecs="mp3"').replace(/^no$/, '');
        }
    }
});

export {audio, canPlay}

Это результаты работы с различными index.js файлами, rollup index.js -f iife:

import {} from './audio';
(function () {
    'use strict';



}());
import {audio} from './audio';

console.log(audio());
(function () {
    'use strict';

    let audio = () => new Audio();

    console.log(audio());

}());
import {canPlay} from './audio';

console.log(canPlay['ogg']);
(function () {
    'use strict';

    let audio = () => new Audio();
    let canPlay = new Proxy({}, {
        get: (target, name) => {
            switch(name) {
                case 'ogg':
                    return audio().canPlayType('audio/ogg; codecs="vorbis"').replace(/^no$/, '');
                case 'mp3':
                    return audio().canPlayType('audio/mpeg; codecs="mp3"').replace(/^no$/, '');
            }
        }
    });

    console.log(canPlay['ogg']);

}());

Дополнительно,Нет способа реализовать audio, как первоначально предполагалось, если вы хотите сохранить свойства, описанные в вопросе.Другими короткими возможностями для audio() являются +audio или audio`` (как показано здесь: Вызов функции без скобок ), что можно считать более запутанным.

Наконец,другие глобальные переменные, которые не включают индекс массива или вызов функции, должны быть реализованы аналогично let audio = () => new Audio();.

0 голосов
/ 28 февраля 2019

Вы можете удалить страницу из книги на Haskell / PureScript и просто ограничить себя от появления побочных эффектов при импорте модуля.Вместо этого вы экспортируете thunk, который представляет собой побочный эффект, например, получения доступа к глобальному элементу Audio в браузере пользователя, и параметризации других функций / значений по значению, создаваемому этим thunk.

Вот чтоэто будет выглядеть для вашего фрагмента кода:

// :: type IO a = () -!-> a

// :: IO Audio
let getAudio = () => new Audio();

// :: Audio -> { [MimeType]: Boolean }
let canPlay = audio => {
  ogg: audio.canPlayType('audio/ogg; codecs="vorbis"').replace(/^no$/, '');
  mp3: audio.canPlayType('audio/mpeg; codecs="mp3"').replace(/^no$/, '');
  // ...
};

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

Совершенно очевидно, как вручную подключить все эти новые параметры, но это может оказаться утомительным.Есть несколько методов, чтобы смягчить это;подход, который вы снова можете украсть из Haskell / PureScript, заключается в использовании монады ридера, которая облегчает своего рода внедрение зависимостей для программ, состоящих из простых функций.

Гораздо более подробное объяснение монады ридера и как использоватьэто вписать некоторый контекст в вашу программу выходит за рамки этого ответа, но вот несколько ссылок, где вы можете прочитать об этих вещах:

(заявление об отказе: я не полностью прочитал или не проверил все эти ссылки, я просто погуглил ключевые слова и скопировал некоторые ссылки, где введение выглядело многообещающе)

...