前端面試--50個JS的高級知識點


歡迎關注前端早茶,與廣東靚仔攜手共同進階

前端早茶專注前端,一起結伴同行,緊跟業界發展步伐~

知識點

1、undeclared 與 undefined 的區別?

  • undefined:聲明了變量,但是沒有賦值
  • undeclared:沒有聲明變量就直接使用
var a; //undefined b; // b is not defined

2、let & const與 var 的區別?

  • var存在變量提升,可重復聲明同一變量,聲明的變量均可改
  • let沒有變量提升,不可重復聲明同一變量,聲明的變量均可改
  • const沒有變量提升,不可重復聲明同一變量,聲明的基本數據類型不可改,引用類型可改屬性,不可只聲明變量而不賦值

3、暫時性死區問題

var a = 100; if(1){ a = 10; //在當前塊作用域中存在a使用let/const聲明的情況下,給a賦值10時,只會在當前作用域找變量a, // 而這時,還未到聲明時候,所以控制台Error:Cannot access 'a' before initialization let a = 1; }

4、獲取DOM元素有哪些方法

方法 描述 返回類型
document.getElementById(id) 通過id獲取dom 符合條件的dom對象
document.getElementsByTagName(tagName) 通過標簽名獲取dom 符合條件的所有dom對象組成的類數組
document.getElementsByClassName(class) 通過class獲取dom 符合條件的所有dom對象組成的類數組
document.getElementsByName(name) 通過標簽的屬性name獲取dom 符合條件的所有dom對象組成的類數組
document.querySelector(選擇器) 通過選擇器獲取dom 符合條件的第一個dom對象
document.querySelectorAll(選擇器) 通過選擇器獲取dom 符合條件的所有dom對象組成的類數組

5、操作DOM元素有哪些方法

標題 描述
createElement 創建一個標簽節點
createTextNode 創建一個文本節點
cloneNode(deep) 復制一個節點,連同屬性與值都復制,deep為true時,連同后代節點一起復制,不傳或者傳false,則只復制當前節點
createDocumentFragment 創建一個文檔碎片節點
appendChild 追加子元素
insertBefore 將元素插入前面
removeChild 刪除子元素
replaceChild 替換子元素
getAttribute 獲取節點的屬性
createAttribute 創建屬性
setAttribute 設置節點屬性
romoveAttribute 刪除節點屬性
element.attributes 將屬性生成類數組對象

6、DOM的類型有哪幾種?

12種

元素節點              Node.ELEMENT_NODE(1)
屬性節點              Node.ATTRIBUTE_NODE(2)
文本節點              Node.TEXT_NODE(3)
CDATA節點             Node.CDATA_SECTION_NODE(4)
實體引用名稱節點       Node.ENTRY_REFERENCE_NODE(5)
實體名稱節點          Node.ENTITY_NODE(6)
處理指令節點          Node.PROCESSING_INSTRUCTION_NODE(7)
注釋節點              Node.COMMENT_NODE(8)
文檔節點              Node.DOCUMENT_NODE(9)
文檔類型節點          Node.DOCUMENT_TYPE_NODE(10)
文檔片段節點          Node.DOCUMENT_FRAGMENT_NODE(11)
DTD聲明節點            Node.NOTATION_NODE(12)

7、JS的作用域及作用域鏈

什么是作用域呢?

在 Javascript 中,作用域分為 全局作用域 和 函數作用域

  • 全局作用域:代碼在程序任何地方都能訪問,window對象的內置屬性都屬於全局作用域
  • 函數作用域:在固定的代碼片段才能被訪問

image.png

作用域有上下級關系,上下級關系的確定就看函數是在哪個作用域下創建的。如上,fn作用域下創建了bar函數,那么“fn作用域”就是“bar作用域”的上級。

作用域最大的用處就是隔離變量,不同作用域下同名變量不會有沖突。

什么是作用域鏈?

一般情況下,變量取值到 創建 這個變量 的函數的作用域中取值

但是如果在當前作用域中沒有查到值,就會向上級作用域去查,直到查到全局作用域,這么一個查找過程形成的鏈條就叫做作用域鏈

var x = 10; function fn(){ console.log(x); } function show(f){ var x = 20; f(); // 10 } show(fn);

image.png

8、數組的splice 與 slice 的區別?

方法 參數 描述
splice splice(start, num, item1, item2, ...) 從start索引開始,截取num個元素,並插入item1、item2到原數組里,影響原數組
slice slice(start, end) 從start開始,截取到end - 1,如果沒有end,則截取到左后一個元素,不影響原數組

9、substr 和 substring 的區別?

方法 參數 描述
substr substr(start,length) 返回從start位置開始length長度的子串
substring substring(start,end) 返回從start位置開始到end位置的子串(不包含end)

10、includes 比 indexOf好在哪?

includes可以檢測NaN,indexOf不能檢測NaN,includes內部使用了Number.isNaNNaN進行了匹配

11、下面代碼輸出的結果?

for(var i = 0; i < 3; i++){ setTimeout(function(){ console.log(i); },0); };

答案:3,3,3

解決方法

for(let i = 0; i < 3; i++){ setTimeout(function(){ console.log(i); },0); }; // 0 1 2
for (var i = 0; i < 3; i++) { (function(i) { setTimeout(function () { console.log(i); }, 0, i) })(i) }; // 0 1 2

12、什么是Promise?解決了什么問題?

有什么用呢?

  • 1、解決回調地獄問題
  • 2、代碼可讀性提高
  • 3、你可以信任Promise,它的狀態只會改變一次並且不可逆

推薦閱讀

13、什么是async/await?解決了什么問題?

對於async/await,我總結為一句話async/await是generator + Promise的語法糖,它用同步方式執行異步代碼

推薦閱讀

14、常用的正則表達式有哪些?

看我這篇文章有了這25個正則表達式,代碼效率提高80%【閱讀:1.6w 點贊:830】

15、JS延遲加載的方法有哪些?

  • 1、<script async src="script.js"></script>:給script標簽加async屬性,則加載和渲染后續文檔元素的過程將和 script.js 的加載與執行並行進行(異步)
  • 2、<script defer src="script.js"></script>:給script標簽加defer屬性,加載后續文檔元素的過程將和 script.js 的加載並行進行(異步),但是 script.js 的執行要在所有元素解析完成之后,DOMContentLoaded 事件觸發之前完成
  • 3、動態創建script標簽:等到DOMContentLoaded 事件觸發時,生成一個script標簽,渲染到頁面上上
  • 4、setTimeout定時器延遲代碼執行

16、new操作符為什么能創建一個實例對象?

分析一下new的整個過程:

  • 1、創建一個空對象
  • 2、繼承構造函數的原型
  • 3、this指向obj,並調用構造函數
  • 4、返回對象

簡單實現一下new:

function myNew (fn, ...args) { // 第一步:創建一個空對象 const obj = {} // 第二步:繼承構造函數的原型 obj.__proto__ = fn.prototype // 第三步:this指向obj,並調用構造函數 fn.apply(obj, args) // 第四步:返回對象 return obj }

17、什么是文檔碎片?

  • 是什么:一個容器,用於暫時存放創建的dom元素,使用document.createDocumentFragment()創建
  • 有什么用:將需要添加的大量元素 先添加到文檔碎片 中,再將文檔碎片添加到需要插入的位置,大大減少dom操作,提高性能

例子

var oFragmeng = document.createDocumentFragment(); for(var i=0;i<10000;i++) { var op = document.createElement("span"); var oText = document.createTextNode(i); op.appendChild(oText); //先附加在文檔碎片中 oFragmeng.appendChild(op); } //最后一次性添加到document中 document.body.appendChild(oFragmeng); 

18、async/await如何檢測報錯?

推薦這篇async await 更優雅的錯誤處理【閱讀量:1.5w,點贊:210】

19、宏任務與微任務有哪些?

宏任務

# 瀏覽器 Node
I/O
setTimeout
setInterval
setImmediate
requestAnimationFrame

微任務

# 瀏覽器 Node
Promise.prototype.then catch finally
process.nextTick
MutationObserver

20、宏任務與微任務的執行順序?說說EventLoop?

看看我這篇setTimeout+Promise+Async輸出順序?很簡單呀!

21、Object.defineProperty(target, key, options),options可傳什么參數?

  • value:給target[key]設置初始值
  • get:調用target[key]時觸發
  • set:設置target[key]時觸發
  • writable:規定target[key]是否可被重寫,默認false
  • enumerable:規定了key是否會出現在target的枚舉屬性中,默認為false
  • configurable:規定了能否改變options,以及刪除key屬性,默認false,具體詳細請看Object.defineProperty函數的configurable配置

22、什么是防抖?什么是節流?

操作 描述 場景
防抖 頻繁去觸發一個事件,但是只觸發最后一次。以最后一次為准 1、電腦息屏時間,每動一次電腦又重新計算時間
2、input框變化頻繁觸發事件可加防抖
3、頻繁點擊按鈕提交表單可加防抖
節流 頻繁去觸發一個事件,但是只能每隔一段時間觸發一次 1、滾動頻繁請求列表可加節流
2、游戲里長按鼠標,但是動作都是每隔一段時間做一次

23、什么是高階函數?簡單實現一個?

高階函數:英文叫Higher-order function。JavaScript的函數其實都指向某個變量。既然變量可以指向函數,函數的參數能接收變量,那么一個函數就可以接收另一個函數作為參數,這種函數就稱之為高階函數。

// 簡單的高階函數 function add(x, y, f) { return f(x) + f(y); } //用代碼驗證一下: add(-5, 6, Math.abs); // 11

像數組的map、reduce、filter這些都是高階函數

24、什么是函數柯里化?簡單實現一個?

柯里化,英語:Currying(果然是滿滿的英譯中的既視感),是把接受多個參數的函數變換成接受一個單一參數(最初函數的第一個參數)的函數,並且返回接受余下的參數而且返回結果的新函數的技術。

// 普通的add函數 function add(x, y) { return x + y } // Currying后 function curryingAdd(x) { return function (y) { return x + y } } add(1, 2) // 3 curryingAdd(1)(2) // 3

好處

  • 1、參數復用
// 正常正則驗證字符串 reg.test(txt) // 普通情況 function check(reg, txt) { return reg.test(txt) } check(/\d+/g, 'test') //false check(/[a-z]+/g, 'test') //true // Currying后 function curryingCheck(reg) { return function(txt) { return reg.test(txt) } } var hasNumber = curryingCheck(/\d+/g) var hasLetter = curryingCheck(/[a-z]+/g) hasNumber('test1') // true hasNumber('testtest') // false hasLetter('21212') // false
  • 2、延遲執行

其實Function.prototype.bind就是科里化的實現例子

function sayKey(key) { console.log(this[key]) } const person = { name: 'Sunshine_Lin', age: 23 } // call不是科里化 sayKey.call(person, 'name') // 立即輸出 Sunshine_Lin sayKey.call(person, 'age') // 立即輸出 23 // bind是科里化 const say = sayKey.bind(person) // 不執行 // 想執行再執行 say('name') // Sunshine_Lin say('age') // 23

25、什么是compose?簡單實現一個?

簡單的compose函數

const compose = (a , b) => c => a( b( c ) );

例子:統計單詞個數

const space = (str) => str.split(' ') const len = (arr) => arr.length // 普通寫法 console.log(len(space('i am linsanxin'))) // 3 console.log(len(space('i am 23 year old'))) // 6 console.log(len(space('i am a author in juejin'))) // 7 // compose寫法 const compose = (...fn) => value => { return fn.reduce((value, fn) => { return fn(value) }, value) } const computedWord = compose(space, len) console.log(computedWord('i am linsanxin')) // 3 console.log(computedWord('i am 23 year old')) // 6 console.log(computedWord('i am a author in juejin')) // 7

26、箭頭函數與普通函數的區別?

  • 1、箭頭函數不可作為構造函數,不能使用new
  • 2、箭頭函數沒有自己的this
  • 3、箭頭函數沒有arguments對象
  • 4、箭頭函數沒有原型對象

27、Symbol的應用場景?

應用場景1:使用Symbol來作為對象屬性名

平常我們對象的屬性都是字符串

const obj = { name: 'Sunshine_Lin', age: 23 } console.log(obj['name']) // 'Sunshine_Lin' console.log(obj['age']) // 23

其實也可以用Symbol來當做屬性名

const gender = Symbol('gender') const obj = { name: 'Sunshine_Lin', age: 23, [gender]: '男' } console.log(obj['name']) // 'Sunshine_Lin' console.log(obj['age']) // 23 console.log(obj[gender]) // 男

但是Symbol作為屬性的屬性不會被枚舉出來,這也是JSON.stringfy(obj)時,Symbol屬性會被排除在外的原因

console.log(Object.keys(obj)) // [ 'name', 'age' ] for(const key in obj) { console.log(key) // name age } console.log(JSON.stringify(obj)) // {"name":"Sunshine_Lin","age":23}

其實想獲取Symbol屬性也不是沒辦法。

// 方法一 console.log(Object.getOwnPropertySymbols(obj)) // [ Symbol(gender) ] // 方法二 console.log(Reflect.ownKeys(obj)) // [ 'name', 'age', Symbol(gender) ]

應用場景2:使用Symbol來替代常量

有以下場景

// 賦值 const one = 'oneXin' const two = 'twoXin' function fun(key) { switch (key) { case one: return 'one' break; case two: return 'two' break; } }

如果變量少的話還好,但是變量多的時候,賦值命名很煩,可以利用Symbol的唯一性

const one = Symbol() const two = Symbol()

應用場景3:使用Symbol定義類的私有屬性

以下例子,PASSWORD屬性無法在實例里獲取到

class Login { constructor(username, password) { const PASSWORD = Symbol() this.username = username this[PASSWORD] = password } checkPassword(pwd) { return this[PASSWORD] === pwd } } const login = new Login('123456', 'hahah') console.log(login.PASSWORD) // 報錯 console.log(login[PASSWORD]) // 報錯 console.log(login[PASSWORD]) // 報錯 

28、AMD 和 CMD 的區別?

模塊化 代表應用 特點
AMD require.js 1、AMD的api默認一個當多個用
2、依賴前置,異步執行
CMD sea.js 1、CMD的api嚴格區分,推崇職責單一
2、依賴就近,按需加載,同步執行

29、Commonjs 和 ES6 Module的區別

取自阿里巴巴淘系技術前端團隊的回答:

  • 1、Commonjs是拷貝輸出,ES6模塊化是引用輸出
  • 2、Commonjs是運行時加載,ES6模塊化是編譯時輸出接口
  • 3、Commonjs是單個值導出,ES6模塊化可以多個值導出
  • 4、Commonjs是動態語法可寫在函數體中,ES6模塊化靜態語法只能寫在頂層
  • 5、Commonjs的this是當前模塊化,ES6模塊化的this是undefined

推薦文章CommonJS模塊與ES6模塊的區別

30、為什么Commonjs不適用於瀏覽器

var math = require('math'); math.add(2, 3);

第二行math.add(2, 3),在第一行require('math')之后運行,因此必須等math.js加載完成。也就是說,如果加載時間很長,整個應用就會停在那里等。

這對服務器端不是一個問題,因為所有的模塊都存放在本地硬盤,可以同步加載完成,等待時間就是硬盤的讀取時間。但是,對於瀏覽器,這卻是一個大問題,因為模塊都放在服務器端,等待時間取決於網速的快慢,可能要等很長時間,瀏覽器處於"假死"狀態。

因此,瀏覽器端的模塊,不能采用"同步加載"(synchronous),只能采用"異步加載"(asynchronous)。這就是AMD規范誕生的背景。

31、常用的ES6-ES12的語法有哪些?

請看我這篇文章基礎很好?總結了38個ES6-ES12的開發技巧,倒要看看你能拿幾分?【閱讀量:4w,點贊:1.8k】

32、(a == 1 && a == 2 && a == 3) 有可能是 true 嗎?

請看我這篇文章(a == 1 && a == 2 && a == 3) 有可能是 true 嗎?

33、函數的length是多少?

請看我這篇文章95%的人都回答不上來的問題:函數的length是多少?

35、JS中的 MUL 函數

MUL表示數的簡單乘法。在這種技術中,將一個值作為參數傳遞給一個函數,而該函數將返回另一個函數,將第二個值傳遞給該函數,然后重復繼續。例如:xyz可以表示為

const mul = x => y => z => x * y * z console.log(mul(1)(2)(3)) // 6

36、深度遍歷廣度遍歷的區別?

對於算法來說 無非就是時間換空間 空間換時間

  • 1、深度優先不需要記住所有的節點, 所以占用空間小, 而廣度優先需要先記錄所有的節點占用空間大
  • 2、深度優先有回溯的操作(沒有路走了需要回頭)所以相對而言時間會長一點
  • 3、深度優先采用的是堆棧的形式, 即先進后出
  • 4、廣度優先則采用的是隊列的形式, 即先進先出

37、JS中的設計模式有哪些?

推薦這篇文章:JavaScript設計模式【閱讀:4.4w,點贊:1250】

38、forEach如何跳出循環?

forEach是不能通過break或者return來實現跳出循環的,為什么呢?實現過forEach的同學應該都知道,forEach的的回調函數形成了一個作用域,在里面使用return並不會跳出,只會被當做continue

那怎么跳出循環呢?可以利用try catch

  function getItemById(arr, id) { var item = null; try { arr.forEach(function (curItem, i) { if (curItem.id == id) { item = curItem; throw Error(); } }) } catch (e) { } return item; } 

39、JS中如何將頁面重定向到另一個頁面?

40、實現一遍常用的JS原生方法?

推薦我這篇:3小時實現了這30個JS原生方法【閱讀:1.2w,點贊:488】

41、鼠標事件有哪些?

注明:鼠標左中右鍵看event對象上的button屬性,對應1、2、3

事件 說明
click 單機鼠標左鍵觸發,右鍵無效,當用戶焦點在按鈕並按下Enter,也會觸發
dbclick 雙擊鼠標左鍵觸發,右鍵無效
mousedown 單機鼠標任意一個按鍵都觸發
mouseout 鼠標指針位於某個元素上且將要移出元素邊界時觸發
mouseover 鼠標指針移出某個元素到另一個元素上時觸發
mouseup 鼠標指針移出某個元素到另一個元素上時觸發
mouseover 松開任意鼠標按鍵時觸發
mousemove 鼠標在某個元素上時持續發生
mouseenter 鼠標進入某個元素邊界時觸發
mouseleave 鼠標離開某個元素邊界時觸發

42、鍵盤事件有哪些?

注明:event對象上的keyCode屬性,是按下的按鍵的ASCLL值,通過這個值可辨別是按下哪個按鍵。ASCLL表在此ASCII碼一覽表,ASCII碼對照表

事件 說明
onkeydown 某個鍵盤按鍵被按下時觸發
onkeyup 某個鍵盤按鍵被松開時觸發
onkeypress 某個按鍵被按下時觸發,不監聽功能鍵,如ctrl,shift

43、JS中鼠標事件的各個坐標?

屬性 說明 兼容性
offsetX 以當前的目標元素左上角為原點,定位x軸坐標 除Mozilla外都兼容
offsetY 以當前的目標元素左上角為原點,定位y軸坐標 除Mozilla外都兼容
clientX 以瀏覽器可視窗口左上角為原點,定位x軸坐標 都兼容
clientY 以瀏覽器可視窗口左上角為原點,定位y軸坐標 都兼容
pageX 以doument對象左上角為原點,定位x軸坐標 除IE外都兼容
pageY 以doument對象左上角為原點,定位y軸坐標 除IE外都兼容
screenX 以計算機屏幕左上頂角為原點,定位x軸坐標(多屏幕會影響) 全兼容
screenY 以計算機屏幕左上頂角為原點,定位y軸坐標 全兼容
layerX 最近的絕對定位的父元素(如果沒有,則為 document 對象)左上頂角為元素,定位 x 軸坐標 Mozilla 和 Safari
layerY 最近的絕對定位的父元素(如果沒有,則為 document 對象)左上頂角為元素,定位 y 軸坐標 Mozilla 和 Safari

44、JS中元素視圖的各個尺寸?

屬性 說明
offsetLeft 獲取當前元素到定位父節點的left方向的距離
offsetTop 獲取當前元素到定位父節點的top方向的距離
offsetWidth 獲取當前元素 width + 左右padding + 左右border-width
offsetHeight 獲取當前元素 height + 上下padding + 上下border-width
clientWidth 獲取當前元素 width + 左右padding
clientHeight 獲取當前元素 height + 上下padding
scrollWidth 當前元素內容真實的寬度,內容不超出盒子寬度時為盒子的clientWidth
scrollHeight 當前元素內容真實的高度,內容不超出盒子高度時為盒子的clientHeight

45、Window視圖的各個尺寸?

屬性 說明
innerWidth innerWidth 瀏覽器窗口可視區寬度(不包括瀏覽器控制台、菜單欄、工具欄)
innerHeight innerWidth 瀏覽器窗口可視區高度(不包括瀏覽器控制台、菜單欄、工具欄)

46、Document文檔視圖的各個尺寸?

屬性 說明
document.documentElement.clientWidth 瀏覽器窗口可視區寬度(不包括瀏覽器控制台、菜單欄、工具欄、滾動條)
document.documentElement.clientHeight 瀏覽器窗口可視區高度(不包括瀏覽器控制台、菜單欄、工具欄、滾動條)
document.documentElement.offsetHeight 獲取整個文檔的高度(包含body的margin)
document.body.offsetHeight 獲取整個文檔的高度(不包含body的margin)
document.documentElement.scrollTop 返回文檔的滾動top方向的距離(當窗口發生滾動時值改變)
document.documentElement.scrollLeft 返回文檔的滾動left方向的距離(當窗口發生滾動時值改變)

9個高級的JavaScript方法

1. getBoundingClientRect

1.1 是什么

Element.getBoundingClientRect()  方法返回元素的大小及其相對於視口的位置。返回的是一個對象,對象里有這8個屬性:left,right,top,bottom,width,height,x,y

截屏2021-07-25 下午7.42.59.png

1.2 兼容性

基本在每一個瀏覽器都可以使用getBoundingClientRect 截屏2021-07-25 下午7.45.23.png

1.3 判斷元素是否在可視區域

這是getBoundingClientRect最常應用的場景了,判斷一個元素是否完整出現在視口里

// html <div id="box"></div> body { height: 3000px; width: 3000px; } #box { width: 300px; height: 300px; background-color: red; margin-top: 300px; margin-left: 300px; } // js const box = document.getElementById('box') window.onscroll = function () { // box完整出現在視口里才會輸出true,否則為false console.log(checkInView(box)) } function checkInView(dom) { const { top, left, bottom, right } = dom.getBoundingClientRect() console.log(top, left, bottom, right) console.log(window.innerHeight, window.innerWidth) return top >= 0 && left >= 0 && bottom <= (window.innerHeight || document.documentElement.clientHeight) && right <= (window.innerWidth || document.documentElement.clientWidth) }

根據這個用處,咱們可以實現:懶加載和無限滾動

1.4 缺點?

  • 1、每次scroll都得重新計算,性能耗費大
  • 2、引起重繪回流

2. IntersectionObserver

2.1 是什么

IntersectionObserver接口 提供了一種異步觀察目標元素與其祖先元素或頂級文檔視窗(viewport)交叉狀態的方法。祖先元素與視窗(viewport)被稱為根(root)

通俗點說就是:IntersectionObserver是用來監聽某個元素與視口交叉狀態的。交叉狀態是什么呢?請看下圖,一開始整個元素都在視口內,那么元素與視口的交叉狀態就是100%,而我往下滾動,元素只有一半顯示在視口里,那么元素與視口的交叉狀態為50%

截屏2021-07-25 下午9.11.41.png

2.2 用法

// 接收兩個參數 callback option var io = new IntersectionObserver(callback, option); // 開始觀察(可觀察多個元素) io.observe(document.getElementById('example1')); io.observe(document.getElementById('example2')); // 停止觀察某個元素 io.unobserve(element); // 關閉觀察器 io.disconnect();

2.3 callback

callback一般有兩種觸發情況。一種是目標元素剛剛進入視口(可見),另一種是完全離開視口(不可見)。

var io = new IntersectionObserver( entries => { console.log(entries); } );

callback函數的參數(entries)是一個數組,每個成員都是一個IntersectionObserverEntry對象。舉例來說,如果同時有兩個被觀察的對象的可見性發生變化,entries數組就會有兩個成員。

截屏2021-07-25 下午9.31.02.png

2.4 IntersectionObserverEntry對象

{
  time: 3893.92, rootBounds: ClientRect { bottom: 920, height: 1024, left: 0, right: 1024, top: 0, width: 920 }, boundingClientRect: ClientRect { // ... }, intersectionRect: ClientRect { // ... }, intersectionRatio: 0.54, target: element }

屬性解析:

  • time:可見性發生變化的時間,是一個高精度時間戳,單位為毫秒
  • target:被觀察的目標元素,是一個 DOM 節點對象
  • rootBounds:根元素的矩形區域的信息,getBoundingClientRect()方法的返回值,如果沒有根元素(即直接相對於視口滾動),則返回null
  • boundingClientRect:目標元素的矩形區域的信息
  • intersectionRect:目標元素與視口(或根元素)的交叉區域的信息
  • intersectionRatio:目標元素的可見比例,即intersectionRectboundingClientRect的比例,完全可見時為1,完全不可見時小於等於0

2.5 option

講講第二個參數option里比較重要的兩個屬性:threshold和root

首先講講threshold

threshold屬性決定了什么時候觸發回調函數。它是一個數組,每個成員都是一個門檻值,默認為[0],即交叉比例(intersectionRatio)達到0時觸發回調函數。

new IntersectionObserver(
  entries => {/* ... */}, 
  {
    threshold: [0, 0.25, 0.5, 0.75, 1]
  }
);

用戶可以自定義這個數組。比如,[0, 0.25, 0.5, 0.75, 1]就表示當目標元素 0%、25%、50%、75%、100% 可見時,會觸發回調函數。

再說說root

IntersectionObserver API 支持容器內滾動。root屬性指定目標元素所在的容器節點(即根元素)。注意,容器元素必須是目標元素的祖先節點。

new IntersectionObserver(
  entries => {/* ... */}, 
  {
    threshold: [0, 0.25, 0.5, 0.75, 1],
    root: document.getElementById('#container')
  }
);

2.6 完整例子

body {
            height: 3000px; width: 3000px; } #box1 { width: 300px; height: 300px; background-color: red; margin-top: 100px; margin-left: 300px; } #box2 { width: 300px; height: 300px; background-color: red; margin-top: 100px; margin-left: 300px; } <div id="box1"></div> <div id="box2"></div> const io = new IntersectionObserver(entries => { console.log(entries) }, { threshold: [0, 0.25, 0.5, 0.75, 1] // root: xxxxxxxxx }) io.observe(document.getElementById('box1')) io.observe(document.getElementById('box2'))

2.7 使用場景

  • 1、可以像getBoundingClientRect那樣判斷元素是否在視口里,並且好處是,不會引起重繪回流
  • 2、同理,有了第一點功能,就可以做懶加載和無限滾動功能了

2.8 缺點

想兼容IE的就別考慮這個API了。。。 截屏2021-07-25 下午9.44.42.png

3. createNodeIterator

3.1 結識這個API

我是怎么認識這個API的呢?我面試的時候被問到了:說一說,如何遍歷輸出頁面中的所有元素。我第一時間肯定想到使用循環遞歸去輸出。面試官:行吧,回家等消息吧。

后來我回家一查,才知道了createNodeIterator這個API

3.2 解題

那如何使用createNodeIterator對頁面中所有元素進行遍歷輸出呢?

const body = document.getElementsByTagName('body')[0] const it = document.createNodeIterator(body) let root = it.nextNode() while(root) { console.log(root) root = it.nextNode() }

找個網站測試下:

截屏2021-07-25 下午10.07.30.png

3.3 詳細參數

詳細參數可以看這里,講的很詳細

3.4 兼容性

一片綠啊,大膽放心使用吧! 截屏2021-07-25 下午10.08.43.png

4. getComputedStyle

4.1 是什么

Window.getComputedStyle()方法返回一個對象,該對象在應用活動樣式表並解析這些值可能包含的任何基本計算后報告元素的所有CSS屬性的值。 私有的CSS屬性值可以通過對象提供的API或通過簡單地使用CSS屬性名稱進行索引來訪問。

window.getComputedStyle(element, pseudoElement)
  • element: 必需,要獲取樣式的元素。
  • pseudoElement: 可選,偽類元素,當不查詢偽類元素的時候可以忽略或者傳入 null。

截屏2021-07-25 下午10.23.01.png

4.2 使用

搭配getPropertyValue可以獲取到具體樣式

// html #box { width: 300px; height: 300px; background-color: yellow; } <div id="box"></div> const box = document.getElementById('box') const styles = window.getComputedStyle(box) // 搭配getPropertyValue可以獲取到具體樣式 const height = styles.getPropertyValue("height") const width = styles.getPropertyValue("width") console.log(height, width) // ’300px‘ '300px'

4.3 兼容性

一片綠油油。放心使用。 截屏2021-07-25 下午10.33.29.png

5. requestAnimationFrame

這篇文章講的不錯,介紹,用法,兼容性,都說的明明白白:requestAnimationFrame理解與實踐

6. requestIdleCallback

這篇文章講的不錯,介紹,用法,兼容性,都說的明明白白:你應該知道的requestIdleCallback

7. DOMContentLoaded

7.1 是什么

當初始的 HTML 文檔被完全加載和解析完成之后,DOMContentLoaded 事件被觸發,而無需等待樣式表、圖像和子框架的完全加載。

這時問題又來了,“HTML 文檔被加載和解析完成”是什么意思呢?或者說,HTML 文檔被加載和解析完成之前,瀏覽器做了哪些事情呢?那我們需要從瀏覽器渲染原理來談談。

瀏覽器向服務器請求到了 HTML 文檔后便開始解析,產物是 DOM(文檔對象模型),到這里 HTML 文檔就被加載和解析完成了。如果有 CSS 的會根據 CSS 生成 CSSOM(CSS 對象模型),然后再由 DOM 和 CSSOM 合並產生渲染樹。有了渲染樹,知道了所有節點的樣式,下面便根據這些節點以及樣式計算它們在瀏覽器中確切的大小和位置,這就是布局階段。有了以上這些信息,下面就把節點繪制到瀏覽器上。所有的過程如下圖所示:

截屏2021-07-25 下午10.49.44.png

現在你可能了解 HTML 文檔被加載和解析完成前瀏覽器大概做了哪些工作,但還沒完,因為我們還沒有考慮現在前端的主角之一 JavaScript。

JavaScript 可以阻塞 DOM 的生成,也就是說當瀏覽器在解析 HTML 文檔時,如果遇到

<body>
  <script type="text/javascript">
  console.log(document.getElementById('ele')); // null
  </script>

  <div id="ele"></div>

  <script type="text/javascript">
  console.log(document.getElementById('ele')); // <div id="ele"></div>
  </script>
</body>

另外,因為 JavaScript 可以查詢任意對象的樣式,所以意味着在 CSS 解析完成,也就是 CSSOM 生成之后,JavaScript 才可以被執行。

到這里,我們可以總結一下。當文檔中沒有腳本時,瀏覽器解析完文檔便能觸發 DOMContentLoaded 事件;如果文檔中包含腳本,則腳本會阻塞文檔的解析,而腳本需要等 CSSOM 構建完成才能執行。在任何情況下,DOMContentLoaded 的觸發不需要等待圖片等其他資源加載完成。

7.2 異步腳本

我們到這里一直在說同步腳本對網頁渲染的影響,如果我們想讓頁面盡快顯示,那我們可以使用異步腳本。HTML5 中定義了兩個定義異步腳本的方法:defer 和 async。我們來看一看他們的區別。

image.png 同步腳本(標簽中不含 async 或 defer):

當 HTML 文檔被解析時如果遇見(同步)腳本,則停止解析,先去加載腳本,然后執行,執行結束后繼續解析 HTML 文檔。過程如下圖:

image.png

defer 腳本:

當 HTML 文檔被解析時如果遇見 defer 腳本,則在后台加載腳本,文檔解析過程不中斷,而等文檔解析結束之后,defer 腳本執行。另外,defer 腳本的執行順序與定義時的位置有關。過程如下圖:

image.png

async 腳本:

當 HTML 文檔被解析時如果遇見 async 腳本,則在后台加載腳本,文檔解析過程不中斷。腳本加載完成后,文檔停止解析,腳本執行,執行結束后文檔繼續解析。過程如下圖:

image.png

如果你 Google "async 和 defer 的區別",你可能會發現一堆類似上面的文章或圖片,而在這里,我想跟你分享的是 async 和 defer 對 DOMContentLoaded 事件觸發的影響。

defer 與 DOMContentLoaded

如果 script 標簽中包含 defer,那么這一塊腳本將不會影響 HTML 文檔的解析,而是等到 HTML 解析完成后才會執行。而 DOMContentLoaded 只有在 defer 腳本執行結束后才會被觸發。 所以這意味着什么呢?HTML 文檔解析不受影響,等 DOM 構建完成之后 defer 腳本執行,但腳本執行之前需要等待 CSSOM 構建完成。在 DOM、CSSOM 構建完畢,defer 腳本執行完成之后,DOMContentLoaded 事件觸發。

async 與 DOMContentLoaded

如果 script 標簽中包含 async,則 HTML 文檔構建不受影響,解析完畢后,DOMContentLoaded 觸發,而不需要等待 async 腳本執行、樣式表加載等等。

7.3 DOMContentLoaded 與 load

在回頭看第一張圖:

與標記 1 的藍線平行的還有一條紅線,紅線就代表 load 事件觸發的時間,對應的,在最下面的概覽部分,還有一個用紅色標記的 "Load:1.60s",描述了 load 事件觸發的具體時間。

這兩個事件有啥區別呢?點擊這個網頁你就能明白:testdrive-archive.azu...

解釋一下,當 HTML 文檔解析完成就會觸發 DOMContentLoaded,而所有資源加載完成之后,load 事件才會被觸發。

另外需要提一下的是,我們在 jQuery 中經常使用的 (document).ready(function()//...代碼...);其實監聽的就是DOMContentLoaded事件,而(document).ready(function() { // ...代碼... }); 其實監聽的就是 DOMContentLoaded 事件,而 (document).ready(function()//......);DOMContentLoaded而(document).load(function() { // ...代碼... }); 監聽的是 load 事件。

7.4 使用

document.addEventListener("DOMContentLoaded", function(event) { console.log("DOM fully loaded and parsed"); });

7.5 兼容性

綠油油一片,放心使用

截屏2021-07-25 下午11.00.35.png

8. MutationObserver

8.1 是什么

MutationObserver 是一個內建對象,它觀察 DOM 元素,並在檢測到更改時觸發回調。

8.2 用法

// 選擇需要觀察變動的節點
const targetNode = document.getElementById('some-id');

// 觀察器的配置(需要觀察什么變動)
const config = { attributes: true, childList: true, subtree: true };

// 當觀察到變動時執行的回調函數
const callback = function(mutationsList, observer) {
    // Use traditional 'for loops' for IE 11
    for(let mutation of mutationsList) {
        if (mutation.type === 'childList') {
            console.log('A child node has been added or removed.');
        }
        else if (mutation.type === 'attributes') {
            console.log('The ' + mutation.attributeName + ' attribute was modified.');
        }
    }
};

// 創建一個觀察器實例並傳入回調函數
const observer = new MutationObserver(callback);

// 以上述配置開始觀察目標節點
observer.observe(targetNode, config);

// 之后,可停止觀察
observer.disconnect();

8.3 config

config 是一個具有布爾選項的對象,該布爾選項表示“將對哪些更改做出反應”:

  • childList —— node 的直接子節點的更改,
  • subtree —— node 的所有后代的更改,
  • attributes —— node 的特性(attribute),
  • attributeFilter —— 特性名稱數組,只觀察選定的特性。
  • characterData —— 是否觀察 node.data(文本內容)

其他幾個選項:

  • attributeOldValue —— 如果為 true,則將特性的舊值和新值都傳遞給回調(參見下文),否則只傳新值(需要 attributes 選項),
  • characterDataOldValue —— 如果為 true,則將 node.data 的舊值和新值都傳遞給回調(參見下文),否則只傳新值(需要 characterData 選項)。

8.4 兼容性

截屏2021-07-25 下午11.07.52.png

9. Promise.any

9.1 是什么

Promise.any() 接收一個Promise可迭代對象,只要其中的一個 promise 成功,就返回那個已經成功的 promise 。如果可迭代對象中沒有一個 promise 成功(即所有的 promises 都失敗/拒絕),就返回一個失敗的 promise 和AggregateError類型的實例,它是 Error 的一個子類,用於把單一的錯誤集合在一起。本質上,這個方法和Promise.all()是相反的。

9.2 用法(例子)

const promise1 = new Promise((resolve, reject) => {
  setTimeout(reject, 100, 'promise 1 rejected');
});

const promise2 = new Promise((resolve, reject) => {
  setTimeout(resolve, 400, 'promise 2 resolved at 400 ms');
});

const promise3 = new Promise((resolve, reject) => {
  setTimeout(resolve, 700, 'promise 3 resolved at 800 ms');
});

(async () => {
  try {
    let value = await Promise.any([promise1, promise2, promise3]);
    console.log(value);
  } catch (error) {
    console.log(error);
  }
})();

9.3 兼容性

截屏2021-07-25 下午11.18.15.png

 

歡迎關注前端早茶,與廣東靚仔攜手共同進階

前端早茶專注前端,一起結伴同行,緊跟業界發展步伐~

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM