YvetteLau / Step-By-Step

不积跬步,无以至千里;
704 stars 66 forks source link

如何让 (a == 1 && a == 2 && a == 3) 的值为true? #9

Open YvetteLau opened 5 years ago

DazhiFe commented 5 years ago

我觉得这道题主要考验的是==运算符的工作机制和类型转换的一些知识。==的工作机制自己是明白,也知道对象在转换为原始类型的时候会调用valueOf()toString(),但是却一直没有想到要重写这两个方法,所以这道题自己并没有解答出来,以下是参考其他小伙伴的回答加上自己的理解整理出来的,感谢其他小伙伴的解答。

首先要解答这道题,要先搞懂==的工作机制。

==运算符的工作机制

对于==来说,如果对比双方类型不一样的话,就会先进行类型转换。

假设我们需要对比xy是否相同,就会进行如下判断流程:

1.首先会判断两者类型是否相同,相同的话就比较大小了

2.类型不相同的话,那么就进行类型转换

3.判断两者类型是否为stringnumber,是的话就将字符串转换为number

1 == '1';
比较过程如下:
1 == 1; // true

4.判断其中一方是否为boolean,是的话就会把boolean转换为number再进行判断

'1' == true;
比较过程如下:
'1' == 1;
1 == 1; // true

5.判断其中一方是否为object,另一方为stringnumber、或symbol,是的话就会把object转换为原始类型再进行判断

'1' == { name: 'dazhi' }
比较过程如下:
'1' == '[object object]'

6.会先判断是否在对比nullundefined,是的话就返回true

7.要比较相等性之前,不能将nullundefined转换成其他任何值

8.如果有其中一方是NaN,则相等操作符返回false,而不相等操作符返回true

重要提示:即使两个操作数都是NaN,相等操作符也返回false了;因为按照规则,NaN不等于NaN

弄懂了==的工作机制,我们再回到题目:

(a == 1 && a == 2 && a == 3) == true

根据题目,我们可以推断a不可能是一个基本数据类型,因为a如果是nullundefined或者boolean,这个等式根本不会成立。所以a肯定是一个复杂数据类型:object,有可能是一个对象{}或者是数组[]

当一个对象object和数值做==比较的时候,会先把object转换为原始类型再进行比较。

所以,我们还需明白object到原始类型转换的一个过程:

let obj = {
  [Symbol.toPrimitive]() {
    return 100;
  },

  valueOf() {
    return 200;
  }
}

let obj2 = {
  valueOf() {
    return 200;
  },

  toString() {
    return 300;
  }
}

console.log(obj == 100); // true
console.log(obj2 == 200); // true

上面代码说明,它们之间的一个的优先调用顺序是:[Symbol.toPrimitive] > valueOf > toString

a是一个{}

var a = {
  [Symbol.toPrimitive]: (function () {
    let i = 1;
    //闭包的特性之一:i 不会被回收
    return function () {
      return i++;
    }
  })()
}

console.log(a == 1 && a == 2 && a == 3); // true

如果没有部署[Symbol.toPrimitive]接口,则会调用valueOf接口,所以下面的代码也是可以的:

var a = {
  valueOf: (function () {
    let i = 1;
    //闭包的特性之一:i 不会被回收
    return function () {
      return i++;
    }
  })()
}

console.log(a == 1 && a == 2 && a == 3); // true

a是一个[]数组时

var a = [1,2,3];
a.join = a.shift;
console.log(a == 1 && a == 2 && a == 3); // true

不得不说,此方法真的是很巧妙。 数组也是一个对象,所以也遵循对象到原始类型的转化过程,然后又利用数组的Array.prototype.toString()内部调用的是Array.prototype.join(),所以把join重写为shift,这样当比较a == 1时,相当于执行了

a.shift();

由于shift()方法会改变原数组,并删除数组的第一个元素,然后把该元素返回回去。所以第一次比较删除的就是1,并把1返回回去,a == 1就为true了,然后原数组a就变成了[2,3],以此类推。

