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

👇 內容速覽 👇javascript

  • 實現ES5繼承的4種方法
  • 原型和原型鏈
  • 做用域和做用域鏈
  • Event Loop
  • 執行上下文
  • 閉包的理解和分析

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

ES5繼承

題目:ES5中經常使用繼承方法。

方法一:綁定構造函數前端

缺點:不能繼承父類原型方法/屬性java

function Animal(){
  this.species = '動物'
}

function Cat(){
  // 執行父類的構造方法, 上下文爲實例對象
  Animal.apply(this, arguments)
}


/**
 * 測試代碼
 */
var cat = new Cat()
console.log(cat.species) // output: 動物

方法二:原型鏈繼承node

缺點:沒法向父類構造函數中傳遞參數;子類原型鏈上定義的方法有前後順序問題。webpack

注意:js中交換原型鏈,均須要修復prototype.constructor指向問題。css3

function Animal(species){
  this.species = species
}
Animal.prototype.func = function(){
  console.log('Animal')
}

function Cat(){}
/**
 * func方法是無效的, 由於後面原型鏈被從新指向了Animal實例
 */
Cat.prototype.func = function() {
  console.log('Cat')
}

Cat.prototype = new Animal()
Cat.prototype.constructor = Cat // 修復: 將Cat.prototype.constructor從新指向自己

/**
 * 測試代碼
 */
var cat = new Cat()
cat.func() // output: Animal
console.log(cat.species) // undefined

方法3:組合繼承git

結合綁定構造函數和原型鏈繼承2種方式,缺點是:調用了2次父類的構造函數。es6

function Animal(species){
  this.species = species
}
Animal.prototype.func = function(){
  console.log('Animal')
}

function Cat(){
  Animal.apply(this, arguments)
}

Cat.prototype = new Animal()
Cat.prototype.constructor = Cat 

/**
 * 測試代碼
 */
var cat = new Cat('cat')
cat.func() // output: Animal
console.log(cat.species) // output: cat

方法4:寄生組合繼承github

改進了組合繼承的缺點,只須要調用1次父類的構造函數。它是引用類型最理想的繼承範式。(引自:《JavaScript高級程序設計》)

/**
 * 寄生組合繼承的核心代碼
 * @param {Function} sub 子類
 * @param {Function} parent 父類
 */
function inheritPrototype(sub, parent) {
  // 拿到父類的原型
  var prototype = Object(parent.prototype) 
  // 改變constructor指向
  prototype.constructor = sub
  // 父類原型賦給子類
  sub.prototype = prototype
}

function Animal(species){
  this.species = species
}
Animal.prototype.func = function(){
  console.log('Animal')
}

function Cat(){
  Animal.apply(this, arguments) // 只調用了1次構造函數
}

inheritPrototype(Cat, Animal)

/**
 * 測試代碼
 */

var cat = new Cat('cat')
cat.func() // output: Animal
console.log(cat.species) // output: cat

原型和原型鏈

  • 全部的引用類型(數組、對象、函數),都有一個__proto__屬性,屬性值是一個普通的對象
  • 全部的函數,都有一個prototype屬性,屬性值也是一個普通的對象
  • 全部的引用類型(數組、對象、函數),__proto__屬性值指向它的構造函數的prototype屬性值

:ES6的箭頭函數沒有prototype屬性,可是有__proto__屬性。

const obj = {};
// 引用類型的 __proto__ 屬性值指向它的構造函數的 prototype 屬性值
console.log(obj.__proto__ === Object.prototype); // output: true

原型

題目:如何JS中的原型?
// 構造函數
function Foo(name, age) {
    this.name = name
}
Foo.prototype.alertName = function () {
    alert(this.name)
}
// 建立示例
var f = new Foo('zhangsan')
f.printName = function () {
    console.log(this.name)
}
// 測試
f.printName()
f.alertName()

可是執行alertName時發生了什麼?這裏再記住一個重點 當試圖獲得一個對象的某個屬性時,若是這個對象自己沒有這個屬性,那麼會去它的__proto__(即它的構造函數的prototype)中尋找,所以f.alertName就會找到Foo.prototype.alertName

