譯者按: 總結了大量JavaScript基本知識點,頗有用!javascript
爲了保證可讀性,本文采用意譯而非直譯。另外,本文版權歸原做者全部,翻譯僅用於學習。java
根據StackOverflow調查, 自2014年一來,JavaScript是最流行的編程語言。固然,這也在情理之中,畢竟1/3的開發工做都須要一些JavaScript知識。所以,若是你但願在成爲一個開發者,你應該學會這門語言。面試
這篇博客的主要目的是將全部面試中常見的概念總結,方便你快速去了解。(鑑於本文內容過長,方便閱讀,將分爲三篇博客來翻譯, 此爲第二部分。第一部分請點擊快速掌握JavaScript面試基礎知識(一))編程
閉包由一個函數以及該函數定義是所在的環境組成。咱們經過例子來形象解釋它。數組
function sayHi(name){
var message = `Hi ${name}!`;
function greeting() {
console.log(message)
}
return greeting
}
var sayHiToJon = sayHi('Jon');
console.log(sayHiToJon) // ƒ() { console.log(message) }
console.log(sayHiToJon()) // 'Hi Jon!'
複製代碼
請理解var sayHiToJon = sayHi('Jon');
這行代碼的執行過程,sayHi
函數執行,首先將message
的值計算出來;而後定義了greeting
函數,函數中引用了message
變量;最後,返回greeting
函數。 若是按照C/Java語言的思路,sayHiToJon
就等價於greeting
函數,那麼會報錯:message未定義。可是在JavaScript中不同,這裏的sayHiToJon
函數等於greeting
函數以及一個環境,該環境中包含了message
。所以,當咱們調用sayHiToJon
函數,能夠成功地將message
打印出來。所以,這裏的閉包就是greeting
函數和一個包含message
變量的環境。(備註: 爲了便於理解,此段落未按照原文翻譯。)瀏覽器
閉包的一個優點在於數據隔離。咱們一樣用一個例子來講明:安全
function SpringfieldSchool() {
let staff = ['Seymour Skinner', 'Edna Krabappel'];
return {
getStaff: function() { console.log(staff) },
addStaff: function(name) { staff.push(name) }
}
}
let elementary = SpringfieldSchool()
console.log(elementary) // { getStaff: ƒ, addStaff: ƒ }
console.log(staff) // ReferenceError: staff is not defined
/* Closure allows access to the staff variable */
elementary.getStaff() // ["Seymour Skinner", "Edna Krabappel"]
elementary.addStaff('Otto Mann')
elementary.getStaff() // ["Seymour Skinner", "Edna Krabappel", "Otto Mann"]
複製代碼
在elementary
被建立的時候,SpringfieldSchool
已經返回。也就是說staff
沒法被外部訪問。惟一能夠訪問的方式就是裏面的閉包函數getStaff
和addStaff
。閉包
咱們來看一個面試題:下面的代碼有什麼問題,如何修復?app
const arr = [10, 12, 15, 21];
for (var i = 0; i < arr.length; i++) {
setTimeout(function() {
console.log(`The value ${arr[i]} is at index: ${i}`);
}, (i+1) * 1000);
}
複製代碼
上面的代碼輸出的結果所有都同樣:"The value undefined is at index: 4"。由於全部在setTimeout
中定義的匿名函數都引用了同一個外部變量i
。當匿名函數執行的時候,i
的值爲4。異步
這個問題能夠改用IIFE
(後面會介紹)方法來解決,經過對每個匿名函數構建獨立的外部做用域來實現。
const arr = [10, 12, 15, 21];
for (var i = 0; i < arr.length; i++) {
(function(j) {
setTimeout(function() {
console.log(`The value ${arr[j]} is at index: ${j}`);
}, j * 1000);
})(i)
}
複製代碼
固然,還有一個方法,使用let
來聲明i
。
const arr = [10, 12, 15, 21];
for (let i = 0; i < arr.length; i++) {
setTimeout(function() {
console.log(`The value ${arr[i]} is at index: ${i}`);
}, (i) * 1000);
}
複製代碼
一個IIFE是一個函數表達式在定義以後當即被調用。經常使用在你想對一個新聲明的變量建立一個隔離的做用域。 它的格式爲: (function(){....})()
。前面的大括號用於告訴編譯器這裏不只僅是函數定義,後面的大括號用於執行該函數。
var result = [];
for (var i=0; i < 5; i++) {
result.push( function() { return i } );
}
console.log( result[1]() ); // 5
console.log( result[3]() ); // 5
result = [];
for (var i=0; i < 5; i++) {
(function () {
var j = i; // copy current value of i
result.push( function() { return j } );
})();
}
console.log( result[1]() ); // 1
console.log( result[3]() ); // 3
複製代碼
使用IIFE能夠:
咱們每每容易將環境(Context)和做用域(Scope)搞混,我來簡單解釋一下:
this
。這三個方法都是爲了將this綁定到函數,區別在於調用的方式。
.call()
會當即執行函數,你須要把參數按順序傳入;.apply()
會當即執行函數,你須要把全部的參數組合爲一個數組傳入;.call()
和.apply()
幾乎相同。哪一個傳入參數方便,你就選擇哪一個。
const Snow = {surename: 'Snow'}
const char = {
surename: 'Stark',
knows: function(arg, name) {
console.log(`You know ${arg}, ${name} ${this.surename}`);
}
}
char.knows('something', 'Bran'); // You know something, Bran Stark
char.knows.call(Snow, 'nothing', 'Jon'); // You know nothing, Jon Snow
char.knows.apply(Snow, ['nothing', 'Jon']); // You know nothing, Jon Snow
複製代碼
注意:若是你將數組傳入call
函數,它會認爲只有一個參數。
ES6容許使用新的操做符將數組變換爲一個序列。
char.knows.call(Snow, ...["nothing", "Jon"]); // You know nothing, Jon Snow
複製代碼
.bind()
返回一個新的函數,以及相應的環境和參數。若是你想該函數稍後調用,那麼推薦使用bind
。 .bind()
函數的優勢在於它能夠記錄一個執行環境,對於異步調用和事件驅動的編程頗有用。
.bind()
傳參數的方式和call
相同。
const Snow = {surename: 'Snow'}
const char = {
surename: 'Stark',
knows: function(arg, name) {
console.log(`You know ${arg}, ${name} ${this.surename}`);}
}
const whoKnowsNothing = char.knows.bind(Snow, 'nothing');
whoKnowsNothing('Jon'); // You know nothing, Jon Snow
複製代碼
要理解JavaScript中this
關鍵字,特別是它指向誰,有時候至關地複雜。this
的值一般由函數的執行環境決定。簡單的說,執行環境指函數如何被調用的。this
像是一個佔位符(placeholder),它指向當方法被調用時,調用對應的方法的對象。
下面有序地列出了判斷this
指向的規則。若是第一條匹配,那麼就不用去檢查第二條了。
new
綁定 - 當使用new
關鍵字調用函數的時候,this
指向新構建的對象。
function Person(name, age) {
this.name = name;
this.age =age;
console.log(this);
}
const Rachel = new Person('Rachel', 30); // { age: 30, name: 'Rachel' }
複製代碼
顯示綁定(Explicit binding) - 當使用call
或則apply
的時候,咱們顯示的傳入一個對象參數,該參數會綁定到this
。 注意:.bind()
函數不同。用bind
定義一個新的函數,可是依然綁定到原來的對象。
function fn() {
console.log(this);
}
var agent = {id: '007'};
fn.call(agent); // { id: '007' }
fn.apply(agent); // { id: '007' }
var boundFn = fn.bind(agent);
boundFn(); // { id: '007' }
複製代碼
隱式綁定 - 當一個函數在某個環境下調用(在某個對象裏),this
指向該對象。也就是說該函數是對象的一個方法。
var building = {
floors: 5,
printThis: function() {
console.log(this);
}
}
building.printThis(); // { floors: 5, printThis: function() {…} }
複製代碼
默認綁定 - 若是上面全部的規則都不知足,那麼this
指向全局對象(在瀏覽器中,就是window對象)。當函數沒有綁定到某個對象,而單獨定義的時候,該函數默認綁定到全局對象。
function printWindow() {
console.log(this)
}
printWindow(); // window object
複製代碼
注意:下面的狀況中,inner
函數中的this
指向全局。
function Dinosaur(name) {
this.name = name;
var self = this;
inner();
function inner() {
alert(this); // window object — the function has overwritten the 'this' context
console.log(self); // {name: 'Dino'} — referencing the stored value from the outer context
}
}
var myDinosaur = new Dinosaur('Dino');
複製代碼
=>
來定義函數時,this
指向定義該函數時候外層的this
。 備註:大概是和定義的詞法(=>
)有關,把它稱做Lexical this
。function Cat(name) {
this.name = name;
console.log(this); // { name: 'Garfield' }
( () => console.log(this) )(); // { name: 'Garfield' }
}
var myCat = new Cat('Garfield');
複製代碼
若是你使用了"use strict"
指令,那麼JavaScript代碼會在嚴格模式下執行。在嚴格模式下,對於詞法分析和錯誤處理都有特定的規則。在這裏我列出它的一些優勢:
eval()
更加安全:在eval()
中定義的變量和函數在外部做用域不可見;this
是null或則undefined不在轉換到全局對象。也就是說在瀏覽器中使用this去指向全局對象再也不可行。對於在嚴格(strict)模式和測試階段都沒有發現的bug,不妨接入線上實時監控插件Fundebug。