這些 JS 基礎你都知道嗎【近1.5W字】

JS.jpeg

前言

是時候擼一波 JS 基礎啦,擼熟了,銀十速拿 offer;
包括函數,數組,對象,數據結構,算法,設計模式和 http.

1. 函數

1.1函數的3種定義方法

1.1.1 函數聲明

//ES5
function getSum(){}
function (){}//匿名函數
//ES6
()=>{}//如果{}內容只有一行{}和return關鍵字可省,

1.1.2 函數表達式(函數字面量)

//ES5
var sum=function(){}
//ES6
let sum=()=>{}//如果{}內容只有一行{}和return關鍵字可省,

1.1.3 構造函數

var sum=new GetSum(num1,num2)

1.1.4 三種方法的對比

1.函數聲明有預解析,而且函數聲明的優先級高於變量;
2.使用Function構造函數定義函數的方式是一個函數表達式,這種方式會導致解析兩次代碼,影響性能。第一次解析常規的JavaScript代碼,第二次解析傳入構造函數的字符串

1.2.ES5中函數的4種調用

在ES5中函數內容的this指向和調用方法有關

1.2.1 函數調用模式

包括函數名()和匿名函數調用,this指向window

 function getSum() {
    console.log(this) //window
 }
 getSum()
 
 (function() {
    console.log(this) //window
 })()
 
 var getSum=function() {
    console.log(this) //window
 }
 getSum()

1.2.2 方法調用

對象.方法名(),this指向對象

var objList = {
   name: 'methods',
   getSum: function() {
     console.log(this) //objList對象
   }
}
objList.getSum()

1.2.3 構造器調用

new 構造函數名(),this指向構造函數

function Person() {
  console.log(this); //指向構造函數Person
}
var personOne = new Person();

1.2.4 間接調用

利用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值

1.3 ES6中函數的調用

箭頭函數不可以當作構造函數使用,也就是不能用new命令實例化一個對象,否則會拋出一個錯誤
箭頭函數的this是和定義時有關和調用無關
調用就是函數調用模式

(() => {
   console.log(this)//window
})()

let arrowFun = () => {
  console.log(this)//window
}
arrowFun()

let arrowObj = {
  arrFun: function() {
   (() => {
     console.log(this)//arrowObj
   })()
   }
 }
 arrowObj.arrFun();
 

1.4.call,apply和bind

1.IE5之前不支持call和apply,bind是ES5出來的;
2.call和apply可以調用函數,改變this,實現繼承和借用別的對象的方法;

1.4.1 call和apply定義

調用方法,用一個對象替換掉另一個對象(this)
對象.call(新this對象,實參1,實參2,實參3.....)
對象.apply(新this對象,[實參1,實參2,實參3.....])

1.4.2 call和apply用法

1.間接調用函數,改變作用域的this值
2.劫持其他對象的方法

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({})

1.4.3 bind

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指向

1.4.4 call,apply和bind原生實現

call實現:

Function.prototype.newCall = function(context, ...parameter) {
  context.fn = this;  
  context.fn(...parameter);
  delete context.fn;
}
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') {
    context = context || window
  } else {
    context = Object.create(null)
  }
  let fn = Symbol()
  context[fn] = this
  context[fn](parameter);
  delete context[fn]
}

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('男')

1.4.5 三者異同

同:都是改變this指向,都可接收參數
異:bind和call是接收單個參數,apply是接收數組

1.5.函數的節流和防抖

類型 概念 應用
節流 某個時間段內,只執行一次 滾動條,resize事件一段時間觸發一次
防抖 處理函數截止後一段時間依次執行 scroll,resize事件觸發完後一段時間觸發

節流:

1.5.1 節流

let throttle = function(func, delay) {
    let timer = null;
    return function() {
      if (!timer) {
        timer = setTimeout(function() {
          func.apply(this, arguments);
          timer = null;
        }, delay);
      }
    };
  };
  function handle() {
    console.log(Math.random());
  }
  window.addEventListener("scroll", throttle(handle, 1000)); //事件處理函數

1.5.2 防抖

function debounce(fn, wait) {
    var timeout = null;
    return function() {
      if (timeout !== null) clearTimeout(timeout);//如果多次觸發將上次記錄延遲清除掉
      timeout = setTimeout(function() {
          fn.apply(this, arguments);
          timer = null;
        }, wait);
    };
  }
  // 處理函數
  function handle() {
    console.log(Math.random());
  }
  // 滾動事件
  window.addEventListener("onscroll", debounce(handle, 1000));

1.6.原型鏈

1.6.1 定義

對象繼承屬性的一個鏈條

1.6.2構造函數,實例與原型對象的關係

圖片描述

var Person = function (name) { this.name = name; }//person是構造函數
var o3personTwo = new Person('personTwo')//personTwo是實例

圖片描述

原型對象都有一個默認的constructor屬性指向構造函數

1.6.3 創建實例的方法

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.6.4 new運算符

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 (typeof k === 'object') {         //判斷k的類型是不是對象
        return k;                  //是,返回k
    } else {
        return o;                  //不是返回返回構造函數的執行結果
    }
}  

更多詳情:詳談JavaScript原型鏈

1.6.5 對象的原型鏈

圖片描述

1.7 繼承的方式

JS是一門弱類型動態語言,封裝和繼承是他的兩大特性

1.7.1 原型鏈繼承

將父類的實例作爲子類的原型
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()這樣的語句之後執行,無法實現多繼承

1.7.2 構造繼承

實質是利用call來改變Cat中的this指向
1.代碼實現
子類:

function Cat(name){
  Animal.call(this);
  this.name = name || 'Tom';
}

2.優缺點
可以實現多繼承,不能繼承原型屬性/方法

1.7.3 實例繼承

爲父類實例添加新特性,作爲子類實例返回
1.代碼實現
子類

function Cat(name){
  var instance = new Animal();
  instance.name = name || 'Tom';
  return instance;
}

2.優缺點
不限制調用方式,但不能實現多繼承

1.7.4 拷貝繼承

將父類的屬性和方法拷貝一份到子類中
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.7.5 組合繼承

通過調用父類構造,繼承父類的屬性並保留傳參的優點,然後通過將父類實例作爲子類原型,實現函數複用
1.子類:

function Cat(name){
  Animal.call(this);
  this.name = name || 'Tom';
}
Cat.prototype = new Animal();
Cat.prototype.constructor = Cat;

1.7.6 寄生組合繼承

function Cat(name){
  Animal.call(this);
  this.name = name || 'Tom';
}
(function(){
  // 創建一個沒有實例方法的類
  var Super = function(){};
  Super.prototype = Animal.prototype;
  //將實例作爲子類的原型
  Cat.prototype = new Super();
})();

1.7.7 ES6的extends繼承

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();

更多詳情請戳:JS繼承的實現方式

1.8.高階函數

1.8.1定義

函數的參數是函數或返回函數

1.8.2 常見的高階函數

map,reduce,filter,sort

1.8.3 柯里化

1.定義:只傳遞給函數一部分參數來調用它,讓它返回一個函數去處理剩下的參數

fn(a,b,c,d)=>fn(a)(b)(c)(d)

2.代碼實現:

let currying = function(fn) {
    // args 獲取第一個方法內的全部參數
    var args = Array.prototype.slice.call(arguments, 1)
    return function() {
        // 將後面方法裏的全部參數和args進行合併
        var newArgs = args.concat(Array.prototype.slice.call(arguments))
        // 把合併後的參數通過apply作爲fn的參數並執行
        return fn.apply(this, newArgs)
    }
}

1.8.4 反柯里化

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.8.5偏函數

1.定義:指定部分參數來返回一個新的定製函數的形式
2.例子:

function foo(a, b, c) {
  return a + b + c;
}
function func(a, b) {
  return foo(a,b,8);
}

2.對象

2.1.對象的聲明方法

2.1.1 字面量

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

