JavaScript объявить переменную и использовать запятую в одном выражении? - PullRequest
3 голосов
/ 07 апреля 2020

известно, что для объявления нескольких переменных используется формат, подобный следующему:

let k = 0,
    j = 5 /*etc....*/

Также известно, что для выполнения нескольких операторов в одной строке (что полезно для функций со стрелками, что делает ненужным введите ключевое слово return), также используется запятая ",", например:

let r = "hello there world, how are you?"
.split("")
.map(x => (x+=5000, x.split("").map(
  y => y+ + 8
).join("")))
.join("")

console.log(r)

не самый элегантный пример, но дело в том, что вы можете выполнять несколько операторов в одной строке, разделенных запятой ",", и возвращается последнее значение.

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

как вы комбинируете обе эти техники? То есть, как мы объявляем переменную в одной строке, и через одну запятую используем эту переменную для чего-то?

Следующее не работает:

let k = 0, console.log(k), k += 8

говорит

Uncaught SyntaxError: неожиданный токен '.'

и без console.log, он думает, что я заново объявляю k:

let k = 0, k += 8

дает

Uncaught SyntaxError: Identifier 'k' has already been declared

И поместить все это в круглые скобки так:

(let k = 0, k += 8);

дает

Uncaught SyntaxError: Unexpected identifier

со ссылкой на ключевое слово let. Однако без этого ключевого слова проблем не возникает:

(k = 0, k += 8);

, за исключением того факта, что k теперь становится глобальной переменной, которая не нужна.

Есть ли какой-то обходной путь здесь ?

Как использовать оператор запятой вместе с объявлением локальной переменной в JavaScript?

РЕДАКТИРОВАТЬ в ответ на eval часть ответа VLAZ, чтобы передать параметры в Eval, пользовательские функции могут быть сделаны:

function meval(mainStr, argList) {
    let ID = (
        Math.random().toString() + 
        performance.now().toString()
        
    ).split(".").join("").split("")
    .map(x => ("qwertyuio")[x])
    .join(""),
        varName = "$______"+ID+"_____$",
        str = `
        var ${varName} = {};
        (argList => {
            Object.entries(argList).forEach(x => {
                ${varName}[x[0]] = x[1];   
            })
             
        });
    `;
	let myEval = eval;
    
    
    
    
	return (() => {

		myEval(str)(argList)
		myEval(`
			${
				Object.keys(argList).map(x => 
					"let " + x + " = " + varName + "['" + x +"'];"
				).join("\n")
			}
			${mainStr}
			delete window[${varName}];
		`)
		
	})()
}

meval(`
    var g = a.ko + " world!"
    
`, {
    a: {ko: "hi"}
})
console.log(g);

1 Ответ

4 голосов
/ 07 апреля 2020

Вы не можете этого сделать. Синтаксис объявления переменных допускает запятую для объявления нескольких переменных одновременно. Каждая переменная также может быть дополнительно инициализирована как часть объявления, поэтому синтаксис (более абстрактно):

(var | let | const) variable1 [= value1], variable2 [= value2], variable3 [= value3], ..., variableN [= valueN]

Однако, это НЕ оператор запятой . Так же, как то, что запятая в parseInt("42", 10) также не является оператором запятой - это просто запятая символ , которая имеет другое значение в другом контексте.

Однако реальная проблема заключается в том, что что оператор запятой работает с выражениями , а объявление переменной является оператором .

Краткое объяснение различия:

Выражения

В основном все, что дает значение: 2 + 2, fn(), a ? b : c и др. c. Это то, что будет вычислено и произведет что-то.

Выражения могут быть вложены во многих случаях: например, 2 + fn() или ( a ? ( 2 + 2 ) : ( fn() ) ) (каждое выражение заключено в квадратные скобки для ясности). Даже если выражение не дает полезного значения, которое ничего не меняет - функция без явного возврата выдаст undefined, поэтому 2 + noReturnFn() выдаст gibberi sh, но это все еще допустимый синтаксис выражения.

Примечание 1 из 2 (подробнее в следующем разделе): присвоение переменной - это выражение, выполнение a = 1 приведет к присвоению значения:

let foo;
console.log(foo = "bar")

Заявления

Эти не дают значение. Не undefined просто ничего. Примеры включают в себя if(cond){}, return result, switch.

Оператор является действительным только автономно. Вы не можете вкладывать их как if (return 7), так как это синтаксически недопустимо. Кроме того, вы не можете использовать операторы, где ожидается выражение - console.log(return 7) одинаково недопустим.

Просто примечание, выражение можно использовать в качестве оператора. Они называются выражениями :

console.log("the console.log call itself is an expression statement")

