根據StackOverflow調查, 自2014年一來,JavaScript是最受歡迎的程式語言。當然,這也在情理之中,畢竟1/3的開發工作都需要一些JavaScript知識。因此,如果你希望在成為一個開發者,你應該學會這門語言。
這篇部落格的主要目的是將所有面試中常見的概念總結,方便你快速去了解。 (鑑於本文內容過長,方便閱讀,將分為三篇博客來翻譯, 此為第三部分。第一部分請點擊快速掌握JavaScript面試基礎知識(一))
#推薦相關文章:2020年最全js面試題整理(最新)
如果使用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。
一個物件有兩種屬性,分別是它本身定義的和繼承的。
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
Eric Elliott的文章有非常詳細的介紹:Master the JavaScript Interview: What's the Difference Between Class & Prototypal Inheritance?
作者認為原型繼承是優於經典的繼承的,並提供了一個視頻介紹:https://www.youtube.com/watch...
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面試基礎知識題分享的詳細內容。更多資訊請關注PHP中文網其他相關文章!