爲了說明 JS 面試的複雜性,首先,請嘗試給出如下結果:javascript
onsole.log(2.0 == 「2」 == new Boolean(true) == 「1」)
十有八九的會給出false, 其實運行結果是true,緣由請看 這裏。html
函數是 JavaScript 的精華,是 JS 一等公民。JS 函數不只僅是一個普通的函數,與其餘語言不一樣,JS 函數能夠賦值給變量,做爲參數傳遞給另外一個函數,也能夠從另外一個函數返回。java
console.log(square(5)); /* ... */ function square(n) { return n * n; }
覺得代碼很簡單,你們應該都知道會打印:25
。接着看一個:node
console.log(square(5)); var square = function(n) { return n * n; }
乍一看,你可能會忍不住說也打印了 25
。但很不幸,會報錯:git
TypeError: square is not a function
在 JavaScript 中,若是將函數定義爲變量,變量名將被提高,是 JS 執行到它的定義才能被訪問。es6
你可能在一些代碼中頻繁的見到以下代碼。github
var simpleLibrary = function() { var simpleLibrary = { a, b, add: function(a, b) { return a + b; }, subtract: function(a, b) { return a - b; } } return simpleLibrary; }();
爲何會作這種奇怪的事情? 這是由於一個函數變量中變量和函數被分裝,能夠避免全局變量污染。 JQuery 到Lodash 的庫採用這種技術提供 $
、_
等面試
你可能在全部經常使用庫中看到過這三個函數。它們容許局部套用, 咱們能夠把功能組合到不一樣的函數。一個優秀的js開發者能夠隨時告訴你關於這三個函數。ajax
基本上,這些是改變行爲以實現某些功能的原型方法,根據 JS 開發人員 Chad 的說法,用法以下:正則表達式
但願使用某個上下文調用該函數,請使用 .bind()
,這在事件中頗有用。 若是要當即調用函數,請使用.call()
或 .apply()
,並修改上下文。
讓咱們看看上面的陳述是什麼意思! 假設你的數學老師要求你建立一個庫並提交。你寫了一個抽象的庫,它能夠求出圓的面積和周長:
var mathLib = { pi: 3.14, area: function(r) { return this.pi * r * r; }, circumference: function(r) { return 2 * this.pi * r; } };
提交後,老師調用了它:
mathLib.area(2); 12.56
老師發現他給你要求是 pi
精確到小數點後 5
位數而你只精確到 2
位, 如今因爲最後期限已過你沒有機會提交庫。 這裏 JS的 call
函數能夠幫你, 只須要調用你的代碼以下:
mathLib.area.call({pi: 3.1.159}, 2)
它會動態地獲取新的 pi
值,結果以下:
12.56636
這時,注意到 call
函數具備兩個參數:
在 area
函數中, 上下文是對象被關鍵詞 this
代替,後面的參數做爲函數參數被傳遞。 以下:
var cylinder = { pi: 3.14, volume: function(r, h) { return this.pi * r * r * h; } };
調用方式以下:
cylinder.volume.call({pi: 3.14159}, 2, 6); 75.39815999999999
Apply 相似,只是函數參數做爲數組傳遞。
cylinder.volume.apply({pi: 3.14159}, [2, 6]); 75.39815999999999
若是你會使用 call 你基本就會用 apply 了,反之亦然, 那 bind 的用法又是如何呢 ?
bind 將一個全新的 this 注入到指定的函數上,改變 this 的指向, 使用 bind 時,函數不會像 call
或 apply
當即執行。
var newVolume = cylinder.volume.bind({pi: 3.14159}); newVolume(2,6); // Now pi is 3.14159
bind 用途是什麼?它容許咱們將上下文注入一個函數,該函數返回一個具備更新上下文的新函數。這意味着這個變量將是用戶提供的變量,這在處理 JavaScript 事件時很是有用。
JavaScript 的做用域是一個潘多拉盒子。從這一個簡單的概念中,就能夠構造出數百個難回答的面試問題。有三種做用域:
全局做用域事例以下:
x = 10; function Foo() { console.log(x); // Prints 10 } Foo()
函數做用域生效當你定義一個局部變量時:
pi = 3.14; function circumference(radius) { pi = 3.14159; console.log(2 * pi * radius); // 打印 "12.56636" 不是 "12.56" } circumference(2);
ES16 標準引入了新的塊做用域,它將變量的做用域限制爲給定的括號塊。
var a = 10; function Foo() { if (true) { let a = 4; } alert(a); // alerts '10' because the 'let' keyword } Foo();
函數和條件都被視爲塊。以上例子應該彈出 4
,由於 if
已執行。但 是ES6 銷燬了塊級變量的做用域,做用域進入全局。
如今來到神奇的做用域,可使用閉包來實現,JavaScript 閉包是一個返回另外一個函數的函數。
若是有人問你這個問題,編寫一個輸入一個字符串並逐次返回字符。 若是給出了新字符串,則應該替換舊字符串,相似簡單的一個生成器。
function generator(input) { var index = 0; return { next: function() { if (index < input.lenght) { return input[index -1]; } return ""; } } }
執行以下:
var mygenerator = generator("boomerang"); mygenerator.next(); // returns "b" mygenerator.next() // returns "o" mygenerator = generator("toon"); mygenerator.next(); // returns "t"
在這裏,做用域扮演着重要的角色。閉包是返回另外一個函數並攜帶數據的函數。上面的字符串生成器適用於閉包。index 在多個函數調用之間保留,定義的內部函數能夠訪問在父函數中定義的變量。這是一個不一樣的做用域。若是在第二級函數中再定義一個函數,它能夠訪問全部父級變量。
在 JavaScript 中,咱們老是用函數和對象編寫代碼, 若是使用瀏覽器,則在全局上下文中它引用 window 對象。 個人意思是,若是你如今打開瀏覽器控制檯並輸入如下代碼,輸出結果爲 true。
this === window;
當程序的上下文和做用域發生變化時,this 也會發生相應的變化。如今觀察 this 在一個局部上下文中:
function Foo(){ console.log(this.a); } var food = {a: "Magical this"}; Foo.call(food); // food is this
思考一下,如下輸出的是什麼:
function Foo(){ console.log(this); // 打印 {}? }
由於這是一個全局對象,記住,不管父做用域是什麼,它都將由子做用域繼承。打印出來是 window 對象。上面討論的三個方法實際上用於設置這個對象。
如今,this 的最後一個類型,在對象中的 this, 以下:
var person = { name: "Stranger", age: 24, get identity() { return {who: this.name, howOld: this.age}; } }
上述使用了 getter 語法,這是一個能夠做爲變量調用的函數。
person.identity; // returns {who: "Stranger", howOld: 24}
此時,this 其實是指對象自己。正如咱們前面提到的,它在不一樣的地方有不一樣的表現。
一般對象的格式以下:
var marks = {physics: 98, maths:95, chemistry: 91};
它是一個存儲鍵、值對的映射。 javascript 對象有一個特殊的屬性,能夠將任何東西存儲爲一個值。這意味着咱們能夠將一個列表、另外一個對象、一個函數等存儲爲一個值。
能夠用以下方式來建立對象:
var marks = {}; var marks = new Object();
可使用 JSON.stringify() 將一個對象轉製成字符串,也能夠用 JSON.parse 在將其轉成對象。
// returns "{"physics":98,"maths":95,"chemistry":91}" JSON.stringify(marks); // Get object from string JSON.parse('{"physics":98,"maths":95,"chemistry":91}');
使用 Object.keys 迭代對象:
var highScere = 0; for (i of Object.keys(marks)) { if (marks[i] > highScore) highScore = marks[i]; }
Object.values 以數組的方式返回對象的值。
對象上的其餘重要函數有:
Object.prototype 上提供了許多應用上相關的函數,以下:
Object.prototype.hasOwnProperty 用於檢查給定的屬性/鍵是否存在於對象中。
marks.hasOwnProperty("physics"); // returns true marks.hasOwnProperty("greek"); // returns false
Object.prototype.instanceof 判斷給定對象是不是特定原型的類型。
function Car(make, model, year) { this.make = make; this.model = model; this.year = year; } var newCar = new Car('Honda', 'City', 2007); console.log(newCar instanceof Car); // returns true
使用 Object.freeze 能夠凍結對象,以便不能修改對象現有屬性。
var marks = {physics: 98, maths:95, chemistry: 91}; finalizedMarks = Object.freeze(marks); finalizedMarks["physics"] = 86; // throws error in strict mode console.log(marks); // {physics: 98, maths: 95, chemistry: 91}
在這裏,試圖修改凍結後的 physics
的值,但 JavaScript不容許這樣作。咱們可使用 Object.isFrozen 來判斷,給定對象是否被凍結:
Object.isFrozen(finalizedMarks); // returns true
Object.seal 與 Object.freeze 略有不一樣。 Object.seal() 方法封閉一個對象,阻止添加新屬性並將全部現有屬性標記爲不可配置。當前屬性的值只要可寫就能夠改變。
var marks = {physics: 98, maths:95, chemistry: 91}; Object.seal(marks); delete marks.chemistry; // returns false as operation failed marks.physics = 95; // Works! marks.greek = 86; // Will not add a new property
一樣, 可使用 Object.isSealed 判斷對象是否被密封。
Object.isSealed(marks); // returns true
在全局對象函數上還有許多其餘重要的函數/方法,在這裏找到他們。
在傳統 JavaScript 中,有一種假裝的繼承概念,它是經過使用原型技術來實現的。在ES五、ES6中看到使用 new 的語法只是底層原型OOP的語法糖。建立類是使用 JavaScript 中的函數完成的。
var animalGroups = { MAMMAL: 1, REPTILE: 2, AMPHIBIAN: 3, INVERTEBRATE: 4 }; function Animal(name, type) { this.name = name; this.type = type; } var dog = new Animal("dog", animalGroups.MAMMAL); var crocodile = new Animal("crocodile", animalGroups.REPTILE);
這裏咱們爲類建立對象(使用 new 關鍵字),可使用以下方式對類追加方法:
Animal.prototype.shout = function() { console.log(this.name+'is'+this.sound+'ing...'); }
這裏你可能會有疑問。類中並沒 sound 屬性。是的,它打算由繼承了上述類的子類傳遞。
JavaScript中, 以下實現繼承:
function Dog(name, type) {
Animal.call(this, name, type);
this.sound = 'bow';
}
我定義了一個更具體的函數,叫作 Dog。在這裏,爲了繼承 Animal 類,我須要call傳遞this和其餘參數。使用以下方式來實例化一隻德國牧羊犬
。
var pet = Dog("德國牧羊犬", animalGroups.MAMMAL); console.log(pet); // returns Dog {name: "德國牧羊犬", type: 1, sound: "bow"}
咱們沒有在子函數中分配 name
和 type
屬性,咱們調用的是超級函數 Animal 並設置相應的屬性。pet 具備父類的屬性(name、type)。可是方法呢。他們也繼承的嗎? 來看看:
pet.shout(); // Throws error
爲何會這樣? 之因此發生這種狀況,是由於沒有指定讓 JavaScript來繼承父類方法。 如何解決?
// Link prototype chains Dog.prototype = Object.create(Animal.prototype); var pet = new Dog("germanShepard", animalGroups.MAMMAL); // Now shout method is available pet.shout(); // 德國牧羊犬 bowing...
如今可使用 shout
方法。 咱們可使用 object.constructor 函數檢查 JavaScript 中給定對象的類 來看看 pet 是什麼類:
pet.constructor; // returns Animal
這是模糊的,Animal 是一個父類。可是 pet 究竟是什麼類型的呢? pet 應該是 Dog
的類型。之因此是 Animal 類型,是由於 Dog 類的構造函數:
Dog.prototype.constructor; // returns Animal
它是 Animal 類型的。咱們應該將它設置爲 Dog 自己,這樣類的全部實例(對象)才能給出正確的類名。
Dog.prototype.constructor = Dog;
關於原型繼承, 咱們應該記住如下幾條:
this
綁定prototype
對象來綁定call
函數來傳遞 this
回調是在 I/O 操做完成後執行的函數。一個耗時的I/O操做會阻塞代碼, 所以在Python/Ruby不被容許。可是在 JavaScript中,因爲容許異步執行,咱們能夠提供對異步函數的回調。這個例子是由瀏覽器到服務器的AJAX(XMLHettpRequest)調用,由鼠標、鍵盤事件生成。以下:
function reqListener () { console.log(this.responseText); } var req = new XMLHttpRequest(); req.addEventListener("load", reqListener); req.open("GET", "http://www.example.org/example.txt"); req.send();
這裏的 reqListener 是一個回調函數,當成功響應 GET 請求時將執行該回調函數。
Promise 是回調函數的優雅的封裝, 使得咱們優雅的實現異步代碼。在如下給出的這篇文章中討論了不少 promise,這也是在 JS 中應該知道的重要部分。
Writing neat asynchronous Node JS code with Promises
正則表達式有許多應用地方,處理文本、對用戶輸入執行規則等。JavaScript 開發人員應該知道如何執行基本正則表達式並解決問題。Regex 是一個通用概念,來看看如何從 JS 中作到這一點。
建立正則表達式,有以下兩種方式:
var re = /ar/; var re = new RegExp('ar');
上面的正則表達式是與給定字符串集匹配的表達式。定義正則表達式以後,咱們能夠嘗試匹配並查看匹配的字符串。可使用 exec 函數匹配字符串:
re.exec("car"); // returns ["ar", index: 1, input: "car"] re.exec("cab"); // returns null
有一些特殊的字符類容許咱們編寫複雜的正則表達式。RegEx 中有許多類型的元素,其中一些以下:
\w
-字母數字, \d
- 數字, \D
- 沒有數字x-y]
x-y區間, [^x]
沒有x+
至少一個、?
沒或多個、*
多個^
開始、$
結尾例子以下:
/* Character class */ var re1 = /[AEIOU]/; re1.exec("Oval"); // returns ["O", index: 0, input: "Oval"] re1.exec("2456"); // null var re2 = /[1-9]/; re2.exec('mp4'); // returns ["4", index: 2, input: "mp4"] /* Characters */ var re4 = /\d\D\w/; re4.exec('1232W2sdf'); // returns ["2W2", index: 3, input: "1232W2sdf"] re4.exec('W3q'); // returns null /* Boundaries */ var re5 = /^\d\D\w/; re5.exec('2W34'); // returns ["2W3", index: 0, input: "2W34"] re5.exec('W34567'); // returns null var re6 = /^[0-9]{5}-[0-9]{5}-[0-9]{5}$/; re6.exec('23451-45242-99078'); // returns ["23451-45242-99078", index: 0, input: "23451-45242-99078"] re6.exec('23451-abcd-efgh-ijkl'); // returns null /* Quantifiers */ var re7 = /\d+\D+$/; re7.exec('2abcd'); // returns ["2abcd", index: 0, input: "2abcd"] re7.exec('23'); // returns null re7.exec('2abcd3'); // returns null var re8 = /<([\w]+).*>(.*?)<\/\1>/; re8.exec('<p>Hello JS developer</p>'); //returns ["<p>Hello JS developer</p>", "p", "Hello JS developer", index: 0, input: "<p>Hello JS developer</p>"]
有關 regex 的詳細信息,能夠看 這裏。
除了 exec 以外,還有其餘函數,即 match、search 和 replace,可使用正則表達式在另外一個字符串中查找字符串,可是這些函數在字符串自己上使用。
"2345-678r9".match(/[a-z A-Z]/); // returns ["r", index: 8, input: "2345-678r9"] "2345-678r9".replace(/[a-z A-Z]/, ""); // returns 2345-6789
Regex 是一個重要的主題,開發人員應該理解它,以便輕鬆解決複雜的問題。
函數式編程是當今的一個熱門討論話題。許多編程語言都在新版本中包含了函數概念,好比 lambdas(例如:Java >7)。在 JavaScrip t中,函數式編程結構的支持已經存在很長時間了。咱們須要深刻學習三個主要函數。數學函數接受一些輸入和返回輸出。純函數都是給定的輸入返回相同的輸出。咱們如今討論的函數也知足純度。
map 函數在 JavaScript 數組中可用,使用這個函數,咱們能夠經過對數組中的每一個元素應用一個轉換函數來得到一個新的數組。map 通常語法是:
arr.map((elem){ process(elem) return processedValue }) // returns new array with each element processed
假設,在咱們最近使用的串行密鑰中輸入了一些不須要的字符,須要移除它們。此時可使用 map 來執行相同的操做並獲取結果數組,而不是經過迭代和查找來刪除字符。
var data = ["2345-34r", "2e345-211", "543-67i4", "346-598"]; var re = /[a-z A-Z]/; var cleanedData = data.map((elem) => {return elem.replace(re, "")}); console.log(cleanedData); // ["2345-34", "2345-211", "543-674", "346-598"]
map 接受一個做爲參數的函數, 此函數接受一個來自數組的參數。咱們須要返回一個處理過的元素, 並應用於數組中的全部元素。
reduce 函數將一個給定的列表整理成一個最終的結果。經過迭代數組執行相同的操做, 並保存中間結果到一個變量中。這裏是一個更簡潔的方式進行處理。js 的 reduce 通常使用語法以下:
arr.reduce((accumulator, currentValue, currentIndex) => { process(accumulator, currentValue) return intermediateValue/finalValue }, initialAccumulatorValue) // returns reduced value
accumulator 存儲中間值和最終值。currentIndex、currentValue分別是數組中元素的 index 和 value。initialAccumulatorValue 是 accumulator 初始值。
reduce 的一個實際應用是將一個數組扁平化, 將內部數組轉化爲單個數組, 以下:
var arr = [[1, 2], [3, 4], [5, 6]]; var flattenedArray = [1, 2, 3, 4, 5, 6];
咱們能夠經過正常的迭代來實現這一點,可是使用 reduce,代碼會更加簡潔。
var flattenedArray = arr.reduce((accumulator, currentValue) => { return accumulator.concat(currentValue); }, []); // returns [1, 2, 3, 4, 5, 6]
filter 與 map 更爲接近, 對數組的每一個元素進行操做並返回另一個數組(不一樣於 reduce 返回的值)。過濾後的數組可能比原數組長度更短,由於經過過濾條件,排除了一些咱們不須要的。
filter 語法以下:
arr.filter((elem) => { return true/false })
elem 是數組中的元素, 經過 true/false
表示過濾元素保存/排除。假設, 咱們過濾出以 t
開始以 r
結束的元素:
var words = ["tiger", "toast", "boat", "tumor", "track", "bridge"] var newData = words.filter((str) => { return str.startsWith('t') && str.endsWith('r'); }) newData // (2) ["tiger", "tumor"]
當有人問起JavaScript的函數編程方面時,這三個函數應該信手拈來。 如你所見,原始數組在全部三種狀況下都沒有改變,這證實了這些函數的純度。
這是許多開發人員最不關心的 JavaScript。 我看到不多有開發人員談論錯誤處理, 一個好的開發方法老是謹慎地將 JS 代碼封裝裝在 try/catch
塊周圍。
在 JavaScript中,只要咱們隨意編寫代碼,就可能會失敗,若是所示:
$("button").click(function(){ $.ajax({url: "user.json", success: function(result){ updateUI(result["posts"]); }}); });
這裏,咱們陷入了一個陷阱,咱們說 result 老是 JSON 對象。但有時服務器會崩潰,返回的是 null 而不是 result。在這種狀況下,null["posts"]
將拋出一個錯誤。正確的處理方式多是這樣的:
$("button").click(function(){ $.ajax({url: "user.json", success: function(result){ try { updateUI(result["posts"]); } catch(e) { // Custom functions logError(); flashInfoMessage(); } }}); });
logError 函數用於向服務器報告錯誤。flashInfoMessage 是顯示用戶友好的消息,如「當前不可用的服務」等。
Nicholas 說,當你以爲有什麼意想不到的事情將要發生時,手動拋出錯誤。區分致命錯誤和非致命錯誤。以上錯誤與後端服務器宕機有關,這是致命的。在那裏,應該通知客戶因爲某種緣由服務中斷了。
在某些狀況下,這可能不是致命的,但最好通知服務器。爲了建立這樣的代碼,首先拋出一個錯誤,, 從 window 層級捕捉錯誤事件,而後調用API將該消息記錄到服務器。
reportErrorToServer = function (error) { $.ajax({type: "POST", url: "http://api.xyz.com/report", data: error, success: function (result) {} }); } // Window error event window.addEventListener('error', function (e) { reportErrorToServer({message: e.message}) })} function mainLogic() { // Somewhere you feel like fishy throw new Error("user feeds are having fewer fields than expected..."); }
這段代碼主要作三件事:
你也可使用新的 Boolean 函數(es5,es6)在程序以前監測變量的有效性而且不爲null、undefined
if (Boolean(someVariable)) { // use variable now } else { throw new Error("Custom message") }
始終考慮錯誤處理是你本身, 而不是瀏覽器。
以上全部概念都是 JavaScript 開發人員的須要知道基本概念。有一些內部細節須要知道,這些對你會有很在幫助。 這些是JavaScript引擎在瀏覽器中的工做方式,什麼是提高機制和事件冒泡?
變量提高是 在代碼執行過程當中將聲明的變量的做用域提高到全局做用哉中的一個過程,如:
doSomething(foo); // used before var foo; // declared later
當在 Python 這樣的腳本語言中執行上述操做時,它會拋出一個錯誤,由於須要先定義而後才能使用它。儘管 JS 是一種腳本語言,但它有一種提高機制,在這種機制中,JavaScript VM 在運行程序時作兩件事:
undefined
在上面的代碼片斷中,console.log
打印 「undefined」
。 這是由於在第一次傳遞變量 foo 被收集。 JS 虛擬機 查找爲變量 foo 定義的任何值。 這種提高可能致使許多JavaScript 在某些地方拋出錯誤,和另外地方使用 undefined
。
學習一些 例子 來搞清楚提高。
如今事件開始冒泡了! 根據高級軟件工程師 Arun P的說法:
「當事件發生在另外一個元素內的元素中時,事件冒泡和捕獲是 HTML DOM API 中事件傳播的兩種方式,而且這兩個元素都已爲該事件註冊了處理程序,事件傳播模式肯定元素接收事件的順序。「
經過冒泡,事件首先由最內部的元素捕獲和處理,而後傳播到外部元素。對於捕獲,過程是相反的。咱們一般使用addEventListener 函數將事件附加處處理程序。
addEventListener("click", handler, useCapture=false)
useCapture 是第三個參數的關鍵詞, 默認爲 false
。所以, 冒泡模式是事件由底部向上傳遞。 反之, 這是捕獲模式。
冒泡模式:
<div onClick="divHandler()"> <ul onClick="ulHandler"> <li id="foo"></li> </ul> </div> <script> function handler() { // do something here } function divHandler(){} function ulHandler(){} document.getElementById("foo").addEventListener("click", handler) </script>
點擊li元素, 事件順序:
handler() => ulHandler() => divHandler()
在圖中,處理程序按順序向外觸發。相似地,捕獲模型試圖將事件從父元素向內觸發到單擊的元素。如今更改上面代碼中的這一行。
document.getElementById("foo").addEventListener("click", handler, true)
事件順序:
divHandler => ulHandler() => handler()
你應該正確地理解事件冒泡(不管方向是指向父節點仍是子節點),以實現用戶界面(UI),以免任何不須要的行爲。
這些是 JavaScrip t中的基本概念。正如我最初提到的,除了工做經驗和知識以外,準備有助理於你經過 JavaScript 面試。始終保持學習。留意最新的發展(第六章)。深刻了解JavaScript的各個方面,如 V6 引擎、測試等。最後,沒有掌握數據結構和算法的面試是不成功的。Oleksii Trekhleb 策劃了一個很棒的 git repo,它包含了全部使用 JS 代碼的面試準備算法。
代碼部署後可能存在的BUG無法實時知道,過後爲了解決這些BUG,花了大量的時間進行log 調試,這邊順便給你們推薦一個好用的BUG監控工具 Fundebug。
原文:
https://medium.com/dev-bits/a...
你的點贊是我持續分享好東西的動力,歡迎點贊!
一個笨笨的碼農,個人世界只能終身學習!
更多內容請關注公衆號《大遷世界》!