js面试题整理
2020.07.06
- 1.js有幾種數據類型,其中基本數據類型有哪些?
- 2.undefined 和 null 區別?
- 3.數組去重?
- 4.字面量創建對象和new創建對象有什么區別?手寫一個new ?new時內部發生了什么?
- 5.什么是原型?什么是原型鏈?如何理解?
- 6.JavaScript的成員查找機制?
- 7.字面量和new出來的對象和 Object.create(null)創建出來的對象有什么區別 ?
- 8.JS 中的常用的繼承方式有哪些?以及各個繼承方式的優缺點 ?
- 8.this指向問題
- 9.改變this指向(call、apply、bind )
- 10.什么是作用域,什么是作用域鏈?
- 11.什么是執行棧,什么是執行上下文?
- 12.變量提升?
- 13.閉包
- 14.事件循環(Event Loop)
- 15.宏任務和微任務
- 16.內存泄露、垃圾回收機制
- 17.深拷貝和淺拷貝
- 18.var let const 有什么區別
- 19.JS如何實現異步編程(5種)?
- 20.Promise?
- 21.Generator是怎么樣使用的以及各個階段的變化如何?
- 22.箭頭函數
- 23.forEach、for in 、 for of三者的區別
- 24.模塊化
- 25.跨域的方式都有哪些?他們的特點是什么 ?
- 26.DOM事件流
- 27.防抖和節流
- 28.柯里化
- 29.js常見的設計模式
- 30.判斷數據類型的方法
- 31.數組扁平化
1.js有幾種數據類型,其中基本數據類型有哪些?
五種基本類型: Undefined、Null、Boolean、Number和String。
一種引用數據類型:object
在引用數據類型 object 中包括function/array/object
ES6新增:Symbol,主要用于創建一個獨一無二的標識。
ES10新增:Bigint,解決js中精度問題
2.undefined 和 null 區別?
null 表示一個對象被定義了, 值為"空值",使用typeof運算得到 “object”;
undefined 表示不存在這個值,當一個聲明了一個變量未初始化時,得到的就是undefined。沒有返回值的函數返回為undefined,沒有實參的形參也是undefined。
null 和 undefined 都表示“值的空缺”,可以認為undefined是表示系統級的、出乎意料的或類似錯誤的值的空缺,而null是表示程序級的、正常的或在意料之中的值的空缺。
3.數組去重?
1、簡單方法
var arr = ['abc','abcd','sss','2','d','t','2','ss','f','22','d']; //定義一個新的數組 var s = []; //遍歷數組 for(var i = 0;i<arr.length;i++){if(s.indexOf(arr[i]) == -1){ //判斷在s數組中是否存在,不存在則push到s數組中s.push(arr[i]);} } console.log(s); //輸出結果:["abc", "abcd", "sss", "2", "d", "t", "ss", "f", "22"]2、Set方法
let newArr1 = new Set(arr) console.log([...newArr1]);3、基于對象去重
let arr3 = [1, 2, 1, 1, 1, 2, 3, 3, 3, 2]let obj = {}for (let i = 0; i < arr3.length; i++) {let item = arr3[i]if (obj[item]) {//把數組最后一個元素取出并刪除arr3[i] = arr3[arr3.length - 1]arr3.length--;i--;continue;}obj[item] = item}console.log(arr3);console.log(obj);2021.07.08
4.字面量創建對象和new創建對象有什么區別?手寫一個new ?new時內部發生了什么?
區別
字面量:
- 面量創建對象更簡單,方便閱讀
- 不需要作用域解析,速度更快
new:
- 在內存中創建一個新對象
- 使新對象的__proto__指向原函數的原型對象
- 改變this指向(指向新的obj)并執行該函數,執行結果保存起來作為result
- 判斷執行函數的結果是不是null或Undefined,如果是則返回之前的新對象,如果不是則返回result
手寫new
function myNew(fn,...arg){//在內存中創建一個新對象let obj = {};//使新對象的__proto__指向原函數的原型對象obj.__proto__ = fn.prototype;//改變this指向(指向新的obj)并執行該函數,執行結果保存起來作為resultlet result = fn.call(obj,args)//判斷執行函數的結果是不是null或Undefined,如果是則返回之前的新對象,如果不是則返回resultreturn result instanceof Object ? result : obj }5.什么是原型?什么是原型鏈?如何理解?
原型
一個對象,我們也稱為prototype為原型對象,作用是共享方法。
原型分為隱式原型和顯式原型,每個對象都有一個隱式原型,它指向自己的構造函數的顯式原型。
構造函數通過原型分配的函數是所有對象共享的,javascript規定,每個構造函數都有一個prototype屬性,指向另一個對象。這個prototype就是一個對象,我們可以把不變的方法直接定義在prototype對象身上,這樣所有的對象實例都可以共享這些方法。
原型鏈
- 多個__proto__組成的集合成為原型鏈
- 實例對象的__proto__指向構造函數的原型對象
- 構造函數的原型對象的__proto__指向Object的原型對象
- Object的原型對象的__proto__指向null
6.JavaScript的成員查找機制?
1.當訪問一個對象的屬性(包括方法)時,首先查找這個對象自身有沒有該屬性。
2.如果沒有就查找它的原型(也就是__proto__指向的構造函數的原型對象)
3.如果還沒有就查找原型對象的原型(即Object的原型對象)
4.依此類推一直找到Object為止(Object的原型對象__proto__=>查找機制的終點為null).
5.__proto__對象原型的意義就在于為對象成員查找機制提供一個方向,或者說一條路線。
7.字面量和new出來的對象和 Object.create(null)創建出來的對象有什么區別 ?
8.JS 中的常用的繼承方式有哪些?以及各個繼承方式的優缺點 ?
原型鏈繼承、構造函數繼承、組合繼承、
原型式繼承、寄生式繼承、寄生組合繼承、ES6的extend
原型鏈繼承
- 優點:利用原型讓一個引用類型繼承另一個引用類型的屬性和方法
- 缺點:1、無法傳參 2、包含引用類型的原型屬性會被所有實例屬性共享,容易造成屬性的修改混亂
構造函數繼承
在子類型的構造函數中調用超類型構造函數
- 優點:可以在子類型構造函數中父類構造函數添加參數
- 缺點:無法繼承protype上的屬性,無法復用。
組合繼承
組合繼承指的是將原型鏈和借用構造函數的技術組合到一塊,從而發揮二者之長的一種繼承模式。
方法:在子函數中運行父函數,但是要利用call把this改變一下,再在子函數的prototype里面new Father() ,使Father的原型中的方法也得到繼承,最后改變Son的原型中的constructor
- 優點:組合繼承避免了原型鏈和借用構造函數的缺陷,融合了它們的優點??梢岳^承父類的屬性,可以傳參,父類的實例方法定義在父類的原型對象上,可以復用,不共享父類引用屬性。
- 缺點:調用了兩次父類的構造函數,(第一次是在創建子類原型的時候,第二次是在子類構造函數內部)。導致子的原型對象中增添了不必要的父類的實例對象中的所有屬性。
原型式繼承
簡單來說這個函數的作用就是,傳入一個對象,返回一個原型對象為該對象的新對象。
function object(o){function F(){};F.prototype = o;return new F(); }- 優點:可以實現基于一個對象的簡單繼承,不必創建構造函數
- 缺點:與原型鏈中提到的缺點相同,一個是傳參的問題,一個是屬性共享的問題,無法復用(新實例屬性都是后面添加的)。
寄生式繼承
創建一個僅用于封裝繼承過程的函數,該函數在內部以某種方式增強對象,最后返回這個對象。
- 優點:在主要考慮對象而不是自定義類型和構造函數的情況下,實現簡單的繼承。
- 缺點:使用該繼承方式,在為對象添加函數的時候,沒有辦法做到函數的復用(沒用到原型)。
寄生式組合繼承
解決組合繼承的缺點,在繼承原型時,我們繼承的不是父類的實例對象,而是原型對象是父類原型對象的一個實例對象。
優點:效率高,避免了在 SubType.prototype 上創建不必要的屬性。與此同時還能保持原型鏈不變,開發人員普遍認為寄生組合式繼承是引用類型最理想的繼承范式。
function inheritPrototype(subType, superType){var prototype = object(superType.prototype); // 創建原型對象是超類原型對象的一個實例對象prototype.constructor = subType; // 彌補因為重寫原型而失去的默認的 constructor 屬性。subType.prototype = prototype; // 實現原型繼承 }extend繼承
ES6的extend(寄生組合繼承的語法糖)
8.this指向問題
this 指向最后一次調用這個方法的對象
為什說this指向,誰調用就指向誰,是不嚴謹的說法?
9.改變this指向(call、apply、bind )
相同點:
都可以改變函數內部的this指向。
不同點:
主要應用場景:
10.什么是作用域,什么是作用域鏈?
- 規定變量和函數的可使用范圍稱為作用域
- 查找變量或者函數時,需要從局部作用域到全局作用域依次查找,這些作用域的集合稱作用域鏈。
11.什么是執行棧,什么是執行上下文?
執行棧
- 首先棧特點:先進后出
- 當進入一個執行環境,就會創建出它的執行上下文,然后進行壓棧,當程序執行完成時,它的執行上下文就會被銷毀,進行彈棧。
- 棧底永遠是全局環境的執行上下文,棧頂永遠是正在執行函數的執行上下文
- 只有瀏覽器關閉的時候全局執行上下文才會彈出
執行上下文(執行環境)
-
全局執行上下文
創建一個全局的window對象,并規定this指向window,執行js的時候就壓入棧底,關閉瀏覽器的時候才彈出 -
函數執行上下文
每次函數調用時,都會新創建一個函數執行上下文 -
eval執行上下文
eval的功能是把對應的字符串解析成JS代碼并運行
12.變量提升?
我們JS引擎在運行JS的時候,分為兩步,第一步是預解析,一步是代碼執行,預解析分為變量預解析(變量提升)與函數預解析(函數提升)。函數提升優先級高于變量提升
- 變量提升就是把所有的變量聲明提升到當前作用域的最前邊,變量賦值不會提升。
- 函數提升是函數的聲明會被提升到當前作用域的最前上邊,但是不會調用函數。
13.閉包
什么是閉包?
閉包是指有權訪問另一個函數作用域中變量的函數,
簡單理解就是,一個作用域可以訪問到另一個函數內部的局部變量。
- 作用:延伸變量的作用范圍,其主要目的是保證一個函數內部的變量既可以得到重用,又不被污染(不會被隨意篡改)
- 缺點:會造成內存泄漏(有一塊內存空間被長期占用,而不被釋放)
- 應用:防抖和節流、封裝私有變量、for循環中的保留i的操作、函數柯里化
14.事件循環(Event Loop)
當JS解析執行時,會被引擎分為兩類任務,同步任務(synchronous) 和 異步任務(asynchronous)。
- 對于同步任務來說,會被推到執行棧按順序去執行這些任務。
- 對于異步任務來說,當其可以被執行時,會被放到一個任務隊列(task queue)里等待JS引擎去執行。
當執行棧中的所有同步任務完成后,JS引擎才會去任務隊列里查看是否有任務存在,并將任務放到執行棧中去執行,執行完了又會去任務隊列里查看是否有已經可以執行的任務。這種循環檢查的機制,就叫做事件循環(Event Loop)。
15.宏任務和微任務
對于任務隊列,其實是有更細的分類。其被分為 微任務(microtask)隊列 == & == 宏任務(macrotask)隊列
微任務: Promise的then、Mutation Observer、process.nextTick()等,會被放在微任務(microtask)隊列。
宏任務: setTimeout、setInterval、ajax等,會被放在宏任務(macrotask)隊列。
Event Loop的執行順序是:
- 首先執行執行棧里的任務。(new Promise 構造函數是同步執行的)
- 執行棧清空后,檢查微任務(microtask)隊列,將可執行的微任務全部執行。
- 取宏任務(macrotask)隊列中的第一項執行。
- 回到第二步,循環執行
16.內存泄露、垃圾回收機制
什么是內存泄漏?
內存泄露是指不再用的內存沒有被及時釋放出來,導致該段內存無法被使用就是內存泄漏
為什么會導致的內存泄漏?
內存泄漏指我們無法在通過js訪問某個對象,而垃圾回收機制卻認為該對象還在被引用,因此垃圾回收機制不會釋放該對象,導致該塊內存永遠無法釋放,積少成多,系統會越來越卡以至于崩潰
哪些操作會造成內存泄漏?
- 未使用 var 聲明的全局變量
- 作用域未釋放(閉包)
- 定時器未清除
- 事件監聽為空白
如何優化內存泄漏?
- 全局變量先聲明在使用
- 避免過多使用閉包。
- 注意清除定時器和事件監聽器。
垃圾回收機制都有哪些策略?
- 標記清除法:垃圾回收器會在運行的時候給內存中的所有變量加上標記,然后去掉執行環境中的變量以及被執行環境中變量所引用的變量(閉包),在這些完成之后仍存在標記的就是要刪除的變量了
- 引用計數法:跟蹤記錄每個值被引用的次數。當聲明一個變量并給該變量賦值一個引用類型的值時候,該值的計數+1,當該值賦值給另一個變量的時候,該計數+1,當該值被其他值取代的時候,該計數-1,當計數變為0的時候,說明無法訪問該值了,垃圾回收機制清除該對象
17.深拷貝和淺拷貝
淺拷貝是拷貝一層,深層次的對象級別的就拷貝引用;
深拷貝是拷貝多層,每一級別的數據都會拷貝出來。
具體來說,淺拷貝的時候如果數據是基本數據類型,那么就如同直接賦值那種,會拷貝其本身,如果除了基本數據類型之外還有一層對象,那么對于淺拷貝而言就只能拷貝其引用,對象的改變會反應到拷貝對象上;但是深拷貝就會拷貝多層,即使是嵌套了對象,也會都拷貝出來。
淺拷貝實現:
深拷貝實現:
1.JSON.stringify,JSON.parse;但是遇到正則會變為空對象,函數為空,日期會變為字符串
18.var let const 有什么區別
2021.7.16
19.JS如何實現異步編程(5種)?
-
回調函數(callback)
優點:解決了同步的問題(只要有一個任務耗時很長,后面的任務都必須排隊等著,會拖延整個程序的執行。)
缺點:回調地獄,每個任務只能指定一個回調函數,不能 return. -
事件監聽。這種思路是說異步任務的執行不取決于代碼的順序,而取決于某個事件是否發生。比如一個我們注冊一個按鈕的點擊事件或者注冊一個自定義事件,然后通過點擊或者trigger的方式觸發這個事件。
-
Promise
-
Generator
-
生成器 async/await,是ES7提供的一種解決方案。
20.Promise?
ES6引入的異步編程的新解決方案,語法是一個構造函數 ,用來封裝異步操作并可以獲取其成功或失敗的結果. Promise對象有三種狀態:初始化pending 成功fulfilled 失敗rejected
需要注意:一旦從進行狀態變成為其他狀態就永遠不能更改狀態了。
特點:
-
將異步操作以同步操作的流程表達出來,避免了層層嵌套的回調函數。流程更加清晰,代碼更加優雅。
-
Promise對象提供統一的接口,使得控制異步操作更加容易。
缺點:
- 無法取消Promise,一旦新建它就會立即執行,無法中途取消。
- 如果不設置回調函數,Promise內部拋出的錯誤,不會反應到外部。
- 當處于pending狀態時,無法得知目前進展到哪一個階段(剛剛開始還是即將完成)。
21.Generator是怎么樣使用的以及各個階段的變化如何?
狀態變化
22.箭頭函數
箭頭函數()=>{}:
1.如果形參只有一個,則小括號可以省略;
2.函數體如果只有一條語句,則花括號可以省略,函數的返回值為該條語句的執行結果。
與普通函數的區別:
箭頭函數不能new :
1.沒有自己的this,不能調用call和apply
2.沒有prototype,new關鍵字內部需要把新對象的_proto_指向函數的prototype
箭頭函數的作用:
23.forEach、for in 、 for of三者的區別
forEach遍歷數組,但不能使用break、continue和return語句
for…in是用來循環帶有字符串key的對象的方法。實際是為循環”enumerable“(可枚舉)對象而設計的。
Js基本數據類型自帶的原型屬性不可枚舉,通過Object.defineProperty0方法指定enumeralbe為false的屬性不可枚舉。
for in循環出的是key,for of循環出的是value
for…of數組對象都可以遍歷,它是ES6中新增加的語法
一個數據結構只有部署了 Symbol.iterator 屬性, 才具有 iterator接口可以使用 for of循環。
哪些數據結構部署了 Symbol.iteratoer屬性了呢?
數組 Array
Map
Set
String
arguments對象
Nodelist對象, 就是獲取的dom列表集合
for of 遍歷對象需要通過和Object.keys()
let obj = {a: '1', b: '2', c: '3', d: '4'} for (let o of Object.values(obj)) {console.log(o) // 1,2,3,4 }24.模塊化
什么是模塊?
- 將一個復雜的程序依據一定的規則(規范)封裝成幾個塊(文件), 并進行組合在一起
- 塊的內部數據與實現是私有的, 只是向外部暴露一些接口(方法)與外部其它模塊通信
模塊化的好處
- 避免命名沖突(減少命名空間污染)
- 更好的分離, 按需加載
- 更高復用性
- 高可維護性
實現模塊化的方式
- CommonJS模塊
- AMD
- CMD
- ES6 模塊
CommonJS規范主要用于服務端編程,加載模塊是同步的,這并不適合在瀏覽器環境,因為同步意味著阻塞加載,瀏覽器資源是異步加載的,因此有了AMD CMD解決方案。
AMD規范在瀏覽器環境中異步加載模塊,而且可以并行加載多個模塊。不過,AMD規范開發成本高,代碼的閱讀和書寫比較困難,模塊定義方式的語義不順暢。
CMD規范與AMD規范很相似,都用于瀏覽器編程,依賴就近,延遲執行,可以很容易在Node.js中運行。不過,依賴SPM 打包,模塊的加載邏輯偏重
ES6 模塊在語言標準的層面上,實現了模塊功能,而且實現得相當簡單,完全可以取代 CommonJS 和 AMD 規范,成為瀏覽器和服務器通用的模塊解決方案。
CommonJS模塊 與 ES6模塊
CommonJS模塊:它通過 require 來引入模塊,通過 module.exports 定義模塊的輸出接口。這種模塊加載方案是服務器端的解決方案,它是以同步的方式來引入模塊的,因為在服務端文件都存儲在本地磁盤,所以讀取非常快,所以以同步的方式加載沒有問題。但如果是在瀏覽器端,由于模塊的加載是使用網絡請求,因此使用異步加載的方式更加合適。
ES6模塊:使用 import 和 export 的形式來導入導出模塊。
區別:
- CommonJS 模塊輸出的是值的拷貝,模塊內部的變化就影響不到這個值。ES6 模塊是動態引用,并且不會緩存值,模塊里面的變量綁定其所在的模塊。
- CommonJS 模塊是運行時加載,ES6 模塊是編譯時輸出接口。**CommonJS 模塊就是對象,即在輸入時是先加載整個模塊,生成一個對象,然后再從這個對象上面讀取方法,這種加載稱為“運行時加載”。而 ES6 模塊不是對象,它的對外接口只是一種靜態定義,在代碼靜態解析階段就會生成。
25.跨域的方式都有哪些?他們的特點是什么 ?
出于瀏覽器的同源策略限制。同源策略(Sameoriginpolicy)是一種約定,它是瀏覽器最核心也最基本的安全功能,如果缺少了同源策略,則瀏覽器的正常功能可能都會受到影響??梢哉fWeb是構建在同源策略基礎之上的,瀏覽器只是針對同源策略的一種實現。同源策略會阻止一個域的javascript腳本和另外一個域的內容進行交互。所謂同源(即指在同一個域)就是兩個頁面具有相同的協議(protocol),主機(host)和端口號(port)
JSONP 是服務器與客戶端跨源通信的常用方法。最大特點就是簡單適用,兼容性好(兼容低版本IE),缺點是只支持get請求,不支持post請求。
核心思想:網頁通過添加一個script元素,向服務器請求 JSON 數據,服務器收到請求后,將數據放在一個指定名字的回調函數的參數位置傳回來。
因為瀏覽器是通過document.domain屬性來檢查兩個頁面是否同源,因此只要兩個頁面通過設置相同的document.domain,兩個頁面就可以共享Cookie(此方案僅限主域相同,子域不同的跨域應用場景。)
CORS 是跨域資源分享(Cross-Origin Resource Sharing)的縮寫。它是 W3C 標準,屬于跨源 AJAX 請求的根本解決方法。
1、 普通跨域請求:只需服務器端設置Access-Control-Allow-Origin
2、帶cookie跨域請求:前后端都需要進行設置,前端設置根據xhr.withCredentials字段判斷是否帶有cookie,后端Java還可以使用springMVC的@CrossOrigin
ginx模擬一個虛擬服務器,因為服務器與服務器之間是不存在跨域的。
發送數據時 ,客戶端->nginx->服務端;返回數據時,服務端->nginx->客戶端。
26.DOM事件流
DOM 標準采用捕獲+冒泡。兩種事件流都會觸發 DOM 的所有對象,從 window 對象開始,也在 window 對象結束。
DOM 標準規定事件流包括三個階段:
- 事件捕獲階段
- 處于目標階段
- 事件冒泡階段
27.防抖和節流
防抖:n秒后在執行該事件,若在n秒內被重復觸發,則重新計時
節流: n秒內只運行一次,若在n秒內重復觸發,只有一次生效
28.柯里化
柯里化(Currying),又稱部分求值(Partial Evaluation),可以理解為提前接收部分參數,延遲執行,不立即輸出結果,而是返回一個接受剩余參數的函數。因為這樣的特性,也被稱為部分計算函數。柯里化,是一個逐步接收參數的過程。
柯里化有3個常見作用:
- 參數復用
- 提前返回
- 延遲計算/運行
29.js常見的設計模式
- 單例模式
- 工廠模式
- 構造函數模式
- 發布訂閱者模式
- 迭代器模式
- 代理模式
30.判斷數據類型的方法
typeof:無法判斷null,是obj類型,而且只能判斷出是引用類型
instanceof:
constructor:null和undefined是無效的對象,因此是不會有constructor存在的,這兩種類型的數據需要通過typeof來判斷。
Object.prototype.toString :Object.prototype.toString.call(undefined) ;
31.數組扁平化
遞歸
let arr = [1, [2, [3, 4]]];function flattern(arr) {let result = [];for(let i = 0; i < arr.length; i++) {if(Array.isArray(arr[i])) {flattern(arr[i])} else {result.push(arr[i])}}return result;} console.log(flattern(arr));toString
let arr = [1, [2, [3, 4]]]; function flatten(arr) {return arr.toString().split(',').map(function(item){return +item //+可以快速獲得Number類型}) } console.log(flatten(arr))reduce
let arr = [1, [2, [3, 4]]];function flatten(arr) {return arr.reduce(function(prev, next){return prev.concat(Array.isArray(next) ? flatten(next) : next)}, []) }console.log(flatten(arr))es6 展開運算符
let arr = [1, [2, [3, 4]]];function flatten(arr) {while (arr.some(item => Array.isArray(item))) {arr = [].concat(...arr);} //ES6新方法return arr; }console.log(flatten(arr))總結
- 上一篇: 偏相关系数 计算机,简单相关系数
- 下一篇: 微软拟建统一通信平台 实施大范围战略扩张