JavaScript声明一个变量并在一个语句中使用逗号运算符? [英] JavaScript declare a variable and use the comma operator in one statement?

查看:99
本文介绍了JavaScript声明一个变量并在一个语句中使用逗号运算符?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

众所周知,要声明多个变量,使用的格式如下:

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

也可以在一个语句中执行多个语句行(对于箭头功能很有用,无需编写 return 关键字),还使用逗号,运算符,如下所示:



  let r =你好,世界,你好吗?。split() .map(x =>(x + = 5000,x.split()。map(y => y + + 8).join()))。join()console.log(r)  



不是最优雅的示例,但关键是您可以执行一行中有多个语句,用逗号,分隔,并返回最后一个值。



所以问题是:



你如何组合这两种技术?意思是,我们如何在一行中声明一个变量,然后在逗号后面使用该变量做某事?



以下操作无效:

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


未捕获到的语法错误:意外令牌'。'


并且没有console.log,它认为我正在重新声明k:

 让k = 0,k + = 8 

给予

 未捕获的语法错误:标识符'k'已被声明

然后将整个内容放在括号中:

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

给予

 未捕获的语法错误:意外的标识符

引用关键字 let。但是,没有该关键字,就没有问题:

 (k = 0,k + = 8); 

除了k现在变成一个不需要的全局变量的事实。



这里有某种解决方法吗?



如何将逗号运算符与局部变量声明一起使用



