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