2.1.2 構造函數

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出來的對象

2.1.3 內置方法

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

2.1.4 三種方法的優缺點

1.功能:都能實現對象的聲明,並能夠賦值和取值
2.繼承性:內置方法創建的對象繼承到__proto__屬性上
3.隱藏屬性:三種聲明方法會默認爲內部的每個成員(屬性或方法)生成一些隱藏屬性,這些隱藏屬性是可以讀取和可配置的,屬性分類見下面
4.屬性讀取:Object.getOwnPropertyDescriptor()或getOwnPropertyDescriptor()
5.屬性設置:Object.definePropertype或Object.defineProperties

2.2.對象的屬性

2.2.1 屬性分類

1.數據屬性4個特性:
configurable(可配置),enumerable(可枚舉),writable(可修改),value(屬性值)

2.訪問器屬性2個特性:
get(獲取),set(設置)

3.內部屬性
由JavaScript引擎內部使用的屬性;
不能直接訪問,但是可以通過對象內置方法間接訪問,如:[[Prototype]]可以通過 Object.getPrototypeOf()訪問;
內部屬性用[[]]包圍表示,是一個抽象操作,沒有對應字符串類型的屬性名,如[[Prototype]].

2.2.2 屬性描述符

1.定義:將一個屬性的所有特性編碼成一個對象返回
2.描述符的屬性有:數據屬性和訪問器屬性
3.使用範圍:
作爲方法Object.defineProperty, Object.getOwnPropertyDescriptor, Object.create的第二個參數,

2.2.3 屬性描述符的默認值

1.訪問對象存在的屬性

特性名 默認值
value 對應屬性值
get 對應屬性值
set undefined
writable true
enumerable true
configurable true

所以通過上面三種聲明方法已存在的屬性都是有這些默認描述符
2.訪問對象不存在的屬性

特性名 默認值
value undefined
get undefined
set undefined
writable false
enumerable false
configurable false

2.2.3 描述符屬性的使用規則

get,set與wriable,value是互斥的,如果有交集設置會報錯

2.2.4 屬性定義

1.定義屬性的函數有兩個:Object.defineProperty和Object.defineProperties.例如:
Object.defineProperty(obj, propName, desc)

2.在引擎內部,會轉換成這樣的方法調用:
obj.[[DefineOwnProperty]](propName, desc, true)

2.2.5 屬性賦值

1.賦值運算符(=)就是在調用[[Put]].比如:
obj.prop = v;

2.在引擎內部,會轉換成這樣的方法調用:
obj.[[Put]]("prop", v, isStrictModeOn)

2.2.6 判斷對象的屬性

名稱 含義 用法
in 如果指定的屬性在指定的對象或其原型鏈中,則in 運算符返回true 'name' in test //true
hasOwnProperty() 只判斷自身屬性 test.hasOwnProperty('name') //true
.或[] 對象或原型鏈上不存在該屬性,則會返回undefined test.name //"lei" test["name"] //"lei"

2.3.Symbol

2.3.1概念

是一種數據類型;
不能new,因爲Symbol是一個原始類型的值,不是對象。

2.3.2 定義方法

Symbol(),可以傳參

var s1 = Symbol();
var s2 = Symbol();
s1 === s2 // false

// 有參數的情況
var s1 = Symbol("foo");
var s2 = Symbol("foo");
s1 === s2 // false

2.3.3 用法

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()取到該屬性

2.3.4 Symbol.for

1.定義:在全局中搜索有沒有以該參數作爲名稱的Symbol值,如果有,就返回這個Symbol值,否則就新建並返回一個以該字符串爲名稱的Symbol值
2.舉例:

var s1 = Symbol.for('foo');
var s2 = Symbol.for('foo');
s1 === s2 // true

2.3.5 Symbol.keyFor

1.定義:返回一個已登記的Symbol類型值的key
2.舉例:

var s1 = Symbol.for("foo");
Symbol.keyFor(s1) // "foo"

var s2 = Symbol("foo");
Symbol.keyFor(s2) // undefined 

2.4.遍歷

2.4.1 一級對象遍歷方法

方法 特性
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)可以拿到不可枚舉屬性

2.4.2 多級對象遍歷

數據模型:

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);

2.5.深度拷貝

2.5.1 Object.assign

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.注意:
這個是僞深度拷貝,只能拷貝第一層

2.5.2 JSON.stringify

1.原理:是將對象轉化爲字符串,而字符串是簡單數據類型

2.5.3 遞歸拷貝

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;
}  


2.6.數據攔截

定義:利用對象內置方法,設置屬性,進而改變對象的屬性值

2.6.1 Object.defineProterty

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無法檢測數組索引賦值,改變數組長度的變化;
    但是通過數組方法來操作可以檢測到


6.存在的問題

不能監聽數組索引賦值和改變長度的變化
必須深層遍歷嵌套的對象,因爲defineProterty只能劫持對象的屬性,因此我們需要對每個對象的每個屬性進行遍歷,如果屬性值也是對象那麼需要深度遍歷,顯然能劫持一個完整的對象是更好的選擇

2.6.2 proxy

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;

3.問題和優點
reflect對象沒有構造函數
可以監聽數組索引賦值,改變數組長度的變化,
是直接監聽對象的變化,不用深層遍歷

2.6.3 defineProterty和proxy的對比

1.defineProterty是es5的標準,proxy是es6的標準;

2.proxy可以監聽到數組索引賦值,改變數組長度的變化;

3.proxy是監聽對象,不用深層遍歷,defineProterty是監聽屬性;

3.利用defineProterty實現雙向數據綁定(vue2.x採用的核心)
請戳,剖析Vue原理&實現雙向綁定MVVM
4.利用proxy實現雙向數據綁定(vue3.x會採用)

3.數組

數組基本上考察數組方法多一點,所以這裏就單純介紹常見的場景數組的方法,還有很多場景後續補充;
本文主要從應用來講數組api的一些騷操作;
如一行代碼扁平化n維數組、數組去重、求數組最大值、數組求和、排序、對象和數組的轉化等;
上面這些應用場景你可以用一行代碼實現?

3.1 扁平化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實現扁平

3.2 去重

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]

取新數組存值,循環兩個數組值相比較

3.3排序

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] 

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.開始篇
先排序再取值

3.5求和

1.終極篇

[1,2,3,4].arr.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截取改變數組,再利用遞歸求和

3.6合併

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]

3.7判斷是否包含值

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

3.8類數組轉化

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;  
 } 

3.9每一項設置值

1.終極篇

[1,2,3].fill(false) //[false,false,false] 

fill是ES6的方法
2.開始篇

[1,2,3].map(() => 0)

3.10每一項是否滿足

[1,2,3].every(item=>{return item>2}) //false

every是ES5的api,每一項滿足返回 true

3.11有一項滿足

[1,2,3].some(item=>{return item>2}) //true

some是ES5的api,有一項滿足返回 true

3.12.過濾數組

[1,2,3].filter(item=>{return item>2}) //[3]

filter是ES5的api,返回滿足添加的項的數組

3.13對象和數組轉化

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}

3.14 對象數組

[{count:1},{count:2},{count:3}].reduce((p, e)=>p+(e.count), 0)

4.數據結構篇

數據結構是計算機存儲、組織數據的方式,算法是系統描述解決問題的策略。瞭解基本的數據結構和算法可以提高代碼的性能和質量。
也是程序猿進階的一個重要技能。
手擼代碼實現棧,隊列,鏈表,字典,二叉樹,動態規劃和貪心算法

4.1 棧

棧的特點:先進後出

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

4.2 隊列

隊列:先進先出

  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();
  

4.3 鏈表

鏈表:存貯有序元素的集合,
但是不同於數組,每個元素是一個存貯元素本身的節點和指向下一個元素引用組成
要想訪問鏈表中間的元素,需要從起點開始遍歷找到所需元素

    
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;
        } 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.indexOf(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));  
  

