1. 程式人生 > 實用技巧 >【建議收藏】2020大廠JavaScript面試題彙總,持續更新中~

【建議收藏】2020大廠JavaScript面試題彙總,持續更新中~

1.說幾條寫JavaScript的基本規範

不要在同一行宣告多個變數
請是用 ===/!== 來比較 true/false 或者數值
使用物件字面量替代 new Array 這種形式
不要使用全域性函式
Switch 語句必須帶有 default 分支
If 語句必須使用大括號
for-in 迴圈中的變數 應該使用 let 關鍵字明確限定作用域,從而避免作用域汙染

2.繞不過去的閉包

閉包就是能夠讀取其他函式內部變數的函式
閉包是指有權訪問另一個函式作用域中變數的函式,建立閉包的最常見的方式就是在一個
函式內建立另一個函式,通過另一個函式訪問這個函式的區域性變數,利用閉包可以突破作用鏈域
閉包的特性:

函式內再巢狀函式
內部函式可以引用外層的引數和變數
引數和變數不會被垃圾回收機制回收

優點:能夠實現封裝和快取等
缺點:消耗記憶體、使用不當會記憶體溢位,
解決方法:在退出函式之前,將不使用的區域性變數全部刪除

3.說說你對作用域鏈的理解

作用域鏈的作用是保證執行環境裡有權訪問的變數和函式是有序的,作用域鏈的變數只能向上訪問,變數訪問到 window物件即被終止,作用域鏈向下訪問變數是不被允許的。
簡單的說,作用域就是變數與函式的可訪問範圍,即作用域控制著變數與函式的可見性和生命週期

4.JavaScript原型,原型鏈 ? 有什麼特點?

每個物件都會在其內部初始化一個屬性,就是 prototype (原型),當我們訪問一個物件的屬性時,如果這個物件內部不存在這個屬性,那麼他就會去 prototype 裡找這個屬性,這個prototype 又會有自己的 prototype ,於是就這樣一直找下去,也就是我們平時所說的原型鏈的概念
關係: instance.constructor.prototype = instance.proto


特點:JavaScript 物件是通過引用來傳遞的,我們建立的每個新物件實體中並沒有一份屬於自己的原型副本。當我們修改原型時,與之相關的物件也會繼承這一改變當我們需要一個屬性的時, Javascript 引擎會先看當前物件中是否有這個屬性, 如果沒有的,就會查詢他的 Prototype 物件是否有這個屬性,如此遞推下去,一直檢索到 Object內建物件

5.Javascript如何實現繼承?

  • 構造繼承
  • 原型繼承
  • 例項繼承
  • 拷貝繼承
    原型 prototype 機制或 apply 和 call 方法去實現較簡單,建議使用建構函式與原型混合方式
function Parent(){
this.name = 'wang';
}
function Child(){
 this.age = 28;
}
Child.prototype = new Parent();//繼承了Parent,通過原型
var demo = new Child();
alert(demo.age);
alert(demo.name);//得到被繼承的屬性

6.JS中的垃圾回收機制

必要性:由於字串、物件和陣列沒有固定大小,所有當他們的大小已知時,才能對他們進行動態的儲存分配。JavaScript程式每次建立字串、陣列或物件時,直譯器都必須分配記憶體來儲存那個實體。只要像這樣動態地分配了記憶體,最終都要釋放這些記憶體以便他們能夠被再用,否則,JavaScript的直譯器將會消耗完系統中所有可用的記憶體,造成系統崩潰。

這段話解釋了為什麼需要系統需要垃圾回收,JS不像C/C++,他有自己的一套垃圾回收機制(Garbage Collection)。JavaScript的直譯器可以檢測到何時程式不再使用一個物件了,當他確定了一個物件是無用的時候,他就知道不再需要這個物件,可以把它所佔用的記憶體釋放掉了。例如:

var a="hello world";
var b="world";
var a=b;
//這時,會釋放掉"hello world",釋放記憶體以便再引用

垃圾回收的方法:標記清除、計數引用。

標記清除