Итак, вы можете использовать выражение, где выражение допустимо, но нельзя использовать выражение, где выражение допустимо.

Примечание 2 из 2 : переменная присваивание является выражением, однако объявление переменной с присваиванием не является. Это всего лишь часть синтаксиса для объявления объявления переменной. Таким образом, эти два перекрываются, но не связаны между собой, только то, как оператор запятой и объявление нескольких переменных похожи (позволяют вам делать несколько вещей), но не связаны.

console.log(let foo = "bar"); //invalid - statement instead of expression

Отношение с оператором запятой

Теперь мы знаем, что разница и ее станет легче понять. Оператор запятой имеет форму

exp1, exp2, exp3, ..., expN

и принимает выражений , а не операторов. Он выполняет их один за другим и возвращает последнее значение. Так как операторы не не имеют возвращаемого значения, они никогда не могут быть действительными в таком контексте: (2 + 2, if(7) {}) является бессмысленным кодом с точки зрения компилятора / интерпретатора, поскольку не может быть возвращенным здесь.

Итак, учитывая это, мы не можем смешивать объявление переменной и оператор запятой. let a = 1, a += 1 не работает, потому что запятая обрабатывается как оператор объявления переменной , и если мы попытаемся сделать ( ( let a = 1 ), ( a += 1 ) ), это все равно недопустимо, поскольку первая часть все еще является оператором, а не выражением.

Возможные обходные пути

Если вам действительно нужно создать переменную внутри контекста выражения и , избегайте создания неявных глобальных переменных, тогда у вас мало вариантов. Давайте использовать функцию для иллюстрации:

const fn = x => {
  let k = computeValueFrom(x);
  doSomething1(k);
  doSomething2(k);
  console.log(k);
  return k;
}

Итак, это функция, которая создает значение и использует его в нескольких местах. Мы попытаемся преобразовать его в сокращенный синтаксис.

IIFE

const fn = x => (k => (doSomething1(k), doSomething2(k), console.log(k), k))
                   (computeValueFrom(x));

fn(42);

Объявите новую функцию внутри вашей, которая принимает k в качестве параметра, а затем немедленно вызовите эту функцию со значением computeValueFrom(x). Если мы отделим функцию от вызова для ясности, мы получим:

const extractedFunction = k => (
  doSomething1(k), 
  doSomething2(k), 
  console.log(k), 
  k
);

const fn = x => extractedFunction(computeValueFrom(x));

fn(42);

Итак, функция принимает k и использует ее несколько раз подряд с оператором запятой. Мы просто вызываем функцию и предоставляем значение k.

Cheat с использованием параметров

const fn = (fn, k) => (
  k = computeValueFrom(x), 
  doSomething1(k), 
  doSomething2(k), 
  console.log(k), 
  k
);

fn(42);

В основном так же, как и раньше - мы используем оператор запятой для выполнения нескольких выражений. Однако на этот раз у нас нет дополнительной функции, мы просто добавляем дополнительный параметр к fn. Параметры являются локальными переменными, поэтому они ведут себя подобно let / var с точки зрения создания локальной изменяемой привязки. Затем мы присваиваем этому идентификатору k, не влияя на глобальную область видимости. Это первое из наших выражений, а затем мы продолжаем с остальными.

Даже если кто-то вызовет fn(42, "foo"), второй аргумент будет перезаписан, поэтому в действительности он такой же, как если бы fn принимал только один параметр.

Обман с использованием обычного тела функции

const fn = x => { let k = computeValueFrom(x); doSomething1(k); doSomething2(k); console.log(k); return k; }

fn(42);

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

Композиция функций и функциональное программирование

const log = x => {
  console.log(x);
  return x;
}

const fn = compose(computeValueFrom, doSomething1, doSomething2, log) 

fn(42);

Это огромный topi c, так что я едва собираюсь поцарапать поверхность здесь. Я также сильно упрощаю вещи только для того, чтобы представить концепцию.

Итак, что такое функциональное программирование (FP)?

Это программирование с использованием функций в качестве основы c строительные блоки. Да, у нас do уже есть функции, и мы используем их для создания программ. Однако не-FP программы по существу "склеивают" эффекты, используя императивные конструкции. Таким образом, вы ожидаете, что if s, for s и вызов нескольких функций / методов приведут к эффекту.

В парадигме FP у вас есть функции, которые вы объединяете вместе, используя другие функции. Очень часто это связано с тем, что вы интересуетесь цепочками операций над данными.

itemsToBuy
  .filter(item => item.stockAmount !== 0)      // remove sold out
  .map(item => item.price * item.basketAmount) // get prices
  .map(price => price + 12.50)                 // add shipping tax
  .reduce((a, b) => a + b, 0)                  // get the total

