你真的懂JavaScript

image

放在前面,本文原文的標題是 So you think you know JavaScript?

在下感覺有些標題黨了,不過看了下文章的鏈接還是很不錯的。

原文作者是由幾個問題展開了說明。

問題 1: 瀏覽器的console里會打印出什么?

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

問題2: 如果是有const或let代替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:如果我們在瀏覽器控制臺中運行'foo'函數(shù),是否會導致堆棧溢出錯誤?

function foo() {
  setTimeout(foo, 0); // will there by any stack overflow error?
};

問題5:如果我們在控制臺中運行以下函數(shù),頁面的UI(tab頁)是否仍然響應?

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

問題6:我們可以在不引起TypeError的情況下以某種方式使用以下語句的擴展語法嗎?

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

問題7:運行以下代碼片段時,控制臺上會打印什么?

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 ??

解答

現(xiàn)在,讓我們從頭到尾回答上面的每個問題。我將給一個簡短的解釋,同時試圖揭開這些行為的神秘面紗,并提供一些參考資料。

答案 1: undefined

解釋: 使用var關鍵字聲明的變量被提升并在內存中為其賦值為undefined。但是初始化恰好發(fā)生在你在代碼中寫入它們的地方。另外,var聲明的變量是函數(shù)作用域的,而let和const是塊作用域的。所以,這就是這個過程的樣子:

var a = 10; // 全局作用域
function foo() {
// 使用var聲明的會被提升到函數(shù)作用域內頂部.
// 就像: var a;

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

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

筆:對這個不了解的,可以看下這篇文章了解一番

答案 2: ReferenceError: a is not defined

解釋: letconst允許你聲明一個變量被限制在一個塊級作用域,或語句或表達式中。不像var,這些變量不會被提升,并且具有所謂的temporal dead zone(TDZ)。嘗試在TDZ中訪問這些變量將拋出一個ReferenceError,因為它們只能在執(zhí)行到達聲明才可被訪問。可以閱讀詞法作用域執(zhí)行上下文棧

var a = 10; // 全局作用域
function foo() { // 進入新的作用域, TDZ開始

// 沒有初始綁定的'a'被創(chuàng)建
    console.log(a); // ReferenceError

// TDZ 結束, 'a'只是在這里被初始化了一個值20
    let a = 20;
}

下表概述了與JavaScript中使用的不同關鍵字相關的提升行為和范圍(主要摘選:Axel Rauschmayer的博客文章)。

[圖片上傳失敗...(image-73310c-1565572678275)]

答案 3: [3, 3, 3].

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

// 誤解作用域:認為存在塊級作用域
var array = [];
for (var i = 0; i < 3; i++) {
  // 三個箭頭函數(shù)中的每個都引用同一個綁定,
  // 這就是為什么循環(huán)結束之后返回同樣的數(shù)字3
  array.push(() => i);
}
var newArray = array.map(el => el());
console.log(newArray); // [3, 3, 3]

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

// 使用ES6塊級作用域綁定
var array = [];
for (let i = 0; i < 3; i++) {
  // 這一次,每個“i”引用一個特定迭代的綁定,并保留當時的值。
  // 因此,每個arrow函數(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]

為啥let可以,可以參考這篇文章

答案 4: 不會

解釋: JavaScript并發(fā)模型基于“事件循環(huán)”。當我說“瀏覽器是JS的家(歸宿)”時,我真正的意思是瀏覽器提供運行時環(huán)境來執(zhí)行我們的JavaScript代碼。瀏覽器的主要組件包括 調用堆棧事件循環(huán)任務隊列Web API 。像setTimeout,setInterval和Promise這樣的全局函數(shù)不是JavaScript的一部分,而是Web API的一部分。JavaScript環(huán)境的可視化表示如下所示:

alt text

JS調用堆棧是后進先出(LIFO)。引擎一次從堆棧中獲取一個函數(shù),并從上到下依次運行代碼。每次遇到一些異步代碼(如setTimeout)時,它都會將其交給Web API(箭頭1)。因此,每當觸發(fā)事件時,callback都會被發(fā)送到任務隊列(箭頭2)。Event Loop不斷監(jiān)視任務隊列,并按照排隊順序一次處理一個callback。每當調用堆棧為空時,循環(huán)檢索回調并將其放入堆棧(箭頭3)進行處理。請記住,如果調用堆棧不為空,則事件循環(huán)不會將任何callbacks推送到堆棧。

有關Event Loop如何在JavaScript中工作的更詳細說明,我強烈建議您觀看Philip Roberts的視頻。此外,你還可以通過這個非常棒的工具可視化和理解調用堆棧。來吧,在那里運行'foo'函數(shù),看看會發(fā)生什么!

現(xiàn)在,有了這些知識,讓我們試著回答上述問題:

步驟

  1. 調用foo()將把foo函數(shù)放進調用棧
  2. 在處理內部代碼時,JS引擎遇到setTimeout。
  3. 然后它將foo回調移交給 WebAPI (箭頭1)并從函數(shù)返回。調用堆棧再次為空。
  4. 計時器設置為0,因此foo將被發(fā)送到 任務隊列 (箭頭2)。
  5. 因為,我們的調用堆棧是空的,事件循環(huán)將選擇foo回調并將其推送到調用堆棧進行處理。
  6. 進程再次重復,堆棧不會溢出

筆:其實這個答案里的鏈接和下面答案的鏈接很給力了。

不過也可以看看其他的

答案 5: 不會

解釋: 大多數(shù)時候,我看到開發(fā)人員假設在事件循環(huán)的藍圖中只有一個任務隊列(筆: 也叫task queue或event queue或callback queue )。但事實并非如此。我們可以有多個任務隊列。由瀏覽器選擇任意的隊列并在其中處理callbacks

在高層次上來看,JavaScript中有宏任務和微任務。setTimeout回調是 macrotasks 而Promise回調是 microtasks 。主要的區(qū)別在于他們的執(zhí)行儀式。宏任務在單個循環(huán)周期中一次一個地推入堆棧,但是微任務隊列總是在執(zhí)行返回到event loop(包括任何額外排隊的項)之前清空。因此,如果你將這些項快速的添加到這個你正在處理的隊列,那么你將永遠在處理微任務。有關更深入的解釋,請觀看Jake Archibald視頻文章

在執(zhí)行返回事件循環(huán)之前,微任務隊列總是被清空

現(xiàn)在,當你在控制臺中運行以下代碼段時:

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

每次調用'foo'都會繼續(xù)在微任務隊列上添加另一個'foo'回調,因此事件循環(huán)無法繼續(xù)處理其他事件(scroll,click等),直到該隊列完全清空為止。因此,它會阻止渲染。

筆:Jake的此文絕對是精華,沒有讀過的可以拜讀一番。

答案 6: 可以, 通過是對象iterables

解釋: 拓展運算符for-of語句迭代iterable對象。數(shù)組或Map是具有默認迭代行為的內置iterable。對象不是可迭代的,但你可以使用iterableiterator協(xié)議使它們可迭代。

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

上述陳述可能看起來有點冗長,但下面的例子會更有意義:

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

  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]; // will print [1, 2, 3]

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

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

筆:對這個不熟悉的可以看下一些例子:

iterator&generator

答案 7: 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)在掌握了這些知識,應該很容易理解為什么我們的代碼會打印出這些特定的屬性:

