今年來,各大公司都縮減了HC,甚至是採起了「裁人」措施,在這樣的大環境之下,想要得到一份更好的工做,必然須要付出更多的努力。javascript
本文挑選了20到大廠面試題,你們在閱讀時,建議不要先看個人答案,而是本身先思考一番。儘管,本文全部的答案,都是我在翻閱各類資料,思考並驗證以後,纔給出的。但因水平有限,本人的答案未必是最優的,若是您有更好的答案,歡迎給我留言。css
本文篇幅較長,但願小夥伴們可以堅持讀完,若是想加入交流羣,能夠經過文末的公衆號添加我爲好友。html
更多優質文章可戳: https://github.com/YvetteLau/...前端
new
的實現原理:html5
function _new() { let target = {}; //建立的新對象 //第一個參數是構造函數 let [constructor, ...args] = [...arguments]; //執行[[原型]]鏈接;target 是 constructor 的實例 target.__proto__ = constructor.prototype; //執行構造函數,將屬性或方法添加到建立的空對象上 let result = constructor.apply(target, args); if (result && (typeof (result) == "object" || typeof (result) == "function")) { //若是構造函數執行的結構返回的是一個對象,那麼返回這個對象 return result; } //若是構造函數返回的不是一個對象,返回建立的新對象 return target; }
若是用一句話說明 this 的指向,那麼便是: 誰調用它,this 就指向誰。java
可是僅經過這句話,咱們不少時候並不能準確判斷 this 的指向。所以咱們須要藉助一些規則去幫助本身:node
this 的指向能夠按照如下順序判斷:git
瀏覽器環境:不管是否在嚴格模式下,在全局執行環境中(在任何函數體外部)this 都指向全局對象 window
; github
node 環境:不管是否在嚴格模式下,在全局執行環境中(在任何函數體外部),this 都是空對象 {}
;面試
new
綁定若是是 new
綁定,而且構造函數中沒有返回 function 或者是 object,那麼 this 指向這個新對象。以下:
構造函數返回值不是 function 或 object。
new Super()
返回的是 this 對象。
function Super(age) { this.age = age; } let instance = new Super('26'); console.log(instance.age); //26
構造函數返回值是 function 或 object,
new Super()
是返回的是Super種返回的對象。
function Super(age) { this.age = age; let obj = {a: '2'}; return obj; } let instance = new Super('hello'); console.log(instance);//{ a: '2' } console.log(instance.age); //undefined
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
深拷貝和淺拷貝是針對複雜數據類型來講的,淺拷貝只拷貝一層,而深拷貝是層層拷貝。
深拷貝複製變量值,對於非基本類型的變量,則遞歸至基本類型變量後,再複製。 深拷貝後的對象與原來的對象是徹底隔離的,互不影響,對一個對象的修改並不會影響另外一個對象。
淺拷貝是會將對象的每一個屬性進行依次複製,可是當對象的屬性值是引用類型時,實質複製的是其引用,當引用指向的值改變時也會跟着變化。
可使用 for in
、 Object.assign
、 擴展運算符 ...
、Array.prototype.slice()
、Array.prototype.concat()
等,例如:
let obj = { name: 'Yvette', age: 18, hobbies: ['reading', 'photography'] } let obj2 = Object.assign({}, obj); let obj3 = {...obj}; obj.name = 'Jack'; obj.hobbies.push('coding'); console.log(obj);//{ name: 'Jack', age: 18,hobbies: [ 'reading', 'photography', 'coding' ] } console.log(obj2);//{ name: 'Yvette', age: 18,hobbies: [ 'reading', 'photography', 'coding' ] } console.log(obj3);//{ name: 'Yvette', age: 18,hobbies: [ 'reading', 'photography', 'coding' ] }
能夠看出淺拷貝只最第一層屬性進行了拷貝,當第一層的屬性值是基本數據類型時,新的對象和原對象互不影響,可是若是第一層的屬性值是複雜數據類型,那麼新對象和原對象的屬性值其指向的是同一塊內存地址。
1.深拷貝最簡單的實現是:
JSON.parse(JSON.stringify(obj))
JSON.parse(JSON.stringify(obj))
是最簡單的實現方式,可是有一些缺陷:
2.實現一個 deepClone 函數
RegExp
或者 Date
類型,返回對應類型function deepClone(obj, hash = new WeakMap()) { //遞歸拷貝 if (obj instanceof RegExp) return new RegExp(obj); if (obj instanceof Date) return new Date(obj); if (obj === null || typeof obj !== 'object') { //若是不是複雜數據類型,直接返回 return obj; } if (hash.has(obj)) { return hash.get(obj); } /** * 若是obj是數組,那麼 obj.constructor 是 [Function: Array] * 若是obj是對象,那麼 obj.constructor 是 [Function: Object] */ let t = new obj.constructor(); hash.set(obj, t); for (let key in obj) { //遞歸 if (obj.hasOwnProperty(key)) {//是不是自身的屬性 t[key] = deepClone(obj[key], hash); } } return t; }
call 和 apply 的功能相同,都是改變 this
的執行,並當即執行函數。區別在於傳參方式不一樣。
func.call(thisArg, arg1, arg2, ...)
:第一個參數是 this
指向的對象,其它參數依次傳入。func.apply(thisArg, [argsArray])
:第一個參數是 this
指向的對象,第二個參數是數組或類數組。一塊兒思考一下,如何模擬實現 call
?
首先,咱們知道,函數均可以調用 call
,說明 call
是函數原型上的方法,全部的實例均可以調用。即: Function.prototype.call
。
call
方法中獲取調用call()
函數window / global
(非嚴格模式)call
的第一個參數是 this 指向的對象,根據隱式綁定的規則,咱們知道 obj.foo()
, foo()
中的 this
指向 obj
;所以咱們能夠這樣調用函數 thisArgs.func(...args)
Function.prototype.call = function() { let [thisArg, ...args] = [...arguments]; if (!thisArg) { //context爲null或者是undefined thisArg = typeof window === 'undefined' ? global : window; } //this的指向的是當前函數 func (func.call) thisArg.func = this; //執行函數 let result = thisArg.func(...args); delete thisArg.func; //thisArg上並無 func 屬性,所以須要移除 return result; }
bind 的實現思路和 call
一致,僅參數處理略有差異。以下:
Function.prototype.apply = function(thisArg, rest) { let result; //函數返回結果 if (!thisArg) { //context爲null或者是undefined thisArg = typeof window === 'undefined' ? global : window; } //this的指向的是當前函數 func (func.call) thisArg.func = this; if(!rest) { //第二個參數爲 null / undefined result = thisArg.func(); }else { result = thisArg.func(...rest); } delete thisArg.func; //thisArg上並無 func 屬性,所以須要移除 return result; }
在開始以前,咱們首先須要搞清楚函數柯里化的概念。
函數柯里化是把接受多個參數的函數變換成接受一個單一參數(最初函數的第一個參數)的函數,而且返回接受餘下的參數並且返回結果的新函數的技術。
const curry = (fn, ...args) => args.length < fn.length //參數長度不足時,從新柯里化該函數,等待接受新參數 ? (...arguments) => curry(fn, ...args, ...arguments) //參數長度知足時,執行函數 : fn(...args);
function sumFn(a, b, c) { return a + b + c; } var sum = curry(sumFn); console.log(sum(2)(3)(5));//10 console.log(sum(2, 3, 5));//10 console.log(sum(2)(3, 5));//10 console.log(sum(2, 3)(5));//10
函數柯里化的主要做用:
- 利用隱式類型轉換
==
操做符在左右數據類型不一致時,會先進行隱式轉換。
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
//部署 [Symbol.toPrimitive] / valueOf/ toString 皆可 //一次返回1,2,3 便可。 let a = { [Symbol.toPrimitive]: (function(hint) { let i = 1; //閉包的特性之一:i 不會被回收 return function() { return i++; } })() }
- 利用數據劫持(Proxy/Object.definedProperty)
let i = 1; let a = new Proxy({}, { i: 1, get: function () { return () => this.i++; } });
- 數組的
toString
接口默認調用數組的join
方法,從新join
方法
let a = [1, 2, 3]; a.join = a.shift;
Box 是 CSS 佈局的對象和基本單位,頁面是由若干個Box組成的。
元素的類型 和 display
屬性,決定了這個 Box 的類型。不一樣類型的 Box 會參與不一樣的 Formatting Context。
Formatting Context
Formatting Context 是頁面的一塊渲染區域,而且有一套渲染規則,決定了其子元素將如何定位,以及和其它元素的關係和相互做用。
Formatting Context 有 BFC (Block formatting context),IFC (Inline formatting context),FFC (Flex formatting context) 和 GFC (Grid formatting context)。FFC 和 GFC 爲 CC3 中新增。
BFC佈局規則
margin
屬性決定。屬於同一個BFC的兩個相鄰Box的margin會發生重疊【符合合併原則的margin合併後是使用大的margin】如何建立BFC
BFC 的應用
margin
會發生重疊,觸發生成兩個BFC,即不會重疊)<script>
標籤中增長async
(html5) 或者defer
(html4) 屬性,腳本就會異步加載。
<script src="../XXX.js" defer></script>
defer
和 async
的區別在於:
defer
要等到整個頁面在內存中正常渲染結束(DOM 結構徹底生成,以及其餘腳本執行完成),在window.onload 以前執行;async
一旦下載完,渲染引擎就會中斷渲染,執行這個腳本之後,再繼續渲染。defer
腳本,會按照它們在頁面出現的順序加載async
腳本不能保證加載順序
動態建立
script
標籤
動態建立的 script
,設置 src
並不會開始下載,而是要添加到文檔中,JS文件纔會開始下載。
let script = document.createElement('script'); script.src = 'XXX.js'; // 添加到html文件中才會開始下載 document.body.append(script);
XHR 異步加載JS
let xhr = new XMLHttpRequest(); xhr.open("get", "js/xxx.js",true); xhr.send(); xhr.onreadystatechange = function() { if (xhr.readyState == 4 && xhr.status == 200) { eval(xhr.responseText); } }
ES5 有 6 種方式能夠實現繼承,分別爲:
原型鏈繼承的基本思想是利用原型讓一個引用類型繼承另外一個引用類型的屬性和方法。
function SuperType() { this.name = 'Yvette'; this.colors = ['pink', 'blue', 'green']; } SuperType.prototype.getName = function () { return this.name; } function SubType() { this.age = 22; } SubType.prototype = new SuperType(); SubType.prototype.getAge = function() { return this.age; } SubType.prototype.constructor = SubType; let instance1 = new SubType(); instance1.colors.push('yellow'); console.log(instance1.getName()); //'Yvette' console.log(instance1.colors);//[ 'pink', 'blue', 'green', 'yellow' ] let instance2 = new SubType(); console.log(instance2.colors);//[ 'pink', 'blue', 'green', 'yellow' ]
缺點:
借用構造函數的技術,其基本思想爲:
在子類型的構造函數中調用超類型構造函數。
function SuperType(name) { this.name = name; this.colors = ['pink', 'blue', 'green']; } function SubType(name) { SuperType.call(this, name); } let instance1 = new SubType('Yvette'); instance1.colors.push('yellow'); console.log(instance1.colors);//['pink', 'blue', 'green', yellow] let instance2 = new SubType('Jack'); console.log(instance2.colors); //['pink', 'blue', 'green']
優勢:
缺點:
組合繼承指的是將原型鏈和借用構造函數技術組合到一塊,從而發揮兩者之長的一種繼承模式。基本思路:
使用原型鏈實現對原型屬性和方法的繼承,經過借用構造函數來實現對實例屬性的繼承,既經過在原型上定義方法來實現了函數複用,又保證了每一個實例都有本身的屬性。
function SuperType(name) { this.name = name; this.colors = ['pink', 'blue', 'green']; } SuperType.prototype.sayName = function () { console.log(this.name); } function SuberType(name, age) { SuperType.call(this, name); this.age = age; } SuberType.prototype = new SuperType(); SuberType.prototype.constructor = SuberType; SuberType.prototype.sayAge = function () { console.log(this.age); } let instance1 = new SuberType('Yvette', 20); instance1.colors.push('yellow'); console.log(instance1.colors); //[ 'pink', 'blue', 'green', 'yellow' ] instance1.sayName(); //Yvette let instance2 = new SuberType('Jack', 22); console.log(instance2.colors); //[ 'pink', 'blue', 'green' ] instance2.sayName();//Jack
缺點:
優勢:
原型繼承的基本思想:
藉助原型能夠基於已有的對象建立新對象,同時還沒必要所以建立自定義類型。
function object(o) { function F() { } F.prototype = o; return new F(); }
在 object()
函數內部,先穿甲一個臨時性的構造函數,而後將傳入的對象做爲這個構造函數的原型,最後返回了這個臨時類型的一個新實例,從本質上講,object()
對傳入的對象執行了一次淺拷貝。
ECMAScript5經過新增 Object.create()
方法規範了原型式繼承。這個方法接收兩個參數:一個用做新對象原型的對象和(可選的)一個爲新對象定義額外屬性的對象(能夠覆蓋原型對象上的同名屬性),在傳入一個參數的狀況下,Object.create()
和 object()
方法的行爲相同。
var person = { name: 'Yvette', hobbies: ['reading', 'photography'] } var person1 = Object.create(person); person1.name = 'Jack'; person1.hobbies.push('coding'); var person2 = Object.create(person); person2.name = 'Echo'; person2.hobbies.push('running'); console.log(person.hobbies);//[ 'reading', 'photography', 'coding', 'running' ] console.log(person1.hobbies);//[ 'reading', 'photography', 'coding', 'running' ]
在沒有必要建立構造函數,僅讓一個對象與另外一個對象保持類似的狀況下,原型式繼承是能夠勝任的。
缺點:
同原型鏈實現繼承同樣,包含引用類型值的屬性會被全部實例共享。
寄生式繼承是與原型式繼承緊密相關的一種思路。寄生式繼承的思路與寄生構造函數和工廠模式相似,即建立一個僅用於封裝繼承過程的函數,該函數在內部已某種方式來加強對象,最後再像真地是它作了全部工做同樣返回對象。
function createAnother(original) { var clone = object(original);//經過調用函數建立一個新對象 clone.sayHi = function () {//以某種方式加強這個對象 console.log('hi'); }; return clone;//返回這個對象 } var person = { name: 'Yvette', hobbies: ['reading', 'photography'] }; var person2 = createAnother(person); person2.sayHi(); //hi
基於 person
返回了一個新對象 -—— person2
,新對象不只具備 person
的全部屬性和方法,並且還有本身的 sayHi()
方法。在考慮對象而不是自定義類型和構造函數的狀況下,寄生式繼承也是一種有用的模式。
缺點:
所謂寄生組合式繼承,即經過借用構造函數來繼承屬性,經過原型鏈的混成形式來繼承方法,基本思路:
沒必要爲了指定子類型的原型而調用超類型的構造函數,咱們須要的僅是超類型原型的一個副本,本質上就是使用寄生式繼承來繼承超類型的原型,而後再將結果指定給子類型的原型。寄生組合式繼承的基本模式以下所示:
function inheritPrototype(subType, superType) { var prototype = object(superType.prototype); //建立對象 prototype.constructor = subType;//加強對象 subType.prototype = prototype;//指定對象 }
constructor
屬性至此,咱們就能夠經過調用 inheritPrototype
來替換爲子類型原型賦值的語句:
function SuperType(name) { this.name = name; this.colors = ['pink', 'blue', 'green']; } //...code function SuberType(name, age) { SuperType.call(this, name); this.age = age; } SuberType.prototype = new SuperType(); inheritPrototype(SuberType, SuperType); //...code
優勢:
只調用了一次超類構造函數,效率更高。避免在SuberType.prototype
上面建立沒必要要的、多餘的屬性,與其同時,原型鏈還能保持不變。
所以寄生組合繼承是引用類型最理性的繼承範式。
隱藏類型
屏幕並非惟一的輸出機制,好比說屏幕上看不見的元素(隱藏的元素),其中一些依然可以被讀屏軟件閱讀出來(由於讀屏軟件依賴於可訪問性樹來闡述)。爲了消除它們之間的歧義,咱們將其歸爲三大類:
徹底隱藏
display
屬性display: none;
HTML5 新增屬性,至關於 display: none
<div hidden> </div>
視覺上的隱藏
position
和 盒模型 將元素移出可視區範圍posoition
爲 absolute
或 fixed
,經過設置 top
、left
等值,將其移出可視區域。position:absolute; left: -99999px;
position
爲 relative
,經過設置 top
、left
等值,將其移出可視區域。position: relative; left: -99999px; height: 0
margin-left: -99999px; height: 0;
transform: scale(0); height: 0;
translateX
, translateY
transform: translateX(-99999px); height: 0
rotate
transform: rotateY(90deg);
height: 0; width: 0; font-size: 0;
height: 0; width: 0; overflow: hidden;
opacity: 0;
visibility
屬性visibility: hidden;
z-index
屬性position: relative; z-index: -999;
再設置一個層級較高的元素覆蓋在此元素上。
clip-path: polygon(0 0, 0 0, 0 0, 0 0);
語義上的隱藏
讀屏軟件不可讀,佔據空間,可見。
<div aria-hidden="true"> </div>
聲明方式 | 變量提高 | 暫時性死區 | 重複聲明 | 塊做用域有效 | 初始值 | 從新賦值 |
---|---|---|---|---|---|---|
var | 會 | 不存在 | 容許 | 不是 | 非必須 | 容許 |
let | 不會 | 存在 | 不容許 | 是 | 非必須 | 容許 |
const | 不會 | 存在 | 不容許 | 是 | 必須 | 不容許 |
1.let/const 定義的變量不會出現變量提高,而 var 定義的變量會提高。
2.相同做用域中,let 和 const 不容許重複聲明,var 容許重複聲明。
3.const 聲明變量時必須設置初始值
4.const 聲明一個只讀的常量,這個常量不可改變。
這裏有一個很是重要的點便是:在JS中,複雜數據類型,存儲在棧中的是堆內存的地址,存在棧中的這個地址是不變的,可是存在堆中的值是能夠變得。有沒有至關常量指針/指針常量~
const a = 20; const b = { age: 18, star: 500 }
一圖勝萬言,以下圖所示,不變的是棧內存中 a 存儲的 20,和 b 中存儲的 0x0012ff21(瞎編的一個數字)。而 {age: 18, star: 200} 是可變的。
在開始說明JS上下文棧和做用域以前,咱們先說明下JS上下文以及做用域的概念。
執行上下文就是當前 JavaScript 代碼被解析和執行時所在環境的抽象概念, JavaScript 中運行任何的代碼都是在執行上下文中運行。
執行上下文類型分爲:
執行上下文建立過程當中,須要作如下幾件事:
做用域負責收集和維護由全部聲明的標識符(變量)組成的一系列查詢,並實施一套很是嚴格的規則,肯定當前執行的代碼對這些標識符的訪問權限。—— 摘錄自《你不知道的JavaScript》(上卷)
做用域有兩種工做模型:詞法做用域和動態做用域,JS採用的是詞法做用域工做模型,詞法做用域意味着做用域是由書寫代碼時變量和函數聲明的位置決定的。(with
和 eval
可以修改詞法做用域,可是不推薦使用,對此不作特別說明)
做用域分爲:
執行棧,也叫作調用棧,具備 LIFO (後進先出) 結構,用於存儲在代碼執行期間建立的全部執行上下文。
規則以下:
以一段代碼具體說明:
function fun3() { console.log('fun3') } function fun2() { fun3(); } function fun1() { fun2(); } fun1();
Global Execution Context
(即全局執行上下文)首先入棧,過程以下:
僞代碼:
//全局執行上下文首先入棧 ECStack.push(globalContext); //執行fun1(); ECStack.push(<fun1> functionContext); //fun1中又調用了fun2; ECStack.push(<fun2> functionContext); //fun2中又調用了fun3; ECStack.push(<fun3> functionContext); //fun3執行完畢 ECStack.pop(); //fun2執行完畢 ECStack.pop(); //fun1執行完畢 ECStack.pop(); //javascript繼續順序執行下面的代碼,但ECStack底部始終有一個 全局上下文(globalContext);
做用域鏈就是從當前做用域開始一層一層向上尋找某個變量,直到找到全局做用域仍是沒找到,就宣佈放棄。這種一層一層的關係,就是做用域鏈。
如:
var a = 10; function fn1() { var b = 20; console.log(fn2) function fn2() { a = 20 } return fn2; } fn1()();
fn2做用域鏈 = [fn2做用域, fn1做用域,全局做用域]
防抖函數的做用
防抖函數的做用就是控制函數在必定時間內的執行次數。防抖意味着N秒內函數只會被執行一次,若是N秒內再次被觸發,則從新計算延遲時間。
舉例說明: 小思最近在減肥,可是她很是吃吃零食。爲此,與其男友約定好,若是10天不吃零食,就能夠購買一個包(不要問爲何是包,由於包治百病)。可是若是中間吃了一次零食,那麼就要從新計算時間,直到小思堅持10天沒有吃零食,才能購買一個包。因此,管不住嘴的小思,沒有機會買包(悲傷的故事)... 這就是 防抖。
防抖函數實現
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 timeout, result; // 延遲執行函數 const later = (context, args) => setTimeout(() => { timeout = null;// 倒計時結束 if (!immediate) { //執行回調 result = func.apply(context, args); context = args = null; } }, wait); let debounced = function (...params) { if (!timeout) { timeout = later(this, params); if (immediate) { //當即執行 result = func.apply(this, params); } } else { clearTimeout(timeout); //函數在每一個等待時延的結束被調用 timeout = later(this, params); } return result; } //提供在外部清空定時器的方法 debounced.cancel = function () { clearTimeout(timer); timer = null; }; return debounced; };
immediate
爲 true 時,表示函數在每一個等待時延的開始被調用。immediate
爲 false 時,表示函數在每一個等待時延的結束被調用。
防抖的應用場景
節流函數的做用
節流函數的做用是規定一個單位時間,在這個單位時間內最多隻能觸發一次函數執行,若是這個單位時間內屢次觸發函數,只能有一次生效。
節流函數實現
function throttle(func, wait, options = {}) { var timeout, context, args, result; var previous = 0; var later = function () { previous = options.leading === false ? 0 : (Date.now() || new Date().getTime()); timeout = null; result = func.apply(context, args); if (!timeout) context = args = null; }; var throttled = function () { var now = Date.now() || new Date().getTime(); if (!previous && options.leading === false) previous = now; //remaining 爲距離下次執行 func 的時間 //remaining > wait,表示客戶端系統時間被調整過 var remaining = wait - (now - previous); context = this; args = arguments; //remaining 小於等於0,表示事件觸發的間隔時間大於設置的 wait if (remaining <= 0 || remaining > wait) { if (timeout) { //清空定時器 clearTimeout(timeout); timeout = null; } //重置 previous previous = now; //執行函數 result = func.apply(context, args); if (!timeout) context = args = null; } else if (!timeout && options.trailing !== false) { timeout = setTimeout(later, remaining); } return result; }; throttled.cancel = function () { clearTimeout(timeout); previous = 0; timeout = context = args = null; }; return throttled; }
禁用第一次首先執行,傳遞 {leading: false}
;想禁用最後一次執行,傳遞 {trailing: false}
節流的應用場景
《JavaScript高級程序設計》:
閉包是指有權訪問另外一個函數做用域中的變量的函數
《JavaScript權威指南》:
從技術的角度講,全部的JavaScript函數都是閉包:它們都是對象,它們都關聯到做用域鏈。
《你不知道的JavaScript》
當函數能夠記住並訪問所在的詞法做用域時,就產生了閉包,即便函數是在當前詞法做用域以外執行。
function foo() { var a = 2; return function fn() { console.log(a); } } let func = foo(); func(); //輸出2
閉包使得函數能夠繼續訪問定義時的詞法做用域。拜 fn 所賜,在 foo() 執行後,foo 內部做用域不會被銷燬。
function base() { let x = 10; //私有變量 return { getX: function() { return x; } } } let obj = base(); console.log(obj.getX()); //10
var a = []; for (var i = 0; i < 10; i++) { a[i] = (function(j){ return function () { console.log(j); } })(i); } a[6](); // 6
function coolModule() { let name = 'Yvette'; let age = 20; function sayName() { console.log(name); } function sayAge() { console.log(age); } return { sayName, sayAge } } let info = coolModule(); info.sayName(); //'Yvette'
模塊模式具備兩個必備的條件(來自《你不知道的JavaScript》)
在實現 Promise.all 方法以前,咱們首先要知道 Promise.all 的功能和特色,由於在清楚了 Promise.all 功能和特色的狀況下,咱們才能進一步去寫實現。
Promise.all 功能
Promise.all(iterable)
返回一個新的 Promise 實例。此實例在 iterable
參數內全部的 promise
都 fulfilled
或者參數中不包含 promise
時,狀態變成 fulfilled
;若是參數中 promise
有一個失敗rejected
,此實例回調失敗,失敗緣由的是第一個失敗 promise
的返回結果。
let p = Promise.all([p1, p2, p3]);
p的狀態由 p1,p2,p3決定,分紅如下;兩種狀況:
(1)只有p一、p二、p3的狀態都變成 fulfilled
,p的狀態纔會變成 fulfilled
,此時p一、p二、p3的返回值組成一個數組,傳遞給p的回調函數。
(2)只要p一、p二、p3之中有一個被 rejected
,p的狀態就變成 rejected
,此時第一個被reject的實例的返回值,會傳遞給p的回調函數。
Promise.all 的特色
Promise.all 的返回值是一個 promise 實例
Promise.all
會 同步 返回一個已完成狀態的 promise
Promise.all
會 異步 返回一個已完成狀態的 promise
Promise.all
返回一個 處理中(pending) 狀態的 promise
.Promise.all 返回的 promise 的狀態
Promise.all
返回的 promise
異步地變爲完成。promise
失敗,Promise.all
異步地將失敗的那個結果給失敗狀態的回調函數,而無論其它 promise
是否完成Promise.all
返回的 promise
的完成狀態的結果都是一個數組Promise.all 實現
Promise.all = function (promises) { //promises 是可迭代對象,省略參數合法性檢查 return new Promise((resolve, reject) => { //Array.from 將可迭代對象轉換成數組 promises = Array.from(promises); if (promises.length === 0) { resolve([]); } else { let result = []; let index = 0; for (let i = 0; i < promises.length; i++ ) { //考慮到 i 多是 thenable 對象也多是普通值 Promise.resolve(promises[i]).then(data => { result[i] = data; if (++index === promises.length) { //全部的 promises 狀態都是 fulfilled,promise.all返回的實例才變成 fulfilled 態 resolve(result); } }, err => { reject(err); return; }); } } }); }
例如:
flattenDeep([1, [2, [3, [4]], 5]]); //[1, 2, 3, 4, 5]
利用 Array.prototype.flat
ES6 爲數組實例新增了 flat
方法,用於將嵌套的數組「拉平」,變成一維的數組。該方法返回一個新數組,對原數組沒有影響。
flat
默認只會 「拉平」 一層,若是想要 「拉平」 多層的嵌套數組,須要給 flat
傳遞一個整數,表示想要拉平的層數。
function flattenDeep(arr, deepLength) { return arr.flat(deepLength); } console.log(flattenDeep([1, [2, [3, [4]], 5]], 3));
當傳遞的整數大於數組嵌套的層數時,會將數組拉平爲一維數組,JS能表示的最大數字爲 Math.pow(2, 53) - 1
,所以咱們能夠這樣定義 flattenDeep
函數
function flattenDeep(arr) { //固然,大多時候咱們並不會有這麼多層級的嵌套 return arr.flat(Math.pow(2,53) - 1); } console.log(flattenDeep([1, [2, [3, [4]], 5]]));
利用 reduce 和 concat
function flattenDeep(arr){ return arr.reduce((acc, val) => Array.isArray(val) ? acc.concat(flattenDeep(val)) : acc.concat(val), []); } console.log(flattenDeep([1, [2, [3, [4]], 5]]));
使用 stack 無限反嵌套多層嵌套數組
function flattenDeep(input) { const stack = [...input]; const res = []; while (stack.length) { // 使用 pop 從 stack 中取出並移除值 const next = stack.pop(); if (Array.isArray(next)) { // 使用 push 送回內層數組中的元素,不會改動原始輸入 original input stack.push(...next); } else { res.push(next); } } // 使用 reverse 恢復原數組的順序 return res.reverse(); } console.log(flattenDeep([1, [2, [3, [4]], 5]]));
例如:
uniq([1, 2, 3, 5, 3, 2]);//[1, 2, 3, 5]
法1: 利用ES6新增數據類型
Set
Set
相似於數組,可是成員的值都是惟一的,沒有重複的值。
function uniq(arry) { return [...new Set(arry)]; }
法2: 利用
indexOf
function uniq(arry) { var result = []; for (var i = 0; i < arry.length; i++) { if (result.indexOf(arry[i]) === -1) { //如 result 中沒有 arry[i],則添加到數組中 result.push(arry[i]) } } return result; }
法3: 利用
includes
function uniq(arry) { var result = []; for (var i = 0; i < arry.length; i++) { if (!result.includes(arry[i])) { //如 result 中沒有 arry[i],則添加到數組中 result.push(arry[i]) } } return result; }
法4:利用
reduce
function uniq(arry) { return arry.reduce((prev, cur) => prev.includes(cur) ? prev : [...prev, cur], []); }
法5:利用
Map
function uniq(arry) { let map = new Map(); let result = new Array(); for (let i = 0; i < arry.length; i++) { if (map.has(arry[i])) { map.set(arry[i], true); } else { map.set(arry[i], false); result.push(arry[i]); } } return result; }
ES6 規定,默認的 Iterator
接口部署在數據結構的 Symbol.iterator
屬性,換個角度,也能夠認爲,一個數據結構只要具備 Symbol.iterator
屬性(Symbol.iterator
方法對應的是遍歷器生成函數,返回的是一個遍歷器對象),那麼就能夠其認爲是可迭代的。
Symbol.iterator
屬性,Symbol.iterator()
返回的是一個遍歷器對象for ... of
進行循環Array.from
轉換爲數組let arry = [1, 2, 3, 4]; let iter = arry[Symbol.iterator](); console.log(iter.next()); //{ value: 1, done: false } console.log(iter.next()); //{ value: 2, done: false } console.log(iter.next()); //{ value: 3, done: false }
Iterator
接口的數據結構:儘管瀏覽器有同源策略,可是 <script>
標籤的 src
屬性不會被同源策略所約束,能夠獲取任意服務器上的腳本並執行。jsonp
經過插入 script
標籤的方式來實現跨域,參數只能經過 url
傳入,僅能支持 get
請求。
實現原理:
jsonp源碼實現
function jsonp({url, params, callback}) { return new Promise((resolve, reject) => { //建立script標籤 let script = document.createElement('script'); //將回調函數掛在 window 上 window[callback] = function(data) { resolve(data); //代碼執行後,刪除插入的script標籤 document.body.removeChild(script); } //回調函數加在請求地址上 params = {...params, callback} //wb=b&callback=show let arrs = []; for(let key in params) { arrs.push(`${key}=${params[key]}`); } script.src = `${url}?${arrs.join('&')}`; document.body.appendChild(script); }); }
使用:
function show(data) { console.log(data); } jsonp({ url: 'http://localhost:3000/show', params: { //code }, callback: 'show' }).then(data => { console.log(data); });
服務端代碼(node):
//express啓動一個後臺服務 let express = require('express'); let app = express(); app.get('/show', (req, res) => { let {callback} = req.query; //獲取傳來的callback函數名,callback是key res.send(`${callback}('Hello!')`); }); app.listen(3000);
[1] [JavaScript高級程序設計第六章]
[2] Step-By-Step】高頻面試題深刻解析 / 週刊01
[3] Step-By-Step】高頻面試題深刻解析 / 週刊02
[4] Step-By-Step】高頻面試題深刻解析 / 週刊03
[5] Step-By-Step】高頻面試題深刻解析 / 週刊04
謝謝各位小夥伴願意花費寶貴的時間閱讀本文,若是本文給了您一點幫助或者是啓發,請不要吝嗇你的贊和Star,您的確定是我前進的最大動力。 https://github.com/YvetteLau/...
推薦關注本人公衆號: