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

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

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

2.繞不過去的閉包

閉包就是可以讀取其餘函數內部變量的函數
閉包是指有權訪問另外一個函數做用域中變量的函數,建立閉包的最多見的方式就是在一個
函數內建立另外一個函數,經過另外一個函數訪問這個函數的局部變量,利用閉包能夠突破做用鏈域
閉包的特性:html

函數內再嵌套函數
內部函數能夠引用外層的參數和變量
參數和變量不會被垃圾回收機制回收java

優勢:可以實現封裝和緩存等
缺點:消耗內存、使用不當會內存溢出,
解決方法:在退出函數以前,將不使用的局部變量所有刪除node

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

做用域鏈的做用是保證執行環境裏有權訪問的變量和函數是有序的,做用域鏈的變量只能向上訪問,變量訪問到 window對象即被終止,做用域鏈向下訪問變量是不被容許的。
簡單的說,做用域就是變量與函數的可訪問範圍,即做用域控制着變量與函數的可見性和生命週期jquery

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

每一個對象都會在其內部初始化一個屬性,就是 prototype (原型),當咱們訪問一個對象的屬性時,若是這個對象內部不存在這個屬性,那麼他就會去 prototype 裏找這個屬性,這個prototype 又會有本身的 prototype ,因而就這樣一直找下去,也就是咱們平時所說的原型鏈的概念
關係: instance.constructor.prototype = instance._proto_
特色:JavaScript 對象是經過引用來傳遞的,咱們建立的每一個新對象實體中並無一份屬於本身的原型副本。當咱們修改原型時,與之相關的對象也會繼承這一改變當咱們須要一個屬性的時, Javascript 引擎會先看當前對象中是否有這個屬性, 若是沒有的,就會查找他的 Prototype 對象是否有這個屬性,如此遞推下去,一直檢索到 Object內建對象nginx

5.Javascript如何實現繼承?

  • 構造繼承
  • 原型繼承
  • 實例繼承
  • 拷貝繼承

原型 prototype 機制或 apply 和 call 方法去實現較簡單,建議使用構造函數與原型混合方式es6

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的解釋器能夠檢測到什麼時候程序再也不使用一個對象了,當他肯定了一個對象是無用的時候,他就知道再也不須要這個對象,能夠把它所佔用的內存釋放掉了。例如:ajax

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

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

標記清除

這是最多見的垃圾回收方式,當變量進入環境時,就標記這個變量爲」進入環境「,從邏輯上講,永遠不能釋放進入環境的變量所佔的內存,永遠不能釋放進入環境變量所佔用的內存,只要執行流程進入相應的環境,就可能用到他們。當離開環境時,就標記爲離開環境。

垃圾回收器在運行的時候會給存儲在內存中的變量都加上標記(全部都加),而後去掉環境變量中的變量,以及被環境變量中的變量所引用的變量(條件性去除標記),刪除全部被標記的變量,刪除的變量沒法在環境變量中被訪問因此會被刪除,最後垃圾回收器,完成了內存的清除工做,並回收他們所佔用的內存。

引用計數法

另外一種不太常見的方法就是引用計數法,引用計數法的意思就是每一個值沒引用的次數,當聲明瞭一個變量,並用一個引用類型的值賦值給改變量,則這個值的引用次數爲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
相關文章
相關標籤/搜索