你們好,我是練習時長一年半的前端練習生,喜歡唱、跳、rap、敲代碼。本文是筆者一年多來對前端基礎知識的總結和思考,這些題目對本身是總結,對你們也是一點微薄的資料,但願能給你們帶來一些幫助和啓發。成文過程當中獲得了許多大佬的幫助,在此感謝愷哥的小冊、神三元同窗的前端每日一問以及許多素未謀面的朋友們,讓我等萌新也有機會在前人的財富中拾人牙慧,班門弄斧Thanks♪(・ω・)ノ
css
本文將從如下十一個維度爲讀者總結前端基礎知識html
這個問題實質上是在回答
let
和var
有什麼區別,對於這個問題,咱們能夠直接查看babel
轉換先後的結果,看一下在循環中經過let
定義的變量是如何解決變量提高的問題前端
(function(){
for(var i = 0; i < 5; i ++){
console.log(i) // 0 1 2 3 4
}
})();
console.log(i) // Uncaught ReferenceError: i is not defined
複製代碼
不過這個問題並無結束,咱們回到var
和let/const
的區別上:react
var
聲明的變量會掛到window上,而let
和const
不會var
聲明的變量存在變量提高,而let
和const
不會let
和const
聲明造成塊做用域,只能在塊做用域裏訪問,不能跨塊訪問,也不能跨函數訪問let
和const
不能聲明同名變量,而var
能夠let
和const
聲明的變量不能在聲明前被使用babel的轉化,其實只實現了第二、三、5點git
實現const的關鍵在於Object.defineProperty()
這個API,這個API用於在一個對象上增長或修改屬性。經過配置屬性描述符,能夠精確地控制屬性行爲。Object.defineProperty()
接收三個參數:github
Object.defineProperty(obj, prop, desc)web
參數 | 說明 |
---|---|
obj | 要在其上定義屬性的對象 |
prop | 要定義或修改的屬性的名稱 |
descriptor | 將被定義或修改的屬性描述符 |
屬性描述符 | 說明 | 默認值 |
---|---|---|
value | 該屬性對應的值。能夠是任何有效的 JavaScript 值(數值,對象,函數等)。默認爲 undefined | undefined |
get | 一個給屬性提供 getter 的方法,若是沒有 getter 則爲 undefined | undefined |
set | 一個給屬性提供 setter 的方法,若是沒有 setter 則爲 undefined。當屬性值修改時,觸發執行該方法 | undefined |
writable | 當且僅當該屬性的writable爲true時,value才能被賦值運算符改變。默認爲 false | false |
enumerable | enumerable定義了對象的屬性是否能夠在 for...in 循環和 Object.keys() 中被枚舉 | false |
Configurable | configurable特性表示對象的屬性是否能夠被刪除,以及除value和writable特性外的其餘特性是否能夠被修改 | false |
對於const不可修改的特性,咱們經過設置writable屬性來實現算法
function _const(key, value) {
const desc = {
value,
writable: false
}
Object.defineProperty(window, key, desc)
}
_const('obj', {a: 1}) //定義obj
obj.b = 2 //能夠正常給obj的屬性賦值
obj = {} //沒法賦值新對象
複製代碼
參考資料:如何在 ES5 環境下實現一個const ?數據庫
call() 方法
使用一個指定的 this 值和單獨給出的一個或多個參數來調用一個函數
語法:function.call(thisArg, arg1, arg2, ...)
編程
call()
的原理比較簡單,因爲函數的this指向它的直接調用者,咱們變動調用者即完成this指向的變動:
//變動函數調用者示例
function foo() {
console.log(this.name)
}
// 測試
const obj = {
name: '寫代碼像蔡徐抻'
}
obj.foo = foo // 變動foo的調用者
obj.foo() // '寫代碼像蔡徐抻'
複製代碼
基於以上原理, 咱們兩句代碼就能實現call()
Function.prototype.myCall = function(thisArg, ...args) {
thisArg.fn = this // this指向調用call的對象,即咱們要改變this指向的函數
return thisArg.fn(...args) // 執行函數並return其執行結果
}
複製代碼
可是咱們有一些細節須要處理:
Function.prototype.myCall = function(thisArg, ...args) {
const fn = Symbol('fn') // 聲明一個獨有的Symbol屬性, 防止fn覆蓋已有屬性
thisArg = thisArg || window // 若沒有傳入this, 默認綁定window對象
thisArg[fn] = this // this指向調用call的對象,即咱們要改變this指向的函數
const result = thisArg[fn](...args) // 執行當前函數
delete thisArg[fn] // 刪除咱們聲明的fn屬性
return result // 返回函數執行結果
}
//測試
foo.myCall(obj) // 輸出'寫代碼像蔡徐抻'
複製代碼
apply() 方法調用一個具備給定this值的函數,以及做爲一個數組(或相似數組對象)提供的參數。
語法:func.apply(thisArg, [argsArray])
apply()
和call()
相似,區別在於call()接收參數列表,而apply()接收一個參數數組,因此咱們在call()的實現上簡單改一下入參形式便可
Function.prototype.myApply = function(thisArg, args) {
const fn = Symbol('fn') // 聲明一個獨有的Symbol屬性, 防止fn覆蓋已有屬性
thisArg = thisArg || window // 若沒有傳入this, 默認綁定window對象
thisArg[fn] = this // this指向調用call的對象,即咱們要改變this指向的函數
const result = thisArg[fn](...args) // 執行當前函數(此處說明一下:雖然apply()接收的是一個數組,但在調用原函數時,依然要展開參數數組。能夠對照原生apply(),原函數接收到展開的參數數組)
delete thisArg[fn] // 刪除咱們聲明的fn屬性
return result // 返回函數執行結果
}
//測試
foo.myApply(obj, []) // 輸出'寫代碼像蔡徐抻'
複製代碼
bind()
方法建立一個新的函數,在 bind() 被調用時,這個新函數的 this 被指定爲 bind() 的第一個參數,而其他參數將做爲新函數的參數,供調用時使用。
語法: function.bind(thisArg, arg1, arg2, ...)
從用法上看,彷佛給call/apply包一層function就實現了bind():
Function.prototype.myBind = function(thisArg, ...args) {
return () => {
this.apply(thisArg, args)
}
}
複製代碼
但咱們忽略了三點:
Function.prototype.myBind = function (thisArg, ...args) {
var self = this
// new優先級
var fbound = function () {
self.apply(this instanceof self ? this : thisArg, args.concat(Array.prototype.slice.call(arguments)))
}
// 繼承原型上的屬性和方法
fbound.prototype = Object.create(self.prototype);
return fbound;
}
//測試
const obj = { name: '寫代碼像蔡徐抻' }
function foo() {
console.log(this.name)
console.log(arguments)
}
foo.myBind(obj, 'a', 'b', 'c')() //輸出寫代碼像蔡徐抻 ['a', 'b', 'c']
複製代碼
防抖和節流的概念都比較簡單,因此咱們就不在「防抖節流是什麼」這個問題上浪費過多篇幅了,簡單點一下:
防抖,即
短期內大量觸發同一事件,只會執行一次函數
,實現原理爲設置一個定時器,約定在xx毫秒後再觸發事件處理,每次觸發事件都會從新設置計時器,直到xx毫秒內無第二次操做
,防抖經常使用於搜索框/滾動條的監聽事件處理,若是不作防抖,每輸入一個字/滾動屏幕,都會觸發事件處理,形成性能浪費。
function debounce(func, wait) {
let timeout = null
return function() {
let context = this
let args = arguments
if (timeout) clearTimeout(timeout)
timeout = setTimeout(() => {
func.apply(context, args)
}, wait)
}
}
複製代碼
防抖是
延遲執行
,而節流是間隔執行
,函數節流即每隔一段時間就執行一次
,實現原理爲設置一個定時器,約定xx毫秒後執行事件,若是時間到了,那麼執行函數並重置定時器
,和防抖的區別在於,防抖每次觸發事件都重置定時器,而節流在定時器到時間後再清空定時器
function throttle(func, wait) {
let timeout = null
return function() {
let context = this
let args = arguments
if (!timeout) {
timeout = setTimeout(() => {
timeout = null
func.apply(context, args)
}, wait)
}
}
}
複製代碼
實現方式2:使用兩個時間戳
prev舊時間戳
和now新時間戳
,每次觸發事件都判斷兩者的時間差,若是到達規定時間,執行函數並重置舊時間戳
function throttle(func, wait) {
var prev = 0;
return function() {
let now = Date.now();
let context = this;
let args = arguments;
if (now - prev > wait) {
func.apply(context, args);
prev = now;
}
}
}
複製代碼
對於
[1, [1,2], [1,2,3]]
這樣多層嵌套的數組,咱們如何將其扁平化爲[1, 1, 2, 1, 2, 3]
這樣的一維數組呢:
1.ES6的flat()
const arr = [1, [1,2], [1,2,3]]
arr.flat(Infinity) // [1, 1, 2, 1, 2, 3]
複製代碼
2.序列化後正則
const arr = [1, [1,2], [1,2,3]]
const str = `[${JSON.stringify(arr).replace(/(\[|\])/g, '')}]`
JSON.parse(str) // [1, 1, 2, 1, 2, 3]
複製代碼
3.遞歸
對於樹狀結構的數據,最直接的處理方式就是遞歸
const arr = [1, [1,2], [1,2,3]]
function flat(arr) {
let result = []
for (const item of arr) {
item instanceof Array ? result = result.concat(flat(item)) : result.push(item)
}
return result
}
flat(arr) // [1, 1, 2, 1, 2, 3]
複製代碼
4.reduce()遞歸
const arr = [1, [1,2], [1,2,3]]
function flat(arr) {
return arr.reduce((prev, cur) => {
return prev.concat(cur instanceof Array ? flat(cur) : cur)
}, [])
}
flat(arr) // [1, 1, 2, 1, 2, 3]
複製代碼
5.迭代+展開運算符
// 每次while都會合並一層的元素,這裏第一次合併結果爲[1, 1, 2, 1, 2, 3, [4,4,4]]
// 而後arr.some斷定數組中是否存在數組,由於存在[4,4,4],繼續進入第二次循環進行合併
let arr = [1, [1,2], [1,2,3,[4,4,4]]]
while (arr.some(Array.isArray)) {
arr = [].concat(...arr);
}
console.log(arr) // [1, 1, 2, 1, 2, 3, 4, 4, 4]
複製代碼
實現一個符合規範的Promise篇幅比較長,建議閱讀筆者上一篇文章:異步編程二三事 | Promise/async/Generator實現原理解析 | 9k字
在JS中一切皆對象,但JS並非一種真正的面向對象(OOP)的語言,由於它缺乏類(class)
的概念。雖然ES6引入了class
和extends
,使咱們可以輕易地實現類和繼承。但JS並不存在真實的類,JS的類是經過函數以及原型鏈機制模擬的,本小節的就來探究如何在ES5環境下利用函數和原型鏈實現JS面向對象的特性
在開始以前,咱們先回顧一下原型鏈的知識,後續new
和繼承
等實現都是基於原型鏈機制。不少介紹原型鏈的資料都能寫上洋洋灑灑幾千字,但我以爲讀者們不須要把原型鏈想太複雜,容易把本身繞進去,其實在我看來,原型鏈的核心只須要記住三點:
__proto__屬性
,該屬性指向其原型對象,在調用實例的方法和屬性時,若是在實例對象上找不到,就會往原型對象上找prototype屬性
也指向實例的原型對象constructor屬性
指向構造函數首先咱們要知道new
作了什麼
prototype
,這一步是爲了繼承構造函數原型上的屬性和方法this
被指定爲該新實例,這一步是爲了執行構造函數內的賦值操做// new是關鍵字,這裏咱們用函數來模擬,new Foo(args) <=> myNew(Foo, args)
function myNew(foo, ...args) {
// 建立新對象,並繼承構造方法的prototype屬性, 這一步是爲了把obj掛原型鏈上, 至關於obj.__proto__ = Foo.prototype
let obj = Object.create(foo.prototype)
// 執行構造方法, 併爲其綁定新this, 這一步是爲了讓構造方法能進行this.name = name之類的操做, args是構造方法的入參, 由於這裏用myNew模擬, 因此入參從myNew傳入
let result = foo.apply(obj, args)
// 若是構造方法已經return了一個對象,那麼就返回該對象,不然返回myNew建立的新對象(通常狀況下,構造方法不會返回新實例,但使用者能夠選擇返回新實例來覆蓋new建立的對象)
return Object.prototype.toString.call(result) === '[object Object]' ? result : obj
}
// 測試:
function Foo(name) {
this.name = name
}
const newObj = myNew(Foo, 'zhangsan')
console.log(newObj) // Foo {name: "zhangsan"}
console.log(newObj instanceof Foo) // true
複製代碼
說到繼承,最容易想到的是ES6的extends
,固然若是隻回答這個確定不合格,咱們要從函數和原型鏈的角度上實現繼承,下面咱們一步步地、遞進地實現一個合格的繼承
原型鏈繼承的原理很簡單,直接讓子類的原型對象指向父類實例,當子類實例找不到對應的屬性和方法時,就會往它的原型對象,也就是父類實例上找,從而實現對父類的屬性和方法的繼承
// 父類
function Parent() {
this.name = '寫代碼像蔡徐抻'
}
// 父類的原型方法
Parent.prototype.getName = function() {
return this.name
}
// 子類
function Child() {}
// 讓子類的原型對象指向父類實例, 這樣一來在Child實例中找不到的屬性和方法就會到原型對象(父類實例)上尋找
Child.prototype = new Parent()
Child.prototype.constructor = Child // 根據原型鏈的規則,順便綁定一下constructor, 這一步不影響繼承, 只是在用到constructor時會須要
// 而後Child實例就能訪問到父類及其原型上的name屬性和getName()方法
const child = new Child()
child.name // '寫代碼像蔡徐抻'
child.getName() // '寫代碼像蔡徐抻'
複製代碼
原型繼承的缺點:
super()
的功能// 示例:
function Parent() {
this.name = ['寫代碼像蔡徐抻']
}
Parent.prototype.getName = function() {
return this.name
}
function Child() {}
Child.prototype = new Parent()
Child.prototype.constructor = Child
// 測試
const child1 = new Child()
const child2 = new Child()
child1.name[0] = 'foo'
console.log(child1.name) // ['foo']
console.log(child2.name) // ['foo'] (預期是['寫代碼像蔡徐抻'], 對child1.name的修改引發了全部child實例的變化)
複製代碼
構造函數繼承,即在子類的構造函數中執行父類的構造函數,併爲其綁定子類的this
,讓父類的構造函數把成員屬性和方法都掛到子類的this
上去,這樣既能避免實例之間共享一個原型實例,又能向父類構造方法傳參
function Parent(name) {
this.name = [name]
}
Parent.prototype.getName = function() {
return this.name
}
function Child() {
Parent.call(this, 'zhangsan') // 執行父類構造方法並綁定子類的this, 使得父類中的屬性可以賦到子類的this上
}
//測試
const child1 = new Child()
const child2 = new Child()
child1.name[0] = 'foo'
console.log(child1.name) // ['foo']
console.log(child2.name) // ['zhangsan']
child2.getName() // 報錯,找不到getName(), 構造函數繼承的方式繼承不到父類原型上的屬性和方法
複製代碼
構造函數繼承的缺點:
既然原型鏈繼承和構造函數繼承各有互補的優缺點, 那麼咱們爲何不組合起來使用呢, 因此就有了綜合兩者的組合式繼承
function Parent(name) {
this.name = [name]
}
Parent.prototype.getName = function() {
return this.name
}
function Child() {
// 構造函數繼承
Parent.call(this, 'zhangsan')
}
//原型鏈繼承
Child.prototype = new Parent()
Child.prototype.constructor = Child
//測試
const child1 = new Child()
const child2 = new Child()
child1.name[0] = 'foo'
console.log(child1.name) // ['foo']
console.log(child2.name) // ['zhangsan']
child2.getName() // ['zhangsan']
複製代碼
組合式繼承的缺點:
Parent.call()
和new Parent()
),雖然這並不影響對父類的繼承,但子類建立實例時,原型中會存在兩份相同的屬性和方法,這並不優雅爲了解決構造函數被執行兩次的問題, 咱們將指向父類實例
改成指向父類原型
, 減去一次構造函數的執行
function Parent(name) {
this.name = [name]
}
Parent.prototype.getName = function() {
return this.name
}
function Child() {
// 構造函數繼承
Parent.call(this, 'zhangsan')
}
//原型鏈繼承
// Child.prototype = new Parent()
Child.prototype = Parent.prototype //將`指向父類實例`改成`指向父類原型`
Child.prototype.constructor = Child
//測試
const child1 = new Child()
const child2 = new Child()
child1.name[0] = 'foo'
console.log(child1.name) // ['foo']
console.log(child2.name) // ['zhangsan']
child2.getName() // ['zhangsan']
複製代碼
但這種方式存在一個問題,因爲子類原型和父類原型指向同一個對象,咱們對子類原型的操做會影響到父類原型,例如給Child.prototype
增長一個getName()方法,那麼會致使Parent.prototype
也增長或被覆蓋一個getName()方法,爲了解決這個問題,咱們給Parent.prototype
作一個淺拷貝
function Parent(name) {
this.name = [name]
}
Parent.prototype.getName = function() {
return this.name
}
function Child() {
// 構造函數繼承
Parent.call(this, 'zhangsan')
}
//原型鏈繼承
// Child.prototype = new Parent()
Child.prototype = Object.create(Parent.prototype) //將`指向父類實例`改成`指向父類原型`
Child.prototype.constructor = Child
//測試
const child = new Child()
const parent = new Parent()
child.getName() // ['zhangsan']
parent.getName() // 報錯, 找不到getName()
複製代碼
到這裏咱們就完成了ES5環境下的繼承的實現,這種繼承方式稱爲寄生組合式繼承
,是目前最成熟的繼承方式,babel對ES6繼承的轉化也是使用了寄生組合式繼承
咱們回顧一下實現過程:
一開始最容易想到的是原型鏈繼承
,經過把子類實例的原型指向父類實例來繼承父類的屬性和方法,但原型鏈繼承的缺陷在於對子類實例繼承的引用類型的修改會影響到全部的實例對象
以及沒法向父類的構造方法傳參
。
所以咱們引入了構造函數繼承
, 經過在子類構造函數中調用父類構造函數並傳入子類this來獲取父類的屬性和方法,但構造函數繼承也存在缺陷,構造函數繼承不能繼承到父類原型鏈上的屬性和方法
。
因此咱們綜合了兩種繼承的優勢,提出了組合式繼承
,但組合式繼承也引入了新的問題,它每次建立子類實例都執行了兩次父類構造方法
,咱們經過將子類原型指向父類實例
改成子類原型指向父類原型的淺拷貝
來解決這一問題,也就是最終實現 —— 寄生組合式繼承
上面幾點只是V8執行機制的極簡總結,建議閱讀參考資料:
1.V8 是怎麼跑起來的 —— V8 的 JavaScript 執行管道
2.JavaScript 引擎 V8 執行流程概述
JS引擎中對變量的存儲主要有兩種位置,棧內存和堆內存,棧內存存儲基本類型數據以及引用類型數據的內存地址,堆內存儲存引用類型的數據
棧內存的回收:
棧內存調用棧上下文切換後就被回收,比較簡單
堆內存的回收:
V8的堆內存分爲新生代內存和老生代內存,新生代內存是臨時分配的內存,存在時間短,老生代內存存在時間長
參考資料:聊聊V8引擎的垃圾回收
參考資料:爲何V8引擎這麼快?
defer、async
來進行異步下載width/height/padding/margin/border
)發生變化時會觸發迴流offset/scroll/client
等屬性時會觸發迴流window.getComputedStyle
會觸發迴流class
替代style
,減小style的使用resize、scroll
時進行防抖和節流處理,這二者會直接致使迴流visibility
替換display: none
,由於前者只會引發重繪,後者會引起迴流offsetWidth
這類屬性的值時,能夠使用變量將查詢結果存起來,避免屢次查詢,每次對offset/scroll/client
等屬性進行查詢時都會觸發迴流參考資料:必須明白的瀏覽器渲染機制
- Service Worker
和Web Worker相似,是獨立的線程,咱們能夠在這個線程中緩存文件,在主線程須要的時候讀取這裏的文件,Service Worker使咱們能夠自由選擇緩存哪些文件以及文件的匹配、讀取規則,而且緩存是持續性的
- Memory Cache
即內存緩存,內存緩存不是持續性的,緩存會隨着進程釋放而釋放
- Disk Cache
即硬盤緩存,相較於內存緩存,硬盤緩存的持續性和容量更優,它會根據HTTP header的字段判斷哪些資源須要緩存
- Push Cache
即推送緩存,是HTTP/2的內容,目前應用較少
強緩存(不要向服務器詢問的緩存)
設置Expires
「Expires: Thu, 26 Dec 2019 10:30:42 GMT」
表示緩存會在這個時間後失效,這個過時日期是絕對日期,若是修改了本地日期,或者本地日期與服務器日期不一致,那麼將致使緩存過時時間錯誤。設置Cache-Control
max-age
字段來設置過時時間,例如「Cache-Control:max-age=3600」
除此以外Cache-Control還能設置private/no-cache
等多種字段協商緩存(須要向服務器詢問緩存是否已通過期)
Last-Modified
Last-Modified
,當瀏覽器再次請求該資源時,瀏覽器會在請求頭中帶上If-Modified-Since
字段,字段的值就是以前服務器返回的最後修改時間,服務器對比這兩個時間,若相同則返回304,不然返回新資源,並更新Last-ModifiedETag
二者對比
參考資料:瀏覽器緩存機制剖析
模型 | 概述 | 單位 |
---|---|---|
物理層 | 網絡鏈接介質,如網線、光纜,數據在其中以比特爲單位傳輸 | bit |
數據鏈路層 | 數據鏈路層將比特封裝成數據幀並傳遞 | 幀 |
網絡層 | 定義IP地址,定義路由功能,創建主機到主機的通訊 | 數據包 |
傳輸層 | 負責將數據進行可靠或者不可靠傳遞,創建端口到端口的通訊 | 數據段 |
會話層 | 控制應用程序之間會話能力,區分不一樣的進程 | |
表示層 | 數據格式標識,基本壓縮加密功能 | |
應用層 | 各類應用軟件 |
2xx 開頭(請求成功)
200 OK
:客戶端發送給服務器的請求被正常處理並返回
3xx 開頭(重定向)
301 Moved Permanently
:永久重定向,請求的網頁已永久移動到新位置。 服務器返回此響應時,會自動將請求者轉到新位置
302 Moved Permanently
:臨時重定向,請求的網頁已臨時移動到新位置。服務器目前從不一樣位置的網頁響應請求,但請求者應繼續使用原有位置來進行之後的請求
304 Not Modified
:未修改,自從上次請求後,請求的網頁未修改過。服務器返回此響應時,不會返回網頁內容
4xx 開頭(客戶端錯誤)
400 Bad Request
:錯誤請求,服務器不理解請求的語法,常見於客戶端傳參錯誤
401 Unauthorized
:未受權,表示發送的請求須要有經過 HTTP 認證的認證信息,常見於客戶端未登陸
403 Forbidden
:禁止,服務器拒絕請求,常見於客戶端權限不足
404 Not Found
:未找到,服務器找不到對應資源
5xx 開頭(服務端錯誤)
500 Inter Server Error
:服務器內部錯誤,服務器遇到錯誤,沒法完成請求
501 Not Implemented
:還沒有實施,服務器不具有完成請求的功能
502 Bad Gateway
:做爲網關或者代理工做的服務器嘗試執行請求時,從上游服務器接收到無效的響應。
503 service unavailable
:服務不可用,服務器目前沒法使用(處於超載或停機維護狀態)。一般是暫時狀態。
標準答案:
更進一步:
其實HTTP協議並無要求GET/POST請求參數必須放在URL上或請求體裏,也沒有規定GET請求的長度,目前對URL的長度限制,是各家瀏覽器設置的限制。GET和POST的根本區別在於:GET請求是冪等性的,而POST請求不是
冪等性,指的是對某一資源進行一次或屢次請求都具備相同的反作用。例如搜索就是一個冪等的操做,而刪除、新增則不是一個冪等操做。
因爲GET請求是冪等的,在網絡很差的環境中,GET請求可能會重複嘗試,形成重複操做數據的風險,所以,GET請求用於無反作用的操做(如搜索),新增/刪除等操做適合用POST
一個HTTP請求報文由請求行(request line)、請求頭(header)、空行和請求數據4個部分組成
通用頭(請求頭和響應頭都有的首部)
字段 | 做用 | 值 |
---|---|---|
Cache-Control | 控制緩存 | public:表示響應能夠被任何對象緩存(包括客戶端/代理服務器) private(默認值):響應只能被單個客戶緩存,不能被代理服務器緩存 no-cache:緩存要通過服務器驗證,在瀏覽器使用緩存前,會對比ETag,若沒變則返回304,使用緩存 no-store:禁止任何緩存 |
Connection | 是否須要持久鏈接(HTTP 1.1默認持久鏈接) | keep-alive / close |
Transfer-Encoding | 報文主體的傳輸編碼格式 | chunked(分塊) / identity(未壓縮和修改) / gzip(LZ77壓縮) / compress(LZW壓縮,棄用) / deflate(zlib結構壓縮) |
請求頭
字段 | 做用 | 語法 |
---|---|---|
Accept | 告知(服務器)客戶端能夠處理的內容類型 | text/html、image/*、*/* |
If-Modified-Since | 將Last-Modified 的值發送給服務器,詢問資源是否已通過期(被修改),過時則返回新資源,不然返回304 |
示例:If-Modified-Since: Wed, 21 Oct 2015 07:28:00 GMT |
If-Unmodified-Since | 將Last-Modified 的值發送給服務器,詢問文件是否被修改,若沒有則返回200,不然返回412預處理錯誤,可用於斷點續傳。通俗點說If-Unmodified-Since 是文件沒有修改時下載,If-Modified-Since 是文件修改時下載 |
示例:If-Unmodified-Since: Wed, 21 Oct 2015 07:28:00 GMT |
If-None-Match | 將ETag 的值發送給服務器,詢問資源是否已通過期(被修改),過時則返回新資源,不然返回304 |
示例:If-None-Match: "bfc13a6472992d82d" |
If-Match | 將ETag 的值發送給服務器,詢問文件是否被修改,若沒有則返回200,不然返回412預處理錯誤,可用於斷點續傳 |
示例:If-Match: "bfc129c88ca92d82d" |
Range | 告知服務器返回文件的哪一部分, 用於斷點續傳 | 示例:Range: bytes=200-1000, 2000-6576, 19000- |
Host | 指明瞭服務器的域名(對於虛擬主機來講),以及(可選的)服務器監聽的TCP端口號 | 示例:Host:www.baidu.com |
User-Agent | 告訴HTTP服務器, 客戶端使用的操做系統和瀏覽器的名稱和版本 | User-Agent: Mozilla/<version> (<system-information>) <platform> (<platform-details>) <extensions> |
響應頭
字段 | 做用 | 語法 |
---|---|---|
Location | 須要將頁面從新定向至的地址。通常在響應碼爲3xx的響應中才會有意義 | Location: <url> |
ETag | 資源的特定版本的標識符,若是內容沒有改變,Web服務器不須要發送完整的響應 | ETag: "<etag_value>" |
Server | 處理請求的源頭服務器所用到的軟件相關信息 | Server: <product> |
實體頭(針對請求報文和響應報文的實體部分使用首部)
字段 | 做用 | 語法 |
---|---|---|
Allow | 資源可支持http請求的方法 | Allow: <http-methods>,示例:Allow: GET, POST, HEAD |
Last-Modified | 資源最後的修改時間,用做一個驗證器來判斷接收到的或者存儲的資源是否彼此一致,精度不如ETag | 示例:Last-Modified: Wed, 21 Oct 2020 07:28:00 GMT |
Expires | 響應過時時間 | Expires: <http-date>,示例:Expires: Wed, 21 Oct 2020 07:28:00 GMT |
HTTP首部固然不止這麼幾個,但爲了不寫太多你們記不住(主要是別的我也沒去看),這裏只介紹了一些經常使用的,詳細的能夠看MDN的文檔
Entity tag,If-Unmodified-Since, If-Match, If-None-Match
等新的請求頭來控制緩存,詳見瀏覽器緩存小節HTTP/2解決的問題,就是HTTP/1.1存在的問題:
爲了解決以上幾個問題,HTTP/2一個域名只使用一個TCP⻓鏈接來傳輸數據,並且請求直接是並行的、非阻塞的,這就是多路複用
實現原理: HTTP/2引入了一個二進制分幀層,客戶端和服務端進行傳輸時,數據會先通過二進制分幀層處理,轉化爲一個個帶有請求ID的幀,這些幀在傳輸完成後根據ID組合成對應的數據。
儘管HTTP/2解決了不少1.1的問題,但HTTP/2仍然存在一些缺陷,這些缺陷並非來自於HTTP/2協議自己,而是來源於底層的TCP協議,咱們知道TCP連接是可靠的鏈接,若是出現了丟包,那麼整個鏈接都要等待重傳,HTTP/1.1能夠同時使用6個TCP鏈接,一個阻塞另外五個還能工做,但HTTP/2只有一個TCP鏈接,阻塞的問題便被放大了。
因爲TCP協議已經被普遍使用,咱們很難直接修改TCP協議,基於此,HTTP/3選擇了一個折衷的方法——UDP協議,HTTP/2在UDP的基礎上實現多路複用、0-RTT、TLS加密、流量控制、丟包重傳等功能。
參考資料:http發展史(http0.九、http1.0、http1.一、http二、http3)梳理筆記 (推薦閱讀)
咱們經過分析幾種加密方式,層層遞進,理解HTTPS的加密方式以及爲何使用這種加密方式:
對稱加密
客戶端和服務器公用一個密匙用來對消息加解密,這種方式稱爲對稱加密。客戶端和服務器約定好一個加密的密匙。客戶端在發消息前用該密匙對消息加密,發送給服務器後,服務器再用該密匙進行解密拿到消息。
非對稱加密
採用非對稱加密時,客戶端和服務端均擁有一個公鑰和私鑰,公鑰加密的內容只有對應的私鑰能解密。私鑰本身留着,公鑰發給對方。這樣在發送消息前,先用對方的公鑰對消息進行加密,收到後再用本身的私鑰進行解密。這樣攻擊者只拿到傳輸過程當中的公鑰也沒法破解傳輸的內容
篡改公鑰
的方式來獲取或篡改傳輸內容,並且非對稱加密的性能比對稱加密的性能差了很多
第三方認證
上面這種方法的弱點在於,客戶端不知道公鑰是由服務端返回,仍是中間人返回的,所以咱們再引入一個第三方認證的環節:即第三方使用私鑰加密咱們本身的公鑰
,瀏覽器已經內置一些權威第三方認證機構的公鑰,瀏覽器會使用第三方的公鑰
來解開第三方私鑰加密過的咱們本身的公鑰
,從而獲取公鑰,若是能成功解密,就說明獲取到的本身的公鑰
是正確的
但第三方認證也未能徹底解決問題,第三方認證是面向全部人的,中間人也能申請證書,若是中間人使用本身的證書掉包原證書,客戶端仍是沒法確認公鑰的真僞
數字簽名
爲了讓客戶端可以驗證公鑰的來源,咱們給公鑰加上一個數字簽名,這個數字簽名是由企業、網站等各類信息和公鑰通過單向hash而來,一旦構成數字簽名的信息發生變化,hash值就會改變,這就構成了公鑰來源的惟一標識。
具體來講,服務端本地生成一對密鑰,而後拿着公鑰以及企業、網站等各類信息到CA(第三方認證中心)去申請數字證書,CA會經過一種單向hash算法(好比MD5),生成一串摘要,這串摘要就是這堆信息的惟一標識,而後CA還會使用本身的私鑰對摘要進行加密,連同咱們本身服務器的公鑰一同發送給我咱們。
瀏覽器拿到數字簽名後,會使用瀏覽器本地內置的CA公鑰解開數字證書並驗證,從而拿到正確的公鑰。因爲非對稱加密性能低下,拿到公鑰之後,客戶端會隨機生成一個對稱密鑰,使用這個公鑰加密併發送給服務端,服務端用本身的私鑰解開對稱密鑰,此後的加密鏈接就經過這個對稱密鑰進行對稱加密。
綜上所述,HTTPS在驗證階段使用非對稱加密+第三方認證+數字簽名獲取正確的公鑰,獲取到正確的公鑰後以對稱加密的方式通訊
參考資料:看圖學HTTPS
CSRF即Cross-site request forgery(跨站請求僞造),是一種挾制用戶在當前已登陸的Web應用程序上執行非本意的操做的攻擊方法。
假如黑客在本身的站點上放置了其餘網站的外鏈,例如"www.weibo.com/api
,默認狀況下,瀏覽器會帶着weibo.com
的cookie訪問這個網址,若是用戶已登陸過該網站且網站沒有對CSRF攻擊進行防護,那麼服務器就會認爲是用戶本人在調用此接口並執行相關操做,導致帳號被劫持。
Token
:瀏覽器請求服務器時,服務器返回一個token,每一個請求都須要同時帶上token和cookie纔會被認爲是合法請求Referer
:經過驗證請求頭的Referer來驗證來源站點,但請求頭很容易僞造SameSite
:設置cookie的SameSite,可讓cookie不隨跨域請求發出,但瀏覽器兼容不一XSS即Cross Site Scripting(跨站腳本),指的是經過利用網頁開發時留下的漏洞,注入惡意指令代碼到網頁,使用戶加載並執行攻擊者惡意製造的網頁程序。常見的例如在評論區植入JS代碼,用戶進入評論頁時代碼被執行,形成頁面被植入廣告、帳號信息被竊取
<script><iframe>
等標籤進行轉義或者過濾冒泡排序應該是不少人第一個接觸的排序,比較簡單,不展開講解了
function bubbleSort(arr){
for(let i = 0; i < arr.length; i++) {
for(let j = 0; j < arr.length - i - 1; j++) {
if(arr[j] > arr[j+1]) {
let temp = arr[j]
arr[j] = arr[j+1]
arr[j+1] = temp
}
}
}
return arr
}
複製代碼
冒泡排序總會執行(N-1)+(N-2)+(N-3)+..+2+1趟,但若是運行到當中某一趟時排序已經完成,或者輸入的是一個有序數組,那麼後邊的比較就都是多餘的,爲了不這種狀況,咱們增長一個flag,判斷排序是否在中途就已經完成(也就是判斷有無發生元素交換)
function bubbleSort(arr){
for(let i = 0; i < arr.length; i++) {
let flag = true
for(let j = 0; j < arr.length - i - 1; j++) {
if(arr[j] > arr[j+1]) {
flag = false
let temp = arr[j]
arr[j] = arr[j+1]
arr[j+1] = temp
}
}
// 這個flag的含義是:若是`某次循環`中沒有交換過元素,那麼意味着排序已經完成
if(flag)break;
}
return arr
}
複製代碼
快排基本步驟:
function quickSort(arr) {
if(arr.length <= 1) return arr //遞歸終止條件
const pivot = arr.length / 2 | 0 //基準點
const pivotValue = arr.splice(pivot, 1)[0]
const leftArr = []
const rightArr = []
arr.forEach(val => {
val > pivotValue ? rightArr.push(val) : leftArr.push(val)
})
return [ ...quickSort(leftArr), pivotValue, ...quickSort(rightArr)]
}
複製代碼
原地排序
上邊這個快排只是讓讀者找找感受,咱們不能這樣寫快排,若是每次都開兩個數組,會消耗不少內存空間,數據量大時可能形成內存溢出,咱們要避免開新的內存空間,即原地完成排序
咱們能夠用元素交換來取代開新數組,在每一次分區的時候直接在原數組上交換元素,將小於基準數的元素挪到數組開頭,以[5,1,4,2,3]
爲例:
代碼實現:
function quickSort(arr, left, right) { //這個left和right表明分區後「新數組」的區間下標,由於這裏沒有新開數組,因此須要left/right來確認新數組的位置
if (left < right) {
let pos = left - 1 //pos即「被置換的位置」,第一趟爲-1
for(let i = left; i <= right; i++) { //循環遍歷數組,置換元素
let pivot = arr[right] //選取數組最後一位做爲基準數,
if(arr[i] <= pivot) { //若小於等於基準數,pos++,並置換元素, 這裏使用小於等於而不是小於, 實際上是爲了不由於重複數據而進入死循環
pos++
let temp = arr[pos]
arr[pos] = arr[i]
arr[i] = temp
}
}
//一趟排序完成後,pos位置即基準數的位置,以pos的位置分割數組
quickSort(arr, left, pos - 1)
quickSort(arr, pos + 1, right)
}
return arr //數組只包含1或0個元素時(即left>=right),遞歸終止
}
//使用
var arr = [5,1,4,2,3]
var start = 0;
var end = arr.length - 1;
quickSort(arr, start, end)
複製代碼
這個交換的過程仍是須要一些時間理解消化的,詳細分析能夠看這篇:js算法-快速排序(Quicksort)
三路快排
上邊這個快排還談不上優化,應當說是快排的糾正寫法,其實有兩個問題咱們還能優化一下:
[1,2,2,2,2,3]
, 不管基準點取一、2仍是3, 都會致使基準點兩側數組大小不平衡, 影響快排效率對於第一個問題, 咱們能夠經過在取基準點的時候隨機化來解決,對於第二個問題,咱們能夠使用三路快排
的方式來優化,比方說對於上面的[1,2,2,2,2,3]
,咱們基準點取2,在分區的時候,將數組元素分爲小於2|等於2|大於2
三個區域,其中等於基準點的部分再也不進入下一次排序, 這樣就大大提升了快排效率
歸併排序和快排的思路相似,都是遞歸分治,區別在於快排邊分區邊排序,而歸併在分區完成後纔會排序
function mergeSort(arr) {
if(arr.length <= 1) return arr //數組元素被劃分到剩1個時,遞歸終止
const midIndex = arr.length/2 | 0
const leftArr = arr.slice(0, midIndex)
const rightArr = arr.slice(midIndex, arr.length)
return merge(mergeSort(leftArr), mergeSort(rightArr)) //先劃分,後合併
}
//合併
function merge(leftArr, rightArr) {
const result = []
while(leftArr.length && rightArr.length) {
leftArr[0] <= rightArr[0] ? result.push(leftArr.shift()) : result.push(rightArr.shift())
}
while(leftArr.length) result.push(leftArr.shift())
while(rightArr.length) result.push(rightArr.shift())
return result
}
複製代碼
堆是一棵特殊的樹, 只要知足
這棵樹是徹底二叉樹
和堆中每個節點的值都大於或小於其左右孩子節點
這兩個條件, 那麼就是一個堆, 根據堆中每個節點的值都大於或小於其左右孩子節點
, 又分爲大根堆和小根堆
堆排序的流程:
以[1,5,4,2,3]
爲例構築大根堆:
// 堆排序
const heapSort = array => {
// 咱們用數組來儲存這個大根堆,數組就是堆自己
// 初始化大頂堆,從第一個非葉子結點開始
for (let i = Math.floor(array.length / 2 - 1); i >= 0; i--) {
heapify(array, i, array.length);
}
// 排序,每一次 for 循環找出一個當前最大值,數組長度減一
for (let i = Math.floor(array.length - 1); i > 0; i--) {
// 根節點與最後一個節點交換
swap(array, 0, i);
// 從根節點開始調整,而且最後一個結點已經爲當前最大值,不須要再參與比較,因此第三個參數爲 i,即比較到最後一個結點前一個便可
heapify(array, 0, i);
}
return array;
};
// 交換兩個節點
const swap = (array, i, j) => {
let temp = array[i];
array[i] = array[j];
array[j] = temp;
};
// 將 i 結點如下的堆整理爲大頂堆,注意這一步實現的基礎其實是:
// 假設結點 i 如下的子堆已是一個大頂堆,heapify 函數實現的
// 功能是其實是:找到 結點 i 在包括結點 i 的堆中的正確位置。
// 後面將寫一個 for 循環,從第一個非葉子結點開始,對每個非葉子結點
// 都執行 heapify 操做,因此就知足告終點 i 如下的子堆已是一大頂堆
const heapify = (array, i, length) => {
let temp = array[i]; // 當前父節點
// j < length 的目的是對結點 i 如下的結點所有作順序調整
for (let j = 2 * i + 1; j < length; j = 2 * j + 1) {
temp = array[i]; // 將 array[i] 取出,整個過程至關於找到 array[i] 應處於的位置
if (j + 1 < length && array[j] < array[j + 1]) {
j++; // 找到兩個孩子中較大的一個,再與父節點比較
}
if (temp < array[j]) {
swap(array, i, j); // 若是父節點小於子節點:交換;不然跳出
i = j; // 交換後,temp 的下標變爲 j
} else {
break;
}
}
}
複製代碼
參考資料: JS實現堆排序
排序 | 時間複雜度(最好狀況) | 時間複雜度(最壞狀況) | 空間複雜度 | 穩定性 |
---|---|---|---|---|
快速排序 | O(nlogn) | O(n^2) | O(logn)~O(n) | 不穩定 |
歸併排序 | O(nlogn) | O(nlogn) | O(n) | 穩定 |
堆排序 | O(nlogn) | O(nlogn) | O(1) | 不穩定 |
其實從表格中咱們能夠看到,就時間複雜度而言,快排並無很大優點,然而爲何快排會成爲最經常使用的排序手段,這是由於時間複雜度只能說明隨着數據量的增長,算法時間代價增加的趨勢
,並不直接表明實際執行時間,實際運行時間還包括了不少常數參數的差異,此外在面對不一樣類型數據(好比有序數據、大量重複數據)時,表現也不一樣,綜合來講,快排的時間效率是最高的
在實際運用中, 並不僅使用一種排序手段, 例如V8的Array.sort()
就採起了當 n<=10 時, 採用插入排序, 當 n>10 時,採用三路快排的排序策略
設計模式有許多種,這裏挑出幾個經常使用的:
設計模式 | 描述 | 例子 |
---|---|---|
單例模式 | 一個類只能構造出惟一實例 | Redux/Vuex的store |
工廠模式 | 對建立對象邏輯的封裝 | jQuery的$(selector) |
觀察者模式 | 當一個對象被修改時,會自動通知它的依賴對象 | Redux的subscribe、Vue的雙向綁定 |
裝飾器模式 | 對類的包裝,動態地拓展類的功能 | React高階組件、ES7 裝飾器 |
適配器模式 | 兼容新舊接口,對類的包裝 | 封裝舊API |
代理模式 | 控制對象的訪問 | 事件代理、ES6的Proxy |
單一職責原則:一個類只負責一個功能領域中的相應職責,或者能夠定義爲:就一個類而言,應該只有一個引發它變化的緣由。
開放封閉原則:核心的思想是軟件實體(類、模塊、函數等)是可擴展的、但不可修改的。也就是說,對擴展是開放的,而對修改是封閉的。
單例模式即一個類只能構造出惟一實例,單例模式的意義在於共享、惟一,Redux/Vuex
中的store、JQ
的$或者業務場景中的購物車、登陸框都是單例模式的應用
class SingletonLogin {
constructor(name,password){
this.name = name
this.password = password
}
static getInstance(name,password){
//判斷對象是否已經被建立,若建立則返回舊對象
if(!this.instance)this.instance = new SingletonLogin(name,password)
return this.instance
}
}
let obj1 = SingletonLogin.getInstance('CXK','123')
let obj2 = SingletonLogin.getInstance('CXK','321')
console.log(obj1===obj2) // true
console.log(obj1) // {name:CXK,password:123}
console.log(obj2) // 輸出的依然是{name:CXK,password:123}
複製代碼
工廠模式即對建立對象邏輯的封裝,或者能夠簡單理解爲對new
的封裝,這種封裝就像建立對象的工廠,故名工廠模式。工廠模式常見於大型項目,好比JQ的$對象,咱們建立選擇器對象時之因此沒有new selector就是由於$()已是一個工廠方法,其餘例子例如React.createElement()
、Vue.component()
都是工廠模式的實現。工廠模式有多種:簡單工廠模式
、工廠方法模式
、抽象工廠模式
,這裏只以簡單工廠模式爲例:
class User {
constructor(name, auth) {
this.name = name
this.auth = auth
}
}
class UserFactory {
static createUser(name, auth) {
//工廠內部封裝了建立對象的邏輯:
//權限爲admin時,auth=1, 權限爲user時, auth爲2
//使用者在外部建立對象時,不須要知道各個權限對應哪一個字段, 不須要知道賦權的邏輯,只須要知道建立了一個管理員和用戶
if(auth === 'admin') new User(name, 1)
if(auth === 'user') new User(name, 2)
}
}
const admin = UserFactory.createUser('cxk', 'admin');
const user = UserFactory.createUser('cxk', 'user');
複製代碼
觀察者模式算是前端最經常使用的設計模式了,觀察者模式概念很簡單:觀察者監聽被觀察者的變化,被觀察者發生改變時,通知全部的觀察者。觀察者模式被普遍用於監聽事件的實現,有關觀察者模式的詳細應用,能夠看我另外一篇講解Redux實現的文章
//觀察者
class Observer {
constructor (fn) {
this.update = fn
}
}
//被觀察者
class Subject {
constructor() {
this.observers = [] //觀察者隊列
}
addObserver(observer) {
this.observers.push(observer)//往觀察者隊列添加觀察者
}
notify() { //通知全部觀察者,其實是把觀察者的update()都執行了一遍
this.observers.forEach(observer => {
observer.update() //依次取出觀察者,並執行觀察者的update方法
})
}
}
var subject = new Subject() //被觀察者
const update = () => {console.log('被觀察者發出通知')} //收到廣播時要執行的方法
var ob1 = new Observer(update) //觀察者1
var ob2 = new Observer(update) //觀察者2
subject.addObserver(ob1) //觀察者1訂閱subject的通知
subject.addObserver(ob2) //觀察者2訂閱subject的通知
subject.notify() //發出廣播,執行全部觀察者的update方法
複製代碼
有些文章也把觀察者模式稱爲發佈訂閱模式,其實兩者是有所區別的,發佈訂閱相較於觀察者模式多一個調度中心。
裝飾器模式,能夠理解爲對類的一個包裝,動態地拓展類的功能,ES7的裝飾器
語法以及React中的高階組件
(HoC)都是這一模式的實現。react-redux的connect()也運用了裝飾器模式,這裏以ES7的裝飾器爲例:
function info(target) {
target.prototype.name = '張三'
target.prototype.age = 10
}
@info
class Man {}
let man = new Man()
man.name // 張三
複製代碼
適配器模式,將一個接口轉換成客戶但願的另外一個接口,使接口不兼容的那些類能夠一塊兒工做。咱們在生活中就經常有使用適配器的場景,例如出境旅遊插頭插座不匹配,這時咱們就須要使用轉換插頭,也就是適配器來幫咱們解決問題。
class Adaptee {
test() {
return '舊接口'
}
}
class Target {
constructor() {
this.adaptee = new Adaptee()
}
test() {
let info = this.adaptee.test()
return `適配${info}`
}
}
let target = new Target()
console.log(target.test())
複製代碼
代理模式,爲一個對象找一個替代對象,以便對原對象進行訪問。即在訪問者與目標對象之間加一層代理,經過代理作受權和控制。最多見的例子是經紀人代理明星業務,假設你做爲一個投資者,想聯繫明星打廣告,那麼你就須要先通過代理經紀人,經紀人對你的資質進行考察,並通知你明星排期,替明星本人過濾沒必要要的信息。事件代理、JQuery的$.proxy
、ES6的proxy
都是這一模式的實現,下面以ES6的proxy爲例:
const idol = {
name: '蔡x抻',
phone: 10086,
price: 1000000 //報價
}
const agent = new Proxy(idol, {
get: function(target) {
//攔截明星電話的請求,只提供經紀人電話
return '經紀人電話:10010'
},
set: function(target, key, value) {
if(key === 'price' ) {
//經紀人過濾資質
if(value < target.price) throw new Error('報價太低')
target.price = value
}
}
})
agent.phone //經紀人電話:10010
agent.price = 100 //Uncaught Error: 報價太低
複製代碼
aside / figure / section / header / footer / nav
等),增長多媒體標籤video
和audio
,使得樣式和結構更加分離input
的type屬性;meta
增長charset以設置字符集;script
增長async以異步加載腳本localStorage
、sessionStorage
和indexedDB
,引入了application cache
對web和應用進行緩存拖放API
、地理定位
、SVG繪圖
、canvas繪圖
、Web Worker
、WebSocket
聲明文檔類型,告知瀏覽器用什麼文檔標準解析這個文檔:
href(hyperReference)
即超文本引用:當瀏覽器遇到href時,會並行的地下載資源,不會阻塞頁面解析,例如咱們使用<link>
引入CSS,瀏覽器會並行地下載CSS而不阻塞頁面解析. 所以咱們在引入CSS時建議使用<link>
而不是@import
<link href="style.css" rel="stylesheet" />
複製代碼
src(resource)
即資源,當瀏覽器遇到src時,會暫停頁面解析,直到該資源下載或執行完畢,這也是script標籤之因此放底部的緣由
<script src="script.js"></script>
複製代碼
meta標籤用於描述網頁的元信息
,如網站做者、描述、關鍵詞,meta經過name=xxx
和content=xxx
的形式來定義信息,經常使用設置以下:
<meta charset="UTF-8" >
複製代碼
<meta http-equiv="expires" content="Wed, 20 Jun 2019 22:33:00 GMT">
複製代碼
<meta
name="viewport"
content="width=device-width, initial-scale=1, maximum-scale=1"
>
複製代碼
爲何要清除浮動:清除浮動是爲了解決子元素浮動而致使父元素高度塌陷的問題
<div class="parent">
<div class="child"></div>
<!-- 添加一個空元素,利用css提供的clear:both清除浮動 -->
<div style="clear: both"></div>
</div>
複製代碼
2.使用僞元素
/* 對父元素添加僞元素 */
.parent::after{
content: "";
display: block;
height: 0;
clear:both;
}
複製代碼
3.觸發父元素BFC
/* 觸發父元素BFC */
.parent {
overflow: hidden;
/* float: left; */
/* position: absolute; */
/* display: inline-block */
/* 以上屬性都可觸發BFC */
}
複製代碼
其實我原本還寫了一節水平/垂直居中相關的,不過感受內容過於基礎還佔長篇幅,因此刪去了,做爲一篇總結性的文章,這一小節也不該該從「flex是什麼」開始講,主軸、側軸這些概念相信用過flex佈局都知道,因此咱們直接flex的幾個屬性講起:
容器屬性(使用在flex佈局容器上的屬性)
.container {
justify-content: center | flex-start | flex-end | space-between | space-around;
/* 主軸對齊方式:居中 | 左對齊(默認值) | 右對齊 | 兩端對齊(子元素間邊距相等) | 周圍對齊(每一個子元素兩側margin相等) */
}
複製代碼
.container {
align-items: center | flex-start | flex-end | baseline | stretch;
/* 側軸對齊方式:居中 | 上對齊 | 下對齊 | 項目的第一行文字的基線對齊 | 若是子元素未設置高度,將佔滿整個容器的高度(默認值) */
}
複製代碼
.container {
flex-direction: row | row-reverse | column | column-reverse;
/* 主軸方向:水平由左至右排列(默認值) | 水平由右向左 | 垂直由上至下 | 垂直由下至上 */
}
複製代碼
.container {
flex-wrap: nowrap | wrap | wrap-reverse;
/* 換行方式:不換行(默認值) | 換行 | 反向換行 */
}
複製代碼
.container {
flex-flow: <flex-direction> || <flex-wrap>;
/* 默認值:row nowrap */
}
複製代碼
.container {
align-content: center | flex-start | flex-end | space-between | space-around | stretch;
/* 默認值:與交叉軸的中點對齊 | 與交叉軸的起點對齊 | 與交叉軸的終點對齊 | 與交叉軸兩端對齊 | 每根軸線兩側的間隔都相等 | (默認值):軸線佔滿整個交叉軸 */
}
複製代碼
項目屬性(使用在容器內子元素上的屬性)
.item {
flex-grow: <number>; /* default 0 */
}
複製代碼
flex-shrink
都爲1,某個子元素flex-shrink
爲0,那麼該子元素將不縮小.item {
flex-shrink: <number>; /* default 1 */
}
複製代碼
.item {
flex-basis: <length> | auto; /* default auto */
}
複製代碼
flex-grow
, flex-shrink
和 flex-basis
的簡寫,默認值爲0 1 auto,即有剩餘空間不放大,剩餘空間不夠將縮小,子元素佔據自身大小.item {
flex: none | [ <'flex-grow'> <'flex-shrink'>? || <'flex-basis'> ]
}
複製代碼
flex有兩個快捷值:auto
和none
,分別表明1 1 auto
(有剩餘空間則平均分配,空間不夠將等比縮小,子元素佔據空間等於自身大小)和0 0 auto
(有剩餘空間也不分配,空間不夠也不縮小,子元素佔據空間等於自身大小)
.item {
order: <integer>;
}
複製代碼
.item {
align-self: auto | flex-start | flex-end | center | baseline | stretch;
}
複製代碼
參考資料:阮一峯Flex佈局
編輯中,請稍等-_-||
BFC全稱 Block Formatting Context 即塊級格式上下文
,簡單的說,BFC是頁面上的一個隔離的獨立容器,不受外界干擾或干擾外界
float
不爲 noneoverflow
的值不爲 visibleposition
爲 absolute 或 fixeddisplay
的值爲 inline-block 或 table-cell 或 table-caption 或 grid對於前端基礎知識的講解,到這裏就告一小段落。前端的世界紛繁複雜,遠非筆者寥寥幾筆所能勾畫,筆者就像在沙灘上拾取貝殼的孩童,有時僥倖拾取收集一二,就爲之歡欣鼓舞,火燒眉毛與夥伴們分享。
最後還想可恥地抒(自)發(誇)一下(•‾̑⌣‾̑•)✧˖°:
不知不覺,在掘金已經水了半年有餘,這半年來我寫下了近6萬字,不過其實一共只有5篇文章,這是由於我並不想寫水文,不想把基礎的東西水上幾千字幾十篇來混贊升級。寫下的文章,首先要能說服本身。要對本身寫下的東西負責任,即便是一張圖、一個標點。例如第一張圖,我調整了不下十次,第一次我直接截取babel的轉化結果,以爲很差看,換成了代碼塊,仍是很差看,又換成了carbon的代碼圖,第一次下載,發現兩張圖寬度不同,填充寬度從新下載,又發現本身的代碼少了一個空格,從新下載,爲了實現兩張圖並排效果,寫了一個HTML來調整兩張圖的樣式,爲了保證每張圖的內容和邊距一致,我一邊截圖,一邊記錄下每次截圖的尺寸和邊距,每次截圖都根據上一次的數據調整邊距。
其實我並不是提倡把時間花在這些細枝末節上,只是單純以爲,文章沒寫好,就不能發出來,就像小野二郎先生說的那樣:「菜作的很差,就不能拿給客人吃」,世間的大道理,每每都這樣通俗簡單。
往期文章:
1. 異步編程二三事 | Promise/async/Generator實現原理解析 | 9k字
2. 10行代碼看盡redux實現 —— redux & react-redux & redux中間件設計實現 | 8k字
3.紅黑樹上紅黑果,紅黑樹下你和我 —— 紅黑樹入門 | 6k字
4. 深刻React服務端渲染原理 | 1W字