是時候擼一波 JS 基礎啦,擼熟了,銀十速拿 offer;
本文不從傳統的問答方式梳理,而是從知識維度梳理,以便造成知識網絡
包括函數,數組,對象,數據結構,算法,設計模式和 http.
//ES5 function getSum(){} function (){}//匿名函數 //ES6 ()=>{}//若是{}內容只有一行{}和return關鍵字可省,
//ES5 var sum=function(){} //ES6 let sum=()=>{}//若是{}內容只有一行{}和return關鍵字可省,
const sum = new Function('a', 'b' , 'return a + b')
1.函數聲明有預解析,並且函數聲明的優先級高於變量;
2.使用Function構造函數定義函數的方式是一個函數表達式,這種方式會致使解析兩次代碼,影響性能。第一次解析常規的JavaScript代碼,第二次解析傳入構造函數的字符串html
在ES5中函數內容的this指向和調用方法有關vue
包括函數名()和匿名函數調用,this指向windownode
function getSum() { console.log(this) //window } getSum() (function() { console.log(this) //window })() var getSum=function() { console.log(this) //window } getSum()
對象.方法名(),this指向對象react
var objList = { name: 'methods', getSum: function() { console.log(this) //objList對象 } } objList.getSum()
new 構造函數名(),this指向構造函數es6
function Person() { console.log(this); //指向實例 } var personOne = new Person();
利用call和apply來實現,this就是call和apply對應的第一個參數,若是不傳值或者第一個值爲null,undefined時this指向window算法
function foo() { console.log(this); } foo.apply('我是apply改變的this值');//我是apply改變的this值 foo.call('我是call改變的this值');//我是call改變的this值
箭頭函數不能夠看成構造函數使用,也就是不能用new命令實例化一個對象,不然會拋出一個錯誤
箭頭函數的this是和定義時有關和調用無關
調用就是函數調用模式json
(() => { console.log(this)//window })() let arrowFun = () => { console.log(this)//window } arrowFun() let arrowObj = { arrFun: function() { (() => { console.log(this)//指向函數arrFun })() } } arrowObj.arrFun();
1.IE5以前不支持call和apply,bind是ES5出來的;
2.call和apply能夠調用函數,改變this,實現繼承和借用別的對象的方法;小程序
調用方法,用一個對象替換掉另外一個對象(this)
對象.call(新this對象,實參1,實參2,實參3.....)
對象.apply(新this對象,[實參1,實參2,實參3.....])設計模式
1.間接調用函數,改變做用域的this值
2.劫持其餘對象的方法api
var foo = { name:"張三", logName:function(){ console.log(this.name); } } var bar={ name:"李四" }; foo.logName.call(bar);//李四 實質是call改變了foo的this指向爲bar,並調用該函數
3.兩個函數實現繼承
function Animal(name){ this.name = name; this.showName = function(){ console.log(this.name); } } function Cat(name){ Animal.call(this, name); } var cat = new Cat("Black Cat"); cat.showName(); //Black Cat
4.爲類數組(arguments和nodeList)添加數組方法push,pop
(function(){ Array.prototype.push.call(arguments,'王五'); console.log(arguments);//['張三','李四','王五'] })('張三','李四')
5.合併數組
let arr1=[1,2,3]; let arr2=[4,5,6]; Array.prototype.push.apply(arr1,arr2); //將arr2合併到了arr1中
6.求數組最大值
Math.max.apply(null,arr)
7.判斷字符類型
Object.prototype.toString.call({})
bind是function的一個函數擴展方法,bind之後代碼從新綁定了func內部的this指向,不會調用方法,不兼容IE8
var name = '李四' var foo = { name: "張三", logName: function(age) { console.log(this.name, age); } } var fooNew = foo.logName; var fooNewBind = foo.logName.bind(foo); fooNew(10)//李四,10 fooNewBind(11)//張三,11 由於bind改變了fooNewBind裏面的this指向
call實現:
Function.prototype.newCall = function(context, ...parameter) { if (typeof context === 'object' || typeof context === 'function') { context = context || window } else { context = Object.create(null) } context[fn] = this const res =context[fn](...parameter) delete context.fn; return res } let person = { name: 'Abiel' } function sayHi(age,sex) { console.log(this.name, age, sex); } sayHi.newCall (person, 25, '男'); // Abiel 25 男
apply實現:
Function.prototype.newApply = function(context, parameter) { if (typeof context === 'object' || typeof context === 'function') { context = context || window } else { context = Object.create(null) } let fn = Symbol() context[fn] = this return res=context[fn](..parameter); delete context[fn] return res } sayHi.newApply (person,[ 25, '男']) //Abiel 25 男
bind實現:
Function.prototype.bind = function (context,...innerArgs) { var me = this return function (...finnalyArgs) { return me.call(context,...innerArgs,...finnalyArgs) } } let person = { name: 'Abiel' } function sayHi(age,sex) { console.log(this.name, age, sex); } let personSayHi = sayHi.bind(person, 25) personSayHi('男')
同:都是改變this指向,均可接收參數
異:bind和call是接收單個參數,apply是接收數組
類型 | 概念 | 應用 |
---|---|---|
節流 | 事件觸發後每隔一段時間觸發一次,可觸發屢次 | scroll,resize事件一段時間觸發屢次 |
防抖 | 事件觸發動做完後一段時間觸發一次 | scroll,resize事件觸發完後一段時間觸發 |
節流:
let throttle = function(func, delay) { let timer = null; return function() { if (!timer) { timer = setTimeout(()=> { func.apply(this, arguments); timer = null; }, delay); } }; }; function handle() { console.log(Math.random()); } window.addEventListener("scroll", throttle(handle, 1000)); //事件處理函數
function debounce(fn, wait) { let timeout = null; return function() { if (timeout !== null) clearTimeout(timeout);//若是屢次觸發將上次記錄延遲清除掉 timeout = setTimeout(()=> { fn.apply(this, arguments); timeout = null; }, wait); }; } // 處理函數 function handle() { console.log(Math.random()); } // 滾動事件 window.addEventListener("scroll", debounce(handle, 1000));
對象繼承屬性的一個鏈條
var Person = function (name) { this.name = name; }//person是構造函數 var o3personTwo = new Person('personTwo')//personTwo是實例
原型對象都有一個默認的constructor屬性指向構造函數
1.字面量
let obj={'name':'張三'}
2.Object構造函數建立
let Obj=new Object() Obj.name='張三'
3.使用工廠模式建立對象
function createPerson(name){ var o = new Object(); o.name = name; }; return o; } var person1 = createPerson('張三');
4.使用構造函數建立對象
function Person(name){ this.name = name; } var person1 = new Person('張三');
1.創了一個新對象;
2.this指向構造函數;
3.構造函數有返回,會替換new出來的對象,若是沒有就是new出來的對象
4.手動封裝一個new運算符
var new2 = function (func) { var o = Object.create(func.prototype); //建立對象 var k = func.call(o); //改變this指向,把結果付給k if (k&&typeof k === 'object') { //判斷k的類型是否是對象 return k; //是,返回k } else { return o; //不是返回返回構造函數的執行結果 } }
JS是一門弱類型動態語言,封裝和繼承是他的兩大特性
將父類的實例做爲子類的原型
1.代碼實現
定義父類:
// 定義一個動物類 function Animal (name) { // 屬性 this.name = name || 'Animal'; // 實例方法 this.sleep = function(){ console.log(this.name + '正在睡覺!'); } } // 原型方法 Animal.prototype.eat = function(food) { console.log(this.name + '正在吃:' + food); };
子類:
function Cat(){ } Cat.prototype = new Animal(); Cat.prototype.name = 'cat'; // Test Code var cat = new Cat(); console.log(cat.name);//cat console.log(cat.eat('fish'));//cat正在吃:fish undefined console.log(cat.sleep());//cat正在睡覺! undefined console.log(cat instanceof Animal); //true console.log(cat instanceof Cat); //true
2.優缺點
簡單易於實現,可是要想爲子類新增屬性和方法,必需要在new Animal()這樣的語句以後執行,沒法實現多繼承
實質是利用call來改變Cat中的this指向
1.代碼實現
子類:
function Cat(name){ Animal.call(this); this.name = name || 'Tom'; }
2.優缺點
能夠實現多繼承,不能繼承原型屬性/方法
爲父類實例添加新特性,做爲子類實例返回
1.代碼實現
子類
function Cat(name){ var instance = new Animal(); instance.name = name || 'Tom'; return instance; }
2.優缺點
不限制調用方式,但不能實現多繼承
將父類的屬性和方法拷貝一份到子類中
1.子類:
function Cat(name){ var animal = new Animal(); for(var p in animal){ Cat.prototype[p] = animal[p]; } Cat.prototype.name = name || 'Tom'; }
2.優缺點
支持多繼承,可是效率低佔用內存
經過調用父類構造,繼承父類的屬性並保留傳參的優勢,而後經過將父類實例做爲子類原型,實現函數複用
1.子類:
function Cat(name){ Animal.call(this); this.name = name || 'Tom'; } Cat.prototype = new Animal(); Cat.prototype.constructor = Cat;
function Cat(name){ Animal.call(this); this.name = name || 'Tom'; } (function(){ // 建立一個沒有實例方法的類 var Super = function(){}; Super.prototype = Animal.prototype; //將實例做爲子類的原型 Cat.prototype = new Super(); })();
ES6 的繼承機制是先創造父類的實例對象this(因此必須先調用super方法),而後再用子類的構造函數修改this,連接描述
//父類 class Person { //constructor是構造方法 constructor(skin, language) { this.skin = skin; this.language = language; } say() { console.log('我是父類') } } //子類 class Chinese extends Person { constructor(skin, language, positon) { //console.log(this);//報錯 super(skin, language); //super();至關於父類的構造函數 //console.log(this);調用super後獲得了this,不報錯,this指向子類,至關於調用了父類.prototype.constructor.call(this) this.positon = positon; } aboutMe() { console.log(`${this.skin} ${this.language} ${this.positon}`); } } //調用只能經過new的方法獲得實例,再調用裏面的方法 let obj = new Chinese('紅色', '中文', '香港'); obj.aboutMe(); obj.say();
函數的參數是函數或返回函數
map,reduce,filter,sort
1.定義:只傳遞給函數一部分參數來調用它,讓它返回一個函數去處理剩下的參數
fn(a,b,c,d)=>fn(a)(b)(c)(d)
2.代碼實現:
const currying = fn => { const len = fn.length return function curr (...args1) { if (args1.length >= len) { return fn(...args1) } return (...args2) => curr(...args1, ...args2) }
}
1.定義:
obj.func(arg1, arg2)=>func(obj, arg1, arg2)
2.代碼實現:
Function.prototype.uncurrying = function() { var that = this; return function() { return Function.prototype.call.apply(that, arguments); } }; function sayHi () { return "Hello " + this.value +" "+[].slice.call(arguments); } let sayHiuncurrying=sayHi.uncurrying(); console.log(sayHiuncurrying({value:'world'},"hahaha"));
1.定義:指定部分參數來返回一個新的定製函數的形式
2.例子:
function foo(a, b, c) { return a + b + c; } function func(a, b) { return foo(a,b,8); }
var test2 = {x:123,y:345}; console.log(test2);//{x:123,y:345}; console.log(test2.x);//123 console.log(test2.__proto__.x);//undefined console.log(test2.__proto__.x === test2.x);//false
var test1 = new Object({x:123,y:345}); console.log(test1);//{x:123,y:345} console.log(test1.x);//123 console.log(test1.__proto__.x);//undefined console.log(test1.__proto__.x === test1.x);//false
new的做用:
1.創了一個新對象;
2.this指向構造函數;
3.構造函數有返回,會替換new出來的對象,若是沒有就是new出來的對象
Obejct.create(obj,descriptor),obj是對象,describe描述符屬性(可選)
let test = Object.create({x:123,y:345}); console.log(test);//{} console.log(test.x);//123 console.log(test.__proto__.x);//3 console.log(test.__proto__.x === test.x);//true
1.功能:都能實現對象的聲明,並可以賦值和取值
2.繼承性:內置方法建立的對象繼承到__proto__屬性上
3.隱藏屬性:三種聲明方法會默認爲內部的每一個成員(屬性或方法)生成一些隱藏屬性,這些隱藏屬性是能夠讀取和可配置的,屬性分類見下面
4.屬性讀取:Object.getOwnPropertyDescriptor()或getOwnPropertyDescriptor()
5.屬性設置:Object.definePropertype或Object.defineProperties
1.數據屬性4個特性:
configurable(可配置),enumerable(可枚舉),writable(可修改),value(屬性值)
2.訪問器屬性2個特性:
get(獲取),set(設置)
3.內部屬性
由JavaScript引擎內部使用的屬性;
不能直接訪問,可是能夠經過對象內置方法間接訪問,如:[[Prototype]]能夠經過 Object.getPrototypeOf()訪問;
內部屬性用[[]]包圍表示,是一個抽象操做,沒有對應字符串類型的屬性名,如[[Prototype]].
1.定義:將一個屬性的全部特性編碼成一個對象返回
2.描述符的屬性有:數據屬性和訪問器屬性
3.使用範圍:
做爲方法Object.defineProperty, Object.getOwnPropertyDescriptor, Object.create的第二個參數,
1.訪問對象存在的屬性
特性名 | 默認值 |
---|---|
value | 對應屬性值 |
get | 對應屬性值 |
set | undefined |
writable | true |
enumerable | true |
configurable | true |
因此經過上面三種聲明方法已存在的屬性都是有這些默認描述符
2.訪問對象不存在的屬性
特性名 | 默認值 |
---|---|
value | undefined |
get | undefined |
set | undefined |
writable | false |
enumerable | false |
configurable | false |
get,set與wriable,value是互斥的,若是有交集設置會報錯
1.定義屬性的函數有兩個:Object.defineProperty和Object.defineProperties.例如:
Object.defineProperty(obj, propName, desc)
2.在引擎內部,會轉換成這樣的方法調用:
obj.[[DefineOwnProperty]](propName, desc, true)
1.賦值運算符(=)就是在調用[[Put]].好比:
obj.prop = v;
2.在引擎內部,會轉換成這樣的方法調用:
obj.[[Put]]("prop", v, isStrictModeOn)
名稱 | 含義 | 用法 |
---|---|---|
in | 若是指定的屬性在指定的對象或其原型鏈中,則in 運算符返回true | 'name' in test //true |
hasOwnProperty() | 只判斷自身屬性 | test.hasOwnProperty('name') //true |
.或[] | 對象或原型鏈上不存在該屬性,則會返回undefined | test.name //"lei" test["name"] //"lei" |
是一種數據類型;
不能new,由於Symbol是一個原始類型的值,不是對象。
Symbol(),能夠傳參
var s1 = Symbol(); var s2 = Symbol(); s1 === s2 // false // 有參數的狀況 var s1 = Symbol("foo"); var s2 = Symbol("foo"); s1 === s2 // false
1.不能與其餘類型的值進行運算;
2.做爲屬性名
let mySymbol = Symbol(); // 第一種寫法 var a = {}; a[mySymbol] = 'Hello!'; // 第二種寫法 var a = { [mySymbol]: 'Hello!' }; // 第三種寫法 var a = {}; Object.defineProperty(a, mySymbol, { value: 'Hello!' }); // 以上寫法都獲得一樣結果 a[mySymbol] // "Hello!"
3.做爲對象屬性名時,不能用點運算符,能夠用[]
let a = {}; let name = Symbol(); a.name = 'lili'; a[name] = 'lucy'; console.log(a.name,a[name]);
4.遍歷不會被for...in、for...of和Object.keys()、Object.getOwnPropertyNames()取到該屬性
1.定義:在全局中搜索有沒有以該參數做爲名稱的Symbol值,若是有,就返回這個Symbol值,不然就新建並返回一個以該字符串爲名稱的Symbol值
2.舉例:
var s1 = Symbol.for('foo'); var s2 = Symbol.for('foo'); s1 === s2 // true
1.定義:返回一個已登記的Symbol類型值的key
2.舉例:
var s1 = Symbol.for("foo"); Symbol.keyFor(s1) // "foo" var s2 = Symbol("foo"); Symbol.keyFor(s2) // undefined
方法 | 特性 |
---|---|
for ... in | 遍歷對象自身的和繼承的可枚舉屬性(不含Symbol屬性) |
Object.keys(obj) | 返回一個數組,包括對象自身的(不含繼承的)全部可枚舉屬性(不含Symbol屬性) |
Object.getOwnPropertyNames(obj) | 返回一個數組,包括對象自身的全部可枚舉屬性(不含Symbol屬性) |
Object.getOwnPropertySymbols(obj) | 返回一個數組,包含對象自身的全部Symbol屬性 |
Reflect.ownKeys(obj) | 返回一個數組,包含對象自身的全部(不枚舉、可枚舉和Symbol)屬性 |
Reflect.enumerate(obj) | 返回一個Iterator對象,遍歷對象自身的和繼承的全部可枚舉屬性(不含Symbol屬性) |
總結:1.只有Object.getOwnPropertySymbols(obj)和Reflect.ownKeys(obj)能夠拿到Symbol屬性
2.只有Reflect.ownKeys(obj)能夠拿到不可枚舉屬性
數據模型:
var treeNodes = [ { id: 1, name: '1', children: [ { id: 11, name: '11', children: [ { id: 111, name: '111', children:[] }, { id: 112, name: '112' } ] }, { id: 12, name: '12', children: [] } ], users: [] }, ];
遞歸:
var parseTreeJson = function(treeNodes){ if (!treeNodes || !treeNodes.length) return; for (var i = 0, len = treeNodes.length; i < len; i++) { var childs = treeNodes[i].children; console.log(treeNodes[i].id); if(childs && childs.length > 0){ parseTreeJson(childs); } } }; console.log('------------- 遞歸實現 ------------------'); parseTreeJson(treeNodes);
1.定義:將源對象(source)的全部可枚舉屬性,複製到目標對象(target)
2.用法:
合併多個對象 var target = { a: 1, b: 1 }; var source1 = { b: 2, c: 2 }; var source2 = { c: 3 }; Object.assign(target, source1, source2);
3.注意:
這個是僞深度拷貝,只能拷貝第一層
1.原理:是將對象轉化爲字符串,而字符串是簡單數據類型
function deepClone(source){ const targetObj = source.constructor === Array ? [] : {}; // 判斷複製的目標是數組仍是對象 for(let keys in source){ // 遍歷目標 if(source.hasOwnProperty(keys)){ if(source[keys] && typeof source[keys] === 'object'){ // 若是值是對象,就遞歸一下 targetObj[keys] = source[keys].constructor === Array ? [] : {}; targetObj[keys] = deepClone(source[keys]); }else{ // 若是不是,就直接賦值 targetObj[keys] = source[keys]; } } } return targetObj; }
定義:利用對象內置方法,設置屬性,進而改變對象的屬性值
1.ES5出來的方法;
2.三個參數:對象(必填),屬性值(必填),描述符(可選);
3.defineProterty的描述符屬性
數據屬性:value,writable,configurable,enumerable 訪問器屬性:get,set 注:不能同時設置value和writable,這兩對屬性是互斥的
4.攔截對象的兩種狀況:
let obj = {name:'',age:'',sex:'' }, defaultName = ["這是姓名默認值1","這是年齡默認值1","這是性別默認值1"]; Object.keys(obj).forEach(key => { Object.defineProperty(obj, key, { get() { return defaultName; }, set(value) { defaultName = value; } }); }); console.log(obj.name); console.log(obj.age); console.log(obj.sex); obj.name = "這是改變值1"; console.log(obj.name); console.log(obj.age); console.log(obj.sex); let objOne={},defaultNameOne="這是默認值2"; Object.defineProperty(obj, 'name', { get() { return defaultNameOne; }, set(value) { defaultNameOne = value; } }); console.log(objOne.name); objOne.name = "這是改變值2"; console.log(objOne.name);
5.攔截數組變化的狀況
let a={}; bValue=1; Object.defineProperty(a,"b",{ set:function(value){ bValue=value; console.log("setted"); }, get:function(){ return bValue; } }); a.b;//1 a.b=[];//setted a.b=[1,2,3];//setted a.b[1]=10;//無輸出 a.b.push(4);//無輸出 a.b.length=5;//無輸出 a.b;//[1,10,3,4,undefined]; 結論:defineProperty沒法檢測數組索引賦值,改變數組長度的變化; 可是經過數組方法來操做能夠檢測到
多級對象監聽
let info = {}; function observe(obj) { if (!obj || typeof obj !== "object") { return; } for (var i in obj) { definePro(obj, i, obj[i]); } } function definePro(obj, key, value) { observe(value); Object.defineProperty(obj, key, { get: function() { return value; }, set: function(newval) { console.log("檢測變化", newval); value = newval; } }); } definePro(info, "friends", { name: "張三" }); info.friends.name = "李四";
6.存在的問題
不能監聽數組索引賦值和改變長度的變化 必須深層遍歷嵌套的對象,由於defineProterty只能劫持對象的屬性,所以咱們須要對每一個對象的每一個屬性進行遍歷,若是屬性值也是對象那麼須要深度遍歷,顯然能劫持一個完整的對象是更好的選擇
1.ES6出來的方法,實質是對對象作了一個攔截,並提供了13個處理方法
13個方法詳情請戳,阮一峯的proxy介紹
2.兩個參數:對象和行爲函數
let handler = { get(target, key, receiver) { console.log("get", key); return Reflect.get(target, key, receiver); }, set(target, key, value, receiver) { console.log("set", key, value); return Reflect.set(target, key, value, receiver); } }; let proxy = new Proxy(obj, handler); proxy.name = "李四"; proxy.age = 24;
涉及到多級對象或者多級數組監聽
//傳遞兩個參數,一個是object, 一個是proxy的handler //若是是否是嵌套的object,直接加上proxy返回,若是是嵌套的object,那麼進入addSubProxy進行遞歸。 function toDeepProxy(object, handler) { if (!isPureObject(object)) addSubProxy(object, handler); return new Proxy(object, handler); //這是一個遞歸函數,目的是遍歷object的全部屬性,若是不是pure object,那麼就繼續遍歷object的屬性的屬性,若是是pure object那麼就加上proxy function addSubProxy(object, handler) { for (let prop in object) { if ( typeof object[prop] == 'object') { if (!isPureObject(object[prop])) addSubProxy(object[prop], handler); object[prop] = new Proxy(object[prop], handler); } } object = new Proxy(object, handler) } //是否是一個pure object,意思就是object裏面沒有再嵌套object了 function isPureObject(object) { if (typeof object!== 'object') { return false; } else { for (let prop in object) { if (typeof object[prop] == 'object') { return false; } } } return true; } } let object = { name: { first: { four: 5, second: { third: 'ssss' } } }, class: 5, arr: [1, 2, {arr1:10}], age: { age1: 10 } } //這是一個嵌套了對象和數組的數組 let objectArr = [{name:{first:'ss'}, arr1:[1,2]}, 2, 3, 4, 5, 6] //這是proxy的handler let handler = { get(target, property) { console.log('get:' + property) return Reflect.get(target, property); }, set(target, property, value) { console.log('set:' + property + '=' + value); return Reflect.set(target, property, value); } } //變成監聽對象 object = toDeepProxy(object, handler); objectArr = toDeepProxy(objectArr, handler); //進行一系列操做 console.time('pro') objectArr.length objectArr[3]; objectArr[2]=10 objectArr[0].name.first = 'ss' objectArr[0].arr1[0] object.name.first.second.third = 'yyyyy' object.class = 6; object.name.first.four object.arr[2].arr1 object.age.age1 = 20; console.timeEnd('pro')
3.問題和優勢
reflect對象沒有構造函數
能夠監聽數組索引賦值,改變數組長度的變化,
是直接監聽對象的變化,不用深層遍歷
1.defineProterty是es5的標準,proxy是es6的標準;
2.proxy能夠監聽到數組索引賦值,改變數組長度的變化;
3.proxy是監聽對象,不用深層遍歷,defineProterty是監聽屬性;
3.利用defineProterty實現雙向數據綁定(vue2.x採用的核心)
請戳,剖析Vue原理&實現雙向綁定MVVM
4.利用proxy實現雙向數據綁定(vue3.x會採用)
數組基本上考察數組方法多一點,因此這裏就單純介紹常見的場景數組的方法,還有不少場景後續補充;
本文主要從應用來說數組api的一些騷操做;
如一行代碼扁平化n維數組、數組去重、求數組最大值、數組求和、排序、對象和數組的轉化等;
上面這些應用場景你能夠用一行代碼實現?
1.終極篇
[1,[2,3]].flat(2) //[1,2,3] [1,[2,3,[4,5]].flat(3) //[1,2,3,4,5] [1,[2,3,[4,5]]].toString() //'1,2,3,4,5' [1[2,3,[4,5[...]].flat(Infinity) //[1,2,3,4...n]
Array.flat(n)是ES10扁平數組的api,n表示維度,n值爲Infinity時維度爲無限大
2.開始篇
function flatten(arr) { while(arr.some(item=>Array.isArray(item))) { arr = [].concat(...arr); } return arr; } flatten([1,[2,3]]) //[1,2,3] flatten([1,[2,3,[4,5]]) //[1,2,3,4,5]
實質是利用遞歸和數組合並方法concat實現扁平
1.終極篇
Array.from(new Set([1,2,3,3,4,4])) //[1,2,3,4] [...new Set([1,2,3,3,4,4])] //[1,2,3,4]
set是ES6新出來的一種一種定義不重複數組的數據類型
Array.from是將類數組轉化爲數組
...是擴展運算符,將set裏面的值轉化爲字符串
2.開始篇
Array.prototype.distinct = nums => { const map = {} const result = [] for (const n of nums) { if (!(n in map)) { map[n] = 1 result.push(n) } } return result } [1,2,3,3,4,4].distinct(); //[1,2,3,4]
取新數組存值,循環兩個數組值相比較
1.終極篇
[1,2,3,4].sort((a, b) => a - b); // [1, 2,3,4],默認是升序 [1,2,3,4].sort((a, b) => b - a); // [4,3,2,1] 降序
sort是js內置的排序方法,參數爲一個函數
2.開始篇
冒泡排序:
Array.prototype.bubleSort=function () { let arr=this, len = arr.length; for (let outer = len; outer >= 2; outer--) { for (let inner = 0; inner <= outer - 1; inner++) { if (arr[inner] > arr[inner + 1]) { //升序 [arr[inner], arr[inner + 1]] = [arr[inner + 1], arr[inner]]; console.log([arr[inner], arr[inner + 1]]); } } } return arr; } [1,2,3,4].bubleSort() //[1,2,3,4]
選擇排序
Array.prototype.selectSort=function () { let arr=this, len = arr.length; for (let i = 0, len = arr.length; i < len; i++) { for (let j = i, len = arr.length; j < len; j++) { if (arr[i] > arr[j]) { [arr[i], arr[j]] = [arr[j], arr[i]]; } } } return arr; } [1,2,3,4].selectSort() //[1,2,3,4]
1.終極篇
Math.max(...[1,2,3,4]) //4 Math.max.apply(this,[1,2,3,4]) //4 [1,2,3,4].reduce( (prev, cur,curIndex,arr)=> { return Math.max(prev,cur); },0) //4
Math.max()是Math對象內置的方法,參數是字符串;
reduce是ES5的數組api,參數有函數和默認初始值;
函數有四個參數,pre(上一次的返回值),cur(當前值),curIndex(當前值索引),arr(當前數組)
2.開始篇
先排序再取值
1.終極篇
[1,2,3,4].reduce(function (prev, cur) { return prev + cur; },0) //10
2.開始篇
function sum(arr) { var len = arr.length; if(len == 0){ return 0; } else if (len == 1){ return arr[0]; } else { return arr[0] + sum(arr.slice(1)); } } sum([1,2,3,4]) //10
利用slice截取改變數組,再利用遞歸求和
1.終極篇
[1,2,3,4].concat([5,6]) //[1,2,3,4,5,6] [...[1,2,3,4],...[4,5]] //[1,2,3,4,5,6] let arrA = [1, 2], arrB = [3, 4] Array.prototype.push.apply(arrA, arrB))//arrA值爲[1,2,3,4]
2.開始篇
let arr=[1,2,3,4]; [5,6].map(item=>{ arr.push(item) }) //arr值爲[1,2,3,4,5,6],注意不能直接return出來,return後只會返回[5,6]
1.終極篇
[1,2,3].includes(4) //false [1,2,3].indexOf(4) //-1 若是存在換回索引 [1, 2, 3].find((item)=>item===3)) //3 若是數組中無值返回undefined [1, 2, 3].findIndex((item)=>item===3)) //2 若是數組中無值返回-1
includes(),find(),findIndex()是ES6的api
2.開始篇
[1,2,3].some(item=>{ return item===3 }) //true 若是不包含返回false
1.終極篇
Array.prototype.slice.call(arguments) //arguments是類數組(僞數組) Array.prototype.slice.apply(arguments) Array.from(arguments) [...arguments]
類數組:表示有length屬性,可是不具有數組的方法
call,apply:是改變slice裏面的this指向arguments,因此arguments也可調用數組的方法
Array.from是將相似數組或可迭代對象建立爲數組
...是將類數組擴展爲字符串,再定義爲數組
2.開始篇
Array.prototype.slice = function(start,end){ var result = new Array(); start = start || 0; end = end || this.length; //this指向調用的對象,當用了call後,可以改變this的指向,也就是指向傳進來的對象,這是關鍵 for(var i = start; i < end; i++){ result.push(this[i]); } return result; }
1.終極篇
[1,2,3].fill(false) //[false,false,false]
fill是ES6的方法
2.開始篇
[1,2,3].map(() => 0)
[1,2,3].every(item=>{return item>2}) //false
every是ES5的api,每一項知足返回 true
[1,2,3].some(item=>{return item>2}) //true
some是ES5的api,有一項知足返回 true
[1,2,3].filter(item=>{return item>2}) //[3]
filter是ES5的api,返回知足添加的項的數組
Object.keys({name:'張三',age:14}) //['name','age'] Object.values({name:'張三',age:14}) //['張三',14] Object.entries({name:'張三',age:14}) //[[name,'張三'],[age,14]] Object.fromEntries([name,'張三'],[age,14]) //ES10的api,Chrome不支持 , firebox輸出{name:'張三',age:14}
[{count:1},{count:2},{count:3}].reduce((p, e)=>p+(e.count), 0)
數據結構是計算機存儲、組織數據的方式,算法是系統描述解決問題的策略。瞭解基本的數據結構和算法能夠提升代碼的性能和質量。
也是程序猿進階的一個重要技能。
手擼代碼實現棧,隊列,鏈表,字典,二叉樹,動態規劃和貪心算法
棧的特色:先進後出
class Stack { constructor() { this.items = []; } // 入棧 push(element) { this.items.push(element); } // 出棧 pop() { return this.items.pop(); } // 末位 get peek() { return this.items[this.items.length - 1]; } // 是否爲空棧 get isEmpty() { return !this.items.length; } // 長度 get size() { return this.items.length; } // 清空棧 clear() { this.items = []; } } // 實例化一個棧 const stack = new Stack(); console.log(stack.isEmpty); // true // 添加元素 stack.push(5); stack.push(8); // 讀取屬性再添加 console.log(stack.peek); // 8 stack.push(11); console.log(stack.size); // 3 console.log(stack.isEmpty); // false
隊列:先進先出
class Queue { constructor(items) { this.items = items || []; } enqueue(element) { this.items.push(element); } dequeue() { return this.items.shift(); } front() { return this.items[0]; } clear() { this.items = []; } get size() { return this.items.length; } get isEmpty() { return !this.items.length; } print() { console.log(this.items.toString()); } } const queue = new Queue(); console.log(queue.isEmpty); // true queue.enqueue("John"); queue.enqueue("Jack"); queue.enqueue("Camila"); console.log(queue.size); // 3 console.log(queue.isEmpty); // false queue.dequeue(); queue.dequeue();
鏈表:存貯有序元素的集合,
可是不一樣於數組,每一個元素是一個存貯元素自己的節點和指向下一個元素引用組成
要想訪問鏈表中間的元素,須要從起點開始遍歷找到所需元素
class Node { constructor(element) { this.element = element; this.next = null; } } // 鏈表 class LinkedList { constructor() { this.head = null; this.length = 0; } // 追加元素 append(element) { const node = new Node(element); let current = null; if (this.head === null) { this.head = node; } else { current = this.head; while (current.next) { current = current.next; } current.next = node; } this.length++; } // 任意位置插入元素 insert(position, element) { if (position >= 0 && position <= this.length) { const node = new Node(element); let current = this.head; let previous = null; let index = 0; if (position === 0) { this.head = node; node.next = current; } else { while (index++ < position) { previous = current; current = current.next; } node.next = current; previous.next = node; } this.length++; return true; } return false; } // 移除指定位置元素 removeAt(position) { // 檢查越界值 if (position > -1 && position < length) { let current = this.head; let previous = null; let index = 0; if (position === 0) { this.head = current.next; } else { while (index++ < position) { previous = current; current = current.next; } previous.next = current.next; } this.length--; return current.element; } return null; } // 尋找元素下標 findIndex(element) { let current = this.head; let index = -1; while (current) { if (element === current.element) { return index + 1; } index++; current = current.next; } return -1; } // 刪除指定文檔 remove(element) { const index = this.findIndex(element); return this.removeAt(index); } isEmpty() { return !this.length; } size() { return this.length; } // 轉爲字符串 toString() { let current = this.head; let string = ""; while (current) { string += ` ${current.element}`; current = current.next; } return string; } } const linkedList = new LinkedList(); console.log(linkedList); linkedList.append(2); linkedList.append(6); linkedList.append(24); linkedList.append(152); linkedList.insert(3, 18); console.log(linkedList); console.log(linkedList.findIndex(24));
字典:相似對象,以key,value存貯值
class Dictionary { constructor() { this.items = {}; } set(key, value) { this.items[key] = value; } get(key) { return this.items[key]; } remove(key) { delete this.items[key]; } get keys() { return Object.keys(this.items); } get values() { /* 也可使用ES7中的values方法 return Object.values(this.items) */ // 在這裏咱們經過循環生成一個數組並輸出 return Object.keys(this.items).reduce((r, c, i) => { r.push(this.items[c]); return r; }, []); } } const dictionary = new Dictionary(); dictionary.set("Gandalf", "gandalf@email.com"); dictionary.set("John", "johnsnow@email.com"); dictionary.set("Tyrion", "tyrion@email.com"); console.log(dictionary); console.log(dictionary.keys); console.log(dictionary.values); console.log(dictionary.items);
特色:每一個節點最多有兩個子樹的樹結構
class NodeTree { constructor(key) { this.key = key; this.left = null; this.right = null; } } class BinarySearchTree { constructor() { this.root = null; } insert(key) { const newNode = new NodeTree(key); const insertNode = (node, newNode) => { if (newNode.key < node.key) { if (node.left === null) { node.left = newNode; } else { insertNode(node.left, newNode); } } else { if (node.right === null) { node.right = newNode; } else { insertNode(node.right, newNode); } } }; if (!this.root) { this.root = newNode; } else { insertNode(this.root, newNode); } } //訪問樹節點的三種方式:中序,先序,後序 inOrderTraverse(callback) { const inOrderTraverseNode = (node, callback) => { if (node !== null) { inOrderTraverseNode(node.left, callback); callback(node.key); inOrderTraverseNode(node.right, callback); } }; inOrderTraverseNode(this.root, callback); } min(node) { const minNode = node => { return node ? (node.left ? minNode(node.left) : node) : null; }; return minNode(node || this.root); } max(node) { const maxNode = node => { return node ? (node.right ? maxNode(node.right) : node) : null; }; return maxNode(node || this.root); } } const tree = new BinarySearchTree(); tree.insert(11); tree.insert(7); tree.insert(5); tree.insert(3); tree.insert(9); tree.insert(8); tree.insert(10); tree.insert(13); tree.insert(12); tree.insert(14); tree.inOrderTraverse(value => { console.log(value); }); console.log(tree.min()); console.log(tree.max());
冒泡排序,選擇排序,插入排序,此處不作贅述,請戳 排序
特色:第三項等於前面兩項之和
function fibonacci(num) { if (num === 1 || num === 2) { return 1 } return fibonacci(num - 1) + fibonacci(num - 2) }
特色:經過全局規劃,將大問題分割成小問題來取最優解
案例:最少硬幣找零
美國有如下面額(硬幣):d1=1, d2=5, d3=10, d4=25
若是要找36美分的零錢,咱們能夠用1個25美分、1個10美分和1個便士( 1美分)
class MinCoinChange { constructor(coins) { this.coins = coins this.cache = {} } makeChange(amount) { if (!amount) return [] if (this.cache[amount]) return this.cache[amount] let min = [], newMin, newAmount this.coins.forEach(coin => { newAmount = amount - coin if (newAmount >= 0) { newMin = this.makeChange(newAmount) } if (newAmount >= 0 && (newMin.length < min.length - 1 || !min.length) && (newMin.length || !newAmount)) { min = [coin].concat(newMin) } }) return (this.cache[amount] = min) } } const rninCoinChange = new MinCoinChange([1, 5, 10, 25]) console.log(rninCoinChange.makeChange(36)) // [1, 10, 25] const minCoinChange2 = new MinCoinChange([1, 3, 4]) console.log(minCoinChange2.makeChange(6)) // [3, 3]
特色:經過最優解來解決問題
用貪心算法來解決2.3中的案例
class MinCoinChange2 { constructor(coins) { this.coins = coins } makeChange(amount) { const change = [] let total = 0 this.coins.sort((a, b) => a < b).forEach(coin => { if ((total + coin) <= amount) { change.push(coin) total += coin } }) return change } } const rninCoinChange2 = new MinCoinChange2 ( [ 1, 5, 10, 25]) console.log (rninCoinChange2. makeChange (36))
設計模式若是應用到項目中,能夠實現代碼的複用和解耦,提升代碼質量。 本文主要介紹14種設計模式
寫UI組件,封裝框架必備
1.定義:又叫靜態工廠方法,就是建立對象,並賦予屬性和方法
2.應用:抽取類相同的屬性和方法封裝到對象上
3.代碼:
let UserFactory = function (role) { function User(opt) { this.name = opt.name; this.viewPage = opt.viewPage; } switch (role) { case 'superAdmin': return new User(superAdmin); break; case 'admin': return new User(admin); break; case 'user': return new User(user); break; default: throw new Error('參數錯誤, 可選參數:superAdmin、admin、user') } } //調用 let superAdmin = UserFactory('superAdmin'); let admin = UserFactory('admin') let normalUser = UserFactory('user') //最後獲得角色,能夠調用
1.定義:對產品類的抽象使其建立業務主要負責用於建立多類產品的實例
2.應用:建立實例
3.代碼:
var Factory=function(type,content){ if(this instanceof Factory){ var s=new this[type](content); return s; }else{ return new Factory(type,content); } } //工廠原型中設置建立類型數據對象的屬性 Factory.prototype={ Java:function(content){ console.log('Java值爲',content); }, PHP:function(content){ console.log('PHP值爲',content); }, Python:function(content){ console.log('Python值爲',content); }, } //測試用例 Factory('Python','我是Python');
1.定義:設置函數的原型屬性
2.應用:實現繼承
3.代碼:
function Animal (name) { // 屬性 this.name = name || 'Animal'; // 實例方法 this.sleep = function(){ console.log(this.name + '正在睡覺!'); } } // 原型方法 Animal.prototype.eat = function(food) { console.log(this.name + '正在吃:' + food); }; function Cat(){ } Cat.prototype = new Animal(); Cat.prototype.name = 'cat'; // Test Code var cat = new Cat(); console.log(cat.name);//cat console.log(cat.eat('fish'));//cat正在吃:fish undefined console.log(cat.sleep());//cat正在睡覺! undefined console.log(cat instanceof Animal); //true console.log(cat instanceof Cat); //true
1.定義:只容許被實例化依次的類
2.應用:提供一個命名空間
3.代碼:
let singleCase = function(name){ this.name = name; }; singleCase.prototype.getName = function(){ return this.name; } // 獲取實例對象 let getInstance = (function() { var instance = null; return function(name) { if(!instance) {//至關於一個一次性閥門,只能實例化一次 instance = new singleCase(name); } return instance; } })(); // 測試單體模式的實例,因此one===two let one = getInstance("one"); let two = getInstance("two");
1.定義:爲子系統中的一組接口提供一個一致的界面
2.應用:簡化複雜接口
3.代碼:
外觀模式
1.定義:將一個接口轉換成客戶端須要的接口而不須要去修改客戶端代碼,使得不兼容的代碼能夠一塊兒工做
2.應用:適配函數參數
3.代碼:
適配器模式
1.定義:不改變原對象的基礎上,給對象添加屬性或方法
2.代碼
let decorator=function(input,fn){ //獲取事件源 let input=document.getElementById(input); //若事件源已經綁定事件 if(typeof input.onclick=='function'){ //緩存事件源原有的回調函數 let oldClickFn=input.onclick; //爲事件源定義新事件 input.onclick=function(){ //事件源原有回調函數 oldClickFn(); //執行事件源新增回調函數 fn(); } }else{ //未綁定綁定 input.onclick=fn; } } //測試用例 decorator('textInp',function(){ console.log('文本框執行啦'); }) decorator('btn',function(){ console.log('按鈕執行啦'); })
1.定義:將抽象部分與它的實現部分分離,使它們均可以獨立地變化
2.代碼
橋接模式
1.定義:定義一個模板,供之後傳不一樣參數調用
2.代碼:
模塊方法模式
1.做用:解決類與對象,對象與對象之間的耦合
2.代碼:
let Observer= (function(){ let _message={}; return { //註冊接口, //1.做用:將訂閱者註冊的消息推入到消息隊列 //2.參數:因此要傳兩個參數,消息類型和處理動做, //3.消息不存在從新建立,存在將消息推入到執行方法 regist:function(type,fn){ //若是消息不存在,建立 if(typeof _message[type]==='undefined'){ _message[type]=[fn]; }else{ //將消息推入到消息的執行動做 _message[type].push(fn); } }, //發佈信息接口 //1.做用:觀察這發佈消息將全部訂閱的消息一次執行 //2.參數:消息類型和動做執行傳遞參數 //3.消息類型參數必須校驗 fire:function(type,args){ //若是消息沒有註冊,則返回 if(!_message[type]) return; //定義消息信息 var events={ type:type, //消息類型 args:args||{} //消息攜帶數據 }, i=0, len=_message[type].length; //遍歷消息 for(;i<len;i++){ //依次執行註冊消息 _message[type][i].call(this,events); } }, //移除信息接口 //1.做用:將訂閱者註銷消息從消息隊列清除 //2.參數:消息類型和執行的動做 //3.消息參數校驗 remove:function(type,fn){ //若是消息動做隊列存在 if(_message[type] instanceof Array){ //從最後一個消息動做序遍歷 var i=_message[type].length-1; for(;i>=0;i--){ //若是存在該動做在消息隊列中移除 _message[type][i]===fn&&_message[type].splice(i,1); } } } } })() //測試用例 //1.訂閱消息 Observer.regist('test',function(e){ console.log(e.type,e.args.msg); }) //2.發佈消息 Observer.fire('test',{msg:'傳遞參數1'}); Observer.fire('test',{msg:'傳遞參數2'}); Observer.fire('test',{msg:'傳遞參數3'});
1.定義:一個對象狀態改變會致使行爲變化
2.做用:解決複雜的if判斷
3.代碼
狀態模式
1.定義:定義了一系列家族算法,並對每一種算法單獨封裝起來,讓算法之間能夠相互替換,獨立於使用算法的客戶
2.代碼
策略模式
1.定義:經過繼承封裝一些該數據類型不具有的屬性,
2.做用:讓對象具有數組的操做方法
3.代碼:
訪問者模式
1.定義:設置一箇中間層,處理對象之間的交互
2.代碼:
中介者模式
HTTP 是一個鏈接客戶端,網關和服務器的一個協議。
支持客戶/服務器模式:能夠鏈接客戶端和服務端;
簡單快速:請求只需傳送請求方法,路徑和請求主體;
靈活:傳輸數據類型靈活;
無鏈接:請求結束當即斷開;
無狀態:沒法記住上一次請求。
無狀態:HTTP 協議自己沒法解決這個狀態,只有經過 cookie 和 session 將狀態作貯存,常見的場景是登陸狀態保持;
無鏈接:能夠經過自身屬性 Keep-Alive。
HTTP(S) 請求地址 → DNS 解析 → 三次握手 → 發送請求 → 四次揮手
三次握手過程(圖片來源 CSDN)
在這裏插入圖片描述
在這裏插入圖片描述
只容許客戶端發送 GET 這一種請求;
且不支持請求頭,協議只支持純文本;
無狀態性,每一個訪問獨立處理,完成斷開;
無狀態碼。
有身份認證,三次握手;
請求與響應支持頭域;
請求頭內容;
屬性名 | 含義 |
---|---|
Accept | 可接受的 MIME 類型 |
Accept-Encoding | 數據可解碼的格式 |
Accept-Language | 可接受語言 |
Connection | 值 keep-alive 是長鏈接 |
Host | 主機和端口 |
Pragma | 是否緩存,指定 no-cache 返回刷新 |
Referer | 頁面路由 |
If-Modified-Since | 值爲時間 |
響應頭內容;
屬性名 | 含義 |
---|---|
Connection | 值 keep-alive 是長鏈接 |
Content-Type | 返回文檔類型,常見的值有 text/plain,text/html,text/json |
Date | 消息發送的時間 |
Server | 服務器名字 |
Last-Modified | 值爲時間,s 返回的最後修改時間 |
Expires | 緩存過時時間,b 和 s 時間作對比 |
注意
expires 是響應頭內容,返回一個固定的時間,缺陷是時間到了服務器要從新設置。
請求頭中若是有 If-Modified-Since,服務器會將時間與 last-modified 對比,相同返回 304。
響應對象以一個響應狀態行開始
響應對象不僅限於超文本
支持 GET、HEAD、POST 方法
有狀態碼
支持長鏈接(但默認仍是使用短鏈接)、緩存機制以及身份認證。
請求頭增長 Cache-Control
屬性名 | 含義 |
---|---|
Cache-Control | 在1.1 引入的方法,指定請求和響應遵循的緩存機制,值有:public(b 和 s 都緩存),private(b 緩存),no-cache(不緩存),no-store(不緩存),max-age(緩存時間,s 爲單位),min-fresh(最小更新時間),max-age=3600 |
If-None-Match | 上次請求響應頭返回的 etag 值響應頭增長 Cache-Control,表示全部的緩存機制是否能夠緩存及哪一種類型 etag 返回的哈希值,第二次請求頭攜帶去和服務器值對比 |
注意
Cache-Control 的 max-age 返回是緩存的相對時間
Cache-Control 優先級比 expires 高
缺點:不能第一時間拿到最新修改文件
採用二進制格式傳輸
多路複用,其實就是將請求數據分紅幀亂序發送到 TCP 中。TCP 只能有一個 steam,因此仍是會阻塞
報頭壓縮
服務器推送主動向 B 端發送靜態資源,避免往返延遲。
1.是基於 QUIC 協議,基於 UDP
2.特色:
自定義鏈接機制:TCP 以 IP/端口標識,變化從新鏈接握手,UDP 是一 64 位 ID 標識,是無鏈接;
自定義重傳機制:TCP 使用序號和應答傳輸,QUIC 是使用遞增序號傳輸; 無阻塞的多路複用:同一條 QUIC 能夠建立多個 steam。
1.https 是在 http 協議的基礎上加了個 SSL;
2.主要包括:握手(憑證交換和驗證)和記錄協議(數據進行加密)。
1.按協議分:協議層緩存和非 http 協議緩存:
1.1協議層緩存:利用 http 協議頭屬性值設置;
1.2非協議層緩存:利用 meta 標籤的 http-equiv 屬性值 Expires,set-cookie。
2.按緩存分:強緩存和協商緩存:
2.1強緩存:利用 cache-control 和 expires 設置,直接返回一個過時時間,因此在緩存期間不請求,If-modify-since;
2.2協商緩存:響應頭返回 etag 或 last-modified 的哈希值,第二次請求頭 If-none-match 或 IF-modify-since 攜帶上次哈希值,一致則返回 304。
3.協商緩存對比: etag 優先級高於 last-modified;
4.etag 精度高,last-modified 精度是 s,1s 內 etag 修改多少次都會被記錄; last-modified 性能好,etag 要獲得 hash 值。
5.瀏覽器讀取緩存流程:
會先判斷強緩存;再判斷協商緩存 etag(last-modified)是否存在;
存在利用屬性 If-None-match(If-Modified-since)攜帶值;
請求服務器,服務器對比 etag(last-modified),生效返回 304。
F5 刷新會忽略強緩存不會忽略協商緩存,ctrl+f5 都失效
序列 | 詳情 |
---|---|
1XX(通知) | |
2XX(成功) | 200(成功)、201(服務器建立)、202(服務器接收未處理)、203(非受權信息)、204(未返回內容)、205(重置內容)、206(部份內容) |
3XX(重定向) | 301(永久移動)、302(臨時移動)、303(查看其餘位置)、304(未修改)、305(使用代理)、307(臨時重定向) |
4XX(客戶端錯誤) | 400(錯誤請求)、401(未受權)、403(禁止)、404(未找到)、405(方法禁用)、406(不接受)、407(須要代理受權) |
5XX(服務器錯誤) | 500(服務器異常)、501(還沒有實施)、502(錯誤網關)、503(服務不可用)、504(網關超時)、505(HTTP 版本不受支持) |
在這裏插入圖片描述
協議
版本 | 內容 |
---|---|
http0.9 | 只容許客戶端發送 GET 這一種請求;且不支持請求頭,協議只支持純文本;無狀態性,每一個訪問獨立處理,完成斷開;無狀態碼 |
http1.0 解決 0.9 的缺點,增長 If-modify-since(last-modify)和 expires 緩存屬性 | |
http1.x | 增長 cache-control 和 If-none-match(etag)緩存屬性 |
http2.0 | 採用二進制格式傳輸;多路複用;報頭壓縮;服務器推送 |
http3.0 | 採用 QUIC 協議,自定義鏈接機制;自定義重傳機制;無阻塞的多路複用 |
緩存
類型 | 特性 |
---|---|
強緩存 | 經過 If-modify-since(last-modify)、expires 和 cache-control 設置,屬性值是時間,因此在時間內不用請求 |
協商緩存 | 經過 If-none-match(etag)設置,etag 屬性是哈希值,因此要請求和服務器值對比 |
這只是 JS 原生梳理,後續會再出 react,node,小程序相關的梳理;原創碼字不易,歡迎 star!