快速掌握JavaScript面試基礎知識(三)

譯者按: 總結了大量JavaScript基本知識點,頗有用!javascript

爲了保證可讀性,本文采用意譯而非直譯。另外,本文版權歸原做者全部,翻譯僅用於學習。java

根據StackOverflow調查, 自2014年一來,JavaScript是最流行的編程語言。固然,這也在情理之中,畢竟1/3的開發工做都須要一些JavaScript知識。所以,若是你但願在成爲一個開發者,你應該學會這門語言。面試

這篇博客的主要目的是將全部面試中常見的概念總結,方便你快速去了解。(鑑於本文內容過長,方便閱讀,將分爲三篇博客來翻譯, 此爲第三部分。第一部分請點擊快速掌握JavaScript面試基礎知識(一))編程

new關鍵字

若是使用new關鍵字來調用函數式很特別的形式。咱們把那些用new調用的函數叫作構造函數(constructor function)。瀏覽器

使用了new的函數到底作了什麼事情呢?服務器

  • 建立一個新的對象
  • 將對象的prototype設置爲構造函數的prototype
  • 執行構造函數,this執行新構造的對象
  • 返回該對象。若是構造函數返回對象,那麼返回該構造對象。
// 爲了更好地理解底層,咱們來定義new關鍵字
function myNew(constructor, ...arguments) {
  var obj = {}
  Object.setPrototypeOf(obj, constructor.prototype);
  return constructor.apply(obj, arguments) || obj
}

使用new和不使用的區別在哪裏呢?併發

function Bird() {
  this.wings = 2;
}
/* 普通的函數調用 */
let fakeBird = Bird();
console.log(fakeBird);    // undefined
/* 使用new調用 */
let realBird= new Bird();
console.log(realBird)     // { wings: 2 }

爲了便於對比理解,譯者額外增長了測試了一種狀況:app

function MBird(){
  this.wings =2; 
  return "hello";
}

let realMBrid = new MBird();
console.log(realMBird) // { wings: 2 }

你會發現,這一句return "hello"並無生效!異步

原型和繼承

原型(Prototype)是JavaScript中最容易搞混的概念,其中一個緣由是prototype能夠用在兩個不一樣的情形下。編程語言

  • 原型關係
    每個對象都有一個prototype對象,裏面包含了全部它的原型的屬性。
    .__proto__是一個不正規的機制(ES6中提供),用來獲取一個對象的prototype。你能夠理解爲它指向對象的parent
    全部普通的對象都繼承.constructor屬性,它指向該對象的構造函數。當一個對象經過構造函數實現的時候,__proto__屬性指向構造函數的構造函數的.prototypeObject.getPrototypeOf()是ES5的標準函數,用來獲取一個對象的原型。
  • 原型屬性
    每個函數都有一個.prototype屬性,它包含了全部能夠被繼承的屬性。該對象默認包含了指向原構造函數的.constructor屬性。每個使用構造函數建立的對象都有一個構造函數屬性。

接下來經過例子來幫助理解:

function Dog(breed, name){
  this.breed = breed,
  this.name = name
}
Dog.prototype.describe = function() {
  console.log(`${this.name} is a ${this.breed}`)
}
const rusty = new Dog('Beagle', 'Rusty');

/* .prototype 屬性包含了構造函數以及構造函數中在prototype上定義的屬性。*/
console.log(Dog.prototype)  // { describe: ƒ , constructor: ƒ }

/* 使用Dog構造函數構造的對象 */
console.log(rusty)   //  { breed: "Beagle", name: "Rusty" }
/* 從構造函數的原型中繼承下來的屬性或函數 */
console.log(rusty.describe())   // "Rusty is a Beagle"
/* .__proto__ 屬性指向構造函數的.prototype屬性 */
console.log(rusty.__proto__)    // { describe: ƒ , constructor: ƒ }
/* .constructor 屬性指向構造函數 */
console.log(rusty.constructor)  // ƒ Dog(breed, name) { ... }

JavaScript的使用能夠說至關靈活,爲了不出bug了不知道,不妨接入Fundebug線上實時監控

原型鏈

原型鏈是指對象之間經過prototype連接起來,造成一個有向的鏈條。當訪問一個對象的某個屬性的時候,JavaScript引擎會首先查看該對象是否包含該屬性。若是沒有,就去查找對象的prototype中是否包含。以此類推,直到找到該屬性或則找到最後一個對象。最後一個對象的prototype默認爲null。

擁有 vs 繼承

一個對象有兩種屬性,分別是它自身定義的和繼承的。

function Car() { }
Car.prototype.wheels = 4;
Car.prototype.airbags = 1;

var myCar = new Car();
myCar.color = 'black';

/*  原型鏈中的屬性也能夠經過in來查看:  */
console.log('airbags' in myCar)  // true
console.log(myCar.wheels)        // 4
console.log(myCar.year)          // undefined

/*  經過hasOwnProperty來查看是否擁有該屬性:  */
console.log(myCar.hasOwnProperty('airbags'))  // false — Inherited
console.log(myCar.hasOwnProperty('color'))    // true

Object.create(obj) 建立一個新的對象,prototype指向obj

var dog = { legs: 4 };
var myDog = Object.create(dog);

console.log(myDog.hasOwnProperty('legs'))  // false
console.log(myDog.legs)                    // 4
console.log(myDog.__proto__ === dog)       // true

繼承是引用傳值

繼承屬性都是經過引用的形式。咱們經過例子來形象理解:

var objProt = { text: 'original' };
var objAttachedToProt = Object.create(objProt);
console.log(objAttachedToProt.text)   // original

// 咱們更改objProt的text屬性,objAttachedToProt的text屬性一樣更改了
objProt.text = 'prototype property changed';
console.log(objAttachedToProt.text)   // prototype property changed

// 可是若是咱們講一個新的對象賦值給objProt,那麼objAttachedToProt的text屬性不受影響
objProt = { text: 'replacing property' };
console.log(objAttachedToProt.text)   // prototype property changed

經典繼承 vs 原型繼承

Eric Elliott的文章有很是詳細的介紹:Master the JavaScript Interview: What’s the Difference Between Class & Prototypal Inheritance?
做者認爲原型繼承是優於經典的繼承的,並提供了一個視頻介紹:https://www.youtube.com/watch...

異步JavaScript

JavaScript是一個單線程程序語言,也就是說JavaScript引擎一次只能執行某一段代碼。它致使的問題就是:若是有一段代碼須要耗費很長的時間執行,其它的操做就被卡住了。JavaScript使用Call Stack來記錄函數的調用。一個Call Stack能夠當作是一摞書。最後一本書放在最上面,也最早被移走。最早放的書在最底層,最後被移走。

爲了不復雜代碼佔用CPU太長時間,一個解法就是定義異步回調函數。咱們本身來定義一個異步函數看看:

function greetingAsync(name, callback){
  let greeting = "hello, " + name ;
  setTimeout(_ => callback(greeting),0);
}

greetingAsync("fundebug", console.log);
console.log("start greeting");

咱們在greetingAsync中構造了greeting語句,而後經過setTimeout定義了異步,callback函數,是爲了讓用戶本身去定義greeting的具體方式。爲方便起見,咱們時候直接使用console.log
上面代碼執行首先會打印start greeting,而後纔是hello, fundebug。也就是說,greetingAsync的回調函數後執行。在網站開發中,和服務器交互的時候須要不斷地發送各類請求,而一個頁面可能有幾十個請求。若是咱們一個一個按照順序來請求並等待結果,串行的執行會使得網頁加載很慢。經過異步的方式,咱們能夠先發請求,而後在回調中處理請求結果,高效低併發處理。

下面經過一個例子來描述整個執行過程:

const first = function () {
  console.log('First message')
}
const second = function () {
  console.log('Second message')
}
const third = function() {
  console.log('Third message')
}

first();
setTimeout(second, 0);
third();

// 輸出:
  // First message
  // Third message
  // Second message
  1. 初始狀態下,瀏覽器控制檯沒有輸出,而且事件管理器(Event Manager)是空的;
  2. first()被添加到調用棧
  3. console.log("First message")加到調用棧
  4. console.log("First message")執行並輸出「First message」到控制檯
  5. console.log("First message")從調用棧中移除
  6. first()從調用棧中移除
  7. setTimeout(second, 0)加到調用棧
  8. setTimeout(second, 0)執行,0ms以後,second()被加到回調隊列
  9. setTimeout(second, 0)從調用棧中移除
  10. third()加到調用棧
  11. console.log("Third message")加到調用棧
  12. console.log("Third message")執行並輸出「Third message」到控制檯
  13. console.log("Third message")從調用棧中移除
  14. third()從調用棧中移除
  15. Event Loop 將second()從回調隊列移到調用棧
  16. console.log("Second message")加到調用棧
  17. console.log("Second message")Second message」到控制檯
  18. console.log("Second message")從調用棧中移除
  19. Second()從調用棧中移除

特別注意的是:second()函數在0ms以後並無當即執行,你傳入到setTimeout()函數的時間和second()延遲執行的時間並不必定直接相關。事件管理器等到setTimeout()設置的時間到期纔會將其加入回調隊列,而回調隊列中它執行的時間和它在隊列中的位置已經它前面的函數的執行時間有關。

更多


版權聲明:
轉載時請註明做者Fundebug以及本文地址:
https://blog.fundebug.com/201...

相關文章
相關標籤/搜索