美文网首页JavaScript
[JS] 几个好玩的问题

[JS] 几个好玩的问题

作者: 何幻 | 来源:发表于2018-11-17 12:07 被阅读12次

背景

以下几个好玩的问题,我都不是原创,
解题方法除2.2和4.2之外,都不是我自己想出来,特此声明。

借此向各位JS前辈致敬。

1. 怎样让a同时满足几个不同的条件

if (a === 1 && a === 2 && a === 3) {
    console.log('hello world');
}
// 方法:

let i = 0;
Object.defineProperty(window, 'a', {
    get () {
        return [1, 2, 3][i++];
    }
});

2. 怎样让乘法报错

// 问:如何得到key的值

(function() {
  const key = Math.random();

  function doMath(x) {
    return x * x;
  }

  apiX = function(x) {
    try {
      return doMath(x);
    } catch (err) {
      return key;
    }
  }
})();

let key;
// write code here
// ...

2.1 Symbol

// 方法一:

key = apiX(Symbol());

原理解析:
我们查看ECMAScript规范,12.7 Multiplicative Operators
12.7.3 Runtime Semantics: Evaluation

其中 ToNumber的会针对不同的参数,分情况处理,

我们看到如果ToNumber的参数是一个Symbol就会直接抛异常,
所以,我们第一方法中,给apiX传入了一个Symbol()doMath就抛异常了。

// 方法一:

key = apiX(Symbol());

2.2 valueOf,toString,Symbol.toPrimitive

// 方法二:

key = apiX({
  [Symbol.toPrimitive]: { }
});

key = apiX({
  [Symbol.toPrimitive]() { 
    throw 0; 
  }
});

// 或者
key = apiX({
  valueOf: { }
});

key = apiX({
  valueOf () { 
    throw 0; 
  }
});

// 或者
key = apiX({
  toString: { }
});

key = apiX({
  toString () { 
    throw 0; 
  }
});

除了传入Symbol之外,我们还可以传入一个Object,
因为ToNumber对于Object会调用ToPrimitive,它也可能抛异常。

我们看到,在ToPrimitive中会先查看对象有没有@@toPrimitive方法,
如果有这个方法,且这个方法返回了Object,就报错。

其中,@@toPrimitive在JS中,就是一个以符号Symbol.toPrimitive为名字的方法,

key = apiX({
  [Symbol.toPrimitive]: { }
});

key = apiX({
  [Symbol.toPrimitive]() { 
    throw 0; 
  }
});

我们传入了一个Object,它的属性名是符号Symbol.toPrimitive,值是一个Object。
当然在这个方法中直接抛异常也是可以的。

再看ToPrimitive的逻辑,如果没有@@toPrimitive方法,
就会设置hintnumber,调用OrdinaryToPrimitive

即,如果hintnumber,就按valueOftoString的顺序调用对象的方法。
以下方法都是可以的。

key = apiX({
  valueOf: { }
});

key = apiX({
  valueOf () { 
    throw 0; 
  }
});

key = apiX({
  toString: { }
});

key = apiX({
  toString () { 
    throw 0; 
  }
});

3. 怎样让任意函数报错

(function() {
  const key = Math.random();

  function internal(x) {
    return x;
  }

  apiX = function(x) {
    try {
      return internal(x);
    } catch (err) {
      return key;
    }
  }
})();

let key;
// write code here
// …
// 方法:

function F() {
  var ret = apiX(2);
  if (ret < 1) {
    key = ret;  // key 的范围是 0~1
  }
  return F();   // 无限递归
}

try {
  F();
} catch (err) { }

console.log(key);

由于Chrome中函数的调用栈空间是有限的,
所以我们可以利用栈溢出,让任何一个函数调用报错。
总有一个分界点,让apiX被调用的时候栈未满,而apiX调用internal的时候栈溢出了。

注: 该方法只在Chrome中测试有效,FireFox中测试无效。

4. 如何判断一个对象是否Proxy

const math = new Proxy(Math, {
  get(obj, prop) {
    return obj[prop];
  }
});