這是最常見的垃圾回收方式,當變數進入環境時,就標記這個變數為”進入環境“,從邏輯上講,永遠不能釋放進入環境的變數所佔的記憶體,永遠不能釋放進入環境變數所佔用的記憶體,只要執行流程進入相應的環境,就可能用到他們。當離開環境時,就標記為離開環境。

垃圾回收器在執行的時候會給儲存在記憶體中的變數都加上標記(所有都加),然後去掉環境變數中的變數,以及被環境變數中的變數所引用的變數(條件性去除標記),刪除所有被標記的變數,刪除的變數無法在環境變數中被訪問所以會被刪除,最後垃圾回收器,完成了記憶體的清除工作,並回收他們所佔用的記憶體。

引用計數法

另一種不太常見的方法就是引用計數法,引用計數法的意思就是每個值沒引用的次數,當聲明瞭一個變數,並用一個引用型別的值賦值給改變數,則這個值的引用次數為1,;相反的,如果包含了對這個值引用的變數又取得了另外一個值,則原先的引用值引用次數就減1,當這個值的引用次數為0的時候,說明沒有辦法再訪問這個值了,因此就把所佔的記憶體給回收進來,這樣垃圾收集器再次執行的時候,就會釋放引用次數為0的這些值。

用引用計數法會存在記憶體洩露,下面來看原因:

function problem() {
var objA = new Object();
var objB = new Object();
objA.someOtherObject = objB;
objB.anotherObject = objA;
}

在這個例子裡面,objA和objB通過各自的屬性相互引用,這樣的話,兩個物件的引用次數都為2,在採用引用計數的策略中,由於函式執行之後,這兩個物件都離開了作用域,函式執行完成之後,因為計數不為0,這樣的相互引用如果大量存在就會導致記憶體洩露。

特別是在DOM物件中,也容易存在這種問題:

var element=document.getElementById(’‘);
var myObj=new Object();
myObj.element=element;
element.someObject=myObj;

這樣就不會有垃圾回收的過程。

7.函式柯里化

在一個函式中,首先填充幾個引數,然後再返回一個新的函式的技術,稱為函式的柯里化。通常可用於在不侵入函式的前提下,為函式 預置通用引數,供多次重複呼叫。

const add = function add(x) {
    return function (y) {
        return x + y
    }
}
const add1 = add(1)
add1(2) === 3
add1(20) === 21

8.js的防抖

防抖(Debouncing)

防抖技術即是可以把多個順序地呼叫合併成一次,也就是在一定時間內,規定事件被觸發的次數。
通俗一點來說,看看下面這個簡化的例子:

// 簡單的防抖動函式
function debounce(func, wait, immediate) {
    // 定時器變數
    var timeout;
    return function() {
        // 每次觸發 scroll handler 時先清除定時器
        clearTimeout(timeout);
        // 指定 xx ms 後觸發真正想進行的操作 handler
        timeout = setTimeout(func, wait);
    };
};
 
// 實際想繫結在 scroll 事件上的 handler
function realFunc(){
    console.log("Success");
}
 
// 採用了防抖動
window.addEventListener('scroll',debounce(realFunc,500));
// 沒采用防抖動
window.addEventListener('scroll',realFunc);

上面簡單的防抖的例子可以拿到瀏覽器下試一下,大概功能就是如果 500ms 內沒有連續觸發兩次 scroll 事件,那麼才會觸發我們真正想在 scroll 事件中觸發的函式。

上面的示例可以更好的封裝一下

// 防抖動函式
function debounce(func, wait, immediate) {
    var timeout;
    return function() {
        var context = this, args = arguments;
        var later = function() {
            timeout = null;
            if (!immediate) func.apply(context, args);
        };
        var callNow = immediate && !timeout;
        clearTimeout(timeout);
        timeout = setTimeout(later, wait);
        if (callNow) func.apply(context, args);
    };
};
 
var myEfficientFn = debounce(function() {
    // 滾動中的真正的操作
}, 250);
 
// 繫結監聽
window.addEventListener('resize', myEfficientFn);

9.js節流

