Kaier33

Do You Really Know JavaScript?

December 1, 2020 • ☕️ 7 min read

以下测试行为基于ECMA 262(5.1)在”浏览器环境”中。


['1', '2', '3'].map(parseInt)

// => [1, NaN, NaN] 

parseInt原本传的是2个参数(val,radix) , map 函数传参分别是 (ele,index,array)
so, 当跑到数组的第二个项时,把map的第二个参数index丢给parsetInt的radix , parsetInt(‘2’,1) 返回便是 NaN


[typeof null, null instanceof Object]

// =>  ["object", false]

typeOf null 返回”Object”实际上是个bug, 因为null根本就不在Object的原型上 , instanceof 已经给出了答案; null 不是一个对象,它是原始值.
null(JSVAL_NULL)是机器代码的空指针,一个对象类型的引用,值是零。
标志位 000:对象,数据是对象的应用。
这样就很明显的知道为什么typeof null的值是object了:它检查了标志位的类型,标志位表明它是个对象。


[ [3,2,1].reduce(Math.pow), [].reduce(Math.pow) ]

// Uncaught TypeError: Reduce of empty array with no initial value

MDN已有描述 : if the array is empty and no initialValue is provided, TypeError will be thrown.
如果一个空数组没有指定 initialValue ,则会报错.


var val = 'smtg';
console.log('Value is ' + (val === 'smtg') ? 'Something' : 'Nothing');

// => Something

三元运算符具有更高的优先级。
那如果要连着打印呢? 把 三元 那一段用括号包起来就行了


var name = 'World!';
(function () {
    if (typeof name === 'undefined') {
        var name = 'Jack';
        console.log('Goodbye ' + name);
    } else {
        console.log('Hello ' + name);
    }
})();

// => Goodbye Jack

IIFE自执行函数有自己的作用域~


var END = Math.pow(2, 53);
var START = END - 100;
var count = 0;
for (var i = START; i <= END; i++) {
    count++;
}
console.log(count);

boom~~ 数太大.浏览器被玩坏了,实际上什么都不会输出~~~


var ary = [0,1,2]
ary[10] = 10
ary.filter(function(x) { return x === undefined})

// => []

数组的空位empty ,length长度也会算上去. ary的length是11
虽然ary[3]返回的是undefined, 不代表ary[3]里面就是undefined ,而是 empty ,
filter不会对缺失的项进行操作,所以返回[]


var two   = 0.2
var one   = 0.1
var eight = 0.8
var six   = 0.6
console.log([two - one == one, eight - six == two])

// => [true, false]

浮点运算精度丢失的问题


function showCase(value) {
    switch(value) {
    case 'A':
        console.log('Case A');
        break;
    case 'B':
        console.log('Case B');
        break;
    case undefined:
        console.log('undefined');
        break;
    default:
        console.log('Do not know!');
    }
}

showCase(new String('A'));

// => Do not know!

new String(“A”) 返回的是一个字符对象 {‘A’}


function showCase2(value) {
    switch(value) {
    case 'A':
        console.log('Case A');
        break;
    case 'B':
        console.log('Case B');
        break;
    case undefined:
        console.log('undefined');
        break;
    default:
        console.log('Do not know!');
    }
}
showCase2(String('A'));

// => Case A

注意这里没有加new, 返回的是个字符串


function isOdd(num) {
    return num % 2 == 1;
}
function isEven(num) {
    return num % 2 == 0;
}
function isSane(num) {
    return isEven(num) || isOdd(num);
}
var values = [7, 4, '13', -9, Infinity];
values.map(isSane);

// =>  [true, true, true, false, false] 

-9 % 2 返回的还会是负数 -1, Infinity % 2 为 NaN; 所以后面2个是false


parseInt(3, 8)
parseInt(3, 2)
parseInt(3, 0)

// => 3, NaN, 3

除了“0、1”外,其它数字都不是有效二进制数字,所以第二个为 NaN , 那么0呢?
parseInt将考虑一个伪基数,并假设您指的是10,因此返回3。


Array.isArray(Array.prototype)

// => true

数组的原型链是也是个数组~


