Можно ли написать функцию, которая проверяет существует ли переменная или нет (js)?
Дисклеймер: так как вопрос скорее теоретический, я не стараюсь предоставить "адекватное" решение.
Конечно, все это нельзя использовать в реальном проекте!
Интересный вопрос.
Начнем с того, что определимся насколько критично сохранить сигнатуру функции? В зависимости от этого, решения можно поделить на 2 категории:
- где допускается изменение сигнатуры
- где сигнатура должна быть строго такой и никакой иначе
Также есть ряд решений, подразумевающий предобработку кода тем или иным путем.Стоит оговориться, что строго говоря такую функцию в данном виде написать нельзя, потому как при вызове функции переменная уже должна быть "разименована", а значит мы так или иначе будем работать с ее значением или с какими-то косвенными сайд эффектами вызванными обращением к ней.
Сначала рассмотрим несколько решений с изменением сигнатуры: они попроще и в каком-то смысле более логичны.
есть как минимум 2 категории решений с изменением сигнатуры:
- функция будет принимать строку
- функция будет принимать функцию
При этом проблема того, что все падает до попадания в функцию, пропадает сама собой.
вот несколько решений:
1) класс решений построенный на анализе кода в рантайме:
решение заключается в том, что вызываемая функция анализирует свой код (а также код функции которая вызвала ее и т.д.)
https://stackoverflow.com/questions/2051678/gettin... вот тут есть частчный разбор такого решения.
Приводить его в виде кода я не буду :)
Плюсы:
- вроде как решает задачу
- сигнатура изменилась не сильно
Минусы:
- крайне жирное решение с точки зрения производительности
- сигнатура не та, что просили
- нельзя использовать 'use strict', так как решения возможно будут завязаны на arguments.callee.caller
Уровень безумия: достаточно безумное
2) решение построенное на том, что приниматься будет функция:isExists(() => someVar); // такая будет сигнатура
Это решение хоть и меняет сигнатуру, но ее возможно сохранить максимально подобной желаемой
Одна из возможных реализаций:function isExists(someVarFn){
    try {
        someVarFn();
    } catch(e) {
        if (e instanceof ReferenceError) {
            return false;
        }
        throw e;
    }
    return true;
}
Примеры использования:function f1() {
    let x;
    console.log(isExists(() => x)); //  => true
    console.log(isExists(() => y)); //  => false
}

function f2() {
    let x;
    console.log(isExists(() => x)); //  =>  true
    console.log(isExists(() => y)); //  => false
}

f1();
f2();
Плюсы:
- плюс-минус адекватная производительность
- довольно компактно
Минусы: 
- несильно меняется сигнатура функции
- api получается несколько не очевидным
Уровень безумия: почти адекватное
3) решения с использованием eval:
Суть похожа на решение #2, но с другим api:function isExists(varName, fn){
    return fn(`(()=>{
        try {
            const nop = ${varName};
        } catch(e) {
            if (e instanceof ReferenceError) {
                return false;
            }
            throw e;
        }
        return true;
    })()`);
}

function f1() {
    let x;
    console.log( isExists('x', c => eval(c)) ); //  => true
    console.log( isExists('y', c => eval(c)) ); //  => false
}

function f2() {
    let y;
    console.log( isExists('x', c => eval(c)) ); //  =>  false
    console.log( isExists('y', c => eval(c)) ); //  => true
}

f1();
f2();
Плюсы:
- сложнее чем в №2 передать какую-то фигню
Минусы: 
- сигнатура отлиается сильнее и уродливей
- сложнее / жирнее / больше чем №2
Уровень безумия: безумненькоРешения с сохранением сигнатуры:
4) магия es6 Proxies + with
класс решений строится на стратегии: слушать доступ к глобальному объекту и если он происходит и в нем нет переменной с таким именем - такой переменной нет:const pseudoUndefined = {};

const scopeTrap = new Proxy(window, {
    has() {return true},

    get(target, prop) {
        if (prop in window) {
            return window[prop];
        } else {
            return pseudoUndefined;
        }
    }
})

function isExists(someVar) {
    return someVar !== pseudoUndefined;
}

// весь код использующий функцию должен быть обернут в такой with :(
with (scopeTrap) {

    let y;
    console.log( isExists(x) ); //  =>  false
    console.log( isExists(y) ); //  => true

}
Плюсы:
- мы сохранили желаемую сигнатуру! Ура!
Минусы: 
- теперь вместо исключения будет получаться что-то совсем левое. Хорошего дебагга! :D
- накладные расходы производительности
- все нужно обернуть в with
- необходима поддержка es6+
Уровень безумия: с точки зрения реализации - задача подъемная. использовать в бою - а вы знаете толк!Решения с предобработкой:
Первые две категории решений исходят из того, код (включая код вызовов функции) будет исполняться без изменений напряму интерпретатором. Если развязать себе руки, то открывается целый спектр новых решений:
5) первое, что приходит в голову с приобретением такой свободы - это, очевидно... написание своего языка программирования компилируемого в js / своего интерпретатора js и т.д.
В рамках этого решения особого труда (относительно всего прочего) не составит реализовать такую функцию нативно.
Плюсы: ...
Минусы: ...
(Без комментариев)
Уровень безумия: я думаю все понятно
...есть и более сдержанные решения, например, используя compile-time можно оптимизировать предыдущие решения:
6) можно используя парсер составить дерево областей видимости (хэш-таблица [функция <-> список переменных]), привязать каждую область к функции, через позицию в исходном коде (возможно придется сохранять дополнительную информацию). А дальше все это интегрировать с решением №1.
В итоге:
Плюсы относительно (#1):
- относительно хорошая производительность
Минусы:
- сложно 
- дорого
- требует compile-time 
Уровень безумия: стало еще безумнее, чем было :)
7) написать (или найти) плагин для babel'я (или чего-то другого), который будет трансформировать доступ к переменным в особую функцию
Плюсы относительно (#1):
- достигнут результат
Минусы:
- требует поддержки
- требует compile-time 
- упадет производительность (вероятнее всего существенно)
Уровень безумия: буднечно безумное
f (isExist(a)) {  // <= здесь возникает ошибка, если переменной не существует, вот в чем проблема этого представленияи что бы вы не написали внутри функции isExist - это не поможет, потому что ошибка вылетает еще ДО вызова функции.
Попробуйте запустить вот это:function isExist() {
  console.log('вы не увидите это сообщение')
}
isExist(foo) // но получтие ошибку в этой строке