說一下JS 中的數(shù)據(jù)類型有哪些
JS 數(shù)據(jù)類型包括 基本 / 引用 / 特殊 數(shù)據(jù)類型:
1.基本數(shù)據(jù)類型:String、Number、Boolean
2.引用數(shù)據(jù)類型:Object、Array、Function
3.特殊數(shù)據(jù)類型:Null、Undefined
4.原始數(shù)據(jù)類型 Symbol (ES6)
5.獨(dú)一無二的值,即 Symbol('1’) != Symbol('1’)
追問:判斷 JS 數(shù)據(jù)類型有幾種方法
常用的有 typeof、instanceof,
不常用的有 constructor、 prototype / toString
1.typeof 是個(gè)一元運(yùn)算,放在任意類型的運(yùn)算數(shù)之前,返回一個(gè) 字符串 說明運(yùn)算數(shù)的類型。
可檢測(cè)出的類型有:
'number'、'string'、'boolean'、'object'
'undefined','function'、'symbol'
其中對(duì)象"object" 包括:Object、Array、new RegExp()、new Date() 和 Null 特殊類型
缺點(diǎn):判斷普通類型沒有問題,但不能準(zhǔn)確判斷 引用數(shù)據(jù)類型
2.instanceof 運(yùn)算符用來檢測(cè)一個(gè)對(duì)象在其原型鏈中是否存在一個(gè)構(gòu)造函數(shù)的 prototype 屬性
通俗講 instanceof 檢測(cè)的是原型,檢測(cè)左邊的對(duì)象是否是右邊類的實(shí)例
[] instanceof Array ==> true
注意:instanceof 能夠判斷出 [] 是 Array 的實(shí)例,也是 Object 的實(shí)例
因?yàn)?[].proto 指向 Array.prototype,而 Array.prototype.proto 又指向了 Object.prototype,最終 Object.prototype.proto 指向了 null 原型鏈結(jié)束。
類似的還有 new Date(),new Error() 和 new 自定義類()
歸納:所有對(duì)象都是 Object 的實(shí)例 或 Object是一切對(duì)象的父對(duì)象
3.根據(jù)對(duì)象的 constructor 判斷
原理:每個(gè)構(gòu)造函數(shù)都有一個(gè) constructor 屬,指回它本身
[].coconstructor === Array ==> true
判斷 數(shù)字、字符串、函數(shù) 和 日期時(shí),必須得用關(guān)鍵字 new 創(chuàng)建才行
因?yàn)橹挥袠?gòu)造函數(shù)才有 constructor 屬性,還有兩點(diǎn)需要注意:
null 和 undefined 是無效的對(duì)象,因此不會(huì)有 constructor 存在,
函數(shù)的 constructor 是不穩(wěn)定的,當(dāng)重寫 prototype 后,
原有的 constructor 引用會(huì)丟失,constructor 會(huì)默認(rèn)為 Object
4.使用 toString 判斷
toString() 是 Object 的原型方法,該方法默認(rèn)返回當(dāng)前對(duì)象的 [[Class]] 。
這是一個(gè)內(nèi)部屬性,其格式為 [object Xxx] ,其中 Xxx 就是對(duì)象的類型。
對(duì)于 Object 對(duì)象,直接調(diào)用 toString() 就能返回 [object Object] 。
而對(duì)于其他對(duì)象,則需要通過 call / apply 來調(diào)用才能返回正確的類型信息。
Object.prototype.toString.call(undefined) === '[object Undefined]'
Object.prototype.toString.call(null) === '[object Null]'
Object.prototype.toString.call(123) === '[object Number]'
5.JQuery 提供的 jquery.type()
返回說明操作數(shù)的字符串
jQuery.type(123) === "number"
jQuery.type(undefined) === "undefined"
jQuery.type(null ) === "null "
Query.type(new Date()) === "date"
jQuery.type(new Error()) === "error"
追問:null 和 undefined 有啥區(qū)別?
null:是 Null類型,表示一個(gè) 空對(duì)象指針 或 尚未存在的對(duì)象
即該處不應(yīng)該有值,使用typeof運(yùn)算得到 object ,是個(gè)特殊對(duì)象值,轉(zhuǎn)為數(shù)值為 0。
也可以理解是表示程序級(jí)的、正常的或在意料之中的值的空缺
- 作為函數(shù)的參數(shù),表示該函數(shù)的參數(shù)不是對(duì)象
- 作為對(duì)象原型鏈的終點(diǎn)
注意:null不是一個(gè)對(duì)象,但typeof null === object原因是不同的對(duì)象在底層都會(huì)表示為二進(jìn)制,在 JS 中如果二進(jìn)制的前三位都為 0,就會(huì)被判斷為object類型,null的二進(jìn)制全為 0,自然前三位也是 0,所以typeof null === 'objcet'
undefined:是Undefined 類型,表示一個(gè) 無 的原始值 或 缺少值,
即此處應(yīng)該有一個(gè)值,但還沒有定義,使用 typeof undefined === 'undefined',轉(zhuǎn)為數(shù)值為 NaN。
它是在 ECMAScript 第三版引入的預(yù)定義全局變量,為了區(qū)分空指針對(duì)象 和 未初始化的變量。
也可以理解是表示系統(tǒng)級(jí)的、出乎意料的或類似錯(cuò)誤的值的空缺
1.變量被聲但沒有賦值時(shí)
2.調(diào)用函數(shù)時(shí),應(yīng)該提供的參數(shù)沒有提供時(shí)
3.對(duì)象沒有賦值的屬性時(shí),屬性值為 undefined
4.函數(shù)沒有返回值時(shí),默認(rèn)返回值為 undefined
追問: JS 有哪些內(nèi)置對(duì)象
數(shù)據(jù)封裝類對(duì)象:Object、Array、Boolean、Number、String
其他對(duì)象:Function、Arguments、Math、Date、RegExp、Error
追問:說說你對(duì)原型和原型鏈的理解
原型: 每一個(gè)構(gòu)造函數(shù)都會(huì)自動(dòng)帶一個(gè) prototype 屬性,是個(gè)指針,指向一個(gè)對(duì)象,就是 原型對(duì)象。
原型對(duì)象 上默認(rèn)有一個(gè)屬性constructor ,也是個(gè)指針,指向構(gòu)造函數(shù)本身。
- 優(yōu)點(diǎn):原型對(duì)象上所有的 屬性 和 方法 都能被構(gòu)造函數(shù)的 實(shí)例對(duì)象 共享訪問。
- 缺點(diǎn):多個(gè)實(shí)例對(duì)引用類型的操作會(huì)被篡改。
因?yàn)槊看螌?shí)例化,引用類型的數(shù)據(jù)都指向同一個(gè)地址,所以它們 讀/寫 的是同一個(gè)數(shù)據(jù),當(dāng)一個(gè)實(shí)例對(duì)其進(jìn)行操作,其他實(shí)例的數(shù)據(jù)就會(huì)一起更改( 這也是 Vue 中 data 為什么是一個(gè)函數(shù)的原因 )。
原型鏈: 每個(gè)實(shí)例對(duì)象都有一個(gè)原型__proto__,這個(gè)原型還可以有它自己的原型,以此類推,形成一個(gè)鏈?zhǔn)浇Y(jié)構(gòu)即原型鏈。
每個(gè)構(gòu)造函數(shù)都有一個(gè)原型對(duì)象prototype,原型對(duì)象上包含一個(gè)指向構(gòu)造函數(shù)的指針 constructor
而每個(gè)實(shí)例都包含著一個(gè)指向原型對(duì)象的內(nèi)部指針 __proto__。
可以通過內(nèi)部指針 __proto__訪問到原型對(duì)象,原型對(duì)象通過 constructor 找到構(gòu)造函數(shù)。
如果 A對(duì)象 在 B 對(duì)象的原型鏈上,可以說它們是 B對(duì)象繼承了 A對(duì)象。
原型鏈作用:如果試圖訪問對(duì)象的某個(gè)屬性,會(huì)首先在 對(duì)象內(nèi)部 尋找該屬性,直至找不到,然后才在該對(duì)象的原型里去找這個(gè)屬性,以此類推。
new 關(guān)鍵字創(chuàng)建一個(gè)實(shí)例都做了什么?
1.像普通對(duì)象一樣,形成自己的私有作用域( 形參賦值,變量提升 )
2.創(chuàng)建一個(gè)新對(duì)象,將 this 指向這個(gè)新對(duì)象( 構(gòu)造函數(shù)的作用域賦給新對(duì)象 )
3.執(zhí)行構(gòu)造函數(shù)中的代碼,為這個(gè)新對(duì)象添加屬性、方法
4.返回這個(gè)新對(duì)象( 新對(duì)象為構(gòu)造函數(shù)的實(shí)例 )
手寫一個(gè) new 原理如下:
function myNew(fn, ...arg){
// 創(chuàng)建一個(gè)對(duì)象,讓它的原型鏈指向 fn.prototype
// 普通方法
// let obj = {};
// obj.__proto__ = fn.prototype;
// 使用 Object.create([A對(duì)象]):創(chuàng)建一個(gè)空對(duì)象 obj,并讓 obj.__proto__ 等于 A對(duì)象
let obj = Object.create(fn.prototype);
fn.call(obj, ...arg);
return obj;
}
可以用 instanceof 測(cè)試構(gòu)造函數(shù)的prototype屬性是否出現(xiàn)在實(shí)例對(duì)象的原型鏈中
也可以用 obj.hasOwnProperty(prop)測(cè)試對(duì)象自身屬性中是否具有指定的屬性
追問:call / apply / bind 有啥區(qū)別
都是替換函數(shù)中不想要的this:
call和 apply 是臨時(shí)的且立即執(zhí)行,
bind 是永久綁定不立即執(zhí)行,返回一個(gè)新函數(shù),需要時(shí)再去執(zhí)行這個(gè)新函數(shù)。
call: call( thisObj, obj1, obj2... )
要求傳入函數(shù)的參數(shù)必須單獨(dú)傳入
apply: apply(t hisObj, [argArray] )
要求傳入函數(shù)的參數(shù)必須放入數(shù)組中整體傳入
apply會(huì)將數(shù)組打散為單個(gè)參數(shù)值分別傳入
bind: 永久綁定函數(shù)中的 this,作用如下:
1.創(chuàng)建一個(gè)和原函數(shù)功能完全一樣的新函數(shù).
2.將新函數(shù)中的 this 永久綁定為指定對(duì)象
3.將新函數(shù)中的部分固定參數(shù)提前永久綁定
說說 ES6、ES7、ES8 的新特性
ES6的特性:
1.類(class)
2.模塊化(Module)導(dǎo)出(export)導(dǎo)入(import)
3.箭頭(Arrow)函數(shù)
4.函數(shù)參數(shù)默認(rèn)值
5.模板字符串
6.延展操作符(Spread operator) 和 剩余運(yùn)算符(rest operator)
7.ES6中允許我們?cè)谠O(shè)置一個(gè)對(duì)象的屬性的時(shí)候不指定屬性名
8.Promise 異步編程的解決方案
9.支持 let 與 const 塊級(jí)作用域
ES7的特性
1.includes() 函數(shù)用來判斷一個(gè)數(shù)組是否包含一個(gè)指定的值,返回true / false
2.指數(shù)操作符在ES7中引入了指數(shù)運(yùn)算符,具有與Math.pow(..)等效的計(jì)算結(jié)果
ES8的特性
1.加入了對(duì) async/await 的支持,也就我們所說的異步函數(shù)
2.Object.values() 是一個(gè)與 Object.keys()類似的新函數(shù),但返回的是 Object 自身屬性的所有值,不包括繼承的值
3.Object.entries() 函數(shù)返回一個(gè)給定對(duì)象自身可枚舉屬性的鍵值對(duì)的數(shù)組
4.String.padStart(targetLength,[padString])和 String.padEnd(targetLength,padString])
5.Object.getOwnPropertyDescriptors() 函數(shù)用來獲取一個(gè)對(duì)象的所有自身屬性的描述符,如果沒有任何自身屬性,則返回空對(duì)象。
require 和 import 區(qū)別
import 和 require都是被模塊化所使用。
1.遵循規(guī)范
- require 是AMD規(guī)范引入方式
- import是es6的語法標(biāo)準(zhǔn),如要兼容瀏覽器的話必須轉(zhuǎn)化成es5的語法
2.調(diào)用時(shí)間
- require是運(yùn)行時(shí)調(diào)用,所以require理論上可以運(yùn)用在代碼的任何地方
- import是編譯時(shí)調(diào)用,所以必須放在文件開頭
3.本質(zhì)
- require是賦值過程,其實(shí)require的結(jié)果就是對(duì)象、數(shù)字、字符串、函數(shù)等,再把require的結(jié)果賦值給某個(gè)變量
- import是解構(gòu)過程,但是目前所有的引擎都還沒有實(shí)現(xiàn)import,我們使用babel支持ES6,也僅僅是將ES6轉(zhuǎn)碼為ES5再執(zhí)行,import語法會(huì)被轉(zhuǎn)碼為require
4.性能
- require的性能相對(duì)于import稍低,因?yàn)閞equire是在運(yùn)行時(shí)才引入模塊并且還賦值給某個(gè)變量
- import只需要依據(jù)import中的接口在編譯時(shí)引入指定模塊所以性能稍高
追問:Es6 Module 和 Common.js 的區(qū)別
CommonJS
- 對(duì)于基本數(shù)據(jù)類型,屬于復(fù)制,會(huì)被模塊緩存??稍诹硪粋€(gè)模塊可以對(duì)該模塊輸出的變量重新賦值。
- 對(duì)于復(fù)雜數(shù)據(jù)類型,屬于淺拷貝。由于兩個(gè)模塊引用的對(duì)象指向同一個(gè)內(nèi)存空間,因此對(duì)該模塊的值做修改時(shí)會(huì)影響另一個(gè)模塊。
- 當(dāng)使用 require 命令加載某個(gè)模塊時(shí),就會(huì)運(yùn)行整個(gè)模塊的代碼。
- common.js 同一個(gè)模塊無論加載多少次,都只會(huì)在第一次加載時(shí)運(yùn)行一次,以后再加載就返回第一次運(yùn)行的結(jié)果,除非手動(dòng)清除系統(tǒng)緩存。
- 循環(huán)加載時(shí),屬于加載時(shí)執(zhí)行。即腳本代碼在 require 的時(shí)候,就會(huì)全部執(zhí)行。一旦出現(xiàn)某個(gè)模塊被 "循環(huán)加載",就只輸出已經(jīng)執(zhí)行的部分,還未執(zhí)行的部分不會(huì)輸出
ES6 Module 模塊
ES6 模塊中的值屬于動(dòng)態(tài)只讀引用。
- 只讀:不允許修改引入變量的值,import 的變量是只讀的( 包括 基本/復(fù)雜 數(shù)據(jù)類型 )。當(dāng)模塊遇到 import 命令時(shí),就會(huì)生成一個(gè)只讀引用。等到腳本真正執(zhí)行時(shí),再根據(jù)這個(gè)只讀引用,到被加載的那個(gè)模塊里面去取值。
- 動(dòng)態(tài):原始值發(fā)生變化,import 加載的值也會(huì)發(fā)生變化( 包括 基本/復(fù)雜 數(shù)據(jù)類型)。
循環(huán)加載時(shí),ES6 模塊是動(dòng)態(tài)引用( 只要兩個(gè)模塊之間存在某個(gè)引用,代碼就能執(zhí)行 )。
綜上:
- common.js 是 module.exports / exports 導(dǎo)出,require 導(dǎo)入;ES6 則是 export 導(dǎo)出,import 導(dǎo)入。
- common.js 是運(yùn)行時(shí)加載模塊,ES6 是在靜態(tài)編譯期間就確定模塊的依賴。
- ES6 在編譯期間會(huì)將所有 import 提升到頂部,common.js 不會(huì)提升 require。
- common.js 導(dǎo)出的是一個(gè)值拷貝,會(huì)對(duì)加載結(jié)果進(jìn)行緩存,一旦內(nèi)部再修改這個(gè)值,則不會(huì)同步到外部。ES6 是導(dǎo)出的一個(gè)引用,內(nèi)部修改可以同步到外部。
- 兩者的循環(huán)導(dǎo)入的實(shí)現(xiàn)原理不同,common.js 是當(dāng)模塊遇到循環(huán)加載時(shí),返回的是當(dāng)前已經(jīng)執(zhí)行的部分的值,而不是代碼全部執(zhí)行后的值,兩者可能會(huì)有差異。ES6 模塊是動(dòng)態(tài)引用,如果使用 import 從一個(gè)模塊加載變量(即import foo from 'foo'),那些變量不會(huì)被緩存,而是成為一個(gè)指向被加載模塊的引用。
- common.js 中頂層的 this 指向這個(gè)模塊本身,而 ES6 中頂層 this 指向 undefined。
事件委托是什么,原理是什么
事件委托: 利用事件冒泡,只指定一個(gè)事件處理程序,就可以管理某一類型的所有事件。
原理:利用事件的 冒泡原理
事件冒泡:就是事件從最深的節(jié)點(diǎn)開始,然后逐步向上傳播事件。
作用:
- 提高性能:每一個(gè)函數(shù)都會(huì)占用內(nèi)存空間,只需添加一個(gè)事件處理程序代理所有事件,所占用的內(nèi)存空間更少;
- 動(dòng)態(tài)監(jiān)聽:使用事件委托可以自動(dòng)綁定動(dòng)態(tài)添加的元素,即新增的節(jié)點(diǎn)不需要主動(dòng)添加也可以具有和其它元素一樣的事件。
如何 阻止冒泡 和 默認(rèn)事件
停止冒泡:
window.event ? window.event.cancelBubble = true : e.stopPropagation();
阻止默認(rèn)事件:
window.event ? window.event.returnValue = false : e.preventDefault();
追問:說前端中的事件流
事件發(fā)生時(shí)在元素節(jié)點(diǎn)之間按照特定的順序傳播的過程叫做DOM事件流
共分為三大階段:
捕獲階段(事件從 Document 節(jié)點(diǎn) 自上而下 向目標(biāo)節(jié)點(diǎn)傳播的階段)
目標(biāo)階段(真正的目標(biāo)節(jié)點(diǎn)正在處理事件的階段)
冒泡階段(事件從目標(biāo)節(jié)點(diǎn) 自下而上 向 Document 節(jié)點(diǎn)傳播的階段)
事件冒泡:從事件源逐級(jí)向上傳播到 DOM 最頂層節(jié)點(diǎn)的過程。
事件捕獲:從 DOM 最頂層節(jié)點(diǎn)逐級(jí)向下傳播到事件源的過程。
追問:說說事件隊(duì)列
JavaScript語言的一大特點(diǎn)就是 單線程,同一個(gè)時(shí)間只能做一件事。
作為瀏覽器腳本語言,JavaScript 的主要用途是與用戶互動(dòng),以及操作 DOM。這決定了它只能是 單線程,否則會(huì)帶來很復(fù)雜的同步問題。比如 JavaScript 同時(shí)有兩個(gè)線程,一個(gè)線程在某個(gè) DOM 節(jié)點(diǎn)上添加內(nèi)容,另一個(gè)線程刪除了這個(gè)節(jié)點(diǎn),這時(shí)瀏覽器應(yīng)該以哪個(gè)線程為準(zhǔn)?
為了利用多核 CPU 的計(jì)算能力,HTML5 提出 Web Worker 標(biāo)準(zhǔn),允許 JavaScript 腳本創(chuàng)建多個(gè)線程,但是子線程完全受主線程控制,且不得操作 DOM。所以,這個(gè)新標(biāo)準(zhǔn)并沒有改變 JavaScript單線程 的本質(zhì)。
任務(wù)隊(duì)列的本質(zhì)
- 所有 同步任務(wù) 都在 主線程 上執(zhí)行,形成一個(gè)執(zhí)行棧(execution context stack)。
- 主線程之外,還有一個(gè) 任務(wù)隊(duì)列(task queue)。
只要 異步任務(wù) 有了運(yùn)行結(jié)果,就在 任務(wù)隊(duì)列 之中放置一個(gè)事件。 - 等 執(zhí)行棧 中的所有同步任務(wù)執(zhí)行完畢,系統(tǒng)就會(huì)讀取 任務(wù)隊(duì)列,看看里面有哪些事件。
哪些對(duì)應(yīng)的異步任務(wù),于是結(jié)束等待狀態(tài),進(jìn)入執(zhí)行棧,開始執(zhí)行。 - 主線程不斷重復(fù)上面的第三步。
主線程(執(zhí)行棧)和 任務(wù)隊(duì)列 先進(jìn)先出 的通信稱為 事件循環(huán)( Event Loop )
主要分為:
宏任務(wù)(macro-task):DOM事件綁定,定時(shí)器,Ajax回調(diào)
微任務(wù)(micro-task):Promise,MutationObserver (html5新特性)
事件循環(huán)機(jī)制:主線程 =>所有微任務(wù) ->宏任務(wù)
先進(jìn)先執(zhí)行,如果里面有微任務(wù),則下一步先執(zhí)行微任務(wù),否則繼續(xù)執(zhí)行宏任務(wù)
setTimeout()
將事件插入到了事件隊(duì)列,必須等到當(dāng)前代碼(執(zhí)行棧)執(zhí)行完,主線程才會(huì)去執(zhí)行它指定的回調(diào)函數(shù)。
當(dāng)主線程時(shí)間執(zhí)行過長(zhǎng),無法保證回調(diào)會(huì)在事件指定的時(shí)間執(zhí)行。
瀏覽器端每次setTimeout 會(huì)有 4ms 的延遲,當(dāng)連續(xù)執(zhí)行多個(gè) setTimeout,有可能會(huì)阻塞進(jìn)程,造成性能問題。
setImmediate()
事件插入到事件隊(duì)列尾部,主線程和事件隊(duì)列的函數(shù)執(zhí)行完成之后立即執(zhí)行。和 setTimeout(fn,0) 的效果差不多。
追問:說說堆棧
棧內(nèi)存 一般儲(chǔ)存 基礎(chǔ)數(shù)據(jù)類型,遵循 先進(jìn)后出 與 后進(jìn)先出 的原則,大小固定并由系統(tǒng)自動(dòng)分配內(nèi)存空間,運(yùn)行效率高,有序存儲(chǔ)
棧 中的 DOM render,ajax,setTimeout,setInterval會(huì)依次進(jìn)入到隊(duì)列中,當(dāng)棧中代碼執(zhí)行完畢后,再將隊(duì)列中的事件放到執(zhí)行棧中依次執(zhí)行
堆內(nèi)存 一般儲(chǔ)存 引用數(shù)據(jù)類型,JavaScript 不允許直接訪問 堆內(nèi)存 中的位置,需要從 棧中 獲取該對(duì)象的地址引用/指針,再從 堆內(nèi)存 中獲取數(shù)據(jù)。存儲(chǔ)值大小不定,可動(dòng)態(tài)調(diào)整,主要用來存放對(duì)象??臻g大,但是運(yùn)行效率相對(duì)較低,無序存儲(chǔ),可根據(jù)引用直接獲取。
說下代碼執(zhí)行結(jié)果
let obj = {}, a = 0, b = '0';
obj[a] = 123;
obj[b] = 456;
console.log(obj); // {0: 456}
對(duì)象存在 堆 中,數(shù)字屬性 和 字符串屬性相等
let obj = {}, a = Symbol(1), b = Symbol(1);
obj[a] = 123;
obj[b] = 456;
console.log(obj); // {Symbol(1): 123, Symbol(1): 456}
Symbol 表示獨(dú)一無二的值,即 Symbol(1) != Symbol(1)
let obj = {}, a = {name: '張三'}, b = {name: '李四'};
obj[a] = 123;
obj[b] = 456;
console.log(obj); // {[object Object]: 456}
把對(duì)象作為另一個(gè)對(duì)象的屬性時(shí),會(huì) 調(diào)用 toString 轉(zhuǎn)換為字符串
追問:對(duì)象和數(shù)組有啥區(qū)別
對(duì)象:是包含已命名的值的無序集合,也被稱為關(guān)聯(lián)數(shù)組
數(shù)組:是包含已編碼的值的有序集合
- 創(chuàng)建方式不同,數(shù)組是[] / new Array,對(duì)象是{} / new Object。
- 調(diào)用方式不同,數(shù)組是 arr[下標(biāo)],對(duì)象是 obj.加屬性名 / [屬性名]。
- 數(shù)組是有序數(shù)據(jù)的集合,對(duì)象是無序。
- 數(shù)組的數(shù)據(jù)沒有名稱,只有下標(biāo),對(duì)象的數(shù)據(jù)需要指定名稱。
- 數(shù)組的元素可以重復(fù),對(duì)象的屬性是唯一的。
- 數(shù)組的有長(zhǎng)度,而對(duì)象沒有。
追問:數(shù)組常用的操作方法有哪些
操作數(shù)組:push,splice,join,concat
遍歷數(shù)組:map,forEach,reduce
篩選數(shù)組:filter,some,find,findIndex
追問:如何快速合并兩個(gè)數(shù)組?
(a). arrA.concat(arrB)
(b). Array.prototype.push.apply(arrA,arrB);
(c). Array.prototype.concat.apply(arrA,arrB);
(d). Array.prototype.concat.call(arrA,arrB);
(e). 數(shù)組轉(zhuǎn)成字符串拼接在切割成數(shù)組, 或者是循環(huán)其中一個(gè)數(shù)組等...
性能自測(cè)對(duì)比:
Array.prototype.concat.call > Array.prototype.concat.apply > concat > Array.prototype.push.apply
追問:map 和 forEach 有何區(qū)別
相同點(diǎn):
都是循環(huán)遍歷數(shù)組中的每一項(xiàng)
forEach 和 map方法里每次執(zhí)行匿名函數(shù)都支持3個(gè)參數(shù),
參數(shù)分別是item(當(dāng)前每一項(xiàng)),index(索引值),arr(原數(shù)組)匿名函數(shù)中的
this都是指向 window( 在 Vue 中指向 Vue 實(shí)例)
不同點(diǎn):map() 返回一個(gè)新數(shù)組,原數(shù)組不會(huì)改變,可鏈?zhǔn)秸{(diào)用
forEach() 返回值為 undefined,可鏈?zhǔn)秸{(diào)用
場(chǎng)景:
如只是單純的遍歷可用 forEach()
如操作原數(shù)組得到新數(shù)組可用 map()
追問:什么是數(shù)組扁平化,實(shí)現(xiàn)扁平化的方法有哪些?
數(shù)組扁平化:一個(gè)多維數(shù)組變?yōu)橐痪S數(shù)組,方法如下:
1.flat( ES 6)
flat() 方法會(huì)按照一個(gè)可指定的深度遞歸遍歷數(shù)組,并將所有元素與遍歷到的子數(shù)組中的元素合并為一個(gè)新數(shù)組返回。
let newArray = arr.flat([depth]);
depth值可選: 指定要提取嵌套數(shù)組的結(jié)構(gòu)深度,默認(rèn)值為 1,不確定層級(jí)也可寫 `Infinity`。
2.reduce
function flatten(arr) {
return arr.reduce((result, item)=> {
return result.concat(Array.isArray(item) ? flatten(item) : item);
}, []);
}
3.String & split
function flatten(arr) {
return arr.toString().split(',').map(function(item) {
return Number(item);
})
}
4.join & split
function flatten(arr) {
return arr.join(',').split(',').map(function(item) {
return parseInt(item);
})
}
5.擴(kuò)展運(yùn)算符
[].concat(...[1, 2, 3, [4, 5]]); // [1, 2, 3, 4, 5]
也可以做一個(gè)遍歷,若 arr 中含有數(shù)組則使用一次擴(kuò)展運(yùn)算符,直至沒有為止,如下:
擴(kuò)展運(yùn)算符每次只能展開一層數(shù)組
function flatten(arr) {
while(arr.some(item=>Array.isArray(item))) {
arr = [].concat(...arr);
}
return arr;
}
6.遞歸
function flatten(arr) {
var res = [];
arr.map(item => {
if(Array.isArray(item)) {
res = res.concat(flatten(item));
} else {
res.push(item);
}
});
return res;
}
追問:說說緩存 SessionStorage,LocalStorage,Cookie
sessionStorage 是會(huì)話級(jí)別存儲(chǔ),只要會(huì)話結(jié)束關(guān)閉窗口,sessionStorage 立即被銷毀。
localStorage 是持久化的本地存儲(chǔ),除非主動(dòng)刪除數(shù)據(jù),否則數(shù)據(jù)是永遠(yuǎn)不會(huì)過期的。
sessionStroage 和 localStroage 存儲(chǔ)大小可以達(dá)到 5M,不能和服務(wù)器做交互。
cookie 的數(shù)據(jù)會(huì)始終在同源http請(qǐng)求中攜帶,在瀏覽器和服務(wù)器之間來回傳遞。單個(gè)cookie 不能超過4K,只在設(shè)置的 cookie 過期時(shí)間之前有效,即使窗口關(guān)閉或?yàn)g覽器關(guān)閉 。很多瀏覽器都限制一個(gè)站點(diǎn)最多保存20個(gè)Cookie。
說說深拷貝 和 淺拷貝
淺拷貝:只復(fù)制指向某個(gè)對(duì)象的指針,而不復(fù)制對(duì)象本身,新舊對(duì)象還是共享同一塊內(nèi)存。如果其中一個(gè)對(duì)象改變了這個(gè)地址,就會(huì)影響到另一個(gè)對(duì)象。
- 直接用=賦值
- Object.assign
只是在根屬性(對(duì)象的第一層級(jí))創(chuàng)建了一個(gè)新的對(duì)象,但是對(duì)于屬性的值是仍是對(duì)象的話依然是淺拷貝。
Object.assign 還有一些注意的點(diǎn)是:
(1)不會(huì)拷貝對(duì)象繼承的屬性
(2)不可枚舉的屬性
(3)屬性的數(shù)據(jù)屬性/訪問器屬性
(4)可以拷貝Symbol類型 - for in 循環(huán)只遍歷第一層
深拷貝:將一個(gè)對(duì)象從內(nèi)存中完整的拷貝一份出來,從堆內(nèi)存中開辟一個(gè)新的區(qū)域存放新對(duì)象,且修改新對(duì)象不會(huì)影響原對(duì)象。
- 用 JSON.stringify 把對(duì)象轉(zhuǎn)換成字符串,再用 JSON.parse 把字符串轉(zhuǎn)換成新的對(duì)象
注意:屬性值為函數(shù)時(shí)該屬性會(huì)丟失,為正則時(shí)會(huì)轉(zhuǎn)為空對(duì)象,為new Date()時(shí)會(huì)轉(zhuǎn)為字符串 - 采用遞歸去拷貝所有層級(jí)屬性
- 用 Slice 實(shí)現(xiàn)對(duì)數(shù)組的深拷貝
- 使用擴(kuò)展運(yùn)算符實(shí)現(xiàn)深拷貝
// 遞歸算法實(shí)現(xiàn)深克隆
function deepClone(obj){
if(obj === null) return null;
if(typeof obj !=='object') return obj;
if(obj instanceof RegExp) return new RegExp(obj);
if(obj instanceof Date) return new Date(obj);
// 克隆的結(jié)果和之前保持相同的所屬類
let newObj = new obj.constructor;
for(let key in obj){
if(obj.hasOwnProperty(key)){
newObj[key] = deepFn(obj[key]);
}
}
return newObj
}
.說說 DOM 和 BOM
ECMAScript (核心) : 描述了 JS 的語法 和 基本對(duì)象。
文檔對(duì)象模型 (DOM): 處理 網(wǎng)頁內(nèi)容 的方法和接口。
W3C 的標(biāo)準(zhǔn)( 所有瀏覽器公共遵守的標(biāo)準(zhǔn) )
瀏覽器對(duì)象模型 (BOM): 與 瀏覽器交互 的方法和接口。
各個(gè)瀏覽器廠商根據(jù) DOM 在各自瀏覽器上的實(shí)現(xiàn)( 不同廠商之間實(shí)現(xiàn)存在差異 )
DOM 的 API :
節(jié)點(diǎn)創(chuàng)建型 API:
document.createElement(),document.createTextNode(),parent.cloneNode(true)
document.createDocumentFragment() 創(chuàng)建文檔片段,解決大量添加節(jié)點(diǎn)造成的回流問題
頁面修改型 API:
parent.appendChild(child),parent.removeChild(child)
parent.replcaeChild(newChild,oldChild)
parent.insertBefore(newNode, referenceNode)
節(jié)點(diǎn)查詢型 API:
document.getElementById()
document.getElementsByTagName() 返回即時(shí)的 HTMLCollection 類型
document.getElementsByName() 根據(jù)指定的 name 屬性獲取元素,返回即時(shí)的 NodeList
document.getElementsByClassName() 返回即時(shí)的 HTMLCollection
document.querySelector() 獲取匹配到的第一個(gè)元素,采用的是深度優(yōu)先搜索
docuemnt.querySelectorAll() 返回非即時(shí)的 NodeList,也就是說結(jié)果不會(huì)隨著文檔樹的變化而變化
節(jié)點(diǎn)關(guān)系型 API:
父關(guān)系型:
node.parentNode()
兄弟關(guān)系型:
node.previouSibling() 返回節(jié)點(diǎn)的前一個(gè)節(jié)點(diǎn)(包括元素節(jié)點(diǎn),文本節(jié)點(diǎn),注釋節(jié)點(diǎn))
node.previousElementSibling() 返回前一個(gè)元素節(jié)點(diǎn)
node.nextSibling() 返回下一個(gè)節(jié)點(diǎn)
node.nextElementSibling() 返回下一個(gè)元素節(jié)點(diǎn)
子關(guān)系型
parent.childNodes() 返回一個(gè)即時(shí)的NodeList,包括了文本節(jié)點(diǎn)和注釋節(jié)點(diǎn)
parent.children() 一個(gè)即時(shí)的HTMLCollection,子節(jié)點(diǎn)都是Element
parent.firsrtNode(),parent.lastNode(),hasChildNodes()
元素屬性型 API:
element.setAttribute(“name”,“value”) 為元素添加屬性
element.getAtrribute(“name”) 獲取元素的屬性
元素樣式型 API:
window.getComputedStyle(element) 返回一個(gè)CSSStyleDeclaration,可以從中訪問元素的任意樣式屬性。
element.getBoundingClientRect() 返回一個(gè)DOMRect對(duì)象,里面** 包括了元素相對(duì)于可視區(qū)的位置 top,left**,以及元素的大小,單位為純數(shù)字。可用于判斷某元素是否出現(xiàn)在了可視區(qū)域
BOM的 API :
- location對(duì)象
.href、.search、.hash、.port、.hostname、pathname - history對(duì)象
.go(n)(前進(jìn)或后退指定的頁面數(shù))、history.back(后退一頁)、.forward(前進(jìn)一頁) - navigator對(duì)象
navigator:包含了用戶瀏覽器的信息
navigator.userAgent:返回用戶代理頭的字符串表示(就是包括瀏覽器版本信息等的字符串)
navigator.cookieEnabled:返回瀏覽器是否支持(啟用) cookie
window對(duì)象方法:
- alert() -- 顯示帶有一段消息和一個(gè)確認(rèn)按鈕的警告彈出框。
- confirm() -- 顯示帶有一段消息以及確認(rèn)按鈕和取消按鈕的警告彈出框。
- prompt() -- 顯示帶有一段消息以及可提示用戶輸入的對(duì)話框和確認(rèn),取消的警告彈出框。
- open() -- 打開一個(gè)新的瀏覽器窗口或查找一個(gè)已命名的窗口。
- close() -- 關(guān)閉瀏覽器窗口。
- setInterval() -- 按照指定的周期(以毫秒計(jì))來調(diào)用函數(shù)或計(jì)算表達(dá)式。每隔多長(zhǎng)時(shí)間執(zhí)行一下這個(gè)函數(shù)
- clearInterval() -- 取消由 setInterval() 設(shè)置的 timeout。
- setTimeout() -- 在指定的毫秒數(shù)后調(diào)用函數(shù)或計(jì)算表達(dá)式。
- clearTimeout() -- 取消由 setTimeout() 方法設(shè)置的 timeout。
- scrollTo() -- 把內(nèi)容滾動(dòng)到指定的坐標(biāo)。
.js 延遲加載的方式有哪些?
- fer
會(huì)告訴瀏覽器立即下載,但延遲整個(gè)頁面都解析完畢之后再執(zhí)行
按順序依次執(zhí)行 - async
不讓頁面等待腳本下載和執(zhí)行,從而異步加載頁面其他內(nèi)容。
將會(huì)在下載后盡快執(zhí)行,不能保證腳本會(huì)按順序執(zhí)行( 在onload 事件之前完成 )。 - 動(dòng)態(tài)創(chuàng)建DOM方式(創(chuàng)建script,插入到DOM中,加載完畢后callBack)
- 使用 setTimeout 延遲方法
- 讓 JS 最后加載
.說說跨域
跨域:指一個(gè)域下的文檔或腳本試圖去請(qǐng)求另一個(gè)域下的資源,由于瀏覽器同源策略限制而產(chǎn)生。
同源策略: 同協(xié)議+同端口+同域名。即便兩個(gè)不同的域名指向同一個(gè)ip地址,也非同源。
如果缺少了同源策略,瀏覽器很容易受到XSS、CSFR 等攻擊。
解決方案:
- Vue 配置代理類
proxy - jsonp 利用標(biāo)簽沒有跨越的特點(diǎn),單只能實(shí)現(xiàn)
get請(qǐng)求不能post請(qǐng)求 - CORS 跨域資源共享,只服務(wù)端設(shè)置
Access-Control-Allow-Origin即可,前端無須設(shè)置 - nginx代理轉(zhuǎn)發(fā)
- window.name + iframe跨域: 通過iframe的src屬性由外域轉(zhuǎn)向本地域,跨域數(shù)據(jù)即由iframe的
window.name從外域傳遞到本地域 - location.hash + iframe: a欲與b跨域相互通信,通過中間頁c來實(shí)現(xiàn)。 三個(gè)頁面,不同域之間利用iframe的
location.hash傳值,相同域之間直接js訪問來通信。 - document.domain + iframe跨域(僅限主域相同,子域不同的跨域應(yīng)用場(chǎng)景):兩個(gè)頁面都通過js強(qiáng)制設(shè)置
document.domain為基礎(chǔ)主域,就實(shí)現(xiàn)了同域;
.for in 和 for of 的區(qū)別
-for in遍歷的是數(shù)組的索引,在for in中
(1).for in中 index 索引為字符串型數(shù)字,不能直接進(jìn)行幾何運(yùn)算
(2). for in 遍歷順序有可能不是按照實(shí)際數(shù)組的內(nèi)部順序
(3). 因?yàn)?code>for in是遍歷可枚舉的屬性,也包括原型上的屬性( 如不想遍歷原型上的屬性,可通過 hasOwnProperty 判斷某個(gè)屬性是屬于原型 還是 實(shí)例上 )。
- for of 遍歷的是數(shù)組的元素值
for of只是遍歷數(shù)組的內(nèi)部,不會(huì)遍歷原型上的屬性和索引
也可以通過ES5的Object.keys(obj)來獲取實(shí)例對(duì)象上的屬性組成的數(shù)組
一般是使用for in 來遍歷對(duì)象,for of 遍歷數(shù)組
.instanceof 的原理是什么?
function myInstanceof(left, right) {
let prototype = right.prototype
left = left.__proto__
while (true) {
if (left === null || left === undefined)
return false
if (prototype === left)
return true
left = left.__proto__
}
}
思路:
首先獲取類型的原型
然后獲得對(duì)象的原型
然后一直循環(huán)判斷對(duì)象的原型是否等于類型的原型,直到對(duì)象原型為 null,因?yàn)樵玩溩罱K為 null
. setInterval 存在的問題
定時(shí)器的代碼執(zhí)行部分不斷的被調(diào)入任務(wù)隊(duì)列中,如果定時(shí)器的執(zhí)行時(shí)間比間隔時(shí)間長(zhǎng),最終可能導(dǎo)致定時(shí)器堆疊在一起執(zhí)行。
js 引擎為了解決這個(gè)問題,采用的方式是若任務(wù)隊(duì)列中存在這個(gè)定期器,則不會(huì)將新的定時(shí)器放入任務(wù)隊(duì)列,這樣做的弊端是可能導(dǎo)致某些間隔被跳過。
解決方法:循環(huán)調(diào)用setTimeout來實(shí)現(xiàn)setInterval:(即用setTimeout來實(shí)現(xiàn)setInterval
setTimeout(function fn(){
...
setTimeout(fn,delay)
},delay)
列舉幾條 JS 的基本代碼規(guī)范
- 變量和函數(shù)命名要見名知意
- 當(dāng)命名對(duì)象、函數(shù)和實(shí)例時(shí)使用駝峰命名規(guī)則
- 請(qǐng)使用 === / !== 來值的比較
- 對(duì)字符串使用單引號(hào) ''(因?yàn)榇蠖鄷r(shí)候我們的字符串。特別html會(huì)出現(xiàn)")
- switch 語句必須帶有 default 分支
- 語句結(jié)束一定要加分號(hào)
- for 循環(huán)必須使用大括號(hào)
- 使用 /*.../ 進(jìn)行多行注釋,包括描述,指定類型以及參數(shù)值和返回值
.什么是作用域鏈(scope chain)
作用域鏈: 由各級(jí)作用域?qū)ο筮B續(xù)引用,形成的鏈?zhǔn)浇Y(jié)構(gòu)
函數(shù)的聲明周期:
- 程序開始執(zhí)行前: 程序會(huì)創(chuàng)建全局作用域?qū)ο體indow
- 定義函數(shù)時(shí)
在window中創(chuàng)建函數(shù)名變量引用函數(shù)對(duì)象
函數(shù)對(duì)象的隱藏屬性scope指回函數(shù)來自的全局作用域?qū)ο體indow - 調(diào)用函數(shù)時(shí)
創(chuàng)建本次函數(shù)調(diào)用時(shí)使用的AO對(duì)象
在AO對(duì)象中添加函數(shù)的局部變量
設(shè)置AO的隱藏屬性parent 指向函數(shù)的祖籍作用域?qū)ο?。——?zhí)行時(shí),如果AO中沒有的變量可延parnet向祖籍作用域?qū)ο笳摇?/li> - 函數(shù)調(diào)用后
函數(shù)作用域?qū)ο驛O釋放
導(dǎo)致AO中局部變量釋放
作用
- 保存所有的變量
- 控制變量的使用順序: 先用局部,局部沒有才延作用域鏈向下查找。