防抖函式確實不錯,但是也存在問題,譬如圖片的懶載入,我希望在下滑過程中圖片不斷的被加載出來,而不是隻有當我停止下滑時候,圖片才被加載出來。又或者下滑時候的資料的 ajax 請求載入也是同理。

這個時候,我們希望即使頁面在不斷被滾動,但是滾動 handler 也可以以一定的頻率被觸發(譬如 250ms 觸發一次),這類場景,就要用到另一種技巧,稱為節流函式(throttling)。

節流函式,只允許一個函式在 X 毫秒內執行一次。

與防抖相比,節流函式最主要的不同在於它保證在 X 毫秒內至少執行一次我們希望觸發的事件 handler。
與防抖相比,節流函式多了一個 mustRun 屬性,代表 mustRun 毫秒內,必然會觸發一次 handler ,同樣是利用定時器,看看簡單的示例:

// 簡單的節流函式
function throttle(func, wait, mustRun) {
    var timeout,
        startTime = new Date();
 
    return function() {
        var context = this,
            args = arguments,
            curTime = new Date();
 
        clearTimeout(timeout);
        // 如果達到了規定的觸發時間間隔,觸發 handler
        if(curTime - startTime >= mustRun){
            func.apply(context,args);
            startTime = curTime;
        // 沒達到觸發間隔,重新設定定時器
        }else{
            timeout = setTimeout(func, wait);
        }
    };
};
// 實際想繫結在 scroll 事件上的 handler
function realFunc(){
    console.log("Success");
}
// 採用了節流函式
window.addEventListener('scroll',throttle(realFunc,500,1000));

上面簡單的節流函式的例子可以拿到瀏覽器下試一下,大概功能就是如果在一段時間內 scroll 觸發的間隔一直短於 500ms ,那麼能保證事件我們希望呼叫的 handler 至少在 1000ms 內會觸發一次。

10.說一下Commonjs、AMD和CMD

一個模組是能實現特定功能的檔案,有了模組就可以方便的使用別人的程式碼,想要什麼功能就能載入什麼模組。

Commonjs:開始於伺服器端的模組化,同步定義的模組化,每個模組都是一個單獨的作用域,模組輸出,modules.exports,模組載入require()引入模組。

AMD:中文名非同步模組定義的意思。

requireJS實現了AMD規範,主要用於解決下述兩個問題。

1.多個檔案有依賴關係,被依賴的檔案需要早於依賴它的檔案載入到瀏覽器
2.載入的時候瀏覽器會停止頁面渲染,載入檔案越多,頁面失去響應的時間越長。
語法:requireJS定義了一個函式define,它是全域性變數,用來定義模組。

requireJS的例子:

