JS 面試題匯總

說一下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。
也可以理解是表示程序級的、正常的或在意料之中的值的空缺

  1. 作為函數(shù)的參數(shù),表示該函數(shù)的參數(shù)不是對象
  2. 作為對象原型鏈的終點
    注意: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
callapply 是臨時的且立即執(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ù)是永遠不會過期的。
sessionStroagelocalStroage 存儲大小可以達到 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 inindex 索引為字符串型數(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中局部變量釋放

作用

  • 保存所有的變量
  • 控制變量的使用順序: 先用局部,局部沒有才延作用域鏈向下查找。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容