JavaScript 里的奇葩知識
授權轉載自:原罪?
https://segmentfault.com/a/1190000023941089
久經沙場的前輩們,寫了無數代碼,踩了無數的坑。但有些坑,可能一輩子也踩不到摸不著,因為根本不會發(fā)生在業(yè)務代碼里~~
1
Function.prototype 竟然是個函數類型。而自定義函數的原型卻是對象類型。
typeof?Function.prototype?===?'function';??//?true
function?People()?{}
typeof?People.prototype?===?'object';??????//?true
所以我們設置空函數可以這么做:
//?Good?
const?noop?=?Function.prototype;
//?Bad
const?noop?=?()?=>?{};
2
一個變量真的會不等于自身嗎?
const?x?=?NaN;
x?!==?x??//?true
這是目前為止 js 語言中唯一的一個不等于自己的數據。為什么?因為 NaN 代表的是一個范圍,而不是一個具體的數值。
在早期的 isNaN() 函數中,即使傳入字符串,也會返回 true ,這個問題已經在 es6 中修復。
isNaN('abc');???????//?true
Number.isNaN('abc')?//?false
所以如果您想兼容舊瀏覽器,用 x !== x 來判斷是不是NaN,是一個不錯的方案。
3
構造函數如果 return了新的數據
//?不返回
function?People()?{}
const?people?=?new?People();???//?People?{}
//?返回數字
function?People()?{
??return?1;
}
const?people?=?new?People();???//?People?{}
//?返回新對象
function?Animal()?{
??return?{
????hello:?'world',
??};
}
const?animal?=?new?Animal();??//?{?hello:?'world'?}
在實例化構造函數時,返回非對象類型將不生效
4
.call.call 到底在為誰瘋狂打call?
function?fn1()?{
??console.log(1);
}
function?fn2()?{
??console.log(2);
}
fn1.call.call(fn2);?//?2
所以 fn1.call.call(fn2) 等效于 fn2.call(undefined)。而且無論您加多少個 .call,效果也是一樣的。
5
實例后的對象也能再次實例嗎?
function?People()?{}
const?lili?=?new?People();????????????//?People?{}
const?lucy?=?new?tom.constructor();???//?People?{}
因為 lili 的原型鏈指向了 People 的原型,所以通過向上尋找特性,最終在 Peopel.prototype 上找到了構造器即 People 自身
6
setTimeout 嵌套會發(fā)生什么奇怪的事情?
console.log(0,?Date.now());
setTimeout(()?=>?{
??console.log(1,?Date.now());
??setTimeout(()?=>?{
????console.log(2,?Date.now());
????setTimeout(()?=>?{
??????console.log(3,?Date.now());
??????setTimeout(()?=>?{
????????console.log(4,?Date.now());
????????setTimeout(()?=>?{
??????????console.log(5,?Date.now());
??????????setTimeout(()?=>?{
????????????console.log(6,?Date.now());
??????????});
????????});
??????});
????});
??});
});
在0-4層,setTimeout 的間隔是 1ms ,而到第 5 層時,間隔至少是 4ms 。
7
es6函數帶默認參數時將生成聲明作用域
var?x?=?10;
function?fn(x?=?2,?y?=?function?()?{?return?x?+?1?})?{
??var?x?=?5;
??return?y();
}
fn();???//?3
8
函數表達式(非函數聲明)中的函數名不可覆蓋
const?c?=?function?CC()?{
??CC?=?123;
??return?CC;
};
c();?//?Function
當然,如果設置 var CC = 123 ,加聲明關鍵詞是可以覆蓋的。
9
嚴格模式下,函數的 this 是 undefined 而不是 Window
//?非嚴格
function?fn1()?{
??return?this;
}
fn1();?//?Window
//?嚴格
function?fn2()?{
??'use?strict';
??return?this;
}
fn2();?//?undefined
對于模塊化的經過webpack打包的代碼,基本都是嚴格模式的代碼。
10
取整操作也可以用按位操作
var?x?=?1.23?|?0;??//?1
因為按位操作只支持32位的整型,所以小數點部分全部都被拋棄
11
indexOf() 不需要再比較數字
const?arr?=?[1,?2,?3];
//?存在,等效于?>?-1
if?(~arr.indexOf(1))?{
}
//?不存在,等效于?===?-1
!~arr.indexOf(1);
按位操作效率高點,代碼也簡潔一些。也可以使用es6的 includes() 。但寫開源庫需要考慮兼容性的道友還是用 indexOf 比較好
12
getter/setter 也可以動態(tài)設置嗎?
class?Hello?{
??_name?=?'lucy';
?
??getName()?{
????return?this._name;
??}
??
??//?靜態(tài)的getter
??get?id()?{
????return?1;
??}
}
const?hel?=?new?Hello();
hel.name;???????//?undefined
hel.getName();??//?lucy
//?動態(tài)的getter
Hello.prototype.__defineGetter__('name',?function()?{
??return?this._name;
});
Hello.prototype.__defineSetter__('name',?function(value)?{
??this._name?=?value;
});
hel.name;???????//?lucy
hel.getName();??//?lucy
hel.name?=?'jimi';
hel.name;???????//?jimi
hel.getName();??//?jimi
13
0.3?-?0.2?!==?0.1??//?true
浮點操作不精確,老生常談了,不過可以接受誤差
0.3?-?0.2?-?0.1?<=?Number.EPSILON?//?true
14
class 語法糖到底是怎么繼承的?
function?Super()?{
??this.a?=?1;
}
function?Child()?{
??//?屬性繼承
??Super.call(this);
??this.b?=?2;
}
//?原型繼承
Child.prototype?=?new?Super();
const?child?=?new?Child();
child.a;??//?1
正式代碼的原型繼承,不會直接實例父類,而是實例一個空函數,避免重復聲明動態(tài)屬性
const?extends?=?(Child,?Super)?=>?{
??const?fn?=?function?()?{};
??
??fn.prototype?=?Super.prototype;
??Child.prototype?=?new?fn();
??Child.prototype.constructor?=?Child;
};
15
es6居然可以重復解構對象
const?obj?=?{
??a:?{
????b:?1
??},
??c:?2
};
const?{?a:?{?b?},?a?}?=?obj;
一行代碼同時獲取 a 和 a.b 。
在a和b都要多次用到的情況下,普通人的邏輯就是先解構出 a ,再在下一行解構出 b 。
16
判斷代碼是否壓縮居然也這么秀
function?CustomFn()?{}
const?isCrashed?=?typeof?CustomFn.name?===?'string'?&&?CustomFn.name?===?'CustomFn';
17
對象 === 比較的是內存地址,而 >= 將比較轉換后的值
{}?===?{}?//?false
//?隱式轉換?toString()
{}?>=?{}??//?true
18
intanceof 的判斷方式是原型是否在當前對象的原型鏈上面
function?People()?{}
function?Man()?{}
Man.prototype?=?new?People();
Man.prototype.constructor?=?Man;
const?man?=?new?Man();
man?instanceof?People;????//?true
//?替換People的原型
People.prototype?=?{};
man?instanceof?People;????//?false
如果您用es6的class的話,prototype原型是不允許被重新定義的,所以不會出現上述情況
19
Object.prototype.__proto__?===?null;?//?true
這是原型鏈向上查找的最頂層,一個 null
20
parseInt 太小的數字會產生 bug
parseInt(0.00000000454);??//?4
parseInt(10.23);??????????//?10
21
1?+?null??????????//?1
1?+?undefined?????//?NaN
Number(null)??????//?0
Number(undefined)?//?NaN
22
arguments 和形參是別名關系
function?test(a,?b)?{
??console.log(a,?b);?//?2,?3
??
??arguments[0]?=?100;
??arguments[1]?=?200;
??
??console.log(a,?b);?//?100,?200
}
test(2,?3);
但是您可以用 use strict 嚴格模式來避免這一行為,這樣 arguments 就只是個副本了。
23
void 是個固執(zhí)的老頭
void?0?===?undefined??????????//?true
void?1?===?undefined??????????//?true
void?{}?===?undefined?????????//?true
void?'hello'?===?undefined????//?true
void?void?0?===?undefined?????//?true
跟誰都不沾親~~
24
try/catch/finally 也有特定的執(zhí)行順序
function?fn1()?{
??console.log('fn1');
??return?1;
}
function?fn2()?{
??console.log('fn2');
??return?2;
}
function?getData()?{
??try?{
????throw?new?Error('');
??}?catch?(e)?{
????return?fn1();
??}?finally?{
????return?fn2();
??}
}
console.log(getData());
//?打印順序:?'fn1',?'fn2',?2
在 try/catch 代碼塊中,如果碰到 return xxyyzz; 關鍵詞,那么 xxyyzz 會先執(zhí)行并把值放在臨時變量里,接著去執(zhí)行 finally 代碼塊的內容后再返回該臨時變量。
如果 finally 中也有 return aabbcc ,那么會立即返回新的數據 aabbcc 。
25
是否存在這樣的變量 x ,使得它等于多個數字?
const?x?=?{
??value:?0,
??toString()?{
????return?++this.value;
??}
}
x?==?1?&&?x?==?2?&&?x?==?3;????//?true
通過隱式轉換,這樣不是什么難的事情。
26
clearTimeout 和 clearInterval 可以互換~~~~使用嗎
var?timeout?=?setTimeout(()?=>?console.log(1),?1000);
var?interval?=?setInterval(()?=>?console.log(2),?800);
clearInterval(timeout);
clearTimeout(interval);
答案是:YES 。大部分瀏覽器都支持互相清理定時器,但是建議使用對應的清理函數。
27
下面的打印順序是?
setTimeout(()?=>?{
??console.log(1);
},?0);
new?Promise((resolve)?=>?{
??console.log(2);
??resolve();
}).then(()?=>?console.log(3));
function?callMe()?{
??console.log(4);
}
(async?()?=>?{
??await?callMe();
??console.log(5);
})();
答案是:2, 4, 3, 5, 1
主線任務:2,4?
28
null 是 object 類型,但又不是繼承于 Object ,它更像一個歷史遺留的 bug 。鑒于太多人在用這個特性,修復它反而會導致成千上萬的程序出錯。
typeof?null?===?'object';??????????????//?true
Object.prototype.toString.call(null);??//?[object?Null]
null?instanceof?Object;????????????????//?false
腦袋空了,想到再加。。。
