前端面試題之 JavaScript

JavaScript

  1. js中有幾種數據類型,分別是什么?

    • number,string,boolean,null,undefined,object
  2. call()、apply()、bind()的異同點。

    • 相同點:
      • 三者的相同點:都是用來改變this的指向
      • call() 和 apply() 的相同點:都是調用一個對象的一個方法,用另一個對象替換當前對象(功能相同)
        • 例如:
          • B.call(A, args1,args2);即A對象調用B對象的方法
          • F.apply(G, arguments);即G對象應用F對象的方法
      • call()和 bind() 的相同點:都是用來改變this的指向
    • 不同點:
      • call() 和 apply() 的不同點:參數書寫方式不同
        • call() 的第一個參數是 this 要指向的對象,后面傳入的是參數列表,參數可以是任意類型,當第一個參數為 null、undefined 的時候,默認指向 window
        • apply() 的第一個參數是 this 要指向的對象,第二個參數是數組
      • call()和 bind() 的不同點:
        • call() 改過 this 的指向后,會再執行函數
        • bind()改過 this 后,不執行函數,會返回一個綁定新 this 的函數
  3. 如何準確判斷變量是什么類型?

    • type of 判斷
      • typeof 檢測基本類型是沒有問題的,如字符串、數值、布爾和 undefined,但是如果檢測對象或null的話都會返回'object';
    • instanceof 判斷
      • 此方法后面一定是數據類型:console.log(data instanceof Array),通常我們并不是想知道某個值是對象,而是想知道它是什么類型的對象,如上:變量 data 是 Array 類型的對象?
      • 所有引用類型值都是 object 的實例,所以在檢測一個引用類型值和 Object 構造函數時,instanceof 操作符會一直返回 true。如用 instanceof 操作符檢測基本類型值時,會一直返回false,原因很簡單,因為基本類型不是對象
    • constructor 判斷
      • console.log(data.constructor === Array)
    • Object.prototype.toString.call(data)
      • 此種方式可以準確的判斷出數據類型,console.log(Object.prototype.toString.call(data))
  4. 請解釋一下事件冒泡機制,并說明如何阻止冒泡。

    • 事件冒泡機制
      • 在事件開始時,由最具體的元素(文檔中最底層的那個節點)接受,然后逐級向上傳播到最不具體的節點(文檔)
      • 比如說,在一個頁面有一個div元素,加上一個點擊事件,在點擊事件觸發的時候,它的傳遞過程就是由 div→body→html→document(老版本暫且不談),它會沿著 DOM 樹逐級的傳遞
    • 阻止事件冒泡
      • event.stopPropagation() // 不支持IE低版本
      • cancelBubble = true
  5. new一個對象的過程是什么?

    1. 創建空對象
      • var obj = {}
    2. 設置新對象的constructor屬性為構造函數的名稱,設置新對象的proto屬性指向構造函數的prototype對象
      • obj.proto = ClassA.prototype
    3. 使用新對象調用函數,函數中的this被指向新實例對象:
      • ClassA.call(obj);  //{}.構造函數()
    4. 將初始化完畢的新對象地址,保存到等號左邊的變量中
      • 注意:
        • 若構造函數中返回this或返回值是基本類型(number、string、boolean、null、undefined)的值,則返回新實例對象
        • 若返回值是引用類型的值,則實際返回值為這個引用類型
  6. this 的指向有哪幾種情況,分別是什么?

    • 作為函數調用,非嚴格模式下,this指向window,嚴格模式下,this指向undefined;
    • 作為某對象的方法調用,this通常指向調用的對象。
    • 使用apply、call、bind 可以綁定this的指向。
    • 在構造函數中,this指向新創建的對象
    • 箭頭函數沒有單獨的this值,this在箭頭函數創建時確定,它與聲明所在的上下文相同
  7. 談談對作用域和作用域鏈的理解。

    • 變量的作用域有兩種:全局變量和局部變量
      • 全局作用域:
        • 最外層函數定義的變量擁有全局作用域,即對任何內部函數來說,都是可以訪問的
      • 局部作用域:
        • 和全局作用域相反,局部作用域一般只在固定的代碼片段內可訪問到,而對于函數外部是無法訪問的,最常見的例如函數內部
      • 注意:
        • 函數內部聲明變量的時候,一定要使用var命令。如果不用的話,實際上聲明了一個全局變量
    • 作用域鏈:
      • 全局作用域和局部作用域中變量的訪問權限,其實是由作用域鏈決定的
      • 執行環境決定了變量的生命周期,以及哪部分代碼可以訪問其中變量
      • 執行環境有全局執行環境(全局環境)和局部執行環境之分
      • 每次進入一個新的執行環境,都會創建一個用于搜索變量和函數的作用域鏈
      • 函數的局部環境可以訪問函數作用域中的變量和函數,也可以訪問其父環境,乃至全局環境中的變量和環境
      • 全局環境只能訪問全局環境中定義的變量和函數,不能直接訪問局部環境中的任何數據
      • 變量的執行環境有助于確定應該合適釋放內存
  8. 談談對原型和原型鏈的理解。

    • 原型
      • 原型就是 prototype 對象,用來表示類型之間的關系
      • 原型的作用:
        • 數據共享 節約內存內存空間
        • 實現繼承
      • 注意:函數也是一個對象,對象不一定是函數(對象有proto屬性,函數有prototype屬性)
    • 原型鏈
      • 對象和對象之間是有聯系的,通過prototype對象指向父類對象,在指向Object對象。而通過的方式就是proto連接,而proto最終指向null
      • 圖例:
        • 圖例
  9. 列舉 js 中繼承的幾種方式。

    • 原型鏈繼承
      • 核心: 將父類的實例作為子類的原型
    • 構造繼承
      • 核心:使用父類的構造函數來增強子類實例,等于是復制父類的實例屬性給子類(沒用到原型)
    • 實例繼承
      • 核心:為父類實例添加新特性,作為子類實例返回
    • 拷貝繼承
    • 組合繼承
      • 核心:通過調用父類構造,繼承父類的屬性并保留傳參的優點,然后通過將父類實例作為子類原型,實現函數復用
    • 寄生組合繼承
      • 核心:通過寄生方式,砍掉父類的實例屬性,這樣,在調用兩次父類的構造的時候,就不會初始化兩次實例方法/屬性,避免的組合繼承的缺點
  10. 談談對閉包的理解,并說明其優缺點。

    • 使用閉包主要是為了設計私有的方法和變量
    • 在js中,函數即閉包,只有函數才會產生作用域的概念
    • 閉包有三個特性:
      • 函數嵌套函數
      • 函數內部可以引用外部的參數和變量
      • 參數和變量不會被垃圾回收機制回收
    • 優點:
      • 可以避免全局變量的污染
    • 缺點:
      • 閉包會常駐內存,會增大內存使用量,使用不當很容易造成內存泄露
  11. 談談 js 中的異步(事件循環[Event Loop]機制)。

    • 瀏覽器中的 Event Loop
      • Javascript 有一個 main thread 主線程和 call-stack 調用棧(執行棧),所有的任務都會被放到調用棧等待主線程執行
    • JS調用棧
      • JS調用棧采用的是后進先出的規則,當函數執行的時候,會被添加到棧的頂部,當執行棧執行完成后,就會從棧頂移出,直到棧內被清空
    • 同步任務和異步任務
      • Javascript單線程任務被分為同步任務和異步任務,同步任務會在調用棧中按照順序等待主線程依次執行,異步任務會在異步任務有了結果后,將注冊的回調函數放入任務隊列中等待主線程空閑的時候(調用棧被清空),被讀取到棧內等待主線程的執行
      • [圖片上傳失敗...(image-5b483b-1585842119691)]
      • 任務隊列 Task Queue,即隊列,是一種先進先出的一種數據結構
  12. 列舉 js 中數組的常用方法并說明用途。

    • push() (末尾添加元素)
      • 作用:向數組的末尾添加n個元素
      • 參數:參數時新增的元素,可以串多個
      • 返回值:新數組的數組成員的個數
      • 原數組發生變化
    • pop() (刪除最后一項)
      • 作用:刪除數組的最后一項
      • 參數:不需要傳參數
      • 返回值:被刪除的那一項
      • 原數組發生改變
    • unshift() (開始位置添加一項)
      • 作用:向數組的開始位置添加n個元素
      • 參數:參數是新增的元素,可以是多個
      • 返回值:新數組的長度
      • 原數組發生改變
    • shift() (開始位置刪除一項)
      • 作用:刪除數組的第一項
      • 參數:不需要傳參數
      • 返回值:被刪除的那一項
      • 原數組發生改變
    • splice() (刪除、添加、修改元素)
      • 作用:刪除元素,并向數組添加新元素
      • 參數:
        • splice(m,n);從索引m開始,刪除n項
        • splice(m);從索引m開始刪除到末尾
        • splice(m,n,x,…);從索引m開始刪除n項,并將x添加到原來位置;添加項可以是多個(如果刪除項為0個,那么添加是在m元素前面)
      • 返回值:是個數組,數組中是被刪除的項
      • 原數組發生變化
    • indexOf()(從左向右查詢)不兼容IE低版本瀏覽器:IE6,7,8
      • 作用:檢測數組中是否存在某個元素
      • 參數:被查詢的元素(m,n)從索引n開始,m第一次出現的索引位置
      • 返回值:
        • 返回數組中第一次匹配到的元素的索引
        • 如果數組中沒有匹配項返回-1
      • 原數組不發生變化
    • lastIndexOf()(從左到右查詢)不兼容IE低版本瀏覽器:IE6,7,8
      • 作用:檢測數組中是否存在某個元素
      • 參數:被檢測的元素
      • 返回值:
        • 返回數組中最后一次匹配到的元素的索引
        • 如果數組中沒有匹配項返回-1
      • 原數組不發生變化
    • slice()(截?。?
      • 作用:按照起始和結束位置的索引截取數組
      • 參數:
        • 有兩個參數slice(m,n):從索引m開始,截取到索引n;(包前不包后)
        • 有一個參數slice(m):從索引m開始截取到末尾;
        • 沒有參數:數組的克隆;(slice(0)也是數組的克隆);
        • 以上情況參數都支持負數,負數情況會默認被加上數組的長度,處理成正數
      • 返回值:截取的數組
      • 原數組不發生變化
    • sort()(排序)
      • 作用:對數組的元素進行排序
      • 參數:
        • 沒有參數sort():只能排序數組成員項是相同位數的數字
        • sort(function(a,b){return a-b}):從小到大排序
        • sort(function(a,b){return b-a}):從大到小排序
      • 返回值:排序之后的數組
      • 原數組發生變化
    • reverse()(倒序)
      • 作用:使數組中元素倒序
      • 參數:不需要參數
      • 返回值:成員倒序的數組
      • 原數組發生變化
    • concat()(拼接數組)
      • 作用:拼接兩個或多個數組
      • 參數:
        • 不傳參數:數組的克隆
        • 傳參數:將傳入的參數拼接到數組中、可以傳多個
      • 返回值:拼接之后的新數組
      • 原數組不發生變化
    • join()(數組拼接成字符串)
      • 作用:將數組的成員項通過制定字符拼接成字符串
      • 參數:
        • 不傳參數:會默認按照逗號拼接
        • 傳參數:會按照參數字符拼接
      • 返回值:拼接之后的字符串
      • 原數組不發生變化
    • map()(映射)
      • 作用:方法返回一個新數組,數組中的元素為原始數組元素調用函數處理后的值
      • 參數:參數是一個回調函數;數組有幾項,回調函數就執行多少次;在回調函數中處理數組中的每項
      • 返回值:映射的新數組;
      • 原數組不發生變化;
    • forEach()(遍歷數組)
      • 作用:遍歷數組;
      • 參數:參數是一個回調函數;數組有幾項,回調函數就執行多少次
      • 返回值:沒有返回值;undefined
      • 原數組不發生變化
    • ary.forEach()和ary.map()的區別
      • ary.forEach()是和for循環一樣,是代替for。ary.map()是修改數組其中的數據,并返回新的數據
      • ary.forEach() 沒有return ary.map() 有return
      • map有映射,forEach沒有映射。
    • toString()(數組轉字符串)
      • 作用:數組轉字符串
      • 參數:不需要參數
      • 返回值:一個字符串,由數組的每項組成,數組的每一項用逗號隔開
      • 原數組不發生變化;
    • find(查找)
      • 從左到右依次進行查找,找到符合條件的那一項,直接返回,不再進行查找;如果找不到,那么返回undefined; 返回true,說明就找到了
      • find會根據回調函數的返回值,判斷是否要繼續向右查找;
    • filter(過濾)
      • 過濾;原數組不發生改變;返回一個過濾后的新數組
    • every
      • 每一個都是true則返回true,如果有一個是false,那么直接返回false;只要找到false,直接結束,不再繼續向下查找;返回值是布爾值
    • some
      • 返回一個布爾值;只要有一個符合條件就返回true;找到true,就不再向右進行查找;
    • includes
      • 返回一個布爾值;如果有就返回true;,沒有就返回false;
    • reduce
      • 迭代數組的所有項,累加器,數組中的每個值(從左到右)合并,最終計算為一個值
      • reduce回調函數后面可以傳一個參數
  13. js 如何實現深拷貝與淺拷貝

    • 淺拷貝和深拷貝都只針對于引用數據類型
    • 淺拷貝
      • 淺拷貝只復制指向某個對象的指針,而不復制對象本身,新舊對象還是共享同一塊內存
    • 深拷貝
      • 深拷貝會另外創造一個一模一樣的對象,新對象跟原對象不共享內存,修改新對象不會改到原對象
    • 區別:
      • 淺拷貝只復制對象的第一層屬性、深拷貝可以對對象的屬性進行遞歸復制
  14. 談一談對面向對象的認識。

    • 面向對象是向現實世界模型的自然延伸,這是一種"萬物皆對象"的編程思想
    • 面向對象有三大特性,封裝、繼承和多態
      • 封裝
        • 封裝是為了使得代碼的復用性更高
        • 封裝是將一類事物的屬性和行為抽象成一個類,使其屬性私有化,行為公開化,提高了數據的隱秘性的同時,使代碼模塊化
      • 繼承
        • 繼承是為了擴展了已存在的代碼塊,進一步提高了代碼的復用性
        • 繼承是進一步將一類事物共有的屬性和行為抽象成一個父類,而每一個子類是一個特殊的父類--有父類的行為和屬性,也有自己特有的行為和屬性
      • 多態
        • 多態是為了實現接口重用
        • 多態的一大作用就是為了解耦--為了解除父子類繼承的耦合度。如果說繼承中父子類的關系式IS-A的關系,那么接口和實現類之之間的關系式HAS-A。簡單來說,多態就是允許父類引用(或接口)指向子類(或實現類)對象。很多的設計模式都是基于面向對象的多態性設計的
    • 如果說封裝和繼承是面向對象的基礎,那么多態則是面向對象最精髓的理論。掌握多態必先了解接口,只有充分理解接口才能更好的應用多態
  15. js 的高階函數有哪些?說明其用處。

    • 高階函數
      • 一個函數接受一個或多個函數作為參數,或者可以返回一個參數
    • 常見的高階函數
      • setInterval , setTimeout , sort ,一般用于函數回調
      • map:應用于數組,對數組進行操作
      • reduce 歸納的意思,作用是對數組的每個值求和積等操作
      • filter 數組過濾
  16. 什么是防抖和節流?有什么區別?如何實現?

    • 防抖
      • 觸發高頻事件后 n 秒內函數只會執行一次,如果 n 秒內高頻事件再次被觸發,則重新計算時間
      • 思路:
        • 每次觸發事件時都取消之前的延時調用方法
      • 代碼:
        •  function debounce(fn) {
               let timeout = null; // 創建一個標記用來存放定時器的返回值
               return function () {
                   clearTimeout(timeout); // 每當用戶輸入的時候把前一個 setTimeout clear 掉
                   timeout = setTimeout(() => { // 然后又創建一個新的 setTimeout, 這樣就能保證輸入字符后的 interval 間隔內如果還有字符輸入的話,就不會執行 fn 函數
                   fn.apply(this, arguments);
                   }, 500);
               };
           }
           function sayHi() {
               console.log('防抖成功');
           }
          
           var inp = document.getElementById('inp');
           inp.addEventListener('input', debounce(sayHi)); // 防抖 
          
    • 節流
      • 高頻事件觸發,但在 n 秒內只會執行一次,所以節流會稀釋函數的執行頻率
      • 思路:
        • 每次觸發事件時都判斷當前是否有等待執行的延時函數
      • 代碼:
        •  function throttle(fn) {
               let canRun = true; // 通過閉包保存一個標記
               return function () {
                   if (!canRun) return; // 在函數開頭判斷標記是否為 true,不為 true 則 return
                   canRun = false; // 立即設置為 false
                   setTimeout(() => { // 將外部傳入的函數的執行放在 setTimeout 中
                       fn.apply(this, arguments);
                       // 最后在 setTimeout 執行完畢后再把標記設置為 true(關鍵) 表示可以執行下一次循環了。當定時器沒有執行的時候標記永遠是 false,在開頭被 return 掉
                       canRun = true;
                   }, 500);
               };
           }
           function sayHi(e) {
               console.log(e.target.innerWidth, e.target.innerHeight);
           }
           window.addEventListener('resize', throttle(sayHi));
          
          
  17. 編寫一段代碼:實現數組去重

    • Set
      •   var newArr = Array.from(new Set(arr));//不改變原數組
        
    • 擴展運算符 […]
      •   var resultarr = [...new Set(arr)]; //不改變原數組
        
    • filter
      •   let newArr = arr.filter((item,key,self) => self.indexOf(item) == key);
        
  18. 編寫一段代碼:實現數組扁平化

    • 數組扁平化
      • 是指將一個多維數組變為一維數組
        •   [1, [2, 3, [4, 5]]]  ------>    [1, 2, 3, 4, 5]
          
      • 思路:
        • 遍歷數組 arr,若 arr[i] 為數組則遞歸遍歷,直至 arr[i] 不為數組然后與之前的結果 concat
          • concat() 方法用于連接兩個或多個數組
          • 該方法不會改變現有的數組,而僅僅會返回被連接數組的一個副本
    • 實現方式
      • reduce
        • 遍歷數組每一項,若值為數組則遞歸遍歷,否則concat
        •   function flatten(arr) {  
                return arr.reduce((result, item)=> {
                    return result.concat(Array.isArray(item) ? flatten(item) : item);
                }, []);
            }
          
        • reduce 是數組的一種方法,它接收一個函數作為累加器,數組中的每個值(從左到右)開始縮減,最終計算為一個值
        • reduce包含兩個參數:回調函數,傳給total的初始值
      • toString & split
        • 調用數組的 toString 方法,將數組變為字符串然后再用 split 分割還原為數組
        • 因為split分割后形成的數組的每一項值為字符串,所以需要用一個map方法遍歷數組將其每一項轉換為數值型
        •   function flatten(arr) {
                return arr.toString().split(',').map(function(item) {
                    return Number(item);
                })
            } 
          
      • join & split
        • 和上面的toString一樣,join也可以將數組轉換為字符串
        •   function flatten(arr) {
                return arr.join(',').split(',').map(function(item) {
                    return parseInt(item);
                })                                
            }   
          
      • 遞歸
        • 遞歸的遍歷每一項,若為數組則繼續遍歷,否則concat
        •   function flatten(arr) {
                var res = [];
                arr.map(item => {
                    if(Array.isArray(item)) {
                        res = res.concat(flatten(item));
                    } else {
                        res.push(item);
                    }                
                });                
                return res;
            }
          
      • 擴展運算符
        • es6的擴展運算符能將二維數組變為一維
        • 可以做一個遍歷,若arr中含有數組則使用一次擴展運算符,直至沒有為止
        •   function flatten(arr) { 
                while(arr.some(item=>Array.isArray(item))) {
                    arr = [].concat(...arr);
                } 
                return arr;
            }
          
          
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,646評論 6 533
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,595評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 176,560評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,035評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,814評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,224評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,301評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,444評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,988評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,804評論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,998評論 1 370
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,544評論 5 360
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,237評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,665評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,927評論 1 287
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,706評論 3 393
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,993評論 2 374

推薦閱讀更多精彩內容