Как бороться с побочными эффектами в коде встряхивания дерева?
Я пытался научиться писать код, который дружелюбно трясет дерево, но столкнулся с проблемой с неизбежными побочными эффектами, с которыми я не уверен, что делать.
В одном из моих модулей я получаю доступ к глобальному конструктору 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
элемент, необходимый для обнаружения поддержки функций. Итак, как мне управлять доступом к глобальным функциям/объектам браузера (что я часто и делаю в этой библиотеке) таким образом, чтобы это было дружественным к дереву и все же позволяло мне исключить код?
Ответы
Ответ 1
Вы можете удалить страницу из книги на 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, заключается в использовании монады считывателя, которая облегчает своего рода внедрение зависимостей для программ, состоящих из простых функций.
Гораздо более подробное объяснение монады читателя и того, как использовать ее для создания некоторого контекста в вашей программе, выходит за рамки этого ответа, но вот несколько ссылок, где вы можете прочитать об этих вещах:
(отказ от ответственности: я не полностью прочитал и не проверил все эти ссылки, я просто погуглил ключевые слова и скопировал некоторые ссылки, где введение выглядело многообещающе)
Ответ 2
Вы можете реализовать модуль, чтобы предоставить вам аналогичную схему использования, предложенную в вашем вопросе, с использованием 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();
,