wqertAnna
@wqertAnna

Можно ли написать функцию, которая проверяет существует ли переменная или нет (js)?

Я хочу написать функцию, которая будет возвращать true, если переменная существует, в противном случае false.

Акцентирую внимание, что речь не просто о переменной значение, которой равно undefined, а именно о переменной, которой не существует.

вот код, который проверяет существование переменной:
let exist = true;
    try {
        if (someVar) {}
    }
    catch(e) {
        exist = false;
    }
    if (exist) console.log('она существует!');


Я пыталась засунуть этот весь код в функцию isExist и возвращать значение переменной exist, но упустила тот факт, что ошибка возникает при вызове функции isExist(someVar), если аргумент someVar не существует.

____________________________________
Позднее пояснение)
Меня интересует можно ли написать такую функцию:

function isExists(someVar){
// любой код, который вернет true, если переменная someVar существует
// false, если не существует
}

function f1() {
    let x;
    isExists(x); //  => true
    isExists(y); //  => false
}

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

f1();
f2();


Причем я понимаю, что если переменная не существует и её передать в качестве аргумента, то это вызовет ошибку.
Поэтому вопрос в том, можно ли это обойти или каким образом создать функцию, которая проверяет существует ли переменная или нет?
  • Вопрос задан
  • 3729 просмотров
Решения вопроса 1
@Interface
Дисклеймер: так как вопрос скорее теоретический, я не стараюсь предоставить "адекватное" решение.
Конечно, все это нельзя использовать в реальном проекте!


Интересный вопрос.

Начнем с того, что определимся насколько критично сохранить сигнатуру функции? В зависимости от этого, решения можно поделить на 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
- упадет производительность (вероятнее всего существенно)

Уровень безумия: буднечно безумное
Ответ написан
Пригласить эксперта
Ответы на вопрос 9
Sanasol
@Sanasol Куратор тега JavaScript
нельзя просто так взять и загуглить ошибку
Непонятно зачем вам такая функция?
И непонятно почему undefined не подходит в таком случае.

Других вариантов-то как бы и нет универсальных.

typeof somevar === 'undefined' единственный нормальный вариант.
Ну и выносить в функцию это смысла нет в целом никакого, и работать не будет опять же из-за первоначальной ошибки о том что переменная не объявлена.

Другой возможный вариант подойдёт только если речь про глобальную область видимости типа window, тогда можно проверить существование ключа 'somevar' in window.

Ну и ваш вариант, который по уже озвученной причине нельзя вынести в функцию.
Ответ написан
@kova1ev
Есть как бы небольшая проблема - разные области видимости. Например, переменные, существующие в одной функции не будут существовать в другой.
Ответ написан
@D3NI4
Можно передать имя переменной как string например.

var x = 123;

function exists(str) {
    try { eval(str); return true; }
    catch { return false; }
}

console.log(exists("x"));
console.log(exists("y"));
Ответ написан
@Retreat
для глобального контекста
globalThis.hasOwnProperty("foo")

для текущего
this.hasOwnProperty("bar")
Ответ написан
Stalker_RED
@Stalker_RED
f (isExist(a)) { // <= здесь возникает ошибка, если переменной не существует, вот в чем проблема этого представления
и что бы вы не написали внутри функции isExist - это не поможет, потому что ошибка вылетает еще ДО вызова функции.

Попробуйте запустить вот это:
function isExist() {
  console.log('вы не увидите это сообщение')
}
isExist(foo) // но получтие ошибку в этой строке
Ответ написан
dollar
@dollar
Делай добро и бросай его в воду.
function isExist(varName) {
	let exist = true;
	try { eval(varName); }
	catch(e) { exist = false; }
	if (exist) console.log('она существует!');
	return exist;
}

isExist('someVar'); //false
let someVar = 123;
isExist('someVar'); //true
Ответ написан
@Serega_SRG
А можно уточнить, в чем практический смысл таких действий?
Необходимо знать, создавалась ли когда нибудь эта переменная и на основе этого что-то делать?
Обычно для этого используется какой-нибудь флаг переменная.
А в js есть фундаментальное различие между не созданной переменной и той, которой назначили undefined?
Ответ написан
brizing
@brizing
function isExist(fn) {
	let exist = true;
	let string = fn.toString();
	let exec = new RegExp('return\\s([a-zA-Z0-9_]+)').exec(string);
	let varName = exec !== null ? exec[1] : false;
	    
    try {
        if (eval(varName)) {}
    }
    catch(e) {
        exist = false;
    }
    if (exist) console.log('она существует!');
}
isExist(()=>{return foo});

Это может сработать, если метод toString() везде работает одинаково и возвращает ()=>{return foo}
Ответ написан
peterpro
@peterpro
Сам вопрос некорректный, поэтому ответа на него нет.
Ответ написан
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы