前端面试总结--JS
基本數據類型
String、Number、Boolean、null、undefined
類型判斷
判斷基本數據類型用typeof: MDN
- typeof 'aaa' // string
- typeof 123 // number
- typeof true // boolean
- typeof null // object 因為null和object的類型標簽都是0,null是一個空對象指針
- typeof undefined // undefined
- typeof 函數 // function
- typeof 其他對象 // object
判斷引用類型用instanceof: MDN
- instanceof 運算符用來檢測 constructor.prototype 是否存在于參數 object 的原型鏈上。
- 用instanceof來判斷基本數據類型會報錯
如何判斷一個數據是NaN
NaN==NaN; // false isNaN(NaN); // true Number.isNaN(NaN); // true Object.is(NaN,NaN); // truenull與undefined區別
null 表示值被定義但是個空值,null是一個空對象指針;
undefined 表示變量聲明但未賦值;
參考:理解 | 堆內存棧內存釋放、null和{}、undefined的區別
作用域鏈
定義:訪問一個變量時,會先在當前作用域查找該變量,若找到就直接使用,若未找到就繼續向上一層查找,直到全局作用域。這種鏈式查詢關系就是作用域鏈。
其實作用域鏈在函數定義時已經確定了,作用域鏈是和函數定義時的位置相關的。在函數創建的時候創建一個包含外部對象(包括全局對象和所有包含自己的對象)的作用域鏈,儲存在內部[[scope]]屬性中。函數執行的時候會創建一個執行環境,通過復制[[scope]]屬性中的對象,構建執行環境的作用域鏈,并把自己的活動對象推向當前作用域鏈的前端以此形成完整的作用域鏈。[[scope]]屬性中保存的是對可訪問變量對象的引用,而不是值的復制。
參考:函數的作用域鏈在定義時已經確定!!
閉包
定義:閉包就是能夠讀取其他函數內部變量的函數。
優點:局部變量可被重用且不會被污染(變量私有化)。
缺點:由于變量不會被回收,所以濫用閉包會導致內存溢出,所以要及時釋放不再需要的閉包。
代碼:
閉包是在定義時確定的
參考:JS閉包的理解
事件處理機制
DOM事件流存在三個階段:事件捕獲階段→處于目標階段→事件冒泡階段。
在捕獲階段觸發事件:addEventListener(event, listener, true)
在冒泡階段出發事件:addEventListener(event, listener, false), attachEvent(event,listener)
事件委托
<ul><li></li><li></li><li></li> </ul>window.onload = function(){ var UL = document.getElementById('ul');//委托ul上的點擊事件,將當前點擊的li節點變為紅色UL.onclick = function(ev){ var e = ev || window.event;var target = e.target || window.event.srcElement; //判斷target是否符合要求的元素節點 if(target.tagName.toLowerCase() == 'li'){//將當前點擊這個li節點變成紅色 target.style.backgroundColor = 'red';} }}如果我們需要在每一個li上綁定一個事件,就可以利用事件冒泡原理,將這些事件綁定到ul上,讓ul來代為處理。
優點:
- 提高性能。每個函數都會占用內存,使用一個事件可減少內存占用。
- 動態監聽。新增的節點無需再重新綁定事件也擁有和其他節點一樣的事件。
阻止事件冒泡
W3C的方法: event.stopPropagation()
IE的方法: event.cancelBubble =true
取消默認事件
W3C的方法: event.preventDefault()
IE的方法: event.returnValue =false
垃圾回收機制
標記清除:垃圾收集器在運行的時候會給存儲在內存中的所有變量都加上標記(當然,可以使用任何標記方式)。然后,它會去掉環境中的變量以及被環境中的變量引用的變量的標記。而在此之后再被加上標記的變量將被視為準備刪除的變量,原因是環境中的變量已經無法訪問到這些變量了。最后,垃圾收集器完成內存清除工作,銷毀那些帶標記的值并回收它們所占用的內存空間。
引用計數:當聲明了一個變量并將一個引用類型值賦值該變量時,則這個值的引用次數就是1.如果同一個值又被賦給另外一個變量,則該值得引用次數加1。相反,如果包含對這個值引用的變量又取 得了另外一個值,則這個值的引用次數減 1。當這個值的引用次數變成 0時,則說明沒有辦法再訪問這個值了,因而就可以將其占用的內存空間回收回來。這樣,當垃圾收集器下次再運行時,它就會釋放那 些引用次數為零的值所占用的內存。
參考:js垃圾回收機制和引起內存泄漏的操作;JS垃圾回收機制;談談JS 垃圾回收機制
this的指向
方法調用模式:this指向對象。
函數調用模式:this指向window。
構造器調用模式:this指向實例。
call和apply調用:this指向傳入的第一個參數。
箭頭函數:this指向定義時所在的對象,call和apply失效;不可以當做構造函數;不可以使用arguments對象;不可以使用yield命令。
參考:call、apply和bind方法的用法以及區別;函數的四種調用模式及this指向;JS this指向總結
call、apply和bind的用法以及區別
它們作用都是改變函數運行時this的指向。
function func (a,b,c) {}// call的第一個參數是要綁定給this的值,從第二個參數開始是接收的參數列表。 // 當第一個參數為null、undefined的時候,this默認指向window。 func.call(obj, 1, 2, 3)// apply接受兩個參數,第一個參數是要綁定給this的值,第二個參數是一個參數數組。 // 當第一個參數為null、undefined的時候,this默認指向window。 func.apply(obj, [1,2,3])// bind和call很相似,第一個參數是this的指向,從第二個參數開始是接收的參數列表。 // 區別在于bind的返回值是一個改變了this指向的函數,不會立即執行,原來的函數this的指向是不變的。 func.bind(obj, 1, 2, 3)參考:call、apply和bind方法的用法以及區別;讓你弄懂 call、apply、bind的應用和區別;「干貨」細說 call、apply 以及 bind 的區別和用法
原型和原型鏈
function SuperType(){this.property = true; } SuperType.prototype.getSuperValue = function(){return this.property; } var superInstance = new SuperType(); function SubType(){this.subProperty = false; } SubType.prototype.getSubValue = function(){return this.subProperty; } var subInstance = new SubType(); SubType.prototype = new SuperType(); SubType.prototype.getSubValue1 = function(){return this.subProperty; } var instance = new SubType(); alert(instance.getSuperValue()); //true //查找順序:instance→SubType.prototype→SuperType.prototype注意:subInstance.constructor = SuperType,因為SubType.prototype指向了另一個對象,導致constructor 被重寫了;同理,subInstance.getSubValue()也已經訪問不到了。
如何實現繼承
1-經典繼承(借用構造函數)
優點:可以在子類構造函數中向父類構造函數傳參;避免了引用類型的屬性被所有實例共享。
缺點:方法都在構造函數中定義,每次創建實例都會創建一遍方法,無法復用。
2-原型鏈繼承(借用原型鏈)
優點:方法可以復用。
缺點:引用類型的屬性被所有實例共享;創建子類的實例時,不能向父類傳參。
3-偽經典繼承(組合繼承)
優點:融合原型鏈繼承和構造函數繼承的優點,是 JavaScript 中最常用的繼承模式。
參考:js各種繼承方式和優缺點的介紹
新建對象的方法
1-直接新建
let person = {sex: 'femail',age: '19',eat: function(){console.log('eating');} }2-工廠模式
function createAPerson(){let person = new Object();person.sex = 'femail';person.age = '19';person.eat = function(){console.log('eating');}return person; } let person = createAPerson();3-構造函數模式
function Person(){this.sex = 'femail';this.age = '19';this.eat = function(){console.log('eating');} } let person = new Person();4-原型模式
function Person(){ } Person.prototype.sex = 'femail'; Person.prototype.age = '19'; Person.prototype.eat = function(){console.log('eating'); } let person = new Person();5-混合模式(組合使用構造函數模式和原型模式)
function Person(){this.sex = 'femail';this.age = '19'; } Person.prototype.eat = function(){console.log('eating'); } let person = new Person();參考:js中對象與對象創建方法的各種方法
EventLoop
宏任務
宏任務的例子有很多,包括創建主文檔對象、解析HTML、執行主線JavaScript代碼,更改當前的URL、以及各種事件、setTimeout、setInterval等。
微任務
微任務是更小的任務,主要包括Promise的回調函數、DOM發生變化。微任務需要盡可能快的、通過異步的方式執行。
事件循環基于兩個基本原則
一次處理一個任務。
一個任務開始后直到運行完成,不會被其他任務中斷。
參考:Promise自我修養之事件循環
怎么區分宏任務和微任務
宏任務是由宿主發起的,而微任務由JavaScript自身發起
參考鏈接:宏任務和微任務到底是什么?
MutationObserver
描述:監視一個節點及其全部子節點樹的添加、移除元素,以及任何屬性變化的事件
應用:群組組件監聽disabled屬性
變量和函數的優先級
函數提升優先級高于變量提升,且不會被同名變量聲明覆蓋,但是會被變量賦值后覆蓋。而且存在同名函數與同名變量時,優先執行函數。
參考:JS中變量提升與函數提升及其優先級
堆內存和棧內存
參考:JavaScript棧內存和堆內存
深拷貝和淺拷貝
深拷貝和淺拷貝是針對引用數據類型的,比如數組和對象。基本數據類型不存在深淺拷貝之分。
淺拷貝:只復制引用,原對象屬性值改變,新的屬性值也會改變。(只復值第一層)
深拷貝:創建一個新的內存,復制真正的值,原對象屬性值改變,新的屬性值不會受影響。(復制每一層)
淺拷貝數組:
// 方法1 let arr1 = [1,2,3,4,5]; let arr2 = [...arr1];// 方法2 let arr1 = [1,2,3,4,5]; let arr2 = arr1.slice(0)// 方法3 let arr1 = [1,2,3,4,5]; let arr2 = arr1.concat()淺拷貝對象:
// 方法1 var o2 = Object.assign({}, o1)// 方法2 var o2 = {...o1}深拷貝數組和對象:
// 方法1,需要求目標對象(非 undefined,function) const obj2 = JSON.parse(JSON.stringify(obj1));// 方法2 function deepClone(item){const target = item.constructor === Array ? [] : {}; // 判斷復制的目標是數組還是對象for(let keys in item){ // 遍歷目標if(item.hasOwnProperty(keys)){if(item[keys] && typeof item[keys] === 'object'){ // 如果值是對象,就遞歸一下target[keys] = item[keys].constructor === Array ? [] : {};target[keys] = deepClone(item[keys]);}else{ // 如果不是,就直接賦值target[keys] = item[keys];}}}return target; }淺拷貝內存分析
深拷貝內存分析
參考:內存分析-深淺拷貝
函數柯里化(Currying)
定義:柯里化是指通過函數調用繼續返回函數的方式,實現多次接收參數最后統一處理的函數編碼形式
優點:函數復用、延遲執行、提前確認
參考:詳解JS函數柯里化;「前端面試題系列6」理解函數的柯里化
數組的常用方法
arr.push() // 從后面添加元素,返回值為添加完后的數組的長度 arr.pop() // 從后面刪除元素,只能是一個,返回值是刪除的元素 arr.shift() // 從前面刪除元素,只能刪除一個 返回值是刪除的元素 arr.unshift() // 從前面添加元素, 返回值是添加完后的數組的長度 arr.splice(i,n) // 刪除從i(索引值)開始之后的n個元素。返回值是刪除的元素 arr.concat() // 連接兩個數組 返回值為連接后的新數組 str.split() // 將字符串轉化為數組 arr.sort() // 將數組進行排序,返回值是排好的數組,默認是按照最左邊的數字進行排序,不是按照數字大小排序的 arr.reverse() // 將數組反轉,返回值是反轉后的數組 arr.slice(start,end) // 切去索引值start到索引值end的數組,不包含end索引的值,返回值是切出來的數組 arr.forEach(callback) // 遍歷數組,無return 即使有return,也不會返回任何值,并且會影響原來的數組 arr.map(callback) // 映射數組(遍歷數組),有return 返回一個新數組 arr.filter(callback) // 過濾數組,返回一個滿足要求的數組 arr.reduce(callback) // 對數組中的每個元素執行一個由您提供的reducer函數(升序執行),將其結果匯總為單個返回普通函數和構造函數的區別
- 構造函數也是一個普通函數,創建方式和普通函數一樣,但是構造函數習慣上首字母大寫;
- 調用方式不一樣,普通函數直接調用,構造函數要用關鍵字new來調用;
- 調用時,構造函數內部會創建一個新對象,就是實例,普通函數不會創建新對象;
- 構造函數內部的this指向實例,普通函數內部的this指向調用函數的對象(如果沒有對象調用,默認為window);
- 構造函數默認的返回值是創建的對象(也就是實例),普通函數的返回值由return語句決定;
- 構造函數的函數名與類名相同;
防抖和節流
防抖:當持續觸發事件時,一定時間段內沒有再觸發事件,事件處理函數才會執行一次,如果設定的時間到來之前,又一次觸發了事件,就重新開始延時。
節流:當持續觸發事件時,保證一定時間段內只調用一次事件處理函數。
參考:js防抖和節流
代碼:
new 操作符做了什么
// 創建了一個空對象obj var obj = {}; // 將這個空對象的__proto__成員指向了Base函數對象prototype成員對象 obj.__proto__ = Base.prototype; // 將Base函數對象的this指針替換成obj,然后調用Base函數 Base.call(obj);參考:js中的new()到底做了些什么??
websocket
websocket的特點:
- 服務器可以主動向客戶端推送信息,客戶端也可以主動向服務器發送信息,是真正的雙向平等對話
- 建立在 TCP 協議之上,服務器端的實現比較容易
- 與 HTTP 協議有著良好的兼容性。默認端口也是80和443,并且握手階段采用 HTTP 協議,因此握手時不容易屏蔽,能通過各種 HTTP 代理服務器
- 數據格式比較輕量,性能開銷小,通信高效
- 可以發送文本,也可以發送二進制數據
- 有同源限制,客戶端可以與任意服務器通信
- 協議標識符是ws(如果加密,則為wss),服務器網址就是 URL(ws://example.com:80/some/path)
WebSocket 是什么原理?為什么可以實現持久連接?
export default {name : 'websocket',data() {return {websock: null,}},created() {this.initWebSocket();},destroyed() {this.websock.close() //離開路由之后斷開websocket連接},methods: {initWebSocket(){ //初始化weosocketconst wsuri = "ws://127.0.0.1:8080";this.websock = new WebSocket(wsuri);this.websock.onmessage = this.websocketonmessage;this.websock.onopen = this.websocketonopen;this.websock.onerror = this.websocketonerror;this.websock.onclose = this.websocketclose;},websocketonopen(){ //連接建立之后執行send方法發送數據let actions = {"test":"12345"};this.websocketsend(JSON.stringify(actions));},websocketonerror(){//連接建立失敗重連this.initWebSocket();},websocketonmessage(e){ //數據接收const redata = JSON.parse(e.data);},websocketsend(Data){//數據發送this.websock.send(Data);},websocketclose(e){ //關閉console.log('斷開連接',e);},}, }異步請求xhr、ajax、axios與fetch的區別比較
xhr:現代瀏覽器,最開始與服務器交換數據,都是通過XMLHttpRequest對象。它可以使用JSON、XML、HTML和text文本等格式發送和接收數據。
- 優點:
不重新加載頁面的情況下更新網頁
在頁面已加載后從服務器請求/接收數據
在后臺向服務器發送數據 - 缺點:
使用起來也比較繁瑣,需要設置很多值
早期的IE瀏覽器有自己的實現,這樣需要寫兼容代碼 - 代碼:
jQuery ajax:jQuery對XMLHttpRequest對象的封裝。
- 優點:
對原生XHR的封裝,做了兼容處理,簡化了使用。
增加了對JSONP的支持,可以簡單處理部分跨域。 - 缺點:
如果有多個請求,并且有依賴關系的話,容易形成回調地獄。
本身是針對MVC的編程,不符合現在前端MVVM的浪潮。
ajax是jQuery中的一個方法。如果只是要使用ajax卻要引入整個jQuery非常的不合理。 - 代碼:
axios:Axios是一個基于promise的HTTP庫,可以用在瀏覽器和 node.js 中。它本質也是對原生XMLHttpRequest的封裝,只不過它是Promise的實現版本,符合最新的ES規范。
- 優點:
從瀏覽器中創建XMLHttpRequests
從 node.js 創建 http 請求
支持 Promise API
攔截請求和響應
轉換請求數據和響應數據
取消請求
自動轉換 JSON 數據
客戶端支持防御 XSRF - 缺點:
只持現代代瀏覽器 - 代碼:
fetch:fetch是低層次的API,代替XHR,可以輕松處理各種格式,非文本化格式??梢院苋菀椎谋黄渌夹g使用,例如Service Workers。但是想要很好的使用fetch,需要做一些封裝處理。
- 優點:
mode: 'no-cors’就可以跨域了 - 缺點:
fetch只對網絡請求報錯,對400,500都當做成功的請求,需要封裝去處理
fetch默認不會帶cookie,需要添加配置項
fetch不支持abort,不支持超時控制,使用setTimeout及Promise.reject的實現超時控制并不能阻止請求過程繼續在后臺運行,造成了流量的浪費
fetch沒有辦法原生監測請求的進度,而XHR可以 - 代碼:
參考:異步請求xhr、ajax、axios與fetch的區別比較
對異步的理解
js是單線程的,一次只能做一件事情,遇到需要等待結果的任務,如果一直等候,就會阻塞進程,異步就是可以在某個任務等待結果的時候先執行其他任務,等結果返回后再執行這個任務
總結
以上是生活随笔為你收集整理的前端面试总结--JS的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 请求转发、包含、重定向 getAt
- 下一篇: 查漏补缺2--java中的四大域对象,八