前端工程師自檢清單73答

開篇

本文參考文章《一名【合格】前端工程師的自檢清單》, 並對其中的部分題目進行了解答,若有遺漏或錯誤之處望大家指出糾正,共同進步。(點擊題目展開答案!)

此文章 Markdown 源文件地址:https://github.com/zxpsuper/blog...

一、JavaScript基礎

前端工程師吃飯的傢伙,深度、廣度一樣都不能差。

變量和類型

<details>
<summary>1. JavaScript 規定了幾種語言類型?</summary>

JavaScript中的每一個值都有它自己的類型,JavaScript規定了七種語言類型,他們是:

Undefined Null Boolean String Number Symbol Object
</details>
<details>
<summary>2. JavaScript 對象的底層數據結構是什麼?</summary>

對象數據被存儲於堆中 (如對象、數組、函數等,它們是通過拷貝和new出來的)。

引用類型的數據的地址指針是存儲於棧中的,當我們想要訪問引用類型的值的時候,需要先從棧中獲得對象的地址指針,然後,在通過地址指針找到堆中的所需要的數據。
</details>

<details>
<summary>3. Symbol 類型在實際開發中的應用、可手動實現一個簡單的 Symbol?</summary>

ES6 引入了一種新的原始數據類型 Symbol,表示獨一無二的值。

symbol類型的 key 不能被 Object.keysfor..of 循環枚舉。因此可當作私有變量使用。

let mySymbol = Symbol('key');
// 第一種寫法
let a = {};
a[mySymbol] = 'Hello!';
// 第二種寫法
let a = {
  [mySymbol]: 'Hello!'
};

</details>
<details>
<summary>4. JavaScript 中的變量在內存中的具體存儲形式</summary>

JavaScript 中的變量分爲基本類型和引用類型:

基本類型: 保存在棧內存中的簡單數據段,它們的值都有固定的大小,保存在棧空間,通過按值訪問

引用類型: 保存在堆內存中的對象,值大小不固定,棧內存中存放的該對象的訪問地址指向堆內存中的對象,JavaScript 不允許直接訪問堆內存中的位置,因此操作對象時,實際操作對象的引用
</details>

<details>
<summary>5. 基本類型對應的內置對象,以及他們之間的裝箱拆箱操作</summary>

String(), Number(), Boolean()

裝箱:就是把基本類型轉變爲對應的對象。裝箱分爲隱式和顯示

  // 隱式裝箱: 每當讀取一個基本類型的值時,後臺會創建一個該基本類型所對應的對象。
  // 在這個基本類型上調用方法,其實是在這個基本類型對象上調用方法。
  // 這個基本類型的對象是臨時的,它只存在於方法調用那一行代碼執行的瞬間,執行方法後立刻被銷燬。
  let num=123;
  num.toFixed(2); // '123.00'//上方代碼在後臺的真正步驟爲
  var c = new Number(123);
  c.toFixed(2);
  c = null;
  // 顯式裝箱: 通過內置對象 Boolean、Object、String 等可以對基本類型進行顯示裝箱。
  var obj = new String('123');

拆箱: 拆箱與裝箱相反,把對象轉變爲基本類型的值。

  Number([1]); //1
  // 轉換演變:
  [1].valueOf(); // [1];
  [1].toString(); // '1';Number('1'); //1 

</details>

<details>
<summary>6. 理解值類型和引用類型</summary>

JavaScript中的變量分爲基本類型和引用類型:

基本類型: 保存在棧內存中的簡單數據段,它們的值都有固定的大小,保存在棧空間,通過按值訪問

引用類型: 保存在堆內存中的對象,值大小不固定,棧內存中存放的該對象的訪問地址指向堆內存中的對象,JavaScript 不允許直接訪問堆內存中的位置,因此操作對象時,實際操作對象的引用
</details>

<details>
<summary>7. null 和 undefined 的區別</summary>

  1. Number 轉換的值不同,Number(null) 輸出爲 0, Number(undefined) 輸出爲 NaN
  2. null 表示一個值被定義了,但是這個值是空值
  3. undefined 表示缺少值,即此處應該有值,但是還沒有定義

</details>

<details>
<summary>8. 至少可以說出三種判斷 JavaScript 數據類型的方式,以及他們的優缺點,如何準確的判斷數組類型</summary>

  1. typeof —— 返回給定變量的數據類型,可能返回如下字符串:

      'undefined'——Undefined
      'boolean'——Boolean
      'string'——String
      'number'——Number
      'symbol'——Symbol
      'object'——Object / Null (Null 爲空對象的引用)
      'function'——Function
      // 對於一些如 error() date() array()無法判斷,都是顯示object類型
  2. instanceof 檢測 constructor.prototype 是否存在於參數 object 的原型鏈上,是則返回 true,不是則返回 false

      alert([1,2,3] instanceof Array) // true
      alert(new Date() instanceof Date) // true
      alert(function(){this.name="22";} instanceof Function) //true
      alert(function(){this.name="22";} instanceof function) //false
      // instanceof 只能用來判斷兩個對象是否屬於實例關係,而不能判斷一個對象實例具體屬於哪種類型。
  3. constructor —— 返回對象對應的構造函數。

      alert({}.constructor === Object);  =>  true
      alert([].constructor === Array);  =>  true
      alert('abcde'.constructor === String);  =>  true
      alert((1).constructor === Number);  =>  true
      alert(true.constructor === Boolean);  =>  true
      alert(false.constructor === Boolean);  =>  true
      alert(function s(){}.constructor === Function);  =>  true
      alert(new Date().constructor === Date);  =>  true
      alert(new Array().constructor === Array);  =>  true
      alert(new Error().constructor === Error);  =>  true
      alert(document.constructor === HTMLDocument);  =>  true
      alert(window.constructor === Window);  =>  true
      alert(Symbol().constructor);    =>    undefined 
      // null 和 undefined 是無效的對象,沒有 constructor,因此無法通過這種方式來判斷。
  4. Object.prototype.toString() 默認返回當前對象的 [[Class]] 。這是一個內部屬性,其格式爲 [object Xxx] ,是一個字符串,其中 Xxx 就是對象的類型。

      Object.prototype.toString.call(new Date);//[object Date]
      Object.prototype.toString.call(new String);//[object String]
      Object.prototype.toString.call(Math);//[object Math]
      Object.prototype.toString.call(undefined);//[object Undefined]
      Object.prototype.toString.call(null);//[object Null]
      Object.prototype.toString.call('') ;   // [object String]
      Object.prototype.toString.call(123) ;    // [object Number]
      Object.prototype.toString.call(true) ; // [object Boolean]
      Object.prototype.toString.call(Symbol()); //[object Symbol]
      Object.prototype.toString.call(new Function()) ; // [object Function]
      Object.prototype.toString.call(new Date()) ; // [object Date]
      Object.prototype.toString.call([]) ; // [object Array]
      Object.prototype.toString.call(new RegExp()) ; // [object RegExp]
      Object.prototype.toString.call(new Error()) ; // [object Error]
      Object.prototype.toString.call(document) ; // [object HTMLDocument]
      Object.prototype.toString.call(window) ; //[object global] window 是全局對象 global 的引用
      // 比較全面

</details>

<details>
<summary>9. 可能發生隱式類型轉換的場景以及轉換原則,應如何避免或巧妙應用</summary>

隱式轉換一般說的是 Boolean 的轉換

if 語句中,null""undefinded, 0, false 都會被轉化爲 false

一般應用於對接口數據判空時使用
</details>