Массивы поддерживают методы из функционального мира, поэтому этот является допустимым примером FP.

Что такое функциональная композиция

Теперь предположим, что вы хотите использовать функции многократного использования из приведенного выше, и вы извлекаете эти две функции:

const getPrice = item => item.price * item.basketAmount;
const addShippingTax = price => price + 12.50;

Но вам это не нужно сделать две картографические операции. Мы могли бы просто переписать их в:

const getPriceWithShippingTax = item => (item.price * item.basketAmount) + 12.50;

, но давайте попробуем сделать это без непосредственного изменения функций. Мы можем просто вызывать их один за другим, и это сработает:

const getPriceWithShippingTax = item => addShippingTax(getPrice(item));

Мы снова использовали функции. Мы бы позвонили getPrice и результат был передан addShippingTax. Это работает до тех пор, пока функция next , которую мы вызываем, использует вход предыдущей. Но это не очень хорошо - если мы хотим вызвать три функции f, g и h вместе, нам нужен x => h(g(f(x))).

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

const compose = (...functions) => input => functions.reduce(
    (acc, fn) => fn(acc),
    input
)

const f = x => x + 1;
const g = x => x * 2;
const h = x => x + 3;

//create a new function that calls f -> g -> h
const composed = compose(f, g, h);

const x = 42

console.log(composed(x));

//call f -> g -> h directly
console.log(h(g(f(x))));

И вот вам go, мы «склеили» функции вместе с другой функцией. Это эквивалентно выполнению:

const composed = x => {
  const temp1 = f(x);
  const temp2 = g(temp1);
  const temp3 = h(temp2);
  return temp3;
}

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

Где я тут обманул

Ху, мальчик, время исповеди:

  • Как я уже сказал - функциональная композиция работает с функциями, которые принимают на вход предыдущую. Итак, чтобы сделать то, что у меня было в самом начале раздела FP, тогда doSomething1 и doSomething2 должны вернуть полученное значение. Я включил это log, чтобы показать, что должно произойти - взять значение, сделать что-то с ним, вернуть значение. Я пытаюсь просто представить концепцию, поэтому я пошел с кратчайшим кодом, который сделал это в достаточной степени.
  • compose может быть неправильным. Это варьируется, но с большим количеством реализаций compose работает в обратном направлении через аргументы. Итак, если вы хотите позвонить f -> g -> h, вы на самом деле наберете compose(h, g, f). Для этого есть основания - версия real в конце концов равна h(g(f(x))), так что именно это эмулирует compose. Но это не очень хорошо читается. Композиция слева направо, которую я показал, обычно называется pipe (как в Ramda ) или flow (как в Loda sh). Я подумал, что было бы лучше, если бы compose использовался для функциональной композиции заголовка, но то, как вы читаете compose, поначалу нелогично, поэтому я выбрал версию слева направо .
  • На самом деле действительно гораздо больше функционального программирования. Существуют конструкции (аналогично тому, как массивы являются конструкциями FP), которые позволят вам начать с некоторого значения, а затем вызвать несколько функций с указанным значением. Но композицию проще начинать.

Запрещенная техника eval

Дун, Дун, Данн!

const fn2 = x => (eval(`var k = ${computeValueFrom(x)}`), doSomething1(k), doSomething2(k), console.log(k), k)

fn(42);

Итак ... я снова соврал. Вы можете подумать: «Боже, зачем мне использовать кого-нибудь, кого этот парень написал здесь, если это все ложь». Если вы думаете, что - хорошо , продолжайте думать об этом. не используйте это, потому что это очень плохо .

Во всяком случае, я подумал, что стоит упомянуть, прежде чем кто-то другой заскочит, не объяснив почему это плохо.

Прежде всего, что происходит - использование eval для динамического создания локальной привязки. А затем с помощью указанного связывания. Это не создает глобальную переменную:

const f = x => (eval(`var y =  ${x} + 1`), y);

console.log(f(42));         // 42
console.log(window.y);      // undefined
console.log("y" in window); // false
console.log(y);             // error

Имея это в виду, давайте посмотрим , почему этого следует избегать.

Эй, вы заметили, что я использовал var, вместо let или const? Это только первая из тех ошибок, в которые ты можешь попасть. Причина использования var заключается в том, что eval всегда создает новую лексическую среду при вызове с использованием let или const. Вы можете увидеть спецификации раздел 18.2.1.1 Семантика времени выполнения: PerformEval . Поскольку let и const доступны только в пределах окружающей лексической среды, доступ к ним можно получить только внутри eval, а не снаружи.

eval("const a = 1; console.log('inside eval'); console.log('a:', a)");