约束条件:不能用===Object.is进行判断。

4.1 利用栈溢出

由于Proxy调用比直接调用多一层get方法调用,
因此,可以在栈溢出的临界点上调用对象的get方法进行判断。

// 方法一:

const math = new Proxy(Math, {
    get(obj, prop) {
        return obj[prop];
    }
});

// 先获取栈溢出时的最大深度
let max = 0;
function getStack() {
    max++;
    return getStack();
}

try {
    getStack();
} catch (err) { }

// 在栈溢出的临界点上,检查get方法
let cur = 0, obj;
function check() {
    if (cur > max - 10) {
        obj.a;
    }
    cur++;
    return check();
}

cur = 0;
obj = math;
try {
    check();
} catch (err) { }
console.log('math: ', cur !== max);

cur = 0;
obj = Math;
try {
    check();
} catch (err) { }
console.log('Math: ', cur !== max);

注:
栈帧中的局部变量会影响帧的大小,因此也会影响栈溢出时栈的深度,
所以,getStackcheck中应该保持一致,本例中都没有包含局部变量。

4.2 利用异常堆栈

// 方法二:

// 给math增加一个会抛异常的get属性
Object.defineProperty(math, 'a', {
  get() {
    throw new Error(1);
  }
});

// 获取异常堆栈,Proxy的异常堆栈会多一层
/*
  getErrorStack(Math)
  "Error: 1
    at Math.get (<anonymous>:10:11)
    at getErrorStack (<anonymous>:17:7)
    at <anonymous>:1:1"

  getErrorStack(math)
  "Error: 1
    at Math.get (<anonymous>:10:11)
    at Object.get (<anonymous>:3:15)
    at getErrorStack (<anonymous>:17:7)
    at <anonymous>:1:1"
*/
const getErrorStack = x => {
  try {
    x.a;
  } catch (err) {
    return err.stack;
  }
};

const isProxy = x => {
  const errorStack = getErrorStack(x);
  return /Object\.get/.test(errorStack);
};

console.log('math: ', isProxy(math));  // true
console.log('Math: ', isProxy(Math));  // false

注:
该思路在FireFox或者Node.js也可用,只是异常堆栈的判断逻辑需要调整一下。

相关文章

  • [JS] 几个好玩的问题

    背景 以下几个好玩的问题,我都不是原创,解题方法除2.2和4.2之外,都不是我自己想出来,特此声明。 借此向各位J...

  • 关于几个js问题求解???

    有偿解答 ··· mui.ajax({ type:"get", url:"../visitrecord/get...

  • 几个好玩的事

    好友发了漫画给我,说,改成be的就是你了 我:是我,就是我。哈哈哈。 就是我之前磕他cp,还是爱而不得,独自伤怀的...

  • 好玩的JS数组

    好玩的js数组 数组去重 数组是否相等 两个数组的交集 寻找两个数组的不同 在两个数组中的非共公元素 判断两个数组...

  • 一文读懂 css js 阻塞问题

    前言 先抛出几个问题: css 加载会不会阻塞 js 的加载?(不会) css 加载会不会阻塞 js 的执行?(会...

  • 微信小程序 => 页面之间信息参数的传递(options)

    wxml页面 js页面 写代码只是为了好玩

  • 几个好玩的地图种子

    1.海岛(有海洋遗迹哦) 种子:403693095 2.村庄(铁匠辅有好东西) 种子:253686718 3.雪地...

  • 分享几个好玩的网站

    哈哈 今天,给大家分享几个“好玩“的网站 请容许我思考下 好了,分享开始 1:穿帮网 http://www.bug...

  • JS 中关于类型的几个问题

    参考: winter 的重学前端 为什么有的编程规范要求用void 0代替undefined? 因为JavaScr...

  • JS执行机制

    首先我们来看几个问题: 1.JS是单线程的么?2.JS有异步么?3.JS单线程怎么实现的的异步 1.JS是单线程的...

网友评论

    本文标题:[JS] 几个好玩的问题

    本文链接:https://www.haomeiwen.com/subject/ycybfqtx.html