摘要:只能遍歷數(shù)組,不能中斷,返回值是修改后的數(shù)組。這在語法上,稱為暫時性死區(qū)。作用域鏈無論是還是查詢,都會在當(dāng)前的作用域開始查找,如果沒有找到,就會向上級作用域繼續(xù)查找目標(biāo)標(biāo)識符,每次上升一個作用域,一直到全局作用域為止。
互聯(lián)網(wǎng)寒冬之際,各大公司都縮減了HC,甚至是采取了“裁員”措施,在這樣的大環(huán)境之下,想要獲得一份更好的工作,必然需要付出更多的努力。
一年前,也許你搞清楚閉包,this,原型鏈,就能獲得認可。但是現(xiàn)在,很顯然是不行了。本文梳理出了一些面試中有一定難度的高頻原生JS問題,部分知識點可能你之前從未關(guān)注過,或者看到了,卻沒有仔細研究,但是它們卻非常重要。本文將以真實的面試題的形式來呈現(xiàn)知識點,大家在閱讀時,建議不要先看我的答案,而是自己先思考一番。盡管,本文所有的答案,都是我在翻閱各種資料,思考并驗證之后,才給出的(絕非復(fù)制粘貼而來)。但因水平有限,本人的答案未必是最優(yōu)的,如果您有更好的答案,歡迎給我留言。
本文篇幅較長,但是滿滿的都是干貨!并且還埋伏了可愛的表情包,希望小伙伴們能夠堅持讀完。
衷心的祝愿大家都能找到心儀的工作。
1. 原始類型有哪幾種?null 是對象嗎?原始數(shù)據(jù)類型和復(fù)雜數(shù)據(jù)類型存儲有什么區(qū)別?
原始類型有6種,分別是undefined,null,bool,string,number,symbol(ES6新增)。
雖然 typeof null 返回的值是 object,但是null不是對象,而是基本數(shù)據(jù)類型的一種。
原始數(shù)據(jù)類型存儲在棧內(nèi)存,存儲的是值。
復(fù)雜數(shù)據(jù)類型存儲在堆內(nèi)存,存儲的是地址。當(dāng)我們把對象賦值給另外一個變量的時候,復(fù)制的是地址,指向同一塊內(nèi)存空間,當(dāng)其中一個對象改變時,另一個對象也會變化。
2. typeof 是否正確判斷類型? instanceof呢? instanceof 的實現(xiàn)原理是什么?
首先 typeof 能夠正確的判斷基本數(shù)據(jù)類型,但是除了 null, typeof null輸出的是對象。
但是對象來說,typeof 不能正確的判斷其類型, typeof 一個函數(shù)可以輸出 "function",而除此之外,輸出的全是 object,這種情況下,我們無法準(zhǔn)確的知道對象的類型。
instanceof可以準(zhǔn)確的判斷復(fù)雜數(shù)據(jù)類型,但是不能正確判斷基本數(shù)據(jù)類型。(正確判斷數(shù)據(jù)類型請戳:https://github.com/YvetteLau/...
instanceof 是通過原型鏈判斷的,A instanceof B, 在A的原型鏈中層層查找,是否有原型等于B.prototype,如果一直找到A的原型鏈的頂端(null;即Object.prototype.__proto__),仍然不等于B.prototype,那么返回false,否則返回true.
instanceof的實現(xiàn)代碼:
// L instanceof R function instance_of(L, R) {//L 表示左表達式,R 表示右表達式 var O = R.prototype;// 取 R 的顯式原型 L = L.__proto__; // 取 L 的隱式原型 while (true) { if (L === null) //已經(jīng)找到頂層 return false; if (O === L) //當(dāng) O 嚴(yán)格等于 L 時,返回 true return true; L = L.__proto__; //繼續(xù)向上一層原型鏈查找 } }
3. for of , for in 和 forEach,map 的區(qū)別。
for...of循環(huán):具有 iterator 接口,就可以用for...of循環(huán)遍歷它的成員(屬性值)。for...of循環(huán)可以使用的范圍包括數(shù)組、Set 和 Map 結(jié)構(gòu)、某些類似數(shù)組的對象、Generator 對象,以及字符串。for...of循環(huán)調(diào)用遍歷器接口,數(shù)組的遍歷器接口只返回具有數(shù)字索引的屬性。對于普通的對象,for...of結(jié)構(gòu)不能直接使用,會報錯,必須部署了 Iterator 接口后才能使用??梢灾袛嘌h(huán)。
for...in循環(huán):遍歷對象自身的和繼承的可枚舉的屬性, 不能直接獲取屬性值。可以中斷循環(huán)。
forEach: 只能遍歷數(shù)組,不能中斷,沒有返回值(或認為返回值是undefined)。
map: 只能遍歷數(shù)組,不能中斷,返回值是修改后的數(shù)組。
PS: Object.keys():返回給定對象所有可枚舉屬性的字符串?dāng)?shù)組。
關(guān)于forEach是否會改變原數(shù)組的問題,有些小伙伴提出了異議,為此我寫了代碼測試了下(注意數(shù)組項是復(fù)雜數(shù)據(jù)類型的情況)。
除了forEach之外,map等API,也有同樣的問題。
let arry = [1, 2, 3, 4]; arry.forEach((item) => { item *= 10; }); console.log(arry); //[1, 2, 3, 4] arry.forEach((item) => { arry[1] = 10; //直接操作數(shù)組 }); console.log(arry); //[ 1, 10, 3, 4 ] let arry2 = [ { name: "Yve" }, { age: 20 } ]; arry2.forEach((item) => { item.name = 10; }); console.log(arry2);//[ { name: 10 }, { age: 20, name: 10 } ]
如還不了解 iterator 接口或 for...of, 請先閱讀ES6文檔: Iterator 和 for...of 循環(huán)
更多細節(jié)請戳: https://github.com/YvetteLau/...
4. 如何判斷一個變量是不是數(shù)組?
使用 Array.isArray 判斷,如果返回 true, 說明是數(shù)組
使用 instanceof Array 判斷,如果返回true, 說明是數(shù)組
使用 Object.prototype.toString.call 判斷,如果值是 [object Array], 說明是數(shù)組
通過 constructor 來判斷,如果是數(shù)組,那么 arr.constructor === Array. (不準(zhǔn)確,因為我們可以指定 obj.constructor = Array)
function fn() { console.log(Array.isArray(arguments)); //false; 因為arguments是類數(shù)組,但不是數(shù)組 console.log(Array.isArray([1,2,3,4])); //true console.log(arguments instanceof Array); //fasle console.log([1,2,3,4] instanceof Array); //true console.log(Object.prototype.toString.call(arguments)); //[object Arguments] console.log(Object.prototype.toString.call([1,2,3,4])); //[object Array] console.log(arguments.constructor === Array); //false arguments.constructor = Array; console.log(arguments.constructor === Array); //true console.log(Array.isArray(arguments)); //false } fn(1,2,3,4);
5. 類數(shù)組和數(shù)組的區(qū)別是什么?
類數(shù)組:
1)擁有l(wèi)ength屬性,其它屬性(索引)為非負整數(shù)(對象中的索引會被當(dāng)做字符串來處理);
2)不具有數(shù)組所具有的方法;
類數(shù)組是一個普通對象,而真實的數(shù)組是Array類型。
常見的類數(shù)組有: 函數(shù)的參數(shù) arugments, DOM 對象列表(比如通過 document.querySelectorAll 得到的列表), jQuery 對象 (比如 $("div")).
類數(shù)組可以轉(zhuǎn)換為數(shù)組:
//第一種方法 Array.prototype.slice.call(arrayLike, start); //第二種方法 [...arrayLike]; //第三種方法: Array.from(arrayLike);
PS: 任何定義了遍歷器(Iterator)接口的對象,都可以用擴展運算符轉(zhuǎn)為真正的數(shù)組。
Array.from方法用于將兩類對象轉(zhuǎn)為真正的數(shù)組:類似數(shù)組的對象(array-like object)和可遍歷(iterable)的對象。
6. == 和 === 有什么區(qū)別?
=== 不需要進行類型轉(zhuǎn)換,只有類型相同并且值相等時,才返回 true.
== 如果兩者類型不同,首先需要進行類型轉(zhuǎn)換。具體流程如下:
首先判斷兩者類型是否相同,如果相等,判斷值是否相等.
如果類型不同,進行類型轉(zhuǎn)換
判斷比較的是否是 null 或者是 undefined, 如果是, 返回 true .
判斷兩者類型是否為 string 和 number, 如果是, 將字符串轉(zhuǎn)換成 number
判斷其中一方是否為 boolean, 如果是, 將 boolean 轉(zhuǎn)為 number 再進行判斷
判斷其中一方是否為 object 且另一方為 string、number 或者 symbol , 如果是, 將 object 轉(zhuǎn)為原始類型再進行判斷
let person1 = { age: 25 } let person2 = person1; person2.gae = 20; console.log(person1 === person2); //true,注意復(fù)雜數(shù)據(jù)類型,比較的是引用地址
思考: [] == ![]
我們來分析一下: [] == ![] 是true還是false?
首先,我們需要知道 ! 優(yōu)先級是高于 == (更多運算符優(yōu)先級可查看: 運算符優(yōu)先級)
![] 引用類型轉(zhuǎn)換成布爾值都是true,因此![]的是false
根據(jù)上面的比較步驟中的第五條,其中一方是 boolean,將 boolean 轉(zhuǎn)為 number 再進行判斷,false轉(zhuǎn)換成 number,對應(yīng)的值是 0.
根據(jù)上面比較步驟中的第六條,有一方是 number,那么將object也轉(zhuǎn)換成Number,空數(shù)組轉(zhuǎn)換成數(shù)字,對應(yīng)的值是0.(空數(shù)組轉(zhuǎn)換成數(shù)字,對應(yīng)的值是0,如果數(shù)組中只有一個數(shù)字,那么轉(zhuǎn)成number就是這個數(shù)字,其它情況,均為NaN)
0 == 0; 為true
7. ES6中的class和ES5的類有什么區(qū)別?
ES6 class 內(nèi)部所有定義的方法都是不可枚舉的;
ES6 class 必須使用 new 調(diào)用;
ES6 class 不存在變量提升;
ES6 class 默認即是嚴(yán)格模式;
ES6 class 子類必須在父類的構(gòu)造函數(shù)中調(diào)用super(),這樣才有this對象;ES5中類繼承的關(guān)系是相反的,先有子類的this,然后用父類的方法應(yīng)用在this上。
8. 數(shù)組的哪些API會改變原數(shù)組?
修改原數(shù)組的API有:
splice/reverse/fill/copyWithin/sort/push/pop/unshift/shift
不修改原數(shù)組的API有:
slice/map/forEach/every/filter/reduce/entries/find
注: 數(shù)組的每一項是簡單數(shù)據(jù)類型,且未直接操作數(shù)組的情況下。
9. let、const 以及 var 的區(qū)別是什么?
let 和 const 定義的變量不會出現(xiàn)變量提升,而 var 定義的變量會提升。
let 和 const 是JS中的塊級作用域
let 和 const 不允許重復(fù)聲明(會拋出錯誤)
let 和 const 定義的變量在定義語句之前,如果使用會拋出錯誤(形成了暫時性死區(qū)),而 var 不會。
const 聲明一個只讀的常量。一旦聲明,常量的值就不能改變(如果聲明是一個對象,那么不能改變的是對象的引用地址)
10. 在JS中什么是變量提升?什么是暫時性死區(qū)?
變量提升就是變量在聲明之前就可以使用,值為undefined。
在代碼塊內(nèi),使用 let/const 命令聲明變量之前,該變量都是不可用的(會拋出錯誤)。這在語法上,稱為“暫時性死區(qū)”。暫時性死區(qū)也意味著 typeof 不再是一個百分百安全的操作。
typeof x; // ReferenceError(暫時性死區(qū),拋錯) let x;
typeof y; // 值是undefined,不會報錯
暫時性死區(qū)的本質(zhì)就是,只要一進入當(dāng)前作用域,所要使用的變量就已經(jīng)存在了,但是不可獲取,只有等到聲明變量的那一行代碼出現(xiàn),才可以獲取和使用該變量。
11. 如何正確的判斷this? 箭頭函數(shù)的this是什么?
this的綁定規(guī)則有四種:默認綁定,隱式綁定,顯式綁定,new綁定.
函數(shù)是否在 new 中調(diào)用(new綁定),如果是,那么 this 綁定的是新創(chuàng)建的對象。
函數(shù)是否通過 call,apply 調(diào)用,或者使用了 bind (即硬綁定),如果是,那么this綁定的就是指定的對象。
函數(shù)是否在某個上下文對象中調(diào)用(隱式綁定),如果是的話,this 綁定的是那個上下文對象。一般是 obj.foo()
如果以上都不是,那么使用默認綁定。如果在嚴(yán)格模式下,則綁定到 undefined,否則綁定到全局對象。
如果把 null 或者 undefined 作為 this 的綁定對象傳入 call、apply 或者 bind, 這些值在調(diào)用時會被忽略,實際應(yīng)用的是默認綁定規(guī)則。
箭頭函數(shù)沒有自己的 this, 它的this繼承于上一層代碼塊的this。
測試下是否已經(jīng)成功Get了此知識點(瀏覽器執(zhí)行環(huán)境):
var number = 5; var obj = { number: 3, fn1: (function () { var number; this.number *= 2; number = number * 2; number = 3; return function () { var num = this.number; this.number *= 2; console.log(num); number *= 3; console.log(number); } })() } var fn1 = obj.fn1; fn1.call(null); obj.fn1(); console.log(window.number);
如果this的知識點,您還不太懂,請戳: 嗨,你真的懂this嗎?
12. 詞法作用域和this的區(qū)別。
詞法作用域是由你在寫代碼時將變量和塊作用域?qū)懺谀睦飦頉Q定的
this 是在調(diào)用時被綁定的,this 指向什么,完全取決于函數(shù)的調(diào)用位置(關(guān)于this的指向問題,本文已經(jīng)有說明)
13. 談?wù)勀銓S執(zhí)行上下文棧和作用域鏈的理解。
執(zhí)行上下文就是當(dāng)前 JavaScript 代碼被解析和執(zhí)行時所在環(huán)境, JS執(zhí)行上下文??梢哉J為是一個存儲函數(shù)調(diào)用的棧結(jié)構(gòu),遵循先進后出的原則。
JavaScript執(zhí)行在單線程上,所有的代碼都是排隊執(zhí)行。
一開始瀏覽器執(zhí)行全局的代碼時,首先創(chuàng)建全局的執(zhí)行上下文,壓入執(zhí)行棧的頂部。
每當(dāng)進入一個函數(shù)的執(zhí)行就會創(chuàng)建函數(shù)的執(zhí)行上下文,并且把它壓入執(zhí)行棧的頂部。當(dāng)前函數(shù)執(zhí)行-完成后,當(dāng)前函數(shù)的執(zhí)行上下文出棧,并等待垃圾回收。
瀏覽器的JS執(zhí)行引擎總是訪問棧頂?shù)膱?zhí)行上下文。
全局上下文只有唯一的一個,它在瀏覽器關(guān)閉時出棧。
作用域鏈: 無論是 LHS 還是 RHS 查詢,都會在當(dāng)前的作用域開始查找,如果沒有找到,就會向上級作用域繼續(xù)查找目標(biāo)標(biāo)識符,每次上升一個作用域,一直到全局作用域為止。
題難不難?不難!繼續(xù)挑戰(zhàn)一下難!知道難,就更要繼續(xù)了!
閉包是指有權(quán)訪問另一個函數(shù)作用域中的變量的函數(shù),創(chuàng)建閉包最常用的方式就是在一個函數(shù)內(nèi)部創(chuàng)建另一個函數(shù)。
閉包的作用有:
封裝私有變量
模仿塊級作用域(ES5中沒有塊級作用域)
實現(xiàn)JS的模塊
15. call、apply有什么區(qū)別?call,aplly和bind的內(nèi)部是如何實現(xiàn)的?
call 和 apply 的功能相同,區(qū)別在于傳參的方式不一樣:
fn.call(obj, arg1, arg2, ...),調(diào)用一個函數(shù), 具有一個指定的this值和分別地提供的參數(shù)(參數(shù)的列表)。
fn.apply(obj, [argsArray]),調(diào)用一個函數(shù),具有一個指定的this值,以及作為一個數(shù)組(或類數(shù)組對象)提供的參數(shù)。
call核心:
將函數(shù)設(shè)為傳入?yún)?shù)的屬性
指定this到函數(shù)并傳入給定參數(shù)執(zhí)行函數(shù)
如果不傳入?yún)?shù)或者參數(shù)為null,默認指向為 window / global
刪除參數(shù)上的函數(shù)
Function.prototype.call = function (context) { /** 如果第一個參數(shù)傳入的是 null 或者是 undefined, 那么指向this指向 window/global */ /** 如果第一個參數(shù)傳入的不是null或者是undefined, 那么必須是一個對象 */ if (!context) { //context為null或者是undefined context = typeof window === "undefined" ? global : window; } context.fn = this; //this指向的是當(dāng)前的函數(shù)(Function的實例) let args = [...arguments].slice(1);//獲取除了this指向?qū)ο笠酝獾膮?shù), 空數(shù)組slice后返回的仍然是空數(shù)組 let result = context.fn(...args); //隱式綁定,當(dāng)前函數(shù)的this指向了context. delete context.fn; return result; } //測試代碼 var foo = { name: "Selina" } var name = "Chirs"; function bar(job, age) { console.log(this.name); console.log(job, age); } bar.call(foo, "programmer", 20); // Selina programmer 20 bar.call(null, "teacher", 25); // 瀏覽器環(huán)境: Chirs teacher 25; node 環(huán)境: undefined teacher 25
apply:
apply的實現(xiàn)和call很類似,但是需要注意他們的參數(shù)是不一樣的,apply的第二個參數(shù)是數(shù)組或類數(shù)組.
Function.prototype.apply = function (context, rest) { if (!context) { //context為null或者是undefined時,設(shè)置默認值 context = typeof window === "undefined" ? global : window; } context.fn = this; let result; if(rest === undefined || rest === null) { //undefined 或者 是 null 不是 Iterator 對象,不能被 ... result = context.fn(rest); }else if(typeof rest === "object") { result = context.fn(...rest); } delete context.fn; return result; } var foo = { name: "Selina" } var name = "Chirs"; function bar(job, age) { console.log(this.name); console.log(job, age); } bar.apply(foo, ["programmer", 20]); // Selina programmer 20 bar.apply(null, ["teacher", 25]); // 瀏覽器環(huán)境: Chirs programmer 20; node 環(huán)境: undefined teacher 25
bind
bind 和 call/apply 有一個很重要的區(qū)別,一個函數(shù)被 call/apply 的時候,會直接調(diào)用,但是 bind 會創(chuàng)建一個新函數(shù)。當(dāng)這個新函數(shù)被調(diào)用時,bind() 的第一個參數(shù)將作為它運行時的 this,之后的一序列參數(shù)將會在傳遞的實參前傳入作為它的參數(shù)。
Function.prototype.my_bind = function(context) { if(typeof this !== "function"){ throw new TypeError("not a function"); } let self = this; let args = [...arguments].slice(1); function Fn() {}; Fn.prototype = this.prototype; let bound = function() { let res = [...args, ...arguments]; //bind傳遞的參數(shù)和函數(shù)調(diào)用時傳遞的參數(shù)拼接 context = this instanceof Fn ? this : context || this; return self.apply(context, res); } //原型鏈 bound.prototype = new Fn(); return bound; } var name = "Jack"; function person(age, job, gender){ console.log(this.name , age, job, gender); } var Yve = {name : "Yvette"}; let result = person.my_bind(Yve, 22, "enginner")("female");
16. new的原理是什么?通過new的方式創(chuàng)建對象和通過字面量創(chuàng)建有什么區(qū)別?
new:
創(chuàng)建一個新對象。
這個新對象會被執(zhí)行[[原型]]連接。
將構(gòu)造函數(shù)的作用域賦值給新對象,即this指向這個新對象.
如果函數(shù)沒有返回其他對象,那么new表達式中的函數(shù)調(diào)用會自動返回這個新對象。
function new(func) { lat target = {}; target.__proto__ = func.prototype; let res = func.call(target); if (typeof(res) == "object" || typeof(res) == "function") { return res; } return target; }
字面量創(chuàng)建對象,不會調(diào)用 Object構(gòu)造函數(shù), 簡潔且性能更好;
new Object() 方式創(chuàng)建對象本質(zhì)上是方法調(diào)用,涉及到在proto鏈中遍歷該方法,當(dāng)找到該方法后,又會生產(chǎn)方法調(diào)用必須的 堆棧信息,方法調(diào)用結(jié)束后,還要釋放該堆棧,性能不如字面量的方式。
通過對象字面量定義對象時,不會調(diào)用Object構(gòu)造函數(shù)。
17. 談?wù)勀銓υ偷睦斫猓?/b>
在 JavaScript 中,每當(dāng)定義一個對象(函數(shù)也是對象)時候,對象中都會包含一些預(yù)定義的屬性。其中每個函數(shù)對象都有一個prototype 屬性,這個屬性指向函數(shù)的原型對象。使用原型對象的好處是所有對象實例共享它所包含的屬性和方法。
18. 什么是原型鏈?【原型鏈解決的是什么問題?】
原型鏈解決的主要是繼承問題。
每個對象擁有一個原型對象,通過 proto (讀音: dunder proto) 指針指向其原型對象,并從中繼承方法和屬性,同時原型對象也可能擁有原型,這樣一層一層,最終指向 null(Object.proptotype.__proto__ 指向的是null)。這種關(guān)系被稱為原型鏈 (prototype chain),通過原型鏈一個對象可以擁有定義在其他對象中的屬性和方法。
構(gòu)造函數(shù) Parent、Parent.prototype 和 實例 p 的關(guān)系如下:(p.__proto__ === Parent.prototype)
19. prototype 和 __proto__ 區(qū)別是什么?
prototype是構(gòu)造函數(shù)的屬性。
__proto__ 是每個實例都有的屬性,可以訪問 [[prototype]] 屬性。
實例的__proto__ 與其構(gòu)造函數(shù)的prototype指向的是同一個對象。
function Student(name) { this.name = name; } Student.prototype.setAge = function(){ this.age=20; } let Jack = new Student("jack"); console.log(Jack.__proto__); //console.log(Object.getPrototypeOf(Jack));; console.log(Student.prototype); console.log(Jack.__proto__ === Student.prototype);//true
20. 使用ES5實現(xiàn)一個繼承?
組合繼承(最常用的繼承方式)
function SuperType() { this.name = name; this.colors = ["red", "blue", "green"]; } SuperType.prototype.sayName = function() { console.log(this.name); } function SubType(name, age) { SuperType.call(this, name); this.age = age; } SubType.prototype = new SuperType(); SubType.prototype.constructor = SubType; SubType.prototype.sayAge = function() { console.log(this.age); }
其它繼承方式實現(xiàn),可以參考《JavaScript高級程序設(shè)計》
21. 什么是深拷貝?深拷貝和淺拷貝有什么區(qū)別?
淺拷貝是指只復(fù)制第一層對象,但是當(dāng)對象的屬性是引用類型時,實質(zhì)復(fù)制的是其引用,當(dāng)引用指向的值改變時也會跟著變化。
深拷貝復(fù)制變量值,對于非基本類型的變量,則遞歸至基本類型變量后,再復(fù)制。深拷貝后的對象與原來的對象是完全隔離的,互不影響,對一個對象的修改并不會影響另一個對象。
實現(xiàn)一個深拷貝:
function deepClone(obj) { //遞歸拷貝 if(obj === null) return null; //null 的情況 if(obj instanceof RegExp) return new RegExp(obj); if(obj instanceof Date) return new Date(obj); if(typeof obj !== "object") { //如果不是復(fù)雜數(shù)據(jù)類型,直接返回 return obj; } /** * 如果obj是數(shù)組,那么 obj.constructor 是 [Function: Array] * 如果obj是對象,那么 obj.constructor 是 [Function: Object] */ let t = new obj.constructor(); for(let key in obj) { //如果 obj[key] 是復(fù)雜數(shù)據(jù)類型,遞歸 t[key] = deepClone(obj[key]); } return t; }
看不下去了?別人的送分題會成為你的送命題
防抖和節(jié)流的作用都是防止函數(shù)多次調(diào)用。區(qū)別在于,假設(shè)一個用戶一直觸發(fā)這個函數(shù),且每次觸發(fā)函數(shù)的間隔小于設(shè)置的時間,防抖的情況下只會調(diào)用一次,而節(jié)流的情況會每隔一定時間調(diào)用一次函數(shù)。
防抖(debounce): n秒內(nèi)函數(shù)只會執(zhí)行一次,如果n秒內(nèi)高頻事件再次被觸發(fā),則重新計算時間
function debounce(func, wait, immediate=true) { let timeout, context, args; // 延遲執(zhí)行函數(shù) const later = () => setTimeout(() => { // 延遲函數(shù)執(zhí)行完畢,清空定時器 timeout = null // 延遲執(zhí)行的情況下,函數(shù)會在延遲函數(shù)中執(zhí)行 // 使用到之前緩存的參數(shù)和上下文 if (!immediate) { func.apply(context, args); context = args = null; } }, wait); let debounced = function (...params) { if (!timeout) { timeout = later(); if (immediate) { //立即執(zhí)行 func.apply(this, params); } else { //閉包 context = this; args = params; } } else { clearTimeout(timeout); timeout = later(); } } debounced.cancel = function () { clearTimeout(timeout); timeout = null; }; return debounced; };
防抖的應(yīng)用場景:
每次 resize/scroll 觸發(fā)統(tǒng)計事件
文本輸入的驗證(連續(xù)輸入文字后發(fā)送 AJAX 請求進行驗證,驗證一次就好)
節(jié)流(throttle): 高頻事件在規(guī)定時間內(nèi)只會執(zhí)行一次,執(zhí)行一次后,只有大于設(shè)定的執(zhí)行周期后才會執(zhí)行第二次。
//underscore.js function throttle(func, wait, options) { var timeout, context, args, result; var previous = 0; if (!options) options = {}; 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; var remaining = wait - (now - previous); context = this; args = arguments; if (remaining <= 0 || remaining > wait) { if (timeout) { clearTimeout(timeout); timeout = null; } previous = now; result = func.apply(context, args); if (!timeout) context = args = null; } else if (!timeout && options.trailing !== false) { // 判斷是否設(shè)置了定時器和 trailing timeout = setTimeout(later, remaining); } return result; }; throttled.cancel = function () { clearTimeout(timeout); previous = 0; timeout = context = args = null; }; return throttled; };
函數(shù)節(jié)流的應(yīng)用場景有:
DOM 元素的拖拽功能實現(xiàn)(mousemove)
射擊游戲的 mousedown/keydown 事件(單位時間只能發(fā)射一顆子彈)
計算鼠標(biāo)移動的距離(mousemove)
Canvas 模擬畫板功能(mousemove)
搜索聯(lián)想(keyup)
監(jiān)聽滾動事件判斷是否到頁面底部自動加載更多:給 scroll 加了 debounce 后,只有用戶停止?jié)L動后,才會判斷是否到了頁面底部;如果是 throttle 的話,只要頁面滾動就會間隔一段時間判斷一次
23. 取數(shù)組的最大值(ES5、ES6)
// ES5 的寫法 Math.max.apply(null, [14, 3, 77, 30]); // ES6 的寫法 Math.max(...[14, 3, 77, 30]); // reduce [14,3,77,30].reduce((accumulator, currentValue)=>{ return accumulator = accumulator > currentValue ? accumulator : currentValue });
24. ES6新的特性有哪些?
新增了塊級作用域(let,const)
提供了定義類的語法糖(class)
新增了一種基本數(shù)據(jù)類型(Symbol)
新增了變量的解構(gòu)賦值
函數(shù)參數(shù)允許設(shè)置默認值,引入了rest參數(shù),新增了箭頭函數(shù)
數(shù)組新增了一些API,如 isArray / from / of 方法;數(shù)組實例新增了 entries(),keys() 和 values() 等方法
對象和數(shù)組新增了擴展運算符
ES6 新增了模塊化(import/export)
ES6 新增了 Set 和 Map 數(shù)據(jù)結(jié)構(gòu)
ES6 原生提供 Proxy 構(gòu)造函數(shù),用來生成 Proxy 實例
ES6 新增了生成器(Generator)和遍歷器(Iterator)
25. setTimeout倒計時為什么會出現(xiàn)誤差?
setTimeout() 只是將事件插入了“任務(wù)隊列”,必須等當(dāng)前代碼(執(zhí)行棧)執(zhí)行完,主線程才會去執(zhí)行它指定的回調(diào)函數(shù)。要是當(dāng)前代碼消耗時間很長,也有可能要等很久,所以并沒辦法保證回調(diào)函數(shù)一定會在 setTimeout() 指定的時間執(zhí)行。所以, setTimeout() 的第二個參數(shù)表示的是最少時間,并非是確切時間。
HTML5標(biāo)準(zhǔn)規(guī)定了 setTimeout() 的第二個參數(shù)的最小值不得小于4毫秒,如果低于這個值,則默認是4毫秒。在此之前。老版本的瀏覽器都將最短時間設(shè)為10毫秒。另外,對于那些DOM的變動(尤其是涉及頁面重新渲染的部分),通常是間隔16毫秒執(zhí)行。這時使用 requestAnimationFrame() 的效果要好于 setTimeout();
26. 為什么 0.1 + 0.2 != 0.3 ?
0.1 + 0.2 != 0.3 是因為在進制轉(zhuǎn)換和進階運算的過程中出現(xiàn)精度損失。
下面是詳細解釋:
JavaScript使用 Number 類型表示數(shù)字(整數(shù)和浮點數(shù)),使用64位表示一個數(shù)字。
圖片說明:
第0位:符號位,0表示正數(shù),1表示負數(shù)(s)
第1位到第11位:儲存指數(shù)部分(e)
第12位到第63位:儲存小數(shù)部分(即有效數(shù)字)f
計算機無法直接對十進制的數(shù)字進行運算, 需要先對照 IEEE 754 規(guī)范轉(zhuǎn)換成二進制,然后對階運算。
1.進制轉(zhuǎn)換
0.1和0.2轉(zhuǎn)換成二進制后會無限循環(huán)
0.1 -> 0.0001100110011001...(無限循環(huán)) 0.2 -> 0.0011001100110011...(無限循環(huán))
但是由于IEEE 754尾數(shù)位數(shù)限制,需要將后面多余的位截掉,這樣在進制之間的轉(zhuǎn)換中精度已經(jīng)損失。
2.對階運算
由于指數(shù)位數(shù)不相同,運算時需要對階運算 這部分也可能產(chǎn)生精度損失。
按照上面兩步運算(包括兩步的精度損失),最后的結(jié)果是
0.0100110011001100110011001100110011001100110011001100
結(jié)果轉(zhuǎn)換成十進制之后就是 0.30000000000000004。
27. promise 有幾種狀態(tài), Promise 有什么優(yōu)缺點 ?
promise有三種狀態(tài): fulfilled, rejected, pending.
Promise 的優(yōu)點:
一旦狀態(tài)改變,就不會再變,任何時候都可以得到這個結(jié)果
可以將異步操作以同步操作的流程表達出來,避免了層層嵌套的回調(diào)函數(shù)
Promise 的缺點:
無法取消 Promise
當(dāng)處于pending狀態(tài)時,無法得知目前進展到哪一個階段
28. Promise構(gòu)造函數(shù)是同步還是異步執(zhí)行,then中的方法呢 ?promise如何實現(xiàn)then處理 ?
Promise的構(gòu)造函數(shù)是同步執(zhí)行的。then中的方法是異步執(zhí)行的。
promise的then實現(xiàn),詳見: Promise源碼實現(xiàn)
29. Promise和setTimeout的區(qū)別 ?
Promise 是微任務(wù),setTimeout 是宏任務(wù),同一個事件循環(huán)中,promise總是先于 setTimeout 執(zhí)行。
30. 如何實現(xiàn) Promise.all ?
要實現(xiàn) Promise.all,首先我們需要知道 Promise.all 的功能:
如果傳入的參數(shù)是一個空的可迭代對象,那么此promise對象回調(diào)完成(resolve),只有此情況,是同步執(zhí)行的,其它都是異步返回的。
如果傳入的參數(shù)不包含任何 promise,則返回一個異步完成.
promises 中所有的promise都“完成”時或參數(shù)中不包含 promise 時回調(diào)完成。
如果參數(shù)中有一個promise失敗,那么Promise.all返回的promise對象失敗
在任何情況下,Promise.all 返回的 promise 的完成狀態(tài)的結(jié)果都是一個數(shù)組
Promise.all = function (promises) { return new Promise((resolve, reject) => { let index = 0; let result = []; if (promises.length === 0) { resolve(result); } else { setTimeout(() => { function processValue(i, data) { result[i] = data; if (++index === promises.length) { resolve(result); } } for (let i = 0; i < promises.length; i++) { //promises[i] 可能是普通值 Promise.resolve(promises[i]).then((data) => { processValue(i, data); }, (err) => { reject(err); return; }); } }) } }); }
如果想了解更多Promise的源碼實現(xiàn),可以參考我的另一篇文章:Promise的源碼實現(xiàn)(完美符合Promise/A+規(guī)范)
31.如何實現(xiàn) Promise.finally ?
不管成功還是失敗,都會走到finally中,并且finally之后,還可以繼續(xù)then。并且會將值原封不動的傳遞給后面的then.
Promise.prototype.finally = function (callback) { return this.then((value) => { return Promise.resolve(callback()).then(() => { return value; }); }, (err) => { return Promise.resolve(callback()).then(() => { throw err; }); }); }
32. 什么是函數(shù)柯里化?實現(xiàn) sum(1)(2)(3) 返回結(jié)果是1,2,3之和
函數(shù)柯里化是把接受多個參數(shù)的函數(shù)變換成接受一個單一參數(shù)(最初函數(shù)的第一個參數(shù))的函數(shù),并且返回接受余下的參數(shù)而且返回結(jié)果的新函數(shù)的技術(shù)。
function sum(a) { return function(b) { return function(c) { return a+b+c; } } } console.log(sum(1)(2)(3)); // 6
引申:實現(xiàn)一個curry函數(shù),將普通函數(shù)進行柯里化:
function curry(fn, args = []) { return function(){ let rest = [...args, ...arguments]; if (rest.length < fn.length) { return curry.call(this,fn,rest); }else{ return fn.apply(this,rest); } } } //test function sum(a,b,c) { return a+b+c; } let sumFn = curry(sum); console.log(sumFn(1)(2)(3)); //6 console.log(sumFn(1)(2, 3)); //6
如果您在面試中遇到了更多的原生JS問題,或者有一些本文未涉及到且有一定難度的JS知識,請給我留言。您的問題將會出現(xiàn)在后續(xù)文章中~
本文的寫成耗費了非常多的時間,在這個過程中,我也學(xué)習(xí)到了很多知識,謝謝各位小伙伴愿意花費寶貴的時間閱讀本文,如果本文給了您一點幫助或者是啟發(fā),請不要吝嗇你的贊和Star,您的肯定是我前進的最大動力。https://github.com/YvetteLau/...
后續(xù)寫作計劃
1.《寒冬求職季之你必須要懂的原生JS》(中)(下)
2.《寒冬求職季之你必須要知道的CSS》
3.《寒冬求職季之你必須要懂的前端安全》
4.《寒冬求職季之你必須要懂的一些瀏覽器知識》
5.《寒冬求職季之你必須要知道的性能優(yōu)化》
針對React技術(shù)棧:
1.《寒冬求職季之你必須要懂的React》系列
2.《寒冬求職季之你必須要懂的ReactNative》系列
參考文章:
https://www.ibm.com/developer...
https://juejin.im/post/5c7736...
選用了面試之道上的部分面試題
選用了木易楊說文中提及的部分面試題: https://juejin.im/post/5bc92e...
特別說明: 0.1 + 0.2 !== 0.3 此題答案大量使用了此篇文章的圖文: https://juejin.im/post/5b90e0...
選用了朋友面試大廠時遇到的一些面試題
《你不知道的JavaSctipt》
《JavaScript高級程序設(shè)計》
https://github.com/hanzichi/u...
關(guān)注小姐姐的公眾號,和小姐姐一起學(xué)前端。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/103332.html
摘要:半路出家的前端程序員應(yīng)該不在少數(shù),我也是其中之一。年,馮馮同事兼師兄看我寫太費勁,跟我說對面樓在找,問我要不要學(xué),說出來可能有點丟人,但是在那之前,我真得不知道什么是,什么是。 半路出家的前端程序員應(yīng)該不在少數(shù),我也是其中之一。 為何會走向前端 非計算機專業(yè)的我,畢業(yè)之后,就職于一家電力行業(yè)公司,做過設(shè)備調(diào)試、部門助理、測試,也寫過一段時間的QT,那三年的時間,最難過的不是工作忙不忙,...
摘要:如果你還沒讀過上篇上篇和中篇并無依賴關(guān)系,您可以讀過本文之后再閱讀上篇,可戳面試篇寒冬求職季之你必須要懂的原生上小姐姐花了近百個小時才完成這篇文章,篇幅較長,希望大家閱讀時多花點耐心,力求真正的掌握相關(guān)知識點。 互聯(lián)網(wǎng)寒冬之際,各大公司都縮減了HC,甚至是采取了裁員措施,在這樣的大環(huán)境之下,想要獲得一份更好的工作,必然需要付出更多的努力。 一年前,也許你搞清楚閉包,this,原型鏈,就...
摘要:循環(huán)可以使用的范圍包括數(shù)組和結(jié)構(gòu)某些類似數(shù)組的對象對象,以及字符串。只能遍歷數(shù)組,不能中斷,返回值是修改后的數(shù)組。除了之外,等,也有同樣的問題。聲明一個只讀的常量。這在語法上,稱為暫時性死區(qū)。暫時性死區(qū)也意味著不再是一個百分百安全的操作。 互聯(lián)網(wǎng)寒冬之際,各大公司都縮減了HC,甚至是采取了裁員措施,在這樣的大環(huán)境之下,想要獲得一份更好的工作,必然需要付出更多的努力。 一年前,也許你搞清楚閉包...
摘要:手冊網(wǎng)超級有用的前端基礎(chǔ)技術(shù)面試問題收集前端面試題目及答案匯總史上最全前端面試題含答案常見前端面試題及答案經(jīng)典面試題及答案精選總結(jié)前端面試過程中最容易出現(xiàn)的問題前端面試題整理騰訊前端面試經(jīng)驗前端基礎(chǔ)面試題部分最新前端面試題攻略前端面試前端入 手冊網(wǎng):http://www.shouce.ren/post/index 超級有用的前端基礎(chǔ)技術(shù)面試問題收集:http://www.codec...
摘要:手冊網(wǎng)超級有用的前端基礎(chǔ)技術(shù)面試問題收集前端面試題目及答案匯總史上最全前端面試題含答案常見前端面試題及答案經(jīng)典面試題及答案精選總結(jié)前端面試過程中最容易出現(xiàn)的問題前端面試題整理騰訊前端面試經(jīng)驗前端基礎(chǔ)面試題部分最新前端面試題攻略前端面試前端入 手冊網(wǎng):http://www.shouce.ren/post/index 超級有用的前端基礎(chǔ)技術(shù)面試問題收集:http://www.codec...
閱讀 3224·2021-09-30 09:48
閱讀 3501·2021-09-22 16:00
閱讀 1075·2019-08-30 13:08
閱讀 3112·2019-08-30 10:53
閱讀 2425·2019-08-29 18:33
閱讀 1599·2019-08-29 12:47
閱讀 905·2019-08-29 12:16
閱讀 1940·2019-08-26 12:02