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 構造函數

const sum = new Function('a', 'b' , 'return a + b')

1.1.4 三種方法的對比

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

1.2.ES5中函數的4種調用

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

1.2.1 函數調用模式

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

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

1.2.2 方法調用

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

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

1.2.3 構造器調用

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

function Person() {
  console.log(this); //指向實例
}
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是和定義時有關和調用無關
調用就是函數調用模式json

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

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

let arrowObj = {
  arrFun: function() {
   (() => {
     console.log(this)//指向函數arrFun
   })()
   }
 }
 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.劫持其餘對象的方法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({})

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

1.4.5 三者異同

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

1.5.函數的節流和防抖

類型 概念 應用
節流 事件觸發後每隔一段時間觸發一次,可觸發屢次 scroll,resize事件一段時間觸發屢次
防抖 事件觸發動做完後一段時間觸發一次 scroll,resize事件觸發完後一段時間觸發

節流:

1.5.1 節流

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)); //事件處理函數

1.5.2 防抖

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

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

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

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.代碼實現:

const currying = fn => {
const len = fn.length
return function curr (...args1) {
    if (args1.length >= len) {
         return fn(...args1)
    }
    return (...args2) => curr(...args1, ...args2)
    }

}

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沒法檢測數組索引賦值,改變數組長度的變化;
    可是經過數組方法來操做能夠檢測到

多級對象監聽

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只能劫持對象的屬性,所以咱們須要對每一個對象的每一個屬性進行遍歷,若是屬性值也是對象那麼須要深度遍歷,顯然能劫持一個完整的對象是更好的選擇

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;

涉及到多級對象或者多級數組監聽

//傳遞兩個參數,一個是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對象沒有構造函數
能夠監聽數組索引賦值,改變數組長度的變化,
是直接監聽對象的變化,不用深層遍歷

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

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

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 是一個鏈接客戶端,網關和服務器的一個協議。

7.2 特色

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

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

無狀態:HTTP 協議自己沒法解決這個狀態,只有經過 cookie 和 session 將狀態作貯存,常見的場景是登陸狀態保持;

無鏈接:能夠經過自身屬性 Keep-Alive。

7.4 請求過程

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

三次握手過程(圖片來源 CSDN)
3 次握手.jpg
在這裏插入圖片描述

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

image
在這裏插入圖片描述

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 值爲時間

響應頭內容;

屬性名 含義
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

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

7.5.6 HTTPS

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

7.5.7 緩存

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 都失效

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.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 屬性是哈希值,因此要請求和服務器值對比

8.總結

這只是 JS 原生梳理,後續會再出 react,node,小程序相關的梳理;原創碼字不易,歡迎 star!

相關文章
相關標籤/搜索