大家好,我是若川,加我微信
ruochuan12
進源碼交流群。今天分享一篇7月份新鮮出爐的面經,文章較長,可以收藏再看。學習源碼系列、面試、年度總結、JS基礎系列。
本文來自作者@幾米陽光?投稿?原文鏈接:https://juejin.cn/post/6991724298197008421
最近朋友內推面試了幾家公司(貨拉拉
、蝦皮
、有贊
、樂信
、Qtrade蘋果樹
、富途
、涂鴉
、OPPO
、微保
、微眾
、元戎啟行
),也收獲了滿意的offer。整理了下面試遇到的問題,作為記錄。
JS相關
JS原型及原型鏈
function?Person()?{}
Person.prototype.name?=?'Zaxlct';
Person.prototype.sayName?=?function()?{alert(this.name);
}
var?person1?=?new?Person();
//JS 在創建對象的時候,都有一個__proto__?的內置屬性,用于指向創建它的構造函數的原型對象。
//每個對象都有?__proto__?屬性,但只有函數對象才有?prototype?屬性
//?對象?person1?有一個?__proto__屬性,創建它的構造函數是?Person,構造函數的原型對象是?Person.prototype
console.log(person1.__proto__?==?Person.prototype)?//true
//所有函數對象的__proto__都指向Function.prototype
String.__proto__?===?Function.prototype??//?true
String.constructor?==?Function?//true