根据这个原理,下面的代码也是等价的:

var a = [3,2,1];
a.join = a.pop;
console.log(a == 1 && a == 2 && a == 3); // true

利用数据劫持

数据劫持还是第一次听到,过后需要好好补下这块相关的知识。

重写a属性的getter方法

使用Object.defineProperty定义的属性,在获取属性时,会调用get方法。利用这个特性,我们在window对象上定义a属性,如下:

let i = 1;
Object.defineProperty(window, 'a', {
  get: function() {
    return i++;
  }
})
console.log(a == 1 && a == 2 && a == 3); // true

Proxy形式实现

这个也是盲点,过后也需要好好学习下。

利用ES6新增的Proxy来实现:

const a = new Proxy({}, {
  v: 1,
  get: function() {
    return () => this.v++;
  }
});
console.log(a == 1 && a == 2 && a == 3); // true

最后

感谢各位小伙伴,学到很多知识点。希望大家能一直坚持下去,向前端专家进阶,加油!

KRISACHAN commented 5 years ago

此题目为网上经典面试题的严谨形态(意思就是判断的条件,a就是a,没有各种奇奇怪怪的符号)

此题目的答案可以分为三大类:

1. 类型转换时的劫持

首先我们要知道,在 JS 中类型转换只有三种情况,分别是:

转换为原始类型

对象在转换类型的时候,会执行原生方法ToPrimitive

其算法如下:

  1. 如果已经是 原始类型,则返回当前值;
  2. 如果需要转 字符串 则先调用toSting方法,如果此时是 原始类型 则直接返回,否则再调用valueOf方法并返回结果;
  3. 如果不是 字符串,则先调用valueOf方法,如果此时是 原始类型 则直接返回,否则再调用toString方法并返回结果;
  4. 如果都没有 原始类型 返回,则抛出 TypeError类型错误。

当然,我们可以通过重写Symbol.toPrimitive来制定转换规则,此方法在转原始类型时调用优先级最高。

所以以此定义我们可以有以下四种答案:

var a = {
    arr: [3, 2, 1],
    valueOf () {
        console.group('valueOf')
        console.log(this.arr)
        console.groupEnd('valueOf')
        return this.arr.pop()
    }
}
if (a == 1 && a == 2 && a == 3) {
    console.log('biu')
}

var b = {
    arr: [3, 2, 1],
    toString () {
        console.group('toString')
        console.log(this.arr)
        console.groupEnd('toString')
        return this.arr.pop()
    }
}
if (b == 1 && b == 2 && b == 3) {
    console.log('biu')
}

var c = {
    arr: [3, 2, 1],
    [Symbol.toPrimitive] () {
        console.group('Symbol.toPrimitive')
        console.log(this.arr)
        console.groupEnd('Symbol.toPrimitive')
        return this.arr.pop()
    }
}
if (c == 1 && c == 2 && c == 3) {
    console.log('biu')
}

var d = [1, 2, 3]
d.join = d.shift
if (d == 1 && d == 2 && d == 3) {
    console.log('biu')
}

鱼头注:事实上,这四种可以算是同一种。关于最后一种,我们可以来看看ECMA中的 Array.prototype.toString ( ) 定义:

  1. 定义 arrayToObject(this value)(原生方法,将当前数组转换成对象);
  2. 定义 funcGet(array, 'join')(原生方法,在这一步调用 join 方法);
  3. 如果 IsCallble(func) (原生方法,判断是否有内部可调用的函数)为 false,则 设置 func 原生函数 %ObjProto_toString%(原生函数,toString 的具体实现);
  4. 返回 Call(func, array)

2. 对 getter 的劫持

所谓的 getter 就是对象属性在进行查询时会被调用的方法 get,利用此函数也可以实现题目功能。

代码如下:

window.val = 0
Object.defineProperty(window, 'd', {
    get () {
        return ++this.val
    }
})
if (d == 1 && d == 2 && d == 3) {
    console.log('biu')
}