var a = [0];
if ([0]) {
  console.log(a == true);
} else {
  console.log("wut");
}

// => result: false

[0]的bool是true没错, 但是, [0]和bool 去==的话, 就是发生隐式转换 , 最终[0].toString 去对比,返回便是false

[]==[]

指针地址不同

'5' + 3
'5' - 3

// => '53', 2
  • 在字符串前是当连接符用的, - 会发生隐式转换

1 + - + + + - + 1 
1 - + - + - 1 

// => 2 , 0

个人看法: 简单点理解就是比较 - +哪个多 , +多的就相加, -多的将相减 ~~

var ary = Array(3);
ary[0]=2
ary.map(function(elem) { return '1'; });

// => ["1", empty × 2]

因为map只对数组中已经初始化的元素进行调用~


function sidEffecting(ary) {
  ary[0] = ary[2];
}
function bar(a,b,c) {
  c = 10
  sidEffecting(arguments);
  return a + b + c;
}
bar(1,1,1)

// => 21

传参后被函数改动了数值. 所以是10+1+10


var a = 111111111111111110000,
    b = 1111;
a + b;

// => 111111111111111110000

jvaScript中的数字缺乏精确性会影响小数字和大数字~


var x = [].reverse;
x();

// VM2952:1 Uncaught TypeError: Cannot convert undefined or null to object

我猜这道题本来想考察this指向的问题.. 旧一点的浏览器会返回window, 现在是直接报错


Number.MIN_VALUE > 0

// =>  true

Number.MIN_VALUE提供了一个大于0的最小值


[1 < 2 < 3, 3 < 2 < 1]

// [true, true] 

隐式转换会把 true变成1 , false变成0


2 == [[[2]]]

// => true

值和引用类型去比较,把引用类型转话成值类型.
具体转换过程看这篇:https://stackoverflow.com/questions/7202157/why-does-return-the-string-10/7202287#7202287


3.toString()    //报错.
3..toString()   //3.是 "3"的有效写法
3...toString()  //报错.

运算符优先级的问题,点运算符会被优先识别为数字常量的一部分,然后才是对象属性访问符.
3.toString() 会被 JS 引擎解析成 (3.)toString() 所以报错
3..toString() 会被 JS 引擎解析成 (3.).toString() 会输出 “3”
3…toString() 会被 JS 引擎解析成 (3.)..toString() 还是报错


(function(){
  var x = y = 1;
})();
console.log(y);
console.log(x);

// => : 1, error

作用域问题, Y是全局的, X的作用域在函数内


var a = /123/,
    b = /123/;
a == b
a === b

// =>  false false

reg是个对象 , 2个对象指针不同 ,所以看起来值一样也是没用的,
这里相当于去比较 {a:1}=={a:1}


var a = [1, 2, 3],
    b = [1, 2, 3],
    c = [1, 2, 4]
a ==  b  
a === b
a > c
a < c

// => false false false true

前面两个还好理解, 毕竟引用地址各不相同.
主要是看 比较大小, 引用类型间比较大小是按照字典序比较,就是先比第一项谁大,相同再去比第二项。


var a = {}, b = Object.prototype;
[a.prototype === b, Object.getPrototypeOf(a) === b]

// => [false ,true]

首先, 实例对象没有prototype , 只有proto , 所以第一个是false;
然后, Object.getPrototypeOf() 方法返回指定对象的原型 , a的poroto 指向的是Object.prototype,也就是Object.getPrototypeOf(a)。
注意Object.getPrototypeOf(Object) 不是 Object.prototype


function f() {}
var a = f.prototype, b = Object.getPrototypeOf(f);
a === b

// => false

a是构造函数f的原型 : {constructor: ƒ}
b是实例f的原型对象 : ƒ () { [native code] }


function foo() { }
var oldName = foo.name;
foo.name = "bar";
[oldName, foo.name]

// =>  ['foo','foo']  

函数都有一个name,且修改无效


"1 2 3".replace(/\d/g, parseInt)

// =>  "1 NaN 3" 

如果没有与子表达式匹配的项,第二参数为出现的位置.所以第一个参数是匹配项,第二个参数是位置
parseInt(‘1’, 0) // 0会被默认改为10进制
parseInt(‘2’, 2) // 2进制中不可能有2
parseInt(‘3’, 4) // 4进制中有3的存在


function f() {}
var parent = Object.getPrototypeOf(f);
f.name 
parent.name 
typeof eval(f.name) 
typeof eval(parent.name) 

// =>  'f', '', 'function', 'undefined'

第一个是”f”很好理解吧, 就是f函数的name
parent是f原型对象的名字为 ”
eval(f.name) 为 f,f的数据类型是function
eval(parent.name) 为undefined, ‘undefined’


var lowerCaseOnly =  /^[a-z]+$/
[lowerCaseOnly.test(null), lowerCaseOnly.test()]

// =>  [true, true]

null, undefined(没传参) 会被转义为字符串, 所以就匹配到规则(匹配英文字母)了


[,,,].join(", ")

// =>  ", ,"

数组是 [empty,empty,empty] . 所以转出来便是 ’, , ’


var a = {class: "Animal", name: 'Fido'}
a.class

// =>  大部分情况下输出 'Animal' 

类是一个保留字,但它被Chrome、Firefox和Opera接受为属性名。在IE中,它将失败。另一方面,每个人都会接受大多数其他保留字(int、private、throws等)作为变量名,而class是禁止的。


var a = new Date("epoch")

// =>  Invalid Date

new Date接收的是number或是date , date在内部以数字保存 , NAN类型进来的话便是 Invalid Date 了


var a = Function.length
var b = new Function().length

a === b

// =>  false

像Array,Object, Function他们的长度都被定义为1 , 另一方面 ,函数原型对象的长度属性被定义为0


var a = Date(0)
var b = new Date(0)
var c = new Date()
[a === b, b === c, a === c]

// =>  [false,false,false]

a 为当前时间的字符串, b是时间为1970年的Date对象, c 是也是一个 Date对象;
值 和 引用对象, 以及引用对象之间的比较 , 答案显而易见, 全是 false


var min = Math.min(), max = Math.max()

min < max

// =>  false

Math.min()返回 Infinity, Math.max() 返回 -Infinity ,Infinity应该大于-Infinity, 所以是false


function captureOne(re, str) {
  var match = re.exec(str)
  return match && match[1]
}

var numRe  = /num=(\d+)/ig,
    wordRe = /word=(\w+)/i,
    a1 = captureOne(numRe,  "num=1"),
    a2 = captureOne(wordRe, "word=1"),
    a3 = captureOne(numRe,  "NUM=2"),
    a4 = captureOne(wordRe,  "WORD=2")

[a1 === a2, a3 === a4]

// =>  [true, false]

当正则表达式使用 “g” 标志时,可以多次执行 exec (test) 方法来查找同一个字符串中的成功匹配。
如果匹配不到, lastIndex会被重置为0; 匹配到的话, 则会更新lastIndex的位置。
上面的a1和a3用的是同一个正则 /num=(\d+)/ig, a1匹配到后, 把lastIndex由0更新到5,
这会导致 a3的索引是从 5 开始去匹配的, 导致匹配不到, 返回null。
numRe的lastIndex此时重置为0, 要想a3 === a4, 这里重新计算一下a3就可以了


var a = new Date("2014-03-19")
var b = new Date(2014, 03, 19)
[a.getDay() === b.getDay(), a.getMonth() === b.getMonth()]

// =>  [false, false]

a 返回的正确的时间; b 的月份要-1才对 , date的月份从0开始算


if ('http://giftwrapped.com/picture.jpg'.match('.gif')) {
  'a gif file'
} else {
  'not a gif file'
}

// =>  "a gif file" 

match默认会将string转为reg, 所以这里的点(.), 表示匹配的是任意字符


function foo(a) {
    var a;
    return a;
}
function bar(a) {
    var a = 'bye';
    return a;
}
[foo('hello'), bar('hello')]


// =>  ["hello", "bye"]

foo函数中的var a 是函数默认就会对参数进行的变量提升, 并不是说这里var a 就表示把a 定义为undefined



Referencing :

JavaScript Puzzlers!