JavaScript 里的奇葩知識,你遇到過嗎?
久經(jīng)沙場的前輩們,寫了無數(shù)代碼,踩了無數(shù)的坑。但有些坑,可能一輩子也踩不到摸不著,因?yàn)楦静粫l(fā)生在業(yè)務(wù)代碼里。
1
Function.prototype 竟然是個函數(shù)類型。而自定義函數(shù)的原型卻是對象類型。
- typeof Function.prototype === 'function'; // true
- function People() {}
- typeof People.prototype === 'object'; // true
所以我們設(shè)置空函數(shù)可以這么做:
- // Good
- const noop = Function.prototype;
- // Bad
- const noop = () => {};
2
一個變量真的會不等于自身嗎?
- const x = NaN;
- x !== x // true
這是目前為止 js 語言中唯一的一個不等于自己的數(shù)據(jù)。為什么?因?yàn)?NaN 代表的是一個范圍,而不是一個具體的數(shù)值。在早期的 isNaN() 函數(shù)中,即使傳入字符串,也會返回 true ,這個問題已經(jīng)在 es6 中修復(fù)。
- isNaN('abc'); // true
- Number.isNaN('abc') // false
所以如果您想兼容舊瀏覽器,用 x !== x 來判斷是不是NaN,是一個不錯的方案。
3
構(gòu)造函數(shù)如果 return了新的數(shù)據(jù)
- // 不返回
- function People() {}
- const people = new People(); // People {}
- // 返回數(shù)字
- function People() {
- return 1;
- }
- const people = new People(); // People {}
- // 返回新對象
- function Animal() {
- return {
- hello: 'world',
- };
- }
- const animal = new Animal(); // { hello: 'world' }
在實(shí)例化構(gòu)造函數(shù)時,返回非對象類型將不生效
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
實(shí)例后的對象也能再次實(shí)例嗎?
- function People() {}
- const lili = new People(); // People {}
- const lucy = new tom.constructor(); // People {}
因?yàn)?lili 的原型鏈指向了 People 的原型,所以通過向上尋找特性,最終在 Peopel.prototype 上找到了構(gòu)造器即 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函數(shù)帶默認(rèn)參數(shù)時將生成聲明作用域
- var x = 10;
- function fn(x = 2, y = function () { return x + 1 }) {
- var x = 5;
- return y();
- }
- fn(); // 3
8
函數(shù)表達(dá)式(非函數(shù)聲明)中的函數(shù)名不可覆蓋
- const c = function CC() {
- CC = 123;
- return CC;
- };
- c(); // Function
當(dāng)然,如果設(shè)置 var CC = 123 ,加聲明關(guān)鍵詞是可以覆蓋的。
9
嚴(yán)格模式下,函數(shù)的 this 是 undefined 而不是 Window
- // 非嚴(yán)格
- function fn1() {
- return this;
- }
- fn1(); // Window
- // 嚴(yán)格
- function fn2() {
- 'use strict';
- return this;
- }
- fn2(); // undefined
對于模塊化的經(jīng)過webpack打包的代碼,基本都是嚴(yán)格模式的代碼。
10
取整操作也可以用按位操作
- var x = 1.23 | 0; // 1
因?yàn)榘次徊僮髦恢С?2位的整型,所以小數(shù)點(diǎn)部分全部都被拋棄
11
indexOf() 不需要再比較數(shù)字
- const arr = [1, 2, 3];
- // 存在,等效于 > -1
- if (~arr.indexOf(1)) {
- }
- // 不存在,等效于 === -1
- !~arr.indexOf(1);
按位操作效率高點(diǎn),代碼也簡潔一些。也可以使用es6的 includes() 。但寫開源庫需要考慮兼容性的道友還是用 indexOf 比較好
12
getter/setter 也可以動態(tài)設(shè)置嗎?
- 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
浮點(diǎn)操作不精確,老生常談了,不過可以接受誤差
- 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
正式代碼的原型繼承,不會直接實(shí)例父類,而是實(shí)例一個空函數(shù),避免重復(fù)聲明動態(tài)屬性
- const extends = (Child, Super) => {
- const fn = function () {};
- fn.prototype = Super.prototype;
- Child.prototype = new fn();
- ChildChild.prototype.constructor = Child;
- };
15
es6居然可以重復(fù)解構(gòu)對象
- const obj = {
- a: {
- b: 1
- },
- c: 2
- };
- const { a: { b }, a } = obj;
一行代碼同時獲取 a 和 a.b 。在a和b都要多次用到的情況下,普通人的邏輯就是先解構(gòu)出 a ,再在下一行解構(gòu)出 b 。
16
判斷代碼是否壓縮居然也這么秀
- function CustomFn() {}
- const isCrashed = typeof CustomFn.name === 'string' && CustomFn.name === 'CustomFn';
17
對象 === 比較的是內(nèi)存地址,而 >= 將比較轉(zhuǎn)換后的值
- {} === {} // false
- // 隱式轉(zhuǎn)換 toString()
- {} >= {} // true
18
intanceof 的判斷方式是原型是否在當(dāng)前對象的原型鏈上面
- function People() {}
- function Man() {}
- Man.prototype = new People();
- ManMan.prototype.constructor = Man;
- const man = new Man();
- man instanceof People; // true
- // 替換People的原型
- People.prototype = {};
- man instanceof People; // false
如果您用es6的class的話,prototype原型是不允許被重新定義的,所以不會出現(xiàn)上述情況
19
- Object.prototype.__proto__ === null; // true
這是原型鏈向上查找的最頂層,一個 null
20
parseInt 太小的數(shù)字會產(chǎn)生 bug
- parseInt(0.00000000454); // 4
- parseInt(10.23); // 10
21
- 1 + null // 1
- 1 + undefined // NaN
- Number(null) // 0
- Number(undefined) // NaN
22
arguments 和形參是別名關(guān)系
- 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 嚴(yán)格模式來避免這一行為,這樣 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; 關(guān)鍵詞,那么 xxyyzz 會先執(zhí)行并把值放在臨時變量里,接著去執(zhí)行 finally 代碼塊的內(nèi)容后再返回該臨時變量。如果 finally 中也有 return aabbcc ,那么會立即返回新的數(shù)據(jù) aabbcc 。
25
是否存在這樣的變量 x ,使得它等于多個數(shù)字?
- const x = {
- value: 0,
- toString() {
- return ++this.value;
- }
- }
- x == 1 && x == 2 && x == 3; // true
通過隱式轉(zhuǎn)換,這樣不是什么難的事情。
26
clearTimeout 和 clearInterval 可以互換~~~~使用嗎
- var timeout = setTimeout(() => console.log(1), 1000);
- var interval = setInterval(() => console.log(2), 800);
- clearInterval(timeout);
- clearTimeout(interval);
答案是:YES 。大部分瀏覽器都支持互相清理定時器,但是建議使用對應(yīng)的清理函數(shù)。
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
主線任務(wù):2,4
微任務(wù):3,5宏任務(wù):1
28
null 是 object 類型,但又不是繼承于 Object ,它更像一個歷史遺留的 bug 。鑒于太多人在用這個特性,修復(fù)它反而會導(dǎo)致成千上萬的程序出錯。
- typeof null === 'object'; // true
- Object.prototype.toString.call(null); // [object Null]
- null instanceof Object; // false
腦袋空了,想到再加。。。