<details>
<summary>10. 出現小數精度丟失的原因,JavaScript 可以存儲的最大數字、最大安全數字,JavaScript處理大數字的方法、避免精度丟失的方法</summary>

  • 精度丟失原因,說是 JavaScript 使用了 IEEE 754 規範,二進制儲存十進制的小數時不能完整的表示小數
  • 能夠表示的最大數字 Number.MAX_VALUE 等於 1.7976931348623157e+308 ,最大安全數字 Number.MAX_SAFE_INTEGER 等於 9007199254740991
  • 避免精度丟失

    • 計算小數時,先乘 1001000,變成整數再運算
    • 如果值超出了安全整數,有一個最新提案,BigInt 大整數,它可以表示任意大小的整數,注意只能表示整數,而不受安全整數的限制

</details>

原型和原型鏈

<details>
<summary>1. 理解原型設計模式以及 JavaScript 中的原型規則</summary>

A. 所有的引用類型(數組、對象、函數),都具有對象特性,即可自由擴展屬性;
B. 所有的引用類型(數組、對象、函數),都有一個`__proto__`屬性(隱式原型),屬性值是一個普通的對象;
C. 所有的函數,都具有一個 `prototype`(顯式原型),屬性值也是一個普通對象;
D. 所有的引用類型(數組、對象、函數),其隱式原型指向其構造函數的顯式原型;`(obj._proto_ === Object.prototype)`;
E. 當試圖得到一個對象的某個屬性時,如果這個對象本身沒有這個屬性,那麼會去它的 `__proto__` (即它的構造函數的 `prototype`)中去尋找;

</details>

<details>
<summary>2. instanceof 的底層實現原理,手動實現一個 instanceof</summary>

簡單說就是判斷實例對象的__proto__是不是強等於對象的prototype屬性,如果不是繼續往原型鏈上找,直到 __proto__null 爲止。

function instanceOf(obj, object) {//obj 表示實例對象,object 表示對象
  var O = object.prototype;
  obj = obj.__proto__;
  while (true) { 
      if (obj === null) 
          return false; 
      if (O === obj) // 這裏重點:當 O 嚴格等於 obj 時,返回 true 
          return true; 
      obj = obj.__proto__; 
  } 
}

</details>

<details>
<summary>3. 理解 JavaScript 的執行上下文棧,可以應用堆棧信息快速定位問題</summary>

執行上下文 就是當前 JavaScript 代碼被解析和執行時所在環境的抽象概念, JavaScript 中運行任何的代碼都是在執行上下文中運行。

執行上下文總共有三種類型:全局執行上下文, 函數執行上下文, Eval 函數執行上下文

執行棧,在其他編程語言中也被叫做調用棧,具有 LIFO(後進先出)結構,用於存儲在代碼執行期間創建的所有執行上下文。

</details>

<details>
<summary>4. 實現繼承的幾種方式以及他們的優缺點</summary>

詳情請點擊:《繼承的幾種實現方式》

</details>

<details>
<summary>5. 可以描述 new 一個對象的詳細過程,手動實現一個 new 操作符</summary>

  • new一個對象的詳細過程:

    function Test() {}
    const test = new Test();
  1. 創建一個對象 const obj = {}
  2. 設置新對象的 constructor 屬性爲構造函數的名稱,設置新對象的__proto__屬性指向構造函數的 prototype 對象

    obj.constructor = Test;
    obj.__proto__ = Test.prototype;
  3. 使用新對象調用函數,函數中的 this 被指向新實例對象 Test.call(obj)
  4. 將初始化完畢的新對象地址,保存到等號左邊的變量中
  • 實現一個new操作符

    function myNew(Obj,...args){
        var obj = Object.create(Obj.prototype);//使用指定的原型對象及其屬性去創建一個新的對象
        Obj.apply(obj,args); // 綁定 this 到obj, 設置 obj 的屬性
        return obj; // 返回實例
    }

</details>

<details>
<summary>6. 理解 es6 class 構造以及繼承的底層實現原理</summary>

  • ES6 類的底層還是通過構造函數去創建的。

    // es6 Parent類實現
    class Parent {
      constructor(name,age){
          this.name = name;
          this.age = age;
      }
      speakSomething(){
          console.log("I can speek chinese");
      }
    }
    // 轉化爲
    var Parent = function () {
      function Parent(name, age) {
          _classCallCheck(this, Parent); // 判斷實例 Parent instanceof Parent(函數)是否爲true
    
          this.name = name;
          this.age = age;
      }
      // 此方法通過使用 Object.defineProperty 爲 function Parent 的 prototype 添加屬性值
      _createClass(Parent, [{
          key: "speakSomething",
          value: function speakSomething() {
              console.log("I can speek chinese");
          }
      }]);
    
      return Parent;
    }();
  • ES6 的繼承實現

    //定義子類,繼承父類
    class Child extends Parent {
      static width = 18
      constructor(name,age){
          super(name,age);
      }
      coding(){
          console.log("I can code JS");
      }
    }
    // 轉化爲
    var Child = function (_Parent) {
      _inherits(Child, _Parent);
    
      function Child(name, age) {
          _classCallCheck(this, Child);
    
          return _possibleConstructorReturn(this, (Child.__proto__ || Object.getPrototypeOf(Child)).call(this, name, age));
      }
    
      _createClass(Child, [{
          key: "coding",
          value: function coding() {
              console.log("I can code JS");
          }
      }]);
    
      return Child;
    }(Parent);

這裏其實就是多了一個 _inherits(Child, _Parent); 方法,實現了以下功能,具體可看文章《ES6類以及繼承的實現原理》

  //實現的結果是:
  subClass.prototype.__proto__ = superClass.prototype
  subClass.__proto__ = superClass // 實現靜態屬性的繼承

</details>

作用域和閉包

<details>
<summary>1. 理解詞法作用域和動態作用域</summary>

詞法作用域也稱靜態作用域,javascript 採用靜態作用域

靜態作用域 —— 函數的作用域基於函數創建的位置。

動態作用域 —— 函數的作用域基於函數的使用位置。

var value = 1;

function foo() {
  console.log(value);
}

function bar() {
  var value = 2;
  foo();
}

bar(); // 輸出 1 。JavaScript 採用的是詞法作用域,也稱爲靜態作用域。相同的,動態作用域此代碼應該輸出 2

</details>

<details>
<summary>2. 理解 JavaScript 的作用域和作用域鏈</summary>

作用域(scope)就是變量訪問規則的有效範圍。

JavaScript 中全局變量的作用域是全局的,在代碼的任何地方都是有定義的。然而函數的參數和局部變量只在函數體內有定義。另外局部變量的優先級要高於同名的全局變量,也就是說當局部變量與全局變量重名時,局部變量會覆蓋全局變量。

</details>

<details>
<summary>3. this的原理以及幾種不同使用場景的取值</summary>

this的幾種不同使用場景的取值 +

<a href="http://www.ruanyifeng.com/blog/2018/06/javascript-this.html" target="_blank">JavaScript 的 this 原理</a>

</details>

<details>
<summary>4. 閉包的實現原理和作用,可以列舉幾個開發中閉包的實際應用</summary>

原理:閉包就是能夠讀取其他函數內部變量的函數。由於在Javascript語言中,只有函數內部的子函數才能讀取局部變量,因此可以把閉包簡單理解成"定義在一個函數內部的函數"。

所以,在本質上,閉包就是將函數內部和函數外部連接起來的一座橋樑。

作用:閉包可以用在許多地方。它的最大用處有兩個,一個是前面提到的可以讀取函數內部的變量,另一個就是讓這些變量的值始終保持在內存中。

應用:1. 匿名自執行函數 2. 結果緩存 3. 封裝局部變量

參考鏈接:《學習Javascript閉包(Closure)》
</details>

<details>
<summary>5. 理解堆棧溢出和內存泄漏的原理,如何防止</summary>

堆棧溢出 的產生是由於過多的函數調用,導致調用堆棧無法容納這些調用的返回地址,一般在遞歸中產生。堆棧溢出很可能由無限遞歸(Infinite recursion)產生,但也可能僅僅是過多的堆棧層級.

