8個問題檢測你是否真的了解js

問題1:瀏覽器控制臺上會打印什么萍丐?

var a = 10;
function foo() {
    console.log(a); // ??
    var a = 20;
}
foo();

問題2:如果我們使用 let 或 const 代替 var凛虽,輸出是否相同

var a = 10;
function foo() {
    console.log(a); // ??
    let a = 20;
}
foo();    

問題3:“newArray”中有哪些元素?

var array = [];
for(var i = 0; i <3; i++) {
 array.push(() => i);
}
var newArray = array.map(el => el());
console.log(newArray); // ??   

問題4:如果我們在瀏覽器控制臺中運(yùn)行'foo'函數(shù),是否會導(dǎo)致堆棧溢出錯誤?

function foo() {
  setTimeout(foo, 0); // 是否存在堆棧溢出錯誤?
};    

問題5: 如果在控制臺中運(yùn)行以下函數(shù)裳扯,頁面(選項(xiàng)卡)的 UI 是否仍然響應(yīng)

function foo() {
  return Promise.resolve().then(foo);
};    

問題6: 我們能否以某種方式為下面的語句使用展開運(yùn)算而不導(dǎo)致類型錯誤

var obj = { x: 1, y: 2, z: 3 };
[...obj]; // TypeError 

問題7:運(yùn)行以下代碼片段時(shí),控制臺上會打印什么谤职?

var obj = { a: 1, b: 2 };
Object.setPrototypeOf(obj, {c: 3});
Object.defineProperty(obj, 'd', { value: 4, enumerable: false });

// what properties will be printed when we run the for-in loop?
for(let prop in obj) {
    console.log(prop);
}  

問題8:xGetter() 會打印什么值饰豺?

var x = 10;
var foo = {
  x: 90,
  getX: function() {
    return this.x;
  }
};
foo.getX(); // prints 90
var xGetter = foo.getX;
xGetter(); // prints ??

答案

問題1:瀏覽器控制臺上會打印什么?

var a = 10;
function foo() {
    console.log(a); // ??
    var a = 20;
}
foo();

答案: undefined

解析:

使用var關(guān)鍵字聲明的變量在JavaScript中會被提升允蜈,并在內(nèi)存中分配值undefined冤吨。 但初始化恰發(fā)生在你給變量賦值的地方。 另外饶套,var聲明的變量是函數(shù)作用域的漩蟆,而letconst是塊作用域的。 所以妓蛮,這就是這個過程的樣子:

var a = 10; // 全局使用域
function foo() {
// var a 的聲明將被提升到到函數(shù)的頂部怠李。
// 比如:var a

console.log(a); // 打印 undefined

// 實(shí)際初始化值20只發(fā)生在這里
   var a = 20; // local scope
}

問題2:如果我們使用 let 或 const 代替 var,輸出是否相同?

var a = 10;
function foo() {
    console.log(a); // ??
    let a = 20;
}
foo();   

答案:ReferenceError:a undefined蛤克。

解析:

letconst聲明可以讓變量在其作用域上受限于它所使用的塊扔仓、語句或表達(dá)式。與var不同的是咖耘,這些變量沒有被提升,并且有一個所謂的暫時(shí)死區(qū)(TDZ)撬码。試圖訪問TDZ中的這些變量將引發(fā)ReferenceError儿倒,因?yàn)橹挥性趫?zhí)行到達(dá)聲明時(shí)才能訪問它們。

var a = 10; // 全局使用域
function foo() { // TDZ 開始

// 創(chuàng)建了未初始化的'a'
    console.log(a); // ReferenceError

// TDZ結(jié)束,'a'僅在此處初始化夫否,值為20
    let a = 20;
}

下表概述了與JavaScript中使用的不同關(guān)鍵字聲明的變量對應(yīng)的提升行為和使用域:

問題3:“newArray”中有哪些元素彻犁?

var array = [];
for(var i = 0; i <3; i++) {
 array.push(() => i);
}
var newArray = array.map(el => el());
console.log(newArray); // ??   

答案: [3, 3, 3]

解析:

for循環(huán)的頭部聲明帶有var關(guān)鍵字的變量會為該變量創(chuàng)建單個綁定(存儲空間)。 閱讀更多關(guān)于閉包的信息凰慈。 讓我們再看一次for循環(huán)汞幢。