编辑以响应VLAZ的eval答案,将参数传递到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];})})}; `;让myEval = eval; return(()=> {myEval(str)(argList)myEval(`$ {Object.keys(argList).map(x => let + x + = + varName + [' + x +'];).join( \n)} $ {mainStr}删除窗口[$ {varName}];`)})()} meval(`var g = a.ko + world !`,{a:{ko: hi}})console.log(g);  

解决方案

您不能这样做。变量声明语法允许使用逗号来一次声明多个变量。每个变量也可以作为声明的一部分进行初始化,因此语法(更抽象):

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

但是,这不是逗号运算符。就像 parseInt( 42,10)中的逗号也不是逗号运算符一样-只是逗号字符具有一个


然而,真正的问题是逗号运算符与 expressions 一起使用,而变量声明是一个语句


差异的简短说明:


表达式


基本上任何会产生值的东西: 2 + 2 fn() a? b:c 等。这将被计算并产生一些东西。


表达式可以在很多情况下嵌套: 2 +例如,fn()(a?(2 + 2):(fn()))(为清晰起见,每个表达式都用方括号括起来) 。即使表达式不会产生不会改变事情的可用值-没有显式返回的函数也会产生 undefined ,因此 2 + noReturnFn ()会产生乱码,但仍然是有效的表达式语法。


注2之1 (在下一节中有更多说明):变量赋值表达式,执行 a = 1 将产生要赋值的值:


  let foo; 
console.log(foo = bar)


声明


这些不要产生一个值。不是 undefined 一样。示例包括 if(cond){} 返回结果开关


一条语句仅有效。您不能像 if(返回7)这样嵌套它们,因为这在语法上无效。您不能再使用期望表达式的语句- console.log(返回7)同样无效。


仅需注意,表达式 可以用作语句。这些称为表达式语句


  console.log( console.log调用本身就是一个表达式语句) 


因此,您可以使用表达式有效的表达式,但是不能使用表达式有效的表达式。


注释2之2 :变量 assignment 是一个表达式,但不是带有赋值的变量声明 。它只是变量声明语句语法的一部分。因此,这两个重叠但不相关,只是逗号运算符和声明多个变量的方式相似(允许您做多个事情)但不相关。


  console.log(let foo = bar); //无效-语句而不是表达式 


与逗号运算符的关系


现在我们知道区别,它应该变得更容易理解。逗号运算符的格式为

  exp1,exp2,exp3,...,expN 

并接受表达式,而不是语句。它一个接一个地执行它们并返回最后一个值。由于语句具有返回值,因此它们在这种情况下永远无效:(2 + 2,if(7){})是无意义的代码,因为这里不能返回任何内容。


因此,考虑到这一点,我们不能真正将变量声明和逗号混合使用操作员。 let a = 1,+ = 1 不起作用,因为逗号被视为变量声明语句,如果我们尝试执行((let a = 1),(a + = 1))仍然无效,因为第一部分仍然是语句,而不是表达式。


可能的解决方法


如果您确实需要在表达式上下文中生成一个变量并且避免生成隐式全局变量,那么可用的选项很少给你。让我们使用一个函数进行说明:

  const fn = x => {
让k = computeValueFrom(x);
doSomething1(k);
doSomething2(k);
console.log(k);
返回k;
}

因此,它是一个产生值并在少数地方使用的函数。我们将尝试将其转换为简写语法。


IIFE


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

fn(42);

在自己的内部声明一个以 k 为参数的新函数然后立即使用 computeValueFrom(x)的值调用该函数。如果为了清楚起见,将函数与调用分开,则会得到:

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

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

fn(42);

因此,该函数使用 k 并按顺序使用它几次用逗号运算符。我们只是调用该函数并提供 k 的值。


使用参数作弊


  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 => {让k = computeValueFrom(x); doSomething1(k); doSomething2(k); console.log(k);返回k; } 

fn(42);

我撒了谎。或更确切地说,我作弊。在表达式上下文中,这不是 ,您拥有与以前相同的所有功能,但是只是删除了换行符。请务必记住,您可以这样做,并用分号分隔不同的语句。仍然是一行,并且几乎没有以前。


函数组成和函数编程


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

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

fn(42);

这是一个巨大主题,所以我在这里几乎不做文章。我也大大简化了工作,只是为了介绍这个概念。


那么,什么是 函数式编程(FP)?


使用功能作为基本构建块进行编程。是的,我们已经了,我们确实使用它们来生成程序。然而,非FP程序本质上胶粘于胶粘剂。使用命令式构造共同作用。因此,您希望 if s, s,并调用多个函数/方法来产生效果。 / p>

在FP范例中,您具有使用其他功能一起编排的功能。很多时候,这是因为您对数据操作链感兴趣。

  itemsToBuy 
.filter(item => item。 stockAmount!== 0)//删除售罄的
.map(item => item.price * item.basketAmount)//获取价格
.map(price => price + 12.50)/ /添加运费税
.reduce((a,b)=> a + b,0)//获得总计

数组支持功能世界中的方法,因此,这 是有效的FP示例。


什么是功能组合


现在,假设您想从上面获得可重用的函数,并提取了这两个函数:

  const getPrice = item = > item.price * item.basketAmount; 
const addShippingTax = 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)=>输入=> functions.reduce(
(acc,fn)=> fn(acc),
输入


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

//创建一个新函数,调用f-> g-> h
const组成= compose(f,g,h);

const x = 42

console.log(composited(x));

//调用f-> g-> h直接
console.log(h(g(f(x))));


然后您就可以将粘贴到这些功能以及另一个功能。等效于:

  const composition = x => {
const temp1 = f(x);
const temp2 = g(temp1);
const temp3 = h(temp2);
返回temp3;
}

但支持任意数量的函数,并且不使用临时变量。因此,我们可以归纳出许多可以有效执行相同操作的过程-从一个函数传递一些输入,获取输出并将其输入到下一个函数,然后重复。


我在这里作弊


哦,男孩,坦白的时间:



  • 正如我所说,功能组合与需要前一个的输入。因此,为了执行FP部分开头的内容, doSomething1 doSomething2 返回他们获得的价值。我已经包括了 log 来显示需要发生的事情-取一个值,用它做点什么,然后返回该值。我只是想提出这个概念,所以在一定程度上做了最短的代码。

  • 撰写可能是误称。它有所不同,但是有很多实现 compose 通过参数实现向后。因此,如果您要呼叫 f -> g -> h 实际上是您进行 compose(h,g,f)的。这样做是有道理的-毕竟 real 版本是 h(g(f(x())),所以这就是撰写进行模拟。但是它读起来并不好。我显示的从左到右的组合通常命名为 pipe (例如 Ramda )或 flow (如 Lodash )。我认为将 compose 用于功能组合标题会更好,但是阅读 compose 的方式会更好code>起初是违反直觉的,所以我使用了从左到右的版本。

  • 真的还有很多功能编程。有一些构造(类似于数组是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); //未定义
console.log( y in window); // false
console.log(y); //错误


牢记这一点,让我们看看为什么 应该避免。


嘿,您是否注意到我使用了 var 而不是 let const ?这只是您首先要了解的陷阱。使用 var 的原因是 eval 始终在使用调用时会创建一个新的词汇环境。 let const 。您可以查看规范 18.2.1.1运行时语义:PerformEval 。由于 let const 仅在封闭的词法环境中可用,因此您只能在<$ c $内部访问它们c> eval 而不在外面。


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

console.log(外部评估);
console.log( a:,a); //错误


因此,作为一种hack,您只能使用 var ,这样声明就可以在 eval 之外使用。


但这还不是全部。您必须非常小心地输入到 eval 中,因为您正在生成代码。我使用数字作弊(...一如既往)。数字文字和数字值是相同的。但是,如果没有数字,则会发生以下情况:


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

常数= f(42);
console.log(number,typeof number); //仍然是数字

const numericString = f( 42);
console.log(numericString,typeof numericString); //转换为数字

const nonNumericString = f( abc); //错误
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); //还是一个字符串

const nonNumericString = f( abc); //没有错误
console.log(nonNumericString,typeof nonNumericString); //一个字符串

const number = f(42);
console.log(number,typeof number); //转换为字符串

const undef = f(undefined);
console.log(undef,typeof undef); //转换为字符串

const nul = f(null);
console.log(nul,typeof nul); //转换为字符串


这可行...但是您实际上丢失了类型放入- var a =空 null 不同。


如果获得数组和对象,则更糟,因为必须序列化它们才能将它们传递给 eval 。而且 JSON.stringify 不会削减它,因为它不能完美地序列化对象-例如,它将删除(或更改)未定义的 的值,函数及其在保留原型或圆形结构上的失败。


此外, eval 代码不能由编译器进行了优化,因此比单纯创建绑定要慢得多。如果您不确定会发生这种情况,则可能没有点击该规范的链接。现在这样做。


返回吗?确定,您是否注意到运行 eval 时涉及了多少东西?每个规范有29个步骤,其中多个步骤引用了其他抽象操作。是的,有些是有条件的,是的,步骤的数量并不一定意味着要花费更多的时间,但肯定会比创建绑定需要做的工作要多得多。提醒,引擎无法即时优化,因此您会比真实搜索引擎慢(非 eval ed)源代码。


这甚至还没有提到安全性。如果您曾经需要对代码进行安全性分析,那么您将满怀恨意 eval 。是的, eval 可以是安全的 eval( 2 + 2)不会产生任何副作用或问题。问题在于,您必须绝对确保将已知的良好代码提供给 eval 。那么, eval( 2 + + x)的分析结果是什么?我们必须说出要设置的 x 的所有可能路径,然后才能说。然后追溯用于设置 x 的所有内容。然后回溯那些,等等,直到您发现 initial 值是否安全。如果它来自不受信任的地方,那么您会遇到问题。


示例:您只需将URL的一部分放入 x 。假设您有一个 example.com?myParam=42 ,因此您从查询字符串中获取了 myParam 的值。攻击者可以轻易地将查询字符串的 myParam 设置为可窃取用户凭据或专有信息并将其发送给自己的代码。因此,您需要确保正在过滤 myParam 的值。但是,您还必须经常进行相同的分析-如果您引入了新事物,现在又从Cookie中获取 x 的值,该怎么办?好吧,现在这很容易受到攻击。


即使如果每个 x 可能的值都是安全的,您无法跳过重新运行分析。而且您必须定期进行 操作,然后在最佳情况下,只需确定就可以了。但是,您可能还需要证明这一点。您可能需要 just 来填写 x 。如果您又使用了 eval 次,则需要整整一周的时间。


因此,请遵守旧的格言评估是邪恶的。当然,它不是必须,但它应该是不得已的工具。


it's known that to declare multiple variables, one uses a format like:

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

It's also known that to execute multiple statements in one line (which is useful for arrow functions, making it not necessary to write the return keyword), the comma "," operator is also used, like so:

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

console.log(r)

not the most elegant example, but the point is you can execute multiple statements in one line, separated by a comma ",", and the last value is returned.

So the question:

how do you combine both of these techniques? Meaning, how do we declare a variable in one line, and, one comma later, use that variable for something?

The following is not working:

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

says

Uncaught SyntaxError: Unexpected token '.'

and without the console.log, it thinks I'm re-declaring k:

let k = 0, k += 8

gives

Uncaught SyntaxError: Identifier 'k' has already been declared

And putting the whole thing in parenthesis like so:

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

gives

Uncaught SyntaxError: Unexpected identifier

referring to the keyword "let". However, without that keyword, there is no problem:

(k = 0, k += 8);

except for the fact that k now becomes a global variable, which is not wanted.

Is there some kind of workaround here?

How can I use the comma operator together with a local variable declaration in JavaScript?

EDIT in response to VLAZ's eval part of the answer, to pass parameters into eval, a custom function can be made:

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);

解决方案

You cannot do that. The variable declaration syntax allows for a comma in order to declare multiple variables at once. Each variable can also be optionally initialised as part of the declaration, so the syntax is (more abstractly):

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

However, that is NOT the comma operator. Same like how the comma in parseInt("42", 10) is also not the comma operator - it's just the comma character that has a different meaning in a different context.

The real problem, however, is that the comma operator works with expressions, while the variable declaration is a statement.

Short explanation of the difference:

Expressions

Basically anything that produces a value: 2 + 2, fn(), a ? b : c, etc. It's something that will be computed and produces something.

Expressions can be nested in many occasions: 2 + fn() or ( a ? ( 2 + 2 ) : ( fn() ) ) (each expression surrounded by brackets for clarity) for example. Even if an expression doesn't produce a usable value that doesn't change things - a function with no explicit return will produce undefined so 2 + noReturnFn() will produce gibberish but it's still a valid expression syntax.

Note 1 of 2 (more in the next section): variable assignment is an expression, doing a = 1 will produce the value being assigned:

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

Statements

These don't produce a value. Not undefined just nothing. Examples include if(cond){}, return result, switch.

A statement is only valid standalone. You cannot nest them like if (return 7) as that's not syntactically valid. You can further not use statements where an expression is expected - console.log(return 7) is equally invalid.

Just a note, an expression can be used as a statement. These are called expression statements:

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

So, you can use an expression where a statement is valid but you cannot use a statement where an expression is valid.

Note 2 of 2: variable assignment is an expression, however variable declaration with assignment is not. It's just part of the syntax for variable declaration statement. So, the two overlap but aren't related, just how the comma operator and declaring multiple variables are similar (allow you to do multiple things) but not related.

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

Relation with the comma operator

Now we know that the difference and it should become easier to understand. The comma operator has a form of

exp1, exp2, exp3, ..., expN

and accepts expressions, not statements. It executes them one by one and returns the last value. Since statements don't have a return value then they can never be valid in such context: (2 + 2, if(7) {}) is meaningless code from compiler/interpreter perspective as there cannot be anything returned here.

So, with this in mind we cannot really mix the variable declaration and comma operator. let a = 1, a += 1 doesn't work because the comma is treated as variable declaration statement, and if we try to do ( ( let a = 1 ), ( a += 1 ) ) that's still not valid, as the first part is still a statement, not an expression.

Possible workarounds

If you really need to produce a variable inside an expression context and avoid producing implicit globals, then there are few options available to you. Let's use a function for illustration:

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

So, it's a function that produces a value and uses it in few places. We'll try to transform it into shorthand syntax.

IIFE

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

fn(42);

Declare a new function inside your own that takes k as parameter and then immediately invoke that function with the value of computeValueFrom(x). If we separate the function from the invocation for clarity we get:

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

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

fn(42);

So, the function is taking k and using it in sequence few times with the comma operator. We just call the function and supply the value of k.

Cheat using parameters

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

fn(42);

Basically the same as before - we use the comma operator to execute several expressions. However, this time we don't have an extra function, we just add an extra parameter to fn. Parameters are local variables, so they behave similar to let/var in terms of creating a local mutable binding. We then assign to that k identifier without affecting global scope. It's the first of our expressions and then we continue with the rest.

Even if somebody calls fn(42, "foo") the second argument would be overwritten, so in effect it's the same as if fn only took a single parameter.

Cheat using normal body of a function

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

fn(42);

I lied. Or rather, I cheated. This is not in expression context, you have everything the same as before, but it's just removing the newlines. It's important to remember that you can do that and separate different statements with a semicolon. It's still one line and it's barely longer than before.

Function composition and functional programming

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

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

fn(42);

This is a huge topic, so I'm barely going to scratch the surface here. I'm also vastly oversimplifying things only to introduce the concept.

So, what is functional programming (FP)?

It's programming using functions as the basic building blocks. Yes, we do have functions already and we do use them to produce programs. However, non-FP programs essentially "glue" together effects using imperative constructs. So, you'd expect ifs, fors, and calling several functions/methods to produce an effect.

In the FP paradigm, you have functions that you orchestrate together using other functions. Very frequently, that's because you're interested in chains of operations over data.

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

Arrays support methods that come from the functional world, so this is a valid FP example.

What is functional composition

Now, let's say you want to have reusable functions from the above and you extract these two:

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

But you don't really need to do two mapping operations. We could just re-write them into:

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

but let's try doing it without directly modifying the functions. We can just call them one after another and that would work:

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

We've reused the functions now. We'd call getPrice and the result is passed to addShippingTax. This works as long as the next function we call uses the input of the previous one. But it's not really nice - if we want to call three functions f, g, and h together, we need x => h(g(f(x))).

Now finally here is where function composition comes in. There is order in calling these and we can generalise it.

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))));

And there you go, we've "glued" the functions together with another function. It is equivalent to doing:

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

but supports any amount of functions and it doesn't use temporary variables. So, we can generalise a lot of processes where we do effectively the same - pass some input from one function, take the output and feed it into the next function, then repeat.

Where did I cheat here

Hoo, boy, confession time:

  • As I said - functional composition works with functions that take the input of the previous one. So, in order to do what I had in the very beginning of the FP section, then doSomething1 and doSomething2 need to return the value they get. I've included that log to show what needs to happen - take a value, do something with it, return the value. I'm trying to just present the concept, so I went with the shortest code that did it to enough of a degree.
  • compose might be a misnomer. It varies but with a lot of implementations compose works backwards through the arguments. So, if you want to call f -> g -> h you'd actually do compose(h, g, f). There is rationale for that - the real version is h(g(f(x))) after all, so that's what compose emulates. But it doesn't read very well. The left-to-right composition I showed is usually named pipe (like in Ramda) or flow (like in Lodash). I thought it'd be better if compose was used for the functional composition headline but the way you read compose is counter-intuitive at first, so I went with the left-to-right version.
  • There is really, really a lot more to functional programming. There are constructs (similar to how arrays are FP constructs) that will allow you to start with some value and then call multiple functions with said value. But composition is simpler to start with.

The Forbidden Technique eval

Dun, dun, dunn!

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

fn(42);

So...I lied again. You might be thinking "geez, why would I use anybody this guy wrote hereme if it's all lies". If you are thinking that - good, keep thinking it. Do not use this because it's super bad.

At any rate, I thought it's worth mentioning before somebody else jumps in without properly explaining why it's bad.

First of all, what is happening - using eval to dynamically create local binding. And then using said binding. This does not create a global variable:

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

With that in mind, let's see why this should be avoided.

Hey did you notice I used var, instead of let or const? That's just the first of the gotchas you can get yourself into. The reason to use var is that eval always creates a new lexical environment when called using let or const. You can see the specs chapter 18.2.1.1 Runtime Semantics: PerformEval. Since let and const are only available within the enclosing lexical environment, then you can only access them inside eval and not outside.

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

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

So, as a hack, you can only use var so that the declaration is available outside eval.

But that's not all. You have to be very careful with what you pass into eval because you are producing code. I did cheat (...as always) by using a number. Numeric literals and numeric values are the same. But here is what happens if you don't have a numeric:

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);

The problem is that the code produced for numericString is var a = 42; - that's the value of the string. So, it gets converted. Then with nonNumericString you get error since it produces var a = abc and there is no abc variable.

Depending on the content of the string, you'd get all sorts of things - you might get the same value but converted to a number, you might get something different entirely or you might get a SyntaxError or ReferenceError.

If you want to preserve the string variable to still be a string, you need to produce a string 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

This works...but you lose the types you actually put in - var a = "null" is not the same as null.

It's even worse if you get arrays and objects, as you have to serialise them in order to be able to pass them to eval. And JSON.stringify will not cut it, since it does not perfectly serialise objects - for example, it will remove (or change) undefined values, functions, and it flat out fails at preserving prototypes or circular structures.

Furthermore, eval code cannot be optimised by the compiler, so it will be drastically slower than simply creating a binding. If you are unsure that would be the case, then you probably haven't clicked on the link to the spec. Do this now.

Back? OK did you notice how much stuff is involved when running eval? There are 29 steps per the spec and multiple of them reference other abstract operations. Yes, some are conditional and yes, the number of steps doesn't necessarily mean it takes more time but it's definitely going to do a lot more work than you need just to create a binding. Reminder, that cannot be optimised by the engine on the fly, so you it's going to be slower than "real" (non-evaled) source code.

That's before even mentioning security. If you ever had to do security analysis of your code you'd hate eval with passion. Yes, eval can be safe eval("2 + 2") will not produce any side effects or problems. The problem is that you have to be absolutely sure that you are feeding known good code to eval. So, what would be the analysis for eval("2 + " + x)? We cannot say until we trace back all possible paths for x to be set. Then trace back anything that is used to set x. Then trace back those, etc, until you find that the initial value is safe or not. If it comes from untrusted place then you have a problem.

Example: you just take part of the URL and put it in x. Say, you have a example.com?myParam=42 so you take the value of myParam from the query string. An attacker can trivially craft a query string that has myParam set to code that will steal the user's credentials or proprietary information and send them over to himself. Thus, you need to ensure that you are filtering the value of myParam. But you also have to re-do the same analysis every so often - what if you've introduced a new thing where you now take the value for x from a cookie? Well, now that's vulnerable.

Even if every possible value for x is safe, you cannot skip re-running the analysis. And you have to do this regularly then in the best case, just say "OK it's fine". However, you might need to also prove it. You might need a fill day just for x. If you've used eval another four times, there goes a full week.

So, just abide to the old adage "eval is evil". Sure, it doesn't have to be but it should be a last resort tool.

这篇关于JavaScript声明一个变量并在一个语句中使用逗号运算符?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