Javascript面試的完美指南(開發(fā)者視角)
為了說明 JS 面試的復雜性,首先,請嘗試給出以下結果:
- onsole.log(2.0 == “2” == new Boolean(true) == “1”)
 
十有八九的會給出false, 其實運行結果是true,原因請看 這里。
1) 理解 JS 函數(shù)
函數(shù)是 JavaScript 的精華,是 JS 一等公民。JS 函數(shù)不僅僅是一個普通的函數(shù),與其他語言不同,JS 函數(shù)可以賦值給變量,作為參數(shù)傳遞給另一個函數(shù),也可以從另一個函數(shù)返回。
- console.log(square(5));
 - /* ... */
 - function square(n) { return n * n; }
 
以為代碼很簡單,大家應該都知道會打?。?5。接著看一個:
- console.log(square(5));
 - var square = function(n) {
 - return n * n;
 - }
 
乍一看,你可能會忍不住說也打印了 25。但很不幸,會報錯:
- TypeError: square is not a function
 
在 JavaScript 中,如果將函數(shù)定義為變量,變量名將被提升,是 JS 執(zhí)行到它的定義才能被訪問。
你可能在一些代碼中頻繁的見到如下代碼。
- var simpleLibrary = function() {
 - var simpleLibrary = {
 - a,
 - b,
 - add: function(a, b) {
 - return a + b;
 - },
 - subtract: function(a, b) {
 - return a - b;
 - }
 - }
 - return simpleLibrary;
 - }();
 
為什么會做這種奇怪的事情? 這是因為一個函數(shù)變量中變量和函數(shù)被分裝,可以避免全局變量污染。 JQuery 到Lodash 的庫采用這種技術提供 $、_ 等
2) 理解 bind、apply 和 call
你可能在所有常用庫中看到過這三個函數(shù)。它們允許局部套用, 我們可以把功能組合到不同的函數(shù)。一個優(yōu)秀的js開發(fā)者可以隨時告訴你關于這三個函數(shù)。
基本上,這些是改變行為以實現(xiàn)某些功能的原型方法,根據(jù) JS 開發(fā)人員 Chad 的說法,用法如下:
希望使用某個上下文調用該函數(shù),請使用 .bind() ,這在事件中很有用。 如果要立即調用函數(shù),請使用.call() 或 .apply(),并修改上下文。
舉例說明
讓我們看看上面的陳述是什么意思! 假設你的數(shù)學老師要求你創(chuàng)建一個庫并提交。你寫了一個抽象的庫,它可以求出圓的面積和周長:
- 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
 
老師發(fā)現(xiàn)他給你要求是 pi 精確到小數(shù)點后 5 位數(shù)而你只精確到 2 位, 現(xiàn)在由于***期限已過你沒有機會提交庫。 這里 JS的 call 函數(shù)可以幫你, 只需要調用你的代碼如下:
- mathLib.area.call({pi: 3.1.159}, 2)
 
它會動態(tài)地獲取新的 pi 值,結果如下:
- 12.56636
 
這時,注意到 call 函數(shù)具有兩個參數(shù):
- Context
 - 函數(shù)參數(shù)
 
在 area 函數(shù)中, 上下文是對象被關鍵詞 this 代替,后面的參數(shù)作為函數(shù)參數(shù)被傳遞。 如下:
- 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 類似,只是函數(shù)參數(shù)作為數(shù)組傳遞。
- cylinder.volume.apply({pi: 3.14159}, [2, 6]);
 - 75.39815999999999
 
如果你會使用 call 你基本就會用 apply 了,反之亦然, 那 bind 的用法又是如何呢 ?
bind 將一個全新的 this 注入到指定的函數(shù)上,改變 this 的指向, 使用 bind 時,函數(shù)不會像 call 或 apply 立即執(zhí)行。
- var newVolume = cylinder.volume.bind({pi: 3.14159});
 - newVolume(2,6); // Now pi is 3.14159
 
bind 用途是什么?它允許我們將上下文注入一個函數(shù),該函數(shù)返回一個具有更新上下文的新函數(shù)。這意味著這個變量將是用戶提供的變量,這在處理 JavaScript 事件時非常有用。
3) 理解 js 作用域(閉包)
JavaScript 的作用域是一個潘多拉盒子。從這一個簡單的概念中,就可以構造出數(shù)百個難回答的面試問題。有三種作用域:
- 全局作用域
 - 本地/函數(shù)作用域
 - 塊級作用域(ES6引進)
 
全局作用域事例如下:
- x = 10;
 - function Foo() {
 - console.log(x); // Prints 10
 - }
 - Foo()
 
函數(shù)作用域生效當你定義一個局部變量時:
- 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();
 
函數(shù)和條件都被視為塊。以上例子應該彈出 4,因為 if 已執(zhí)行。但 是ES6 銷毀了塊級變量的作用域,作用域進入全局。
現(xiàn)在來到神奇的作用域,可以使用閉包來實現(xiàn),JavaScript 閉包是一個返回另一個函數(shù)的函數(shù)。
如果有人問你這個問題,編寫一個輸入一個字符串并逐次返回字符。 如果給出了新字符串,則應該替換舊字符串,類似簡單的一個生成器。
- function generator(input) {
 - var index = 0;
 - return {
 - next: function() {
 - if (index < input.lenght) {
 - return input[index -1];
 - }
 - return "";
 - }
 - }
 - }
 
執(zhí)行如下:
- var mygenerator = generator("boomerang");
 - mygenerator.next(); // returns "b"
 - mygenerator.next() // returns "o"
 - mygenerator = generator("toon");
 - mygenerator.next(); // returns "t"
 
在這里,作用域扮演著重要的角色。閉包是返回另一個函數(shù)并攜帶數(shù)據(jù)的函數(shù)。上面的字符串生成器適用于閉包。index 在多個函數(shù)調用之間保留,定義的內部函數(shù)可以訪問在父函數(shù)中定義的變量。這是一個不同的作用域。如果在第二級函數(shù)中再定義一個函數(shù),它可以訪問所有父級變量。
4) this (全局域、函數(shù)域、對象域)
在 JavaScript 中,我們總是用函數(shù)和對象編寫代碼, 如果使用瀏覽器,則在全局上下文中它引用 window 對象。 我的意思是,如果你現(xiàn)在打開瀏覽器控制臺并輸入以下代碼,輸出結果為 true。
- this === window;
 
當程序的上下文和作用域發(fā)生變化時,this 也會發(fā)生相應的變化。現(xiàn)在觀察 this 在一個局部上下文中:
- function Foo(){
 - console.log(this.a);
 - }
 - var food = {a: "Magical this"};
 - Foo.call(food); // food is this
 
思考一下,以下輸出的是什么:
- function Foo(){
 - console.log(this); // 打印 {}?
 - }
 
因為這是一個全局對象,記住,無論父作用域是什么,它都將由子作用域繼承。打印出來是 window 對象。上面討論的三個方法實際上用于設置這個對象。
現(xiàn)在,this 的***一個類型,在對象中的 this, 如下:
- var person = {
 - name: "Stranger",
 - age: 24,
 - get identity() {
 - return {who: this.name, howOld: this.age};
 - }
 - }
 
上述使用了 getter 語法,這是一個可以作為變量調用的函數(shù)。
- person.identity; // returns {who: "Stranger", howOld: 24}
 
此時,this 實際上是指對象本身。正如我們前面提到的,它在不同的地方有不同的表現(xiàn)。
5) 理解對象 (Object.freeze, Object.seal)
通常對象的格式如下:
- var marks = {physics: 98, maths:95, chemistry: 91};
 
它是一個存儲鍵、值對的映射。 javascript 對象有一個特殊的屬性,可以將任何東西存儲為一個值。這意味著我們可以將一個列表、另一個對象、一個函數(shù)等存儲為一個值。
可以用如下方式來創(chuàng)建對象:
- 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 以數(shù)組的方式返回對象的值。
對象上的其他重要函數(shù)有:
- Object.prototype(object)
 - Object.freeze(function)
 - Object.seal(function)
 
Object.prototype 上提供了許多應用上相關的函數(shù),如下:
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 可以凍結對象,以便不能修改對象現(xiàn)有屬性。
- 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() 方法封閉一個對象,阻止添加新屬性并將所有現(xiàn)有屬性標記為不可配置。當前屬性的值只要可寫就可以改變。
- 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
 
在全局對象函數(shù)上還有許多其他重要的函數(shù)/方法,在這里找到他們。
6) 理解原型繼承
在傳統(tǒng) JavaScript 中,有一種偽裝的繼承概念,它是通過使用原型技術來實現(xiàn)的。在ES5、ES6中看到使用 new 的語法只是底層原型OOP的語法糖。創(chuàng)建類是使用 JavaScript 中的函數(shù)完成的。
- 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);
 
這里我們?yōu)轭悇?chuàng)建對象(使用 new 關鍵字),可以使用如下方式對類追加方法:
- Animal.prototype.shout = function() {
 - console.log(this.name+'is'+this.sound+'ing...');
 - }
 
這里你可能會有疑問。類中并沒 sound 屬性。是的,它打算由繼承了上述類的子類傳遞。
JavaScript中, 如下實現(xiàn)繼承:
- function Dog(name, type) {
 - Animal.call(this, name, type);
 - this.sound = 'bow';
 - }
 
我定義了一個更具體的函數(shù),叫做 Dog。在這里,為了繼承 Animal 類,我需要call傳遞this和其他參數(shù)。使用如下方式來實例化一只德國牧羊犬。
- var pet = Dog("德國牧羊犬", animalGroups.MAMMAL);
 - console.log(pet); // returns Dog {name: "德國牧羊犬", type: 1, sound: "bow"}
 
我們沒有在子函數(shù)中分配 name 和 type 屬性,我們調用的是超級函數(shù) Animal 并設置相應的屬性。pet 具有父類的屬性(name、type)。但是方法呢。他們也繼承的嗎? 來看看:
- pet.shout(); // Throws error
 
為什么會這樣? 之所以發(fā)生這種情況,是因為沒有指定讓 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...
 
現(xiàn)在可以使用 shout 方法。 我們可以使用 object.constructor 函數(shù)檢查 JavaScript 中給定對象的類 來看看 pet 是什么類:
- pet.constructor; // returns Animal
 
這是模糊的,Animal 是一個父類。但是 pet 到底是什么類型的呢? pet 應該是 Dog 的類型。之所以是 Animal 類型,是因為 Dog 類的構造函數(shù):
- Dog.prototype.constructor; // returns Animal
 
它是 Animal 類型的。我們應該將它設置為 Dog 本身,這樣類的所有實例(對象)才能給出正確的類名。
- Dog.prototype.constructor = Dog;
 
關于原型繼承, 我們應該記住以下幾條:
- 類屬性使用 this 綁定
 - 類方法使用 prototype 對象來綁定
 - 為了繼承屬性, 使用 call 函數(shù)來傳遞 this
 - 為了繼承方法, 使用 Object.create 連接父和子的原型
 - 始終將子類構造函數(shù)設置為自身,以獲得其對象的正確類型
 
7)理解 callback 和 promise
回調是在 I/O 操作完成后執(zhí)行的函數(shù)。一個耗時的I/O操作會阻塞代碼, 因此在Python/Ruby不被允許。但是在 JavaScript中,由于允許異步執(zhí)行,我們可以提供對異步函數(shù)的回調。這個例子是由瀏覽器到服務器的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 是一個回調函數(shù),當成功響應 GET 請求時將執(zhí)行該回調函數(shù)。
Promise 是回調函數(shù)的優(yōu)雅的封裝, 使得我們優(yōu)雅的實現(xiàn)異步代碼。在以下給出的這篇文章中討論了很多 promise,這也是在 JS 中應該知道的重要部分。
8)理解正則表達
正則表達式有許多應用地方,處理文本、對用戶輸入執(zhí)行規(guī)則等。JavaScript 開發(fā)人員應該知道如何執(zhí)行基本正則表達式并解決問題。Regex 是一個通用概念,來看看如何從 JS 中做到這一點。
創(chuàng)建正則表達式,有如下兩種方式:
- var re = /ar/;
 - var re = new RegExp('ar');
 
上面的正則表達式是與給定字符串集匹配的表達式。定義正則表達式之后,我們可以嘗試匹配并查看匹配的字符串??梢允褂?exec 函數(shù)匹配字符串:
- re.exec("car"); // returns ["ar", index: 1, input: "car"]
 - re.exec("cab"); // returns null
 
有一些特殊的字符類允許我們編寫復雜的正則表達式。RegEx 中有許多類型的元素,其中一些如下:
- 字符正則:\w-字母數(shù)字, \d- 數(shù)字, \D- 沒有數(shù)字
 - 字符類正則:[x-y] x-y區(qū)間, [^x] 沒有x
 - 數(shù)量正則:+ 至少一個、? 沒或多個、* 多個
 - 邊界正則,^ 開始、$ 結尾
 
例子如下:
- /* 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>"]
 
除了 exec 之外,還有其他函數(shù),即 match、search 和 replace,可以使用正則表達式在另一個字符串中查找字符串,但是這些函數(shù)在字符串本身上使用。
- "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 是一個重要的主題,開發(fā)人員應該理解它,以便輕松解決復雜的問題。
9)理解 map、reduce 和 filter
函數(shù)式編程是當今的一個熱門討論話題。許多編程語言都在新版本中包含了函數(shù)概念,比如 lambdas(例如:Java >7)。在 JavaScrip t中,函數(shù)式編程結構的支持已經存在很長時間了。我們需要深入學習三個主要函數(shù)。數(shù)學函數(shù)接受一些輸入和返回輸出。純函數(shù)都是給定的輸入返回相同的輸出。我們現(xiàn)在討論的函數(shù)也滿足純度。
map
map 函數(shù)在 JavaScript 數(shù)組中可用,使用這個函數(shù),我們可以通過對數(shù)組中的每個元素應用一個轉換函數(shù)來獲得一個新的數(shù)組。map 一般語法是:
- arr.map((elem){
 - process(elem)
 - return processedValue
 - }) // returns new array with each element processed
 
假設,在我們最近使用的串行密鑰中輸入了一些不需要的字符,需要移除它們。此時可以使用 map 來執(zhí)行相同的操作并獲取結果數(shù)組,而不是通過迭代和查找來刪除字符。
- 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 接受一個作為參數(shù)的函數(shù), 此函數(shù)接受一個來自數(shù)組的參數(shù)。我們需要返回一個處理過的元素, 并應用于數(shù)組中的所有元素。
reduce
reduce 函數(shù)將一個給定的列表整理成一個最終的結果。通過迭代數(shù)組執(zhí)行相同的操作, 并保存中間結果到一個變量中。這里是一個更簡潔的方式進行處理。js 的 reduce 一般使用語法如下:
- arr.reduce((accumulator,
 - currentValue,
 - currentIndex) => {
 - process(accumulator, currentValue)
 - return intermediateValue/finalValue
 - }, initialAccumulatorValue) // returns reduced value
 
accumulator 存儲中間值和最終值。currentIndex、currentValue分別是數(shù)組中元素的 index 和 value。initialAccumulatorValue 是 accumulator 初始值。
reduce 的一個實際應用是將一個數(shù)組扁平化, 將內部數(shù)組轉化為單個數(shù)組, 如下:
- var arr = [[1, 2], [3, 4], [5, 6]];
 - var flattenedArray = [1, 2, 3, 4, 5, 6];
 
我們可以通過正常的迭代來實現(xiàn)這一點,但是使用 reduce,代碼會更加簡潔。
- var flattenedArray = arr.reduce((accumulator, currentValue) => {
 - return accumulator.concat(currentValue);
 - }, []); // returns [1, 2, 3, 4, 5, 6]
 
filter
filter 與 map 更為接近, 對數(shù)組的每個元素進行操作并返回另外一個數(shù)組(不同于 reduce 返回的值)。過濾后的數(shù)組可能比原數(shù)組長度更短,因為通過過濾條件,排除了一些我們不需要的。
filter 語法如下:
- arr.filter((elem) => {
 - return true/false
 - })
 
elem 是數(shù)組中的元素, 通過 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的函數(shù)編程方面時,這三個函數(shù)應該信手拈來。 如你所見,原始數(shù)組在所有三種情況下都沒有改變,這證明了這些函數(shù)的純度。
10) 理解錯誤處理模式
這是許多開發(fā)人員最不關心的 JavaScript。 我看到很少有開發(fā)人員談論錯誤處理, 一個好的開發(fā)方法總是謹慎地將 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 函數(shù)用于向服務器報告錯誤。flashInfoMessage 是顯示用戶友好的消息,如“當前不可用的服務”等。
Nicholas 說,當你覺得有什么意想不到的事情將要發(fā)生時,手動拋出錯誤。區(qū)分致命錯誤和非致命錯誤。以上錯誤與后端服務器宕機有關,這是致命的。在那里,應該通知客戶由于某種原因服務中斷了。
在某些情況下,這可能不是致命的,但***通知服務器。為了創(chuàng)建這樣的代碼,首先拋出一個錯誤,, 從 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...");
 - }
 
這段代碼主要做三件事:
- 監(jiān)聽window層級錯誤
 - 無論何時發(fā)生錯誤,都要調用 API
 - 在服務器中記錄
 
你也可以使用新的 Boolean 函數(shù)(es5,es6)在程序之前監(jiān)測變量的有效性并且不為null、undefined。
- if (Boolean(someVariable)) {
 - // use variable now
 - } else {
 - throw new Error("Custom message")
 - }
 
始終考慮錯誤處理是你自己, 而不是瀏覽器。
其他(提升機制和事件冒泡)
以上所有概念都是 JavaScript 開發(fā)人員的需要知道基本概念。有一些內部細節(jié)需要知道,這些對你會有很在幫助。 這些是JavaScript引擎在瀏覽器中的工作方式,什么是提升機制和事件冒泡?
提升機制
變量提升是 在代碼執(zhí)行過程中將聲明的變量的作用域提升到全局作用哉中的一個過程,如:
- doSomething(foo); // used before
 - var foo; // declared later
 
當在 Python 這樣的腳本語言中執(zhí)行上述操作時,它會拋出一個錯誤,因為需要先定義然后才能使用它。盡管 JS 是一種腳本語言,但它有一種提升機制,在這種機制中,JavaScript VM 在運行程序時做兩件事:
- 首先掃描程序,收集所有的變量和函數(shù)聲明,并為其分配內存空間
 - 通過填充分配的變量來執(zhí)行程序, 沒有分配則填充 undefined
 
在上面的代碼片段中,console.log 打印 “undefined”。 這是因為在***次傳遞變量 foo 被收集。 JS 虛擬機 查找為變量 foo 定義的任何值。 這種提升可能導致許多JavaScript 在某些地方拋出錯誤,和另外地方使用 undefined 。
學習一些 例子來搞清楚提升。
事件冒泡
現(xiàn)在事件開始冒泡了! 根據(jù)高級軟件工程師 Arun P的說法:
“當事件發(fā)生在另一個元素內的元素中時,事件冒泡和捕獲是 HTML DOM API 中事件傳播的兩種方式,并且這兩個元素都已為該事件注冊了處理程序,事件傳播模式確定元素接收事件的順序。“
通過冒泡,事件首先由最內部的元素捕獲和處理,然后傳播到外部元素。對于捕獲,過程是相反的。我們通常使用addEventListener 函數(shù)將事件附加到處理程序。
- addEventListener("click", handler, useCapture=false)
 
useCapture 是第三個參數(shù)的關鍵詞, 默認為 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()
在圖中,處理程序按順序向外觸發(fā)。類似地,捕獲模型試圖將事件從父元素向內觸發(fā)到單擊的元素?,F(xiàn)在更改上面代碼中的這一行。
- document.getElementById("foo").addEventListener("click", handler, true)
 
事件順序:
divHandler => ulHandler() => handler()
你應該正確地理解事件冒泡(無論方向是指向父節(jié)點還是子節(jié)點),以實現(xiàn)用戶界面(UI),以避免任何不需要的行為。
這些是 JavaScrip t中的基本概念。正如我最初提到的,除了工作經驗和知識之外,準備有助理于你通過 JavaScript 面試。始終保持學習。留意***的發(fā)展(第六章)。深入了解JavaScript的各個方面,如 V6 引擎、測試等。***,沒有掌握數(shù)據(jù)結構和算法的面試是不成功的。Oleksii Trekhleb 策劃了一個很棒的 git repo,它包含了所有使用 JS 代碼的面試準備算法。
代碼部署后可能存在的BUG沒法實時知道,事后為了解決這些BUG,花了大量的時間進行l(wèi)og 調試,這邊順便給大家推薦一個好用的BUG監(jiān)控工具 Fundebug。


















 
 
 











 
 
 
 