var obj = { a: 1, b: 2 }; // a, b are both enumerables properties


Object.setPrototypeOf(obj, { c: 3 });

Object.defineProperty(obj, "d", { value: 4, enumerable: false });

for (let prop in obj) {
  console.log(prop);
}

筆:對這個不了解的可以看文章了解一下

property descriptors

object getPrototypeOf&setPrototypeOf

答案 8: 10

解釋: 當我們將x初始化為全局作用域時,它將成為window對象的屬性(假設它是瀏覽器環(huán)境而不是嚴格模式)。看下面代碼:

var x = 10; // 全局作用域
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 將始終指向調用該方法的對象。因此,在foo.getX() 的情況下,this 指向foo對象返回值90。而在xGetter()的情況下,this 指向window 對象返回值10。

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

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

筆:這個說的主要就是this了,不了解的可以看下

this All Makes Sense Now

就是這樣!如果你所有的答案都正確,那就做得好。錯了不可怕,因為我們都從錯誤中學習。關鍵是要知道背后的“原因”。

你都對了嗎老兄。

原文:
1.So you think you know JavaScript?

  1. 你真的懂JavaScript嗎

Kyle simpson: You don't know js

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,030評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,310評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,951評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,796評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,566評論 6 407
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,055評論 1 322
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,142評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,303評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 48,799評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,683評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,899評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,409評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,135評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,520評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,757評論 1 282
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,528評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,844評論 2 372

推薦閱讀更多精彩內容

  • 夯實Javascript基礎。 基本類型有六種: null,undefined,boolean,number,st...
    zhongmeizhi閱讀 233評論 0 1
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴謹 對...
    cosWriter閱讀 11,120評論 1 32
  • __block和__weak修飾符的區(qū)別其實是挺明顯的:1.__block不管是ARC還是MRC模式下都可以使用,...
    LZM輪回閱讀 3,350評論 0 6
  • 洛洛走后,黎歡忽然想起在大學干兼職時遇到的達達,一樣的年齡,同時作為新手員工,她們總是有聊不完的話題,穿著具有民俗...
    惟冬逝雪閱讀 197評論 0 0
  • 主角光環(huán)哪來的? 苗壯 電影、電視劇、小說里的主角光環(huán)比比皆是: 槍林彈雨中,主角就是不中彈; 馬上就要端到嘴邊的...
    苗老師雜談閱讀 442評論 0 2