console.log("outside eval");
console.log("a: ", a); //error

Итак, в качестве хака вы можете использовать только var, чтобы объявление было доступно за пределами eval.

Но это еще не все. Вы должны быть очень осторожными с тем, что вы передаете в eval, потому что вы создаете код. Я обманул (... как всегда), используя номер. Числовые значения c и числовые значения c совпадают. Но вот что происходит, если у вас нет цифры c:

const f = (x) => (eval("var a = " + x), a);

const number = f(42);
console.log(number, typeof number); //still a number

const numericString = f("42");
console.log(numericString, typeof numericString); //converted to number

const nonNumericString = f("abc"); //error
console.log(nonNumericString, typeof nonNumericString);

Проблема в том, что код, созданный для numericString, равен var a = 42; - это значение строки. Итак, оно конвертируется. Затем с nonNumericString вы получите ошибку, так как она выдает var a = abc, а переменная abc отсутствует.

В зависимости от содержимого строки вы можете получить всевозможные вещи - вы можете получить то же значение, но преобразовать его в число, вы можете получить что-то совсем другое или вы можете получить SyntaxError или ReferenceError.

Если вы хотите сохранить строковую переменную, чтобы она оставалась строкой, вам нужно создать строку literal :

const f = (x) => (eval(`var a = "${x}"`), a);

const numericString = f("42");
console.log(numericString, typeof numericString); //still a string

const nonNumericString = f("abc"); //no error
console.log(nonNumericString, typeof nonNumericString); //a string

const number = f(42);
console.log(number, typeof number); //converted to string

const undef = f(undefined);
console.log(undef, typeof undef); //converted to string

const nul = f(null);
console.log(nul, typeof nul); //converted to string

Это работает ... но вы теряете типы, которые вы фактически вставили - var a = "null" - это не то же самое, что null.

Это даже хуже, если вы получаете массивы и объекты, так как вы должны их сериализовать, чтобы иметь возможность передавать их в eval. И JSON.stringify не обрежет его, так как он не идеально сериализует объекты - например, он удалит (или изменит) undefined значения, функции и потерпит неудачу при сохранении прототипов или круговых структур.

Более того, код eval не может быть оптимизирован компилятором, поэтому он будет значительно медленнее, чем простое создание привязки. Если вы не уверены, что это так, то вы, вероятно, не нажали на ссылку на спец. c. Сделай это сейчас.

Назад? Хорошо, вы заметили , сколько вещей задействовано при запуске eval? Для каждой операции c имеется 29 шагов, и несколько из них ссылаются на другие абстрактные операции. Да, некоторые являются условными и да, количество шагов не обязательно означает, что это займет больше времени, но определенно будет делать намного больше работы, чем нужно для создания привязки. Напоминание, которое не может быть оптимизировано движком на лету, поэтому вы будете работать медленнее, чем «реальный» (не eval ed) исходный код.

Это даже прежде, чем упоминать о безопасности. Если бы вам когда-либо приходилось проводить анализ безопасности вашего кода, вы бы ненавидели eval со страстью. Да, eval может быть безопасным eval("2 + 2") не вызовет никаких побочных эффектов или проблем. Проблема в том, что вы должны быть абсолютно уверены, что вы вводите известный хороший код eval. Итак, что будет анализ для eval("2 + " + x)? Мы не можем сказать, пока не проследим все возможные пути для установки x. Затем проследите все, что используется для установки x. Затем проследите их и т. Д. c, пока не обнаружите, что начальное значение безопасно или нет. Если это происходит из ненадежного места, то у вас есть проблема.

Пример: вы просто берете часть URL и помещаете его в x. Скажем, у вас есть example.com?myParam=42, поэтому вы берете значение myParam из строки запроса. Злоумышленник может легко создать строку запроса, для которой myParam установлен код, который украдет учетные данные пользователя или конфиденциальную информацию, и отправит их самому себе. Таким образом, вам необходимо убедиться, что вы фильтруете значение myParam. Но вам также приходится повторять один и тот же анализ время от времени - что, если вы ввели новую вещь, где вы теперь берете значение для x у повара ie? Что ж, теперь это уязвимо.

Даже , если каждое возможное значение для x безопасно, вы не можете пропустить повторный запуск анализа. И вы должны делать это регулярно , а в лучшем случае просто сказать"Хорошо, все в порядке". Тем не менее, вам может понадобиться доказать это. Вам может потребоваться заполнить день просто для x. Если вы использовали eval еще четыре раза, уходит целая неделя.

Итак, просто придерживайтесь старой пословицы "eval is evil". Конечно, не должно быть , но оно должно быть последним средством.

...