前端面试JS三部分(三)
生活随笔
收集整理的這篇文章主要介紹了
前端面试JS三部分(三)
小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.
前端面試JS三部分(三)
1、Generator了解?
ES6 提供的一種異步編程解決方案, Generator 函數(shù)是一個(gè)狀態(tài)機(jī),封裝了多個(gè)內(nèi)部狀態(tài)。 function* helloWorldGenerator() {yield 'hello';yield 'world';return 'ending'; }var hw = helloWorldGenerator(); 調(diào)用后返回指向內(nèi)部狀態(tài)的指針, 調(diào)用next()才會(huì)移向下一個(gè)狀態(tài), 參數(shù): hw.next() // { value: 'hello', done: false }hw.next() // { value: 'world', done: false }hw.next() // { value: 'ending', done: true }hw.next() // { value: undefined, done: true }2、手寫(xiě)Promise實(shí)現(xiàn)?
var myPromise = new Promise((resolve, reject) => {// 需要執(zhí)行的代碼...if (/* 異步執(zhí)行成功 */) {resolve(value)} else if (/* 異步執(zhí)行失敗 */) {reject(error)} })myPromise.then((value) => {// 成功后調(diào)用, 使用value值 }, (error) => {// 失敗后調(diào)用, 獲取錯(cuò)誤信息error })3. Promise優(yōu)缺點(diǎn)?
優(yōu)點(diǎn): 解決回調(diào)地獄, 對(duì)異步任務(wù)寫(xiě)法更標(biāo)準(zhǔn)化與簡(jiǎn)潔化 缺點(diǎn): 首先,無(wú)法取消Promise,一旦新建它就會(huì)立即執(zhí)行,無(wú)法中途取消; 其次,如果不設(shè)置回調(diào)函數(shù),Promise內(nèi)部拋出的錯(cuò)誤,不會(huì)反應(yīng)到外部; 第三,當(dāng)處于pending狀態(tài)時(shí),無(wú)法得知目前進(jìn)展到哪一個(gè)階段(剛剛開(kāi)始還是即將完成). 極簡(jiǎn)版promise封裝: function promise () {this.msg = '' // 存放value和errorthis.status = 'pending'var that = thisvar process = arguments[0]process (function () {that.status = 'fulfilled'that.msg = arguments[0]}, function () {that.status = 'rejected'that.msg = arguments[0]})return this }promise.prototype.then = function () {if (this.status === 'fulfilled') {arguments[0](this.msg)} else if (this.status === 'rejected' && arguments[1]) {arguments[1](this.msg)} }4.手寫(xiě)實(shí)現(xiàn)bind?
Function.prototype.bind = function () {// 保存原函數(shù)var self = this// 取出第一個(gè)參數(shù)作為上下文, 相當(dāng)于[].shift.call(arguments)var context = Array.prototype.shift.call(arguments)// 取剩余的參數(shù)作為arg; 因?yàn)閍rguments是偽數(shù)組, 所以要轉(zhuǎn)化為數(shù)組才能使用數(shù)組方法var arg = Array.prototype.slice.call(arguments)// 返回一個(gè)新函數(shù)return function () {// 綁定上下文并傳參self.apply(context, Array.prototype.concat.call(arg, Array.prototype.slice.call(arguments)))} }5.手寫(xiě)實(shí)現(xiàn)4種繼承
function Father () {} function Child () {} // 1\. 原型繼承 Child.prototype = new Father() // 2\. 構(gòu)造繼承 function Child (name) {Father.call(this, name) } // 3\. 組合繼承 function Child (name) {Father.call(this, name) } Child.prototype = new Father() // 4\. 寄生繼承 function cloneObj (o) {var clone = object.create(o)clone.sayName = ...return clone } // 5\. 寄生組合繼承 // 6\. ES6 class extend繼承6.封裝JSONP
``
function jsonp ({url, param, callback}) {return new Promise((resolve, reject) => {var script = document.createElement('script')window.callback = function (data) {resolve(data)document.body.removeChild('script')}var param = {...param, callback}var arr = []for (let key in param) {arr.push(`${key}=${param[key]}`)}script.src = `${url}?${arr.join('&')}`document.body.appendChild(script)}) }7.手動(dòng)實(shí)現(xiàn)map(forEach以及filter也類(lèi)似)
// for循環(huán)實(shí)現(xiàn) Array.prototype.myMap = function () {var arr = thisvar [fn, thisValue] = Array.prototype.slice.call(arguments)var result = []for (var i = 0; i < arr.length; i++) {result.push(fn.call(thisValue, arr[i], i, arr))}return result } var arr0 = [1, 2, 3] console.log(arr0.myMap(v => v + 1))// forEach實(shí)現(xiàn)(reduce類(lèi)似) Array.prototype.myMap = function (fn, thisValue) {var result = []this.forEach((v, i, arr) => {result.push(fn.call(thisValue, v, i, arr))})return result } var arr0 = [1, 2, 3] console.log(arr0.myMap(v => v + 1))8. js實(shí)現(xiàn)checkbox全選以及反選
<body><button id="other">反選</button><input type="checkbox" id="all" />全選<input type="checkbox" class="check" />1<input type="checkbox" class="check" />2<input type="checkbox" class="check" />3<script>var checkbox = document.getElementsByClassName('check')var checkAll = document.getElementById('all')var checkOther = document.getElementById('other')checkAll.onclick = function() {var flag = truefor (var i = 0; i < checkbox.length; i++) {if (!checkbox[i].checked) flag = false}if (flag) {for (var i = 0; i < checkbox.length; i++) {checkbox[i].checked = false}} else {for (var i = 0; i < checkbox.length; i++) {checkbox[i].checked = true}}}checkOther.onclick = function() {for (var i = 0; i < checkbox.length; i++) {checkbox[i].checked = !checkbox[i].checked}}</script></body>9.節(jié)流和防抖
函數(shù)節(jié)流是指一定時(shí)間內(nèi)js方法只跑一次。比如人的眨眼睛,就是一定時(shí)間內(nèi)眨一次。這是函數(shù)節(jié)流最形象的解釋。 // 函數(shù)節(jié)流 滾動(dòng)條滾動(dòng) var canRun = true; document.getElementById("throttle").onscroll = function(){if(!canRun){// 判斷是否已空閑,如果在執(zhí)行中,則直接returnreturn;}canRun = false;setTimeout(function(){console.log("函數(shù)節(jié)流");canRun = true;}, 300); }; 函數(shù)防抖是指頻繁觸發(fā)的情況下,只有足夠的空閑時(shí)間,才執(zhí)行代碼一次。比如生活中的坐公交,就是一定時(shí)間內(nèi),如果有人陸續(xù)刷卡上車(chē),司機(jī)就不會(huì)開(kāi)車(chē)。只有別人沒(méi)刷卡了,司機(jī)才開(kāi)車(chē)。 // 函數(shù)防抖 var timer = false; document.getElementById("debounce").onscroll = function(){clearTimeout(timer); // 清除未執(zhí)行的代碼,重置回初始化狀態(tài)timer = setTimeout(function(){console.log("函數(shù)防抖");}, 300); };10.實(shí)現(xiàn)一個(gè)sleep函數(shù)
// 這種實(shí)現(xiàn)方式是利用一個(gè)偽死循環(huán)阻塞主線(xiàn)程。因?yàn)镴S是單線(xiàn)程的。所以通過(guò)這種方式可以實(shí)現(xiàn)真正意義上的sleep()。 function sleep(delay) {var start = (new Date()).getTime();while ((new Date()).getTime() - start < delay) {continue;} }function test() {console.log('111');sleep(2000);console.log('222'); }test()11.js實(shí)現(xiàn)instanceof
// 檢測(cè)l的原型鏈(__proto__)上是否有r.prototype,若有返回true,否則false function myInstanceof (l, r) {var R = r.prototypewhile (l.__proto__) {if (l.__proto__ === R) return true}return false }12.for in 和 for of區(qū)別
for in遍歷數(shù)組會(huì)遍歷到數(shù)組原型上的屬性和方法, 更適合遍歷對(duì)象 forEach不支持break, continue, return等 使用for of可以成功遍歷數(shù)組的值, 而不是索引, 不會(huì)遍歷原型 for in 可以遍歷到myObject的原型方法method,如果不想遍歷原型方法和屬性的話(huà),可以在循環(huán)內(nèi)部判斷一下,hasOwnPropery方法可以判斷某屬性是否是該對(duì)象的實(shí)例屬性13.ajax和axios、fetch的區(qū)別
1. XMLHttpRequest對(duì)象 現(xiàn)代瀏覽器,最開(kāi)始與服務(wù)器交換數(shù)據(jù),都是通過(guò)XMLHttpRequest對(duì)象。它可以使用JSON、XML、HTML和text文本等格式發(fā)送和接收數(shù)據(jù)。它給我們帶來(lái)了很多好處。1.不重新加載頁(yè)面的情況下更新網(wǎng)頁(yè)2.在頁(yè)面已加載后從服務(wù)器請(qǐng)求/接收數(shù)據(jù)3.在后臺(tái)向服務(wù)器發(fā)送數(shù)據(jù)。 但是,它也有一些缺點(diǎn):1.使用起來(lái)也比較繁瑣,需要設(shè)置很多值。2.早期的IE瀏覽器有自己的實(shí)現(xiàn),這樣需要寫(xiě)兼容代碼。 if (window.XMLHttpRequest) { // model browserxhr = new XMLHttpRequest() } else if (window.ActiveXObject) { // IE 6 and olderxhr = new ActiveXObject('Microsoft.XMLHTTP') } xhr.open('POST', url, true) xhr.send(data) xhr.onreadystatechange = function () {try {// TODO 處理響應(yīng)if (xhr.readyState === XMLHttpRequest.DONE) {// XMLHttpRequest.DONE 對(duì)應(yīng)值是 4// Everything is good, the response was received.if (xhr.status === 200) {// Perfect!} else {// There was a problem with the request.// For example, the response may hava a 404 (Not Found)// or 500 (Internal Server Error) response code.}} else {// Not ready yet}} catch (e) {// 通信錯(cuò)誤的事件中(例如服務(wù)器宕機(jī))alert('Caught Exception: ' + e.description)} } 2. jQuery ajax 為了更快捷的操作DOM,并且規(guī)避一些瀏覽器兼容問(wèn)題,產(chǎn)生了jQuery。它里面的AJAX請(qǐng)求也兼容了各瀏覽器,可以有簡(jiǎn)單易用的方法$.get,$.post。簡(jiǎn)單點(diǎn)說(shuō),就是對(duì)XMLHttpRequest對(duì)象的封裝。$.ajax({type: 'POST',url: url, data: data,dataType: dataType,success: function () {},error: function () {} }) 優(yōu)點(diǎn):1.對(duì)原生XHR的封裝,做了兼容處理,簡(jiǎn)化了使用。2.增加了對(duì)JSONP的支持,可以簡(jiǎn)單處理部分跨域。 缺點(diǎn):1.如果有多個(gè)請(qǐng)求,并且有依賴(lài)關(guān)系的話(huà),容易形成回調(diào)地獄。2.本身是針對(duì)MVC的編程,不符合現(xiàn)在前端MVVM的浪潮。3.ajax是jQuery中的一個(gè)方法。如果只是要使用ajax卻要引入整個(gè)jQuery非常的不合理。 3. axios Axios是一個(gè)基于promise的HTTP庫(kù),可以用在瀏覽器和 node.js 中。它本質(zhì)也是對(duì)原生XMLHttpRequest的封裝,只不過(guò)它是Promise的實(shí)現(xiàn)版本,符合最新的ES規(guī)范。axios({method: 'post',url: '/user/12345',data: {firstName: 'liu',lastName: 'weiqin'}}).then(res => console.log(res)).catch(err => console.log(err)) Vue2.0之后,推薦大家使用axios來(lái)請(qǐng)求數(shù)據(jù)。 優(yōu)點(diǎn):從瀏覽器中創(chuàng)建XMLHttpRequests從 node.js 創(chuàng)建 http 請(qǐng)求支持 Promise API攔截請(qǐng)求和響應(yīng)轉(zhuǎn)換請(qǐng)求數(shù)據(jù)和響應(yīng)數(shù)據(jù)取消請(qǐng)求自動(dòng)轉(zhuǎn)換 JSON 數(shù)據(jù)客戶(hù)端支持防御 XSRF 缺點(diǎn):只持現(xiàn)代代瀏覽器. 4. fetch Fetch API提供了一個(gè) JavaScript 接口,用于訪(fǎng)問(wèn)和操作HTTP管道的部分,例如請(qǐng)求和響應(yīng)。它還提供了一個(gè)全局fetch()方法,該方法提供了一種簡(jiǎn)單,合理的方式來(lái)跨網(wǎng)絡(luò)異步獲取資源。 fetch是低層次的API,代替XHR,可以輕松處理各種格式,非文本化格式。可以很容易的被其他技術(shù)使用,例如Service Workers。但是想要很好的使用fetch,需要做一些封裝處理。fetch('http://example.com/movies.json').then(function(response) {return response.json();}).then(function(myJson) {console.log(myJson);}); **優(yōu)勢(shì):跨域的處理**在配置中,添加mode: 'no-cors'就可以跨域了fetch('/users.json', {method: 'post', mode: 'no-cors',data: {} }).then(function() { /* handle response */ }); fetch目前遇到的問(wèn)題:fetch只對(duì)網(wǎng)絡(luò)請(qǐng)求報(bào)錯(cuò),對(duì)400,500都當(dāng)做成功的請(qǐng)求,需要封裝去處理fetch默認(rèn)不會(huì)帶cookie,需要添加配置項(xiàng)。fetch不支持abort,不支持超時(shí)控制,使用setTimeout及Promise.reject的實(shí)現(xiàn)超時(shí)控制并不能阻止請(qǐng)求過(guò)程繼續(xù)在后臺(tái)運(yùn)行,造成了流量的浪費(fèi)。fetch沒(méi)有辦法原生監(jiān)測(cè)請(qǐng)求的進(jìn)度,而XHR可以。14.promise.finally實(shí)現(xiàn)
Promise.prototype.finally = function (callback) {let P = this.constructor;return this.then(value => P.resolve(callback()).then(() => value),reason => P.resolve(callback()).then(() => { throw reason })); };15.檢測(cè)瀏覽器版本版本有哪些方式?
功能檢測(cè)、userAgent特征檢測(cè)比如:navigator.userAgent//"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_2) AppleWebKit/537.36(KHTML, like Gecko) Chrome/41.0.2272.101 Safari/537.36"16.What is a Polyfill?
polyfill 是“在舊版瀏覽器上復(fù)制標(biāo)準(zhǔn) API 的 JavaScript 補(bǔ)充”,可以動(dòng)態(tài)地加載 JavaScript 代碼或庫(kù),在不支持這些標(biāo)準(zhǔn) API 的瀏覽器中模擬它們。例如,geolocation(地理位置)polyfill 可以在 navigator 對(duì)象上添加全局的 geolocation 對(duì)象,還能添加 getCurrentPosition 函數(shù)以及“坐標(biāo)”回調(diào)對(duì)象,所有這些都是 W3C 地理位置 API 定義的對(duì)象和函數(shù)。因?yàn)?polyfill 模擬標(biāo)準(zhǔn) API,所以能夠以一種面向所有瀏覽器未來(lái)的方式針對(duì)這些 API 進(jìn)行開(kāi)發(fā),一旦對(duì)這些 API 的支持變成絕對(duì)大多數(shù),則可以方便地去掉 polyfill,無(wú)需做任何額外工作。17.做的項(xiàng)目中,有沒(méi)有用過(guò)或自己實(shí)現(xiàn)一些 polyfill 方案(兼容性處理方案)?
比如: html5shiv、Geolocation、Placeholder18.我們給一個(gè)dom同時(shí)綁定兩個(gè)點(diǎn)擊事件,一個(gè)用捕獲,一個(gè)用冒泡。會(huì)執(zhí)行幾次事件,會(huì)先執(zhí)行冒泡還是捕獲?
`綁定在被點(diǎn)擊元素的事件是按照代碼順序發(fā)生,其他元素通過(guò)冒泡或者捕獲“感知”的事件,按照W3C的標(biāo)準(zhǔn),先發(fā)生捕獲事件,后發(fā)生冒泡事件。所有事件的順序是:其他元素捕獲階段事件 -> 本元素代碼順序事件 -> 其他元素冒泡階段事件 。` addEventListener參數(shù) element.addEventListener(type, function, useCapture) //【事件類(lèi)型】,【事件處理程序】,【可選。布爾值,指定事件是否在捕獲或冒泡階段執(zhí)行。true:事件句柄在捕獲階段執(zhí)行;false:默認(rèn)。事件句柄在冒泡階段執(zhí)行】 1.冒泡 冒泡是從下向上,DOM元素綁定的事件被觸發(fā)時(shí),此時(shí)該元素為目標(biāo)元素,目標(biāo)元素執(zhí)行后,它的的祖元素綁定的事件會(huì)向上順序執(zhí)行。如下代碼所示,四個(gè)嵌套的div:addEventListener函數(shù)的第三個(gè)參數(shù)設(shè)置為false說(shuō)明不為捕獲事件,即為冒泡事件。<div id='one'><div id='two'><div id='three'><div id='four'></div></div></div> </div><script type='text/javascript'>var one=document.getElementById('one');var two=document.getElementById('two');var three=document.getElementById('three');var four=document.getElementById('four');one.addEventListener('click',function(){alert('one');},false);two.addEventListener('click',function(){alert('two');},false);three.addEventListener('click',function(){alert('three');},false);four.addEventListener('click',function(){alert('four');},false); </script>代碼的執(zhí)行順序是: 點(diǎn)擊one元素,輸出one; 點(diǎn)擊two元素,輸出two one; 點(diǎn)擊three元素,輸出 three two one; 點(diǎn)擊four元素,輸出 four three two one;2.捕獲 捕獲則和冒泡相反,目標(biāo)元素被觸發(fā)后,會(huì)從目標(biāo)元素的最頂層的祖先元素事件往下執(zhí)行到目標(biāo)元素為止。將上面的代碼第三個(gè)參數(shù)均改為true,事件為捕獲階段執(zhí)行。 注意: 在事件處理程序中刪除目標(biāo)元素也能阻止事件冒泡,目標(biāo)元素在文檔中是事件冒泡的前提。則執(zhí)行結(jié)果如下:點(diǎn)擊one,輸出one; 點(diǎn)擊two,輸出one two; 點(diǎn)擊three,輸出one two three; 點(diǎn)擊four,輸出one two three four;很明顯執(zhí)行順序是不同的。3.當(dāng)一個(gè)元素綁定兩個(gè)事件,一個(gè)冒泡,一個(gè)捕獲 首先,無(wú)論是冒泡事件還是捕獲事件,元素都會(huì)先執(zhí)行捕獲階段。從上往下,如有捕獲事件,則執(zhí)行;一直向下到目標(biāo)元素后,從目標(biāo)元素開(kāi)始向上執(zhí)行冒泡元素,即第三個(gè)參數(shù)為true表示捕獲階段調(diào)用事件處理程序,如果是false則是冒泡階段調(diào)用事件處理程序。(在向上執(zhí)行過(guò)程中,已經(jīng)執(zhí)行過(guò)的捕獲事件不再執(zhí)行,只執(zhí)行冒泡事件。)如下代碼:one.addEventListener('click',function(){ alert('one'); },true); two.addEventListener('click',function(){ alert('two'); },false); three.addEventListener('click',function(){ alert('three'); },true); four.addEventListener('click',function(){ alert('four'); },false);此時(shí)點(diǎn)擊four元素,four元素為目標(biāo)元素,one為根元素祖先,從one開(kāi)始向下判斷執(zhí)行。分析: one為捕獲事件,輸出one; two為冒泡事件,忽略; three為捕獲時(shí)間,輸出three; four為目標(biāo)元素,開(kāi)始向上冒泡執(zhí)行,輸出four;(從此處分為兩部分理解較容易。) three為捕獲已執(zhí)行,忽略; two為冒泡事件,輸出two; one為捕獲已執(zhí)行,忽略。最終執(zhí)行結(jié)果為: one three four two例如,three作為目標(biāo)元素,執(zhí)行結(jié)果為:one three two(因?yàn)閠wo是冒泡事件,在向下執(zhí)行時(shí)沒(méi)有執(zhí)行到)。執(zhí)行次數(shù):綁定了幾個(gè)事件便執(zhí)行幾次。如下代碼,two元素綁定了兩個(gè)不同事件,點(diǎn)擊two都會(huì)執(zhí)行這兩個(gè)事件。而執(zhí)行順序有所差異。one.addEventListener('click',function(){ alert('one'); },true); two.addEventListener('click',function(){ alert('two,bubble'); },false); two.addEventListener('click',function(){ alert('two,capture'); },true); three.addEventListener('click',function(){ alert('three,bubble'); },true); four.addEventListener('click',function(){ alert('four'); },true);1、如果two為目標(biāo)元素,目標(biāo)元素的同類(lèi)型事件按順序執(zhí)行,而其他元素根據(jù)W3C的標(biāo)準(zhǔn)執(zhí)行,即先捕獲后冒泡。點(diǎn)擊two執(zhí)行結(jié)果:one(因?yàn)槭莟wo的父元素支持捕獲事件所以先執(zhí)行) two,bubble two,capture(順序執(zhí)行,注意逗號(hào)不是間隔,是輸出內(nèi)容。)2、如果目標(biāo)元素不是two,則two的同類(lèi)型事件按先捕獲后冒泡觸發(fā)執(zhí)行,也就是跟前面討論的執(zhí)行過(guò)程是一樣的,只不過(guò)兩個(gè)事件都綁定在同一個(gè)DOM元素上。點(diǎn)擊three執(zhí)行結(jié)果:one two,capture three,bubble two,bubble總結(jié) 所以,看到這里,你就應(yīng)該明白了:綁定在被點(diǎn)擊元素的事件是按照代碼順序發(fā)生。 其他元素通過(guò)冒泡或者捕獲“感知”的事件。 按照W3C的標(biāo)準(zhǔn),先發(fā)生捕獲事件,后發(fā)生冒泡事件。所有事件的順序是:其他元素捕獲階段事件 -> 本元素代碼順序事件 -> 其他元素冒泡階段事件 。舉個(gè)例子 <!DOCTYPE html> <html lang="en"> <head><meta charset="UTF-8"><title>Document</title><style type="text/css"></style> </head> <body><div class="outer"><div class="inner"><button id="btn">click</button></div></div><script>const inner = document.querySelector('.inner');const outer = document.querySelector('.outer');const body = document.body;function h(stopPropagation){return function(e){console.log(`${this.id||this.className||this.tagName}`);if(stopPropagation){e.stopPropagation();}}}body.addEventListener('click',h()); //冒泡階段執(zhí)行outer.addEventListener('click',h(),true); //捕獲階段執(zhí)行inner.addEventListener('click',h(true)); //冒泡階段執(zhí)行,取消冒泡//解析:W3c執(zhí)行順序:其他元素的捕獲事件,自身元素的順序事件,其他元素的冒泡事件。//此處,//body的click事件為冒泡階段,暫不執(zhí)行;//outer的click事件為捕獲階段執(zhí)行,觸發(fā)。輸出outer//inner的click事件為冒泡階段執(zhí)行,本身觸發(fā),輸出inner。//但是因?yàn)閕nner在這里取消了冒泡,所以body的click冒泡事件也不能執(zhí)行了。</script> </body> </html>結(jié)果是: outer inner改動(dòng),若此處:inner.addEventListener(‘click’,h()); //不取消冒泡 輸出: outer inner body19.使用JS實(shí)現(xiàn)獲取文件擴(kuò)展名?
function getFileExtension(filename) {return filename.slice((filename.lastIndexOf(".") - 1 >>> 0) + 2);}String.lastIndexOf() 方法返回指定值(本例中的'.')在調(diào)用該方法的字符串中最后出現(xiàn)的位置,如果沒(méi)找到則返回 -1。對(duì)于'filename'和'.hiddenfile',lastIndexOf的返回值分別為0和-1無(wú)符號(hào)右移操作符(?>) 將-1轉(zhuǎn)換為4294967295,將-2轉(zhuǎn)換為4294967294,這個(gè)方法可以保證邊緣情況時(shí)文件名不變。String.prototype.slice() 從上面計(jì)算的索引處提取文件的擴(kuò)展名。如果索引比文件名的長(zhǎng)度大,結(jié)果為""。暫時(shí)只有這么多,后期會(huì)經(jīng)常更新~~ 明天更新內(nèi)容是 ES6~~
總結(jié)
以上是生活随笔為你收集整理的前端面试JS三部分(三)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: html:link标签
- 下一篇: 高级Web前端必会面试题知识点,大厂面试