參考鏈接:《內存泄漏與避免》
</details>

<details>
<summary>6. 如何處理循環的異步操作</summary>

  1. 將異步操作變同步,使用 async/await.
  2. 去掉循環,將循環變成遞歸

</details>

執行機制

<details>
<summary>1. 爲何 try 裏面放 return,finally 還會執行,理解其內部機制</summary>

try 語句中,在執行 return 語句時,要返回的結果已經準備好了,就在此時,程序轉到 finally 執行了。

在轉去之前,try 中先把要返回的結果存放到局部變量中去,執行完 finally 之後,在從中取出返回結果。

因此,即使finally 中對返回的結果進行了改變,但是不會影響返回結果。

它應該使用棧保存返回值。

</details>

<details>
<summary>2. JavaScript 如何實現異步編程,可以詳細描述 EventLoop 機制</summary>

JavaScript 如何實現異步編程:

  1. callback (回調函數)

回調函數代表着,當某個任務處理完,然後需要做的事。比如讀取文件,連接數據庫,等文件準備好,或數據庫連接成功執行編寫的回調函數,又比如像一些動畫處理,當動畫走完,然後執行回調。

  1. 發佈訂閱模式

顧名思義,便是先訂閱了事件,有人一發布事件你就知道了,接着執行後面的操作。

  1. Promise

Promise,簡單說就是一個容器,裏面保存着某個未來纔會結束的事件的結果,相比回調函數,Promise 提供統一的 API,各種異步操作都可以用同樣的方法進行處理。

  1. Generator (生成器)函數

Generator 函數是 ES6 提供的一種異步編程解決方案,其行爲類似於狀態機。

  1. async/await

async/await 本質上還是基於 Generator 函數,可以說是 Generator 函數的語法糖,async 就相當於之前寫的run函數(執行Generator函數的函數),而 await 就相當於 yield ,只不過 await 表達式後面只能跟着 Promise 對象,如果不是 Promise 對象的話,會通過 Promise.resolve 方法使之變成 Promise 對象。async 修飾 function,其返回一個 Promise 對象。

《瀏覽器 Event Loop 機制》
</details>

<details>
<summary>3. 宏任務和微任務分別有哪些</summary>

宏任務: setTimeout,setInterval,setImmediate (Node獨有),requestAnimationFrame (瀏覽器獨有),I/O,UI rendering (瀏覽器獨有)

微任務: process.nextTick (Node獨有),Promise,Object.observe,MutationObserver

</details>

<details>
<summary>4. 可以快速分析一個複雜的異步嵌套邏輯,並掌握分析方法</summary>

// 執行順序,先微隊列,後宏隊列。
console.log(1);
setTimeout(() => {
  console.log(2);
  setTimeout(() => {
    console.log(8);
  })
  Promise.resolve().then(() => {
    console.log(3)
  });
});
new Promise((resolve, reject) => {
  console.log(4)
  setTimeout(() => {
    console.log(10);
  })
  resolve()
}).then(() => {
  console.log(5);
  Promise.resolve().then(() => {
    console.log(11)
  });
  setTimeout(() => {
    console.log(13);
  })
})
setTimeout(() => {
  Promise.resolve().then(() => {
    console.log(9)
  });
  console.log(6);
  setTimeout(() => {
    console.log(12);
  })
})
console.log(7);

從頭至尾執行一次代碼,根據上面分類規則分至不同隊列, new promise( function ) 也是立即執行。setTimeout 的回調函數屬於宏隊列(macrotask)resolve 的回調函數屬於微隊列

// 棧區(stack)
console.log(1);
console.log(4);
console.log(7);
// 宏隊列
() => {
  console.log(2);
  setTimeout(() => {
    console.log(8);
  })
  Promise.resolve().then(() => {
    console.log(3)
  });
}
() => {
  console.log(10);
}
() => {
  Promise.resolve().then(() => {
    console.log(9)
  });
  console.log(6);
  setTimeout(() => {
    console.log(12);
  })
}
// 微隊列
() => {
  console.log(5);
  Promise.resolve().then(() => {
    console.log(11)
  });
  setTimeout(() => {
    console.log(13);
  })
}

優先執行微隊列,微隊列執行過程中產生的微隊列和宏隊列置於隊列末尾排序執行,而宏隊列產生的微隊列和宏隊列於新的隊列中等待。。

執行微隊列:(分類)

// 棧區(stack)
console.log(1);
console.log(4);
console.log(7);
//////////
console.log(5);
// 微隊列
() => {
  console.log(11)
});
// 宏隊列
() => {
  console.log(2);
  setTimeout(() => {
    console.log(8);
  })
  Promise.resolve().then(() => {
    console.log(3)
  });
}
() => {
  console.log(10);
}
() => {
  Promise.resolve().then(() => {
    console.log(9)
  });
  console.log(6);
  setTimeout(() => {
    console.log(12);
  })
}
() => {
    console.log(13);
}

此時新增了一個微隊列console.log(11),因爲是微隊列產生的,繼續執行:

// 棧區(stack)
console.log(1);
console.log(4);
console.log(7);
//////////
console.log(5);
/////////
console.log(11)
// 微隊列-空
// 宏隊列
() => {
  console.log(2);
  setTimeout(() => {
    console.log(8);
  })
  Promise.resolve().then(() => {
    console.log(3)
  });
}
() => {
  console.log(10);
}
() => {
  Promise.resolve().then(() => {
    console.log(9)
  });
  console.log(6);
  setTimeout(() => {
    console.log(12);
  })
}
() => {
    console.log(13);
}

執行完微隊列後執行宏隊列:

// 棧區(stack)
console.log(1);
console.log(4);
console.log(7);
//////////
console.log(5);
/////////
console.log(11);
/////////
console.log(2);
console.log(10);
console.log(6);
console.log(13);
// 微隊列
() => {
  console.log(3)
}
() => {
  console.log(9)
}
// 宏隊列
() => {
  console.log(8);
}
() => {
  console.log(12);
}

接下來執行微隊列後宏隊列,即:

// 棧區(stack)
console.log(1);
console.log(4);
console.log(7);
//////////
console.log(5);
/////////
console.log(11);
/////////
console.log(2);
console.log(10);
console.log(6);
console.log(13);
////////
console.log(3)
console.log(9)
////////
console.log(8);
console.log(12);

</details>

<details>
<summary>5. 使用 Promise 實現串行</summary>

// 一個 promise 的 function
function delay(time) {
 return new Promise((resolve, reject) => {
   console.log(`wait ${time}s`)
   setTimeout(() => {
     console.log('execute');
     resolve()
   }, time * 1000)
 })
}
const arr = [3, 4, 5];
  1. reduce

    arr.reduce((s, v) => {
     return s.then(() => delay(v))
    }, Promise.resolve())
  2. async + 循環 + await

    (
     async function () {
       for (const v of arr) {
         await delay(v)
       }
     }
    )()
  3. 普通循環

    let p = Promise.resolve()
    for (const i of arr) {
     p = p.then(() => delay(i))
    }
  4. 遞歸

    function dispatch(i, p = Promise.resolve()) {
     if (!arr[i]) return Promise.resolve()
     return p.then(() => dispatch(i + 1, delay(arr[i])))
    }
    dispatch(0)

</details>

<details>
<summary>6. Node 與瀏覽器 EventLoop 的差異</summary>

《JavaScript 運行機制詳解:再談Event Loop》

《帶你徹底弄懂Event Loop》
</details>

<details>
<summary>7. 如何解決頁面加載海量數據而頁面不卡頓</summary>

  1. 分治思想,在一定的時間內多次加載數據,直至渲染完成,使用 window.requestAnimationFramedocument.createDocumentFragment() 實現, 可參考文章【如何解決頁面加載海量數據而不凍結前端UI】
  2. 局部顯示,畢竟用戶能看到的就一屏內容,監聽用戶的滾動行爲,改變顯示元素,可使 DOM 結構最簡單化。可參考文章【大數據如何在前端流暢展示】,不過他的 Demo有點問題.