// 誤解作用域:認(rèn)為存在塊級作用域
var array = [];
for (var i = 0; i < 3; i++) {
  // 三個箭頭函數(shù)體中的每個`'i'`都指向相同的綁定,
  // 這就是為什么它們在循環(huán)結(jié)束時(shí)返回相同的值'3'微谓。
  array.push(() => i);
}
var newArray = array.map(el => el());
console.log(newArray); // [3, 3, 3]

如果使用let聲明一個具有塊級作用域的變量森篷,則為每個循環(huán)迭代創(chuàng)建一個新的綁定。

// 使用ES6塊級作用域
var array = [];
for (let i = 0; i < 3; i++) {
  // 這一次豺型,每個'i'指的是一個新的的綁定仲智,并保留當(dāng)前的值。
 // 因此姻氨,每個箭頭函數(shù)返回一個不同的值钓辆。
  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]  

問題4:如果我們在瀏覽器控制臺中運(yùn)行'foo'函數(shù)肴焊,是否會導(dǎo)致堆棧溢出錯誤前联?

function foo() {
  setTimeout(foo, 0); // 是否存在堆棧溢出錯誤?
};    

答案 : 不會溢出

解析:

JavaScript并發(fā)模型基于“事件循環(huán)”。 當(dāng)我們說“瀏覽器是 JS 的家”時(shí)我真正的意思是瀏覽器提供運(yùn)行時(shí)環(huán)境來執(zhí)行我們的JS代碼娶眷。

瀏覽器的主要組件包括調(diào)用堆棧似嗤,事件循環(huán)****,任務(wù)隊(duì)列Web API茂浮。 像setTimeout揍移,setIntervalPromise這樣的全局函數(shù)不是JavaScript的一部分,而是 Web API 的一部分由缆。 JavaScript 環(huán)境的可視化形式如下所示:

JS調(diào)用棧是后進(jìn)先出(LIFO)的矩欠。引擎每次從堆棧中取出一個函數(shù),然后從上到下依次運(yùn)行代碼幌羞。每當(dāng)它遇到一些異步代碼寸谜,如setTimeout,它就把它交給Web API(箭頭1)属桦。因此熊痴,每當(dāng)事件被觸發(fā)時(shí),callback都會被發(fā)送到任務(wù)隊(duì)列(箭頭2)聂宾。

事件循環(huán)(Event loop)不斷地監(jiān)視任務(wù)隊(duì)列(Task Queue)果善,并按它們排隊(duì)的順序一次處理一個回調(diào)。每當(dāng)調(diào)用堆棧(call stack)為空時(shí)系谐,Event loop獲取回調(diào)并將其放入堆棧(stack )(箭頭3)中進(jìn)行處理巾陕。請記住讨跟,如果調(diào)用堆棧不是空的,則事件循環(huán)不會將任何回調(diào)推入堆棧鄙煤。

現(xiàn)在晾匠,有了這些知識,讓我們來回答前面提到的問題:

步驟

1梯刚、調(diào)用foo()會將foo函數(shù)放入調(diào)用堆棧(call stack)凉馆。

2、在處理內(nèi)部代碼時(shí)亡资,JS引擎遇到setTimeout澜共。

3、然后將foo回調(diào)函數(shù)傳遞給WebAPIs(箭頭1)并從函數(shù)返回沟于,調(diào)用堆棧再次為空

4咳胃、計(jì)時(shí)器被設(shè)置為0,因此foo將被發(fā)送到任務(wù)隊(duì)列(箭頭2)旷太。

5展懈、由于調(diào)用堆棧是空的,事件循環(huán)將選擇foo回調(diào)并將其推入調(diào)用堆棧進(jìn)行處理供璧。

6存崖、進(jìn)程再次重復(fù),堆棧不會溢出睡毒。

運(yùn)行示意圖如下所示:

問題5: 如果在控制臺中運(yùn)行以下函數(shù)来惧,頁面(選項(xiàng)卡)的 UI 是否仍然響應(yīng)?

function foo() {
  return Promise.resolve().then(foo);
};   

答案:不會響應(yīng)

解析:

大多數(shù)時(shí)候,開發(fā)人員假設(shè)在事件循環(huán)圖中只有一個任務(wù)隊(duì)列演顾。但事實(shí)并非如此供搀,我們可以有多個任務(wù)隊(duì)列。由瀏覽器選擇其中的一個隊(duì)列并在該隊(duì)列中處理回調(diào)钠至。

在底層來看葛虐,JavaScript中有宏任務(wù)和微任務(wù)。setTimeout回調(diào)是宏任務(wù)棉钧,而Promise回調(diào)是微任務(wù)屿脐。

