不積跬步無以致千里。javascript
Step-By-Step (點擊進入項目) 是我於 2019-05-20
開始的一個項目,項目願景:一步一個腳印,量變引發質變。css
Step-By-Step 僅會在工做日發佈面試題,主要考慮到部分小夥伴平時工做較爲繁忙,或週末有出遊計劃。每一個週末我會仔細閱讀你們的答案,整理最一份較優答案出來,因本人水平有限,有誤的地方,你們及時指正。參與答題的小夥伴,能夠對比本身的回答。html
答題不是目的,不但願你們僅僅是簡單的搜索答案,複製粘貼到issue下。更多的是但願你們及時查漏補缺 / 鞏固相關知識。html5
更多優質文章可戳: https://github.com/YvetteLau/...java
若是用一句話說明 this 的指向,那麼便是: 誰調用它,this 就指向誰。node
可是僅經過這句話,咱們不少時候並不能準確判斷 this 的指向。所以咱們須要藉助一些規則去幫助本身:git
this 的指向能夠按照如下順序判斷:github
瀏覽器環境:不管是否在嚴格模式下,在全局執行環境中(在任何函數體外部)this 都指向全局對象 window
; 面試
node 環境:不管是否在嚴格模式下,在全局執行環境中(在任何函數體外部),this 都是空對象 {}
;canvas
new
綁定若是是 new
綁定,而且構造函數中沒有返回 function 或者是 object,那麼 this 指向這個新對象。以下:
構造函數返回值不是 function 或 object。
function Super(age) { this.age = age; } let instance = new Super('26'); console.log(instance.age); //26
構造函數返回值是 function 或 object,這種狀況下 this 指向的是返回的對象。
function Super(age) { this.age = age; let obj = {a: '2'}; return obj; } let instance = new Super('hello'); console.log(instance.age); //undefined
你能夠想知道爲何會這樣?咱們來看一下 new
的實現原理:
[[原型]]
鏈接。function new(func) { let target = {}; target.__proto__ = func.prototype; let res = func.call(target); //排除 null 的狀況 if (res && typeof(res) == "object" || typeof(res) == "function") { return res; } return target; }
function info(){ console.log(this.age); } var person = { age: 20, info } var age = 28; var info = person.info; info.call(person); //20 info.apply(person); //20 info.bind(person)(); //20
這裏一樣須要注意一種特殊狀況,若是 call,apply 或者 bind 傳入的第一個參數值是 undefined
或者 null
,嚴格模式下 this 的值爲傳入的值 null /undefined。非嚴格模式下,實際應用的默認綁定規則,this 指向全局對象(node環境爲global,瀏覽器環境爲window)
function info(){ //node環境中:非嚴格模式 global,嚴格模式爲null //瀏覽器環境中:非嚴格模式 window,嚴格模式爲null console.log(this); console.log(this.age); } var person = { age: 20, info } var age = 28; var info = person.info; //嚴格模式拋出錯誤; //非嚴格模式,node下輸出undefined(由於全局的age不會掛在 global 上) //非嚴格模式。瀏覽器環境下輸出 28(由於全局的age會掛在 window 上) info.call(null);
xxx.fn()
function info(){ console.log(this.age); } var person = { age: 20, info } var age = 28; person.info(); //20;執行的是隱式綁定
非嚴格模式: node環境,執行全局對象 global,瀏覽器環境,執行全局對象 window。
嚴格模式:執行 undefined
function info(){ console.log(this.age); } var age = 28; //嚴格模式;拋錯 //非嚴格模式,node下輸出 undefined(由於全局的age不會掛在 global 上) //非嚴格模式。瀏覽器環境下輸出 28(由於全局的age不會掛在 window 上) //嚴格模式拋出,由於 this 此時是 undefined info();
箭頭函數沒有本身的this,繼承外層上下文綁定的this。
let obj = { age: 20, info: function() { return () => { console.log(this.age); //this繼承的是外層上下文綁定的this } } } let person = {age: 28}; let info = obj.info(); info(); //20 let info2 = obj.info.call(person); info2(); //28
點擊查看更多
ES10新增了一種基本數據類型:BigInt
複雜數據類型只有一種: Object
null 不是一個對象,儘管 typeof null
輸出的是 object
,這是一個歷史遺留問題,JS 的最第一版本中使用的是 32 位系統,爲了性能考慮使用低位存儲變量的類型信息,000 開頭表明是對象,null
表示爲全零,因此將它錯誤的判斷爲 object
。
let b = { age: 10 } let a = b; a.age = 20; console.log(a); //{ age: 20 }
函數傳參都是按值傳遞(棧中的存儲的內容):基本數據類型,拷貝的是值;複雜數據類型,拷貝的是引用地址
//基本數據類型 let b = 10 function change(info) { info=20; } //info=b;基本數據類型,拷貝的是值得副本,兩者互不干擾 change(b); console.log(b);//10
//複雜數據類型 let b = { age: 10 } function change(info) { info.age = 20; } //info=b;根據第三條差別,能夠看出,拷貝的是地址的引用,修改互相影響。 change(b); console.log(b);//{ age: 20 }
點擊查看更多
語義化意味着顧名思義,HTML5的語義化指的是合理正確的使用語義化的標籤來建立頁面結構,如 header,footer,nav,從標籤上便可以直觀的知道這個標籤的做用,而不是濫用div。
<aside>
的內容可用做文章的側欄。例如使用這些可視化標籤,構建下面的頁面結構:
對於早期不支持 HTML5 的瀏覽器,如IE8及更早以前的版本,咱們能夠引入 html5shiv 來支持。
點擊查看更多
==
操做符在左右數據類型不一致時,會先進行隱式轉換。
a == 1 && a == 2 && a == 3
的值意味着其不多是基本數據類型。由於若是 a 是 null 或者是 undefined bool類型,都不可能返回true。
所以能夠推測 a 是複雜數據類型,JS 中複雜數據類型只有 object
,回憶一下,Object 轉換爲原始類型會調用什麼方法?
[Symbol.toPrimitive]
接口,那麼調用此接口,若返回的不是基本數據類型,拋出錯誤。若是沒有部署 [Symbol.toPrimitive]
接口,那麼根據要轉換的類型,先調用 valueOf
/ toString
hint
是 default
時,調用順序爲:valueOf
>>> toString
,即valueOf
返回的不是基本數據類型,纔會繼續調用 valueOf
,若是toString
返回的還不是基本數據類型,那麼拋出錯誤。hint
是 string
(Date對象默人的hint是string) ,調用順序爲:toString
>>> valueOf
,即toString
返回的不是基本數據類型,纔會繼續調用 valueOf
,若是valueOf
返回的還不是基本數據類型,那麼拋出錯誤。hint
是 number
,調用順序爲: valueOf
>>> toString
var obj = { [Symbol.toPrimitive](hint) { console.log(hint); return 10; }, valueOf() { console.log('valueOf'); return 20; }, toString() { console.log('toString'); return 'hello'; } } console.log(obj + 'yvette'); //default //若是沒有部署 [Symbol.toPrimitive]接口,調用順序爲`valueOf` >>> `toString` console.log(obj == 'yvette'); //default //若是沒有部署 [Symbol.toPrimitive]接口,調用順序爲`valueOf` >>> `toString` console.log(obj * 10);//number //若是沒有部署 [Symbol.toPrimitive]接口,調用順序爲`valueOf` >>> `toString` console.log(Number(obj));//number //若是沒有部署 [Symbol.toPrimitive]接口,調用順序爲`valueOf` >>> `toString` console.log(String(obj));//string //若是沒有部署 [Symbol.toPrimitive]接口,調用順序爲`toString` >>> `valueOf`
那麼對於這道題,只要 [Symbol.toPrimitive]
接口,第一次返回的值是 1,而後遞增,即成功成立。
let a = { [Symbol.toPrimitive]: (function(hint) { let i = 1; //閉包的特性之一:i 不會被回收 return function() { return i++; } })() } console.log(a == 1 && a == 2 && a == 3); //true
調用 valueOf
接口的狀況:
let a = { valueOf: (function() { let i = 1; //閉包的特性之一:i 不會被回收 return function() { return i++; } })() } console.log(a == 1 && a == 2 && a == 3); //true
另外,除了i自增的方法外,還能夠利用 正則,以下
let a = { reg: /\d/g, valueOf () { return this.reg.exec(123)[0] } } console.log(a == 1 && a == 2 && a == 3); //true
調用 toString
接口的狀況,再也不作說明。
使用 Object.defineProperty
定義的屬性,在獲取屬性時,會調用 get
方法。利用這個特性,咱們在 window
對象上定義 a
屬性,以下:
let i = 1; Object.defineProperty(window, 'a', { get: function() { return i++; } }); console.log(a == 1 && a == 2 && a == 3); //true
ES6 新增了 Proxy
,此處咱們一樣能夠利用 Proxy
去實現,以下:
let a = new Proxy({}, { i: 1, get: function () { return () => this.i++; } }); console.log(a == 1 && a == 2 && a == 3); // true
toString
接口默認調用數組的 join
方法,重寫數組的 join
方法。let a = [1, 2, 3]; a.join = a.shift; console.log(a == 1 && a == 2 && a == 3); //true
with
關鍵字我本人對 with
向來是敬而遠之的。不過 with
的確也是此題方法之一:
let i = 0; with ({ get a() { return ++i; } }) { console.log(a == 1 && a == 2 && a == 3); //true }
點擊查看更多
防抖函數的做用就是控制函數在必定時間內的執行次數。防抖意味着N秒內函數只會被執行一次,若是N秒內再次被觸發,則從新計算延遲時間。
舉例說明:小思最近在減肥,可是她很是貪吃。爲此,與其男友約定好,若是10天不吃零食,就能夠購買一個包(不要問爲何是包,由於包治百病)。可是若是中間吃了一次零食,那麼就要從新計算時間,直到小思堅持10天沒有吃零食,才能購買一個包。因此,管不住嘴的小思,沒有機會買包(悲傷的故事)...這就是防抖。
無論吃沒吃零食,每10天買一個包,中間想買包,忍着,等到第十天的時候再買,這種狀況是節流。如何控制女友的消費,各位攻城獅們,get到了嗎?防抖可比節流有效多了!
timer
是 null
,調用 later()
,若 immediate
爲true
,那麼當即調用 func.apply(this, params)
;若是 immediate
爲 false
,那麼過 wait
以後,調用 func.apply(this, params)
timer
已經重置爲 null
(即 setTimeout 的倒計時結束),那麼流程與第一次觸發時同樣,若 timer
不爲 null
(即 setTimeout 的倒計時未結束),那麼清空定時器,從新開始計時。function debounce(func, wait, immediate = true) { let timer; // 延遲執行函數 const later = (context, args) => setTimeout(() => { timer = null;// 倒計時結束 if (!immediate) { func.apply(context, args); //執行回調 context = args = null; } }, wait); let debounced = function (...params) { let context = this; let args = params; if (!timer) { timer = later(context, args); if (immediate) { //當即執行 func.apply(context, args); } } else { clearTimeout(timer); //函數在每一個等待時延的結束被調用 timer = later(context, args); } } debounced.cancel = function () { clearTimeout(timer); timer = null; }; return debounced; };
immediate
爲 true 時,表示函數在每一個等待時延的開始被調用。
immediate
爲 false 時,表示函數在每一個等待時延的結束被調用。
只要高頻事件觸發,那麼回調函數至少被調用一次。
點擊查看更多
參考文章:
[1] https://www.ecma-internationa...
[2] 嗨,你真的懂this嗎?
[5] https://digcss.com/throttle-t...
謝謝各位小夥伴願意花費寶貴的時間閱讀本文,若是本文給了您一點幫助或者是啓發,請不要吝嗇你的贊和Star,您的確定是我前進的最大動力。https://github.com/YvetteLau/...
推薦關注本人公衆號: