《前端面試手記》之JavaScript基礎知識梳理(上)

👇 內容速覽 👇javascript

  • 普通函數和箭頭函數的this
  • 原始數據類型及其判斷和轉化方法
  • 深淺拷貝及實現
  • JS事件模型
  • 常見的高階函數

🔍查看所有教程 / 閱讀原文🔍css

普通函數和箭頭函數的this

仍是一道經典題目,下面的這段代碼的輸出是什麼?(爲了方便解釋,輸出放在了註釋中)html

function fn() {
  console.log(this); // 1. {a: 100}
  var arr = [1, 2, 3];

  (function() {
    console.log(this); // 2. Window
  })();

  // 普通 JS
  arr.map(function(item) {
    console.log(this); // 3. Window
    return item + 1;
  });
  // 箭頭函數
  let brr = arr.map(item => {
    console.log("es6", this); // 4. {a: 100}
    return item + 1;
  });
}
fn.call({ a: 100 });

其實訣竅很簡單,常見的基本是3種狀況:es5普通函數、es6的箭頭函數以及經過bind改變過上下文返回的新函數。前端

es5普通函數java

  • 函數被直接調用,上下文必定是window
  • 函數做爲對象屬性被調用,例如:obj.foo(),上下文就是對象自己obj
  • 經過new調用,this綁定在返回的實例上

es6箭頭函數: 它自己沒有this,會沿着做用域向上尋找,直到global / window。請看下面的這段代碼:webpack

function run() {
  const inner = () => {
    return () => {
      console.log(this.a)
    }
  }

  inner()()
}

run.bind({a: 1})() // Output: 1

bind綁定上下文返回的新函數:就是被第一個bind綁定的上下文,並且bind對「箭頭函數」無效。請看下面的這段代碼:css3

function run() {
  console.log(this.a)
}

run.bind({a: 1})() // output: 1

// 屢次bind,上下文由第一個bind的上下文決定
run
  .bind({a: 2})
  .bind({a: 1})
  () // output: 2

最後,再說說這幾種方法的優先級:new > bind > 對象調用 > 直接調用git

至此,這道題目的輸出就說能夠解釋明白了。es6

原始數據類型和判斷方法

題目:JS中的原始數據類型?

ECMAScript 中定義了 7 種原始類型:github

  • Boolean
  • String
  • Number
  • Null
  • Undefined
  • Symbol(新定義)
  • BigInt(新定義)

注意:原始類型不包含Object和Function

題目:經常使用的判斷方法?

在進行判斷的時候有typeofinstanceof。對於數組的判斷,使用Array.isArray()

  • typeof:

    • typeof基本均可以正確判斷數據類型
    • typeof nulltypeof [1, 2, 3]均返回"object"
    • ES6新增:typeof Symbol()返回"symbol"
  • instanceof:

    • 專門用於實例和構造函數對應

      function Obj(value){ 
          this.value = value; 
      }
      let obj = new Obj("test");
      console.log(obj instanceof Obj); // output: true
    • 判斷是不是數組:[1, 2, 3] instanceof Array
  • Array.isArray():ES6新增,用來判斷是不是'Array'。Array.isArray({})返回false

原始類型轉化

當咱們對一個「對象」進行數學運算操做時候,會涉及到對象 => 基礎數據類型的轉化問題。

事實上,當一個對象執行例如加法操做的時候,若是它是原始類型,那麼就不須要轉換。不然,將遵循如下規則:

  1. 調用實例的valueOf()方法,若是有返回的是基礎類型,中止下面的過程;不然繼續
  2. 調用實例的toString()方法,若是有返回的是基礎類型,中止下面的過程;不然繼續
  3. 都沒返回原始類型,就會報錯

請看下面的測試代碼:

let a = {
  toString: function() {
    return 'a'
  }
}

let b = {
  valueOf: function() {
    return 100
  },
  toString: function() {
    return 'b'
  }
}

let c = Object.create(null) // 建立一個空對象

console.log(a + '123') // output: a123
console.log(b + 1) // output: 101
console.log(c + '123') // 報錯

除了valueOftoString,es6還提供了Symbol.toPrimitive供對象向原始類型轉化,而且它的優先級最高!!稍微改造下上面的代碼:

let b = {
  valueOf: function() {
    return 100
  },
  toString: function() {
    return 'b'
  },
  [Symbol.toPrimitive]: function() {
    return 10000
  }
}

console.log(b + 1) // output: 10001

最後,其實關於instanceof判斷是不是某個對象的實例,es6也提供了Symbol.hasInstance接口,代碼以下:

