天小編給大家?guī)砹?個實用寶藏網(wǎng)站,常用、簡潔、方便、無廣告,滿足你日常工作需求!讓你做到資源在手,再不求人!做辦公室最閃亮的那一顆Star!
1.在線工具
在線工具,顧名思義,線上的工具箱。里面的工具大多是比較偏向生活化的,而且網(wǎng)站上面的功能各種各樣,具有多樣性。比較生活化的,就類似格字帖生成、數(shù)字轉(zhuǎn)大寫、各國首都列表、高校查詢、尺碼對照表、地圖坐標系等等。
當(dāng)然比較專業(yè)的也有,像在線運行代碼、css工具、html工具、php工具等等碼農(nóng)必備。畢竟這個網(wǎng)站給自己的定位是程序員的工具箱。網(wǎng)站優(yōu)點在于總共匯總了近百款工具,而且完全免費,不用注冊,隨用隨關(guān)!十分良心!
2.阿貓阿狗導(dǎo)航
阿貓阿狗網(wǎng)址導(dǎo)航是產(chǎn)品經(jīng)理和運營人必備的導(dǎo)航站,整個網(wǎng)站多達17個大類別。涵蓋了行業(yè)資訊、區(qū)塊鏈、數(shù)據(jù)分析、前端框架、域名注冊等等。收錄產(chǎn)品運營必備工具、國外優(yōu)秀網(wǎng)站等相關(guān)網(wǎng)站。無論是新媒體還是自媒體,無論是產(chǎn)品汪還是運營喵,這都是必須Ctrl+D的一個網(wǎng)站~
3.迅捷畫圖
作為一款可以直接在線繪制各種思維導(dǎo)圖、流程圖等多種圖表的在線網(wǎng)站,完全可以滿足上班族的日常需要。網(wǎng)站上的模板種類繁多,并且可以直接套用進行在線編輯。上手簡單,功能齊全。同時支持多種格式的導(dǎo)出與保存,對新手小白很友好的一個在線繪圖網(wǎng)站。最重要一點!這個網(wǎng)站導(dǎo)出.xsd格式是免費的,沒有任何限制,而且后續(xù)還可以隨時修改。如果想要提高工作效率,這個網(wǎng)站也是不錯的選擇。
1.蟲部落快搜
這個網(wǎng)站雖然作為一個搜索網(wǎng)站,倒不如說是搜索引擎的集合體。當(dāng)你打開這個網(wǎng)站的時候,你會發(fā)現(xiàn),啊!原來搜索還可以這么玩!界面左邊布滿了各種搜索類別,你可以直接按需搜索。其中涵蓋了英文電子書、姓氏家譜、漢典、古文獻、MBA智庫、商品歷史價格等等類別。棒極了!立馬Ctrl+D!
2.鳩摩搜索
這個網(wǎng)站是一個電子書搜索網(wǎng)站,由一名電子書愛好者建立的。果然愛好使人強大!整個界面十分簡潔、可愛。該網(wǎng)站通過搜索引擎抓取網(wǎng)絡(luò)上現(xiàn)有的電子書,并且支持多種格式下載。擁有這個網(wǎng)站就可以隨心所欲下載電子書了~書蟲寶寶們不能錯過的一款搜索網(wǎng)站。
以上就是今天小編要分享給大家的寶藏網(wǎng)站啦!上面的網(wǎng)站大家有用過嗎?還是大家有更好的網(wǎng)站呀,不要吝嗇!歡迎分享給小編哦~有好用的話后續(xù)小編也可以收錄匯總一下!
大家好,今年的金三銀四已經(jīng)來了,也有人說是銅三鐵四,不過我想說的是這重要嗎,環(huán)境只是一個因素,它確實會影響大家找工作或者跳槽漲薪,但是影響不多,最重要的一個因素在于自己是否已經(jīng)做好了準備。我呢為大家準備了百道面試題,為大家保駕護航,后續(xù)也會持續(xù)更新吧,畢竟還有金九銀十與新技術(shù)的出現(xiàn)。
希望大家可以點個贊支持一下,整理不易
因為內(nèi)容太多所以需要完整版PDF的小伙伴可關(guān)注+私信【學(xué)習(xí)】自行查看
- Vue.js 2.x 中響應(yīng)式系統(tǒng)的核心是 Object.defineProperty,劫持整個對象,然后進行深度遍歷所有屬性,給每個屬性添加`getter`和`setter`,實現(xiàn)響應(yīng)式
- Vue.js 3.x 中使用 Proxy 對象重寫響應(yīng)式系統(tǒng)
- 可以監(jiān)聽動態(tài)新增的屬性
- 可以監(jiān)聽刪除的屬性
- 可以監(jiān)聽數(shù)組的索引和length屬性
* 實現(xiàn)原理:
* 通過Proxy(代理): 攔截對象中任意屬性的變化, 包括:屬性值的讀寫、屬性的添加、屬性的刪除等。
* 通過Reflect(反射): 對源對象的屬性進行操作。
* MDN文檔中描述的Proxy與Reflect:
* Proxy:[Proxy - JavaScript | MDN](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy "Proxy - JavaScript | MDN")
* Reflect:[Reflect - JavaScript | MDN](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Reflect "Reflect - JavaScript | MDN")
new Proxy(data, {
// 攔截讀取屬性值
get (target, prop) {
return Reflect.get(target, prop)
},
// 攔截設(shè)置屬性值或添加新屬性
set (target, prop, value) {
return Reflect.set(target, prop, value)
},
// 攔截刪除屬性
deleteProperty (target, prop) {
return Reflect.deleteProperty(target, prop)
}
})
proxy.name='tom' ![]
- Vue.js 2.x 通過標記靜態(tài)節(jié)點,優(yōu)化 diff 的過程
- Vue.js 3.x
* vue.js 3.x中標記和提升所有的靜態(tài)節(jié)點,diff的時候只需要對比動態(tài)節(jié)點內(nèi)容;
* Fragments(升級vetur插件): template中不需要唯一根節(jié)點,可以直接放文本或者同級標簽
* 靜態(tài)提升(hoistStatic),當(dāng)使用 hoistStatic 時,所有靜態(tài)的節(jié)點都被提升到 render 方法之外.只會在應(yīng)用啟動的時候被創(chuàng)建一次,之后使用只需要應(yīng)用提取的靜態(tài)節(jié)點,隨著每次的渲染被不停的復(fù)用。
* patch flag, 在動態(tài)標簽?zāi)┪布由舷鄳?yīng)的標記,只能帶 patchFlag 的節(jié)點才被認為是動態(tài)的元素,會被追蹤屬性的修改,能快速的找到動態(tài)節(jié)點,而不用逐個逐層遍歷,提高了虛擬dom diff的性能。
* 緩存事件處理函數(shù)cacheHandler,避免每次觸發(fā)都要重新生成全新的function去更新之前的函數(shù)
markdown
- 相比Vue2,Vue3整體體積變小了,除了移出一些不常用的AP
- tree shanking
- 任何一個函數(shù),如ref、reavtived、computed等,僅僅在用到的時候才打包
- 通過編譯階段的靜態(tài)分析,找到?jīng)]有引入的模塊并打上標記,將這些模塊都給搖掉
* 在Vue2中: 組件必須有一個根標簽
* 在Vue3中: 組件可以沒有根標簽, 內(nèi)部會將多個標簽包含在一個Fragment虛擬元素中
* 好處: 減少標簽層級, 減小內(nèi)存占用
markdown
什么是Teleport?—— Teleport 是一種能夠?qū)⑽覀兊?strong>組件html結(jié)構(gòu)移動到指定位置的技術(shù)。
<teleport to="移動位置">
<div v-if="isShow" class="mask">
<div class="dialog">
<h3>我是一個彈窗</h3>
<button @click="isShow=false">關(guān)閉彈窗</button>
</div>
</div>
</teleport>
import {defineAsyncComponent} from 'vue'
const Child=defineAsyncComponent(()=>import('./components/Child.vue'))
<template>
<div class="app">
<h3>我是App組件</h3>
<Suspense>
<template v-slot:default>
<Child/>
</template>
<template v-slot:fallback>
<h3>加載中.....</h3>
</template>
</Suspense>
</div>
</template>
1. 響應(yīng)式系統(tǒng)的重新配置,使用proxy替換Object.defineProperty
2. typescript支持
3. 新增組合API,更好的邏輯重用和代碼組織
4. v-if和v-for的優(yōu)先級
5. 靜態(tài)元素提升
6. 虛擬節(jié)點靜態(tài)標記
7. 生命周期變化
8. 打包體積優(yōu)化
9. ssr渲染性能提升
10. 支持多個根節(jié)點
* Vue3.0中可以繼續(xù)使用Vue2.x中的生命周期鉤子,但有有兩個被更名:
* `beforeDestroy`改名為 `beforeUnmount`
?
* `destroyed`改名為 `unmounted`
?
* Vue3.0也提供了 Composition API 形式的生命周期鉤子,與Vue2.x中鉤子對應(yīng)關(guān)系如下:
?
* `beforeCreate`===>`setup()`
?
* `created`=======>`setup()`
?
* `beforeMount`===>`onBeforeMount`
?
* `mounted`=======>`onMounted`
?
* `beforeUpdate`===>`onBeforeUpdate`
?
* `updated`=======>`onUpdated`
?
* `beforeUnmount`==>`onBeforeUnmount`
?
* `unmounted`=====>`onUnmounted`
Vue 實例有?個完整的?命周期,也就是從開始創(chuàng)建、初始化數(shù)據(jù)、編譯模版、掛載Dom -> 渲染、更新 -> 渲染、卸載 等?系列過程,稱這是Vue的?命周期。
1、beforeCreate(創(chuàng)建前) :數(shù)據(jù)觀測和初始化事件還未開始,此時 data 的響應(yīng)式追蹤、event/watcher 都還沒有被設(shè)置,也就是說不能訪問到data、computed、watch、methods上的方法和數(shù)據(jù)。
2、created(創(chuàng)建后) :實例創(chuàng)建完成,實例上配置的 options 包括 data、computed、watch、methods 等都配置完成,但是此時渲染得節(jié)點還未掛載到 DOM,所以不能訪問到 `$el` 屬性。
3、beforeMount(掛載前) :在掛載開始之前被調(diào)用,相關(guān)的render函數(shù)首次被調(diào)用。實例已完成以下的配置:編譯模板,把data里面的數(shù)據(jù)和模板生成html。此時還沒有掛載html到頁面上。
4、mounted(掛載后) :在el被新創(chuàng)建的 vm.$el 替換,并掛載到實例上去之后調(diào)用。實例已完成以下的配置:用上面編譯好的html內(nèi)容替換el屬性指向的DOM對象。完成模板中的html渲染到html 頁面中。此過程中進行ajax交互。
5、beforeUpdate(更新前) :響應(yīng)式數(shù)據(jù)更新時調(diào)用,此時雖然響應(yīng)式數(shù)據(jù)更新了,但是對應(yīng)的真實 DOM 還沒有被渲染。
6、updated(更新后):在由于數(shù)據(jù)更改導(dǎo)致的虛擬DOM重新渲染和打補丁之后調(diào)用。此時 DOM 已經(jīng)根據(jù)響應(yīng)式數(shù)據(jù)的變化更新了。調(diào)用時,組件 DOM已經(jīng)更新,所以可以執(zhí)行依賴于DOM的操作。然而在大多數(shù)情況下,應(yīng)該避免在此期間更改狀態(tài),因為這可能會導(dǎo)致更新無限循環(huán)。該鉤子在服務(wù)器端渲染期間不被調(diào)用。
7、beforeDestroy(銷毀前) :實例銷毀之前調(diào)用。這一步,實例仍然完全可用,`this` 仍能獲取到實例。
8、destroyed(銷毀后) :實例銷毀后調(diào)用,調(diào)用后,Vue 實例指示的所有東西都會解綁定,所有的事件監(jiān)聽器會被移除,所有的子實例也會被銷毀。該鉤子在服務(wù)端渲染期間不被調(diào)用。
從 React Hook 從實現(xiàn)的角度來看,React Hook 是基于 useState 的調(diào)用順序來確定下一個 re 渲染時間狀態(tài)從哪個 useState 開始,所以有以下幾個限制
?
* 不在循環(huán)中、條件、調(diào)用嵌套函數(shù) Hook
* 你必須確保它總是在你這邊 React Top level 調(diào)用函數(shù) Hook
* 使用效果、使用備忘錄 依賴關(guān)系必須手動確定
?
和 Composition API 是基于 Vue 的響應(yīng)系統(tǒng),和 React Hook 相比
?
* 在設(shè)置函數(shù)中,一個組件實例只調(diào)用一次設(shè)置,而 React Hook 每次重新渲染時,都需要調(diào)用 Hook,給 React 帶來的 GC 比 Vue 更大的壓力,性能也相對 Vue 對我來說也比較慢
* Compositon API 你不必擔(dān)心調(diào)用的順序,它也可以在循環(huán)中、條件、在嵌套函數(shù)中使用
* 響應(yīng)式系統(tǒng)自動實現(xiàn)依賴關(guān)系收集,而且組件的性能優(yōu)化是由 Vue 內(nèi)部完成的,而 React Hook 的依賴關(guān)系需要手動傳遞,并且依賴關(guān)系的順序必須得到保證,讓路 useEffect、useMemo 等等,否則組件性能會因為依賴關(guān)系不正確而下降。
?
雖然Compoliton API看起來像React Hook來使用,但它的設(shè)計思路也是React Hook的參考。
Options API,即大家常說的選項API,即以vue為后綴的文件,通過定義methods,computed,watch,data等屬性與方法,共同處理頁面邏輯
如下圖:
可以看到Options代碼編寫方式,如果是組件狀態(tài),則寫在data屬性上,如果是方法,則寫在methods屬性上...
用組件的選項 (data、computed、methods、watch) 組織邏輯在大多數(shù)情況下都有效
然而,當(dāng)組件變得復(fù)雜,導(dǎo)致對應(yīng)屬性的列表也會增長,這可能會導(dǎo)致組件難以閱讀和理解
在 Vue3 Composition API 中,組件根據(jù)邏輯功能來組織的,一個功能所定義的所有 API 會放在一起(更加的高內(nèi)聚,低耦合)
即使項目很大,功能很多,我們都能快速的定位到這個功能所用到的所有 API
下面對Composition Api與Options Api進行兩大方面的比較
假設(shè)一個組件是一個大型組件,其內(nèi)部有很多處理邏輯關(guān)注點(對應(yīng)下圖不用顏色)
可以看到,這種碎片化使得理解和維護復(fù)雜組件變得困難
選項的分離掩蓋了潛在的邏輯問題。此外,在處理單個邏輯關(guān)注點時,我們必須不斷地“跳轉(zhuǎn)”相關(guān)代碼的選項塊
而Compositon API正是解決上述問題,將某個邏輯關(guān)注點相關(guān)的代碼全都放在一個函數(shù)里,這樣當(dāng)需要修改一個功能時,就不再需要在文件中跳來跳去
下面舉個簡單例子,將處理count屬性相關(guān)的代碼放在同一個函數(shù)了
inifunction useCount() {
let count=ref(10);
let double=computed(()=> {
return count.value * 2;
});
?
const handleConut=()=> {
count.value=count.value * 2;
};
?
console.log(count);
?
return {
count,
double,
handleConut,
};
}
組件上中使用count
export default defineComponent({
setup() {
const { count, double, handleConut }=useCount();
return {
count,
double,
handleConut
}
},
});
再來一張圖進行對比,可以很直觀地感受到 Composition API在邏輯組織方面的優(yōu)勢,以后修改一個屬性功能的時候,只需要跳到控制該屬性的方法中即可
在Vue2中,我們是用過mixin去復(fù)用相同的邏輯
下面舉個例子,我們會另起一個mixin.js文件
export const MoveMixin={
data() {
return {
x: 0,
y: 0,
};
},
?
methods: {
handleKeyup(e) {
console.log(e.code);
// 上下左右 x y
switch (e.code) {
case "ArrowUp":
this.y--;
break;
case "ArrowDown":
this.y++;
break;
case "ArrowLeft":
this.x--;
break;
case "ArrowRight":
this.x++;
break;
}
},
},
?
mounted() {
window.addEventListener("keyup", this.handleKeyup);
},
?
unmounted() {
window.removeEventListener("keyup", this.handleKeyup);
},
};
然后在組件中使用
<template>
<div>
Mouse position: x {{ x }} / y {{ y }}
</div>
</template>
<script>
import mousePositionMixin from './mouse'
export default {
mixins: [mousePositionMixin]
}
</script>
使用單個mixin似乎問題不大,但是當(dāng)我們一個組件混入大量不同的 mixins 的時候
mixins: [mousePositionMixin, fooMixin, barMixin, otherMixin]
會存在兩個非常明顯的問題:
現(xiàn)在通過Compositon API這種方式改寫上面的代碼
import { onMounted, onUnmounted, reactive } from "vue";
export function useMove() {
const position=reactive({
x: 0,
y: 0,
});
?
const handleKeyup=(e)=> {
console.log(e.code);
// 上下左右 x y
switch (e.code) {
case "ArrowUp":
// y.value--;
position.y--;
break;
case "ArrowDown":
// y.value++;
position.y++;
break;
case "ArrowLeft":
// x.value--;
position.x--;
break;
case "ArrowRight":
// x.value++;
position.x++;
break;
}
};
?
onMounted(()=> {
window.addEventListener("keyup", handleKeyup);
});
?
onUnmounted(()=> {
window.removeEventListener("keyup", handleKeyup);
});
?
return { position };
}
在組件中使用
<template>
<div>
Mouse position: x {{ x }} / y {{ y }}
</div>
</template>
?
<script>
import { useMove } from "./useMove";
import { toRefs } from "vue";
export default {
setup() {
const { position }=useMove();
const { x, y }=toRefs(position);
return {
x,
y,
};
},
};
</script>
可以看到,整個數(shù)據(jù)來源清晰了,即使去編寫更多的 hook 函數(shù),也不會出現(xiàn)命名沖突的問題
單頁Web應(yīng)用(single page web application,SPA),就是只有一張Web頁面的應(yīng)用,是加載單個HTML頁面并在用戶與應(yīng)用程序交互時動態(tài)更新該頁面的Web應(yīng)用程序。我們開發(fā)的`Vue`項目大多是借助個官方的`CLI`腳手架,快速搭建項目,直接通過`new Vue`構(gòu)建一個實例,并將`el:'#app'`掛載參數(shù)傳入,最后通過`npm run build`的方式打包后生成一個`index.html`,稱這種只有一個`HTML`的頁面為單頁面應(yīng)用。
?
當(dāng)然,`vue`也可以像`jq`一樣引入,作為多頁面應(yīng)用的基礎(chǔ)框架。
?
?
SPA首屏優(yōu)化方式
?
減小入口文件積
靜態(tài)資源本地緩存
UI框架按需加載
圖片資源的壓縮
組件重復(fù)打包
開啟GZip壓縮
使用SSR
1、`v-if`和`v-show`
* 頻繁切換時使用`v-show`,利用其緩存特性
* 首屏渲染時使用`v-if`,如果為`false`則不進行渲染
2、`v-for`的`key`
* 列表變化時,循環(huán)時使用唯一不變的`key`,借助其本地復(fù)用策略
* 列表只進行一次渲染時,`key`可以采用循環(huán)的`index`
3、偵聽器和計算屬性
* 偵聽器`watch`用于數(shù)據(jù)變化時引起其他行為
* 多使用`compouter`計算屬性顧名思義就是新計算而來的屬性,如果依賴的數(shù)據(jù)未發(fā)生變化,不會觸發(fā)重新計算
4、合理使用生命周期
* 在`destroyed`階段進行綁定事件或者定時器的銷毀
* 使用動態(tài)組件的時候通過`keep-alive`包裹進行緩存處理,相關(guān)的操作可以在`actived`階段激活
5、數(shù)據(jù)響應(yīng)式處理
* 不需要響應(yīng)式處理的數(shù)據(jù)可以通過`Object.freeze`處理,或者直接通過`this.xxx=xxx`的方式進行定義
* 需要響應(yīng)式處理的屬性可以通過`this.$set`的方式處理,而不是`JSON.parse(JSON.stringify(XXX))`的方式
6、路由加載方式
* 頁面組件可以采用異步加載的方式
7、插件引入
* 第三方插件可以采用按需加載的方式,比如`element-ui`。
8、減少代碼量
* 采用`mixin`的方式抽離公共方法
* 抽離公共組件
* 定義公共方法至公共`js`中
* 抽離公共`css`
9、編譯方式
* 如果線上需要`template`的編譯,可以采用完成版`vue.esm.js`
* 如果線上無需`template`的編譯,可采用運行時版本`vue.runtime.esm.js`,相比完整版體積要小大約`30%`
10、渲染方式
* 服務(wù)端渲染,如果是需要`SEO`的網(wǎng)站可以采用服務(wù)端渲染的方式
* 前端渲染,一些企業(yè)內(nèi)部使用的后端管理系統(tǒng)可以采用前端渲染的方式
11、字體圖標的使用
* 有些圖片圖標盡可能使用字體圖標
vue中8種常規(guī)的通信方案
?
通過 props 傳遞
通過 $emit 觸發(fā)自定義事件
使用 ref
EventBus
$parent 或$root
attrs 與 listeners
Provide 與 Inject
Vuex
?
組件間通信的分類可以分成以下
?
父子關(guān)系的組件數(shù)據(jù)傳遞選擇 props 與 $emit進行傳遞,也可選擇ref
兄弟關(guān)系的組件數(shù)據(jù)傳遞可選擇$bus,其次可以選擇$parent進行傳遞
祖先與后代組件數(shù)據(jù)傳遞可選擇attrs與listeners或者 Provide與 Inject
復(fù)雜關(guān)系的組件數(shù)據(jù)傳遞可以通過vuex存放共享的變量
1、表單修飾符
?
(1)`.lazy`
?
在默認情況下,`v-model` 在每次 `input` 事件觸發(fā)后將輸入框的值與數(shù)據(jù)進行同步 ,可以添加 `lazy` 修飾符,從而轉(zhuǎn)為在 `change` 事件之后進行同步:
?
```
<input v-model.lazy="msg">
?
```
?
(2)`.number`
?
如果想自動將用戶的輸入值轉(zhuǎn)為數(shù)值類型,可以給 `v-model` 添加 `number` 修飾符:
?
```
<input v-model.number="age" type="number">
?
```
?
(3)`.trim`
?
如果要自動過濾用戶輸入的首尾空白字符,可以給 `v-model` 添加 `trim` 修飾符:
?
```
<input v-model.trim="msg">
?
```
?
2、事件修飾符
?
(1)`.stop`
?
阻止單擊事件繼續(xù)傳播。
?
```
<!--這里只會觸發(fā)a-->
<div @click="divClick"><a v-on:click.stop="aClick">點擊</a></div>
?
```
?
(2)`.prevent`
?
阻止標簽的默認行為。
?
```
<a href="http://www.baidu.com" v-on:click.prevent="aClick">點擊</a>
?
```
?
(3)`.capture`
?
事件先在有`.capture`修飾符的節(jié)點上觸發(fā),然后在其包裹的內(nèi)部節(jié)點中觸發(fā)。
?
```
<!--這里先執(zhí)行divClick事件,然后再執(zhí)行aClick事件-->
<div @click="divClick"><a v-on:click="aClick">點擊</a></div>
?
```
?
(4)`.self`
?
只當(dāng)在 event.target 是當(dāng)前元素自身時觸發(fā)處理函數(shù),即事件不是從內(nèi)部元素觸發(fā)的。
?
```
<!--在a標簽上點擊時只會觸發(fā)aClick事件,只有點擊phrase的時候才會觸發(fā)divClick事件-->
<div @click.self="divClick">phrase<a v-on:click="aClick">點擊</a></div>
?
```
?
(5)`.once`
?
不像其它只能對原生的 DOM 事件起作用的修飾符,`.once` 修飾符還能被用到自定義的組件事件上,表示當(dāng)前事件只觸發(fā)一次。
?
```
<a v-on:click.once="aClick">點擊</a>
?
```
(6)`.passive`
?
`.passive` 修飾符尤其能夠提升移動端的性能
?
```
<!-- 滾動事件的默認行為 (即滾動行為) 將會立即觸發(fā) -->
<!-- 而不會等待 `onScroll` 完成 -->
<!-- 這其中包含 `event.preventDefault()` 的情況 -->
<div v-on:scroll.passive="onScroll">...</div>
```
?
const callbacks=[]
let pending=false
?
/**
* 完成兩件事:
* 1、用 try catch 包裝 flushSchedulerQueue 函數(shù),然后將其放入 callbacks 數(shù)組
* 2、如果 pending 為 false,表示現(xiàn)在瀏覽器的任務(wù)隊列中沒有 flushCallbacks 函數(shù)
* 如果 pending 為 true,則表示瀏覽器的任務(wù)隊列中已經(jīng)被放入了 flushCallbacks 函數(shù),
* 待執(zhí)行 flushCallbacks 函數(shù)時,pending 會被再次置為 false,表示下一個 flushCallbacks 函數(shù)可以進入
* 瀏覽器的任務(wù)隊列了
* pending 的作用:保證在同一時刻,瀏覽器的任務(wù)隊列中只有一個 flushCallbacks 函數(shù)
* @param {*} cb 接收一個回調(diào)函數(shù)=> flushSchedulerQueue
* @param {*} ctx 上下文
* @returns
*/
export function nextTick (cb?: Function, ctx?: Object) {
let _resolve
// 用 callbacks 數(shù)組存儲經(jīng)過包裝的 cb 函數(shù)
callbacks.push(()=> {
if (cb) {
// 用 try catch 包裝回調(diào)函數(shù),便于錯誤捕獲
try {
cb.call(ctx)
} catch (e) {
handleError(e, ctx, 'nextTick')
}
} else if (_resolve) {
_resolve(ctx)
}
})
if (!pending) {
pending=true
// 執(zhí)行 timerFunc,在瀏覽器的任務(wù)隊列中(首選微任務(wù)隊列)放入 flushCallbacks 函數(shù)
timerFunc()
}
// $flow-disable-line
if (!cb && typeof Promise !=='undefined') {
return new Promise(resolve=> {
_resolve=resolve
})
}
}
官方對其的定義
在下次 DOM 更新循環(huán)結(jié)束之后執(zhí)行延遲回調(diào)。在修改數(shù)據(jù)之后立即使用這個方法,獲取更新后的 DOM
什么意思呢?
我們可以理解成,Vue 在更新 DOM 時是異步執(zhí)行的。當(dāng)數(shù)據(jù)發(fā)生變化,Vue將開啟一個異步更新隊列,視圖需要等隊列中所有數(shù)據(jù)變化完成之后,再統(tǒng)一進行更新
Vue 的異步更新機制的核心是利用了瀏覽器的異步任務(wù)隊列來實現(xiàn)的,首選微任務(wù)隊列,宏任務(wù)隊列次之。
當(dāng)響應(yīng)式數(shù)據(jù)更新后,會調(diào)用 dep.notify 方法,通知 dep 中收集的 watcher 去執(zhí)行 update 方法,watcher.update 將 watcher 自己放入一個 watcher 隊列(全局的 queue 數(shù)組)。
然后通過 nextTick 方法將一個刷新 watcher 隊列的方法(flushSchedulerQueue)放入一個全局的 callbacks 數(shù)組中。
如果此時瀏覽器的異步任務(wù)隊列中沒有一個叫 flushCallbacks 的函數(shù),則執(zhí)行 timerFunc 函數(shù),將 flushCallbacks 函數(shù)放入異步任務(wù)隊列。如果異步任務(wù)隊列中已經(jīng)存在 flushCallbacks 函數(shù),等待其執(zhí)行完成以后再放入下一個 flushCallbacks 函數(shù)。
flushCallbacks 函數(shù)負責(zé)執(zhí)行 callbacks 數(shù)組中的所有 flushSchedulerQueue 函數(shù)。
flushSchedulerQueue 函數(shù)負責(zé)刷新 watcher 隊列,即執(zhí)行 queue 數(shù)組中每一個 watcher 的 run 方法,從而進入更新階段,比如執(zhí)行組件更新函數(shù)或者執(zhí)行用戶 watch 的回調(diào)函數(shù)。
我們都知道 Vue 是數(shù)據(jù)雙向綁定的框架,雙向綁定由三個重要部分構(gòu)成
?
數(shù)據(jù)層(Model):應(yīng)用的數(shù)據(jù)及業(yè)務(wù)邏輯
視圖層(View):應(yīng)用的展示效果,各類UI組件
業(yè)務(wù)邏輯層(ViewModel):框架封裝的核心,它負責(zé)將數(shù)據(jù)與視圖關(guān)聯(lián)起來
而上面的這個分層的架構(gòu)方案,可以用一個專業(yè)術(shù)語進行稱呼:MVVM這里的控制層的核心功能便是 “數(shù)據(jù)雙向綁定” 。自然,我們只需弄懂它是什么,便可以進一步了解數(shù)據(jù)綁定的原理
?
理解ViewModel
它的主要職責(zé)就是:
?
數(shù)據(jù)變化后更新視圖
視圖變化后更新數(shù)據(jù)
當(dāng)然,它還有兩個主要部分組成
?
監(jiān)聽器(Observer):對所有數(shù)據(jù)的屬性進行監(jiān)聽
解析器(Compiler):對每個元素節(jié)點的指令進行掃描跟解析,根據(jù)指令模板替換數(shù)據(jù),以及綁定相應(yīng)的更新函數(shù)
v-show 與 v-if 的作用效果是相同的(不含v-else),都能控制元素在頁面是否顯示,在用法上也是相同的
?
- 區(qū)別
控制手段不同
編譯過程不同
編譯條件不同
?
控制手段:v-show隱藏則是為該元素添加css--display:none,dom元素依舊還在。v-if顯示隱藏是將dom元素整個添加或刪除
?
編譯過程:v-if切換有一個局部編譯/卸載的過程,切換過程中合適地銷毀和重建內(nèi)部的事件監(jiān)聽和子組件;v-show只是簡單的基于css切換
?
編譯條件:v-if是真正的條件渲染,它會確保在切換過程中條件塊內(nèi)的事件監(jiān)聽器和子組件適當(dāng)?shù)乇讳N毀和重建。只有渲染條件為假時,并不做操作,直到為真才渲染
?
v-show 由false變?yōu)閠rue的時候不會觸發(fā)組件的生命周期
?
v-if由false變?yōu)閠rue的時候,觸發(fā)組件的beforeCreate、create、beforeMount、mounted鉤子,由true變?yōu)閒alse的時候觸發(fā)組件的beforeDestory、destoryed方法
?
性能消耗:v-if有更高的切換消耗;v-show有更高的初始渲染消耗
`vue`中支持組件化,并且也有用于緩存的內(nèi)置組件`keep-alive`可直接使用,使用場景為`路由組件`和`動態(tài)組件`。
?
* `activated`表示進入組件的生命周期,`deactivated`表示離開組件的生命周期
* `include`表示匹配到的才緩存,`exclude`表示匹配到的都不緩存
* `max`表示最多可以緩存多少組件
?
?
關(guān)于keep-alive的基本用法:
?
<keep-alive>
<component :is="view"></component>
</keep-alive>
使用includes和exclude:
?
<keep-alive include="a,b">
<component :is="view"></component>
</keep-alive>
?
<!-- 正則表達式 (使用 `v-bind`) -->
<keep-alive :include="/a|b/">
<component :is="view"></component>
</keep-alive>
?
<!-- 數(shù)組 (使用 `v-bind`) -->
<keep-alive :include="['a', 'b']">
<component :is="view"></component>
</keep-alive>
匹配首先檢查組件自身的 name 選項,如果 name 選項不可用,則匹配它的局部注冊名稱 (父組件 components 選項的鍵值),匿名組件不能被匹配
?
設(shè)置了 keep-alive 緩存的組件,會多出兩個生命周期鉤子(activated與deactivated):
?
首次進入組件時:beforeRouteEnter > beforeCreate > created> mounted > activated > ... ... > beforeRouteLeave > deactivated
?
再次進入組件時:beforeRouteEnter >activated > ... ... > beforeRouteLeave > deactivated
先看瀏覽器對HTML的理解:
<div>
<h1>My title</h1>
Some text content
<!-- TODO: Add tagline -->
</div>
當(dāng)瀏覽器讀到這些代碼時,它會建立一個DOM樹來保持追蹤所有內(nèi)容,如同你會畫一張家譜樹來追蹤家庭成員的發(fā)展一樣。 上述 HTML 對應(yīng)的 DOM 節(jié)點樹如下圖所示:
每個元素都是一個節(jié)點。每段文字也是一個節(jié)點。甚至注釋也都是節(jié)點。一個節(jié)點就是頁面的一個部分。就像家譜樹一樣,每個節(jié)點都可以有孩子節(jié)點 (也就是說每個部分可以包含其它的一些部分)。
**再看`Vue`對`HTML template`的理解**
Vue 通過建立一個**虛擬 DOM** 來追蹤自己要如何改變真實 DOM。因為它所包含的信息會告訴 Vue 頁面上需要渲染什么樣的節(jié)點,包括及其子節(jié)點的描述信息。我們把這樣的節(jié)點描述為“虛擬節(jié)點 (virtual node)”,也常簡寫它為“**VNode**”。“虛擬 DOM”是我們對由 Vue 組件樹建立起來的整個 VNode 樹的稱呼。
簡言之,瀏覽器對HTML的理解是DOM樹,Vue對`HTML`的理解是虛擬DOM,最后在`patch`階段通過DOM操作的api將其渲染成真實的DOM節(jié)點。
首先可以看看vue中VNode的結(jié)構(gòu)
源碼位置:src/core/vdom/vnode.js
export default class VNode {
tag: string | void;
data: VNodeData | void;
children: ?Array<VNode>;
text: string | void;
elm: Node | void;
ns: string | void;
context: Component | void; // rendered in this component's scope
functionalContext: Component | void; // only for functional component root nodes
key: string | number | void;
componentOptions: VNodeComponentOptions | void;
componentInstance: Component | void; // component instance
parent: VNode | void; // component placeholder node
raw: boolean; // contains raw HTML? (server only)
isStatic: boolean; // hoisted static node
isRootInsert: boolean; // necessary for enter transition check
isComment: boolean; // empty comment placeholder?
isCloned: boolean; // is a cloned node?
isOnce: boolean; // is a v-once node?
?
constructor (
tag?: string,
data?: VNodeData,
children?: ?Array<VNode>,
text?: string,
elm?: Node,
context?: Component,
componentOptions?: VNodeComponentOptions
) {
/*當(dāng)前節(jié)點的標簽名*/
this.tag=tag
/*當(dāng)前節(jié)點對應(yīng)的對象,包含了具體的一些數(shù)據(jù)信息,是一個VNodeData類型,可以參考VNodeData類型中的數(shù)據(jù)信息*/
this.data=data
/*當(dāng)前節(jié)點的子節(jié)點,是一個數(shù)組*/
this.children=children
/*當(dāng)前節(jié)點的文本*/
this.text=text
/*當(dāng)前虛擬節(jié)點對應(yīng)的真實dom節(jié)點*/
this.elm=elm
/*當(dāng)前節(jié)點的名字空間*/
this.ns=undefined
/*編譯作用域*/
this.context=context
/*函數(shù)化組件作用域*/
this.functionalContext=undefined
/*節(jié)點的key屬性,被當(dāng)作節(jié)點的標志,用以優(yōu)化*/
this.key=data && data.key
/*組件的option選項*/
this.componentOptions=componentOptions
/*當(dāng)前節(jié)點對應(yīng)的組件的實例*/
this.componentInstance=undefined
/*當(dāng)前節(jié)點的父節(jié)點*/
this.parent=undefined
/*簡而言之就是是否為原生HTML或只是普通文本,innerHTML的時候為true,textContent的時候為false*/
this.raw=false
/*靜態(tài)節(jié)點標志*/
this.isStatic=false
/*是否作為跟節(jié)點插入*/
this.isRootInsert=true
/*是否為注釋節(jié)點*/
this.isComment=false
/*是否為克隆節(jié)點*/
this.isCloned=false
/*是否有v-once指令*/
this.isOnce=false
}
?
// DEPRECATED: alias for componentInstance for backwards compat.
/* istanbul ignore next https://github.com/answershuto/learnVue*/
get child (): Component | void {
return this.componentInstance
}
}
這里對VNode進行稍微的說明:
vue`是通過`createElement`生成`VNode
源碼位置:
src/core/vdom/create-element.js
export function createElement (
context: Component,
tag: any,
data: any,
children: any,
normalizationType: any,
alwaysNormalize: boolean
): VNode | Array<VNode> {
if (Array.isArray(data) || isPrimitive(data)) {
normalizationType=children
children=data
data=undefined
}
if (isTrue(alwaysNormalize)) {
normalizationType=ALWAYS_NORMALIZE
}
return _createElement(context, tag, data, children, normalizationType)
}
上面可以看到createElement 方法實際上是對 _createElement 方法的封裝,對參數(shù)的傳入進行了判斷
export function _createElement(
context: Component,
tag?: string | Class<Component> | Function | Object,
data?: VNodeData,
children?: any,
normalizationType?: number
): VNode | Array<VNode> {
if (isDef(data) && isDef((data: any).__ob__)) {
process.env.NODE_ENV !=='production' && warn(
`Avoid using observed data object as vnode data: ${JSON.stringify(data)}\n` +
'Always create fresh vnode data objects in each render!',
context`
)
return createEmptyVNode()
}
// object syntax in v-bind
if (isDef(data) && isDef(data.is)) {
tag=data.is
}
if (!tag) {
// in case of component :is set to falsy value
return createEmptyVNode()
}
...
// support single function children as default scoped slot
if (Array.isArray(children) &&
typeof children[0]==='function'
) {
data=data || {}
data.scopedSlots={ default: children[0] }
children.length=0
}
if (normalizationType===ALWAYS_NORMALIZE) {
children=normalizeChildren(children)
} else if (===SIMPLE_NORMALIZE) {
children=simpleNormalizeChildren(children)
}
// 創(chuàng)建VNode
...
}
可以看到_createElement接收5個參數(shù):
根據(jù)normalizationType 的類型,children會有不同的定義
if (normalizationType===ALWAYS_NORMALIZE) {
children=normalizeChildren(children)
} else if (===SIMPLE_NORMALIZE) {
children=simpleNormalizeChildren(children)
}
simpleNormalizeChildren方法調(diào)用場景是 render 函數(shù)是編譯生成的
normalizeChildren方法調(diào)用場景分為下面兩種:
無論是simpleNormalizeChildren還是normalizeChildren都是對children進行規(guī)范(使children 變成了一個類型為 VNode 的 Array),這里就不展開說了
規(guī)范化children的源碼位置在:
src/core/vdom/helpers/normalzie-children.js
在規(guī)范化children后,就去創(chuàng)建VNode
let vnode, ns
// 對tag進行判斷
if (typeof tag==='string') {
let Ctor
ns=(context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag)
if (config.isReservedTag(tag)) {
// 如果是內(nèi)置的節(jié)點,則直接創(chuàng)建一個普通VNode
vnode=new VNode(
config.parsePlatformTagName(tag), data, children,
undefined, undefined, context
)
} else if (isDef(Ctor=resolveAsset(context.$options, 'components', tag))) {
// component
// 如果是component類型,則會通過createComponent創(chuàng)建VNode節(jié)點
vnode=createComponent(Ctor, data, context, children, tag)
} else {
vnode=new VNode(
tag, data, children,
undefined, undefined, context
)
}
} else {
// direct component options / constructor
vnode=createComponent(tag, data, context, children)
}
createComponent`同樣是創(chuàng)建`VNode
源碼位置:
src/core/vdom/create-component.js
export function createComponent (
Ctor: Class<Component> | Function | Object | void,
data: ?VNodeData,
context: Component,
children: ?Array<VNode>,
tag?: string
): VNode | Array<VNode> | void {
if (isUndef(Ctor)) {
return
}
// 構(gòu)建子類構(gòu)造函數(shù)
const baseCtor=context.$options._base
?
// plain options object: turn it into a constructor
if (isObject(Ctor)) {
Ctor=baseCtor.extend(Ctor)
}
?
// if at this stage it's not a constructor or an async component factory,
// reject.
if (typeof Ctor !=='function') {
if (process.env.NODE_ENV !=='production') {
warn(`Invalid Component definition: ${String(Ctor)}`, context)
}
return
}
?
// async component
let asyncFactory
if (isUndef(Ctor.cid)) {
asyncFactory=Ctor
Ctor=resolveAsyncComponent(asyncFactory, baseCtor, context)
if (Ctor===undefined) {
return createAsyncPlaceholder(
asyncFactory,
data,
context,
children,
tag
)
}
}
?
data=data || {}
?
// resolve constructor options in case global mixins are applied after
// component constructor creation
resolveConstructorOptions(Ctor)
?
// transform component v-model data into props & events
if (isDef(data.model)) {
transformModel(Ctor.options, data)
}
?
// extract props
const propsData=extractPropsFromVNodeData(data, Ctor, tag)
?
// functional component
if (isTrue(Ctor.options.functional)) {
return createFunctionalComponent(Ctor, propsData, data, context, children)
}
?
// extract listeners, since these needs to be treated as
// child component listeners instead of DOM listeners
const listeners=data.on
// replace with listeners with .native modifier
// so it gets processed during parent component patch.
data.on=data.nativeOn
?
if (isTrue(Ctor.options.abstract)) {
const slot=data.slot
data={}
if (slot) {
data.slot=slot
}
}
?
// 安裝組件鉤子函數(shù),把鉤子函數(shù)合并到data.hook中
installComponentHooks(data)
?
//實例化一個VNode返回。組件的VNode是沒有children的
const name=Ctor.options.name || tag
const vnode=new VNode(
`vue-component-${Ctor.cid}${name ? `-${name}` : ''}`,
data, undefined, undefined, undefined, context,
{ Ctor, propsData, listeners, tag, children },
asyncFactory
)
if (__WEEX__ && isRecyclableComponent(vnode)) {
return renderRecyclableComponentTemplate(vnode)
}
?
return vnode
}
稍微提下createComponent生成VNode的三個關(guān)鍵流程:
createElement 創(chuàng)建 VNode 的過程,每個 VNode 有 children,children 每個元素也是一個VNode,這樣就形成了一個虛擬樹結(jié)構(gòu),用于描述真實的DOM樹結(jié)構(gòu)
是不是一定是函數(shù),得看場景。并且,也無需擔(dān)心什么時候該將`data`寫為函數(shù)還是對象,因為`vue`內(nèi)部已經(jīng)做了處理,并在控制臺輸出錯誤信息。
?
**場景一**:`new Vue({data: ...})`
這種場景主要為項目入口或者多個`html`頁面各實例化一個`Vue`時,這里的`data`即可用對象的形式,也可用工廠函數(shù)返回對象的形式。因為,這里的`data`只會出現(xiàn)一次,不存在重復(fù)引用而引起的數(shù)據(jù)污染問題。
?
**場景二**:組件場景中的選項
在生成組件`vnode`的過程中,組件會在生成構(gòu)造函數(shù)的過程中執(zhí)行合并策略:
?
```
// data合并策略
strats.data=function (
parentVal,
childVal,
vm
) {
if (!vm) {
if (childVal && typeof childVal !=='function') {
process.env.NODE_ENV !=='production' && warn(
'The "data" option should be a function ' +
'that returns a per-instance value in component ' +
'definitions.',
vm
);
?
return parentVal
}
return mergeDataOrFn(parentVal, childVal)
}
?
return mergeDataOrFn(parentVal, childVal, vm)
};
```
?
如果合并過程中發(fā)現(xiàn)子組件的數(shù)據(jù)不是函數(shù),即`typeof childVal !=='function'`成立,進而在開發(fā)環(huán)境會在控制臺輸出警告并且直接返回`parentVal`,說明這里壓根就沒有把`childVal`中的任何`data`信息合并到`options`中去。
?
?
上面講到組件data必須是一個函數(shù),不知道大家有沒有思考過這是為什么呢?
?
在我們定義好一個組件的時候,vue最終都會通過Vue.extend()構(gòu)成組件實例
?
這里我們模仿組件構(gòu)造函數(shù),定義data屬性,采用對象的形式
?
function Component(){
}
Component.prototype.data={
count : 0
}
創(chuàng)建兩個組件實例
?
const componentA=new Component()
const componentB=new Component()
修改componentA組件data屬性的值,componentB中的值也發(fā)生了改變
?
console.log(componentB.data.count) // 0
componentA.data.count=1
console.log(componentB.data.count) // 1
產(chǎn)生這樣的原因這是兩者共用了同一個內(nèi)存地址,componentA修改的內(nèi)容,同樣對componentB產(chǎn)生了影響
?
如果我們采用函數(shù)的形式,則不會出現(xiàn)這種情況(函數(shù)返回的對象內(nèi)存地址并不相同)
?
function Component(){
this.data=this.data()
}
Component.prototype.data=function (){
return {
count : 0
}
}
修改componentA組件data屬性的值,componentB中的值不受影響
?
console.log(componentB.data.count) // 0
componentA.data.count=1
console.log(componentB.data.count) // 0
vue組件可能會有很多個實例,采用函數(shù)返回一個全新data形式,使每個實例對象的數(shù)據(jù)不會受到其他實例對象數(shù)據(jù)的污染
new Vue走到了vue的構(gòu)造函數(shù)中:`src\core\instance\index.js`文件。
?
this._init(options)
?
然后從Mixin增加的原型方法看,initMixin(Vue),調(diào)用的是為Vue增加的原型方法_init
?
// src/core/instance/init.js
?
function initMixin (Vue) {
Vue.prototype._init=function (options) {
var vm=this; 創(chuàng)建vm,
...
// 合并options 到 vm.$options
vm.$options=mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
);
}
...
initLifecycle(vm); //初始生命周期
initEvents(vm); //初始化事件
initRender(vm); //初始render函數(shù)
callHook(vm, 'beforeCreate'); //執(zhí)行 beforeCreate生命周期鉤子
...
initState(vm); //初始化data,props,methods computed,watch
...
callHook(vm, 'created'); //執(zhí)行 created 生命周期鉤子
if (vm.$options.el) {
vm.$mount(vm.$options.el); //這里也是重點,下面需要用到
}
}
?
總結(jié)
?
所以,從上面的函數(shù)看來,new vue所做的事情,就像一個流程圖一樣展開了,分別是
?
- 合并配置
- 初始化生命周期
- 初始化事件
- 初始化渲染
- 調(diào)用 `beforeCreate` 鉤子函數(shù)
- init injections and reactivity(這個階段屬性都已注入綁定,而且被 `$watch` 變成reactivity,但是 `$el` 還是沒有生成,也就是DOM沒有生成)
- 初始化state狀態(tài)(初始化了data、props、computed、watcher)
- 調(diào)用created鉤子函數(shù)。
?
在初始化的最后,檢測到如果有 el 屬性,則調(diào)用 vm.$mount 方法掛載 vm,掛載的目標就是把模板渲染成最終的 DOM。
- 初始化的一個大概流程
?
createApp()=> mount()=> render()=> patch()=> processComponent()=> mountComponent()
?
- 簡易版流程編寫
?
1.Vue.createApp() 實際執(zhí)行的是renderer的createApp()
?
2.renderer是createRenderer這個方法創(chuàng)建
?
3.renderer的createApp()是createAppAPI()返回的
?
4.createAppApi接受到render之后,創(chuàng)建一個app實例,定義mount方法
?
5.mount會調(diào)用render函數(shù)。將vnode轉(zhuǎn)換為真實dom
?
createRenderer()=> renderer=> renderer.createApp() <=createAppApi()
?
?
<div id="app"></div>
?
<script>
// 3.createAppAPI
const createAppAPI=render=> {
return function createApp(rootComponent) {
// 返回應(yīng)用程序?qū)嵗?
const app={
mount(rootContainer) {
// 掛載vnode=> dom
const vnode={
tag: rootComponent
}
// 執(zhí)行渲染
render(vnode, rootContainer)
}
}
return app;
}
}
?
// 1. 創(chuàng)建createApp
const Vue={
createApp(options) {
//實際執(zhí)行的為renderer的createApp()
// 返回app實例
return renderer.createApp(options)
}
}
?
// 2.實現(xiàn)renderer工廠函數(shù)
const createRenderer=options=> {
// 實現(xiàn)patch
const patch=(n1, n2, container)=> {
// 獲取根組件配置
const rootComponent=n2.tag;
const ctx={ ...rootComponent.data()}
// 執(zhí)行render獲取vnode
const vnode=rootComponent.render.call(ctx);
?
// 轉(zhuǎn)換vnode=> dom
const parent=options.querySelector(container)
const child=options.createElement(vnode.tag)
if (typeof vnode.children==='string') {
child.textContent=vnode.children
} else {
//array
}
// 追加
options.insert(child, parent)
}
?
// 實現(xiàn)render
const render=(vnode, container)=> {
patch(container._vnode || null, vnode, container)
container._vnode=vnode;
}
?
// 該對象就是renderer
return {
render,
createApp: createAppAPI(render)
}
}
?
const renderer=createRenderer({
querySelector(el) {
return document.querySelector(el)
},
createElement(tag) {
return document.createElement(tag)
},
insert(child, parent) {
parent.appendChild(child)
}
})
?
Vue.createApp({
data() {
return {
bar: 'hello,vue3'
}
},
render() {
return {
tag: 'h1',
children: this.bar
}
}
}).mount('#app')
</script>
var activeEffect=null;
function effect(fn) {
activeEffect=fn;
activeEffect();
activeEffect=null;
}
var depsMap=new WeakMap();
function gather(target, key) {
// 避免例如console.log(obj1.name)而觸發(fā)gather
if (!activeEffect) return;
let depMap=depsMap.get(target);
if (!depMap) {
depsMap.set(target, (depMap=new Map()));
}
let dep=depMap.get(key);
if (!dep) {
depMap.set(key, (dep=new Set()));
}
dep.add(activeEffect)
}
function trigger(target, key) {
let depMap=depsMap.get(target);
if (depMap) {
const dep=depMap.get(key);
if (dep) {
dep.forEach((effect)=> effect());
}
}
}
function reactive(target) {
const handle={
set(target, key, value, receiver) {
Reflect.set(target, key, value, receiver);
trigger(receiver, key); // 設(shè)置值時觸發(fā)自動更新
},
get(target, key, receiver) {
gather(receiver, key); // 訪問時收集依賴
return Reflect.get(target, key, receiver);
},
};
return new Proxy(target, handle);
}
?
function ref(name){
return reactive(
{
value: name
}
)
}
與傳統(tǒng) SPA (單頁應(yīng)用程序 (Single-Page Application)) 相比,服務(wù)器端渲染 (SSR) 的優(yōu)勢主要在于:
?
* 更好的 SEO,由于搜索引擎爬蟲抓取工具可以直接查看完全渲染的頁面。
* 更快的內(nèi)容到達時間 (time-to-content),特別是對于緩慢的網(wǎng)絡(luò)情況或運行緩慢的設(shè)備。
?
Vue.js 是構(gòu)建客戶端應(yīng)用程序的框架。默認情況下,可以在瀏覽器中輸出 Vue 組件,進行生成 DOM 和操作 DOM。然而,也可以將同一個組件渲染為服務(wù)器端的 HTML 字符串,將它們直接發(fā)送到瀏覽器,最后將這些靜態(tài)標記"激活"為客戶端上完全可交互的應(yīng)用程序
?
服務(wù)器渲染的 Vue.js 應(yīng)用程序也可以被認為是"同構(gòu)"或"通用",因為應(yīng)用程序的大部分代碼都可以在服務(wù)器和客戶端上運行
?
* Vue SSR是一個在SPA上進行改良的服務(wù)端渲染
* 通過Vue SSR渲染的頁面,需要在客戶端激活才能實現(xiàn)交互
* Vue SSR將包含兩部分:服務(wù)端渲染的首屏,包含交互的SPA
?
使用ssr不存在單例模式,每次用戶請求都會創(chuàng)建一個新的vue實例
實現(xiàn)ssr需要實現(xiàn)服務(wù)端首屏渲染和客戶端激活
服務(wù)端異步獲取數(shù)據(jù)asyncData可以分為首屏異步獲取和切換組件獲取
首屏異步獲取數(shù)據(jù),在服務(wù)端預(yù)渲染的時候就應(yīng)該已經(jīng)完成
切換組件通過mixin混入,在beforeMount鉤子完成數(shù)據(jù)獲取
diff 算法是一種通過同層的樹節(jié)點進行比較的高效算法
?
diff整體策略為:深度優(yōu)先,同層比較
比較只會在同層級進行, 不會跨層級比較
比較的過程中,循環(huán)從兩邊向中間收攏
?
- 當(dāng)數(shù)據(jù)發(fā)生改變時,訂閱者watcher就會調(diào)用patch給真實的DOM打補丁
- 通過isSameVnode進行判斷,相同則調(diào)用patchVnode方法
- patchVnode做了以下操作:
- 找到對應(yīng)的真實dom,稱為el
- 如果都有都有文本節(jié)點且不相等,將el文本節(jié)點設(shè)置為Vnode的文本節(jié)點
- 如果oldVnode有子節(jié)點而VNode沒有,則刪除el子節(jié)點
- 如果oldVnode沒有子節(jié)點而VNode有,則將VNode的子節(jié)點真實化后添加到el
- 如果兩者都有子節(jié)點,則執(zhí)行updateChildren函數(shù)比較子節(jié)點
- updateChildren主要做了以下操作:
- 設(shè)置新舊VNode的頭尾指針
- 新舊頭尾指針進行比較,循環(huán)向中間靠攏,根據(jù)情況調(diào)用patchVnode進行patch重復(fù)流程、調(diào)用createElem創(chuàng)建一個新節(jié)點,從哈希表尋找 key一致的VNode 節(jié)點再分情況操作
* 架子:選用合適的初始化腳手架(`vue-cli2.0`或者`vue-cli3.0`)
* 請求:數(shù)據(jù)`axios`請求的配置
* 登錄:登錄注冊系統(tǒng)
* 路由:路由管理頁面
* 數(shù)據(jù):`vuex`全局數(shù)據(jù)管理
* 權(quán)限:權(quán)限管理系統(tǒng)
* 埋點:埋點系統(tǒng)
* 插件:第三方插件的選取以及引入方式
* 錯誤:錯誤頁面
* 入口:前端資源直接當(dāng)靜態(tài)資源,或者服務(wù)端模板拉取
* `SEO`:如果考慮`SEO`建議采用`SSR`方案
* 組件:基礎(chǔ)組件/業(yè)務(wù)組件
* 樣式:樣式預(yù)處理起,公共樣式抽取
* 方法:公共方法抽離
JavaScript 一共有 8 種數(shù)據(jù)類型,其中有 7 種基本數(shù)據(jù)類型:Undefined、Null、Boolean、Number、String、Symbol(es6 新增,表示獨一無二的值)和 BigInt(es10 新增);
?
1 種引用數(shù)據(jù)類型——Object(Object 本質(zhì)上是由一組無序的名值對組成的)。里面包含 function、Array、Date 等。JavaScript 不支持任何創(chuàng)建自定義類型的機制,而所有值最終都將是上述 8 種數(shù)據(jù)類型之一。
原始數(shù)據(jù)類型:直接存儲在**棧**(stack)中,占據(jù)空間小、大小固定,屬于被頻繁使用數(shù)據(jù),所以放入棧中存儲。
引用數(shù)據(jù)類型:同時存儲在**棧**(stack)和**堆**(heap)中,占據(jù)空間大、大小不固定。引用數(shù)據(jù)類型在棧中存儲了指針,該指針指向堆中該實體的起始地址。當(dāng)解釋器尋找引用值時,會首先檢索其在棧中的地址,取得地址后從堆中獲得實體。
var a=Object.prototype.toString;
console.log(a.call(2));
console.log(a.call(true));
console.log(a.call('str'));
console.log(a.call([]));
console.log(a.call(function(){}));
console.log(a.call({}));
console.log(a.call(undefined));
console.log(a.call(null));https://link.juejin.cn?target=https%3A%2F%2Fsegmentfault.com%2Fa%2F1190000011467723%23articleHeader24 "https://segmentfault.com/a/1190000011467723#articleHeader24")
首先 Undefined 和 Null 都是基本數(shù)據(jù)類型,這兩個基本數(shù)據(jù)類型分別都只有一個值,就是 undefined 和 null。
?
undefined 代表的含義是未定義, null 代表的含義是空對象(其實不是真的對象,請看下面的**注意**!)。一般變量聲明了但還沒有定義的時候會返回 undefined,null 主要用于賦值給一些可能會返回對象的變量,作為初始化。
?
其實 null 不是對象,雖然 typeof null 會輸出 object,但是這只是 JS 存在的一個悠久 Bug。在 JS 的最初版本中使用的是 32 位系統(tǒng),為了性能考慮使用低位存儲變量的類型信息,000 開頭代表是對象,然而 null 表示為全零,所以將它錯誤的判斷為 object 。雖然現(xiàn)在的內(nèi)部類型判斷代碼已經(jīng)改變了,但是對于這個 Bug 卻是一直流傳下來。
?
undefined 在 js 中不是一個保留字,這意味著我們可以使用 undefined 來作為一個變量名,這樣的做法是非常危險的,它 會影響我們對 undefined 值的判斷。但是我們可以通過一些方法獲得安全的 undefined 值,比如說 void 0。
?
當(dāng)我們對兩種類型使用 typeof 進行判斷的時候,Null 類型化會返回 “object”,這是一個歷史遺留的問題。當(dāng)我們使用雙等 號對兩種類型的值進行比較時會返回 true,使用三個等號時會返回 false。
{} 的 valueOf 結(jié)果為 {} ,toString 的結(jié)果為 "[object Object]"
[] 的 valueOf 結(jié)果為 [] ,toString 的結(jié)果為 ""
**作用域:** 作用域是定義變量的區(qū)域,它有一套訪問變量的規(guī)則,這套規(guī)則來管理瀏覽器引擎如何在當(dāng)前作用域以及嵌套的作用域中根據(jù)變量(標識符)進行變量查找。
?
**作用域鏈:** 作用域鏈的作用是保證對執(zhí)行環(huán)境有權(quán)訪問的所有變量和函數(shù)的有序訪問,通過作用域鏈,我們可以訪問到外層環(huán)境的變量和 函數(shù)。
?
作用域鏈的本質(zhì)上是一個指向變量對象的指針列表。變量對象是一個包含了執(zhí)行環(huán)境中所有變量和函數(shù)的對象。作用域鏈的前 端始終都是當(dāng)前執(zhí)行上下文的變量對象。全局執(zhí)行上下文的變量對象(也就是全局對象)始終是作用域鏈的最后一個對象。
?
當(dāng)我們查找一個變量時,如果當(dāng)前執(zhí)行環(huán)境中沒有找到,我們可以沿著作用域鏈向后查找。
?
作用域鏈的創(chuàng)建過程跟執(zhí)行上下文的建立有關(guān)
1. 在瀏覽器里,在全局范圍內(nèi) this 指向 window 對象;
2. 在函數(shù)中,this 永遠指向最后調(diào)用他的那個對象;
3. 構(gòu)造函數(shù)中,this 指向 new 出來的那個新的對象;
4. call、apply、bind 中的 this 被強綁定在指定的那個對象上;
5. 箭頭函數(shù)中 this 比較特殊, 箭頭函數(shù) this 為父作用域的 this,不是調(diào)用時的 this. 要知道前四種方式, 都是調(diào)用時確定, 也就是動態(tài)的, 而箭頭函數(shù)的 this 指向是靜態(tài)的, 聲明的時候就確定了下來;
6. apply、call、bind 都是 js 給函數(shù)內(nèi)置的一些 API,調(diào)用他們可以為函數(shù)指定 this 的執(zhí)行, 同時也可以傳參。
在 js 中我們是使用構(gòu)造函數(shù)來新建一個對象的,每一個構(gòu)造函數(shù)的內(nèi)部都有一個 prototype 屬性值,這個屬性值是一個對 象,這個對象包含了可以由該構(gòu)造函數(shù)的所有實例共享的屬性和方法。當(dāng)我們使用構(gòu)造函數(shù)新建一個對象后,在這個對象的內(nèi)部 將包含一個指針,這個指針指向構(gòu)造函數(shù)的 prototype 屬性對應(yīng)的值,在 ES5 中這個指針被稱為對象的原型。一般來說我們 是不應(yīng)該能夠獲取到這個值的,但是現(xiàn)在瀏覽器中都實現(xiàn)了 **proto** 屬性來讓我們訪問這個屬性,但是我們最好不要使用這 個屬性,因為它不是規(guī)范中規(guī)定的。ES5 中新增了一個 Object.getPrototypeOf() 方法,我們可以通過這個方法來獲取對 象的原型。
當(dāng)我們訪問一個對象的屬性時,如果這個對象內(nèi)部不存在這個屬性,那么它就會去它的原型對象里找這個屬性,這個原型對象又 會有自己的原型,于是就這樣一直找下去,也就是原型鏈的概念。原型鏈的盡頭一般來說都是 Object.prototype 所以這就 是我們新建的對象為什么能夠使用 toString() 等方法的原因。
特點:
JavaScript 對象是通過引用來傳遞的,我們創(chuàng)建的每個新對象實體中并沒有一份屬于自己的原型副本。當(dāng)我們修改原型時,與 之相關(guān)的對象也會繼承這一改變。
參考文章: 《JavaScript 深入理解之原型與原型鏈》
- 能夠訪問其它函數(shù)內(nèi)部變量的函數(shù),稱為閉包
- 能夠訪問自由變量的函數(shù),稱為閉包
?
場景
至于閉包的使用場景,其實在日常開發(fā)中使用到是非常頻繁的
?
- 防抖節(jié)流函數(shù)
- 定時器回調(diào)
- 等就不一一列舉了
?
優(yōu)點
閉包幫我們解決了什么問題呢
**內(nèi)部變量是私有的,可以做到隔離作用域,保持數(shù)據(jù)的不被污染性**
?
缺點
同時閉包也帶來了不小的壞處
**說到了它的優(yōu)點`內(nèi)部變量是私有的,可以做到隔離作用域`,那也就是說垃圾回收機制是無法清理閉包中內(nèi)部變量的,那最后結(jié)果就是內(nèi)存泄漏**
**事件** 是用戶操作網(wǎng)頁時發(fā)生的交互動作或者網(wǎng)頁本身的一些操作,現(xiàn)代瀏覽器一共有三種事件模型。
1. **DOM0 級模型:** ,這種模型不會傳播,所以沒有事件流的概念,但是現(xiàn)在有的瀏覽器支持以冒泡的方式實現(xiàn),它可以在網(wǎng)頁中直接定義監(jiān)聽函數(shù),也可以通過 js 屬性來指定監(jiān)聽函數(shù)。這種方式是所有瀏覽器都兼容的。
2. **IE 事件模型:** 在該事件模型中,一次事件共有兩個過程,事件處理階段,和事件冒泡階段。事件處理階段會首先執(zhí)行目標元素綁定的監(jiān)聽事件。然后是事件冒泡階段,冒泡指的是事件從目標元素冒泡到 document,依次檢查經(jīng)過的節(jié)點是否綁定了事件監(jiān)聽函數(shù),如果有則執(zhí)行。這種模型通過 attachEvent 來添加監(jiān)聽函數(shù),可以添加多個監(jiān)聽函數(shù),會按順序依次執(zhí)行。
3. **DOM2 級事件模型:** 在該事件模型中,一次事件共有三個過程,第一個過程是事件捕獲階段。捕獲指的是事件從 document 一直向下傳播到目標元素,依次檢查經(jīng)過的節(jié)點是否綁定了事件監(jiān)聽函數(shù),如果有則執(zhí)行。后面兩個階段和 IE 事件模型的兩個階段相同。這種事件模型,事件綁定的函數(shù)是 addEventListener,其中第三個參數(shù)可以指定事件是否在捕獲階段執(zhí)行。
js 的加載、解析和執(zhí)行會阻塞頁面的渲染過程,因此我們希望 js 腳本能夠盡可能的延遲加載,提高頁面的渲染速度。
?
1. 將 js 腳本放在文檔的底部,來使 js 腳本盡可能的在最后來加載執(zhí)行。
2. 給 js 腳本添加 defer 屬性,這個屬性會讓腳本的加載與文檔的解析同步解析,然后在文檔解析完成后再執(zhí)行這個腳本文件,這樣的話就能使頁面的渲染不被阻塞。多個設(shè)置了 defer 屬性的腳本按規(guī)范來說最后是順序執(zhí)行的,但是在一些瀏覽器中可能不是這樣。
3. 給 js 腳本添加 async 屬性,這個屬性會使腳本異步加載,不會阻塞頁面的解析過程,但是當(dāng)腳本加載完成后立即執(zhí)行 js 腳本,這個時候如果文檔沒有解析完成的話同樣會阻塞。多個 async 屬性的腳本的執(zhí)行順序是不可預(yù)測的,一般不會按照代碼的順序依次執(zhí)行。
4. 動態(tài)創(chuàng)建 DOM 標簽的方式,我們可以對文檔的加載事件進行監(jiān)聽,當(dāng)文檔加載完成后再動態(tài)的創(chuàng)建 script 標簽來引入 js 腳本。
js 中現(xiàn)在比較成熟的有四種模塊加載方案:
?
* 第一種是 CommonJS 方案,它通過 require 來引入模塊,通過 module.exports 定義模塊的輸出接口。這種模塊加載方案是服務(wù)器端的解決方案,它是以同步的方式來引入模塊的,因為在服務(wù)端文件都存儲在本地磁盤,所以讀取非常快,所以以同步的方式加載沒有問題。但如果是在瀏覽器端,由于模塊的加載是使用網(wǎng)絡(luò)請求,因此使用異步加載的方式更加合適。
* 第二種是 AMD 方案,這種方案采用異步加載的方式來加載模塊,模塊的加載不影響后面語句的執(zhí)行,所有依賴這個模塊的語句都定義在一個回調(diào)函數(shù)里,等到加載完成后再執(zhí)行回調(diào)函數(shù)。require.js 實現(xiàn)了 AMD 規(guī)范。
* 第三種是 CMD 方案,這種方案和 AMD 方案都是為了解決異步模塊加載的問題,sea.js 實現(xiàn)了 CMD 規(guī)范。它和 require.js 的區(qū)別在于模塊定義時對依賴的處理不同和對依賴模塊的執(zhí)行時機的處理不同。
* 第四種方案是 ES6 提出的方案,使用 import 和 export 的形式來導(dǎo)入導(dǎo)出模塊。
它們之間的主要區(qū)別有兩個方面。
?
1. 第一個方面是在模塊定義時對依賴的處理不同。AMD 推崇依賴前置,在定義模塊的時候就要聲明其依賴的模塊。而 CMD 推崇就近依賴,只有在用到某個模塊的時候再去 require。
2. 第二個方面是對依賴模塊的執(zhí)行時機處理不同。首先 AMD 和 CMD 對于模塊的加載方式都是異步加載,不過它們的區(qū)別在于 模塊的執(zhí)行時機,AMD 在依賴模塊加載完成后就直接執(zhí)行依賴模塊,依賴模塊的執(zhí)行順序和我們書寫的順序不一定一致。而 CMD 在依賴模塊加載完成后并不執(zhí)行,只是下載而已,等到所有的依賴模塊都加載好后,進入回調(diào)函數(shù)邏輯,遇到 require 語句 的時候才執(zhí)行對應(yīng)的模塊,這樣模塊的執(zhí)行順序就和我們書寫的順序保持一致了。
?
// CMD
define(function(require, exports, module) {
var a=require("./a");
a.doSomething();
// 此處略去 100 行
var b=require("./b"); // 依賴可以就近書寫
b.doSomething();
// ...
});
?
// AMD 默認推薦
define(["./a", "./b"], function(a, b) {
// 依賴必須一開始就寫好
a.doSomething();
// 此處略去 100 行
b.doSomething();
// ...
});
1、語法上
CommonJS 使用的是 module.exports={} 導(dǎo)出一個模塊對象,require(‘file_path’) 引入模塊對象;
ES6使用的是 export 導(dǎo)出指定數(shù)據(jù), import 引入具體數(shù)據(jù)。
?
2、CommonJS 模塊輸出的是一個值的拷貝,ES6 模塊輸出的是值的引用
?
CommonJS 模塊輸出的是值的拷貝,也就是說,一旦輸出一個值,模塊內(nèi)部的變化就影響不到這個值。
?
ES6 Modules 的運行機制與 CommonJS 不一樣。JS 引擎對腳本靜態(tài)分析的時候,遇到模塊加載命令import,就會生成一個只讀引用。等到腳本真正執(zhí)行時,再根據(jù)這個只讀引用,到被加載的那個模塊里面去取值。換句話說,ES6的import 有點像 Unix 系統(tǒng)的“符號連接”,原始值變了,import加載的值也會跟著變。因此,ES6模塊是動態(tài)引用,并且不會緩存值,模塊里面的變量綁定其所在的模塊。
?
3、CommonJS 模塊是運行時加載,ES6 模塊是編譯時加載
?
運行時加載: CommonJS 模塊就是對象;即在輸入時是先加載整個模塊,生成一個對象,然后再從這個對象上面讀取方法,這種加載稱為“運行時加載”。
?
編譯時加載: ES6 模塊不是對象,而是通過 export 命令顯式指定輸出的代碼,import時采用靜態(tài)命令的形式。即在import時可以指定加載某個輸出值,而不是加載整個模塊,這種加載稱為“編譯時加載”
?
PS:CommonJS 加載的是一個對象(即module.exports屬性),該對象只有在腳本運行完才會生成。而 ES6 模塊不是對象,它的對外接口只是一種靜態(tài)定義,在代碼靜態(tài)解析階段就會生成
異步任務(wù)分類:宏任務(wù),微任務(wù)
同步任務(wù)和異步任務(wù)分別進入不同的執(zhí)行"場所"
先執(zhí)行主線程執(zhí)行棧中的宏任務(wù)
執(zhí)行過程中如果遇到微任務(wù),進入Event Table并注冊函數(shù),完成后移入到微任務(wù)的任務(wù)隊列中
宏任務(wù)執(zhí)行完畢后,立即執(zhí)行當(dāng)前微任務(wù)隊列中的所有微任務(wù)(依次執(zhí)行)
主線程會不斷獲取任務(wù)隊列中的任務(wù)、執(zhí)行任務(wù)、再獲取、再執(zhí)行任務(wù)也就是常說的Event Loop(事件循環(huán))。
v8 的垃圾回收機制基于分代回收機制,這個機制又基于世代假說,這個假說有兩個特點,一是新生的對象容易早死,另一個是不死的對象會活得更久。基于這個假說,v8 引擎將內(nèi)存分為了新生代和老生代。
?
新創(chuàng)建的對象或者只經(jīng)歷過一次的垃圾回收的對象被稱為新生代。經(jīng)歷過多次垃圾回收的對象被稱為老生代。
?
新生代被分為 From 和 To 兩個空間,To 一般是閑置的。當(dāng) From 空間滿了的時候會執(zhí)行 Scavenge 算法進行垃圾回收。當(dāng)我們執(zhí)行垃圾回收算法的時候應(yīng)用邏輯將會停止,等垃圾回收結(jié)束后再繼續(xù)執(zhí)行。這個算法分為三步:
?
(1)首先檢查 From 空間的存活對象,如果對象存活則判斷對象是否滿足晉升到老生代的條件,如果滿足條件則晉升到老生代。如果不滿足條件則移動 To 空間。
?
(2)如果對象不存活,則釋放對象的空間。
?
(3)最后將 From 空間和 To 空間角色進行交換。
?
新生代對象晉升到老生代有兩個條件:
?
(1)第一個是判斷是對象否已經(jīng)經(jīng)過一次 Scavenge 回收。若經(jīng)歷過,則將對象從 From 空間復(fù)制到老生代中;若沒有經(jīng)歷,則復(fù)制到 To 空間。
?
(2)第二個是 To 空間的內(nèi)存使用占比是否超過限制。當(dāng)對象從 From 空間復(fù)制到 To 空間時,若 To 空間使用超過 25%,則對象直接晉升到老生代中。設(shè)置 25% 的原因主要是因為算法結(jié)束后,兩個空間結(jié)束后會交換位置,如果 To 空間的內(nèi)存太小,會影響后續(xù)的內(nèi)存分配。
?
老生代采用了標記清除法和標記壓縮法。標記清除法首先會對內(nèi)存中存活的對象進行標記,標記結(jié)束后清除掉那些沒有標記的對象。由于標記清除后會造成很多的內(nèi)存碎片,不便于后面的內(nèi)存分配。所以了解決內(nèi)存碎片的問題引入了標記壓縮法。
?
由于在進行垃圾回收的時候會暫停應(yīng)用的邏輯,對于新生代方法由于內(nèi)存小,每次停頓的時間不會太長,但對于老生代來說每次垃圾回收的時間長,停頓會造成很大的影響。 為了解決這個問題 V8 引入了增量標記的方法,將一次停頓進行的過程分為了多步,每次執(zhí)行完一小步就讓運行邏輯執(zhí)行一會,就這樣交替運行。
相關(guān)資料:
《深入理解 V8 的垃圾回收原理》
《JavaScript 中的垃圾回收》
* 1. 意外的全局變量
* 2. 被遺忘的計時器或回調(diào)函數(shù)
* 3. 脫離 DOM 的引用
* 4. 閉包
* 塊作用域
* 類
* 箭頭函數(shù)
* 模板字符串
* 加強的對象字面
* 對象解構(gòu)
* Promise
* 模塊
* Symbol
* 代理(proxy)Set
* 函數(shù)默認參數(shù)
* 展開
//ES5 Version
var getCurrentDate=function (){
return new Date();
}
?
//ES6 Version
const getCurrentDate=()=> new Date();
?
箭頭函數(shù)表達式的語法比函數(shù)表達式更簡潔,并且沒有自己的`this,arguments,super或new.target`。箭頭函數(shù)表達式更適用于那些本來需要匿名函數(shù)的地方,并且它不能用作構(gòu)造函數(shù)。
?
箭頭函數(shù)沒有自己的 this 值。它捕獲詞法作用域函數(shù)的 this 值,如果我們在全局作用域聲明箭頭函數(shù),則 this 值為 window 對象。
高階函數(shù)只是將函數(shù)作為參數(shù)或返回值的函數(shù)。
?
function higherOrderFunction(param,callback){
return callback(param);
}
實現(xiàn)步驟:
Function.prototype.mu_call=function (context, ...args) {
//obj不存在指向window
if (!context || context===null) {
context=window;
}
// 創(chuàng)造唯一的key值 作為我們構(gòu)造的context內(nèi)部方法名
let fn=Symbol();
?
//this指向調(diào)用call的函數(shù)
context[fn]=this;
?
// 執(zhí)行函數(shù)并返回結(jié)果 相當(dāng)于把自身作為傳入的context的方法進行調(diào)用了
return context[fn](...args);
};
?
// 測試
var value=2;
var obj1={
value: 1,
};
function bar(name, age) {
var myObj={
name: name,
age: age,
value: this.value,
};
console.log(this.value, myObj);
}
bar.mu_call(null); //打印 2 {name: undefined, age: undefined, value: 2}
bar.mu_call(obj1, 'tom', '110'); // 打印 1 {name: "tom", age: "110", value: 1}
實現(xiàn)步驟:
Function.prototype.mu_apply=function (context, args) {
//obj不存在指向window
if (!context || context===null) {
context=Window;
}
// 創(chuàng)造唯一的key值 作為我們構(gòu)造的context內(nèi)部方法名
let fn=Symbol();
?
//this指向調(diào)用call的函數(shù)
context[fn]=this;
?
// 執(zhí)行函數(shù)并返回結(jié)果 相當(dāng)于把自身作為傳入的context的方法進行調(diào)用了
return context[fn](...args);
};
?
// 測試
var value=2;
var obj1={
value: 1,
};
function bar(name, age) {
var myObj={
name: name,
age: age,
value: this.value,
};
console.log(this.value, myObj);
}
bar.mu_apply(obj1, ["tom", "110"]); // 打印 1 {name: "tom", age: "110", value: 1}
Function.prototype.mu_bind=function (context, ...args) {
if (!context || context===null) {
context=window;
}
// 創(chuàng)造唯一的key值 作為我們構(gòu)造的context內(nèi)部方法名
let fn=Symbol();
context[fn]=this;
let _this=this;
// bind情況要復(fù)雜一點
const result=function (...innerArgs) {
// 第一種情況 :若是將 bind 綁定之后的函數(shù)當(dāng)作構(gòu)造函數(shù),通過 new 操作符使用,則不綁定傳入的 this,而是將 this 指向?qū)嵗鰜淼膶ο?
// 此時由于new操作符作用 this指向result實例對象 而result又繼承自傳入的_this 根據(jù)原型鏈知識可得出以下結(jié)論
// this.__proto__===result.prototype //this instanceof result=>true
// this.__proto__.__proto__===result.prototype.__proto__===_this.prototype; //this instanceof _this=>true
if (this instanceof _this===true) {
// 此時this指向指向result的實例 這時候不需要改變this指向
this[fn]=_this;
this[fn](...[...args, ...innerArgs]); //這里使用es6的方法讓bind支持參數(shù)合并
delete this[fn];
} else {
// 如果只是作為普通函數(shù)調(diào)用 那就很簡單了 直接改變this指向為傳入的context
context[fn](...[...args, ...innerArgs]);
delete context[fn];
}
};
// 如果綁定的是構(gòu)造函數(shù) 那么需要繼承構(gòu)造函數(shù)原型屬性和方法
// 實現(xiàn)繼承的方式: 使用Object.create
result.prototype=Object.create(this.prototype);
return result;
};
function Person(name, age) {
console.log(name); //'我是參數(shù)傳進來的name'
console.log(age); //'我是參數(shù)傳進來的age'
console.log(this); //構(gòu)造函數(shù)this指向?qū)嵗龑ο?
}
// 構(gòu)造函數(shù)原型的方法
Person.prototype.say=function () {
console.log(123);
};
?
// 普通函數(shù)
function normalFun(name, age) {
console.log(name); //'我是參數(shù)傳進來的name'
console.log(age); //'我是參數(shù)傳進來的age'
console.log(this); //普通函數(shù)this指向綁定bind的第一個參數(shù) 也就是例子中的obj
console.log(this.objName); //'我是obj傳進來的name'
console.log(this.objAge); //'我是obj傳進來的age'
}
?
let obj={
objName: '我是obj傳進來的name',
objAge: '我是obj傳進來的age',
};
?
// 先測試作為構(gòu)造函數(shù)調(diào)用
// let bindFun=Person.mu_bind(obj, '我是參數(shù)傳進來的name');
// let a=new bindFun('我是參數(shù)傳進來的age');
// a.say(); //123
?
// 再測試作為普通函數(shù)調(diào)用a;
let bindFun=normalFun.mu_bind(obj, '我是參數(shù)傳進來的name');
bindFun('我是參數(shù)傳進來的age');
參考文章: 高頻JavaScript手寫面試題,你“行”嗎
// 函數(shù)柯里化指的是一種將使用多個參數(shù)的一個函數(shù)轉(zhuǎn)換成一系列使用一個參數(shù)的函數(shù)的技術(shù)。
?
function curry(fn, args) {
// 獲取函數(shù)需要的參數(shù)長度
let length=fn.length;
?
args=args || [];
?
return function() {
let subArgs=args.slice(0);
?
// 拼接得到現(xiàn)有的所有參數(shù)
for (let i=0; i < arguments.length; i++) {
subArgs.push(arguments[i]);
}
?
// 判斷參數(shù)的長度是否已經(jīng)滿足函數(shù)所需參數(shù)的長度
if (subArgs.length >=length) {
// 如果滿足,執(zhí)行函數(shù)
return fn.apply(this, subArgs);
} else {
// 如果不滿足,遞歸返回科里化的函數(shù),等待參數(shù)的傳入
return curry.call(this, fn, subArgs);
}
};
}
?
// es6 實現(xiàn)
function curry(fn, ...args) {
return fn.length <=args.length ? fn(...args) : curry.bind(null, fn, ...args);
}
參考文章: 《JavaScript 專題之函數(shù)柯里化》
首先需要了解new做了什么事情:
// 代碼實現(xiàn)
function mu_new(fn,...arg){
// 首先創(chuàng)建空對象
const obj={};
// 將空對象的原型proto指向構(gòu)造函數(shù)的原型prototype
Object.setPrototypeOf(obj, fn.prototype)
// 將this指向新創(chuàng)建的對象,并且執(zhí)行構(gòu)造函數(shù)
const result=fn.apply(obj,arg);
// 執(zhí)行結(jié)果有返回值并且是一個對象,返回執(zhí)行的結(jié)果,否側(cè)返回新創(chuàng)建的對象
return result instanceof Object ? result : obj;
}
// 驗證mu_new函數(shù)
function Dog(name){
this.name=name;
this.say=function(){
console.log('my name is' + this.name);
}
}
const dog=mu_new(Dog, "傻");
dog.say() //my name is傻
Promise 是異步編程的一種解決方案,比傳統(tǒng)的解決方案——回調(diào)函數(shù)和事件——更合理和更強大。它由社區(qū)最早提出和實現(xiàn),ES6 將其寫進了語言標準,統(tǒng)一了用法,原生提供了`Promise`對象。
所謂`Promise`,簡單說就是一個容器,里面保存著某個未來才會結(jié)束的事件(通常是一個異步操作)的結(jié)果。從語法上說,Promise 是一個對象,從它可以獲取異步操作的消息。Promise 提供統(tǒng)一的 API,各種異步操作都可以用同樣的方法進行處理。
**那我們來看看我們所熟知的`Promise`的基本原理**
+ 首先我們在調(diào)用Promise時,會返回一個Promise對象。
+ 構(gòu)建Promise對象時,需要傳入一個executor函數(shù),Promise的主要業(yè)務(wù)流程都在executor函數(shù)中執(zhí)行。
+ 如果運行在excutor函數(shù)中的業(yè)務(wù)執(zhí)行成功了,會調(diào)用resolve函數(shù);如果執(zhí)行失敗了,則調(diào)用reject函數(shù)。
+ Promise的狀態(tài)不可逆,同時調(diào)用resolve函數(shù)和reject函數(shù),默認會采取第一次調(diào)用的結(jié)果。
**結(jié)合Promise/A+規(guī)范,我們還可以分析出哪些基本特征**
Promise/A+的規(guī)范比較多,在這列出一下核心的規(guī)范。[Promise/A+規(guī)范](https://link.juejin.cn/?target=https%3A%2F%2Fpromisesaplus.com%2F)
+ promise有三個狀態(tài):pending,fulfilled,rejected,默認狀態(tài)是pending。
+ promise有一個value保存成功狀態(tài)的值,有一個reason保存失敗狀態(tài)的值,可以是undefined/thenable/promise。
+ promise只能從pending到rejected, 或者從pending到fulfilled,狀態(tài)一旦確認,就不會再改變。
+ promise 必須有一個then方法,then接收兩個參數(shù),分別是promise成功的回調(diào)onFulfilled, 和promise失敗的回調(diào)onRejected。
+ 如果then中拋出了異常,那么就會把這個異常作為參數(shù),傳遞給下一個then的失敗的回調(diào)onRejected。
那`CustomPromise`,還實現(xiàn)不了基本原理的3,4兩條,那我們來根據(jù)基本原理與Promise/A+分析下,還缺少什么
- promise有三個狀態(tài):pending,fulfilled,rejected。
- executor執(zhí)行器調(diào)用reject與resolve兩個方法
- 還需要有保存成功或失敗兩個值的變量
- then接收兩個參數(shù),分別是成功的回調(diào)onFulfilled,失敗的回調(diào)onRejected
手寫實現(xiàn)promise
1、Async—聲明一個異步函數(shù)
- 自動將常規(guī)函數(shù)轉(zhuǎn)換成Promise,返回值也是一個Promise對象
- 只有async函數(shù)內(nèi)部的異步操作執(zhí)行完,才會執(zhí)行then方法指定的回調(diào)函數(shù)
- 異步函數(shù)內(nèi)部可以使用await
2、Await—暫停異步的功能執(zhí)行(var result=await someAsyncCall();)
- 放置在Promise調(diào)用之前,await強制其他代碼等待,直到Promise完成并返回結(jié)果
- 只能與Promise一起使用,不適用與回調(diào)
- 只能在async函數(shù)內(nèi)部使用
手寫實現(xiàn)async
優(yōu)缺點:
實現(xiàn)步驟:
// 傳入?yún)?shù)左側(cè)為實例L, 右側(cè)為構(gòu)造函數(shù)R
function mu_instanceof(L,R){
// 處理邊界:檢測實例類型是否為原始類型
const baseTypes=['string','number','boolean','symbol','undefined'];
?
if(baseTypes.includes(typeof L) || L===null) return false;
?
// 分別取傳入?yún)?shù)的原型
let Lp=L.__proto__;
let Rp=R.prototype; // 函數(shù)才擁有prototype屬性
?
// 判斷原型
while(true){
if(Lp===null) return false;
if(Lp===Rp) return true;
Lp=Lp.__proto__;
}
}
?
// 驗證
const isArray=mu_instanceof([],Array);
console.log(isArray); //true
const isDate=mu_instanceof('2023-01-09',Date);
console.log(isDate); // false
函數(shù)防抖是在事件被觸發(fā)n秒后再執(zhí)行回調(diào),如果在「n秒內(nèi)又被觸發(fā)」,則「重新計時」
function debounce(fn, wait) {
let timer=null;
return function () {
if (timer !=null) {
clearTimeout(timer);
}
timer=setTimeout(()=> {
fn();
}, wait);
};
}
// 測試
function handle() {
console.log(Math.random());
}
// 窗口大小改變,觸發(fā)防抖,執(zhí)行handle
window.addEventListener('resize', debounce(handle, 1000));
當(dāng)事件觸發(fā)時,保證一定時間段內(nèi)只調(diào)用一次函數(shù)。例如頁面滾動的時候,每隔一段時間發(fā)一次請求
實現(xiàn)步驟:
function throttle(fn, wait, ...args) {
var pre=Date.now();
return function () {
// 函數(shù)可能會有入?yún)?
var context=this;
var now=Date.now();
if (now - pre >=wait) {
// 將執(zhí)行函數(shù)的this指向當(dāng)前作用域
fn.apply(context, args);
pre=Date.now();
}
};
}
?
// 測試
var name='mu';
function handle(val) {
console.log(val + this.name);
}
// 滾動鼠標,觸發(fā)防抖,執(zhí)行handle
window.addEventListener('scroll', throttle(handle, 1000, '木由'));
- `HTML`:超文本標記語言,是語法較為松散的、不嚴格的`Web`語言;
- `XML`:可擴展的標記語言,主要用于存儲數(shù)據(jù)和結(jié)構(gòu),可擴展;
- `XHTML`:可擴展的超文本標記語言,基于`XML`,作用與`HTML`類似,但語法更嚴格。
XHTML與HTML的區(qū)別
?
- `XHTML`標簽名必須小寫;
- `XHTML`元素必須被關(guān)閉;
- `XHTML`元素必須被正確的嵌套;
- `XHTML`元素必須要有根元素。
?
XHTML與HTML5的區(qū)別
?
- `HTML5`新增了`canvas`繪畫元素;
- `HTML5`新增了用于繪媒介回放的`video`和`audio`元素;
- 更具語義化的標簽,便于瀏覽器識別;
- 對本地離線存儲有更好的支持;
- `MATHML`,`SVG`等,可以更好的`render`;
- 添加了新的表單控件:`calendar`、`date`、`time`、`email`等。
?
HTML、XHTML、HTML5之間聯(lián)系
?
- `XHTML`是`HTML`規(guī)范版本;
- `HTML5`是`HTML`、`XHTML`以及`HTML DOM`的新標準。
- 行內(nèi)元素: `a`, `b`, `span`, `img`, `input`, `select`, `strong`;
- 塊級元素: `div`, `ul`, `li`, `dl`, `dt`, `dd`, `h1-5`, `p`等;
- 空元素: `<br>`, `<hr>`, `<img>`, `<link>`, `<meta>`;
- `link`屬于`HTML`標簽,而`@import`是`css`提供的;
- 頁面被加載時,`link`會同時被加載,而`@import`引用的css會等到頁面被加載完再加載;
- `@import`只在`IE5`以上才能識別,而`link`是`XHTML`標簽,無兼容問題;
- `link`方式的樣式的權(quán)重高于`@import`的權(quán)重。
概念
?
語義化是指根據(jù)內(nèi)容的結(jié)構(gòu)化(內(nèi)容語義化),選擇合適的標簽(代碼語義化),便于開發(fā)者閱讀和寫出更優(yōu)雅的代碼的同時,讓瀏覽器的爬蟲和機器很好的解析。
?
語義化的好處
?
- 用正確的標簽做正確的事情;
- 去掉或者丟失樣式的時候能夠讓頁面呈現(xiàn)出清晰的結(jié)構(gòu);
- 方便其他設(shè)備解析(如屏幕閱讀器、盲人閱讀器、移動設(shè)備)以意義的方式來渲染網(wǎng)頁;
- 有利于`SEO`:和搜索引擎建立良好溝通,有助于爬蟲抓取更多的有效信息:爬蟲依賴于標簽來確定上下文和各個關(guān)鍵字的權(quán)重;
- 便于團隊開發(fā)和維護,語義化更具可讀性,遵循W3C標準的團隊都遵循這個標準,可以減少差異化。
- `property`是`DOM`中的屬性,是`JavaScript`里的對象;
- `attribute`是`HTML`標簽上的特性,它的值只能夠是字符串;
?
簡單的理解就是:`Attribute`就是`DOM`節(jié)點自帶的屬性,例如`html`中常用的`id`、`class`、`title`、`align`等;而`Property`是這個`DOM`元素作為對象,其附加的內(nèi)容,例如`childNodes`、`firstChild`等。
新特性
?
**HTML5 現(xiàn)在已經(jīng)不是 SGML 的子集,主要是關(guān)于圖像,位置,存儲,多任務(wù)等功能的增加。**
?
- 拖拽釋放`(Drag and drop)` `API`;
- 語義化更好的內(nèi)容標簽(`header`, `nav`, `footer`, `aside`, `article`, `section`);
- 音頻、視頻API(`audio`, `video`);
- 畫布`(Canvas)` `API`;
- 地理`(Geolocation)` `API`;
- 本地離線存儲 `localStorage` 長期存儲數(shù)據(jù),瀏覽器關(guān)閉后數(shù)據(jù)不丟失;
- `sessionStorage` 的數(shù)據(jù)在瀏覽器關(guān)閉后自動刪除;
- 表單控件:`calendar`、`date`、`time`、`email`、`url`、`search` ;
- 新的技術(shù)`webworker`, `websocket`, `Geolocation`等;
?
移除元素
?
**純表現(xiàn)元素**:
?
- `<basefont>` 默認字體,不設(shè)置字體,以此渲染;
- `<font>` 字體標簽;
- `<center>` 水平居中;
- `<u>` 下劃線;
- `<big>`字體;
- `<strike>`中橫字;
- `<tt>`文本等寬;
?
**對可用性產(chǎn)生負面影響的元素**:
?
`<frameset>`,`<noframes>`和`<frame>`;
結(jié)構(gòu),樣式和行為分離
?
若是將前端比作一個人來舉例子,結(jié)構(gòu)(`HTML`)就相當(dāng)于是人體的“骨架”,樣式就相當(dāng)于人體的“裝飾”,例如衣服,首飾等;行為就相當(dāng)于人做出的一系列“動作”。
?
在結(jié)構(gòu),樣式和行為分離,就是將三者分離開,各自負責(zé)各自的內(nèi)容,各部分可以通過引用進行使用。
?
在分離的基礎(chǔ)上,我們需要做到代碼的:**精簡**, **重用**, **有序**。
?
分離的好處
?
- 代碼分離,利于團隊的開發(fā)和后期的維護;
- 減少維護成本,提高可讀性和更好的兼容性;
- 文件合并(目的是減少`http`請求);
- 文件壓縮 (目的是直接減少文件下載的體積);
- 使用緩存;
- 使用`cdn`托管資源;
- `gizp`壓縮需要的js和css文件;
- 反向鏈接,網(wǎng)站外鏈接優(yōu)化;
- meta標簽優(yōu)化(`title`, `description`, `keywords`),`heading`標簽的優(yōu)化,`alt`優(yōu)化;
`HTML5`的`Web storage`的存儲方式有兩種:`sessionStorage`和`localStorage`。
?
- `sessionStorage`用于本地存儲一個會話中的數(shù)據(jù),當(dāng)會話結(jié)束后就會銷毀;
- 和`sessionStorage`不同,`localStorage`用于持久化的本地存儲,除非用戶主動刪除數(shù)據(jù),否則數(shù)據(jù)永遠不會過期;
- `cookie`是網(wǎng)站為了標示用戶身份而儲存在用戶本地終端(`Client Side`)上的數(shù)據(jù)(通常經(jīng)過加密)。
?
**區(qū)別**:
?
- **從瀏覽器和服務(wù)器間的傳遞看**: `cookie`數(shù)據(jù)始終在同源的http請求中攜帶(即使不需要),即`cookie`在瀏覽器和服務(wù)器間來回傳遞;而`sessionStorage`和`localStorage`不會自動把數(shù)據(jù)發(fā)給服務(wù)器,僅在本地保存。
- **從大小看**: 存儲大小限制不同,`cookie`數(shù)據(jù)不能超過`4k`,只適合保存很小的數(shù)據(jù);而`sessionStorage`和`localStorage` 雖然也有存儲大小的限制,但比`cookie`大得多,可以達到5M或更大。
- **從數(shù)據(jù)有效期看**: `sessionStorage`在會話關(guān)閉會立刻關(guān)閉,因此持續(xù)性不久;`cookie`只在設(shè)置的cookie過期時間之前一直有效,即使窗口或瀏覽器關(guān)閉。而`localStorage`始終有效。
- **從作用域看**: `sessionStorage`不在不同的瀏覽器窗口中共享,即使是同一個頁面;而`localStorage`和`cookie`都是可以在所有的同源窗口中共享的。
- `Trident`內(nèi)核:IE最先開發(fā)或使用的, 360瀏覽器;
- `Webkit`內(nèi)核:Google Chrome,Safari, 搜狗瀏覽器,360極速瀏覽器, 阿里云瀏覽器等;
- `Gecko`內(nèi)核: Mozilla FireFox (火狐瀏覽器) ,K-Meleon瀏覽器;
- `Presto`內(nèi)核:Opera瀏覽器;
`localStorage`本地存儲相當(dāng)于一個輕量級的數(shù)據(jù)庫,可以在本地永久的儲存數(shù)據(jù)(除非人為刪除)。此外,還可以在斷網(wǎng)情況下讀取本地緩存的`cookies`。
?
- 使用`localStorage`保存數(shù)據(jù): `localStorage.setItem(key, value)`;
- 使用`localStorage`獲取保存的數(shù)據(jù): `localStorage.getItem(key)`;
- 清除`localStorage`保存的數(shù)據(jù): `localStorage.removeItem(key)`;
- 清除全部`localStorage`對象保存的數(shù)據(jù): `localStorage.clear( )`;
- `CDN`緩存更加方便;
- 突破瀏覽器并發(fā)限制;
- 節(jié)約`cookie`寬帶;
- 節(jié)約主域名的連接數(shù),優(yōu)化頁面下響應(yīng)速度;
- 防止不必要的安全問題;
全稱 Domain Name System , 即域名系統(tǒng)。
> 萬維網(wǎng)上作為域名和 IP 地址相互映射的一個分布式數(shù)據(jù)庫,能夠使用戶更方便的訪問互聯(lián)網(wǎng),而不用去記住能夠被機器直接讀取的 IP 數(shù)串。DNS 協(xié)議運行在 UDP 協(xié)議之上,使用端口號 53。
簡單的說, 通過域名, 最終得到該域名對應(yīng)的 IP 地址的過程叫做域名解析(或主機名解析)。
```text
www.zuofc.com (域名) - DNS解析 -> 111.222.33.444 (IP地址)
```
有 dns 的地方, 就有緩存。瀏覽器、操作系統(tǒng)、Local DNS、根域名服務(wù)器,它們都會對 DNS 結(jié)果做一定程度的緩存。
DNS 查詢過程如下:
1. 首先搜索瀏覽器自身的 DNS 緩存, 如果存在,則域名解析到此完成。
2. 如果瀏覽器自身的緩存里面沒有找到對應(yīng)的條目,那么會嘗試讀取操作系統(tǒng)的 hosts 文件看是否存在對應(yīng)的映射關(guān)系, 如果存在,則域名解析到此完成。
3. 如果本地 hosts 文件不存在映射關(guān)系,則查找本地 DNS 服務(wù)器 (ISP 服務(wù)器, 或者自己手動設(shè)置的 DNS 服務(wù)器), 如果存在, 域名到此解析完成。
4. 如果本地 DNS 服務(wù)器還沒找到的話, 它就會向根服務(wù)器發(fā)出請求, 進行遞歸查詢。
強緩存
瀏覽器在加載資源時,會先根據(jù)本地緩存資源的 header 中的信息判斷是否命中強緩存,如果命中則直接使用緩存中的資源不會再向服務(wù)器發(fā)送請求。
?
這里的 header 中的信息指的是 expires 和 cahe-control.
?
Expires
該字段是 http1.0 時的規(guī)范,它的值為一個絕對時間的 GMT 格式的時間字符串,比如 Expires:Mon,18 Oct 2066 23:59:59 GMT。這個時間代表著這個資源的失效時間,在此時間之前,即命中緩存。這種方式有一個明顯的缺點,由于失效時間是一個絕對時間,所以當(dāng)服務(wù)器與客戶端時間偏差較大時,就會導(dǎo)致緩存混亂(本地時間也可以隨便更改)。
?
Cache-Control(優(yōu)先級高于 Expires)
Cache-Control 是 http1.1 時出現(xiàn)的 header 信息,主要是利用該字段的 max-age 值來進行判斷,它是一個相對時間,例如 Cache-Control:max-age=3600,代表著資源的有效期是 3600 秒。cache-control 除了該字段外,還有下面幾個比較常用的設(shè)置值:
?
no-cache:需要進行協(xié)商緩存,發(fā)送請求到服務(wù)器確認是否使用緩存。
no-store:禁止使用緩存,每一次都要重新請求數(shù)據(jù)。
public:可以被所有的用戶緩存,包括終端用戶和 CDN 等中間代理服務(wù)器。
private:只能被終端用戶的瀏覽器緩存,不允許 CDN 等中繼緩存服務(wù)器對其緩存。
Cache-Control 與 Expires 可以在服務(wù)端配置同時啟用,同時啟用的時候 Cache-Control 優(yōu)先級高。
強緩存
瀏覽器在加載資源時,會先根據(jù)本地緩存資源的 header 中的信息判斷是否命中強緩存,如果命中則直接使用緩存中的資源不會再向服務(wù)器發(fā)送請求。
?
這里的 header 中的信息指的是 expires 和 cahe-control.
?
Expires
該字段是 http1.0 時的規(guī)范,它的值為一個絕對時間的 GMT 格式的時間字符串,比如 Expires:Mon,18 Oct 2066 23:59:59 GMT。這個時間代表著這個資源的失效時間,在此時間之前,即命中緩存。這種方式有一個明顯的缺點,由于失效時間是一個絕對時間,所以當(dāng)服務(wù)器與客戶端時間偏差較大時,就會導(dǎo)致緩存混亂(本地時間也可以隨便更改)。
?
Cache-Control(優(yōu)先級高于 Expires)
Cache-Control 是 http1.1 時出現(xiàn)的 header 信息,主要是利用該字段的 max-age 值來進行判斷,它是一個相對時間,例如 Cache-Control:max-age=3600,代表著資源的有效期是 3600 秒。cache-control 除了該字段外,還有下面幾個比較常用的設(shè)置值:
?
no-cache:需要進行協(xié)商緩存,發(fā)送請求到服務(wù)器確認是否使用緩存。
no-store:禁止使用緩存,每一次都要重新請求數(shù)據(jù)。
public:可以被所有的用戶緩存,包括終端用戶和 CDN 等中間代理服務(wù)器。
private:只能被終端用戶的瀏覽器緩存,不允許 CDN 等中繼緩存服務(wù)器對其緩存。
Cache-Control 與 Expires 可以在服務(wù)端配置同時啟用,同時啟用的時候 Cache-Control 優(yōu)先級高。
當(dāng)強緩存沒有命中的時候,瀏覽器會發(fā)送一個請求到服務(wù)器,服務(wù)器根據(jù) header 中的部分信息來判斷是否命中緩存。如果命中,則返回 304 ,告訴瀏覽器資源未更新,可使用本地的緩存。
這里的 header 中的信息指的是 Last-Modify/If-Modify-Since 和 ETag/If-None-Match.
Last-Modify/If-Modify-Since
瀏覽器第一次請求一個資源的時候,服務(wù)器返回的 header 中會加上 Last-Modify,Last-modify 是一個時間標識該資源的最后修改時間(只能精確到秒,所以間隔時間小于 1 秒的請求是檢測不到文件更改的。)。
當(dāng)瀏覽器再次請求該資源時,request 的請求頭中會包含 If-Modify-Since,該值為緩存之前返回的 Last-Modify。服務(wù)器收到 If-Modify-Since 后,根據(jù)資源的最后修改時間判斷是否命中緩存。
如果命中緩存,則返回 304,并且不會返回資源內(nèi)容,并且不會返回 Last-Modify。
缺點:
短時間內(nèi)資源發(fā)生了改變,Last-Modified 并不會發(fā)生變化。
周期性變化。如果這個資源在一個周期內(nèi)修改回原來的樣子了,我們認為是可以使用緩存的,但是 Last-Modified 可不這樣認為, 因此便有了 ETag。
ETag/If-None-Match
Etag 是基于文件內(nèi)容進行編碼的,可以保證如果服務(wù)器有更新,一定會重新請求資源,但是編碼需要付出額外的開銷。
與 Last-Modify/If-Modify-Since 不同的是,Etag/If-None-Match 返回的是一個校驗碼。ETag 可以保證每一個資源是唯一的,資源變化都會導(dǎo)致 ETag 變化。服務(wù)器根據(jù)瀏覽器上送的 If-None-Match 值來判斷是否命中緩存。
與 Last-Modified 不一樣的是,當(dāng)服務(wù)器返回 304 Not Modified 的響應(yīng)時,由于 ETag 重新生成過,response header 中還會把這個 ETag 返回,即使這個 ETag 跟之前的沒有變化。
Last-Modified 與 ETag 是可以一起使用的,服務(wù)器會優(yōu)先驗證 ETag,一致的情況下,才會繼續(xù)比對 Last-Modified,最后才決定是否返回 304。
最新的 Chrome 瀏覽器包括至少四個: 1 個瀏覽器(Browser)主進程、1 個 GPU 進程、1 個網(wǎng)絡(luò)(NetWork)進程、多個渲染進程和多個插件進程, 當(dāng)然還有復(fù)雜的情況;
頁面中有 iframe 的話, iframe 會單獨在進程中
有插件的話,插件也會開啟進程
多個頁面屬于同一站點,并且從 a 打開 b 頁面,會共用一個渲染進程
裝了擴展的話,擴展也會占用進程
這些進程都可以通過 Chrome 任務(wù)管理器來查看
提供一種情況,就是同一站點, 圍繞這個展開也行。
Chrome 的默認策略是,每個標簽對應(yīng)一個渲染進程。但是如果從一個頁面打開了新頁面,而新頁面和當(dāng)前頁面屬于同一站點時,那么新頁面會復(fù)用父頁面的渲染進程。官方把這個默認策略叫 process-per-site-instance。
更加簡單的來說,就是如果多個頁面符合同一站點,這幾個頁面會分配到一個渲染進程中去, 所以有這樣子的一種情況, 一個頁面崩潰了,會導(dǎo)致同一個站點的其他頁面也奔潰,這是因為它們使用的是同一個渲染進程。
有人會問為什么會跑到一個進程里面呢?
你想一想呀, 屬于同一家的站點,比如下面三個:
https://time.geekbang.org
https://www.geekbang.org
https://www.geekbang.org:8080
它們在一個渲染進程中的話,它們就會共享 JS 執(zhí)行環(huán)境,也就是 A 頁面可以直接在 B 頁面中執(zhí)行腳本了, 有些時候就是有這樣子的需求嘛。
**三次握手**
第一次握手
客戶端向服務(wù)端發(fā)送連接請求報文段。該報文段的頭部中 SYN=1,ACK=0,seq=x。請求發(fā)送后,客戶端便進入 SYN-SENT 狀態(tài)。
PS1:SYN=1,ACK=0 表示該報文段為連接請求報文。
PS2:x 為本次 TCP 通信的字節(jié)流的初始序號。
TCP 規(guī)定:SYN=1 的報文段不能有數(shù)據(jù)部分,但要消耗掉一個序號。
第二次握手
服務(wù)端收到連接請求報文段后,如果同意連接,則會發(fā)送一個應(yīng)答:SYN=1,ACK=1,seq=y,ack=x+1。
該應(yīng)答發(fā)送完成后便進入 SYN-RCVD 狀態(tài)。
PS1:SYN=1,ACK=1 表示該報文段為連接同意的應(yīng)答報文。
PS2:seq=y 表示服務(wù)端作為發(fā)送者時,發(fā)送字節(jié)流的初始序號。
PS3:ack=x+1 表示服務(wù)端希望下一個數(shù)據(jù)報發(fā)送序號從 x+1 開始的字節(jié)。
第三次握手
當(dāng)客戶端收到連接同意的應(yīng)答后,還要向服務(wù)端發(fā)送一個確認報文段,表示:服務(wù)端發(fā)來的連接同意應(yīng)答已經(jīng)成功收到。
該報文段的頭部為:ACK=1,seq=x+1,ack=y+1。
客戶端發(fā)完這個報文段后便進入 ESTABLISHED 狀態(tài),服務(wù)端收到這個應(yīng)答后也進入 ESTABLISHED 狀態(tài),此時連接的建立完成!
**為什么連接建立需要三次握手,而不是兩次握手**
在謝希仁著《計算機網(wǎng)絡(luò)》第四版中講 “三次握手” 的目的是 “為了防止已失效的連接請求報文段突然又傳送到了服務(wù)端,因而產(chǎn)生錯誤”。在另一部經(jīng)典的《計算機網(wǎng)絡(luò)》一書中講“三次握手” 的目的是為了解決 “網(wǎng)絡(luò)中存在延遲的重復(fù)分組” 的問題。這兩種不用的表述其實闡明的是同一個問題。
謝希仁版《計算機網(wǎng)絡(luò)》中的例子是這樣的,“已失效的連接請求報文段”的產(chǎn)生在這樣一種情況下:client 發(fā)出的第一個連接請求報文段并沒有丟失,而是在某個網(wǎng)絡(luò)結(jié)點長時間的滯留了,以致延誤到連接釋放以后的某個時間才到達 server。本來這是一個早已失效的報文段。但 server 收到此失效的連接請求報文段后,就誤認為是 client 再次發(fā)出的一個新的連接請求。于是就向 client 發(fā)出確認報文段,同意建立連接。假設(shè)不采用 “三次握手”,那么只要 server 發(fā)出確認,新的連接就建立了。由于現(xiàn)在 client 并沒有發(fā)出建立連接的請求,因此不會理睬 server 的確認,也不會向 server 發(fā)送數(shù)據(jù)。但 server 卻以為新的運輸連接已經(jīng)建立,并一直等待 client 發(fā)來數(shù)據(jù)。這樣,server 的很多資源就白白浪費掉了。采用“三次握手” 的辦法可以防止上述現(xiàn)象發(fā)生。例如剛才那種情況,client 不會向 server 的確認發(fā)出確認。server 由于收不到確認,就知道 client 并沒有要求建立連接。”
**四次揮手**
第一次揮手
若 A 認為數(shù)據(jù)發(fā)送完成,則它需要向 B 發(fā)送連接釋放請求。該請求只有報文頭,頭中攜帶的主要參數(shù)為:
FIN=1,seq=u。此時,A 將進入 FIN-WAIT-1 狀態(tài)。
PS1:FIN=1 表示該報文段是一個連接釋放請求。
PS2:seq=u,u-1 是 A 向 B 發(fā)送的最后一個字節(jié)的序號。
第二次揮手
B 收到連接釋放請求后,會通知相應(yīng)的應(yīng)用程序,告訴它 A 向 B 這個方向的連接已經(jīng)釋放。此時 B 進入 CLOSE-WAIT 狀態(tài),并向 A 發(fā)送連接釋放的應(yīng)答,其報文頭包含:
ACK=1,seq=v,ack=u+1。
PS1:ACK=1:除 TCP 連接請求報文段以外,TCP 通信過程中所有數(shù)據(jù)報的 ACK 都為 1,表示應(yīng)答。
PS2:seq=v,v-1 是 B 向 A 發(fā)送的最后一個字節(jié)的序號。
PS3:ack=u+1 表示希望收到從第 u+1 個字節(jié)開始的報文段,并且已經(jīng)成功接收了前 u 個字節(jié)。
A 收到該應(yīng)答,進入 FIN-WAIT-2 狀態(tài),等待 B 發(fā)送連接釋放請求。
第二次揮手完成后,A 到 B 方向的連接已經(jīng)釋放,B 不會再接收數(shù)據(jù),A 也不會再發(fā)送數(shù)據(jù)。但 B 到 A 方向的連接仍然存在,B 可以繼續(xù)向 A 發(fā)送數(shù)據(jù)。
第三次揮手
當(dāng) B 向 A 發(fā)完所有數(shù)據(jù)后,向 A 發(fā)送連接釋放請求,請求頭:FIN=1,ACK=1,seq=w,ack=u+1。B 便進入 LAST-ACK 狀態(tài)。
第四次揮手
A 收到釋放請求后,向 B 發(fā)送確認應(yīng)答,此時 A 進入 TIME-WAIT 狀態(tài)。該狀態(tài)會持續(xù) 2MSL 時間,若該時間段內(nèi)沒有 B 的重發(fā)請求的話,就進入 CLOSED 狀態(tài),撤銷 TCB。當(dāng) B 收到確認應(yīng)答后,也便進入 CLOSED 狀態(tài),撤銷 TCB。
為什么 A 要先進入 TIME-WAIT 狀態(tài),等待 2MSL 時間后才進入 CLOSED 狀態(tài)?
為了保證 B 能收到 A 的確認應(yīng)答。
若 A 發(fā)完確認應(yīng)答后直接進入 CLOSED 狀態(tài),那么如果該應(yīng)答丟失,B 等待超時后就會重新發(fā)送連接釋放請求,但此時 A 已經(jīng)關(guān)閉了,不會作出任何響應(yīng),因此 B 永遠無法正常關(guān)閉。
URL解析
- 首先判斷你輸入的是一個合法的URL 還是一個待搜索的關(guān)鍵詞,并且根據(jù)你輸入的內(nèi)容進行對應(yīng)操作
DNS 查詢
- DNS查詢對應(yīng)ip
TCP 連接
- 在確定目標服務(wù)器服務(wù)器的IP地址后,則經(jīng)歷三次握手建立TCP連接
HTTP 請求
- 當(dāng)建立tcp連接之后,就可以在這基礎(chǔ)上進行通信,瀏覽器發(fā)送 http 請求到目標服務(wù)器
響應(yīng)請求
- 當(dāng)服務(wù)器接收到瀏覽器的請求之后,就會進行邏輯操作,處理完成之后返回一個HTTP響應(yīng)消息
頁面渲染
- 當(dāng)瀏覽器接收到服務(wù)器響應(yīng)的資源后,首先會對資源進行解析:
?
查看響應(yīng)頭的信息,根據(jù)不同的指示做對應(yīng)處理,比如重定向,存儲cookie,解壓gzip,緩存資源等等
查看響應(yīng)頭的 Content-Type的值,根據(jù)不同的資源類型采用不同的解析方式
關(guān)于頁面的渲染過程如下:
?
解析HTML,構(gòu)建 DOM 樹
解析 CSS ,生成 CSS 規(guī)則樹
合并 DOM 樹和 CSS 規(guī)則,生成 render 樹
布局 render 樹( Layout / reflow ),負責(zé)各元素尺寸、位置的計算
繪制 render 樹( paint ),繪制頁面像素信息
瀏覽器會將各層的信息發(fā)送給 GPU,GPU 會將各層合成( composite ),顯示在屏幕上
全稱 Content Delivery Network, 即內(nèi)容分發(fā)網(wǎng)絡(luò)。
?
摘錄一個形象的比喻, 來理解 CDN 是什么。
?
10 年前,還沒有火車票代售點一說,12306.cn 更是無從說起。那時候火車票還只能在火車站的售票大廳購買,而我所在的小縣城并不通火車,火車票都要去市里的火車站購買,而從我家到縣城再到市里,來回就是 4 個小時車程,簡直就是浪費生命。后來就好了,小縣城里出現(xiàn)了火車票代售點,甚至鄉(xiāng)鎮(zhèn)上也有了代售點,可以直接在代售點購買火車票,方便了不少,全市人民再也不用在一個點苦逼的排隊買票了。
?
簡單的理解 CDN 就是這些代售點 (緩存服務(wù)器) 的承包商, 他為買票者提供了便利, 幫助他們在最近的地方 (最近的 CDN 節(jié)點) 用最短的時間 (最短的請求時間) 買到票(拿到資源), 這樣去火車站售票大廳排隊的人也就少了。也就減輕了售票大廳的壓力(起到分流作用, 減輕服務(wù)器負載壓力)。
?
用戶在瀏覽網(wǎng)站的時候,CDN 會選擇一個離用戶最近的 CDN 邊緣節(jié)點來響應(yīng)用戶的請求,這樣海南移動用戶的請求就不會千里迢迢跑到北京電信機房的服務(wù)器(假設(shè)源站部署在北京電信機房)上了。
?
CDN 緩存
關(guān)于 CDN 緩存, 在瀏覽器本地緩存失效后, 瀏覽器會向 CDN 邊緣節(jié)點發(fā)起請求。類似瀏覽器緩存, CDN 邊緣節(jié)點也存在著一套緩存機制。CDN 邊緣節(jié)點緩存策略因服務(wù)商不同而不同,但一般都會遵循 http 標準協(xié)議,通過 http 響應(yīng)頭中的
?
Cache-control: max-age //后面會提到
的字段來設(shè)置 CDN 邊緣節(jié)點數(shù)據(jù)緩存時間。
?
當(dāng)瀏覽器向 CDN 節(jié)點請求數(shù)據(jù)時,CDN 節(jié)點會判斷緩存數(shù)據(jù)是否過期,若緩存數(shù)據(jù)并沒有過期,則直接將緩存數(shù)據(jù)返回給客戶端;否則,CDN 節(jié)點就會向服務(wù)器發(fā)出回源請求,從服務(wù)器拉取最新數(shù)據(jù),更新本地緩存,并將最新數(shù)據(jù)返回給客戶端。 CDN 服務(wù)商一般會提供基于文件后綴、目錄多個維度來指定 CDN 緩存時間,為用戶提供更精細化的緩存管理。
?
CDN 優(yōu)勢
CDN 節(jié)點解決了跨運營商和跨地域訪問的問題,訪問延時大大降低。
大部分請求在 CDN 邊緣節(jié)點完成,CDN 起到了分流作用,減輕了源服務(wù)器的負載。
復(fù)制代碼HTTPS是HTTP協(xié)議的安全版本,HTTP協(xié)議的數(shù)據(jù)傳輸是明文的,是不安全的,HTTPS使用了SSL/TLS協(xié)議進行了加密處理,相對更安全
HTTP 和 HTTPS 使用連接方式不同,默認端口也不一樣,HTTP是80,HTTPS是443
HTTPS 由于需要設(shè)計加密以及多次握手,性能方面不如 HTTP
HTTPS需要SSL,SSL 證書需要錢,功能越強大的證書費用越高
hash策略:是以項目為單位的,項目內(nèi)容改變則會生成新的hash,內(nèi)容不變則hash不變
?
chunkhash策略:是以chunk為單位的,當(dāng)一個文件內(nèi)容改變,則整個相應(yīng)的chunk組模塊的hash回發(fā)生改變
?
contenthash策略:是以自身內(nèi)容為單為的
?
推薦使用:css :contenthash
?
?js:chunkhash
1.初始化參數(shù):解析webpack配置參數(shù),合并shell傳入和webpack.config.js文件配置的參數(shù),形成最后的配置結(jié)果。
2.開始編譯:上一步得到的參數(shù)初始化compiler對象,注冊所有配置的插件,插件監(jiān)聽webpack構(gòu)建生命周期的事件節(jié)點,做出相應(yīng)的反應(yīng),執(zhí)行對象的 run 方法開始執(zhí)行編譯。
3.確定入口:從配置的entry入口,開始解析文件構(gòu)建AST語法樹,找出依賴,遞歸下去。
4.編譯模塊:遞歸中根據(jù)文件類型和loader配置,調(diào)用所有配置的loader對文件進行轉(zhuǎn)換,再找出該模塊依賴的模塊,再遞歸本步驟直到所有入口依賴的文件都經(jīng)過了本步驟的處理。
5.完成模塊編譯:在經(jīng)過第4步使? Loader 翻譯完所有模塊后,得到了每個模塊被翻譯后的最終內(nèi)容以及它們之間的依賴關(guān)系;
6.輸出資源:根據(jù)??和模塊之間的依賴關(guān)系,組裝成?個個包含多個模塊的 Chunk,再把每個 Chunk 轉(zhuǎn)換成?個單獨的?件加?到輸出列表,這步是可以修改輸出內(nèi)容的最后機會;
7.輸出完成:在確定好輸出內(nèi)容后,根據(jù)配置確定輸出的路徑和?件名,把?件內(nèi)容寫?到?件系統(tǒng)。
從整個運行時機上來看,如下圖所示:
可以看到,兩者在運行時機上的區(qū)別:
在Webpack 運行的生命周期中會廣播出許多事件,Plugin 可以監(jiān)聽這些事件,在合適的時機通過Webpack提供的 API改變輸出結(jié)果
對于loader,實質(zhì)是一個轉(zhuǎn)換器,將A文件進行編譯形成B文件,操作的是文件,比如將A.scss或A.less轉(zhuǎn)變?yōu)锽.css,單純的文件轉(zhuǎn)換過程
在編寫 loader 前,我們首先需要了解 loader 的本質(zhì)
其本質(zhì)為函數(shù),函數(shù)中的 this 作為上下文會被 webpack 填充,因此我們不能將 loader設(shè)為一個箭頭函數(shù)
函數(shù)接受一個參數(shù),為 webpack 傳遞給 loader 的文件源內(nèi)容
函數(shù)中 this 是由 webpack 提供的對象,能夠獲取當(dāng)前 loader 所需要的各種信息
函數(shù)中有異步操作或同步操作,異步操作通過 this.callback 返回,返回值要求為 string 或者 Buffer
代碼如下所示:
// 導(dǎo)出一個函數(shù),source為webpack傳遞給loader的文件源內(nèi)容
module.exports=function(source) {
const content=doSomeThing2JsString(source);
// 如果 loader 配置了 options 對象,那么this.query將指向 options
const options=this.query;
// 可以用作解析其他模塊路徑的上下文
console.log('this.context');
/*
* this.callback 參數(shù):
* error:Error | null,當(dāng) loader 出錯時向外拋出一個 error
* content:String | Buffer,經(jīng)過 loader 編譯后需要導(dǎo)出的內(nèi)容
* sourceMap:為方便調(diào)試生成的編譯后內(nèi)容的 source map
* ast:本次編譯生成的 AST 靜態(tài)語法樹,之后執(zhí)行的 loader 可以直接使用這個 AST,進而省去重復(fù)生成 AST 的過程
*/
this.callback(null, content); // 異步
return content; // 同步
}
一般在編寫loader的過程中,保持功能單一,避免做多種功能
如less文件轉(zhuǎn)換成 css文件也不是一步到位,而是 less-loader、css-loader、style-loader幾個 loader的鏈式調(diào)用才能完成轉(zhuǎn)換
由于webpack基于發(fā)布訂閱模式,在運行的生命周期中會廣播出許多事件,插件通過監(jiān)聽這些事件,就可以在特定的階段執(zhí)行自己的插件任務(wù)
在之前也了解過,webpack編譯會創(chuàng)建兩個核心對象:
如果自己要實現(xiàn)plugin,也需要遵循一定的規(guī)范:
實現(xiàn)plugin的模板如下:
class MyPlugin {
// Webpack 會調(diào)用 MyPlugin 實例的 apply 方法給插件實例傳入 compiler 對象
apply (compiler) {
// 找到合適的事件鉤子,實現(xiàn)自己的插件功能
compiler.hooks.emit.tap('MyPlugin', compilation=> {
// compilation: 當(dāng)前打包構(gòu)建流程的上下文
console.log(compilation);
// do something...
})
}
}
在 emit 事件發(fā)生時,代表源文件的轉(zhuǎn)換和組裝已經(jīng)完成,可以讀取到最終將輸出的資源、代碼塊、模塊及其依賴,并且可以修改輸出資源的內(nèi)容
優(yōu)化 loader 配置
合理使用 resolve.extensions
優(yōu)化 resolve.modules
優(yōu)化 resolve.alias
使用 DLLPlugin 插件
使用 cache-loader
terser 啟動多線程
合理使用 sourceMap
通過webpack-dev-server創(chuàng)建兩個服務(wù)器:提供靜態(tài)資源的服務(wù)(express)和Socket服務(wù)
express server 負責(zé)直接提供靜態(tài)資源的服務(wù)(打包后的資源直接被瀏覽器請求和解析)
socket server 是一個 websocket 的長連接,雙方可以通信
當(dāng) socket server 監(jiān)聽到對應(yīng)的模塊發(fā)生變化時,會生成兩個文件.json(manifest文件)和.js文件(update chunk)
通過長連接,socket server 可以直接將這兩個文件主動發(fā)送給客戶端(瀏覽器)
瀏覽器拿到兩個新的文件后,通過HMR runtime機制,加載這兩個文件,并且針對修改的模塊進行更新
style-loader: 將css添加到DOM的內(nèi)聯(lián)樣式標簽style里
css-loader :允許將css文件通過require的方式引入,并返回css代碼
less-loader: 處理less
sass-loader: 處理sass
postcss-loader: 用postcss來處理CSS
autoprefixer-loader: 處理CSS3屬性前綴,已被棄用,建議直接使用postcss
file-loader: 分發(fā)文件到output目錄并返回相對路徑
url-loader: 和file-loader類似,但是當(dāng)文件小于設(shè)定的limit時可以返回一個Data Url
html-minify-loader: 壓縮HTML
babel-loader :用babel來轉(zhuǎn)換ES6文件到ES
**基本操作**
git init 初始化倉庫,默認為 master 分支
git add . 提交全部文件修改到緩存區(qū)
git add <具體某個文件路徑+全名> 提交某些文件到緩存區(qū)
git diff 查看當(dāng)前代碼 add后,會 add 哪些內(nèi)容
git diff --staged查看現(xiàn)在 commit 提交后,會提交哪些內(nèi)容
git status 查看當(dāng)前分支狀態(tài)
git pull <遠程倉庫名> <遠程分支名> 拉取遠程倉庫的分支與本地當(dāng)前分支合并
git pull <遠程倉庫名> <遠程分支名>:<本地分支名> 拉取遠程倉庫的分支與本地某個分支合并
git commit -m "<注釋>" 提交代碼到本地倉庫,并寫提交注釋
git commit -v 提交時顯示所有diff信息
git commit --amend [file1] [file2] 重做上一次commit,并包括指定文件的新變化
**提交規(guī)則**
feat: 新特性,添加功能
fix: 修改 bug
refactor: 代碼重構(gòu)
docs: 文檔修改
style: 代碼格式修改, 注意不是 css 修改
test: 測試用例修改
chore: 其他修改, 比如構(gòu)建流程, 依賴管理
**分支操作**
git branch 查看本地所有分支
git branch -r 查看遠程所有分支
git branch -a 查看本地和遠程所有分支
git merge <分支名> 合并分支
git merge --abort 合并分支出現(xiàn)沖突時,取消合并,一切回到合并前的狀態(tài)
git branch <新分支名> 基于當(dāng)前分支,新建一個分支
git checkout --orphan <新分支名> 新建一個空分支(會保留之前分支的所有文件)
git branch -D <分支名> 刪除本地某個分支
git push <遠程庫名> :<分支名> 刪除遠程某個分支
git branch <新分支名稱> <提交ID> 從提交歷史恢復(fù)某個刪掉的某個分支
git branch -m <原分支名> <新分支名> 分支更名
git checkout <分支名> 切換到本地某個分支
git checkout <遠程庫名>/<分支名> 切換到線上某個分支
git checkout -b <新分支名> 把基于當(dāng)前分支新建分支,并切換為這個分支
**遠程操作**
git fetch [remote] 下載遠程倉庫的所有變動
git remote -v 顯示所有遠程倉庫
git pull [remote] [branch] 拉取遠程倉庫的分支與本地當(dāng)前分支合并
git fetch 獲取線上最新版信息記錄,不合并
git push [remote] [branch] 上傳本地指定分支到遠程倉庫
git push [remote] --force 強行推送當(dāng)前分支到遠程倉庫,即使有沖突
git push [remote] --all 推送所有分支到遠程倉庫
**撤銷操作**
git checkout [file] 恢復(fù)暫存區(qū)的指定文件到工作區(qū)
git checkout [commit] [file] 恢復(fù)某個commit的指定文件到暫存區(qū)和工作區(qū)
git checkout . 恢復(fù)暫存區(qū)的所有文件到工作區(qū)
git reset [commit] 重置當(dāng)前分支的指針為指定commit,同時重置暫存區(qū),但工作區(qū)不變
git reset --hard 重置暫存區(qū)與工作區(qū),與上一次commit保持一致
git reset [file] 重置暫存區(qū)的指定文件,與上一次commit保持一致,但工作區(qū)不變
git revert [commit] 后者的所有變化都將被前者抵消,并且應(yīng)用到當(dāng)前分支
reset:真實硬性回滾,目標版本后面的提交記錄全部丟失了
revert:同樣回滾,這個回滾操作相當(dāng)于一個提價,目標版本后面的提交記錄也全部都有
**存儲操作**
git stash 暫時將未提交的變化移除
git stash pop 取出儲藏中最后存入的工作狀態(tài)進行恢復(fù),會刪除儲藏
git stash list 查看所有儲藏中的工作
git stash apply <儲藏的名稱> 取出儲藏中對應(yīng)的工作狀態(tài)進行恢復(fù),不會刪除儲藏
git stash clear 清空所有儲藏中的工作
git stash drop <儲藏的名稱> 刪除對應(yīng)的某個儲藏
標準(W3C)盒子模型:width=內(nèi)容寬度(content) + border + padding + margin
低版本IE盒子模型: width=內(nèi)容寬度(content + border + padding)+ margin
圖片展示:
區(qū)別: 標準盒子模型盒子的height和width是content(內(nèi)容)的寬高,而IE盒子模型盒子的寬高則包括content+padding+border部分。
- 由`float`引起的雙邊距的問題,使用`display`解決;
- 由`float`引起的3像素問題,使用`display: inline -3px`;
- 使用正確的書寫順序`link visited hover active`,解決超鏈接`hover`點擊失效問題;
- 對于`IE` 的`z-index`問題,通過給父元素增加`position: relative`解決;
- 使用`!important`解決`Min-height`最小高度問題;
- 使用`iframe`解決`select`在`IE6`下的覆蓋問題;
- 使用`over: hidden`, `zoom: 0.08`, `line-height: 1px`解決定義1px左右的容器寬度問題;
常見的選擇符有一下:
?
`id`選擇器(`#content`),類選擇器(`.content`), 標簽選擇器(`div`, `p`, `span`等), 相鄰選擇器(`h1+p`), 子選擇器(`ul>li`), 后代選擇器(`li a`), 通配符選擇器(`*`), 屬性選擇器(`a[rel="external"]`), 偽類選擇器(`a:hover`, `li:nth-child`)
?
可繼承的樣式屬性: `font-size`, `font-family`, `color`, `ul`, `li`, `dl`, `dd`, `dt`;
?
不可繼承的樣式屬性: `border`, `padding`, `margin`, `width`, `height`;
首先,使用`position`的時候,應(yīng)該記住一個規(guī)律是‘**子絕父相**’。
?
`relative`(相對定位): 生成相對定位的元素,定位原點是元素本身所在的位置;
?
`absolute`(絕對定位):生成絕對定位的元素,定位原點是離自己這一級元素最近的一級`position`設(shè)置為`absolute`或者`relative`的父元素的左上角為原點的。
?
`fixed` (老IE不支持):生成絕對定位的元素,相對于瀏覽器窗口進行定位。
?
`static`:默認值。沒有定位,元素出現(xiàn)在正常的流中(忽略 `top`, `bottom`, `left`, `right`、`z-index` 聲明)。
?
`inherit`:規(guī)定從父元素繼承 `position` 屬性的值。
?
**更新一個屬性**
?
`sticky`: (新增元素,目前兼容性可能不是那么的好),可以設(shè)置 position:sticky 同時給一個 (top,bottom,right,left) 之一即可。
?
**注意**:
?
- 使用`sticky`時,必須指定top、bottom、left、right4個值之一,不然只會處于相對定位;
- `sticky`只在其父元素內(nèi)其效果,且保證父元素的高度要高于`sticky`的高度;
- 父元素不能`overflow:hidden`或者`overflow:auto`等屬性。
85.CSS3有哪些新特性?
關(guān)于`CSS`新增的特性,有以下:
?
- 選擇器;
- 圓角`(border-raduis)`;
- 多列布局`(multi-column layout)`;
- 陰影`(shadow)`和反射`(reflect)`;
- 文字特效`(text-shadow)`;
- 文字渲染`(text-decoration`);
- 線性漸變`(gradient)`;
- 旋轉(zhuǎn)`(rotate`)/縮放`(scale)`/傾斜`(skew)`/移動`(translate)`;
- 媒體查詢`(@media)`;
- `RGBA`和透明度 ;
- `@font-face`屬性;
- 多背景圖 ;
- 盒子大小;
- 語音;
實現(xiàn)步驟: 1.首先保證元素是塊級元素;2.設(shè)置元素的邊框;3.不需要顯示的邊框使用透明色。
css:
* {margin: 0; padding: 0;}
.content {
width:0;
height:0;
margin:0 auto;
border:50px solid transparent;
border-top: 50px solid pink;
}
?
html:
<div class="content"></div>
響應(yīng)式網(wǎng)站設(shè)計(Responsive Web design)是一個網(wǎng)站能夠兼容多個終端,而不是為每一個終端做一個特定的版本。
關(guān)于原理: 基本原理是通過媒體查詢(@media)查詢檢測不同的設(shè)備屏幕尺寸做處理。
關(guān)于兼容: 頁面頭部必須有mate聲明的viewport。
<meta name="’viewport’" content="”width=device-width," initial-scale="1." maximum-scale="1,user-scalable=no”"/>
- 多個`css`可合并,并盡量減少`http`請求
- 屬性值為0時,不加單位
- 將`css`文件放在頁面最上面
- 避免后代選擇符,過度約束和鏈式選擇符
- 使用緊湊的語法
- 避免不必要的重復(fù)
- 使用語義化命名,便于維護
- 盡量少的使用`!impotrant`,可以選擇其他選擇器
- 精簡規(guī)則,盡可能合并不同類的重復(fù)規(guī)則
- 遵守盒子模型規(guī)則
- 有空格時候會有間隙, 可以刪除空格解決;
- `margin`正值的時候, 可以讓`margin`使用負值解決;
- 使用`font-size`時候,可通過設(shè)置`font-size:0`、`letter-spacing`、`word-spacing`解決;
首先,外邊距重疊就是 `margin-collapse`。相鄰的兩個盒子(可能是兄弟關(guān)系也可能是祖先關(guān)系)的外邊距可以結(jié)合成一個單獨的外邊距。 這種合并外邊距的方式被稱為折疊,結(jié)合而成的外邊距稱為折疊外邊距。
?
折疊結(jié)果遵循下列計算原則:
?
- 兩個相鄰的外面邊距是正數(shù)時,折疊結(jié)果就是他們之中的較大值;
- 兩個相鄰的外邊距都是負數(shù)時,折疊結(jié)果是兩者絕對值的較大值;
- 兩個外邊距一正一負時,折疊結(jié)果是兩者的相加的和;
- `visibility: hidden;` 這個屬性只是簡單的隱藏某個元素,但是元素占用的空間任然存在;
- `opacity: 0;``CSS3`屬性,設(shè)置0可以使一個元素完全透明;
- `position: absolute;` 設(shè)置一個很大的 left 負值定位,使元素定位在可見區(qū)域之外;
- `display: none;` 元素會變得不可見,并且不會再占用文檔的空間;
- `transform: scale(0);` 將一個元素設(shè)置為縮放無限小,元素將不可見,元素原來所在的位置將被保留;
- `<div hidden="hidden">` `HTML5`屬性,效果和`display:none;`相同,但這個屬性用于記錄一個元素的狀態(tài);
- `height: 0;` 將元素高度設(shè)為 0 ,并消除邊框;
- `filter: blur(0);` `CSS3`屬性,括號內(nèi)的數(shù)值越大,圖像高斯模糊的程度越大,到達一定程度可使圖像消失`(此處感謝小伙伴支持)`;
`BFC`規(guī)定了內(nèi)部的`Block Box`如何布局。一個頁面是由很多個`Box`組成的,元素的類型和`display`屬性,決定了這個`Box`的類型。不同類型的`box`,會參與不同的`Formatting Context`(決定如何渲染文檔的容器),因此`Box`內(nèi)的元素會以不用的方式渲染,也是就是說`BFC`內(nèi)部的元素和外部的元素不會相互影響。
?
定位方案:
?
- 內(nèi)部的`box`會在垂直方向上一個接一個的放置;
- `box`垂直方向的距離由`margin`決定,屬于同一個`BFC`的兩個相鄰`Box`的`margin`會發(fā)生重疊;
- 每個元素`margin box`的左邊,與包含塊`border box`的左邊相接觸;
- `BFC`的區(qū)域不會與float box重疊;
- `BFC`是頁面上的一個隔離的獨立容器,容器里面的元素不會影響到外面的元素;
- 計算`BFC`的高度時,浮動元素也會參與計算。
?
滿足下列條件之一就可以出發(fā)BFC:
?
- 根元素變化,即`html`;
- `float`的值不為`none`(默認);
- `overflow`的值不為`visible`(默認);
- `display`的值為`inline-block`, `tabke-cell`,`table-caption`;
- `position`的值為`absolute`或`fixed`;
(1)、問題:`png24`位的圖片在`ie`瀏覽器上出現(xiàn)背景。解決: 做成`png8`;
?
(2)、問題:瀏覽器默認的`margin`和`padding`不同。 解決: 添加一個全局的`*{ margin: 0; padding: 0;}`;
?
(3)、問題:`IE`下,可以使用獲取常規(guī)屬性的方法來獲取自定義屬性,也可以使用`getAttribute()`獲取自定義屬性,而`Firefox`下,只能使用`getAttribute()`獲取自定義屬性。 解決: 統(tǒng)一通過`getAttribute()`獲取自定義屬性;
?
(4)、問題: `IE`下,`event`對象有`x`,`y`屬性,但是沒有`pageX`,`pageY`屬性,而`Firefox`下,`event`對象有`pageX`,`pageY`屬性,但是沒有`x`,`y`屬性。 解決: 使用`mX(mX=event.x ? event.x : event.pageX;)`來代替`IE`下的`event.x`或者`Firefox`下的`event.pageX`。
.shrink {
-webkit-transform: scale(0.8);
-o-transform: scale(1);
display: inilne-block;
}
`L-V-H-A`,`l(link)ov(visited)e h(hover)a(active)te`,即用喜歡和討厭兩個詞來概括
- 參數(shù)是`scroll`的時候,一定會出滾動條;
- 參數(shù)是`auto`的時候,子元素內(nèi)容大于父元素時出現(xiàn)滾動條;
- 參數(shù)是`visible`的時候,溢出的內(nèi)容出現(xiàn)在父元素之外;
- 參數(shù)是`hidden`的時候,溢出隱藏;
內(nèi)嵌樣式: 優(yōu)點: 方便書寫,權(quán)重高;缺點: 沒有做到結(jié)構(gòu)和樣式分離;
內(nèi)聯(lián)樣式: 優(yōu)點:結(jié)構(gòu)樣式相分離; 缺點:沒有徹底分離;
外聯(lián)樣式: 優(yōu)點: 完全實現(xiàn)了結(jié)構(gòu)和樣式相分離; 缺點: 需要引入才能使用;
- `display`屬性規(guī)定元素應(yīng)該生成的框的類型;
- `position`屬性規(guī)定元素的定位類型;
- `float`屬性是一種布局方式,定義元素往哪個方向浮動;
?
**疊加結(jié)果**:有點類似于優(yōu)先機制。`position`的值-- `absolute/fixed`優(yōu)先級最高,有他們在時,`float`不起作用,`display`值需要調(diào)整。`float`或者`absolute`定位的元素,只能是塊元素或者表格。
- 回流(重排),`reflow`:當(dāng)`render tree`中的一部分(或全部)因為元素的規(guī)模尺寸,布局,隱藏等改變時而需要重新構(gòu)建;
- 重繪`(repaint`):當(dāng)`render tree`中的一些元素需要更新屬性,而這些屬性只影響元素的外觀,風(fēng)格,而不會影響布局時,稱其為**重繪**,例如顏色改變等。
?
- 增加或者刪除可見的`dom`元素;
- 元素的位置發(fā)生了改變;
- 元素的尺寸發(fā)生了改變,例如邊距,寬高等幾何屬性改變;
- 內(nèi)容改變,例如圖片大小,字體大小改變等;
- 頁面渲染初始化;
- 瀏覽器窗口尺寸改變,例如`resize`事件發(fā)生時等;
- 重排(回流)一定會引發(fā)重繪**。
**三者的區(qū)別:**
?
- px是固定的像素,一旦設(shè)置了就無法因為適應(yīng)頁面大小而改變。
- em和rem相對于px更具有靈活性,他們是相對長度單位,其長度不是固定的,更適用于響應(yīng)式布局。
- em是相對于其父元素來設(shè)置字體大小,這樣就會存在一個問題,進行任何元素設(shè)置,都有可能需要知道他父元素的大小。而rem是相對于根元素,這樣就意味著,只需要在根元素確定一個參考值。
?
**使用場景:**
?
- 對于只需要適配少部分移動設(shè)備,且分辨率對頁面影響不大的,使用px即可 。
- 對于需要適配各種移動設(shè)備,使用rem,例如需要適配iPhone和iPad等分辨率差別比較挺大的設(shè)備
東港新聞》春節(jié)特輯“我的家譜故事”播出預(yù)告(2018)
https://mp.weixin.qq.com/s/nyLPYaoqg0i5QICWvkokaA
1928,動蕩中的國與家——民國萊陽蓋氏族譜修撰之謎(2018)
https://mp.weixin.qq.com/s/ACUd8DtcVSzBfLiEu8QPeA
膠東尋根:登州府萊陽縣幾甲幾社這種地名,應(yīng)如何理解?(2019)
https://mp.weixin.qq.com/s/TQ4halUv9qnG4RzGtfFIWQ
萊陽蓋姓在歷史上多次修撰族譜,為后世留存珍貴資料(2020)
https://mp.weixin.qq.com/s/brKDAQMEj9J-YhvZlEVHpw
膠東古跡:清順治年間萊陽縣“廢城社”碑文中的“蓋姓人”(2023)
https://mp.weixin.qq.com/s/SjkbYtrdtw-DmTdg90HpCw
膠東姓氏:淺談《萊陽縣志》與《蓋氏族譜》記載的蓋通(2024)
https://mp.weixin.qq.com/s/wXd-cvd3rcS2ZjL-8CyUUA
萊陽蓋氏家族在乾隆四十六年修過一次族譜,可惜未能傳世(2024)
https://mp.weixin.qq.com/s/ZLZqmv1ve4nMWGc-ykOSZA
對于《蓋氏族譜》北三支11世蓋天賜三子名字的見解(2023)
https://mp.weixin.qq.com/s/IEPXAUnjtBd2u0CVFL7v3Q
膠東姓氏:淺談萊陽蓋氏家族支派的劃分(2024)
https://mp.weixin.qq.com/s/tY3wRSES7ZvG2RpGonxHPA
中國家譜亂象——山東萊陽蓋氏家族續(xù)譜(2024)
https://m.toutiao.com/article/7329540972633768448
蓋姓起源與族群(2021)
HTTP://d.wanfangdata.com.cn/periodical/ChlQZXJpb2RpY2FsQ0hJTmV3UzIwMjMxMjI2EhpRS0JKQkQyMDIxMjAyMTEwMTUwMDAxMzM3NhoIamxveGtuYTM%3D
萊陽蓋氏家族世系歷史解析(2022)
https://www.qitantan.net/thesis/detail/6527828
通過家族基因關(guān)系推算萊陽《蓋氏族譜》的鼻祖十二世蓋盛賢的生存年代(2024)
https://mp.weixin.qq.com/s/LkkjZ1Bb3Nc4qaEHwLeZKQ
祖源樹之蓋氏父系樹
https://www.theytree.com/surname/%E7%9B%96
祖源樹之山東萊陽蓋氏家族
https://www.theytree.com/?snp=MF56411
23魔方之蓋姓起源分布和家譜家族
https://www.23mofang.com/ancestry/library-surname/5f34ee8eff5a3344d6a8a0af
23魔方之山東萊陽蓋氏家族祖源分析
https://www.23mofang.com/ancestry/family/5d4bea8ce956900001f4a60d
華大-微基因網(wǎng)址
https://www.wegene.com/
Y染色體測序分析判讀
https://www.yfull.com/
中華蓋氏網(wǎng)
http://www.10000xing.cn/x405/wjxsj.html
*請認真填寫需求信息,我們會在24小時內(nèi)與您取得聯(lián)系。