[誠意滿滿✍]帶你填一些JS容易出錯的坑

前言

漫漫編程路,總有一些坑讓你淚流滿面。前端

[1,2,5,10].sort()

不寫回調函數的話,是按照什麼排序呢?git

JavaScript默認使用字典序(alphanumeric)來排序。所以結果是[1,10,2,5]github

正確排序的話,應該[1,2,5,10].sort( (a,b) => a-b )web

"b" + "a" + +"a" + "a"

你認爲輸出是什麼?面試

上面的表達式至關於'b'+'a'+ (+'a')+'a',由於(+'a')是NaN,因此:編程

'b'+'a'+ (+'a')+'a' = 'b'+'a'+ "NaN"+'a'='baNaNa'數組

閉包

這是一個經典JavaScript面試題瀏覽器

let res = new Array()
 for(var i = 0; i < 10; i++){  res.push(function(){  return console.log(i)  })  }  res[0]()  res[1]()  res[2]() 複製代碼

指望輸出的是0,1,2,實際上卻不會。緣由就是涉及做用域,怎麼解決呢?安全

  • [x] 使用let代替var,造成塊級做用域
  • [x] 使用bind函數。
res.push(console.log.bind(null, i))
複製代碼

解法還有其餘的,好比使用IIFE,造成私有做用域等等作法。閉包

又一經典閉包問題

function fun(n,o) {
 console.log(o)  return {  fun:function(m){  return fun(m,n);  }  }; } var a = fun(0); a.fun(1); a.fun(2); a.fun(3);//undefined,?,?,? var b = fun(0).fun(1).fun(2).fun(3);//undefined,?,?,? var c = fun(0).fun(1); c.fun(2); c.fun(3);//undefined,?,?,? 複製代碼

留給大家思考

隱式轉換

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

大家以爲答案是多少呢?這題涉及到隱式轉換了,這個坑我本身的好好補一補

// 答案:false

再來一道?

function fn() {
 return 20; } console.log(fn + 10); // 輸出結果是多少 複製代碼
function fn() {
 return 20; } fn.toString = function() {  return 10; } console.log(fn + 10); // 輸出結果是多少? 複製代碼
function fn() {
 return 20; }  fn.toString = function() {  return 10; }  fn.valueOf = function() {  return 5; }  console.log(fn + 10); // 輸出結果是多少? 複製代碼

說到底JS類型轉換的好好補一補了

你真的理解操做符嗎

[1<2<3,3<2<1]
//[false,false] //[true,true] //[false,true] //[true,false] 複製代碼

選一個吧,比較操做符,賦值運算符優先級哪一個更高呢?

0.1+0.2 !== 0.3 ?

面試的時候,問你這個問題,要是回答錯誤的話,估計面試官對基礎非常懷疑!!!

問你這個題目的時候,你能夠牽扯出不少問題,好比JS如何存儲小數的呢?好比聊一聊二進制,好比實際開發中,遇到精度的問題,你是怎麼解決的,你有什麼好辦法。

聊完這個,你能夠牽扯出最大安全數,好比JavaScript的最大安全整數是多少,超出這個範圍的話,怎麼解決精度問題呢?

ES規範中新提出的BigInt解決了什麼問題呢,你又發現了BigInt中哪些坑呢?

如何解決精度問題呢?

這裏推薦Number-Precision庫,不到1K的體積。

arguments

function sidEffecting(ary) {
 ary[0] = ary[2];  }  function bar(a, b, c) {  c = 10  sidEffecting(arguments);  return a + b + c;  }  function demo (arg) {  arg.name = 'new Name'  }  console.log(bar(2, 2, 2)) 複製代碼

涉及到ES6語法,這題答案確定都會作是22,可是呢,稍微改變一下題目,就比較坑了….

function sidEffecting(ary) {
 ary[0] = ary[2];  }  function bar(a, b, c = 4) {  c = 10  sidEffecting(arguments);  return a + b + c;  }  function demo (arg) {  arg.name = 'new Name'  }  console.log(bar(2, 2, 2)) 複製代碼