</details>

語法和API

<details>
<summary>1. 理解 ECMAScript 和 JavaScript 的關係</summary>

ECMAScriptJavaScript 的規範,JavaScriptECMAScript 的實現。
</details>

<details>
<summary>2. 熟練運用 es5、es6 提供的語法規範</summary>

【JavaScript 標準參考教程(alpha)】

【ECMAScript 6 入門】
</details>

<details>
<summary>3. setInterval 需要注意的點,使用 settimeout 實現 setInterval</summary>

  • setInterval 需要注意的點:

在使用 setInterval 方法時,每一次啓動都需要對 setInterval 方法返回的值做一個判斷,判斷是否是空值,若不是空值,則要停止定時器並將值設爲空,再重新啓動,如果不進行判斷並賦值,有可能會造成計時器循環調用,在同等的時間內同時執行調用的代碼,並會隨着代碼的運行時間增加而增加,導致功能無法實現,甚至佔用過多資源而卡死奔潰。因此在每一次使用setInterval方法時,都需要進行一次判斷。

let timer = setInterval(func, 1000)
// 在其他地方再次用到setInterval(func, 1000)
if (timer !== null) {
    clearInterval(timer)
    timer = null
}
timer = setInterval(func, 1000)
  • 使用 settimeout 實現 setInterval

    setIntervalFunc = () =>{
      console.log(1) //使用遞歸
      setTimeout(setIntervalFunc, 1000);
    };
    setInterval()

</details>

<details>
<summary>4. JavaScript 提供的正則表達式 API、可以使用正則表達式(郵箱校驗、URL解析、去重等)解決常見問題</summary>

郵箱校驗:

function isEmail(emailStr) {
    return /^[a-zA-Z0-9]+([._-]*[a-zA-Z0-9]*)*@[a-zA-Z0-9]+.[a-zA-Z0-9{2,5}$]/.test(emailStr);
}

URL解析:

