標籤:debug inter function org 調用 ber 執行 生效 enc
譯者按: 總結了大量JavaScript基本知識點,很有用!
- 原文: The Definitive JavaScript Handbook for your next developer interview
為了保證可讀性,本文採用意譯而非直譯。另外,本文著作權歸原作者所有,翻譯僅用於學習。
根據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 }
|
為了便於對比理解,譯者額外增加了測試了一種情況:
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__屬性指向建構函式的建構函式的.prototype。Object.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?v=wfMtDGfHWpA&feature=youtu.be
非同步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
|
- 初始狀態下,瀏覽器控制台沒有輸出,並且事件管理器(Event Manager)是空的;
first()被添加到調用棧
- 將
console.log("First message")加到調用棧
console.log("First message")執行並輸出“First message”到控制台
console.log("First message")從調用棧中移除
first()從調用棧中移除
setTimeout(second, 0)加到調用棧
setTimeout(second, 0)執行,0ms之後,second()被加到回調隊列
setTimeout(second, 0)從調用棧中移除
third()加到調用棧
console.log("Third message")加到調用棧
console.log("Third message")執行並輸出“Third message”到控制台
console.log("Third message")從調用棧中移除
third()從調用棧中移除
- Event Loop 將
second()從回調隊列移到調用棧
console.log("Second message")加到調用棧
console.log("Second message")Second message”到控制台
console.log("Second message")從調用棧中移除
Second()從調用棧中移除
特別注意的是:second()函數在0ms之後並沒有立即執行,你傳入到setTimeout()函數的時間和second()順延強制的時間並不一定直接相關。事件管理器等到setTimeout()設定的時間到期才會將其加入回調隊列,而回調隊列中它執行的時間和它在隊列中的位置已經它前面的函數的執行時間有關。
更多
- 快速掌握JavaScript面試基礎知識(一)
- 快速掌握JavaScript面試基礎知識(二)
著作權聲明:轉載時請註明作者Fundebug以及本文地址:https://blog.fundebug.com/2018/01/29/the-definitive-javascript-handbook-for-a-developer-interview-3/
快速掌握JavaScript面試基礎知識(三)