JavaScript
-
js中有幾種數據類型,分別是什么?
- number,string,boolean,null,undefined,object
-
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 的函數
- call() 和 apply() 的不同點:參數書寫方式不同
- 相同點:
-
如何準確判斷變量是什么類型?
- 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))
- type of 判斷
-
請解釋一下事件冒泡機制,并說明如何阻止冒泡。
- 事件冒泡機制
- 在事件開始時,由最具體的元素(文檔中最底層的那個節點)接受,然后逐級向上傳播到最不具體的節點(文檔)
- 比如說,在一個頁面有一個div元素,加上一個點擊事件,在點擊事件觸發的時候,它的傳遞過程就是由 div→body→html→document(老版本暫且不談),它會沿著 DOM 樹逐級的傳遞
- 阻止事件冒泡
- event.stopPropagation() // 不支持IE低版本
- cancelBubble = true
- 事件冒泡機制
-
new
一個對象的過程是什么?- 創建空對象
- var obj = {}
- 設置新對象的constructor屬性為構造函數的名稱,設置新對象的proto屬性指向構造函數的prototype對象
- obj.proto = ClassA.prototype
- 使用新對象調用函數,函數中的this被指向新實例對象:
- ClassA.call(obj); //{}.構造函數()
- 將初始化完畢的新對象地址,保存到等號左邊的變量中
- 注意:
- 若構造函數中返回this或返回值是基本類型(number、string、boolean、null、undefined)的值,則返回新實例對象
- 若返回值是引用類型的值,則實際返回值為這個引用類型
- 注意:
- 創建空對象
-
this 的指向有哪幾種情況,分別是什么?
- 作為函數調用,非嚴格模式下,this指向window,嚴格模式下,this指向undefined;
- 作為某對象的方法調用,this通常指向調用的對象。
- 使用apply、call、bind 可以綁定this的指向。
- 在構造函數中,this指向新創建的對象
- 箭頭函數沒有單獨的this值,this在箭頭函數創建時確定,它與聲明所在的上下文相同
-
談談對作用域和作用域鏈的理解。
- 變量的作用域有兩種:全局變量和局部變量
- 全局作用域:
- 最外層函數定義的變量擁有全局作用域,即對任何內部函數來說,都是可以訪問的
- 局部作用域:
- 和全局作用域相反,局部作用域一般只在固定的代碼片段內可訪問到,而對于函數外部是無法訪問的,最常見的例如函數內部
- 注意:
- 函數內部聲明變量的時候,一定要使用var命令。如果不用的話,實際上聲明了一個全局變量
- 全局作用域:
- 作用域鏈:
- 全局作用域和局部作用域中變量的訪問權限,其實是由作用域鏈決定的
- 執行環境決定了變量的生命周期,以及哪部分代碼可以訪問其中變量
- 執行環境有全局執行環境(全局環境)和局部執行環境之分
- 每次進入一個新的執行環境,都會創建一個用于搜索變量和函數的作用域鏈
- 函數的局部環境可以訪問函數作用域中的變量和函數,也可以訪問其父環境,乃至全局環境中的變量和環境
- 全局環境只能訪問全局環境中定義的變量和函數,不能直接訪問局部環境中的任何數據
- 變量的執行環境有助于確定應該合適釋放內存
- 變量的作用域有兩種:全局變量和局部變量
-
談談對原型和原型鏈的理解。
- 原型
- 原型就是 prototype 對象,用來表示類型之間的關系
- 原型的作用:
- 數據共享 節約內存內存空間
- 實現繼承
- 注意:函數也是一個對象,對象不一定是函數(對象有proto屬性,函數有prototype屬性)
- 原型鏈
- 對象和對象之間是有聯系的,通過prototype對象指向父類對象,在指向Object對象。而通過的方式就是proto連接,而proto最終指向null
- 圖例:
- 圖例
- 原型
-
列舉 js 中繼承的幾種方式。
- 原型鏈繼承
- 核心: 將父類的實例作為子類的原型
- 構造繼承
- 核心:使用父類的構造函數來增強子類實例,等于是復制父類的實例屬性給子類(沒用到原型)
- 實例繼承
- 核心:為父類實例添加新特性,作為子類實例返回
- 拷貝繼承
- 組合繼承
- 核心:通過調用父類構造,繼承父類的屬性并保留傳參的優點,然后通過將父類實例作為子類原型,實現函數復用
- 寄生組合繼承
- 核心:通過寄生方式,砍掉父類的實例屬性,這樣,在調用兩次父類的構造的時候,就不會初始化兩次實例方法/屬性,避免的組合繼承的缺點
- 原型鏈繼承
-
談談對閉包的理解,并說明其優缺點。
- 使用閉包主要是為了設計私有的方法和變量
- 在js中,函數即閉包,只有函數才會產生作用域的概念
- 閉包有三個特性:
- 函數嵌套函數
- 函數內部可以引用外部的參數和變量
- 參數和變量不會被垃圾回收機制回收
- 優點:
- 可以避免全局變量的污染
- 缺點:
- 閉包會常駐內存,會增大內存使用量,使用不當很容易造成內存泄露
-
談談 js 中的異步(事件循環[Event Loop]機制)。
- 瀏覽器中的 Event Loop
- Javascript 有一個 main thread 主線程和 call-stack 調用棧(執行棧),所有的任務都會被放到調用棧等待主線程執行
- JS調用棧
- JS調用棧采用的是后進先出的規則,當函數執行的時候,會被添加到棧的頂部,當執行棧執行完成后,就會從棧頂移出,直到棧內被清空
- 同步任務和異步任務
- Javascript單線程任務被分為同步任務和異步任務,同步任務會在調用棧中按照順序等待主線程依次執行,異步任務會在異步任務有了結果后,將注冊的回調函數放入任務隊列中等待主線程空閑的時候(調用棧被清空),被讀取到棧內等待主線程的執行
- [圖片上傳失敗...(image-5b483b-1585842119691)]
- 任務隊列 Task Queue,即隊列,是一種先進先出的一種數據結構
- 瀏覽器中的 Event Loop
-
列舉 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回調函數后面可以傳一個參數
- push() (末尾添加元素)
-
js 如何實現深拷貝與淺拷貝
- 淺拷貝和深拷貝都只針對于引用數據類型
- 淺拷貝
- 淺拷貝只復制指向某個對象的指針,而不復制對象本身,新舊對象還是共享同一塊內存
- 深拷貝
- 深拷貝會另外創造一個一模一樣的對象,新對象跟原對象不共享內存,修改新對象不會改到原對象
- 區別:
- 淺拷貝只復制對象的第一層屬性、深拷貝可以對對象的屬性進行遞歸復制
-
談一談對面向對象的認識。
- 面向對象是向現實世界模型的自然延伸,這是一種"萬物皆對象"的編程思想
- 面向對象有三大特性,封裝、繼承和多態
- 封裝
- 封裝是為了使得代碼的復用性更高
- 封裝是將一類事物的屬性和行為抽象成一個類,使其屬性私有化,行為公開化,提高了數據的隱秘性的同時,使代碼模塊化
- 繼承
- 繼承是為了擴展了已存在的代碼塊,進一步提高了代碼的復用性
- 繼承是進一步將一類事物共有的屬性和行為抽象成一個父類,而每一個子類是一個特殊的父類--有父類的行為和屬性,也有自己特有的行為和屬性
- 多態
- 多態是為了實現接口重用
- 多態的一大作用就是為了解耦--為了解除父子類繼承的耦合度。如果說繼承中父子類的關系式IS-A的關系,那么接口和實現類之之間的關系式HAS-A。簡單來說,多態就是允許父類引用(或接口)指向子類(或實現類)對象。很多的設計模式都是基于面向對象的多態性設計的
- 封裝
- 如果說封裝和繼承是面向對象的基礎,那么多態則是面向對象最精髓的理論。掌握多態必先了解接口,只有充分理解接口才能更好的應用多態
-
js 的高階函數有哪些?說明其用處。
- 高階函數
- 一個函數接受一個或多個函數作為參數,或者可以返回一個參數
- 常見的高階函數
- setInterval , setTimeout , sort ,一般用于函數回調
- map:應用于數組,對數組進行操作
- reduce 歸納的意思,作用是對數組的每個值求和積等操作
- filter 數組過濾
- 高階函數
-
什么是防抖和節流?有什么區別?如何實現?
- 防抖
- 觸發高頻事件后 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));
- 防抖
-
編寫一段代碼:實現數組去重
- 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);
- Set
-
編寫一段代碼:實現數組扁平化
- 數組扁平化
- 是指將一個多維數組變為一維數組
[1, [2, 3, [4, 5]]] ------> [1, 2, 3, 4, 5]
- 思路:
- 遍歷數組 arr,若 arr[i] 為數組則遞歸遍歷,直至 arr[i] 不為數組然后與之前的結果 concat
- concat() 方法用于連接兩個或多個數組
- 該方法不會改變現有的數組,而僅僅會返回被連接數組的一個副本
- 遍歷數組 arr,若 arr[i] 為數組則遞歸遍歷,直至 arr[i] 不為數組然后與之前的結果 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; }
- reduce
- 數組扁平化