互聯網寒冬之際,各大公司都縮減了HC,甚至是採起了「裁人」措施,在這樣的大環境之下,想要得到一份更好的工做,必然須要付出更多的努力。javascript
一年前,也許你搞清楚閉包,this,原型鏈,就能得到承認。可是如今,很顯然是不行了。本文梳理出了一些面試中有必定難度的高頻原生JS問題,部分知識點可能你以前從未關注過,或者看到了,卻沒有仔細研究,可是它們卻很是重要。本文將以真實的面試題的形式來呈現知識點,你們在閱讀時,建議不要先看個人答案,而是本身先思考一番。儘管,本文全部的答案,都是我在翻閱各類資料,思考並驗證以後,纔給出的(絕非複製粘貼而來)。但因水平有限,本人的答案未必是最優的,若是您有更好的答案,歡迎給我留言。html
本文篇幅較長,可是滿滿的都是乾貨!而且還埋伏了可愛的表情包,但願小夥伴們可以堅持讀完。前端
衷心的祝願你們都能找到心儀的工做。java
1. 原始類型有哪幾種?null 是對象嗎?原始數據類型和複雜數據類型存儲有什麼區別?
2. typeof 是否正確判斷類型? instanceof呢? instanceof 的實現原理是什麼?
首先 typeof 可以正確的判斷基本數據類型,可是除了 null, typeof null輸出的是對象。node
可是對象來講,typeof 不能正確的判斷其類型, typeof 一個函數能夠輸出 'function',而除此以外,輸出的全是 object,這種狀況下,咱們沒法準確的知道對象的類型。git
instanceof能夠準確的判斷複雜數據類型,可是不能正確判斷基本數據類型。(正確判斷數據類型請戳:https://github.com/YvetteLau/...es6
instanceof 是經過原型鏈判斷的,A instanceof B, 在A的原型鏈中層層查找,是否有原型等於B.prototype,若是一直找到A的原型鏈的頂端(null;即Object.prototype.__proto__
),仍然不等於B.prototype,那麼返回false,不然返回true.github
instanceof的實現代碼:web
// L instanceof R function instance_of(L, R) {//L 表示左表達式,R 表示右表達式 var O = R.prototype;// 取 R 的顯式原型 L = L.__proto__; // 取 L 的隱式原型 while (true) { if (L === null) //已經找到頂層 return false; if (O === L) //當 O 嚴格等於 L 時,返回 true return true; L = L.__proto__; //繼續向上一層原型鏈查找 } }
3. for of , for in 和 forEach,map 的區別。
PS: Object.keys():返回給定對象全部可枚舉屬性的字符串數組。面試
關於forEach是否會改變原數組的問題,有些小夥伴提出了異議,爲此我寫了代碼測試了下(注意數組項是複雜數據類型的狀況)。
除了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; //直接操做數組 }); 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 循環
更多細節請戳: https://github.com/YvetteLau/...
4. 如何判斷一個變量是否是數組?
arr.constructor === Array
. (不許確,由於咱們能夠指定 obj.constructor = Array
)function fn() { console.log(Array.isArray(arguments)); //false; 由於arguments是類數組,但不是數組 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. 類數組和數組的區別是什麼?
類數組:
1)擁有length屬性,其它屬性(索引)爲非負整數(對象中的索引會被當作字符串來處理);
2)不具備數組所具備的方法;
類數組是一個普通對象,而真實的數組是Array類型。
常見的類數組有: 函數的參數 arugments, DOM 對象列表(好比經過 document.querySelectorAll 獲得的列表), jQuery 對象 (好比 $("div")).
類數組能夠轉換爲數組:
//第一種方法 Array.prototype.slice.call(arrayLike, start); //第二種方法 [...arrayLike]; //第三種方法: Array.from(arrayLike);
PS: 任何定義了遍歷器(Iterator)接口的對象,均可以用擴展運算符轉爲真正的數組。
Array.from方法用於將兩類對象轉爲真正的數組:相似數組的對象(array-like object)和可遍歷(iterable)的對象。
6. == 和 === 有什麼區別?
=== 不須要進行類型轉換,只有類型相同而且值相等時,才返回 true.
== 若是二者類型不一樣,首先須要進行類型轉換。具體流程以下:
let person1 = { age: 25 } let person2 = person1; person2.gae = 20; console.log(person1 === person2); //true,注意複雜數據類型,比較的是引用地址
思考:
[] == ![]
咱們來分析一下: [] == ![]
是true仍是false?
![]
引用類型轉換成布爾值都是true,所以![]
的是false7. ES6中的class和ES5的類有什麼區別?
8. 數組的哪些API會改變原數組?
修改原數組的API有:
splice/reverse/fill/copyWithin/sort/push/pop/unshift/shift
不修改原數組的API有:
slice/map/forEach/every/filter/reduce/entries/find
注: 數組的每一項是簡單數據類型,且未直接操做數組的狀況下。
9. let、const 以及 var 的區別是什麼?
10. 在JS中什麼是變量提高?什麼是暫時性死區?
變量提高就是變量在聲明以前就可使用,值爲undefined。
在代碼塊內,使用 let/const 命令聲明變量以前,該變量都是不可用的(會拋出錯誤)。這在語法上,稱爲「暫時性死區」。暫時性死區也意味着 typeof 再也不是一個百分百安全的操做。
typeof x; // ReferenceError(暫時性死區,拋錯) let x;
typeof y; // 值是undefined,不會報錯
暫時性死區的本質就是,只要一進入當前做用域,所要使用的變量就已經存在了,可是不可獲取,只有等到聲明變量的那一行代碼出現,才能夠獲取和使用該變量。
11. 如何正確的判斷this? 箭頭函數的this是什麼?
this的綁定規則有四種:默認綁定,隱式綁定,顯式綁定,new綁定.
測試下是否已經成功Get了此知識點(瀏覽器執行環境):
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的區別。
13. 談談你對JS執行上下文棧和做用域鏈的理解。
執行上下文就是當前 JavaScript 代碼被解析和執行時所在環境, JS執行上下文棧能夠認爲是一個存儲函數調用的棧結構,遵循先進後出的原則。
做用域鏈: 不管是 LHS 仍是 RHS 查詢,都會在當前的做用域開始查找,若是沒有找到,就會向上級做用域繼續查找目標標識符,每次上升一個做用域,一直到全局做用域爲止。
題難不難?不難!繼續挑戰一下難!知道難,就更要繼續了!
閉包是指有權訪問另外一個函數做用域中的變量的函數,建立閉包最經常使用的方式就是在一個函數內部建立另外一個函數。
閉包的做用有:
15. call、apply有什麼區別?call,aplly和bind的內部是如何實現的?
call 和 apply 的功能相同,區別在於傳參的方式不同:
call核心:
Function.prototype.call = function (context) { /** 若是第一個參數傳入的是 null 或者是 undefined, 那麼指向this指向 window/global */ /** 若是第一個參數傳入的不是null或者是undefined, 那麼必須是一個對象 */ if (!context) { //context爲null或者是undefined context = typeof window === 'undefined' ? global : window; } context.fn = this; //this指向的是當前的函數(Function的實例) let args = [...arguments].slice(1);//獲取除了this指向對象之外的參數, 空數組slice後返回的仍然是空數組 let result = context.fn(...args); //隱式綁定,當前函數的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); // 瀏覽器環境: Chirs teacher 25; node 環境: undefined teacher 25
apply:
apply的實現和call很相似,可是須要注意他們的參數是不同的,apply的第二個參數是數組或類數組.
Function.prototype.apply = function (context, rest) { if (!context) { //context爲null或者是undefined時,設置默認值 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]); // 瀏覽器環境: Chirs programmer 20; node 環境: undefined teacher 25
bind
bind 和 call/apply 有一個很重要的區別,一個函數被 call/apply 的時候,會直接調用,可是 bind 會建立一個新函數。當這個新函數被調用時,bind() 的第一個參數將做爲它運行時的 this,以後的一序列參數將會在傳遞的實參前傳入做爲它的參數。
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傳遞的參數和函數調用時傳遞的參數拼接 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的方式建立對象和經過字面量建立有什麼區別?
new:
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; }
字面量建立對象,不會調用 Object構造函數, 簡潔且性能更好;
new Object() 方式建立對象本質上是方法調用,涉及到在proto鏈中遍歷該方法,當找到該方法後,又會生產方法調用必須的 堆棧信息,方法調用結束後,還要釋放該堆棧,性能不如字面量的方式。
經過對象字面量定義對象時,不會調用Object構造函數。
17. 談談你對原型的理解?
在 JavaScript 中,每當定義一個對象(函數也是對象)時候,對象中都會包含一些預約義的屬性。其中每一個函數對象都有一個prototype 屬性,這個屬性指向函數的原型對象。使用原型對象的好處是全部對象實例共享它所包含的屬性和方法。
18. 什麼是原型鏈?【原型鏈解決的是什麼問題?】
原型鏈解決的主要是繼承問題。
每一個對象擁有一個原型對象,經過 proto (讀音: dunder proto) 指針指向其原型對象,並從中繼承方法和屬性,同時原型對象也可能擁有原型,這樣一層一層,最終指向 null(Object.proptotype.__proto__
指向的是null)。這種關係被稱爲原型鏈 (prototype chain),經過原型鏈一個對象能夠擁有定義在其餘對象中的屬性和方法。
構造函數 Parent、Parent.prototype 和 實例 p 的關係以下:(p.__proto__ === Parent.prototype)
19. prototype 和
__proto__
區別是什麼?
prototype是構造函數的屬性。
__proto__
是每一個實例都有的屬性,能夠訪問 [[prototype]] 屬性。
實例的__proto__
與其構造函數的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實現一個繼承?
組合繼承(最經常使用的繼承方式)
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); }
其它繼承方式實現,能夠參考《JavaScript高級程序設計》
21. 什麼是深拷貝?深拷貝和淺拷貝有什麼區別?
淺拷貝是指只複製第一層對象,可是當對象的屬性是引用類型時,實質複製的是其引用,當引用指向的值改變時也會跟着變化。
深拷貝複製變量值,對於非基本類型的變量,則遞歸至基本類型變量後,再複製。深拷貝後的對象與原來的對象是徹底隔離的,互不影響,對一個對象的修改並不會影響另外一個對象。
實現一個深拷貝:
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') { //若是不是複雜數據類型,直接返回 return obj; } /** * 若是obj是數組,那麼 obj.constructor 是 [Function: Array] * 若是obj是對象,那麼 obj.constructor 是 [Function: Object] */ let t = new obj.constructor(); for(let key in obj) { //若是 obj[key] 是複雜數據類型,遞歸 t[key] = deepClone(obj[key]); } return t; }
看不下去了?別人的送分題會成爲你的送命題
防抖和節流的做用都是防止函數屢次調用。區別在於,假設一個用戶一直觸發這個函數,且每次觸發函數的間隔小於設置的時間,防抖的狀況下只會調用一次,而節流的狀況會每隔必定時間調用一次函數。
防抖(debounce): n秒內函數只會執行一次,若是n秒內高頻事件再次被觸發,則從新計算時間
function debounce(func, wait, immediate=true) { let timeout, context, args; // 延遲執行函數 const later = () => setTimeout(() => { // 延遲函數執行完畢,清空定時器 timeout = null // 延遲執行的狀況下,函數會在延遲函數中執行 // 使用到以前緩存的參數和上下文 if (!immediate) { func.apply(context, args); context = args = null; } }, wait); let debounced = function (...params) { if (!timeout) { timeout = later(); if (immediate) { //當即執行 func.apply(this, params); } else { //閉包 context = this; args = params; } } else { clearTimeout(timeout); timeout = later(); } } debounced.cancel = function () { clearTimeout(timeout); timeout = null; }; return debounced; };
防抖的應用場景:
節流(throttle): 高頻事件在規定時間內只會執行一次,執行一次後,只有大於設定的執行週期後纔會執行第二次。
//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) { // 判斷是否設置了定時器和 trailing timeout = setTimeout(later, remaining); } return result; }; throttled.cancel = function () { clearTimeout(timeout); previous = 0; timeout = context = args = null; }; return throttled; };
函數節流的應用場景有:
23. 取數組的最大值(ES五、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新的特性有哪些?
25. setTimeout倒計時爲何會出現偏差?
setTimeout() 只是將事件插入了「任務隊列」,必須等當前代碼(執行棧)執行完,主線程纔會去執行它指定的回調函數。要是當前代碼消耗時間很長,也有可能要等好久,因此並沒辦法保證回調函數必定會在 setTimeout() 指定的時間執行。因此, setTimeout() 的第二個參數表示的是最少時間,並不是是確切時間。
HTML5標準規定了 setTimeout() 的第二個參數的最小值不得小於4毫秒,若是低於這個值,則默認是4毫秒。在此以前。老版本的瀏覽器都將最短期設爲10毫秒。另外,對於那些DOM的變更(尤爲是涉及頁面從新渲染的部分),一般是間隔16毫秒執行。這時使用 requestAnimationFrame() 的效果要好於 setTimeout();
26. 爲何 0.1 + 0.2 != 0.3 ?
0.1 + 0.2 != 0.3 是由於在進制轉換和進階運算的過程當中出現精度損失。
下面是詳細解釋:
JavaScript使用 Number 類型表示數字(整數和浮點數),使用64位表示一個數字。
圖片說明:
計算機沒法直接對十進制的數字進行運算, 須要先對照 IEEE 754 規範轉換成二進制,而後對階運算。
1.進制轉換
0.1和0.2轉換成二進制後會無限循環
0.1 -> 0.0001100110011001...(無限循環) 0.2 -> 0.0011001100110011...(無限循環)
可是因爲IEEE 754尾數位數限制,須要將後面多餘的位截掉,這樣在進制之間的轉換中精度已經損失。
2.對階運算
因爲指數位數不相同,運算時須要對階運算 這部分也可能產生精度損失。
按照上面兩步運算(包括兩步的精度損失),最後的結果是
0.0100110011001100110011001100110011001100110011001100
結果轉換成十進制以後就是 0.30000000000000004。
27. promise 有幾種狀態, Promise 有什麼優缺點 ?
promise有三種狀態: fulfilled, rejected, pending.
Promise 的優勢:
Promise 的缺點:
28. Promise構造函數是同步仍是異步執行,then中的方法呢 ?promise如何實現then處理 ?
Promise的構造函數是同步執行的。then中的方法是異步執行的。
promise的then實現,詳見: Promise源碼實現
29. Promise和setTimeout的區別 ?
Promise 是微任務,setTimeout 是宏任務,同一個事件循環中,promise老是先於 setTimeout 執行。
30. 如何實現 Promise.all ?
要實現 Promise.all,首先咱們須要知道 Promise.all 的功能:
promises 中全部的promise都「完成」時或參數中不包含 promise 時回調完成。
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的源碼實現,能夠參考個人另外一篇文章:Promise的源碼實現(完美符合Promise/A+規範)
31.如何實現 Promise.finally ?
無論成功仍是失敗,都會走到finally中,而且finally以後,還能夠繼續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. 什麼是函數柯里化?實現 sum(1)(2)(3) 返回結果是1,2,3之和
函數柯里化是把接受多個參數的函數變換成接受一個單一參數(最初函數的第一個參數)的函數,而且返回接受餘下的參數並且返回結果的新函數的技術。
function sum(a) { return function(b) { return function(c) { return a+b+c; } } } console.log(sum(1)(2)(3)); // 6
引伸:實現一個curry函數,將普通函數進行柯里化:
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知識,請給我留言。您的問題將會出如今後續文章中~
本文的寫成耗費了很是多的時間,在這個過程當中,我也學習到了不少知識,謝謝各位小夥伴願意花費寶貴的時間閱讀本文,若是本文給了您一點幫助或者是啓發,請不要吝嗇你的贊和Star,您的確定是我前進的最大動力。https://github.com/YvetteLau/...
後續寫做計劃
1.《寒冬求職季之你必需要懂的原生JS》(中)(下)
2.《寒冬求職季之你必需要知道的CSS》
3.《寒冬求職季之你必需要懂的前端安全》
4.《寒冬求職季之你必需要懂的一些瀏覽器知識》
5.《寒冬求職季之你必需要知道的性能優化》
針對React技術棧:
1.《寒冬求職季之你必需要懂的React》系列
2.《寒冬求職季之你必需要懂的ReactNative》系列
參考文章:
0.1 + 0.2 !== 0.3
此題答案大量使用了此篇文章的圖文: https://juejin.im/post/5b90e0... 推薦關注本人公衆號: