JavaScript声明一个变量并在一个语句中使用逗号运算符? [英] JavaScript declare a variable and use the comma operator in one statement?
问题描述
众所周知,要声明多个变量,使用的格式如下:
让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程序本质上胶粘于胶粘剂。使用命令式构造共同作用。因此,您希望 在FP范例中,您具有使用其他功能一起编排的功能。很多时候,这是因为您对数据操作链感兴趣。 数组支持功能世界中的方法,因此,这 是有效的FP示例。 现在,假设您想从上面获得可重用的函数,并提取了这两个函数: 但是您实际上并不需要执行两次映射操作。我们可以将它们重写为: ,但让我们尝试在不直接修改功能的情况下进行操作。我们可以一个接一个地调用它们,这将起作用: 我们现在已经重用了这些功能。我们将调用 现在终于有了函数组合的地方了。调用它们有一定的顺序,我们可以概括一下。 然后您就可以将粘贴到这些功能以及另一个功能。等效于: 但支持任意数量的函数,并且不使用临时变量。因此,我们可以归纳出许多可以有效执行相同操作的过程-从一个函数传递一些输入,获取输出并将其输入到下一个函数,然后重复。 哦,男孩,坦白的时间: 邓恩,邓恩,邓恩! 所以...我又撒了谎。您可能会想老兄,如果这全都是谎言,我为什么要用这个人在这里写的任何人。如果您认为-好,请继续考虑。 无论如何,我不应该使用它,因为它非常糟糕。 无论如何,我认为值得一提的是解释为什么不好。 首先,发生了什么事-使用 牢记这一点,让我们看看为什么 应该避免。 嘿,您是否注意到我使用了 if
s, s,并调用多个函数/方法来产生效果。 / p>
而不在外面。
itemsToBuy
.filter(item => item。 stockAmount!== 0)//删除售罄的
.map(item => item.price * item.basketAmount)//获取价格
.map(price => price + 12.50)/ /添加运费税
.reduce((a,b)=> a + b,0)//获得总计
什么是功能组合
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;
}
我在这里作弊
doSomething1
和 doSomething2
返回他们获得的价值。我已经包括了 log
来显示需要发生的事情-取一个值,用它做点什么,然后返回该值。我只是想提出这个概念,所以在一定程度上做了最短的代码。
撰写
可能是误称。它有所不同,但是有很多实现 compose
通过参数实现向后。因此,如果您要呼叫 f
-> g
-> h
实际上是您进行 compose(h,g,f)
的。这样做是有道理的-毕竟 real 版本是 h(g(f(x()))
,所以这就是撰写
进行模拟。但是它读起来并不好。我显示的从左到右的组合通常命名为 pipe
(例如 Ramda )或 flow
(如 Lodash )。我认为将 compose
用于功能组合标题会更好,但是阅读 compose 的方式会更好code>起初是违反直觉的,所以我使用了从左到右的版本。
禁止的技术
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;
-这是值字符串的em>。因此,它被转换。然后使用 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 if
s, for
s, 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
anddoSomething2
need to return the value they get. I've included thatlog
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 implementationscompose
works backwards through the arguments. So, if you want to callf
->g
->h
you'd actually docompose(h, g, f)
. There is rationale for that - the real version ish(g(f(x)))
after all, so that's whatcompose
emulates. But it doesn't read very well. The left-to-right composition I showed is usually namedpipe
(like in Ramda) orflow
(like in Lodash). I thought it'd be better ifcompose
was used for the functional composition headline but the way you readcompose
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-eval
ed) 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屋!