JS繼承的幾種方式
詳解
原型繼承
function?Parent?()?{this.name?=?'Parent'this.sex?=?'boy'
}
function?Child?()?{this.name?=?'child'
}
//?將子類的原型對象指向父類的實例
Child.prototype?=?new?Parent()
//優:繼承了父類的模板,又繼承了父類的原型對象
//缺:1.無法實現多繼承(因為已經指定了原型對象了)
//???2.創建子類時,無法向父類構造函數傳參數
構造函數繼承
在子類構造函數內部使用call或apply
來調用父類構造函數,復制父類的實例屬性給子類。
function?Parent?(name)?{this.name?=?name
}
function?Child?()?{//用.call?來改變?Parent?構造函數內的指向Parent.call(this,?'child')
}
//優:解決了原型鏈繼承中子類實例共享父類引用對象的問題,實現多繼承,創建子類實例時,可以向父類傳遞參數
//缺:構造繼承只能繼承父類的實例屬性和方法,不能繼承父類原型的屬性和方法
組合繼承
組合繼承就是將原型鏈繼承與構造函數繼承組合在一起。
使用原型鏈繼承來保證子類能繼承到父類原型中的屬性和方法
使用構造繼承來保證子類能繼承到父類的實例屬性和方法
寄生組合繼承
class繼承
在
class
中繼承主要是依靠兩個東西:extends
super
class?Parent?{constructor?(name)?{this.name?=?name}getName?()?{console.log(this.name)} } class?Child?extends?Parent?{constructor?(name)?{super(name)this.sex?=?'boy'} }
Event Loop 事件循環
同步與異步、宏任務和微任務分別是函數兩個不同維度的描述。
同步任務指的是,在主線程上排隊執行的任務,只有前一個任務執行完畢,才能執行后一個任務;異步任務指的是,不進入主線程、而進入任務隊列(
task queue
)的任務,只有等主線程任務執行完畢,任務隊列開始通知主線程,請求執行任務,該任務才會進入主線程執行。當某個宏任務執行完后,會查看是否有微任務隊列。如果有,先執行微任務隊列中的所有任務;如果沒有,在執行環境棧中會讀取宏任務隊列中排在最前的任務;執行宏任務的過程中,遇到微任務,依次加入微任務隊列。棧空后,再次讀取微任務隊列里的任務,依次類推。
同步(Promise)>異步(微任務(process.nextTick ,Promises.then, Promise.catch ,resove,reject,MutationObserver)>宏任務(setTimeout,setInterval,setImmediate))
await阻塞 后面的代碼執行,因此跳出async函數執行下一個微任務
Promise 與 Async/Await ?區別
async/await是基于Promise實現的,看起來更像同步代碼,
不需要寫匿名函數處理Promise的resolve值
錯誤處理: Async/Await 讓 try/catch 可以同時處理同步和異步錯誤。
條件語句也跟錯誤處理一樣簡潔一點
中間值處理(第一個方法返回值,用作第二個方法參數) 解決嵌套問題
調試方便
const?makeRequest?=?()?=>?{try?{getJSON().then(result?=>?{//?JSON.parse可能會出錯const?data?=?JSON.parse(result)console.log(data)})//?取消注釋,處理異步代碼的錯誤//?.catch((err)?=>?{//???console.log(err)//?})}?catch?(err)?{console.log(err)} }
使用
aync/await
的話,catch能處理JSON.parse
錯誤:const?makeRequest?=?async?()?=>?{try?{//?this?parse?may?failconst?data?=?JSON.parse(await?getJSON())console.log(data)}?catch?(err)?{console.log(err)} }
promise怎么實現鏈式調用跟返回不同的狀態
實現鏈式調用:使用
.then()
或者.catch()
方法之后會返回一個promise
對象,可以繼續用.then()
方法調用,再次調用所獲取的參數是上個then
方法return
的內容promise的三種狀態是
fulfilled
(已成功)/pengding
(進行中)/rejected
(已拒絕)狀態只能由 Pending --> Fulfilled 或者 Pending --> Rejected,且一但發生改變便不可二次修改;
Promise 中使用
resolve
和reject
兩個函數來更改狀態;then 方法內部做的事情就是狀態判斷:
如果狀態是成功,調用成功回調函數
如果狀態是失敗,調用失敗回調函數
函數柯里化
柯里化(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
JS對象深克隆
遞歸遍歷對象,解決循環引用問題
解決循環引用問題,我們需要一個存儲容器存放當前對象和拷貝對象的對應關系(適合用key-value的數據結構進行存儲,也就是map),當進行拷貝當前對象的時候,我們先查找存儲容器是否已經拷貝過當前對象,如果已經拷貝過,那么直接把返回,沒有的話則是繼續拷貝。
function?deepClone(target)?{const?map?=?new?Map()function?clone?(target)?{if?(isObject(target))?{let?cloneTarget?=?isArray(target)???[]?:?{};if?(map.get(target))?{return?map.get(target)}map.set(target,cloneTarget)for?(const?key?in?target)?{cloneTarget[key]?=?clone(target[key]);}return?cloneTarget;}?else?{return?target;}}return?clone(target) };
JS模塊化
nodeJS
里面的模塊是基于commonJS
規范實現的,原理是文件的讀寫,導出文件要使用exports
、module.exports
,引入文件用require
。每個文件就是一個模塊;每個文件里面的代碼會用默認寫在一個閉包函數里面AMD
規范則是非同步加載模塊,允許指定回調函數,AMD
是RequireJS
在推廣過程中對模塊定義的規范化產出。AMD
推崇依賴前置,CMD
推崇依賴就近。對于依賴的模塊AMD是提前執行,CMD是延遲執行。在
ES6
中,我們可以使用import
關鍵字引入模塊,通過exprot
關鍵字導出模塊,但是由于ES6目前無法在瀏覽器中執行,所以,我們只能通過babel
將不被支持的import
編譯為當前受到廣泛支持的require
。CommonJs 和 ES6 模塊化的區別:
CommonJS 模塊輸出的是一個值的拷貝,ES6 模塊輸出的是值的引用。
CommonJS 模塊是運行時加載,ES6 模塊是編譯時輸出接口。
前端模塊化:CommonJS,AMD,CMD,ES6
import 和 require 導入的區別
import 的ES6 標準模塊;require 是 AMD規范引入方式;
import是編譯時調用,所以必須放在文件開頭;是解構過程 require是運行時調用,所以require理論上可以運用在代碼的任何地方;是賦值過程。其實require的結果就是對象、數字、字符串、函數等,再把require的結果賦值給某個變量
異步加載JS方式
匿名函數自調動態創建script標簽加載js
(function(){var?scriptEle?=?document.createElement("script");scriptEle.type?=?"text/javasctipt";scriptEle.async?=?true;scriptEle.src?=?"http://cdn.bootcss.com/jquery/3.0.0-beta1/jquery.min.js";var?x?=?document.getElementsByTagName("head")[0];x.insertBefore(scriptEle,?x.firstChild);??})();
async屬性
//?async屬性規定一旦加載腳本可用,則會異步執行 <script?type="text/javascript"?src="xxx.js"?async="async"></script>
defer屬性
//?defer屬性規定是否對腳本執行進行延遲,直到頁面加載為止 <script?type="text/javascript"?src="xxx.js"?defer="defer"></script>
Set、Map、WeakSet、WeakMap
Set對象可以存儲任何類型的數據。值是唯一的,沒有重復的值。
Map對象保存鍵值對,任意值都可以成為它的鍵或值。
WeakSet 結構與 Set 類似,也是不重復的值的集合 . WeakMap 對象是一組鍵值對的集合
不同:
WeakSet
的成員只能是對象,而不能是其他類型的值。WeakSet 不可遍歷。WeakMap
只接受對象作為鍵名(null
除外),不接受其他類型的值作為鍵名。WeakMap
的鍵名所指向的對象,不計入垃圾回收機制。call、apply
call( this,a,b,c )
在第一個參數之后的,后續所有參數就是傳入該函數的值。apply( this,[a,b,c] )
只有兩個參數,第一個是對象,第二個是數組,這個數組就是該函數的參數。共同之處:都可以用來代替另一個對象調用一個方法,將一個函數的對象上下文從初始的上下文改變為由thisObj指定的新對象。
所謂防抖,就是指觸發事件后在 n 秒內函數只能執行一次所謂節流,就是指連續觸發事件但是在 n 秒中只執行一次函數。
addEventListener的第三個參數干嘛的,為true時捕獲,false時冒泡
Object.prototype.toString.call()
判斷對象類型//?new?Set是實現數組去重, //?Array.from()把去重之后轉換成數組 let?arr2?=?Array.from(new?Set(arr));
詞法作用域與作用域鏈
作用域規定了如何查找變量,也就是確定當前執行代碼對變量的訪問權限。
ES5只有全局作用域沒和函數作用域,ES6增加塊級作用域
暫時性死區:在代碼塊內,使用 let 和 const 命令聲明變量之前,該變量都是不可用的,語法上被稱為暫時性死區。
JavaScript 采用詞法作用域(lexical scoping),也就是靜態作用域。
函數的作用域在函數定義的時候就決定了。
當查找變量的時候,會先從當前上下文的變量對象中查找,如果沒有找到,就會從父級(詞法層面上的父級執行上下文的變量對象中查找,一直找到全局上下文的變量對象,也就是全局對象。這樣由多個執行上下文的變量對象構成的鏈表就叫做作用域鏈。
new關鍵字做了4件事:
function?_new(constructor,?...arg)?{ //?創建一個空對象var?obj?=?{};//?空對象的`__proto__`指向構造函數的`prototype`,?為這個新對象添加屬性?obj.__proto__?=?constructor.prototype;?//?構造函數的作用域賦給新對象var?res?=?constructor.apply(obj,?arg);?//?返回新對象.如果沒有顯式return語句,則返回thisreturn?Object.prototype.toString.call(res)?===?'[object?Object]'???res?:?obj;? }
不應該使用箭頭函數一些情況:
當想要函數被提升時(箭頭函數是匿名的)
要在函數中使用
this/arguments
時,由于箭頭函數本身不具有this/arguments
,因此它們取決于外部上下文使用命名函數(箭頭函數是匿名的)
使用函數作為構造函數時(箭頭函數沒有構造函數)
當想在對象字面是以將函數作為屬性添加并在其中使用對象時,因為咱們無法訪問
this
即對象本身。
判斷數組的四種方法
Array.isArray() 判斷
instanceof 判斷: 檢驗構造函數的prototype屬性是否出現在對象的原型鏈中,返回一個布爾值。
let a = []; a instanceof Array; //true
constructor判斷: 實例的構造函數屬性constructor指向構造函數
let a = [1,3,4]; a.constructor === Array;//true
Object.prototype.toString.call() 判斷
let a = [1,2,3]; Object.prototype.toString.call(a) === '[object Array]';//true
TS有什么優勢
靜態輸入:靜態類型化是一種功能,可以在開發人員編寫腳本時檢測錯誤。
大型的開發項目:使用TypeScript工具來進行重構更變的容易、快捷。
更好的協作:類型安全是在編碼期間檢測錯誤,而不是在編譯項目時檢測錯誤。
更強的生產力:干凈的 ECMAScript 6 代碼,自動完成和動態輸入等因素有助于提高開發人員的工作效率。
interface 和 type的區別
interface 只能定義對象類型。type聲明可以聲明任何類型。
interface 能夠聲明合并,兩個相同接口會合并。Type聲明合并會報錯
type可以類型推導
框架 Vue | React
Vue3.0 新特性
雙向數據綁定 Proxy
代理,可以理解為在對象之前設置一個“攔截”,當該對象被訪問的時候,都必須經過這層攔截。意味著你可以在這層攔截中進行各種操作。比如你可以在這層攔截中對原對象進行處理,返回你想返回的數據結構。
ES6 原生提供 Proxy 構造函數,MDN上的解釋為:Proxy 對象用于定義基本操作的自定義行為(如屬性查找,賦值,枚舉,函數調用等)。
const?p?=?new?Proxy(target,?handler); //target:?所要攔截的目標對象(可以是任何類型的對象,包括原生數組,函數,甚至另一個代理) //handler:一個對象,定義要攔截的行為const?p?=?new?Proxy({},?{get(target,?propKey)?{return?'哈哈,你被我攔截了';} });console.log(p.name);
新增的屬性,并不需要重新添加響應式處理,因為 Proxy 是對對象的操作,只要你訪問對象,就會走到 Proxy 的邏輯中。
Vue3 Composition API
Vue3.x
推出了Composition API
。setup
是組件內使用 Composition API的入口。setup
執行時機是在beforeCreate
之前執行.reactive、ref 與 toRefs、isRef
Vue3.x 可以使用reactive和ref來進行數據定義。
//?props?傳入組件對屬性 //?context?一個上下文對象,包含了一些有用的屬性:attrs,parent,refs setup(props,?context)?{//?ref?定義數據const?year?=?ref(0);//?reactive?處理對象的雙向綁定const?user?=?reactive({?nickname:?"xiaofan",?age:?26,?gender:?"女"?});setInterval(()?=>?{year.value++;user.age++;},?1000);return?{year,//?使用toRefs,結構解構...toRefs(user),}; }, //?提供isRef,用于檢查一個對象是否是ref對象
watchEffect 監聽函數
watchEffect 不需要手動傳入依賴
watchEffect 會先執行一次用來自動收集依賴
watchEffect 無法獲取到變化前的值, 只能獲取變化后的值
computed可傳入get和set
用于定義可更改的計算屬性
const?plusOne?=?computed({get:?()?=>?count.value?+?1,set:?val?=>?{?count.value?=?val?-?1?} });
使用TypeScript和JSX
setup
現在支持返回一個渲染函數,這個函數返回一個JSX
,JSX
可以直接使用聲明在setup
作用域的響應式狀態:export?default?{setup()?{const?count?=?ref(0);return?()?=>?(<div>{count.value}</div>);}, };
Vue 跟React 對比?
相同點:
都有虛擬DOM(Virtual DOM 是一個映射真實DOM的JavaScript對象)
都提供了響應式和組件化的視圖組件。
不同點:Vue 是
MVVM
框架,雙向數據綁定,當ViewModel
對Model
進行更新時,通過數據綁定更新到View
。React是一個單向數據流的庫,狀態驅動視圖。
State --> View --> New State --> New View
ui = render (data)
模板渲染方式不同。React是通過JSX來渲染模板,而Vue是通過擴展的HTML來進行模板的渲染。
組件形式不同,Vue文件里將HTML,JS,CSS組合在一起。react提供class組件和function組
Vue封裝好了一些v-if,v-for,React什么都是自己實現,自由度更高
Vue 初始化過程,雙向數據綁定原理
vue.js 則是采用數據劫持結合發布者-訂閱者模式的方式,通過
Object.defineProperty()
來劫持各個屬性的setter
,getter
,dep.addSub
來收集訂閱的依賴,watcher
監聽數據的變化,在數據變動時發布消息給訂閱者,觸發相應的監聽回調。監聽器
Observer
,用來劫持并監聽所有屬性,如果有變動的,就通知訂閱者。訂閱者Watcher
,可以收到屬性的變化通知并執行相應的函數,從而調用對應update更新視圖。v-model
指令,它能輕松實現表單輸入和應用狀態之間的雙向綁定。computed: 支持緩存,只有依賴數據結果發生改變,才會重新進行計算,不支持異步操作,如果一個屬性依賴其他屬性,多對一,一般用computed
watch: 數據變,直接觸發相應操作,支持異步,監聽數據必須
data
中聲明過或者父組件傳遞過來的props中
的數據,當數據變化時,觸發其他操作,函數有兩個參數vue-router實現原理
端路由簡介以及vue-router實現原理原理核心就是 更新視圖但不重新請求頁面。路徑之間的切換,也就是組件的切換。vue-router實現單頁面路由跳轉模式:hash模式、history模式。根據設置mode參數
hash模式
:通過錨點值的改變,根據不同的值,渲染指定DOM位置的不同數據。每一次改變#
后的部分,都會在瀏覽器的訪問歷史中增加一個記錄,使用”后退”按鈕,就可以回到上一個位置。history模式
:利用window.history.pushState
API 來完成 URL 跳轉而無須重新加載頁面。vuex實現原理:
Vue.use(vuex)
會調用vuex的install方法在
beforeCreate
鉤子前混入vuexInit
方法,vuexInit
方法實現了store
注入vue組件實例
,并注冊了vuex
store
的引用屬性$store
。Vuex
的state
狀態是響應式,是借助vue
的data
是響應式,將state
存入vue實例組件的data中;Vuex
的getters
則是借助vue的計算屬性computed
實現數據實時監聽。nextTick 的原理以及運行機制?
nextTick的源碼分析
vue進行DOM更新內部也是調用nextTick來做異步隊列控制。只要觀察到數據變化,Vue 將開啟一個隊列,并緩沖在同一事件循環中發生的所有數據改變。如果同一個 watcher 被多次觸發,只會被推入到隊列中一次。
DOM至少會在當前事件循環里面的所有數據變化完成之后,再統一更新視圖。而當我們自己調用nextTick的時候,它就在更新DOM的microtask(微任務隊列)后追加了我們自己的回調函數,
從而確保我們的代碼在DOM更新后執行,同時也避免了setTimeout可能存在的多次執行問題。確保隊列中的微任務在一次事件循環前被執行完畢。
Vue 實現一個高階組件
高階組件就是一個函數,且該函數接受一個組件作為參數,并返回一個新的組件。在不改變對象自身的前提下在程序運行期間動態的給對象添加一些額外的屬性或行為。
//?高階組件(HOC)接收到的?props?應該透傳給被包裝組件即直接將原組件prop傳給包裝組件 //?高階組件完全可以添加、刪除、修改?props export?default?function?Console(BaseComponent)?{return?{props:?BaseComponent.props,mounted()?{console.log("高階組件");},render(h)?{console.log(this);//?將?this.$slots?格式化為數組,因為?h?函數第三個參數是子節點,是一個數組const?slots?=?Object.keys(this.$slots).reduce((arr,?key)?=>?arr.concat(this.$slots[key]),?[]).map((vnode)?=>?{vnode.context?=?this._self;?//?綁定到高階組件上,vm:解決具名插槽被作為默認插槽進行渲染return?vnode;});//?透傳props、透傳事件、透傳slotsreturn?h(BaseComponent,{on:?this.$listeners,attrs:?this.$attrs,?//?attrs?指的是那些沒有被聲明為?props?的屬性props:?this.$props,},slots);},}; }
Vue.component()、Vue.use()、this.$xxx()
Vue.component()方法注冊全局組件。
第一個參數是自定義元素名稱,也就是將來在別的組件中使用這個組件的標簽名稱。
第二個參數是將要注冊的Vue組件。
import?Vue?from?'vue'; //?引入loading組件? import?Loading?from?'./loading.vue'; //?將loading注冊為全局組件,在別的組件中通過<loading>標簽使用Loading組件 Vue.component('loading',?Loading);
Vue.use注冊插件,這接收一個參數。這個參數必須具有install方法。Vue.use函數內部會調用參數的install方法。
如果插件沒有被注冊過,那么注冊成功之后會給插件添加一個installed的屬性值為true。Vue.use方法內部會檢測插件的installed屬性,從而避免重復注冊插件。
插件的install方法將接收兩個參數,第一個是參數是Vue,第二個參數是配置項options。
在install方法內部可以添加全局方法或者屬性
import?Vue?from?'vue';//?這個插件必須具有install方法 const?plugin?=?{install?(Vue,?options)?{//?添加全局方法或者屬性Vue.myGlobMethod?=?function?()?{};//?添加全局指令Vue.directive();//?添加混入Vue.mixin();//?添加實例方法Vue.prototype.$xxx?=?function?()?{};//?注冊全局組件Vue.component()} }//?Vue.use內部會調用plugin的install方法 Vue.use(plugin);
將Hello方法掛載到Vue的prototype上.
import?Vue?from?'vue'; import?Hello?from?'./hello.js'; Vue.prototype.$hello?=?Hello;
vue組件中就可以this.$hello('hello world')
Vue父組件傳遞props數據,子組件修改參數
父子組件傳值時,父組件傳遞的參數,數組和對象,子組件接受之后可以直接進行修改,并且父組件相應的值也會修改。控制臺中發出警告。
如果傳遞的值是字符串,直接修改會報錯。單向數據流,每次父級組件發生更新時,子組件中所有的 prop 都將會刷新為最新的值。
如果子組件想修改prop中數據:
定義一個局部變量,使用prop的值賦值
定義一個計算屬性,處理prop的值并返回
Vue父子組件生命周期執行順序
加載渲染過程
父beforeCreate -> 父created -> 父beforeMount-> 子beforeCreate -> 子created -> 子beforeMount -> 子mounted -> 父mounted
子組件更新過程
父beforeUpdate -> 子beforeUpdate -> 子updated -> 父updated
父組件更新過程
父beforeUpdate -> 父updated
銷毀過程
父beforeDestroy -> 子beforeDestroy -> 子destroyed -> 父destroyed
Vue 自定義指令
自定義指令提供了幾個鉤子函數:
bind
:指令第一次綁定到元素時調用inserted
:被綁定元素插入父節點時調用update
:所在組件的 VNode 更新時調用使用slot后可以在子組件內顯示插入的新標簽
webpack 及工程化
webpack的生命周期,及鉤子
compiler(整個生命周期 [k?m?pa?l?r]) 鉤子 https://webpack.docschina.org/api/compiler-hooks/compilation(編譯 [?kɑ?mp??le??n]) 鉤子
compiler
對象包含了Webpack 環境所有的的配置信息。這個對象在啟動 webpack 時被一次性建立,并配置好所有可操作的設置,包括 options,loader 和 plugin。當在 webpack 環境中應用一個插件時,插件將收到此 compiler 對象的引用。可以使用它來訪問 webpack 的主環境。compilation
對象包含了當前的模塊資源、編譯生成資源、變化的文件等。當運行webpack 開發環境中間件時,每當檢測到一個文件變化,就會創建一個新的 compilation,從而生成一組新的編譯資源。compilation 對象也提供了很多關鍵時機的回調,以供插件做自定義處理時選擇使用。compiler
代表了整個webpack
從啟動到關閉的生命周期
,而compilation
只是代表了一次新的編譯過程
webpack 編譯過程
Webpack 的編譯流程是一個串行的過程,從啟動到結束會依次執行以下流程:
初始化參數:從配置文件和 Shell 語句中讀取與合并參數,得出最終的參數;
開始編譯:用上一步得到的參數初始化
Compiler
對象,加載所有配置的插件,執行對象的run
方法開始執行編譯;確定入口:根據配置中的
entry
找出所有的入口文件;編譯模塊:從入口文件出發,調用所有配置的
Loader
對模塊進行翻譯,再找出該模塊依賴的模塊,再遞歸本步驟直到所有入口依賴的文件都經過了本步驟的處理;完成模塊編譯:在經過第4步使用
Loader
翻譯完所有模塊后,得到了每個模塊被翻譯后的最終內容以及它們之間的依賴關系;輸出資源:根據入口和模塊之間的依賴關系,組裝成一個個包含多個模塊的
Chunk
,再把每個Chunk
轉換成一個單獨的文件加入到輸出列表,這步是可以修改輸出內容的最后機會;輸出完成:在確定好輸出內容后,根據配置確定輸出的路徑和文件名,把文件內容寫入到文件系統。
優化項目的webpack打包編譯過程
1.構建打點:構建過程中,每一個
Loader
和Plugin
的執行時長,在編譯 JS、CSS 的 Loader 以及對這兩類代碼執行壓縮操作的 Plugin上消耗時長 。一款工具:speed-measure-webpack-plugin2.緩存:大部分 Loader 都提供了
cache
配置項。cache-loader
,將 loader 的編譯結果寫入硬盤緩存3.多核編譯,
happypack
項目接入多核編譯,理解為happypack
將編譯工作灌滿所有線程4.抽離,
webpack-dll-plugin
將這些靜態依賴從每一次的構建邏輯中抽離出去,靜態依賴單獨打包,Externals
將不需要打包的靜態資源從構建邏輯中剔除出去,使用CDN 引用
5.
tree-shaking
,雖然依賴了某個模塊,但其實只使用其中的某些功能。通過tree-shaking
,將沒有使用的模塊剔除,來達到刪除無用代碼的目的。首屏加載優化
路由懶加載:改為用
import
引用,以函數的形式動態引入,可以把各自的路由文件分別打包,只有在解析給定的路由時,才會下載路由組件;element-ui
按需加載:引用實際上用到的組件 ;組件重復打包:
CommonsChunkPlugin
配置來拆包,把使用2次及以上的包抽離出來,放進公共依賴文件,首頁也有復用的組件,也會下載這個公共依賴文件;gzip: 拆完包之后,再用
gzip
做一下壓縮,關閉sourcemap。UglifyJsPlugin: ?生產環境,壓縮混淆代碼,移除console代碼
CDN部署靜態資源:靜態請求打在nginx時,將獲取靜態資源的地址進行重定向CDN內容分發網絡
移動端首屏加載可以使用骨架屏,自定義loading,首頁單獨做服務端渲染。
如何進行前端性能優化(21種優化+7種定位方式)
webpack 熱更新機制
熱更新流程總結:
啟動本地
server
,讓瀏覽器可以請求本地的靜態資源頁面首次打開后,服務端與客戶端通過 websocket建立通信渠道,把下一次的 hash 返回前端
客戶端獲取到hash,這個hash將作為下一次請求服務端 hot-update.js 和 hot-update.json的hash
修改頁面代碼后,Webpack 監聽到文件修改后,開始編譯,編譯完成后,發送 build 消息給客戶端
客戶端獲取到hash,成功后客戶端構造hot-update.js script鏈接,然后插入主文檔
hot-update.js 插入成功后,執行hotAPI 的 createRecord 和 reload方法,獲取到 Vue 組件的 render方法,重新 render 組件, 繼而實現 UI 無刷新更新。
webpack的 loader和plugin介紹,css-loader,style-loader的區別
loader 它就是一個轉換器,將A文件進行編譯形成B文件,
plugin ,它就是一個擴展器,來操作的是文件,針對是loader結束后,webpack打包的整個過程,它并不直接操作文件,會監聽webpack打包過程中的某些節點(run, build-module, program)
Babel 能把ES6/ES7的代碼轉化成指定瀏覽器能支持的代碼。
css-loader
的作用是把 css文件進行轉碼style-loader
: 使用<style>
將css-loader
內部樣式注入到我們的HTML頁面先使用
css-loader
轉碼,然后再使用style-loader
插入到文件如何編寫一個webpack的plugin?
https://segmentfault.com/a/1190000037513682
webpack 插件的組成:
一個 JS 命名函數或一個類(可以想下我們平時使用插件就是
new XXXPlugin()
的方式)在插件類/函數的 (prototype) 上定義一個 apply 方法。
通過 apply 函數中傳入 compiler 并插入指定的事件鉤子,在鉤子回調中取到 compilation 對象
通過 compilation 處理 webpack 內部特定的實例數據
如果是插件是異步的,在插件的邏輯編寫完后調用 webpack 提供的 callback
為什么 Vite 啟動這么快
Webpack 會
先打包
,然后啟動開發服務器,請求服務器時直接給予打包結果。而 Vite 是
直接啟動
開發服務器,請求哪個模塊再對該模塊進行實時編譯
。Vite 將開發環境下的模塊文件,就作為瀏覽器要執行的文件,而不是像 Webpack 那樣進行
打包合并
。由于 Vite 在啟動的時候
不需要打包
,也就意味著不需要分析模塊的依賴
、不需要編譯
。因此啟動速度非常快。當瀏覽器請求某個模塊時,再根據需要對模塊內容進行編譯。你的腳手架是怎么做的
使用
download-git-repo
下載倉庫代碼democommander
:完整的node.js
命令行解決方案。聲明program
,使用.option()
方法來定義選項Inquirer.js
:命令行用戶界面的集合。前端監控
前端監控通常包括行為監控(PV/UV,埋點接口統計)、異常監控、性能監控。
一個監控系統,大致可以分為四個階段:日志采集、日志存儲、統計與分析、報告和警告。
錯誤監控
Vue專門的錯誤警告的方法
Vue.config.errorHandler
,(Vue提供只能捕獲其頁面生命周期內的函數,比如created,mounted)Vue.config.errorHandler?=?function?(err)?{ console.error(‘Vue.error’,err.stack) //?邏輯處理 };
框架:betterjs,fundebug(收費) 捕獲錯誤的腳本要放置在最前面,確保可以收集到錯誤信息 方法:
window.onerror()
當有js運行時錯誤觸發時,onerror可以接受多個參數(message, source, lineno, colno, error)。window.addEventListener('error'), function(e) {}, true
會比window.onerror先觸發,不能阻止默認事件處理函數的執行,但可以全局捕獲資源加載異常的錯誤
前端JS錯誤捕獲--sourceMap
如何監控網頁崩潰?**崩潰和卡頓有何差別?**監控錯誤
Service Worker 有自己獨立的工作線程,與網頁區分開,網頁崩潰了,Service Worker 一般情況下不會崩潰;
Service Worker 生命周期一般要比網頁還要長,可以用來監控網頁的狀態;
卡頓:加載中,渲染遇到阻塞
性能監控 && 性能優化
性能指標:
FP
(首次繪制)FCP
(首次內容繪制 First contentful paint)LCP
(最大內容繪制時間 Largest contentful paint)FPS
(每秒傳輸幀數)TTI
(頁面可交互時間 Time to Interactive)HTTP
請求響應時間DNS
解析時間TCP
連接時間
性能數據采集需要使用
window.performance API
, ? JS庫web-vitals
:import {getLCP} from 'web-vitals'
;????//?重定向耗時redirect:?timing.redirectEnd?-?timing.redirectStart,//?DOM?渲染耗時dom:?timing.domComplete?-?timing.domLoading,//?頁面加載耗時load:?timing.loadEventEnd?-?timing.navigationStart,//?頁面卸載耗時unload:?timing.unloadEventEnd?-?timing.unloadEventStart,//?請求耗時request:?timing.responseEnd?-?timing.requestStart,//?獲取性能信息時當前時間time:?new?Date().getTime(),//?DNS查詢耗時domainLookupEnd?-?domainLookupStart//?TCP鏈接耗時connectEnd?-?connectStart//?request請求耗時responseEnd?-?responseStart//?解析dom樹耗時domComplete?-?domInteractive//?白屏時間domloadng?-?fetchStart//?onload時間loadEventEnd?-?fetchStart
性能優化常用手段:緩存技術、 ? 預加載技術、 ? 渲染方案。
緩存 :主要有 cdn、瀏覽器緩存、本地緩存以及應用離線包
預加載 :資源預拉取(prefetch)則是另一種性能優化的技術。通過預拉取可以告訴瀏覽器用戶在未來可能用到哪些資源。
prefetch支持預拉取圖片、腳本或者任何可以被瀏覽器緩存的資源。
在head里 添加
<linkrel="prefetch"href="image.png">
prerender是一個重量級的選項,它可以讓瀏覽器提前加載指定頁面的所有資源。
subresource可以用來指定資源是最高優先級的。當前頁面需要,或者馬上就會用到時。
渲染方案:
靜態渲染(SR)
前端渲染(CSR)
服務端渲染(SSR)
客戶端渲染(NSR):NSR 數據請求,首屏數據請求和數據線上與 webview 的一個初始化和框架 JS 初始化并行了起來,大大縮短了首屏時間。
640.png 常見的六種設計模式以及應用場景
https://www.cnblogs.com/whu-2017/p/9471670.html
觀察者模式的概念
觀察者模式模式,屬于行為型模式的一種,它定義了一種一對多的依賴關系,讓多個觀察者對象同時監聽某一個主題對象。這個主體對象在狀態變化時,會通知所有的觀察者對象。
發布訂閱者模式的概念
發布-訂閱模式,消息的發送方,叫做發布者(publishers),消息不會直接發送給特定的接收者,叫做訂閱者。意思就是發布者和訂閱者不知道對方的存在。需要一個第三方組件,叫做信息中介,它將訂閱者和發布者串聯起來,它過濾和分配所有輸入的消息。換句話說,發布-訂閱模式用來處理不同系統組件的信息交流,即使這些組件不知道對方的存在。
需要一個第三方組件,叫做信息中介,它將訂閱者和發布者串聯起來
工廠模式 ?主要是為創建對象提供了接口。場景:在編碼時不能預見需要創建哪種類的實例。
代理模式 命令模式
單例模式
保證一個類僅有一個實例,并提供一個訪問它的全局訪問點。(window)
Http 及瀏覽器相關
七層網絡模型
應用層、表示層、會話層、傳輸層、網絡層、數據鏈路層、物理層
TCP:面向連接、傳輸可靠(保證數據正確性,保證數據順序)、用于傳輸大量數據(流模式)、速度慢,建立連接需要開銷較多(時間,系統資源) 。(應用場景:HTP,HTTP,郵件)
UDP:面向非連接、傳輸不可靠、用于傳輸少量數據(數據包模式)、速度快 ,可能丟包(應用場景:即時通訊)
是否連接?????面向連接?????面向非連接 傳輸可靠性???可靠????????不可靠 應用場合????少量數據????傳輸大量數據
https
客戶端先向服務器端索要公鑰,然后用公鑰加密信息,服務器收到密文后,用自己的私鑰解密。服務器公鑰放在數字證書中。
url到加載渲染全過程
DNS域名解析。
TCP三次握手,建立接連。
發送HTTP請求報文。
服務器處理請求返回響應報文。
瀏覽器解析渲染頁面。
四次揮手,斷開連接。
DNS 協議提供通過
域名查找 IP地址
,或逆向從IP地址反查域名
的服務。DNS 是一個網絡服務器,我們的域名解析簡單來說就是在 DNS 上記錄一條信息記錄。TCP 三次握手,四次揮手:握手揮手都是客戶端發起,客戶端結束。三次握手與四次揮手詳解
負載均衡:請求在進入到真正的應用服務器前,可能還會先經過負責負載均衡的機器,它的作用是將請求
合理地分配到多個服務器上
,轉發HTTP請求;同時具備具備防攻擊等功能。可分為DNS負載均衡,HTTP負載均衡,IP負載均衡,鏈路層負載均衡等。Web Server:請求經過前面的負載均衡后,將進入到對應服務器上的 Web Server,比如
Apache
、Tomcat
反向代理是工作在 HTTP 上的,一般都是
Nginx
。全國各地訪問baidu.com就肯定要通過代理訪問,不可能都訪問百度的那臺服務器。?(VPN正向代理,代理客戶端)瀏覽器解析渲染過程:返回的html傳遞到瀏覽器后,如果有gzip會先解壓,找出文件編碼格式,外鏈資源的加載 html從上往下解析,遇到js,css停止解析渲染,直到js執行完成。解析HTML,構建DOM樹 解析CSS,生成CSS規則樹 合并DOM樹和CSS規則,生成render樹去渲染
不會引起DOM樹變化,頁面布局變化,改變元素樣式的行為叫重繪
引起DOM樹結構變化,頁面布局變化的行為叫回流
GUI渲染線程
負責渲染瀏覽器界面HTML元素,當界面需要重繪(Repaint)
或由于某種操作引發回流(reflow)
時,該線程就會執行。在Javascript引擎運行腳本期間,GUI渲染線程都是處于掛起狀態的,也就是說被”凍結”了. 直到JS程序執行完成,才會接著執行。因此如果JS執行的時間過長,這樣就會造成頁面的渲染不連貫,導致頁面渲染加載阻塞的感覺。JavaScript是可操縱DOM的,如果在修改這些元素屬性同時渲染界面,渲染前后元素數據可能不一致GPU繪制多進程的瀏覽器:主控進程,插件進程,GPU,tab頁(瀏覽器內核)多線程的瀏覽器內核:每一個tab頁面可以看作是瀏覽器內核進程,然后這個進程是多線程的。
它有幾大類子線程:
GUI線程
JS引擎線程
事件觸發線程
定時器線程
HTTP請求線程
http1 跟HTTP2
http2
多路復用:相同域名多個請求,共享同一個TCP連接,降低了延遲
請求優先級:給每個request設置優先級
二進制傳輸;之前是用純文本傳輸
數據流:數據包不是按順序發送,對數據包做標記。每個請求或回應的所有數據包成為一個數據流,
服務端推送:可以主動向客戶端發送消息。
頭部壓縮:減少包的大小跟數量
HTTP/1.1 中的管道( pipeline)傳輸中如果有一個
請求阻塞
了,那么隊列后請求也統統被阻塞住了 HTTP/2 多請求復用一個TCP連接,一旦發生丟包,就會阻塞住所有的 HTTP 請求。HTTP/3 把 HTTP 下層的 TCP 協議改成了 UDP!http1 keep alive 串行傳輸http 中的 keep-alive 有什么作用
響應頭中設置?
keep-alive
?可以在一個 TCP 連接上發送多個 http 請求瀏覽器緩存策略
強緩存:cache-control;no-cache max-age=<10000000>;expires;其中Cache-Conctrol的優先級比Expires高;
控制強制緩存的字段分別是Expires和Cache-Control,如果客戶端的時間小于Expires的值時,直接使用緩存結果。
協商緩存:Last-Modified / If-Modified-Since和Etag / If-None-Match,其中Etag / If-None-Match的優先級比Last-Modified / 首次請求,服務器會在返回的響應頭中加上Last-Modified字段,表示資源最后修改的時間。
瀏覽器再次請求時,請求頭中會帶上If-Modified-Since字段,比較兩個字段,一樣則證明資源未修改,返回304,否則重新返回資源,狀態碼為200;
垃圾回收機制:
標記清除
:進入執行環境的變量都被標記,然后執行完,清除這些標記跟變量。查看變量是否被引用。引用計數
:會記錄每個值被引用的次數,當引用次數變成0后,就會被釋放掉。前端安全
同源策略:如果兩個 URL 的協議、域名和端口都相同,我們就稱這兩個 URL 同源。因為瀏覽器有
cookies
。XSS:跨站腳本攻擊(Cross Site Scripting) input, textarea等所有可能輸入文本信息的區域,輸入
<script src="http://惡意網站"></script>
等,提交后信息會存在服務器中 。CSRF:跨站請求偽造 。引誘用戶打開黑客的網站,在黑客的網站中,利用用戶的登錄狀態發起的跨站請求。
A
站點img
的src=B
站點的請求接口,可以訪問;解決:referer
攜帶請求來源訪問該頁面后,表單自動提交, 模擬完成了一次POST操作,發送post請求
解決:后端注入一個
隨機串
到Cookie
,前端請求取出隨機串添加傳給后端。http 劫持:電信運營商劫持
SQL注入
點擊劫持:誘使用戶點擊看似無害的按鈕(實則點擊了透明
iframe
中的按鈕) ,解決后端請求頭加一個字段X-Frame-Options
文件上傳漏洞 :服務器未校驗上傳的文件
CSS 及 HTML
什么是BFC(塊級格式化上下文)、IFC(內聯格式化上下文 )、FFC(彈性盒模型)
BFC(Block formatting context)
,即塊級格式化上下文
,它作為HTML頁面上的一個獨立渲染區域
,只有區域內元素參與渲染,且不會影響其外部元素。簡單來說,可以將 BFC 看做是一個“圍城”,外面的元素進不來,里面的元素出不去(互不干擾)。一個決定如何渲染元素的容器 ,渲染規則 :
1、內部的塊級元素會在垂直方向,一個接一個地放置。
2、塊級元素垂直方向的距離由margin決定。屬于同一個BFC的兩個相鄰塊級元素的margin會發生重疊。
3、對于從左往右的格式化,每個元素(塊級元素與行內元素)的左邊緣,與包含塊的左邊緣相接觸,(對于從右往左的格式化則相反)。即使包含塊中的元素存在浮動也是如此,除非其中元素再生成一個BFC。
4、BFC的區域不會與浮動元素重疊。
5、BFC是一個隔離的獨立容器,容器里面的子元素和外面的元素互不影響。
6、計算BFC容器的高度時,浮動元素也參與計算。
形成BFC的條件:
1、浮動元素,float 除 none 以外的值;
2、定位元素,position(absolute,fixed);
3、display 為以下其中之一的值 inline-block,table-cell,table-caption;
4、overflow 除了 visible 以外的值(hidden,auto,scroll);
BFC 一般用來解決以下幾個問題
邊距重疊問題
消除浮動問題
自適應布局問題
flex: 0 1 auto;
?是什么意思?元素會根據自身寬高設置尺寸。它會縮短自身以適應
flex
容器,但不會伸長并吸收flex
容器中的額外自由空間來適應flex
容器?。水平的主軸(main axis
)和垂直的交叉軸(cross axis
)幾個屬性決定按哪個軸的排列方向flex-grow
:0
?一個無單位數(): 它會被當作<flex-grow>的值。
flex-shrink
:1
?一個有效的**寬度(width)**值: 它會被當作<flex-basis>的值。
flex-basis
:auto
?關鍵字none
,auto
或initial
.
放大比例、縮小比例、分配多余空間之前占據的主軸空間。
避免CSS全局污染
scoped 屬性
css in js
const?styles?=?{bar:?{backgroundColor:?'#000'} } const?example?=?(props)=>{<div?style={styles.bar}?/> }
CSS Modules
使用less,盡量少使用全局對選擇器
//?選擇器上>要記得寫,免得污染所有ul下面的li ul{>li{color:red;} }
CSS Modules
阮一峰 CSS Modules
CSS Modules是一種構建步驟中的一個進程。通過構建工具來使
指定class
達到scope
的過程。CSS Modules
允許使用::global(.className)
的語法,聲明一個全局規則。凡是這樣聲明的class
,都不會被編譯成哈希字符串
。:local(className)
: 做 localIdentName 規則處理,編譯唯一哈希類名。CSS Modules使用特點:
不使用選擇器,只使用 class 名來定義樣式
不層疊多個 class,只使用一個 class 把所有樣式定義好
不嵌套class
盒子模型和?
box-sizing
?屬性width: 160px; padding: 20px; border: 8px solid orange;
標準 box-sizing:content-box;
元素的總寬度 = 160 + 202 + 82; IE的border-box
:總寬度160
margin/padding
取百分比的值時 ,基于父元素的寬度和高度的。css繪制三角形
通過border 處理
//?border?處理 .class?{width:?0;height:?0;border-left:?50px?solid?transparent;border-right:?50px?solid?transparent;border-bottom:?100px?solid?red; } //?寬高+border div?{width:?50px;height:?50px;border:?2px?solid?orange; }
clip-path裁剪獲得
div{clip-path:?polygon(0?100%,?50%?0,?100%?100%); }
漸變linear-gradient 實現
div?{width:?200px;height:?200px;background:linear-gradient(to?bottom?right,?#fff?0%,?#fff?49.9%,?rgba(148,88,255,1)?50%,rgba(185,88,255,1)?100%); }
CSS實現了三角形后如何給三角形添加陰影
???
CSS兩列布局的N種實現
兩列布局分為兩種,一種是左側定寬、右側自適應,另一種是兩列都自適應(即左側寬度由子元素決定,右側補齊剩余空間)。
左側定寬、右側自適應如何實現
//?兩個元素都設置dislpay:inline-block .left?{display:?inline-block;width:?100px;height:?200px;background-color:?red;vertical-align:?top; } .right?{display:?inline-block;width:?calc(100%?-?100px);height:?400px;background-color:?blue;vertical-align:?top; } //?兩個元素設置浮動,右側自適應元素寬度使用calc函數計算 .left{float:?left;width:?100px;height:?200px;background-color:?red; } .right{float:?left;width:?calc(100%?-?100px);height:?400px;background-color:?blue; } //?父元素設置display:flex,自適應元素設置flex:1 .box{height:?600px;width:?100%;display:?flex; } .left{width:?100px;height:?200px;background-color:?red; } .right{flex:?1;height:?400px;background-color:?blue; } //?父元素相對定位,左側元素絕對定位,右側自適應元素設置margin-left的值大于定寬元素的寬度 .left{position:?absolute;width:?100px;height:?200px;background-color:?red; } .right{margin-left:?100px;height:?400px;background-color:?blue; }
左右兩側元素都自適應
//?flex布局?同上 //?父元素設置display:grid;?grid-template-columns:auto?1fr;(這個屬性定義列寬,auto關鍵字表示由瀏覽器自己決定長度。fr是一個相對尺寸單位,表示剩余空間做等分)grid-gap:20px(行間距) .parent{display:grid;grid-template-columns:auto?1fr;grid-gap:20px }? .left{background-color:?red;height:?200px; } .right{height:300px;background-color:?blue; } //?浮動+BFC???父元素設置overflow:hidden,左側定寬元素浮動,右側自適應元素設置overflow:auto創建BFC .box{height:?600px;width:?100%;overflow:?hidden; } .left{float:?left;width:?100px;height:?200px;background-color:?red; } .right{overflow:?auto;height:?400px;background-color:?blue; }
CSS三列布局
float布局:左邊左浮動,右邊右浮動,中間margin:0 100px;
Position布局: 左邊left:0; 右邊right:0; 中間left: 100px; right: 100px;
table布局: 父元素 display: table; 左右 width: 100px; 三個元素display: table-cell;
彈性(flex)布局:父元素 display: flex; 左右 width: 100px;
網格(gird)布局:
//?gird提供了?gird-template-columns、grid-template-rows屬性讓我們設置行和列的高、寬 .div{width:?100%;display:?grid;grid-template-rows:?100px;grid-template-columns:?300px?auto?300px; }
常見場景及問題
app與H5 如何通訊交互的?
//?兼容IOS和安卓 callMobile(parameters,messageHandlerName)?{//handlerInterface由iOS addScriptMessageHandler與andorid addJavascriptInterface 代碼注入而來。if?(/(iPhone|iPad|iPod|iOS)/i.test(navigator.userAgent))?{//?alert('ios')window.webkit.messageHandlers[messageHandlerName].postMessage(JSON.stringify(parameters))}?else?{//?alert('安卓')//安卓傳輸不了js?json對象,只能傳輸stringwindow.webkit[messageHandlerName](JSON.stringify(parameters))} }
由app將原生方法注入到window上供js調用
messageHandlerName
約定的通信方法parameters
需要傳入的參數移動端適配方案
rem
是相對于HTML的根元素em
相對于父級元素的字體大小。VW,VH
屏幕寬度高度的高分比//按照寬度375圖算,?1rem?=?100px; (function?(win,?doc)?{function?changeSize()?{doc.documentElement.style.fontSize?=?doc.documentElement.clientWidth?/?3.75?+?'px';console.log(100?*?doc.documentElement.clientWidht?/?3.75)}changeSize();win.addEventListener('resize',?changeSize,?false);})(window,?document);
代碼編程相關
實現發布訂閱
/*?Pubsub?*/ function?Pubsub(){//存放事件和對應的處理方法this.handles?=?{}; }Pubsub.prototype?=?{//傳入事件類型type和事件處理handleon:?function?(type,?handle)?{if(!this.handles[type]){this.handles[type]?=?[];}this.handles[type].push(handle);},emit:?function?()?{//通過傳入參數獲取事件類型//將arguments轉為真數組var?type?=?Array.prototype.shift.call(arguments);if(!this.handles[type]){return?false;}for?(var?i?=?0;?i?<?this.handles[type].length;?i++)?{var?handle?=?this.handles[type][i];//執行事件handle.apply(this,?arguments);}},off:?function?(type,?handle)?{handles?=?this.handles[type];if(handles){if(!handle){handles.length?=?0;//清空數組}else{for?(var?i?=?0;?i?<?handles.length;?i++)?{var?_handle?=?handles[i];if(_handle?===?handle){//從數組中刪除handles.splice(i,1);}}}}?? }
promise怎么實現鏈式調用跟返回不同的狀態
//?MyPromise.js//?先定義三個常量表示狀態 const?PENDING?=?'pending'; const?FULFILLED?=?'fulfilled'; const?REJECTED?=?'rejected';//?新建?MyPromise?類 class?MyPromise?{constructor(executor){//?executor?是一個執行器,進入會立即執行//?并傳入resolve和reject方法executor(this.resolve,?this.reject)}//?儲存狀態的變量,初始值是?pendingstatus?=?PENDING;// resolve和reject為什么要用箭頭函數?//?如果直接調用的話,普通函數this指向的是window或者undefined//?用箭頭函數就可以讓this指向當前實例對象//?成功之后的值value?=?null;//?失敗之后的原因reason?=?null;//?更改成功后的狀態resolve?=?(value)?=>?{//?只有狀態是等待,才執行狀態修改if?(this.status?===?PENDING)?{//?狀態修改為成功this.status?=?FULFILLED;//?保存成功之后的值this.value?=?value;}}//?更改失敗后的狀態reject?=?(reason)?=>?{//?只有狀態是等待,才執行狀態修改if?(this.status?===?PENDING)?{//?狀態成功為失敗this.status?=?REJECTED;//?保存失敗后的原因this.reason?=?reason;}}then(onFulfilled,?onRejected)?{//?判斷狀態if?(this.status?===?FULFILLED)?{//?調用成功回調,并且把值返回onFulfilled(this.value);}?else?if?(this.status?===?REJECTED)?{//?調用失敗回調,并且把原因返回onRejected(this.reason);}}}
實現Promise.all
//?Promise.all function?all(promises)?{let?len?=?promises.length,?res?=?[]if?(len)?{return?new?Promise(function?(resolve,?reject)?{for(let?i=0;?i?<?len;?i++){let?promise?=?promises[i];promise.then(response?=>?{res[i]?=?response//?當返回結果為最后一個時if?(res.length?===?len)?{resolve(res)}},?error?=>?{reject(error)})}}) }
對象數組轉換成tree數組
>?將entries?按照?level?轉換成?result?數據結構const?entries?=?[{"province":?"浙江",?"city":?"杭州",?"name":?"西湖"},?{"province":?"四川",?"city":?"成都",?"name":?"錦里"},?{"province":?"四川",?"city":?"成都",?"name":?"方所"},?{"province":?"四川",?"city":?"阿壩",?"name":?"九寨溝"} ];const?level?=?["province",?"city",?"name"];const??result?=?[{value:'浙江',children:[{value:'杭州',children:[{value:'西湖'}]}]},{value:'四川',children:[{value:'成都',children:[{value:'錦里'},{value:'方所'}]},{value:'阿壩',children:[{value:'九寨溝'}]}]}, ]
思路:涉及到樹形數組,采用遞歸遍歷的方式
function?transfrom(list,?level)?{const?res?=?[];list.forEach(item?=>?{pushItem(res,?item,?0);});function?pushItem(arr,?obj,?i)?{const?o?=?{value:?obj[level[i]],children:?[],};//?判斷傳入數組里是否有value等于要傳入的項const?hasItem?=?arr.find(el?=>?el.value?===?obj[level[i]]);let?nowArr;if(hasItem)?{//?存在,則下一次遍歷傳入存在項的childrennowArr?=?hasItem.children;}else{//?不存在?壓入arr,下一次遍歷傳入此項的childrenarr.push(o);nowArr?=?o.children;}if(i?===?level.length?-?1)?delete?o.children;i++;if(i?<?level.length)?{//?遞歸進行層級的遍歷pushItem(nowArr,?obj,?i);}} }transfrom(entries,?level);
JS instanceof 方法原生實現
簡單用法
function?Fn?()?{} const?fn?=?new?Fn() fn?instanceof?Fn??//?true
實現如下:
//?left?instanceof?right function?_instanceof(left,?right)?{//?構造函數原型const?prototype?=?right.prototype//?實列對象屬性,指向其構造函數原型left?=?left.__proto__//?查實原型鏈while?(true)?{//?如果為null,說明原型鏈已經查找到最頂層了,真接返回falseif?(left?===?null)?{return?false}//?查找到原型if?(prototype?===?left){return?true}//?繼續向上查找left?=?left.__proto__} }const?str?=?"abc" _instanceof(str,?String)?//?true
編程題
將有同樣元素的數組進行合并
//?例如: const?arr?=?[['a',?'b',?'c'],['a',?'d'],['d',?'e'],['f',?'g'],['h',?'g'],['i'] ] //?運行后的返回結果是: [['a',?'b',?'c',?'d',?'e'],['f',?'g',?'h'],['i'] ] //?思路一: const?arr?=?[['a',?'b',?'c'],?['a',?'d'],?['d',?'e'],?['f',?'g'],?['h',?'g'],?['i']] function?transform(arr){let?res?=?[]arr?=?arr.map(el?=>?el.sort()).sort()const?item?=?arr.reduce((pre,?cur)?=>?{if?(cur.some(el?=>?pre?&&?pre.includes(el)))?{pre?=?pre.concat(cur)}?else?{res.push(pre)pre?=?cur}return?[...new?Set(pre)]})res.push(item)return?res; } transform(arr) //?console.log(transform(arr));//?思路二:?function?r?(arr)?{const?map?=?new?Map()arr.forEach((array,?index)?=>?{const?findAlp?=?array.find((v)?=>?map.get(v))if?(findAlp)?{const?set?=?map.get(findAlp)array.forEach((alp)?=>?{set.add(alp)const?findAlp2?=?map.get(alp)if?(findAlp2?&&?findAlp2?!==?set)?{for(const?v?of?findAlp2.values()){set.add(v)map.set(v,?set)}}map.set(alp,?set)})}?else?{const?set?=?new?Set(arr[index])array.forEach((alp)?=>?map.set(alp,?set))}})const?set?=?new?Set()const?ret?=?[]for?(const?[key,?value]?of?map.entries())?{if?(set.has(value))?continueset.add(value)ret.push([...value])}return?ret }
最近組建了一個江西人的前端交流群,如果你是江西人可以加我微信?ruochuan12?私信 江西?拉你進群。
推薦閱讀
我在阿里招前端,該怎么幫你(可進面試群)
我讀源碼的經歷在字節做前端一年后,有啥收獲~
老姚淺談:怎么學JavaScript?·················?若川簡介?·················
你好,我是若川,畢業于江西高校。現在是一名前端開發“工程師”。寫有《學習源碼整體架構系列》多篇,在知乎、掘金收獲超百萬閱讀。
從2014年起,每年都會寫一篇年度總結,已經寫了7篇,點擊查看年度總結。
同時,活躍在知乎@若川,掘金@若川。致力于分享前端開發經驗,愿景:幫助5年內前端人走向前列。識別上方二維碼加我微信、長期交流學習
今日話題
略。歡迎分享、收藏、點贊、在看我的公眾號文章~