4.4 字典

字典:類似對象,以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", "[email protected]");
  dictionary.set("John", "[email protected]");
  dictionary.set("Tyrion", "[email protected]");

  console.log(dictionary);
  console.log(dictionary.keys);
  console.log(dictionary.values);
  console.log(dictionary.items);
  

4.5 二叉樹

特點:每個節點最多有兩個子樹的樹結構

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());
  

5.算法篇

5.1 冒泡算法

冒泡排序,選擇排序,插入排序,此處不做贅述,請戳 排序

5.2 斐波那契

特點:第三項等於前面兩項之和

function fibonacci(num) { 
    if (num === 1 || num === 2) { 
        return 1
    }
    return fibonacci(num - 1) + fibonacci(num - 2)
  }

5.3 動態規劃

特點:通過全局規劃,將大問題分割成小問題來取最優解
案例:最少硬幣找零
美國有以下面額(硬幣):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]

5.4 貪心算法

特點:通過最優解來解決問題
用貪心算法來解決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))

6 設計模式

設計模式如果應用到項目中,可以實現代碼的複用和解耦,提高代碼質量。 本文主要介紹14種設計模式
寫UI組件,封裝框架必備

6.1 簡單工廠模式

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')
//最後得到角色,可以調用

6.2工廠方法模式

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');

6.3原型模式

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  

6.4單例模式

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");   

6.5外觀模式

1.定義:爲子系統中的一組接口提供一個一致的界面
2.應用:簡化複雜接口
3.代碼:
外觀模式

6.6適配器模式

1.定義:將一個接口轉換成客戶端需要的接口而不需要去修改客戶端代碼,使得不兼容的代碼可以一起工作
2.應用:適配函數參數
3.代碼:
適配器模式

6.7裝飾者模式

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('按鈕執行啦');
})

6.8橋接模式

1.定義:將抽象部分與它的實現部分分離,使它們都可以獨立地變化
2.代碼
橋接模式

6.9模塊方法模式

1.定義:定義一個模板,供以後傳不同參數調用
2.代碼:
模塊方法模式

6.10.觀察者模式

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'});

6.11狀態模式

1.定義:一個對象狀態改變會導致行爲變化
2.作用:解決複雜的if判斷
3.代碼
狀態模式

6.12策略模式

1.定義:定義了一系列家族算法,並對每一種算法單獨封裝起來,讓算法之間可以相互替換,獨立於使用算法的客戶
2.代碼
策略模式

6.13.訪問模式

1.定義:通過繼承封裝一些該數據類型不具備的屬性,
2.作用:讓對象具備數組的操作方法
3.代碼:
訪問者模式

6.14中介者模式

1.定義:設置一箇中間層,處理對象之間的交互
2.代碼:
中介者模式

7. HTTP

1.1 什麼是 HTTP

(圖片來源百度圖庫)HTTP 協議

從上圖可以看出 HTTP 是一個連接客戶端,網關和服務器的一個協議。

7.2 特點

支持客戶/服務器模式:可以連接客戶端和服務端;
簡單快速:請求只需傳送請求方法,路徑和請求主體;
靈活:傳輸數據類型靈活;
無連接:請求結束立即斷開;
無狀態:無法記住上一次請求。

7.3 怎麼解決無狀態和無連接

無狀態:HTTP 協議本身無法解決這個狀態,只有通過 cookie 和 session 將狀態做貯存,常見的場景是登錄狀態保持;

無連接:可以通過自身屬性 Keep-Alive。

7.4 請求過程

HTTP(S) 請求地址 → DNS 解析 → 三次握手 → 發送請求 → 四次揮手

三次握手過程(圖片來源 CSDN)

在這裏插入圖片描述

  1. 四次揮手過程(圖片來源 CSDN)

在這裏插入圖片描述

7.5 HTTP 0.9~3.0 對比

7.5.1 HTTP 0.9