class Even {
  static [Symbol.hasInstance](num) {
    return Number(num) % 2 === 0;
  }
}

const Odd = {
  [Symbol.hasInstance](num) {
    return Number(num) % 2 !== 0;
  }
};

console.log(1 instanceof Even); // output: false
console.log(1 instanceof Odd); // output: true

深拷貝和淺拷貝

題目:實現對象的深拷貝。

在JS中,函數和對象都是淺拷貝(地址引用);其餘的,例如布爾值、數字等基礎數據類型都是深拷貝(值引用)。

值得提醒的是,ES6的Object.assign()和ES7的...解構運算符都是「淺拷貝」。實現深拷貝仍是須要本身手動擼「輪子」或者藉助第三方庫(例如lodash):

  • 手動作一個「完美」的深拷貝函數:https://godbmw.com/passages/2019-03-18-interview-js-code/
  • 藉助第三方庫:jq的extend(true, result, src1, src2[ ,src3])、lodash的cloneDeep(src)
  • JSON.parse(JSON.stringify(src)):這種方法有侷限性,若是屬性值是函數或者一個類的實例的時候,沒法正確拷貝
  • 藉助HTML5的MessageChannel:這種方法有侷限性,當屬性值是函數的時候,會報錯

    <script>
      function deepClone(obj) {
        return new Promise(resolve => {
          const {port1, port2} = new MessageChannel();
          port2.onmessage = ev => resolve(ev.data);
          port1.postMessage(obj);
        });
      }
    
      const obj = {
        a: 1,
        b: {
          c: [1, 2],
          d: '() => {}'
        }
      };
    
      deepClone(obj)
        .then(obj2 => {
          obj2.b.c[0] = 100;
          console.log(obj.b.c); // output: [1, 2]
          console.log(obj2.b.c); // output: [100, 2]
        })
    </script>

JS事件流

事件冒泡和事件捕獲

事件流分爲:冒泡捕獲,順序是先捕獲再冒泡。

事件冒泡:子元素的觸發事件會一直向父節點傳遞,一直到根結點中止。此過程當中,能夠在每一個節點捕捉到相關事件。能夠經過stopPropagation方法終止冒泡。

事件捕獲:和「事件冒泡」相反,從根節點開始執行,一直向子節點傳遞,直到目標節點。

addEventListener給出了第三個參數同時支持冒泡與捕獲:默認是false,事件冒泡;設置爲true時,是事件捕獲。

<div id="app" style="width: 100vw; background: red;">
  <span id="btn">點我</span>
</div>
<script>
  // 事件捕獲:先輸出 "外層click事件觸發"; 再輸出 "內層click事件觸發"
  var useCapture = true;
  var btn = document.getElementById("btn");
  btn.addEventListener(
    "click",
    function() {
      console.log("內層click事件觸發");
    },
    useCapture
  );

  var app = document.getElementById("app");
  app.onclick = function() {
    console.log("外層click事件觸發");
  };
</script>

DOM0級 和 DOM2級

DOM2級:前面說的addEventListener,它定義了DOM事件流,捕獲 + 冒泡。

DOM0級

  • 直接在html標籤內綁定on事件
  • 在JS中綁定on系列事件

注意:如今通用DOM2級事件,優勢以下:

  1. 能夠綁定 / 卸載事件
  2. 支持事件流
  3. 冒泡 + 捕獲:至關於每一個節點同一個事件,至少2次處理機會
  4. 同一類事件,能夠綁定多個函數

常見的高階函數

沒什麼好說的,跑一下下面的代碼就能夠理解了:

// map: 生成一個新數組,遍歷原數組,
// 將每一個元素拿出來作一些變換而後放入到新的數組中
let newArr = [1, 2, 3].map(item => item * 2);
console.log(`New array is ${newArr}`);

// filter: 數組過濾, 根據返回的boolean
// 決定是否添加到數組中
let newArr2 = [1, 2, 4, 6].filter(item => item !== 6);
console.log(`New array2 is ${newArr2}`);

// reduce: 結果彙總爲單個返回值
// acc: 累計值; current: 當前item
let arr = [1, 2, 3];
const sum = arr.reduce((acc, current) => acc + current);
const sum2 = arr.reduce((acc, current) => acc + current, 100);
console.log(sum); // 6
console.log(sum2); // 106

更多系列文章

⭐在GitHub上收藏/訂閱⭐

《前端知識體系》

《設計模式手冊》

《Webpack4漸進式教程》

⭐在GitHub上收藏/訂閱⭐

相關文章
相關標籤/搜索