主要的區(qū)別在于他們的執(zhí)行方式。宏任務(wù)在單個循環(huán)周期中一次一個地推入堆棧宪卿,但是微任務(wù)隊(duì)列總是在執(zhí)行后返回到事件循環(huán)之前清空的诵。因此,如果你以處理?xiàng)l目的速度向這個隊(duì)列添加條目佑钾,那么你就永遠(yuǎn)在處理微任務(wù)西疤。只有當(dāng)微任務(wù)隊(duì)列為空時(shí),事件循環(huán)才會重新渲染頁面休溶。

現(xiàn)在代赁,當(dāng)你在控制臺中運(yùn)行以下代碼段

function foo() {
  return Promise.resolve().then(foo);
};

每次調(diào)用'foo'都會繼續(xù)在微任務(wù)隊(duì)列上添加另一個'foo'回調(diào)撒遣,因此事件循環(huán)無法繼續(xù)處理其他事件(滾動,單擊等)管跺,直到該隊(duì)列完全清空為止。 因此禾进,它會阻止渲染豁跑。

問題6: 我們能否以某種方式為下面的語句使用展開運(yùn)算而不導(dǎo)致類型錯誤?

var obj = { x: 1, y: 2, z: 3 };
[...obj]; // TypeError 

答案:會導(dǎo)致TypeError錯誤

解析:

展開語法for-of 語句遍歷iterable對象定義要遍歷的數(shù)據(jù)泻云。ArrayMap 是具有默認(rèn)迭代行為的內(nèi)置迭代器艇拍。對象不是可迭代的,但是可以通過使用iterableiterator協(xié)議使它們可迭代宠纯。

Mozilla文檔中卸夕,如果一個對象實(shí)現(xiàn)了@@iterator方法,那么它就是可迭代的婆瓜,這意味著這個對象(或者它原型鏈上的一個對象)必須有一個帶有@@iterator鍵的屬性快集,這個鍵可以通過常量Symbol.iterator獲得。

上述語句可能看起來有點(diǎn)冗長廉白,但是下面的示例將更有意義:

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]

還可以使用generator函數(shù)來定制對象的迭代行為:

var obj = {x:1, y:2, z: 3}
obj[Symbol.iterator] = function*() {
  yield 1;
  yield 2;
  yield 3;
}
[...obj]; // 打印 [1, 2, 3]

問題7:運(yùn)行以下代碼片段時(shí)猴蹂,控制臺上會打印什么院溺?

var obj = { a: 1, b: 2 };
Object.setPrototypeOf(obj, {c: 3});
Object.defineProperty(obj, 'd', { value: 4, enumerable: false });

// what properties will be printed when we run the for-in loop?
for(let prop in obj) {
    console.log(prop);
}  

答案: a, b, c

解析:

for-in循環(huán)遍歷對象本身的可枚舉屬性以及對象從其原型繼承的屬性。 可枚舉屬性是可以在for-in循環(huán)期間包含和訪問的屬性磅轻。

var obj = { a: 1, b: 2 };
var descriptor = Object.getOwnPropertyDescriptor(obj, "a");
console.log(descriptor.enumerable); // true
console.log(descriptor);
// { value: 1, writable: true, enumerable: true, configurable: true }

現(xiàn)在你已經(jīng)掌握了這些知識珍逸,應(yīng)該很容易理解為什么我們的代碼要打印這些特定的屬性

var obj = { a: 1, b: 2 }; //a,b 都是 enumerables 屬性

// 將{c:3}設(shè)置為'obj'的原型聋溜,并且我們知道
// for-in 循環(huán)也迭代 obj 繼承的屬性
// 從它的原型谆膳,'c'也可以被訪問。
Object.setPrototypeOf(obj, { c: 3 });

// 我們在'obj'中定義了另外一個屬性'd'勤婚,但是 
// 將'enumerable'設(shè)置為false摹量。 這意味著'd'將被忽略。
Object.defineProperty(obj, "d", { value: 4, enumerable: false });

for (let prop in obj) {
  console.log(prop);
}
// 打印
// a
// b
// c

問題8:xGetter() 會打印什么值馒胆?

var x = 10;
var foo = {
  x: 90,
  getX: function() {
    return this.x;
  }
};
foo.getX(); // prints 90
var xGetter = foo.getX;
xGetter(); // prints ??

答案 : 10

解析:

