這篇博客的主要目的是將所有面試中常見(jiàn)的概念總結(jié),方便你快速去了解。(鑒于本文內(nèi)容過(guò)長(zhǎng),方便閱讀,將分為三篇博客來(lái)翻譯, 此為第三部分。第一部分請(qǐng)點(diǎn)擊快速掌握J(rèn)avascript面試基礎(chǔ)知識(shí)(一))
new關(guān)鍵字
如果使用new
關(guān)鍵字來(lái)調(diào)用函數(shù)式很特別的形式。我們把那些用new
調(diào)用的函數(shù)叫做構(gòu)造函數(shù)(constructor function)。
使用了new
的函數(shù)到底做了什么事情呢?
創(chuàng)建一個(gè)新的對(duì)象
將對(duì)象的prototype設(shè)置為構(gòu)造函數(shù)的prototype
執(zhí)行構(gòu)造函數(shù),
this
執(zhí)行新構(gòu)造的對(duì)象返回該對(duì)象。如果構(gòu)造函數(shù)返回對(duì)象,那么返回該構(gòu)造對(duì)象。
// 為了更好地理解底層,我們來(lái)定義new關(guān)鍵字 function myNew(constructor, ...arguments) { var obj = {} Object.setPrototypeOf(obj, constructor.prototype); return constructor.apply(obj, arguments) || obj }
使用new
和不使用的區(qū)別在哪里呢?
function Bird() { this.wings = 2; } let fakeBird = Bird(); console.log(fakeBird); // undefined let realBird= new Bird(); console.log(realBird) // { wings: 2 }
為了便于對(duì)比理解,譯者額外增加了測(cè)試了一種情況:
function MBird(){ this.wings =2; return "hello"; } let realMBrid = new MBird(); console.log(realMBird) // { wings: 2 }
你會(huì)發(fā)現(xiàn),這一句return "hello"
并沒(méi)有生效!
原型和繼承
原型(Prototype)是Javascript中最容易搞混的概念,其中一個(gè)原因是prototype
可以用在兩個(gè)不同的情形下。
原型關(guān)系
每一個(gè)對(duì)象都有一個(gè)prototype
對(duì)象,里面包含了所有它的原型的屬性。.__proto__
是一個(gè)不正規(guī)的機(jī)制(ES6中提供),用來(lái)獲取一個(gè)對(duì)象的prototype。你可以理解為它指向?qū)ο蟮?code>parent。
所有普通的對(duì)象都繼承.constructor
屬性,它指向該對(duì)象的構(gòu)造函數(shù)。當(dāng)一個(gè)對(duì)象通過(guò)構(gòu)造函數(shù)實(shí)現(xiàn)的時(shí)候,__proto__
屬性指向構(gòu)造函數(shù)的構(gòu)造函數(shù)的.prototype
。Object.getPrototypeOf()
是ES5的標(biāo)準(zhǔn)函數(shù),用來(lái)獲取一個(gè)對(duì)象的原型。原型屬性
每一個(gè)函數(shù)都有一個(gè).prototype
屬性,它包含了所有可以被繼承的屬性。該對(duì)象默認(rèn)包含了指向原構(gòu)造函數(shù)的.constructor
屬性。每一個(gè)使用構(gòu)造函數(shù)創(chuàng)建的對(duì)象都有一個(gè)構(gòu)造函數(shù)屬性。
接下來(lái)通過(guò)例子來(lái)幫助理解:
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'); console.log(Dog.prototype) // { describe: ? , constructor: ? } console.log(rusty) // { breed: "Beagle", name: "Rusty" } console.log(rusty.describe()) // "Rusty is a Beagle" console.log(rusty.__proto__) // { describe: ? , constructor: ? } console.log(rusty.constructor) // ? Dog(breed, name) { ... }
Javascript的使用可以說(shuō)相當(dāng)靈活,為了避免出bug了不知道,不妨接入Fundebug線(xiàn)上實(shí)時(shí)監(jiān)控。
原型鏈
原型鏈?zhǔn)侵笇?duì)象之間通過(guò)prototype鏈接起來(lái),形成一個(gè)有向的鏈條。當(dāng)訪(fǎng)問(wèn)一個(gè)對(duì)象的某個(gè)屬性的時(shí)候,Javascript引擎會(huì)首先查看該對(duì)象是否包含該屬性。如果沒(méi)有,就去查找對(duì)象的prototype中是否包含。以此類(lèi)推,直到找到該屬性或則找到最后一個(gè)對(duì)象。最后一個(gè)對(duì)象的prototype默認(rèn)為null。
擁有 vs 繼承
一個(gè)對(duì)象有兩種屬性,分別是它自身定義的和繼承的。
function Car() { } Car.prototype.wheels = 4; Car.prototype.airbags = 1; var myCar = new Car(); myCar.color = 'black'; console.log('airbags' in myCar) // true console.log(myCar.wheels) // 4 console.log(myCar.year) // undefined console.log(myCar.hasOwnProperty('airbags')) // false — Inherited console.log(myCar.hasOwnProperty('color')) // true
Object.create(obj)
創(chuàng)建一個(gè)新的對(duì)象,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
繼承是引用傳值
繼承屬性都是通過(guò)引用的形式。我們通過(guò)例子來(lái)形象理解:
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 // 但是如果我們講一個(gè)新的對(duì)象賦值給objProt,那么objAttachedToProt的text屬性不受影響 objProt = { text: 'replacing property' }; console.log(objAttachedToProt.text) // prototype property changed
經(jīng)典繼承 vs 原型繼承
Eric Elliott的文章有非常詳細(xì)的介紹:Master the Javascript Interview: What’s the Difference Between Class & Prototypal Inheritance?
作者認(rèn)為原型繼承是優(yōu)于經(jīng)典的繼承的,并提供了一個(gè)視頻介紹:https://www.youtube.com/watch...
異步Javascript
Javascript是一個(gè)單線(xiàn)程程序語(yǔ)言,也就是說(shuō)Javascript引擎一次只能執(zhí)行某一段代碼。它導(dǎo)致的問(wèn)題就是:如果有一段代碼需要耗費(fèi)很長(zhǎng)的時(shí)間執(zhí)行,其它的操作就被卡住了。Javascript使用Call Stack來(lái)記錄函數(shù)的調(diào)用。一個(gè)Call Stack可以看成是一摞書(shū)。最后一本書(shū)放在最上面,也最先被移走。最先放的書(shū)在最底層,最后被移走。
為了避免復(fù)雜代碼占用CPU太長(zhǎng)時(shí)間,一個(gè)解法就是定義異步回調(diào)函數(shù)。我們自己來(lái)定義一個(gè)異步函數(shù)看看:
function greetingAsync(name, callback){ let greeting = "hello, " + name ; setTimeout(_ => callback(greeting),0); } greetingAsync("fundebug", console.log); console.log("start greeting");
我們?cè)?code>greetingAsync中構(gòu)造了greeting
語(yǔ)句,然后通過(guò)setTimeout
定義了異步,callback
函數(shù),是為了讓用戶(hù)自己去定義greeting的具體方式。為方便起見(jiàn),我們時(shí)候直接使用console.log
。
上面代碼執(zhí)行首先會(huì)打印start greeting
,然后才是hello, fundebug
。也就是說(shuō),greetingAsync
的回調(diào)函數(shù)后執(zhí)行。在網(wǎng)站開(kāi)發(fā)中,和服務(wù)器交互的時(shí)候需要不斷地發(fā)送各種請(qǐng)求,而一個(gè)頁(yè)面可能有幾十個(gè)請(qǐng)求。如果我們一個(gè)一個(gè)按照順序來(lái)請(qǐng)求并等待結(jié)果,串行的執(zhí)行會(huì)使得網(wǎng)頁(yè)加載很慢。通過(guò)異步的方式,我們可以先發(fā)請(qǐng)求,然后在回調(diào)中處理請(qǐng)求結(jié)果,高效低并發(fā)處理。
下面通過(guò)一個(gè)例子來(lái)描述整個(gè)執(zhí)行過(guò)程:
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
初始狀態(tài)下,瀏覽器控制臺(tái)沒(méi)有輸出,并且事件管理器(Event Manager)是空的;
first()
被添加到調(diào)用棧將
console.log("First message")
加到調(diào)用棧console.log("First message")
執(zhí)行并輸出“First message”到控制臺(tái)console.log("First message")
從調(diào)用棧中移除first()
從調(diào)用棧中移除setTimeout(second, 0)
加到調(diào)用棧setTimeout(second, 0)
執(zhí)行,0ms之后,second()
被加到回調(diào)隊(duì)列setTimeout(second, 0)
從調(diào)用棧中移除third()
加到調(diào)用棧console.log("Third message")
加到調(diào)用棧console.log("Third message")
執(zhí)行并輸出“Third message”到控制臺(tái)console.log("Third message")
從調(diào)用棧中移除third()
從調(diào)用棧中移除Event Loop 將
second()
從回調(diào)隊(duì)列移到調(diào)用棧console.log("Second message")
加到調(diào)用棧console.log("Second message")
Second message”到控制臺(tái)console.log("Second message")
從調(diào)用棧中移除Second()
從調(diào)用棧中移除
特別注意的是:second()
函數(shù)在0ms之后并沒(méi)有立即執(zhí)行,你傳入到setTimeout()
函數(shù)的時(shí)間和second()
延遲執(zhí)行的時(shí)間并不一定直接相關(guān)。事件管理器等到setTimeout()
設(shè)置的時(shí)間到期才會(huì)將其加入回調(diào)隊(duì)列,而回調(diào)隊(duì)列中它執(zhí)行的時(shí)間和它在隊(duì)列中的位置已經(jīng)它前面的函數(shù)的執(zhí)行時(shí)間有關(guān)。
相關(guān)推薦:
五道典型的javascript面試題
3 個(gè) Javascript面試中需要注意的問(wèn)題
五個(gè)典型的javascript面試題
以上就是Javascript面試基礎(chǔ)知識(shí)題分享的詳細(xì)內(nèi)容,更多請(qǐng)關(guān)注php中文網(wǎng)其它相關(guān)文章!