//定義模組
define(['dependency'], function(){
var name = 'Byron';
function printName(){
console.log(name);
}
return {
printName: printName
};
});
//載入模組
require(['myModule'], function (my){
my.printName();
}

requirejs定義了一個函式define,它是全域性變數,用來定義模組:
define(id?dependencies?,factory)

在頁面上使用模組載入函式:
require([dependencies],factory);

總結AMD規範:require()函式在載入依賴函式的時候是非同步載入的,這樣瀏覽器不會失去響應,它指定的回撥函式,只有前面的模組載入成功,才會去執行。

因為網頁在載入js的時候會停止渲染,因此我們可以通過非同步的方式去載入js,而如果需要依賴某些,也是非同步去依賴,依賴後再執行某些方法。

由於篇幅有限,只能分享部分面試題,更多面試題及答案可以【點選我】閱讀下載哦~無償分享給大家,算是一個感恩回饋吧

11.請解釋什麼是事件委託/事件代理

事件代理( Event Delegation ),又稱之為事件委託。是 JavaScript 中常用的繫結事件的常用技巧。顧名思義,“事件代理”即是把原本需要繫結的事件委託給父元素,讓父元素擔當事件監聽的職務。事件代理的原理是DOM元素的事件冒泡。使用事件代理的好處是可以提高效能
可以大量節省記憶體佔用,減少事件註冊,比如在 table 上代理所有 td 的 click 事件就非常棒
可以實現當新增子物件時無需再次對其繫結

12.事件模型

W3C 中定義事件的發生經歷三個階段:捕獲階段( capturing )、目標階段
( targetin )、冒泡階段( bubbling )

冒泡型事件:當你使用事件冒泡時,子級元素先觸發,父級元素後觸發
捕獲型事件:當你使用事件捕獲時,父級元素先觸發,子級元素後觸發
DOM 事件流:同時支援兩種事件模型:捕獲型事件和冒泡型事件
阻止冒泡:在 W3c 中,使用 stopPropagation() 方法;在IE下設定 cancelBubble =true
阻止捕獲:阻止事件的預設行為,例如 click - a 後的跳轉。在 W3c 中,使用preventDefault() 方法,在 IE 下設定 window.event.returnValue = false

13.new操作符具體幹了什麼呢?

建立一個空物件,並且 this 變數引用該物件,同時還繼承了該函式的原型
屬性和方法被加入到 this 引用的物件中
新建立的物件由 this 所引用,並且最後隱式的返回 this

14.Ajax原理

Ajax 的原理簡單來說是在使用者和伺服器之間加了—箇中間層( AJAX 引擎),通過XmlHttpRequest 物件來向伺服器發非同步請求,從伺服器獲得資料,然後用 javascript來操作 DOM 而更新頁面。使使用者操作與伺服器響應非同步化。這其中最關鍵的一步就是從伺服器獲得請求資料
Ajax 的過程只涉及 JavaScript 、 XMLHttpRequest 和 DOM 。 XMLHttpRequest 是ajax的核心機制

15.物件深度克隆的簡單實現

function deepClone(obj){
var newObj= obj instanceof Array ? []:{};
for(var item in obj){
var temple= typeof obj[item] == 'object' ? deepClone(obj[item]):obj[item];
newObj[item] = temple;
}
return newObj;
}

ES5的常用的物件克隆的一種方式。注意陣列是物件,但是跟物件又有一定區別,所以我們一開始判斷了一些型別,決定newObj是物件還是陣列~

16.將原生的ajax封裝成promise

var  myNewAjax=function(url){
return new Promise(function(resolve,reject){
var xhr = new XMLHttpRequest();
xhr.open('get',url);
xhr.send(data);
xhr.onreadystatechange=function(){
if(xhr.status==200&&readyState==4){
var json=JSON.parse(xhr.responseText);
resolve(json)
}else if(xhr.readyState==4&&xhr.status!=200){
reject('error');
}
}
})
}

17.實現一個once函式,傳入函式引數只執行一次

function ones(func){
var tag=true;
return function(){
if(tag==true){
func.apply(null,arguments);
tag=false;
}
return undefined
}
}

18.js監聽物件屬性的改變

我們假設這裡有一個user物件,

(1)在ES5中可以通過Object.defineProperty來實現已有屬性的監聽

Object.defineProperty(user,'name',{
set:function(key,value){
}
})

缺點:如果id不在user物件中,則不能監聽id的變化
(2)在ES6中可以通過Proxy來實現

var  user = new Proxy({},{
set:function(target,key,value,receiver){
}
})

這樣即使有屬性在user中不存在,通過user.id來定義也同樣可以這樣監聽這個屬性的變化哦~

19.如何實現sleep的效果(es5或者es6)

(1)while迴圈的方式

function sleep(ms){
var start=Date.now(),expire=start+ms;
while(Date.now()<expire);
console.log('1111');
return;
}

執行sleep(1000)之後,休眠了1000ms之後輸出了1111。上述迴圈的方式缺點很明顯,容易造成死迴圈。

(2)通過promise來實現

function sleep(ms){
var temple=new Promise(
(resolve)=>{
console.log(111);setTimeout(resolve,ms)
});
return temple
}
sleep(500).then(function(){
//console.log(222)
})
//先輸出了111,延遲500ms後輸出222

(3)通過async封裝

function sleep(ms){
return new Promise((resolve)=>setTimeout(resolve,ms));
}
async function test(){
var temple=await sleep(1000);
console.log(1111)
return temple
}
test();
//延遲1000ms輸出了1111

(4).通過generate來實現

function* sleep(ms){
yield new Promise(function(resolve,reject){
console.log(111);
setTimeout(resolve,ms);
})
}
sleep(500).next().value.then(function(){console.log(2222)})

20.Function.proto(getPrototypeOf)是什麼?

獲取一個物件的原型,在chrome中可以通過proto的形式,或者在ES6中可以通過Object.getPrototypeOf的形式。
那麼Function.proto是什麼麼?也就是說Function由什麼物件繼承而來,我們來做如下判別。
Function.proto==Object.prototype //false
Function.proto==Function.prototype//true
我們發現Function的原型也是Function。

由於篇幅有限,只能分享部分面試題,更多面試題及答案可以【點選我】閱讀下載哦~無償分享給大家,算是一個感恩回饋吧

21.如何解決跨域問題?

首先了解下瀏覽器的同源策略 同源策略 /SOP(Same origin policy) 是一種約定,由Netscape公司1995年引入瀏覽器,它是瀏覽器最核心也最基本的安全功能,如果缺少了同源策略,瀏覽器很容易受到 XSS 、 CSFR 等攻擊。所謂同源是指"協議+域名+埠"三者相同,即便兩個不同的域名指向同一個ip地址,也非同源

  • 通過jsonp跨域
var script = document.createElement('script');
script.type = 'text/javascript';
// 傳參並指定回撥執行函式為onBack
script.src = 'http://www.....:8080/login?user=admin&callback=onBack';
document.head.appendChild(script);
// 回撥執行函式
function onBack(res) {
 alert(JSON.stringify(res));
}
  • document.domain + iframe跨域
//父視窗:(http://www.domain.com/a.html)
<iframe id="iframe" src="http://child.domain.com/b.html"></iframe>
<script>
 document.domain = 'domain.com';
 var user = 'admin';
</script>

//子視窗:(http://child.domain.com/b.html)
document.domain = 'domain.com';
// 獲取父視窗中變數
alert('get js data from parent ---> ' + window.parent.user);
  • nginx代理跨域
  • nodejs中介軟體代理跨域
  • 後端在頭部資訊裡面設定安全域名

22.介紹js有哪些內建物件

Object 是 JavaScript 中所有物件的父物件
資料封裝類物件: Object 、 Array 、 Boolean 、 Number 和 String
其他物件: Function 、 Arguments 、 Math 、 Date 、 RegExp 、 Error

23.JS有哪些方法定義物件

物件字面量: var obj = {};
建構函式: var obj = new Object();
Object.create(): var obj = Object.create(Object.prototype);

24.你覺得jQuery原始碼有哪些寫的好的地方

  • jquery 原始碼封裝在一個匿名函式的自執行環境中,有助於防止變數的全域性汙染,然後通過傳入 window 物件引數,可以使 window 物件作為區域性變數使用,好處是當 jquery 中訪問 window 物件的時候,就不用將作用域鏈退回到頂層作用域了,從而可以更快的訪問window物件。同樣,傳入 undefined 引數,可以縮短查詢 undefined 時的作用域鏈

  • jquery 將一些原型屬性和方法封裝在了 jquery.prototype 中,為了縮短名稱,又賦值給了 jquery.fn ,這是很形象的寫法

  • 有一些陣列或物件的方法經常能使用到, jQuery 將其儲存為區域性變數以提高訪問速度

  • jquery 實現的鏈式呼叫可以節約程式碼,所返回的都是同一個物件,可以提高程式碼效率

25.如何通過JS判斷一個數組

  • instanceof 運算子是用來測試一個物件是否在其原型鏈原型建構函式的屬性
var arr = [];
arr instanceof Array; // true
  • isArray
Array.isArray([]) //true
Array.isArray(1) //false
  • constructor 屬性返回對建立此物件的陣列函式的引用,就是返回物件相對應的建構函式
var arr = [];
arr.constructor == Array; //true
  • Object.prototype
Object.prototype.toString.call([]) == '[object Array]'
// 寫個方法
var isType = function (obj) {
 return Object.prototype.toString.call(obj).slice(8,-1);
 //return Object.prototype.toString.apply([obj]).slice(8,-1);
}
isType([])  //Array