在全局范圍內(nèi)初始化x時(shí)缨称,它成為window對象的屬性(不是嚴(yán)格的模式)∽S兀看看下面的代碼:

var x = 10; // global scope
var foo = {
  x: 90,
  getX: function() {
    return this.x;
  }
};
foo.getX(); // prints 90
let xGetter = foo.getX;
xGetter(); // prints 10

咱們可以斷言:

window.x === 10; // true

this始終指向調(diào)用方法的對象睦尽。因此,在foo.getx()的例子中型雳,它指向foo對象当凡,返回90的值山害。而在xGetter()的情況下,this指向 window對象, 返回window中的x的值沿量,即10浪慌。

要獲取foo.x的值,可以通過使用Function.prototype.bindthis的值綁定到foo對象來創(chuàng)建新函數(shù)朴则。

let getFooX = foo.getX.bind(foo);
getFooX(); // 90

就這樣权纤! 如果你的所有答案都正確,那么干漂亮乌妒。 咱們都是通過犯錯來學(xué)習(xí)的汹想。 這一切都是為了了解背后的“原因”。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末撤蚊,一起剝皮案震驚了整個濱河市古掏,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌侦啸,老刑警劉巖槽唾,帶你破解...
    沈念sama閱讀 217,734評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異匹中,居然都是意外死亡夏漱,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,931評論 3 394
  • 文/潘曉璐 我一進(jìn)店門顶捷,熙熙樓的掌柜王于貴愁眉苦臉地迎上來挂绰,“玉大人,你說我怎么就攤上這事服赎】伲” “怎么了?”我有些...
    開封第一講書人閱讀 164,133評論 0 354
  • 文/不壞的土叔 我叫張陵重虑,是天一觀的道長践付。 經(jīng)常有香客問我,道長缺厉,這世上最難降的妖魔是什么永高? 我笑而不...
    開封第一講書人閱讀 58,532評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮提针,結(jié)果婚禮上命爬,老公的妹妹穿的比我還像新娘。我一直安慰自己辐脖,他們只是感情好饲宛,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,585評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著嗜价,像睡著了一般艇抠。 火紅的嫁衣襯著肌膚如雪幕庐。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,462評論 1 302
  • 那天家淤,我揣著相機(jī)與錄音异剥,去河邊找鬼。 笑死絮重,一個胖子當(dāng)著我的面吹牛届吁,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播绿鸣,決...
    沈念sama閱讀 40,262評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼暂氯!你這毒婦竟也來了潮模?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,153評論 0 276
  • 序言:老撾萬榮一對情侶失蹤痴施,失蹤者是張志新(化名)和其女友劉穎擎厢,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體辣吃,經(jīng)...
    沈念sama閱讀 45,587評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡动遭,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,792評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了神得。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片厘惦。...
    茶點(diǎn)故事閱讀 39,919評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖哩簿,靈堂內(nèi)的尸體忽然破棺而出宵蕉,到底是詐尸還是另有隱情,我是刑警寧澤节榜,帶...
    沈念sama閱讀 35,635評論 5 345
  • 正文 年R本政府宣布羡玛,位于F島的核電站,受9級特大地震影響宗苍,放射性物質(zhì)發(fā)生泄漏稼稿。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,237評論 3 329
  • 文/蒙蒙 一讳窟、第九天 我趴在偏房一處隱蔽的房頂上張望让歼。 院中可真熱鬧,春花似錦挪钓、人聲如沸是越。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,855評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽倚评。三九已至浦徊,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間天梧,已是汗流浹背盔性。 一陣腳步聲響...
    開封第一講書人閱讀 32,983評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留呢岗,地道東北人冕香。 一個月前我還...
    沈念sama閱讀 48,048評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像后豫,于是被迫代替她去往敵國和親悉尾。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,864評論 2 354

推薦閱讀更多精彩內(nèi)容

  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴(yán)謹(jǐn) 對...
    cosWriter閱讀 11,101評論 1 32
  • __block和__weak修飾符的區(qū)別其實(shí)是挺明顯的:1.__block不管是ARC還是MRC模式下都可以使用挫酿,...
    LZM輪回閱讀 3,310評論 0 6
  • topics: 1.The Node.js philosophy 2.The reactor pattern 3....
    宮若石閱讀 1,080評論 0 1
  • 我是一個愛筆之人构眯。可是搞笑的是早龟,我自己并沒有買過一支屬于自己的筆惫霸。我的筆有很多,最喜歡的一支卻還沒有拆封...
    孫100閱讀 452評論 1 2