只允許客戶端發送 GET 這一種請求;
且不支持請求頭,協議只支持純文本;
無狀態性,每個訪問獨立處理,完成斷開;
無狀態碼。

7.5.2 HTTP 1.0

有身份認證,三次握手;
請求與響應支持頭域;
請求頭內容;
屬性名 含義
Accept 可接受的 MIME 類型
Accept-Encoding 數據可解碼的格式
Accept-Language 可接受語言
Connection 值 keep-alive 是長連接
Host 主機和端口
Pragma 是否緩存,指定 no-cache 返回刷新
Referer 頁面路由
If-Modified-Since 值爲時間

  1. 響應頭內容;

屬性名 含義
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 方法
有狀態碼
支持長連接(但默認還是使用短連接)、緩存機制以及身份認證。

7.5.3 HTTP 1.1

請求頭增加 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 高
缺點:不能第一時間拿到最新修改文件

7.5.4 HTTP 2.0

採用二進制格式傳輸
多路複用,其實就是將請求數據分成幀亂序發送到 TCP 中。TCP 只能有一個 steam,所以還是會阻塞
報頭壓縮
服務器推送主動向 B 端發送靜態資源,避免往返延遲。

7.5.5 HTTP 3.0

是基於 QUIC 協議,基於 UDP
特點 自定義連接機制:TCP 以 IP/端口標識,變化重新連接握手,UDP 是一 64 位 ID 標識,是無連接; 自定義重傳機制:TCP 使用序號和應答傳輸,QUIC 是使用遞增序號傳輸; 無阻塞的多路複用:同一條 QUIC 可以創建多個 steam。

7.5.6 HTTPS

https 是在 http 協議的基礎上加了個 SSL;
主要包括:握手(憑證交換和驗證)和記錄協議(數據進行加密)。

7.5.7 緩存

按協議分:協議層緩存和非 http 協議緩存: 協議層緩存:利用 http 協議頭屬性值設置; 非協議層緩存:利用 meta 標籤的 http-equiv 屬性值 Expires,set-cookie。

按緩存分:強緩存和協商緩存: 強緩存:利用 cache-control 和 expires 設置,直接返回一個過期時間,所以在緩存期間不請求,If-modify-since; 協商緩存:響應頭返回 etag 或 last-modified 的哈希值,第二次請求頭 If-none-match 或 IF-modify-since 攜帶上次哈希值,一致則返回 304。

協商緩存對比: etag 優先級高於 last-modified; etag 精度高,last-modified 精度是 s,1s 內 etag 修改多少次都會被記錄; last-modified 性能好,etag 要得到 hash 值。

瀏覽器讀取緩存流程: 會先判斷強緩存; 再判斷協商緩存 etag(last-modified)是否存在; 存在利用屬性 If-None-match(If-Modified-since)攜帶值; 請求服務器,服務器對比 etag(last-modified),生效返回 304。 (圖片來源 CSDN)在這裏插入圖片描述

F5 刷新會忽略強緩存不會忽略協商緩存,ctrl+f5 都失效

7.5.8 狀態碼

序列 詳情
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 版本不受支持)

7.5.9 瀏覽器請求分析

在這裏插入圖片描述

7.5.10 總結

協議

版本 內容
http0.9 只允許客戶端發送 GET 這一種請求;且不支持請求頭,協議只支持純文本;無狀態性,每個訪問獨立處理,完成斷開;無狀態碼
http1.0 解決 0.9 的缺點,增加 If-modify-since(last-modify)和 expires 緩存屬性
http1. 增加 cache-control 和 If-none-match(etag)緩存屬性
http2.0 採用二進制格式傳輸;多路複用;報頭壓縮;服務器推送
http3.0 採用 QUIC 協議,自定義連接機制;自定義重傳機制;無阻塞的多路複用
緩存

類型 特性
強緩存 通過 If-modify-since(last-modify)、expires 和 cache-control 設置,屬性值是時間,所以在時間內不用請求
協商緩存 通過 If-none-match(etag)設置,etag 屬性是哈希值,所以要請求和服務器值對比

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章