JavaScript:等式混乱,或者说 x === 1 && x === 2
挑战
撰写
结论
参考
由 Mux 主办的 DEV 全球展示挑战赛:展示你的项目!
JavaScript 甚至能让最优秀的人都对当前发生的事情产生怀疑。
在这篇文章中,我将向您展示使以下语句返回 true 的不同方法:
x === 1 && x === 2
首先,我给大家出一个挑战,欢迎各位先自己尝试解答。
如果您只想知道答案,请直接跳到解答部分!
挑战
此挑战共有三个难度级别,并提供多种解决方案!
你的目标是赋予 X 所需的Flag!打印值。
请将以下代码片段放置在某个位置以便打印。Flag!
一级
// Your code here
if(x == 1 && x == 2 && x == 3) {
console.log('Flag!');
} else {
console.log('Wrong flag!');
}
// Your code here
二级
让我们使用严格相等运算符来增加一些难度吧!
// Your code here
if(x === 1 && x === 2 && x === 3) {
console.log('Flag!');
} else {
console.log('Wrong flag!');
}
// Your code here
3级
最后,让我们在当前作用域内打印标志!
这意味着这条语句不应该放在类或函数中,而应该单独放在脚本中。
// Your code here
if(x === 1 && x === 2 && x === 3) {
console.log('Flag!');
} else {
console.log('Wrong flag!');
}
撰写
你成功打印了吗Flag!?
第一部分
以下是上一个挑战的第一部分:
if(x == 1 && x == 2 && x == 3) {
解决这部分挑战的关键在于了解 JavaScript 如何比较两个对象。
使用相等运算符 ==而非严格相等运算符 ===意味着引擎会先尝试将两个对象都转换为基本类型,然后再进行比较。您可以在 MDN 的比较运算符
页面 上找到更多关于比较的信息。
这意味着,如果我们要将对象与字符串进行比较,myObject.toString()则会使用 `s` 的结果进行比较,而不是比较失败。
例如:
const myObject = {
toString() {
return 'My Object!';
}
}
console.log(myObject == 'My Object!');
返回true
在我们的场景中,当我们将 x 与原始类型进行比较时Number,后台将执行以下步骤:
如果 Type(x) 为 Object 且 Type(y) 为 String 或 Number,
则返回比较结果 ToPrimitive(x) == y。
此行为已在 EcmaScript:抽象相等性比较算法中进行了记录。
将对象转换为原始类型可以通过调用对象的 `toPrimitive`toString或 ` valueOftoPrimitive` 方法来实现,具体文档请参见:`Object [[DefaultValue]]`。
在 ES6 中,我们还可以直接重写`Symbol.toPrimitive` 方法来返回自定义值。
因此,我们可以创建一个 `toPrimitive`toString或 `toPrimitive`valueOf函数返回递增数字的对象!
解决方案
let i = 1,
x = {
valueOf() { // Default conversion for numbers
return i++;
},
toString() { // Default conversion for strings
return i++;
},
[Symbol.toPrimitive]() { // toPrimitive override
return i++;
}
}
if(x == 1 && x == 2 && x == 3) {
console.log('Flag!');
} else {
console.log('Wrong flag!');
}
请注意,Symbol.toPrimitive 是第一个尝试调用的函数,然后valueOf是最后一个toString,如果顺序在您未来的挑战中很重要的话。
第二部分
这个挑战的第一部分可以用对象和非严格比较来解决,但这在这里行不通。
因为我们使用的是严格相等运算符,所以x需要先进行比较1,然后进行比较2,最后进行比较3。
解决这个问题需要两个技巧:
Getter 方法和一个晦涩的with语句。
该解决方案的第一部分需要创建一个对象,myObject该对象具有一个x设置为 getter 函数的属性:
let i = 1,
myObject = {
get x() {
return i++;
}
}
现在我们可以访问它myObject.x,它将返回一个递增的值!
myObject但这仍然不足以解决问题,因为 if 语句的比较中缺少我们的前缀。
幸运的是(或者说不幸的是),JavaScript 中有一个晦涩的语句,允许我们将作用域设置为对象的属性:with
难道你不喜欢 MDN 上关于该运算符的页面以这么大的警告开头吗?
MDN文档对 with 的描述如下:
`with` 语句会在其语句体执行期间,将给定的对象添加到作用域链的头部。如果语句体中使用的非限定名称与作用域链中的某个属性匹配,则该名称将绑定到该属性以及包含该属性的对象。
因此,该解决方案的第二部分是将比较操作包装在一个with语句中,这样就可以x像访问原生属性一样访问它。
解决方案
let i = 1,
myObject = {
get x() {
return i++;
}
}
with(myObject) {
if(x === 1 && x === 2 && x === 3) {
console.log('Flag!');
} else {
console.log('Wrong flag!');
}
}
第三部分
之前的解决方案只有在你能控制 if 语句的上下文时才有效,而这在查找 XSS 漏洞时很少见。
因此,我们可以调整解决方案,要求在 if 语句之前设置一个单独的入口点来打印输出结果Flag!。
注意:如果你的入口点只位于比较语句下方,你可能需要查看我之前的文章:小心不必要的提升!
由于我们仍然使用严格的相等性检查,因此仍然需要使用 getter 来生成 X。
此解决方案的不同之处在于,它将访问器直接添加到当前作用域(即this对象)上。
在浏览器中,this它将指向window由 DOM 模型定义的对象。
在 NodeJS 中,this它将指向global对象本身。
要修改当前对象的属性,我们将使用Object.defineProperty。
解决方案
let a = 1;
Object.defineProperty(
window, // Object to assign the new property to: this, window, or global depending on the context
'x', // Name of the property to assign
{ // Properties of the object, the getter function in our case
get: function() {
return a++;
}
}
);
if(x === 1 && x === 2 && x === 3) {
console.log('Flag!');
} else {
console.log('Wrong flag!');
}
结论
由于 JavaScript 的动态特性,即使是理智的开发者也能理解它x === 1 && x === 2 && x !== x的工作原理!
希望实际代码中不会有人依赖这些特性,但我很想看到这些特性在现实世界中的应用案例。
另外,这让我想到 JavaScript 中某些比较可能只返回 false 的情况。
如您所知,`if`NaN运算符在比较中总是返回 false,包括与自身比较。
我所知的唯一可能返回 true 的情况是 `if` 运算符本身Symbol(),因为每个符号都是唯一的。
现在,我们可以为 `if` 运算符创建一个动态值,使其在x比较结果x !== x为 true 时仍然有效。
你还知道其他有趣的JS特性,觉得值得写篇文章介绍吗?
请留言或在Twitter上联系我!
参考
MDN:比较运算符
EcmaScript:抽象相等性比较算法
EcmaScript:对象 [[DefaultValue]]
MDN:Getters
MDN:使用
MDN:Object.defineProperty