原型鏈

題目:如何JS中的原型鏈?

以上一題爲基礎,若是調用f.toString()

  1. f試圖從__proto__中尋找(即Foo.prototype),仍是沒找到toString()方法。
  2. 繼續向上找,從f.__proto__.__proto__中尋找(即Foo.prototype.__proto__中)。由於Foo.prototype就是一個普通對象,所以Foo.prototype.__proto__ = Object.prototype
  3. 最終對應到了Object.prototype.toString

這是對深度遍歷的過程,尋找的依據就是一個鏈式結構,因此叫作「原型鏈」。

做用域和做用域鏈

題目:如何理解 JS 的做用域和做用域鏈。

①做用域

ES5有」全局做用域「和」函數做用域「。ES6的letconst使得JS用了」塊級做用域「。

爲了解決ES5的全局衝突,通常都是閉包編寫:(function(){ ... })()。將變量封裝到函數做用域。

②做用域鏈

當前做用域沒有找到定義,繼續向父級做用域尋找,直至全局做用域。這種層級關係,就是做用域鏈

Event Loop

單線程

題目:講解下面代碼的執行過程和結果。
var a = true;
setTimeout(function(){
    a = false;
}, 100)
while(a){
    console.log('while執行了')
}

這段代碼會一直執行而且輸出"while..."。JS是單線程的,先跑執行棧裏的同步任務,而後再跑任務隊列的異步任務

執行棧和任務隊列

題目:說一下JS的Event Loop。

簡單總結以下:

  1. JS是單線程的,其上面的全部任務都是在兩個地方執行:執行棧和任務隊列。前者是存放同步任務;後者是異步任務有結果後,就在其中放入一個事件。
  2. 當執行棧的任務都執行完了(棧空),js會讀取任務隊列,並將能夠執行的任務從任務隊列丟到執行棧中執行。
  3. 這個過程是循環進行,因此稱做Loop

執行上下文

題目:解釋下「全局執行上下文「和「函數執行上下文」。

全局執行上下文

解析JS時候,建立一個 全局執行上下文 環境。把代碼中即將執行的(內部函數的不算,由於你不知道函數什麼時候執行)變量、函數聲明都拿出來。未賦值的變量就是undefined

下面這段代碼輸出:undefined;而不是拋出Error。由於在解析JS的時候,變量a已經存入了全局執行上下文中了。

console.log(a);
var a = 1;

函數執行上下文

和全局執行上下文差很少,可是多了thisarguments和參數。

在JS中,this是關鍵字,它做爲內置變量,其值是在執行的時候肯定(不是定義的時候肯定)

閉包的理解和分析

題目:解釋下js的閉包

直接上MDN的解釋:閉包是函數聲明該函數的詞法環境的組合。

而在JavaScript中,函數是被做爲一級對象使用的,它既能夠本看成值返回,還能夠看成參數傳遞。理解了:「Js中的函數運行在它們被定義的做用域,而不是它們被執行的做用域」(摘自《JavaScript語言精粹》) 這句話便可。

題目:閉包優缺點

閉包封住了變量做用域,有效地防止了全局污染;但同時,它也存在內存泄漏的風險:

  • 在瀏覽器端能夠經過強制刷新解決,對用戶體驗影響不大
  • 在服務端,因爲node的內存限制和累積效應,可能會形成進程退出甚至服務器沓機

解決方法是顯式對外暴露一個接口,專門用以清理變量:

function mockData() {
  const mem = {}
  
  return {
    clear: () => mem = null, // 顯式暴露清理接口

    get: (page) => {
      if(page in mem) {
        return mem[page]
      }
      mem[page] = Math.random()
    }
  }
}

更多系列教程

⭐在GitHub上收藏/訂閱⭐

《前端知識體系》

《設計模式手冊》

《Webpack4漸進式教程》

⭐在GitHub上收藏/訂閱⭐

相關文章
相關標籤/搜索