這個答案是多少呢?根據MDN上對argument有更加準確的定義,看argument

當非嚴格模式中的函數包含剩餘參數默認參數解構賦值,那麼arguments對象中的值不會跟蹤參數的值(反之亦然)。

找到這句話,bar函數存在默認參數,而且在非嚴格模式下,因此不會跟蹤參數的值,天然結果就14

請讀者細細體會

瀏覽器懵逼史

let demo1 = {class: "Animal", name: 'sheet'};
 console.log(demo1.class)  複製代碼

比較流氓,這個跟瀏覽器相關,class是保留字(如今的話,class是關鍵字),答案並沒關係,重要的是本身在取屬性名稱的時候儘可能避免保留字. 若是使用的話請加引號 a['class']。

保留字vs關鍵字

我的理解的話,關鍵字就是有特殊含義的,不用用做變量名。好比

let class = 123;
 複製代碼

如今看來確定報錯,那有什麼須要咱們注意的呢?

let undefined = 123;
 複製代碼

這樣子並不會報錯,這個跟瀏覽器有點關係,這樣子看來undefined不是關鍵字。因此爲了保險起見,建議你們在判斷一個變量是否是未定義的話,儘可能使用void 0 === undefined 頗有可能undefined會被看成是變量來賦值

void 0 值就是undefined

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

這個應該是常常碰見的題了,搞明白很簡單,map函數怎麼使用,parseInt函數怎麼使用

關於Array數組的話,我以前寫了一篇文章,從源碼角度解析大部分方法

點進去重溫一遍:[乾貨👍]從詳細操做js數組到淺析v8中array.js

map接受兩個參數,一個callback,一個this,即調用函數時this指向,其中callback回調函數是三個參數,一個currentValue,index,array;

parseInt接受兩個參數:string,radix(基數)

返回NaN有兩種狀況

  • radix 小於 2 或大於 36 ,或
  • 第一個非空格字符不能轉換爲數字。
  • 當radix是0或者undefined時,又是特殊狀況,具體異步 MDN
parseInt('1', 0);
parseInt('2', 1); parseInt('3', 2);  複製代碼

二者結合的話,結果天然很明顯,[1,NaN,NaN]

Math.min() 爲何比 Math.max() 大?

Math.min() < Math.max() // false
 複製代碼

按照常規思路的話,應該是true,畢竟最小值應該小於最大值,可是實際狀況是false

緣由:

  • Math.min 的參數是 0 個或者多個。若是是多個參數很容易理解,返回參數中最小的。
  • 若是是0個參數,或者沒有參數,則返回 Infinity
  • 而 Math.max() 沒有傳遞參數時返回的是 -Infinity。

要是面試官問這個問題,額。。。。

[].concat[1,2,3]

輸出是什麼?注意不是[].concat([1,2,3])

// [1,2,3]
 // Uncaught SyntaxError: ....  // undefined  複製代碼

答案是undefined,緣由是什麼呢?

  1. 第一步計算[].concat,結果是Array.prototype.concat

  2. 第二步執行一個逗號操做符,逗號操做符對它的每一個操做對象求值(從左至右),而後返回最後一個操做對象的值。

    >1,2,3
    返回3  複製代碼
  3. 第三步執行一個數組訪問運算或屬性訪問運算

因此上面[].concat[1,2,3] 等價於Array.prototype.concat[3]

那麼結果天然就是 undefined

[1,2,NaN,3].indexOf(NaN)

//2 or -1

  • indexOf方法會進行嚴格相等判斷
  • NaN !== NaN

怎麼辦呢?

let realIsNaN = value => typeof value === 'number' && isNaN(value);
複製代碼

先要判斷類型,是由於字符串轉換會先轉換成數字,轉換失敗爲 NaN。因此和 NaN 相等。

isNaN('jjjj') —> true
複製代碼

第二種方法

let realIsNaN = value => value !== value;
複製代碼

Number.isFinite & isFinite

Number.isFinite('0') === isFinite('0')
 Number.isFinite(0) === isFinite('0') 複製代碼

打印結果是什麼,能不能具體說一說?