function isUrl(urlStr) {
    return /^(?:http(s)?:\/\/)?[\w.-]+(?:\.[\w\.-]+)+[\w\-\._~:/?#[\]@!\$&'\*\+,;=.%]+$/.test(value)
}

數組去重:

// set結構
let arr = [1, 1, 2, 2, 3, 3]
arr2 = [...new Set(arr)]
console.log(arr2) // [1,2,3]

// Object.keys(), 利用屬性 key 的唯一性
let arrObj = [1, 1, 2, 2, 3, 3]
arrObj2 = {}
for (i in arrObj) {
    arrObj2[arrObj[i]] = true
}
let arrObj3 = Object.keys(arrObj2)
console.log(arrObj3)

// 利用 indexOf() 查詢數組內是否已經包含該元素
var arrIndexOf = ['a','c','b','d','a','b']
var arrIndexOf2 = [];
for(var i = 0;i<arrIndexOf.length;i++){
    if(arrIndexOf2.indexOf(arrIndexOf[i])<0){
        arrIndexOf2.push(arrIndexOf[i]);
    }
}
console.log(arrIndexOf2)// ['a', 'c', 'b', 'd']

</details>

二、HTML和CSS

HTML

<details>
<summary>1. 從規範的角度理解 HTML,從分類和語義的角度使用標籤</summary>

語義化標籤<header> <footer> <nav> <section> <article> <aside> 等

  • 讓頁面呈現清晰的結構
  • 屏幕閱讀器(如果訪客有視障)會完全根據你的標記來“讀”你的網頁
  • 搜索引擎的爬蟲依賴標籤確定上下文和權重問題
  • 便於團隊開發和維護

標籤分類

  • 文檔標籤(10 個):`<html>、<head>、<body>、<title>、<meta>、<base>

、<style>、<link>、<script>、<noscript>`

  • 表格標籤(10 個):`<table>、<thead>、<tbody>、<tfoot>、<tr>、<td>、<th>

、<col>、<colgroup>、<caption>`

  • 表單標籤(10 個):`<from>、<input>、<textarea>、<button>、<select>

、<optgroup>、<option>、<label>、<fieldset>、<legend>`

  • 列表標籤(6個):<ul>、<ol>、<li>、<dl>、<dt>、<dd>
  • 多媒體標籤(5個):<img>、<map>、<area>、<object>、<param>
  • 文章標籤:<h1> - <h6> 、<p>、<br>、<span>、<bdo>、<pre>、<acronym>、<abbr>、<blockquote>、<q>、<ins>、<del>、<address>
  • 字體樣式標籤:<tt>、<i>、<b>、<big>、<small>、<em>、<strong>、<dfn>、<code>、<samp>、<kbd>、<var>、<cite>、<sup>、<sub>

    在不同的場景使用不同的標籤,更能顯示清晰的結構。

</details>

<details>
<summary>2. 元信息類標籤 (head、title、meta) 的使用目的和配置方法</summary>

<head> 標籤用於定義文檔的頭部,它是所有頭部元素的容器。<head> 中的元素可以引用腳本、指示瀏覽器在哪裏找到樣式表、提供元信息等等。可以包含的標籤有: <base>, <link>, <meta>, <script>, <style>, 以及 <title>。

<title> 定義文檔的標題,它是 head 部分中唯一必需的元素。

<meta>元素可提供有關頁面的元信息(meta-information),比如針對搜索引擎和更新頻度的描述和關鍵詞。使用方法參考【meta標籤詳解】

</details>

<details>
<summary>3. HTML5 離線緩存原理</summary>

  • [ ] 待補充

</details>

<details>
<summary>4. 可以使用 Canvas API、SVG 等繪製高性能的動畫</summary>

</details>

CSS

<details>
<summary>1. CSS 盒模型,在不同瀏覽器的差異</summary>

  • 標準 w3c 盒子模型的範圍包括 margin、border、padding、content,並且 content 部分不包含其他部分
  • ie 盒子模型的範圍也包括 margin、border、padding、content,和標準 w3c 盒子模型不同的是:ie 盒子模型的 content 部分包含了 borderpading

</details>

<details>
<summary>2. CSS 所有選擇器及其優先級、使用場景,哪些可以繼承,如何運用at規則</summary>

不同級別優先級!important > 行內樣式 > ID選擇器 > 類選擇器 > 元素 > 通配符 > 繼承 > 瀏覽器默認屬性

相同級別優先級內聯(行內)樣式 > 內部樣式表 > 外部樣式表 > 導入樣式(@import)。

可繼承屬性

字體系列屬性, font-family, font-weight, font-size, font-style...
文本系列屬性, text-indent, text-align, line-heigh, word-spacing, letter-spacing, text-transform, color
元素可見性:visibility, 光標屬性:cursor

AT rule:

一、什麼是 at-rules

eg:@charset "utf-8";

at-ruleCSS 樣式聲明,以 @ 開頭,緊跟着是標識符(charset),最後以分號(;)結尾。

二、幾個 at-rules

1、@charset —定義被樣式表使用的字符集

2、@import ——告訴 CSS 引擎包含外部的 CSS 樣式表

3、@namespace——告訴 CSS 引擎所有的內容都必須考慮使用 XML 命名空間前綴

4、嵌套at-rules

(1)@media——條件組規則。如果設備符合標準定義的條件查詢則使用該媒體

(2)@font-face——描述了一個將從外部下載的字體

(3)@keyframes——描述了中間步驟在 CSS 動畫的序列

(4)@page——描述了文件的佈局變化,當要打印文檔時。

(5)@supports——條件組規則,如果瀏覽器滿足給出的規則,則把它應用到內容中

(6)@document——條件組規則,如果被用到文檔的 CSS 樣式表滿足了給定的標準,那麼將被應用到所有的內容中。
</details>

<details>
<summary>3. CSS 僞類和僞元素有哪些,它們的區別和實際應用</summary>

僞類:用於向某些選擇器添加特殊的效果. :active, :focus, :link, :visited, :hover, :first-child

僞元素:用於將特殊的效果添加到某些選擇器. :before, :after, :first-line, :first-letter

僞類和僞元素的根本區別在於:它們是否創造了新的元素(抽象)。從我們模仿其意義的角度來看,如果需要添加新元素加以標識的,就是僞元素,反之,如果只需要在既有元素上添加類別的,就是僞類。

</details>

<details>
<summary>4. HTML 文檔流的排版規則,CSS 幾種定位的規則、定位參照物、對文檔流的影響,如何選擇最好的定位方式,雪碧圖實現原理</summary>

HTML 文檔流的排版規則: 把元素按從上而下,從左到右的順序默認排列。不在一行的元素從上而下,在一行的從左到右排列。

CSS 幾種定位的規則:

  • static 定位(普通流定位)
  • float 定位(浮動定位), 有兩個取值:left (左浮動)和 right (右浮動)。

浮動元素會在沒有浮動元素的上方,效果上看是遮擋住了沒有浮動的元素,有float樣式規則的元素是脫離文檔流的,它的父元素的高度並不能有它撐開。

  • relative 定位(相對定位), 相對本元素的左上角進行定位,top,left,bottom,right 都可以有值。雖然經過定位後,位置可能會移動,但是本元素並沒有脫離文檔流,還佔有原來的頁面空間。
  • absolute 定位(絕對定位), 相對於祖代中有 relative (相對定位)並且離本元素層級關係上是最近的元素的左上角進行定位,如果在祖代元素中沒有有 relative定位的,就默認相對於body進行定位。絕對定位是脫離文檔流的
  • fixed 定位(固定定位),這種定位方式是相對於整個文檔的,只需設置它相對於各個方向的偏移值,就可以將該元素固定在頁面固定的位置,通常用來顯示一些提示信息,脫離文檔流;

雪碧圖實現原理CSS Sprite,是一種 CSS 圖像合併技術,該方法是將小圖標和背景圖像合併到一張圖片上,然後利用 css 的背景定位來顯示需要顯示的圖片部分。
</details>

<details>
<summary>5. 水平垂直居中的方案、可以實現6種以上並對比它們的優缺點</summary>

參考文章: 【CSS實現水平垂直居中的1010種方式】
</details>

<details>
<summary>6. BFC 實現原理,可以解決的問題,如何創建BFC</summary>

BFC(Block formatting context) 直譯爲"塊級格式化上下文"。它是一個獨立的渲染區域,只有塊級元素參與, 它規定了內部的塊級元素如何佈局,並且與這個區域外部毫不相干。

BCF 可以解決的問題:浮動定位,消除外邊距摺疊,清除浮動,自適應多欄佈局

BFC的創建:根元素或包含根元素的元素,浮動元素(float 不爲none),絕對定位元素( positionabsolute 或者 fixed),displayinline-block,table-cell,table-caption,overflow 值不爲 visible,彈性元素( flex 佈局),網格元素( grid 佈局)
</details>

<details>
<summary>7. CSS模塊化方案、如何配置按需加載、如何防止 CSS 阻塞渲染</summary>

CSS模塊化方案: 文件細化,命名約定,CSS Modules , css in js

如何防止 CSS 阻塞渲染:

CSS 是阻塞渲染的資源。需要將它儘早、儘快地下載到客戶端,以便縮短首次渲染的時間。

有一些 CSS 樣式只在特定條件下(例如顯示網頁或將網頁投影到大型顯示器上時)使用,我們可以通過 CSS“媒體類型”和“媒體查詢”來解決這類用例:

<link href="print.css" rel="stylesheet" media="print">
<link href="other.css" rel="stylesheet" media="(min-width: 40em)">

首屏相關的關鍵 `CSS` 使用阻塞渲染的方式加載,所有的非關鍵 `CSS` 在首屏渲染完成後加載。

</details>

<details>
<summary>8. 手寫圖片瀑布流效果</summary>

參考文章:【瀑布流佈局的實現】
</details>

<details>
<summary>9. 使用CSS繪製幾何圖形(圓形、三角形、扇形、菱形等)</summary>

// 圓形
.circle{
    width:100px;
    height:100px;
    border-radius:50%;
    background:blue;
}
// 三角形
.triangle {
    width: 0;
    height: 0;
    border: 50px solid blue;
    /* 通過改變邊框顏色,可以改變三角形的方向 */
    border-color: blue transparent transparent transparent;
}
// 扇形,扇形是由一個圓形和一個矩形進行組合得到的,用矩形遮住圓形的一部分就形成了扇形。
.sector {
    width: 142px;
    height: 142px;
    background: #fff;
    border-radius: 50%;
    background-image: linear-gradient(to right, transparent 50%, #655 0);
}

.sector::before {
    content: '';
    display: block;
    margin-left: 50%;
    height: 100%;
    width: 100%;
    background-color: inherit;
    transform-origin: left;
    /*調整角度,改變扇形大小*/
    transform: rotate(230deg);
}
// 菱形
.rhombus {
    width: 200px;
    height: 200px;
    transform: rotateZ(45deg) skew(30deg, 30deg);
    background: blue;
}

</details>

<details>
<summary>10. 使用純 CSS 實現曲線運動(貝塞爾曲線)</summary>

CSS3 新增了 transition-timing-function 屬性,它的取值就可以設置爲一個三次貝塞爾曲線方程。

參考文章: 【貝塞爾曲線的css實現——淘寶加入購物車基礎動畫】
</details>

<details>
<summary>11. 實現常用佈局(三欄、聖盃、雙飛翼、吸頂),說出多種方式並理解其優缺點</summary>

聖盃佈局, 兩邊頂寬,中間自適應的三欄佈局。

  • [ ] 期待評論補充

</details>

三、計算機基礎

關於編譯原理,不需要理解非常深入,但是最基本的原理和概念一定要懂,這對於學習一門編程語言非常重要

編譯原理

<details>
<summary>1. 理解代碼到底是什麼,計算機如何將代碼轉換爲可以運行的目標程序</summary>

代碼就是程序員用開發工具所支持的語言寫出來的源文件,是一組由字符、符號或信號碼元以離散形式表示信息的明確的規則體系。

計算機源代碼最終目的是將人類可讀文本翻譯成爲計算機可執行的二進制指令,這種過程叫編譯,它由通過編譯器完成。
</details>

<details>
<summary>2. 正則表達式的匹配原理和性能優化</summary>

  • [ ] 待補充

</details>

<details>
<summary>3. 如何將JavaScript代碼解析成抽象語法樹(AST)</summary>

  • [ ] 待補充

</details>

<details>
<summary>4. base64 的編碼原理</summary>

  • [ ] 待補充

</details>

<details>
<summary>5. 幾種進制的相互轉換計算方法,在 JavaScript 中如何表示和轉換</summary>

parseInt(str, radix) 將一個 radix 進制的 str 轉化爲十進制,parseInt('23',8) // 19,將八進制的‘23’轉化爲10進制的‘19’

number.toString(radix) 將一個數字轉化爲 radix 進制的數字字符串

0x11.toString(8) // 21
0x11.toString(10) // 17
0x11.toString(2) // 10001

</details>

網絡協議

<details>
<summary>1. 理解什麼是協議,瞭解 TCP/IP 網絡協議族的構成,每層協議在應用程序中發揮的作用</summary>

協議,網絡協議的簡稱,網絡協議是通信計算機雙方必須共同遵從的一組約定。如怎麼樣建立連接、怎麼樣互相識別等。只有遵守這個約定,計算機之間才能相互通信交流。它的三要素是:語法、語義、時序。

TCP/IP 網絡協議族的構成: TCP/IP 協議是 Internet 最基本的協議。由傳輸層的 TCP 協議和網絡層的 IP 協議組成。

TCP 負責發現傳輸的問題,一有問題就發出信號,要求重新傳輸,直到所有數據安全正確地傳輸到目的地。而 IP 是給因特網的每一臺聯網設備規定一個地址。

應用層

應用層決定了向用戶提供應該服務時通信的活動。

TCP/IP 協議族內預存了各類通用的應用服務。比如,FTP(File Transfer Protocol,文件傳輸協議)和 DNS(Domain Name System,域名系統)服務就是其中的兩類。HTTP 協議也處於該層。

傳輸層

傳輸層對上層應用層,提供處於網絡連接中兩臺計算機之間的數據傳輸。

在傳輸層有兩個性質不同的協議:TCP(Transmission Control Protocol,傳輸控制協議)和 UDP(User Data Protocol,用戶數據報協議)。

網絡層(又名網絡互連層)

網絡層用來處理在網絡上流動的數據包。數據包是網絡傳輸的最小數據單位。該層規定了通過怎樣的路徑(所謂的傳輸路線)到達對方計算機,並把數據包傳送給對方。

與對方計算機之間通過多臺計算機或網絡設備進行傳輸時,網絡層所起的所用就是在衆多的選項內選擇一條傳輸路線。

鏈路層(又名數據鏈路層,網絡接口層)

用來處理連接網絡的硬件部分。包括控制操作系統、硬件的設備驅動、NIC(Network Interface Card,網絡適配器,即網卡),及光纖等物理可見部分(還包括連接器等一切傳輸媒介)。硬件上的範疇均在鏈路層的作用範圍之內。
</details>

<details>
<summary>2. 三次握手和四次揮手詳細原理,爲什麼要使用這種機制</summary>

三次握手和四次揮手詳細原理:

三次握手:避免連接請求的數據包丟失,數據傳輸過程因爲網絡併發量很大在某結點被阻塞

四次揮手: TCP連接是全雙工通道,需要雙向關閉。

參考文章: 【TCP/IP協議族】

</details>

<details>
<summary>3. 有哪些協議是可靠,TCP有哪些手段保證可靠交付</summary>

TCP的協議:FTP(文件傳輸協議)、Telnet(遠程登錄協議)、SMTP(簡單郵件傳輸協議)、POP3(和 SMTP 相對,用於接收郵件)、HTTP 協議等。

TCP 提供可靠的、面向連接的數據傳輸服務。使用 TCP 通信之前,需要進行“三次握手”建立連接,通信結束後還要使用“四次揮手”斷開連接。
</details>

<details>
<summary>4. DNS的作用、DNS解析的詳細過程,DNS優化原理</summary>

DNS 的作用:DNS是互聯網的一項服務。它作爲將域名和IP地址相互映射的一個分佈式數據庫,能夠使人更方便地訪問互聯網。

DNS解析過程

1、在瀏覽器中輸入 www.qq.com 域名,操作系統會先檢查自己本地的 hosts 文件是否有這個網址映射關係,如果有,就先調用這個 IP 地址映射,完成域名解析。

2、如果 hosts 裏沒有這個域名的映射,則查找本地 DNS解析器緩存,是否有這個網址映射關係,如果有,直接返回,完成域名解析。

3、如果 hosts本地DNS解析器緩存 都沒有相應的網址映射關係,首先會找 TCP/ip 參數中設置的 首選DNS服務器,在此我們叫它 本地DNS服務器,此服務器收到查詢時,如果要查詢的域名,包含在本地配置區域資源中,則返回解析結果給客戶機,完成域名解析,此解析具有權威性。

4、如果要查詢的域名,不由 本地DNS服務器區域解析,但該服務器已緩存了此網址映射關係,則調用這個 IP 地址映射,完成域名解析,此解析不具有權威性。

5、如果 本地DNS服務器 本地區域文件與緩存解析都失效,則根據 本地DNS服務器 的設置(是否設置轉發器)進行查詢,如果未用轉發模式,本地 DNS 就把請求發至 13 臺根 DNS,根 DNS 服務器收到請求後會判斷這個域名 (.com) 是誰來授權管理,並會返回一個負責該頂級域名服務器的一個IP本地DNS服務器 收到 IP 信息後,將會聯繫負責 .com 域的這臺服務器。這臺負責 .com 域的服務器收到請求後,如果自己無法解析,它就會找一個管理 .com 域的下一級 DNS 服務器地址(http://qq.com)本地DNS服務器。當 本地DNS服務器 收到這個地址後,就會找 http://qq.com 域服務器,重複上面的動作,進行查詢,直至找到 www.qq .com 主機。

6、如果用的是轉發模式,此 DNS 服務器就會把請求轉發至上一級 DNS 服務器,由上一級服務器進行解析,上一級服務器如果不能解析,或找根 DNS 或把轉請求轉至上上級,以此循環。不管是 本地DNS服務器 用是是轉發,還是根提示,最後都是把結果返回給 本地DNS服務器,由此 DNS 服務器再返回給客戶機。

DNS 優化:減少DNS的請求次數;進行DNS預獲取 。

減少DNS的請求次數————在項目中減少不同域名的http請求,儘量少的域名減少DNS的請求數

DNS預獲取————減少用戶的等待時間,提升用戶體驗 。

默認情況下瀏覽器會對頁面中和當前域名(正在瀏覽網頁的域名)不在同一個域的域名進行預獲取,並且緩存結果,這就是隱式的 DNS Prefetch。如果想對頁面中沒有出現的域進行預獲取,那麼就要使用顯示的 DNS Prefetch 了。

<meta http-equiv="x-dns-prefetch-control" content="on">
<link rel="dns-prefetch" href="//www.itechzero.com">
<link rel="dns-prefetch" href="//api.share.baidu.com">
<link rel="dns-prefetch" href="//bdimg.share.baidu.com">

</details>

<details>
<summary>5. CDN的作用和原理</summary>

  • CDN的作用

CDN 的全稱是 Content Delivery Network,即內容分發網絡。其基本思路是儘可能避開互聯網上有可能影響數據傳輸速度和穩定性的瓶頸和環節,使內容傳輸的更快、更穩定。

  • CDN 的原理

1、多域名加載資源

一般情況下,瀏覽器都會對單個域名下的併發請求數(文件加載)進行限制,通常最多有 4 個,那麼第 5 個加載項將會被阻塞,直到前面的某一個文件加載完畢。因爲 CDN 文件是存放在不同區域(不同 IP)的,所以對瀏覽器來說是可以同時加載頁面所需的所有文件(遠不止 4 個),從而提高頁面加載速度。

2、文件可能已經被加載過並保存有緩存

一些通用的 js 庫或者是 css 樣式庫,如 jQuery ,在網絡中的使用是非常普遍的。當一個用戶在瀏覽你的某一個網頁的時候,很有可能他已經通過你網站使用的 CDN 訪問過了其他的某一個網站,恰巧這個網站同樣也使用了 jQuery,那麼此時用戶瀏覽器已經緩存有該 jQuery 文件(同 IP 的同名文件如果有緩存,瀏覽器會直接使用緩存文件,不會再進行加載),所以就不會再加載一次了,從而間接的提高了網站的訪問速度。

3、分佈式的數據中心

假如你的站點佈置在北京,當一個香港或者更遠的用戶訪問你的站點的時候,他的數據請求勢必會很慢很慢。而 CDN 則會讓用戶從離他最近的節點去加載所需的文件,所以加載速度提升就是理所當然的了。
</details>

<details>
<summary>6. HTTP 請求報文和響應報文的具體組成,能理解常見請求頭的含義,有幾種請求方式,區別是什麼</summary>

參考文章:【HTTP 請求詳解】

HTTP協議的六種請求方法

  1. GET: 發送請求來獲得服務器上的資源,請求體中不會包含請求數據,請求數據放在協議頭中
  2. POST: 和 get 一樣很常見,向服務器提交資源讓服務器處理,比如提交表單、上傳文件等,可能導致建立新的資源或者對原有資源的修改。提交的資源放在請求體中。不支持快取。非冪等
  3. HEAD: 本質和 get 一樣,但是響應中沒有呈現數據,而是 http 的頭信息,主要用來檢查資源或超鏈接的有效性或是否可以可達、檢查網頁是否被串改或更新,獲取頭信息等,特別適用在有限的速度和帶寬下。
  4. PUT: 和 post 類似,html 表單不支持,發送資源與服務器,並存儲在服務器指定位置,要求客戶端事先知道該位置;比如 post 是在一個集合上(/province),而 put 是具體某一個資源上(/province/123)。所以 put 是安全的,無論請求多少次,都是在 123 上更改,而 post 可能請求幾次創建了幾次資源。冪等
  5. DELETE: 請求服務器刪除某資源。和 put 都具有破壞性,可能被防火牆攔截
  6. CONNECT: HTTP/1.1 協議中預留給能夠將連接改爲管道方式的代理服務器。就是把服務器作爲跳板,去訪問其他網頁然後把數據返回回來,連接成功後,就可以正常的 getpost 了。

7: OPTIONS: 獲取 http 服務器支持的 http 請求方法,允許客戶端查看服務器的性能,比如 ajax 跨域時的預檢等。
8: TRACE: 回顯服務器收到的請求,主要用於測試或診斷。一般禁用,防止被惡意攻擊或盜取信息。

</details>

<details>
<summary>7. HTTP 所有狀態碼的具體含義,看到異常狀態碼能快速定位問題</summary>

  • 1XX:信息狀態碼

    • 100 Continue 繼續,一般在發送post請求時,已發送了http header之後服務端將返回此信息,表示確認,之後發送具體參數信息
  • 2XX:成功狀態碼

    • 200 OK 正常返回信息
    • 201 Created 請求成功並且服務器創建了新的資源
    • 202 Accepted 服務器已接受請求,但尚未處理
  • 3XX:重定向

    • 301 Moved Permanently 請求的網頁已永久移動到新位置。
    • 302 Found 臨時性重定向。
    • 303 See Other 臨時性重定向,且總是使用 GET 請求新的 URI。
    • 304 Not Modified 自從上次請求後,請求的網頁未修改過。
  • 4XX:客戶端錯誤

    • 400 Bad Request 服務器無法理解請求的格式,客戶端不應當嘗試再次使用相同的內容發起請求。
    • 401 Unauthorized 請求未授權。
    • 403 Forbidden 禁止訪問。
    • 404 Not Found 找不到如何與 URI 相匹配的資源。
  • 5XX: 服務器錯誤

    • 500 Internal Server Error 最常見的服務器端錯誤。
    • 503 Service Unavailable 服務器端暫時無法處理請求(可能是過載或維護)。

</details>

<details>
<summary>8. HTTP1.1、HTTP2.0帶來的改變</summary>

緩存處理,在 HTTP1.0 中主要使用 header 裏的 If-Modified-Since,Expires 來做爲緩存判斷的標準,HTTP1.1 則引入了更多的緩存控制策略例如 Entity tag,If-Unmodified-Since, If-Match, If-None-Match 等更多可供選擇的緩存頭來控制緩存策略。

帶寬優化及網絡連接的使用HTTP1.0 中,存在一些浪費帶寬的現象,例如客戶端只是需要某個對象的一部分,而服務器卻將整個對象送過來了,並且不支持斷點續傳功能,HTTP1.1 則在請求頭引入了 range 頭域,它允許只請求資源的某個部分,即返回碼是 206(Partial Content),這樣就方便了開發者自由的選擇以便於充分利用帶寬和連接。

錯誤通知的管理,在 HTTP1.1 中新增了 24 個錯誤狀態響應碼,如 409(Conflict)表示請求的資源與資源的當前狀態發生衝突;410(Gone)表示服務器上的某個資源被永久性的刪除。

Host頭處理,在 HTTP1.0 中認爲每臺服務器都綁定一個唯一的 IP 地址,因此,請求消息中的 URL 並沒有傳遞主機名 (hostname)。但隨着虛擬主機技術的發展,在一臺物理服務器上可以存在多個虛擬主機(Multi-homed Web Servers),並且它們共享一個 IP 地址。HTTP1.1 的請求消息和響應消息都應支持 Host 頭域,且請求消息中如果沒有 Host 頭域會報告一個錯誤(400 Bad Request)

長連接HTTP 1.1 支持長連接(PersistentConnection)和請求的流水線(Pipelining)處理,在一個 TCP 連接上可以傳送多個 HTTP 請求和響應,減少了建立和關閉連接的消耗和延遲,在 HTTP1.1 中默認開啓 Connection: keep-alive,一定程度上彌補了 HTTP1.0 每次請求都要創建連接的缺點。

HTTP2.0和HTTP1.X相比的新特性

  • 新的二進制格式(Binary Format),HTTP1.x 的解析是基於文本。基於文本協議的格式解析存在天然缺陷,文本的表現形式有多樣性,要做到健壯性考慮的場景必然很多,二進制則不同,只認 01 的組合。基於這種考慮 HTTP2.0 的協議解析決定採用二進制格式,實現方便且健壯。
  • 多路複用(MultiPlexing),即連接共享,即每一個 request 都是是用作連接共享機制的。一個 request 對應一個 id,這樣一個連接上可以有多個 request,每個連接的 request 可以隨機的混雜在一起,接收方可以根據 requestidrequest 再歸屬到各自不同的服務端請求裏面。
  • header 壓縮,如上文中所言,對前面提到過 HTTP1.xheader 帶有大量信息,而且每次都要重複發送,HTTP2.0 使用 encoder 來減少需要傳輸的 header 大小,通訊雙方各自 cache 一份 header fields 表,既避免了重複 header 的傳輸,又減小了需要傳輸的大小。
  • 服務端推送(server push),HTTP2.0具有 server push 功能。

</details>

<details>
<summary>9. HTTPS 的加密原理,如何開啓 HTTPS,如何劫持 HTTPS 請求</summary>

參考文章:【一個故事講完 https】

</details>

設計模式

<details>
<summary>1. 熟練使用前端常用的設計模式編寫代碼,如單例模式、裝飾器模式、代理模式等</summary>

參考文章:【設計模式】

</details>

<details>
<summary>2. 發佈訂閱模式和觀察者模式的異同以及實際應用</summary>

觀察者模式和發佈訂閱模式最大的區別就是發佈訂閱模式有個事件調度中心。

// 觀察者模式
class Subject{
  constructor(){
    this.subs = [];
  }
  addSub(sub){
    this.subs.push(sub);
  }
  notify(){
    this.subs.forEach(sub=> {
      sub.update();
    });
  }
}
class Observer{
  update(){
    console.log('update');
  }
}
let subject = new Subject();
let ob = new Observer();
//目標添加觀察者了
subject.addSub(ob);
//目標發佈消息調用觀察者的更新方法了
subject.notify();   //update
// 發佈訂閱者模式
class PubSub {
    constructor() {
        this.subscribers = {}
    }
    subscribe(type, fn) {
        if (!Object.prototype.hasOwnProperty.call(this.subscribers, type)) {
          this.subscribers[type] = [];
        }
        
        this.subscribers[type].push(fn);
    }
    unsubscribe(type, fn) {
        let listeners = this.subscribers[type];
        if (!listeners || !listeners.length) return;
        this.subscribers[type] = listeners.filter(v => v !== fn);
    }
    publish(type, ...args) {
        let listeners = this.subscribers[type];
        if (!listeners || !listeners.length) return;
        listeners.forEach(fn => fn(...args));        
    }
}

let ob = new PubSub();
ob.subscribe('add', (val) => console.log(val));
ob.publish('add', 1);

</details>

四、數據結構和算法

據我瞭解的大部分前端對這部分知識有些欠缺,甚至牴觸,但是,如果突破更高的天花板,這部分知識是必不可少的,而且我親身經歷——非常有用!

JavaScript編碼能力

<details>
<summary>1. 多種方式實現數組去重、扁平化、對比優缺點</summary>

參考文章:【JS 數組去重方法整理】

【5種方式實現數組扁平化】
</details>

<details>
<summary>2. 多種方式實現深拷貝、對比優缺點</summary>

參考文章:【遞歸實現深拷貝】

</details>
<details>
<summary>3. 手寫函數柯里化工具函數、並理解其應用場景和優勢</summary>

  • [ ] 待補充

</details>

<details>
<summary>4.手寫防抖和節流工具函數、並理解其內部原理和應用場景</summary>

參考文章:【函數的防抖與節流】

</details>

<details>
<summary>5.實現一個 sleep 函數</summary>

function sleep(time) {
  return new Promise((resolve,reject) => setTimeout(resolve, time))
}
sleep(3000).then(() => {console.log('沉睡3000ms')})

</details>

手動實現前端輪子

<details>
<summary>1. 手動實現call、apply、bind</summary>

call

  1. 判斷當前 this 是否爲函數,防止 Function.prototype.myCall() 直接調用
  2. context 爲可選參數,如果不傳的話默認上下文爲 window
  3. context 創建一個 Symbol(保證不會重名)屬性,將當前函數賦值給這個屬性
  4. 處理參數,傳入第一個參數後的其餘參數
  5. 調用函數後即刪除該 Symbol 屬性
Function.prototype.myCall = function(context = window, ...args) {
    if (this === Function.prototype) {
        return undefined; // 用於防止 Function.prototype.myCall() 直接調用
    }
    context = context || window;
    const fn = Symbol();
    context[fn] = this;
    const result = context[fn](...args);
    delete context[fn];
    return result;
};

apply

apply 實現類似 call,參數爲數組

Function.prototype.myApply = function(context = window, args) {
    if (this === Function.prototype) {
        return undefined; // 用於防止 Function.prototype.myCall() 直接調用
    }
    const fn = Symbol();
    context[fn] = this;
    let result;
    if (Array.isArray(args)) {
        result = context[fn](...args);
    } else {
        result = context[fn]();
    }
    delete context[fn];
    return result;
};

bind

因爲 bind() 返回一個方法需手動執行,因此利用閉包實現。

Function.prototype.myBind = function(context, ...args1) {
    if (this === Function.prototype) {
        throw new TypeError('Error');
    }
    const _this = this;
    return function F(...args2) {
        // 判斷是否用於構造函數
        if (this instanceof F) {
            return new _this(...args1, ...args2);
        }
        return _this.apply(context, args1.concat(args2));
    };
};

</details>

<details>
<summary>2.手動實現符合 Promise/A+ 規範的 Promise</summary>

參考文章:【手動實現 promise】
</details>

<details>
<summary>3. 手寫一個 EventEmitter 實現事件發佈、訂閱</summary>

    function EventEmitter() {
      this._events = Object.create(null);
    }

    // 向事件隊列添加事件
    // prepend爲true表示向事件隊列頭部添加事件
    EventEmitter.prototype.addListener = function (type, listener, prepend) {
      if (!this._events) {
        this._events = Object.create(null);
      }
      if (this._events[type]) {
        if (prepend) {
          this._events[type].unshift(listener);
        } else {
          this._events[type].push(listener);
        }
      } else {
        this._events[type] = [listener];
      }
    };

    // 移除某個事件
    EventEmitter.prototype.removeListener = function (type, listener) {
      if (Array.isArray(this._events[type])) {
        if (!listener) {
          delete this._events[type]
        } else {
          this._events[type] = this._events[type].filter(e => e !== listener && e.origin !== listener)
        }
      }
    };

    // 向事件隊列添加事件,只執行一次
    EventEmitter.prototype.once = function (type, listener) {
      const only = (...args) => {
        listener.apply(this, args);
        this.removeListener(type, listener);
      }
      only.origin = listener;
      this.addListener(type, only);
    };

    // 執行某類事件
    EventEmitter.prototype.emit = function (type, ...args) {
      if (Array.isArray(this._events[type])) {
        this._events[type].forEach(fn => {
          fn.apply(this, args);
        });
      }
    };
    // 測試一下
    var emitter = new EventEmitter();

    var onceListener = function (args) {
      console.log('我只能被執行一次', args, this);
    }

    var listener = function (args) {
      console.log('我是一個listener', args, this);
    }

    emitter.once('click', onceListener);
    emitter.addListener('click', listener);

    emitter.emit('click', '參數');
    emitter.emit('click');

    emitter.removeListener('click', listener);
    emitter.emit('click');

</details>

<details>
<summary>4.可以說出兩種實現雙向綁定的方案、可以手動實現</summary>

參考文章:【Vue 雙向數據綁定原理】
</details>

<details>
<summary>5.手寫JSON.stringify、JSON.parse</summary>

let Myjson = {
      parse: function(jsonStr) {
          return eval('(' + jsonStr + ')');
      },
      stringify: function(jsonObj) {
          var result = '',
              curVal;
          if (jsonObj === null) {
              return String(jsonObj);
          }
          switch (typeof jsonObj) {
              case 'number':
              case 'boolean':
                  return String(jsonObj);
              case 'string':
                  return '"' + jsonObj + '"';
              case 'undefined':
              case 'function':
                  return undefined;
          }

          switch (Object.prototype.toString.call(jsonObj)) {
              case '[object Array]':
                  result += '[';
                  for (var i = 0, len = jsonObj.length; i < len; i++) {
                      curVal = JSON.stringify(jsonObj[i]);
                      result += (curVal === undefined ? null : curVal) + ",";
                  }
                  if (result !== '[') {
                      result = result.slice(0, -1);
                  }
                  result += ']';
                  return result;
              case '[object Date]':
                  return '"' + (jsonObj.toJSON ? jsonObj.toJSON() : jsonObj.toString()) + '"';
              case '[object RegExp]':
                  return "{}";
              case '[object Object]':
                  result += '{';
                  for (i in jsonObj) {
                      if (jsonObj.hasOwnProperty(i)) {
                          curVal = JSON.stringify(jsonObj[i]);
                          if (curVal !== undefined) {
                              result += '"' + i + '":' + curVal + ',';
                          }
                      }
                  }
                  if (result !== '{') {
                      result = result.slice(0, -1);
                  }
                  result += '}';
                  return result;

              case '[object String]':
                  return '"' + jsonObj.toString() + '"';
              case '[object Number]':
              case '[object Boolean]':
                  return jsonObj.toString();
          }
      }
  };

</details>

<details>
<summary>6. 手寫懶加載效果</summary>

參考文章:【圖片懶加載】
</details>

瀏覽器原理

<details>
<summary>1. 可詳細描述瀏覽器從輸入URL到頁面展現的詳細過程</summary>

參考文章:【輸入URL至頁面渲染】
</details>

<details>
<summary>2. 瀏覽器的垃圾回收機制,如何避免內存泄漏</summary>

參考文章:【瀏覽器內存回收機制】

參考文章:【內存泄漏與避免】
</details>

資源推薦

語言基礎

計算機基礎

數據結構和算法

運行環境

框架和類庫

前端工程

項目和業務

學習提升

博客推薦

技術之外

此文章 Markdown 源文件地址:https://github.com/zxpsuper/blog...

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