const e = new Proxy({}, {
  val: 1,
  get ()  {
    return () => this.val++;
  }
});
if (e == 1 && e == 2 && e == 3) {
    console.log('biu')
}

3. 正则表达式

JS 中的 RegExp.prototype.exec() 作用是在一个指定字符串中执行一个搜索匹配,返回一个结果数组或 null

当正则表达式使用 "g" 标志时,可以多次执行 exec 方法来查找同一个字符串中的成功匹配。当你这样做时,查找将从正则表达式的 lastIndex 属性指定的位置开始。(test() 也会更新 lastIndex 属性)。

lastIndex 是正则表达式的一个可读可写的整型属性,用来指定下一次匹配的起始索引。只有正则表达式使用了表示全局检索的 "g" 标志时,该属性才会起作用。

鱼头注:只有正则表达式使用了表示全局检索的 "g" 标志时,该属性才会起作用。

综上所述,我们可以有方案如下:

var f = {
    reg: /\d/g,
    valueOf () {
        return this.reg.exec(123)[0]
    }
}
if (f == 1 && f == 2 && f == 3) {
    console.log('biu')
}

鱼头注:上述方法其实也利用了类型转换的特点。

ivan0525 commented 5 years ago
let num = 1
    Object.defineProperty(window, 'a', {
      get() {
        return num++
      }
    })
ivan0525 commented 5 years ago
qinmeishuying commented 5 years ago

const a = { value: 0 } a.valueOf = function () { return this.value += 1 } console.log(a==1 && a==2 && a==3)

== 在js 中 会发生类型转换 ===全等择不会发生强转 == 会把左右两边的值 转化为相同的原始数据类型然后在去比较他们是否相当 如果输入的是个基本类型就直接返回这个类型值 如果输入是Object类型 那就先调用输入对象的valueOf()如果是基本类型的话就直接返回 如果不是在调用toSting() 是基本类型 就返回他 js 在解析a==1 时 1 是基本数据类型 所以会把a 转换为Nubmer a.valueof 覆盖的原来的valueof 被调用时 会返回1 自增+1返回自身

=== 全等下不会执行 valueof 此时就可以 用到Object.defineProperty 因为get 和 set是可以通过"."操作符调用的方法 var v = 1; Object.defineProperty(window,'a',{ get(){ return v++; } }) a == 1 && a == 2 && a==3

ZhangWeiC commented 5 years ago

var aᅠ = 1; var a = 2; var ᅠa = 3; console.log(aᅠ==1 && a== 2 &&ᅠa==3)

954545647 commented 5 years ago

let a = { [Symbol.toPrimitive]: (function() { let i = 1; return function() { return i++; } })() }

EcSunshine commented 5 years ago

let a = { valueOf : (fnction(){ let i = 1; return function(){ return i++ } })() }

riluocanyang commented 5 years ago

如何让a === 1 && a === 2 && a === 3的值为true

toString

let obj = {
  a:1,
  toString() {
    this.a ++;
  }
}
if(a === 1 && a === 2 && a === 3) {
  console.log('success')
}

valueOf

let obj = {
  a:1,
  valueOf() {
    this.a ++;
  }
}
if(a === 1 && a === 2 && a === 3) {
  console.log('success')
}

[Symbol.toPrimitive]

let obj = {
  a:1,
  [Symbol.toPrimitive]() {
    this.a ++;
  }
}
if(a === 1 && a === 2 && a === 3) {
  console.log('success')
}

Object.defineProperty

let a= 1
Object.defineProperty(window, 'a', {
   get() {
        return a++
   }
})
if(a === 1 && a === 2 && a === 3) {
  console.log('success')
}

修改数组join方法

var a = [1,2,3];
a.join = a.shift;
if(a === 1 && a === 2 && a === 3) {
  console.log('success')
}
index-swf commented 3 years ago

var aᅠ = 1; var a = 2; var ᅠa = 3; console.log(aᅠ==1 && a== 2 &&ᅠa==3)

这个答案用了不可见字符 \uffa0 作为变量名,过于取巧了