Number.isFinite()檢測有窮性的值,惟一和全局isFinite()函數相比,這個方法不會強制將一個非數值的參數轉換成數值,這就意味着,只有數值類型的值,且是有窮的(finite),才返回 true

天然答案就是 false,true

一道容易被人輕視的面試題

function Foo() {
 getName = function () { alert (1); };  return this; } Foo.getName = function () { alert (2);}; Foo.prototype.getName = function () { alert (3);}; var getName = function () { alert (4);}; function getName() { alert (5);}  //請寫出如下輸出結果: Foo.getName(); getName(); Foo().getName(); getName(); new Foo.getName(); new Foo().getName(); new new Foo().getName();  複製代碼

push方法

let newList = [1,2,3].push(4)
console.log(newList.push(4)) 複製代碼

認爲輸出什麼?

// Error

緣由在於Array.prototype.pu sh()返回的是新數組的長度,因此呢4.push(5)天然Error

7.13更新一波

自動分號插入

function foo1() {  return {  bar: "hello"  }; }  function foo2() {  return  {  bar: "hello"  }; } var a=foo1(); var b=foo2(); console.log(a) //Object {bar: "hello"} console.log(b) //underfind //仔細看就知道了 // 會在第10行加入一個`;` 複製代碼

會在第10行自動加一個分號; 因此返回的就是undefined


let var

function foo() {
let a = b = 0; a++; return a; } foo(); typeof a; // => ??? typeof b; // => ???  複製代碼

上面的let a = b = 0; 等價於 window.b = 0, let a = b;


眼力題

const length = 4;
const numbers = []; for (var i = 0; i < length; i++);{  numbers.push(i + 1); }  numbers; // => ??? 複製代碼

惟一須要注意的就是for語句後面帶了;沙雕題

加了;,會認爲for執行完,因此指定的都是空語句,最後numbers爲[5]


獲取字符串中特定索引字符

console.log('Hello World'[4])
複製代碼

使用的就是方括號表示法獲取字符串特定索引的字符,值得注意的是,IE7低版本使用的是charAt()

因此這題輸出o


!==

const name = 'TianTianUp'
console.log(!typeof name === 'string') console.log(!typeof name === 'object') 複製代碼

typeof name 返回的是 ’string‘, 字符串’string‘是一個truthy值。所以!typeof name 返回一個布爾值false。因此

false === ’string'

和 false === ’object‘返回false

(檢測一個類型的值話,咱們應該使用 !==而不是!typeof)


forEach

const nums = [1, 2, 3, 4, 5, 6];
let firstEven; nums.forEach(n => {  if (n % 2 ===0 ) {  firstEven = n;  return n;  } }); console.log(firstEven); 複製代碼

惟一須要注意的就是forEach源碼是怎麼寫的,看過源碼的都知道,forEach使用return是不能停止循環的,或者說每一次調用callback函數,終止的是當前的一次,而不是整個循環。

結果天然就是6


持續更新中

有不錯的題目,或者是JS中容易出錯的坑,掘金網友能夠提出來❤️,我會更新的啦🆗

❤️ 感謝你們

若是你以爲這篇內容對你挺有有幫助的話:

  1. 點贊支持下吧,讓更多的人也能看到這篇內容(收藏不點贊,都是耍流氓 -_-)

  2. 歡迎在留言區與我分享你的想法,也歡迎你在留言區記錄你的思考過程。

  3. 以爲不錯的話,也能夠看看往期文章:

    [誠意滿滿👍]Chrome DevTools調試小技巧,效率➡️🚀🚀🚀

    [實用👍]推薦一些很是棒的前端網站

    [乾貨👍]從詳細操做js數組到淺析v8中array.js

    [1.2W字👍]寫給女朋友的祕籍-瀏覽器工做原理(上)篇

    [1.1W字]寫給女朋友的祕籍-瀏覽器工做原理(渲染流程)篇

    [建議👍]再來100道JS輸出題酸爽繼續(共1.8W字+鞏固JS基礎)

    [誠意滿滿✍]帶你填一些JS容易出錯的坑

相關文章
相關標籤/搜索