JavaScript是前端開發中很是重要的一門語言,瀏覽器是他主要運行的地方。JavaScript是一個很是有意思的語言,可是他有不少一些概念,你們常常都會忽略。好比說,原型,閉包,原型鏈,事件循環等等這些概念,不少JS開發人員都研究很少。javascript
因此今天,就來和你們看看下面幾個問題,你們能夠先思考一下,嘗試做答。前端
前面的問題咱們都舉例出來了,接下來咱們會從頭至尾,一個個來分析咱們這些問題的答案,給你們一些學習的思路java
var a = 10; // 全局做用域,全局變量。a=10
function foo() {
// var a
//的聲明將被提高到到函數的頂部。
// 好比:var a
console.log(a); // 打印 undefined
// 實際初始化值20只發生在這裏
var a = 20; // local scope
}複製代碼
圖解在下面,好理解一點
因此問題1的答案是:undefined面試
var a = 10; // 全局使用域
function foo() { // TDZ 開始
// 建立了未初始化的'a'
console.log(a); // ReferenceError
// TDZ結束,'a'僅在此處初始化,值爲20
let a = 20;
}複製代碼
圖解:
問題2答案:ReferenceError: a is not definedtypescript
這個問題,是循環結構會給你們帶來一種塊級做用域的誤區,在for的循環的頭部使用var聲明的變量,就是單個聲明的變量綁定(單個存儲空間)。在循環過程當中,這個var聲明的i變量是會隨循環變化的。可是在循環中執行的數組push方法,最後其實是push了i最終循環結束的3這個值。因此最後push進去的全都是3。數組
// 誤解做用域:認爲存在塊級做用域
var array = [];
for (var i = 0; i < 3; i++) {
// 三個箭頭函數體中的每一個'i'都指向相同的綁定,
// 這就是爲何它們在循環結束時返回相同的值'3'。
array.push(() => i);
}
var newArray = array.map(el => el());
console.log(newArray); // [3, 3, 3]複製代碼
圖解:
若是想記錄每一次循環的值下來,可使用let聲明一個具備塊級做用域的變量,這樣爲每一個循環迭代建立一個新的綁定。瀏覽器
// 使用ES6塊級做用域
var array = [];
for (let i = 0; i < 3; i++) {
// 這一次,每一個'i'指的是一個新的的綁定,並保留當前的值。
// 所以,每一個箭頭函數返回一個不一樣的值。
array.push(() => i);
}
var newArray = array.map(el => el());
console.log(newArray); // [0, 1, 2]複製代碼
還有解決這個問題的另一種解決方案就是使用閉包就行了。閉包
let array = [];
for (var i = 0; i < 3; i++) {
array[i] = (function(x) {
return function() {
return x;
};
})(i);
}
const newArray = array.map(el => el());
console.log(newArray); // [0, 1, 2] 複製代碼
問題3答案:3,3,3併發
好了,如今有了前面這些知識,咱們能夠看一下這道題的講解過程:
實現步驟:異步
問題4答案:堆棧不會溢出。
他們有什麼區別呢?
主要的區別在於他們的執行方式。宏任務在單個循環週期中一次一個低堆入堆棧,可是微任務隊列老是在執行後返回到事件以前清空。因此,若是你以處理條目的速度向這個隊列添加條目,那麼你就永遠在處理微任務。只有當微任務隊列爲空時,事件循環纔會從新渲染頁面。
而後咱們再回到咱們前面講的問題5中:
function foo() {
return Promise.resolve().then(foo);
}; 複製代碼
咱們這段代碼,每次咱們去調用【foo】的時候,都會在微任務隊列上加另外一個【foo】的回調,所以事件循環沒辦法繼續去處理其餘的事件了(好比說滾動,點擊事件等等),直到該隊列徹底清空位置。所以,不會執行渲染,會被阻止。
問題5答案:不會響應。
var obj = { x: 1, y: 2, z: 3 };
obj[Symbol.iterator] = function() {
// iterator 是一個具備 next 方法的對象,
// 它的返回至少有一個對象
// 兩個屬性:value&done。
// 返回一個 iterator 對象
return {
next: function() {
if (this._countDown === 3) {
const lastValue = this._countDown;
return { value: this._countDown, done: true };
}
this._countDown = this._countDown + 1;
return { value: this._countDown, done: false };
},
_countDown: 0
};
};
[...obj]; // 打印 [1, 2, 3]複製代碼
問題6答案:如上是一種方案,能夠避免TypeError異常。
var obj = { a: 1, b: 2 }; //a,b 都是可枚舉屬性
// 將{c:3}設置爲'obj'的原型,
// 而且咱們知道for-in 循環也迭代 obj 繼承的屬性
// 從它的原型,'c'也能夠被訪問。
Object.setPrototypeOf(obj, { c: 3 });
// 咱們在'obj'中定義了另一個屬性'd',
// 可是將'enumerable'可枚舉設置爲false。 這意味着'd'將被忽略。
Object.defineProperty(obj, "d", { value: 4, enumerable: false });
//因此最後使用for-in遍歷這個對象集合,那就是隻能遍歷出可枚舉屬性
for (let prop in obj) {
console.log(prop);
}
// 也就是隻能打印
// a
// b
// c複製代碼
圖解
問題7答案:a、b、c
var x = 10; // 全局變量
var foo = {
x: 90,//foo對象的內部屬性
getX: function() {
return this.x;
}
};
foo.getX(); // 此時是指向的foo對象,
//因此打印的是X屬性 值就是90
let xGetter = foo.getX;//xGetter是在全局做用域,
//這裏的this就是指向window對象
xGetter(); // 打印 10複製代碼
問題8答案:10
ok,咱們的8道問題都解決了,若是你前面寫的答案所有都正確,那麼你很是棒!去面試前端工做起碼12k起步了。就算作不出來或者作錯了也沒有關係,咱們都是不斷經過犯錯來學習的,一步步的理解錯誤,理解背後的緣由,才能進步。
更多技術好文,前端開發學習教程,歡迎關注公衆號【前端研究所】看更多前端技術文章!