Front End Interview

概述

  1. 概念題 => 是什么 + 怎么做 + 解決了什么問題 + 優(yōu)點 + 缺點 + 怎么解決缺點

HTML

如何理解 HTML 中的語義化標簽

  1. 語義化標簽是一種寫 HTML 標簽的方法論
  2. 實現(xiàn)方法是遇到標題就用 h1 到 h6昌执,遇到段落用 p要门,遇到文檔用 article行贪,主要內(nèi)容用 main诺舔,邊欄用 aside,導航用 nav
  3. 它主要是明確了 HTML 的書寫規(guī)范
  4. 優(yōu)點在于 1. 適合搜索引擎檢索 2. 適合人類閱讀,利于團隊維護

HTML5 有哪些新標簽

文章相關:header恕酸、main、footer胯陋、nav蕊温、section袱箱、article
多媒體相關:video、audio义矛、svg发笔、canvas

Canvas 和 SVG 的區(qū)別是什么?

  1. Canvas 主要是用筆刷來繪制 2D 圖形的
  2. SVG 主要是用標簽來繪制不規(guī)則矢量圖的
  3. 相同點:都是主要用來畫 2D 圖形的
  4. 不同點
    1. SVG 畫的是矢量圖凉翻,Canvas 畫的是位圖
    2. SVG 節(jié)點多時渲染慢了讨,Canvas 性能更好一點,但寫起來更復雜
    3. SVG 支持分層和事件制轰,Canvas 不支持前计,但是可以用庫實現(xiàn)

CSS

BFC 是什么

BFC 是 Block Formatting Context,是塊級格式化上下文垃杖。以下可以觸發(fā) BFC

  1. 浮動元素(float 值不為 none)
  2. 絕對定位元素(position 值為 absolute 或 fixed)
  3. inline-block 行內(nèi)塊元素
  4. overflow 值不為 visible男杈、clip 的塊元素
  5. 彈性元素(display 值為 flex 或 inline-flex 元素的直接子元素)

BFC 可以解決 1. 清除浮動 2. 防止 margin 合并 的問題
但是它有相應的副作用,可以使用最新的 display: flow-root 來觸發(fā) BFC调俘,該屬性專門用來觸發(fā) BFC

如何實現(xiàn)垂直居中

  1. flex
  2. position + transform

CSS 選擇器優(yōu)先級如何確定

  1. 選擇器越具體伶棒,其優(yōu)先級越高
  2. 相同優(yōu)先級,出現(xiàn)在后面的彩库,覆蓋前面的
  3. 屬性后面加 !important 的優(yōu)先級最高肤无,但是要少用

如何清除浮動

.clearfix:after {
    content: '';
    display: block;
    clear: both;
}

兩種盒模型區(qū)別

  1. content-box => width 和 height 只包含內(nèi)容的寬和高,不包括邊框和內(nèi)邊距骇钦。case:{width: 350px, border: 10px solid red;} 實際寬度為 370
  2. border-box => width 和 height 包含內(nèi)容宛渐、內(nèi)邊距和邊框。case:{width: 350px, border: 10px solid red;} 實際寬度為 350

JS

JS 的數(shù)據(jù)類型

基本數(shù)據(jù)類型:number/boolean/string/null/undefined/Symbol/BigInt(任意精度的整數(shù))
引用數(shù)據(jù)類型:Object

判斷數(shù)據(jù)類型

  1. typeof => 返回一個字符串司忱,表示操作數(shù)的類型
    1. typeof null === 'object'
    2. typeof <function> === 'function'
  2. instanceof => 在原型鏈中查找是否是其實例 => object instanceof constructor
  3. 判斷是否是數(shù)組
    1. arr instanceof Array
    2. arr.constructor === Array
    3. Array.isArray(arr)
    4. Object.prototype.toString.call(arr) === '[object Array]'

原型鏈是什么皇忿?

  1. case:const a = {}畴蹭,此時 a.proto == Object.prototype坦仍,即 a 的原型是 Object.prototype
  2. case:我們有一個數(shù)組對象,const a = []叨襟,此時 a.proto == Array.prototype繁扎,此時 a 的原型是 Array.prototype,此時 Array.prototype.proto == Object.prototype糊闽,此時:
    1. a 的原型是 Array.prototype
    2. a 的原型的原型是 Object.prototype
    3. 于是形成了一條原型鏈
  3. 可以通過 const x = Object.create(原型) 或者 const x = new 構(gòu)造函數(shù)() 的方式改變 x 的原型
    1. const x = Object.create(原型) => x.proto == 原型
    2. const x = new 構(gòu)造函數(shù)() => x.proto == 構(gòu)造函數(shù).prototype
  4. 原型鏈可以實現(xiàn)繼承梳玫,以上面的數(shù)組為例:a ===> Array.prototype ===> Object.prototype
    1. a 是 Array 的實例,a 擁有 Array.prototype 里的屬性
    2. Array 繼承了 Object
    3. a 是 Object 的間接實例右犹,a 也就擁有 Object.prototype 里的屬性
    4. a 即擁有了 Array.prototype 的屬性提澎,也擁有了 Object.prototype 的屬性
  5. 原型鏈的優(yōu)點在于:簡單優(yōu)雅
  6. 但是不支持私有屬性,ES6新增加的 class 可以支持私有屬性

代碼中的 this 是什么念链?

  1. 將所有的函數(shù)調(diào)用轉(zhuǎn)化為 call => this 就是 call 的第一個參數(shù)
  2. func(p1, p2) => func.call(undefined, p1, p2) => 如果 context 是 null 或 undefined盼忌,window 是默認的 context(嚴格模式下默認是 undefined)
  3. obj.child.method(p1, p2) => obj.child.method.call(obj.child, p1, p2)

JS 的 new 做的什么积糯?

function Person(name) {
  this.name = name;
}

const ming = new Person("ming");

const ming = (function (name) {
  // 1. var temp = {}; => 創(chuàng)建臨時對象
  // 2. this = temp; => 指定 this = 臨時對象
  this.name = name;
  // 3. Person.prototype = {...Person.prototype, constructor: Person} => 執(zhí)行構(gòu)造函數(shù)
  // 4. this.__proto__ == Person.prototype => 綁定原型
  // return this; => 返回臨時對象
})("ming");

JS 的立即執(zhí)行函數(shù)是什么?

  1. 聲明一個匿名函數(shù)谦纱,然后立即執(zhí)行它看成,這種做法就是立即執(zhí)行函數(shù)
  2. 例如: 每一行代碼都是一個立即執(zhí)行函數(shù)
    1. (function() {} ())
    2. (function() {})()
    3. !function() {}()
    4. +function() {}()
    5. -function() {}()
    6. ~function() {}()
  3. 在 ES6 之前只能通過立即執(zhí)行函數(shù)來創(chuàng)建局部作用域
  4. 其優(yōu)點在于兼容性好
  5. 目前可以使用 ES6 的 block + let 代替
    {
       let a = '局部變量';
       console.log(a); // 局部變量
    }
    console.log(a); // Uncaught ReferenceError: a is not defined
    

JS 的閉包是什么?

  1. 閉包是 JS 的一種語法特性跨嘉,閉包 = 函數(shù) + 自由變量川慌。對于一個函數(shù)來說,變量分為:全局變量祠乃、本地變量梦重、自由變量
  2. case:閉包就是 count + add 組成的整體
    const add2 = (function() {
       var count = 0;
       return function add() {
         count++;
       }
    })()
    
    // 此時 add2 就是 add
    add2(); 
    
    // 相當于
    add();
    
    // 相當于
    count++;
    
  3. 以上就是一個完整的閉包的應用
  4. 閉包解決了
    1. 避免污染全局環(huán)境 => 因為使用了局部變量
    2. 提供對局部變量的間接訪問 => 只能 count++,不能 count--
    3. 維持變量跳纳,使其不被垃圾回收
  5. 其優(yōu)點是:簡單好用
  6. 但是閉包使用不當可能造成內(nèi)存泄漏忍饰。case:
    function test() {
       var x = {name: 'x'};
       var y = {name: 'y', content: '這里很長很長,占用了很多很多字節(jié)'}
       return function fn() {
         return x;
       }
    }
    
    const myFn = test(); // myFn 就是 fn 了
    const myX = myFn(); // myX 就是 x 了
    
  7. 對于正常的瀏覽器來說寺庄,y會在一段時間內(nèi)自動消失艾蓝,被垃圾回收器回收,但是舊版本的 IE 瀏覽器不會回收斗塘,這是 IE 瀏覽器的問題

JS 如何實現(xiàn)類

  1. 使用原型
function Dog(name) {
  this.name = name;
  this.legsNum = 4;
}

Dog.prototype.kind = 'dog';
Dog.prototype.run = function () {
  console.log("I am running with " + this.legsNum + " legs.")
}
Dog.prototype.say = function () {
  console.log("Wang Wang, I am " + this.name);
}

const dog = new Dog("ming");
dog.say();
  1. 使用類
class Dog {
  kind = 'dog';

  constructor(name) {
    this.name = name;
    this.legsNum = 4;
  }

  run() {
    console.log("I am running with " + this.legsNum + " legs.")
  }

  say() {
    console.log("Wang Wang, I am " + this.name);
  }
}

const dog = new Dog('ming');
dog.say();

JS 實現(xiàn)繼承

  1. 使用原型鏈
// dog => Dog => Animal
function Animal(legsNum) {
  this.legsNum = legsNum;
}

Animal.prototype.kind = 'animal';
Animal.prototype.run = function () {
  console.log("I am running with " + this.legsNum + " legs.");
}

function Dog(name) {
  this.name = name;
  Animal.call(this, 4); // 繼承屬性
}

// Dog.prototype.__proto__ == Animal.prototype
const temp = function () {}
temp.prototype = Animal.prototype;
Dog.prototype = new temp();

Dog.prototype.kind = 'dog';
Dog.prototype.say = function () {
  console.log("Wang Wang, I am " + this.name);
}

const dog = new Dog("ming"); // Dog 函數(shù)就是一個類
console.log(dog);
  1. 使用類
class Animal {
  kind = 'animal';

  constructor(legsNum) {
    this.legsNum = legsNum;
  }

  run() {
    console.log("I am running with " + this.legsNum + " legs.");
  }
}

class Dog extends Animal {
  kind = 'dog';

  constructor(name) {
    super(4);
    this.name = name;
  }

  say() {
    console.log("Wang Wang, I am " + this.name);
  }
}

const dog = new Dog('ming');
console.log(dog);

JS 手寫節(jié)流 & 防抖

  1. 節(jié)流 throttle => 技能冷卻中 => 場景
    1. Select 去服務端動態(tài)搜索
    2. 按鈕用戶點擊過快赢织,發(fā)送多次請求
function throttle(time, callback) {
  let flag = true;
  return (...args) => {
    if (flag) {
      flag = false;
      callback(args);
      setTimeout(() => {
        flag = true;
      }, time);
    }
  }
}

const fn = throttle(2000, () => {console.log("Hello!")});
fn();
fn();
setTimeout(fn, 3000);
  1. 防抖 debounce => 回城被打斷 => 場景
    1. 滾動事件
function debounce(time, callback) {
  let timer;
  return (...args) => {
    timer && clearTimeout(timer);
    timer = setTimeout(() => {
      callback(args);
    }, time);
  }
}

const fn = debounce(2000, () => console.log("Hello!"));
fn();
setTimeout(fn, 1000);

JS 手寫發(fā)布訂閱

const eventBus = {
  bus: {},
  on(eventName, callback) {
    if (!this.bus[eventName]) {
      this.bus[eventName] = [callback];
    } else {
      this.bus[eventName].push(callback);
    }
  },
  emit(eventName, data) {
    if (!this.bus[eventName]) {
      throw new Error("Please check eventName " + eventName);
    }
    this.bus[eventName].forEach(callback => callback.call(null, data));
  },
  off(eventName, callback) {
    if (!this.bus[eventName]) {
      throw new Error("Please check eventName " + eventName);
    }
    const index = this.bus[eventName].indexOf(callback);
    if (index < 0) {
      return;
    }
    this.bus[eventName].splice(index, 1);
  }
}

eventBus.on('click', console.log)
eventBus.on('click', console.error)
setTimeout(() => {
  eventBus.emit('click', 'Hello!');
}, 3000)

JS 手寫 AJAX

const ajax = (method, url, data, success, fail) => {
  const request = new XMLHttpRequest();
  request.open(method, url);
  request.onreadystatechange = function () {
    if (request.readyState === 4) {
      if (request.status >= 200 && request.status < 300 || request.status === 304) {
        success(request);
      } else {
        fail(request);
      }
    }
  }
  if (method === "post") {
    request.send(data);
  } else {
    request.send();
  }
}

JS 手寫簡化版 Promise

class Promise2 {
  #status = 'pending';

  constructor(fn) {
    this.queue = [];

    const resolve = (data) => {
      this.#status = 'fulfilled';
      const f1f2 = this.queue.shift();
      if (!f1f2 || !f1f2[0]) return;
      const x = f1f2[0].call(undefined, data);
      if (x instanceof Promise2) {
        x.then(data => resolve(data), reason => reject(reason));
      } else {
        resolve(x);
      }
    }

    const reject = (reason) => {
      this.#status = 'rejected';
      const f1f2 = this.queue.shift();
      if (!f1f2 || !f1f2[1]) return;
      const x = f1f2[1].call(undefined, reason);
      if (x instanceof Promise2) {
        x.then(data => resolve(data), reason => reject(reason));
      } else {
        resolve(x);
      }
    }

    fn.call(undefined, resolve, reject);
  }

  then(f1, f2) {
    this.queue.push([f1, f2]);
  }
}

const p = new Promise2((resolve, reject) => {
  setTimeout(() => {
    reject('Error!');
  }, 3000);
});

p.then((data) => console.log(data), error => console.error(error));

JS 手寫 Promise.all

  1. 要在 Promise 上寫而不是在原型上寫
  2. Promise.all 參數(shù)(Promise 數(shù)組)和返回值(新 Promise 對象)
  3. 用數(shù)組記錄結(jié)果
  4. 只要有一個 reject 就整體 reject
Promise.myAll = function (list) {
  const results = [];
  let count = 0;
  return new Promise((resolve, reject) => {
    list.map((promise, index) => {
      promise.then((result) => {
        results[index] = result;
        count++;
        if (count >= list.length) {
          resolve(results);
        }
      }, reason => reject(reason));
    });
  });
}

JS 手寫深拷貝

JSON

const copy = JSON.parse(JSON.stringify(a));

缺點:

  1. 不支持 Date、正則馍盟、undefined于置、函數(shù)等數(shù)據(jù)
  2. 不支持引用,即環(huán)狀結(jié)構(gòu)

遞歸贞岭。要點:

  1. 判斷類型
  2. 檢查環(huán)
  3. 不拷貝原型上的屬性
const deepCopy = (a, cache) => {
  if (!cache) {
    cache = new Map();
  }

  // 不考慮跨 iframe
  if (a instanceof Object) {
    if (cache.get(a)) {
      return cache.get(a);
    }
    let result = null;
    if (a instanceof Function) {
      // 有 prototype 就是普通函數(shù)
      if (a.prototype) {
        result = function () {
          return a.apply(this, arguments);
        }
      } else {
        result = (...args) => {
          return a.call(undefined, ...args);
        }
      }
    } else if (a instanceof Array) {
      result = [];
    } else if (a instanceof Date) {
      result = new Date(a - 0);
    } else if (a instanceof RegExp) {
      result = new RegExp(a.source, a.flags);
    } else {
      result = {};
    }
    cache.set(a, result);
    for (let key in a) {
      if (a.hasOwnProperty(key)) {
        result[key] = deepCopy(a[key], cache);
      }
    }
    return result;
  }
  return a;
}

const a = {
  number: 1, bool: false, str: 'hi', empty1: undefined, empty2: null,
  array: [
    {name: 'frank', age: 18},
    {name: 'jacky', age: 19}
  ],
  date: new Date(2000, 0, 1, 20, 30, 0),
  regex: /\.(j|t)sx/i,
  obj: {name: 'frank', age: 18},
  f1: (a, b) => a + b,
  f2: function (a, b) { return a + b }
}
a.self = a;

const b = deepCopy(a);
console.log(b.self === b); // true
b.self = 'hi'
console.log(a.self !== 'hi'); //true

JS 手寫數(shù)組去重

const unique = (nums) => {
  const map = new Map();
  for (let i = 0; i < nums.length; i++) {
    if (nums[i] === undefined || map.has(nums[i])) {
      continue;
    }
    map.set(nums[i], true);
  }
  return [...map.keys()];
}

DOM

DOM 事件模型

  1. 先經(jīng)歷從上到下的捕獲階段八毯,再經(jīng)歷從下到上的冒泡階段
  2. addEventListener("click", fn, options, useCapture)
    1. options 中有一個 capture 參數(shù),true 表示捕獲階段瞄桨,false 表示冒泡階段
    2. useCapture true 表示捕獲階段话速,false 表示冒泡階段
  3. 可以使用 event.stopPropagation() 來阻止捕獲或冒泡

手寫事件委托

ul.addEventListener('click', (e) => {
  if (e.target.tagName.toLowerCase() === 'li') {
    // do something
  }
});
  1. 如果點擊 li 里面的 span,就沒有辦法觸發(fā)事件
  2. 點擊元素之后芯侥,遞歸遍歷點擊元素的祖先元素直至遇到 li 或者 ul
const delegate = (element, eventType, selector, fn) => {
  element.addEventListener(eventType, e => {
    let ele = el.target;
    while (!ele.matches(selector)) {
      if (ele === element) {
        return;
      }
      ele = ele.parentNode;
    }
    fn.call(ele, e);
  });
  return element;
}

delegate(ul, 'click', 'li', (e) => console.log(e));
  1. 事件委托優(yōu)點
    1. 節(jié)省監(jiān)聽器
    2. 實現(xiàn)動態(tài)監(jiān)聽
  2. 事件委托缺點 => 調(diào)試比較復雜泊交,不容易確定監(jiān)聽者

手寫可拖曳 div

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Drag</title>
    <style>
        .drag {
            border: 1px solid red;
            position: absolute;
            top: 0;
            left: 0;
            width: 100px;
            height: 100px;
        }
    </style>
</head>
<body>
<div class="drag"></div>
<script>
    let dragging = false;
    let position = null;
    const dragEle = document.querySelector('.drag');
    dragEle.addEventListener("mousedown", e => {
        dragging = true;
        position = [e.clientX, e.clientY];
    });

    document.addEventListener("mousemove", e => {
        if (!dragging) return;
        const x = e.clientX;
        const y = e.clientY;
        const moveX = x - position[0];
        const moveY = y - position[1];
        const left = parseInt(dragEle.style.left || 0);
        const top = parseInt(dragEle.style.top || 0);
        dragEle.style.left = left + moveX + 'px';
        dragEle.style.top = top + moveY + 'px';
        position = [x, y];
    });

    document.addEventListener('mouseup', e => {
        dragging = false;
    })
</script>
</body>
</html>

HTTP

HTTP status code

  • 200 OK
  • 201 Created
  • 204 No Content
  • 301 Move Permanently
  • 302 Found
  • 304 Not Modify
  • 400 Bad Request
  • 401 Unauthorized
  • 403 Forbidden
  • 404 Not Found
  • 405 Method Not Allowed
  • 409 Conflict
  • 410 Gone
  • 414 URI Too Long
  • 415 Unsupported Media Type
  • 500 Internal Server Error
  • 502 Bad Gateway
  • 504 Gateway Timeout

GET 和 POST 的區(qū)別

  1. 根據(jù)技術文檔規(guī)格,GET 和 POST 最大區(qū)別就是語義柱查,一個讀一個寫
  2. 實踐上會有很多區(qū)別廓俭,如:
  3. 由于 GET 是讀,POST 是寫唉工。所以 GET 是冪等的研乒,POST 是不冪等的
  4. 由于 GET 是讀,POST 是寫淋硝。所以 GET 結(jié)果會被緩存雹熬,POST 結(jié)果不會被緩存
  5. 由于 GET 是讀错维,POST 是寫。所以 GET 打開的頁面刷新是無害的橄唬,POST 打開的頁面刷新需要確認
  6. 通常情況下赋焕,GET 請求參數(shù)放置在 URL 里,POST 請求參數(shù)放在 body 里
  7. GET 比 POST 更不安全仰楚,因為參數(shù)直接暴露在 URL 上隆判,所以不能用來傳遞敏感信息
  8. GET 請求參數(shù)放在 URL 里是有長度限制的(瀏覽器限制的,414 URI to long)僧界,而 POST 放在 body 里沒有長度限制(長度其實可是配置)
  9. GET 產(chǎn)生一個 TCP 數(shù)據(jù)包侨嘀,POST 產(chǎn)生兩個或以上 TCP 數(shù)據(jù)包

簡單請求 vs 復雜請求

  1. 簡單請求不會觸發(fā) CORS 預檢請求
  2. 以下條件是簡單請求:
    1. method => GET | POST
    2. header => 需要關注 Content-Type => text/plain | multipart/form-data | application/x-www-form-urlencoded
  3. 復雜請求會觸發(fā) CORS 預檢請求
  4. 預檢請求 => 首先使用 OPTION 方法發(fā)起一個預檢請求到服務器,已獲知服務器是否允許該實際請求

Cookie

  1. Cookie 是服務器發(fā)送到用戶瀏覽器并保存在本地的一小塊數(shù)據(jù)捂襟,它會在瀏覽器下次向同一服務器再發(fā)起請求時被攜帶并發(fā)送到服務器上
  2. 通過 Set-Cookie 設置咬腕,是一個 key=value 結(jié)構(gòu)
  3. Expires | Max-Age => 指明過期時間,只與客戶端有關
  4. HttpOnly => 保證 Cookie 不會被腳本訪問 => JS document.cookie API 無法訪問帶有 HttpOnly 屬性的 Cookie
  5. Domain | Path => 允許 Cookie 應該發(fā)送給哪些 URL

HTTP 緩存有哪些方案葬荷?

  1. HTTP 緩存分為強緩存(緩存)弱緩存(內(nèi)容協(xié)商)
  2. HTTP 1.1 時代
    1. 強緩存(緩存)
      • 在 response header 中添加 Cache-Control: max-age = 3600涨共,瀏覽器會自動緩存一個小時,如果在此時間內(nèi)宠漩,再次訪問相同的 url(path + query)举反,直接不發(fā)送這個請求
      • 在 response header 中添加 Etag:ABC,代表該文件的特征值
    2. 弱緩存(內(nèi)容協(xié)商)
      • 強緩存過期之后扒吁,該緩存是否可以繼續(xù)使用 => request header 中添加 if-None-Match: ABC => 瀏覽器向服務器發(fā)送的一個詢問
      • 服務器返回相應的狀態(tài)碼:304(Not Modified火鼻,繼續(xù)使用緩存的內(nèi)容) 或 200(使用新的文件,其中 response header 中包含了 Cache-Control 和 Etag)
  3. HTTP 1.0 時代
    1. 強緩存(緩存)
      • Expires => 以電腦本地時間為準
      • Last-Modified => 一個文件1s內(nèi)更改多次雕崩,無法區(qū)分是否是最新的
    2. 弱緩存(內(nèi)容協(xié)商)
      • if-Modified-Since
      • 狀態(tài)碼:304 或 200

HTTP 和 HTTPS 的區(qū)別

  1. HTTPS = HTTP + SSL/TLS(安全層)
  2. HTTP 是明文傳輸?shù)目鳎话踩卜TTPS 是加密傳輸?shù)乃律浅0踩?/li>
  3. HTTP 使用 80 端口绑雄。HTTPS 使用 443 端口
  4. HTTP 較快晚树。HTTPS 較慢
  5. HTTP 不需要證書。HTTPS 需要證書

HTTP/1.1 和 HTTP/2 的區(qū)別有哪些?

  1. HTTP/2 使用二進制傳輸传泊,并且將 head 和 body 分成來傳輸。HTTP/1.1 是字符串傳輸
  2. HTTP/2 支持多路復用,一個 TCP 連接可以發(fā)送多個請求趁窃。HTTP/1.1 不支持,一個請求建立一個 TCP 連接急前。多路復用就是一個 TCP 連接從單車道變成了幾百個雙向通行的車道
  3. HTTP/2 可以壓縮 head醒陆。HTTP/1.1 不可以
  4. HTTP/2 支持服務器推送。HTTP/1.1 不支持

TCP 三次握手和四次揮手

TCP 三次握手和四次揮手
  • ACK => acknowledge => 接受
  • RCVD => received => 收到
  • SYN => synchronize => 同步
  • seq => sequence => 順序
  • EATABLISHED => established => 已建立
  1. 三次握手
    1. 瀏覽器向服務器發(fā)送 TCP 數(shù)據(jù) => SYN(seq = x)
    2. 服務器向瀏覽器發(fā)送 TCP 數(shù)據(jù) => SYN(seq = y), ACK = x + 1
    3. 瀏覽器向服務器發(fā)送 TCP 數(shù)據(jù) => ACK = y + 1
  2. 四次揮手
    1. 瀏覽器向服務器發(fā)送 TCP 數(shù)據(jù) => FIN(seq = x + 2), ACK = y + 1
    2. 服務器向瀏覽器發(fā)送 TCP 數(shù)據(jù) => ACK = x + 3
    3. 服務器向瀏覽器發(fā)送 TCP 數(shù)據(jù) => FIN(seq = y + 1)
    4. 瀏覽器向服務器發(fā)送 TCP 數(shù)據(jù) => ACK = y + 2

同源策略和跨域

  1. 同源指的是 protocol + host + port 相同便是同源的
  2. 同源策略用于控制不同源之間的交互裆针。跨源寫跨源資源嵌入一般是允許的刨摩,但是跨源讀操作一般是不允許的寺晌。
  3. 只要在瀏覽器里打開頁面,默認遵守同源策略
  4. 保證了用戶的隱私安全和數(shù)據(jù)安全
  5. 很多時候前端需要訪問另一個域名的后端接口澡刹,此時瀏覽器會將響應屏蔽呻征,并報錯 CORS
  6. 解決跨域 => 通常需要在 response header 中添加以下即可,此時瀏覽器將不會屏蔽響應
    Access-Control-Allow-Origin: <前端訪問域名>
    Access-Control-Allow-Method: POST, OPTIONS, GET, PUT
    Access-Control-Allow-header: Content-Type
    
  7. 使用 Node.js 或者 NGINX 代理 => 前端 -> NGINX/Node.js -> 另一個域名的服務端

Session罢浇、Cookie陆赋、LocalStorage、SessionStorage 的區(qū)別

  1. Session => 會話嚷闭,用戶信息 => 存儲在服務器的文件中攒岛,如 MySQL 或者 Redis
  2. Cookie => 保存了用戶憑證 => 存儲在瀏覽器文件中,在請求的時候會發(fā)送到服務端胞锰,大小 4k 左右
  3. LocalStorage vs SessionStorage => 存儲
    1. LocalStorage 如果不手動清除灾锯,會一直存在。SessionStorage 會話關閉就清除

TypeScript

TS 和 JS 的區(qū)別是什么嗅榕?有什么優(yōu)勢顺饮?

  1. 語法層面 => TS = JS + Type。TS 就是 JS 的超集
  2. 執(zhí)行環(huán)境層面 => 瀏覽器凌那、NodeJS 可以直接執(zhí)行 JS领突,但不能直接執(zhí)行 TS
  3. 編譯層面 => TS 有編譯階段。JS 沒有編譯階段
  4. TS 類型更安全案怯,IDE 可以進行提示

any君旦、unknown嘲碱、never 的區(qū)別是什么金砍?

  1. any 和 unknown 都是頂級類型(top type),任何類型的值都可以賦值給頂級類型變量
    let foo: any = 123; // 不報錯
    let bar: unknown = 123; // 不報錯
    
  2. 但是 unknown 比 any 類型檢查更嚴格麦锯,any 什么檢查都不做恕稠,unknown 要求先收窄類型
    const value: unknown = "hello world";
    const str: string = value; // 報錯:Type 'unknown' is not assignable to type 'string'.(2322)
    const str1: string = value as string; // 不報錯
    
  3. 如果改成 any,基本在哪都不會報錯扶欣。所以能用 unknown 就優(yōu)先使用 unknown鹅巍,類型更安全一點
  4. never 是底類型,表示不應該出現(xiàn)的類型
    interface Foo {
       type: 'foo'
    }
    interface Bar {
       type: 'bar'
    }
    
    type All = Foo | Bar;
    
    function handleValue(val: All) {
         switch (val.type) {
             case 'foo':
                 // 這里的 val 被收窄為 Foo
                 break;
             case 'bar':
                 // 這里的 val 被收窄為 Bar 
                 break;
             default:
                 // val 在這里是 never
                 const check: never = val;
                 break;
         }
    }
    
  5. 在 default 里把被收窄為 never 的 val 賦值給了一個顯示聲明為 never 的變量料祠,如果一切邏輯正確骆捧,那么這里可以編譯通過
  6. 某一天更改了 All 的類型 => type All = Foo | Bar | Baz
  7. 此時如果沒有修改 handleValue,此時 default 會被收窄為 Baz髓绽,無法賦值給 never敛苇,此時會產(chǎn)生一個編譯錯誤
  8. 通過這個方法,可以確保 handleValue 總是窮盡(exhaust)了所有 All 的可能類型

type 和 interface 的區(qū)別是什么顺呕?

  1. 組合方式 => interface 使用 extends 來實現(xiàn)繼承枫攀。type 使用 & 來實現(xiàn)聯(lián)合類型
  2. 擴展方式 => interface 可以重復聲明用來擴展(merge)括饶。type 一個類型只能聲明一次
    interface Foo {
       title: string
    }
    interface Foo {
       content: string
    }
    
    type Bar = {
       title: string
    }
    
    // Error: Duplicate identifier 'Bar'
    type Bar = {
       content: string
    }
    
  3. 范圍不同 => type 適用于基本類型。interface 被用于描述對象(declare the shapes of objects)
  4. 命名方式 => interface 會創(chuàng)建新的類型名来涨。type 只是創(chuàng)建類型別名图焰,并沒有新創(chuàng)建類型

瀏覽器

單頁面應用中實現(xiàn)前端路由有哪些方式

  1. hash 模式 和 history 模式
  2. hash 模式 =>
    1. 通過監(jiān)聽 URL 中 hash 部分的變化(hashchange),從而做出對應的渲染邏輯
    2. URL 中帶有 #
    3. 前端即可完成
    4. 請求的時候 # 后面的內(nèi)容不會包含在 HTTP 請求中蹦掐,所以改變 hash 不會重新加載頁面
  3. history 模式 => HTML5 history 全局對象 => go/forward/back/pushState/replaceState
    1. 使用 pushState 實現(xiàn)
    2. 需要后端配合楞泼,將所有路徑都指向首頁
  4. 調(diào)用 pushState()window.location = '#foo' 基本上一樣,都會創(chuàng)建一個新的歷史記錄
pushState(state, title, url) window.location = '#foo'
URL 新的 URL 必須是同源的 只有在設置錨的時候才使用當前 URL
URL 可能會改變頁面的 URL笤闯,
輸入相同的 URL 時不會改變 URL堕阔,
但是會創(chuàng)建新的歷史記錄
設置相同的錨不會創(chuàng)建新的歷史記錄
數(shù)據(jù) 數(shù)據(jù)可以放在 state 中 只能將數(shù)據(jù)寫在錨的字符串中

微任務和宏任務

  1. 瀏覽器中并不存在宏任務,宏任務(Macrotask)是 Node.js 發(fā)明的術語
  2. 瀏覽器中只有任務和微任務(Microtask)
    1. 使用 script 標簽颗味、setTimeout 可以創(chuàng)建任務
    2. 使用 Promise#then超陆、window.queueMicrotask 可以創(chuàng)建微任務
  3. 微任務會在任務間隙執(zhí)行 => 微任務只能插任務的隊
  4. 多個 then 里面的回調(diào)并不會一次性插入到等待隊列中,而是執(zhí)行完一個再插入下一個
  5. 一個 return Promise.resolve(...) 等于兩個 then
// queue => [0, 1] => 0 -> [1, 4x] => 1 -> [4x, 2] => [2, 4x(剩余一個then)] => 2 -> [4x, 3] => [3, 4x(下一次就將打印)] => 3 -> [4x, 5] => 4x -> [5] => 5 -> [6] => 6
Promise.resolve().then(() => {
    console.log(0);
    return Promise.resolve('4x');
}).then((res) => {
    console.log(res)
});
Promise.resolve().then(() => {
    console.log(1);
}).then(() => {
    console.log(2);
}, () => {
    console.log(2.1)
}).then(() => {
    console.log(3);
}).then(() => {
    console.log(5);
}).then(() => {
    console.log(6);
})

Web 性能優(yōu)化

  1. HTTP/1.1 => 連接復用 + 并行連接
  2. HTTP/2 => 多路復用 => Frame + Stream => 1個 TCP 連接中可以同時進行多個請求和響應
  3. 緩存 + 內(nèi)容協(xié)商
    • HTTP/1.1 => Cache-Control + ETag + If-None-Match + 304 | 200
    • HTTP/1.0 => Expires + Last-Modified + If-Modified-Since + 304 | 200
  4. cookie-free => cookie 最大有4k浦马,每一個同源請求都會帶著 cookie时呀,某些文件可以啟用新的域名,從而做到 cookie-free 和 并行連接
  5. 使用 CDN => cookie-free + 并行連接 + 下載速度快
  6. 資源合并 => 典型的方案有 icon font 和 SVG symbol
  7. 代碼層面
    • 分層 => 將一個 JS 拆分成多個 JS晶默,從而達到分層的目的
    • 懶加載 | 預加載 => 多屏圖片先加載第一屏谨娜,滾動到第二屏在加載第二屏的圖片
    • js 動態(tài)導入 => import("lodash").then(_ => _.deepClone())

工程化

babel 原理

  1. babel 主要是將 A 類型的文件轉(zhuǎn)化為 B 類型
  2. webpack 只支持 JS 文件,所以需要將其他文件類型都轉(zhuǎn)化為 JS 文件
  3. parse => 主要將代碼轉(zhuǎn)化為 AST
  4. traversal => 遍歷 AST 進行修改
  5. generator => 將修改后的 AST 轉(zhuǎn)化為 code

webpack 流程

webpack 常見 loader 和 plugin 有哪些磺陡?二者區(qū)別是什么趴梢?

loader

  1. Transpiling
    1. babel-loader => 將 ES2015+ 轉(zhuǎn)化為 ES5
    2. ts-loader => 加載 TS,并提示類型錯誤
    3. thread-loader => 多線程打包
  2. Templating
    1. html-loader => 將 HTML 導出為字符串
  3. Styling
    1. sass-loader => 加載并編譯 sass 文件
    2. less-loader => 加載并編譯 less 文件
    3. style-loader => 將 css 轉(zhuǎn)化為 style
    4. css-loader => 把 CSS 變成 JS 字符串
  4. Frameworks
    1. vue-loader => 加載并編譯 Vue 組件

plugin

  1. HtmlWebpackPlugin => 創(chuàng)建 HTML 文件并自動引入 JS 和 css
  2. CleanWebpackPlugin => 用于清理之前打包的殘余文件
  3. SplitChunksPlugin => 用于代碼分包
  4. DLLPlugin + DLLReferencePlugin => 用于避免大依賴被頻繁重新打包币他,大幅降低打包時間
  5. EslintWebpackPlugin => 用于檢查代碼中的錯誤
  6. DefinePlugin => 用于在 webpack config 中添加全局變量
  7. CopyWebpackPlugin => 用于拷貝靜態(tài)文件到 dist

區(qū)別

  1. loader 是文件加載器坞靶,主要使用在 make 階段,將 A 類型的文件轉(zhuǎn)化為 B 類型蝴悉。它能夠?qū)ξ募M行編譯彰阴、優(yōu)化、壓縮等
  2. plugin 是 webpack 插件拍冠,plugin 可以掛載在整個 webpack 打包過程中的鉤子中尿这,可以實現(xiàn)更多功能,如定義全局變量庆杜、加速編譯射众、Code Split

webpack 如何解決開發(fā)時的跨域問題

  1. 在配置中添加代理即可
    devService: {
       proxy: {
          '/api': {
             target: 'http://host', 
             changeOrigin: true
          }
       }
    }
    

如何實現(xiàn) tree-shaking?

  1. tree-shaking 就是讓沒有用到的 JS 代碼不打包,以減少包的體積
  2. 利用 tree-shaking 部分 JS 代碼
    1. 使用 ES2015 模塊語法欣福,即 export 和 import责球。
    2. 不要使用 CommonJS焦履,CommonJS 無法 tree-shaking => babel-loader 添加 modules: false 選項
    3. 引入的時候只引用需要的模塊
      • import {cloneDeep} from 'lodash-es' => 僅僅打包 cloneDeep
      • import _ from 'lodash' => lodash 全部打包拓劝,無法 tree-shaking 沒有用到的模塊
  3. 不 tree-shaking JS 代碼
    1. 在項目的 package.json 中添加 "sideEffects" 屬性雏逾,防止某些文件被 tree-shaking
      • case: import x.js x.js 添加了 window.x 屬性,那么 x.js 就要放到 sideEffects 中
      • 所有被 import 的 CSS 都要放在 sideEffects 中
  4. 如何開啟 tree-shaking => 在 webpack config 中將 mode 設置為 production => mode: production 給 webpack 加了非常多的優(yōu)化

如何提高 webpack 構(gòu)建速度

  1. 使用 DLLPlugin 將不常變化的代碼提前打包并復用郑临,如 Vue栖博、React
  2. 使用 thread-loader 進行多線程打包
  3. 處于開發(fā)環(huán)境時,在 webpack config 中將 cache 設為 true
  4. 處于生產(chǎn)環(huán)境時厢洞,關閉不必要的環(huán)節(jié)仇让,如 source map

Webpack 和 Vite 的區(qū)別

開發(fā)環(huán)境區(qū)別

  1. Vite 自己實現(xiàn) server,不對代碼打包躺翻,充分利用瀏覽器對 <script type=module> 的支持
    • 假設 main.js 引入了 vue
    • 該 server 會把 import {createApp} from 'vue' 改為 import {createApp} from '/node_modules/.vite/vue.js'
      這樣瀏覽器就知道去哪里找 vue.js 了
  2. webpack-dev-server 常使用 babel-loader 基于內(nèi)存打包丧叽,比 Vite 慢很多很多
    • 該 server 會把 vue.js 的代碼(遞歸地)打包進 main.js

生產(chǎn)環(huán)境區(qū)別

  1. Vite 使用 rollup + esbuild 來打包 JS 代碼
  2. Webpack 使用 babel 來打包 JS 代碼,比 esbuild 慢很多很多

文件處理時機

  1. Vite 只會在你請求某個文件的時候處理該文件
  2. Webpack 會提前打包好 main.js公你,等你請求的時候直接輸出打包好的 JS 給你

目前已知 Vite 缺點

  1. 熱更新常常失敗
  2. 有些功能 rollup 不支持踊淳,需要自己寫 rollup 插件
  3. 不支持非現(xiàn)代瀏覽器

Webpack 怎么配置多頁應用

const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  entry: {
    app: './src/app.js',
    admin: './src/admin.js'
  },
  plugins: [
    new HtmlWebpackPlugin({
      filename: 'index.html',
      chunks: ['app']
    }),
    new HtmlWebpackPlugin({
      filename: 'admin.html',
      chunks: ['admin']
    })
  ]
}
  1. 但是這樣配置后會有一個重復打包的問題:假設 app.js 和 admin.js 都引入了 vue.js,那么 vue.js 的代碼會打包進 app.js陕靠,也會打包進 admin.js
  2. 我們需要使用 optimization.splitChunks 將共同依賴單獨打包成 common.js迂尝,HtmlWebpackPlugin 會自動引入 common.js

swc、esbuild 是什么剪芥?

swc

  • 實現(xiàn)語言 => Rust
  • 功能 => 編譯 JS/TS垄开,打包 JS/TS => TS: 類型擦除
  • 優(yōu)勢 => 比 babel 快很多很多(20倍以上)
  • 能否集成進 webpack => 可以
  • 缺點 => 1. 對 TS 代碼進行類型檢查(用 tsc 先檢查再打包)+ 2. 無法打包 CSS/SVG 等非 JS 文件

esbuild

  • 實現(xiàn)語言 => GO
  • 功能 => 編譯 JS/TS,打包 JS/TS => TS: 類型擦除
  • 優(yōu)勢 => 比 babel 快很多很多(10 - 100倍)
  • 能否集成進 webpack => 可以
  • 缺點 => 1. 對 TS 代碼進行類型檢查(用 tsc 先檢查在打包)+ 2. 無法打包 CSS/SVG 等非 JS 文件

Docker

  1. Docker 是一種技術
  2. 在系統(tǒng)上安裝 Docker 軟件即可使用
  3. 核心概念
    1. 容器(Container) => 一臺虛擬的計算機税肪,擁有獨立的網(wǎng)絡溉躲、文件系統(tǒng)、進程益兄。默認和宿主機不發(fā)生任何交互
    2. 鏡像(Image) => 一個預先定義好的模板文件签财,Docker 引擎按照這個模板文件啟動無數(shù)個一模一樣,互不干擾的容器偏塞。默認是分層的 => 復用唱蒸、節(jié)省空間
  4. 命令 =>
    1. docker run --name -v -p -e
    2. docker images
    3. docker ps
  5. 解決了什么問題 =>
    1. 保證開發(fā)、測試灸叼、交付神汹、部署的環(huán)境完全一致
    2. 保證資源的隔離
    3. 啟動臨時的、用完即棄的環(huán)境古今,如測試
    4. 秒級的超大規(guī)模的部署和擴容
  6. 實現(xiàn)隔離技術 => namespace + Control Groups

React

React setState 異步同步

  1. 在 setTimeout屁魏、Promise 等原生事件 API 調(diào)用中
    • setState 和 useState 是同步執(zhí)行的,立即執(zhí)行 render
      • Class Component 能獲取到最新值 => this.state => 引用類型
      • Function Component 不能獲取到最新值 => 只能得到之前的值 => 閉包
    • 多次執(zhí)行 setState 和 useState捉腥,每一次執(zhí)行都會調(diào)用一次 render
  2. 在 React 合成事件中
    • setState 和 useState 是異步執(zhí)行的氓拼,不會立即執(zhí)行 render,都不能立即獲取到更新后的值
    • 多次執(zhí)行 setState 和 useState,只會調(diào)用一次 render
    • setState 可能會進行 state 合并
      • 傳入一個對象桃漾,會進行 state 合并 => +1
      • 傳入一個函數(shù)坏匪,則不會進行 state 合并 => +2
    • useState 不會進行 state 合并

React Fiber

在 Fiber 出現(xiàn)之前 React 的問題

  1. 在 Fiber 出現(xiàn)之前對比更新虛擬 DOM 使用遞歸加循環(huán),這種對比方式有一個問題撬统,就是任務一旦開始則無法中斷
  2. 如果應用組件過多适滓,層級較深,那么主線程將會被一直占用
  3. 這會導致一些用戶操作或者動畫無法立即得到執(zhí)行恋追,頁面會產(chǎn)生卡頓
  4. 總結(jié) => 遞歸無法中斷凭迹,執(zhí)行任務耗時長,JavaScript 是單線程的苦囱,比較虛擬 DOM 過程中無法執(zhí)行其他任務嗅绸,導致任務延遲頁面卡頓

Fiber 架構(gòu)思路

  1. Fiber 架構(gòu)使用循環(huán)模擬遞歸,循環(huán)可以隨時被中斷
  2. Fiber 將大的任務拆分成一個個小任務
  3. 使用 requestIdleCallback 利用瀏覽器空余時間執(zhí)行小任務撕彤,執(zhí)行完一個任務單元后朽砰,查看是否有其他優(yōu)先級更高的任務,如果有喉刘,放棄占用線程瞧柔,先執(zhí)行優(yōu)先級更高的任務
  4. requestIdleCallback 方法插入一個函數(shù),這個函數(shù)將在瀏覽器空閑時期被調(diào)用

Fiber 節(jié)點

  1. tag
  2. key
  3. stateNode => DOM 節(jié)點
  4. child => 子節(jié)點
  5. sibling => 下一個兄弟節(jié)點
  6. return => 父級節(jié)點
  7. firstEffect
  8. nextEffect
  9. lastEffect

Fiber 執(zhí)行

  1. 兩棵樹 => currentFiber + workInProgress
  2. render 階段 => 利用 DFS 和 diff 算法構(gòu)建 workInProgress Fiber 樹睦裳,可中斷造锅,收集 Effect List 鏈表
  3. commit 階段 => 根據(jù) Fiber 節(jié)點的 tag 進行 DOM 操作 => 不可中斷

render 階段

  1. DFS 遍歷 Fiber 樹,遍歷的時候會進行 current 和 workInProgress 的 diff廉邑,決定哪些舊節(jié)點需要復用哥蔚、刪除、移動蛛蒙,哪些新節(jié)點需要創(chuàng)建
  2. 邊構(gòu)建糙箍、邊遍歷、邊對比牵祟、可中斷
  3. performUnitOfWork -> beginWork -> completeUnitOfWork -> completeWork
  4. performUnitOfWork => 開始函數(shù)
  5. beginWork => 返回 workInProgress.child 節(jié)點
  6. completeUnitOfWork => check sibling -> parent
  7. completeWork => 如果有 stateNode 則更新深夯,如果沒有 stateNode 則創(chuàng)建 => stateNode 對應 DOM 節(jié)點,有 stateNode 說明這個節(jié)點有對應的 DOM 節(jié)點
  8. completeWork 執(zhí)行完之后收集 effect
  9. completeUnitOfWork 的內(nèi)部循環(huán)會自底向上收集 effect诺苹,不斷把有 effectTag 的子節(jié)點和自身向上合并到父節(jié)點的 effectList 中咕晋,直至根節(jié)點,Effect List 就是一個鏈表
  10. diff
    1. 如果根節(jié)點類型改變了收奔,比如 div 變?yōu)榱?p掌呜,那么直接認為整顆樹都變了,不再對比子節(jié)點坪哄。此時直接刪除對應的真實 DOM 樹质蕉,創(chuàng)建新的真實 DOM 樹
    2. 如果根節(jié)點類型沒有改變势篡,就看看屬性變了沒有
      1. 屬性沒變,保留對應的真實節(jié)點
      2. 屬性變了模暗,就只更新該節(jié)點的屬性禁悠,不重新創(chuàng)建節(jié)點 => case: 更新 style 時,如果多個 css 屬性只有一個改變了汰蓉,那么 React 只更新改變的
    3. 遍歷子節(jié)點
      1. 子節(jié)點是單節(jié)點 => 遍歷舊的節(jié)點绷蹲,如果有可以復用的棒卷,復用顾孽,沒有則將舊節(jié)點的 effect 標記為刪除,新建子節(jié)點
      2. 子節(jié)點是多節(jié)點 =>
        • 第一輪:
          1. 如果新節(jié)點遍歷完比规,則刪除所有舊節(jié)點若厚,對比結(jié)束
          2. 如果舊節(jié)點遍歷完,則新增所有新節(jié)點蜒什,對比結(jié)束
        • 第二輪:建立 map 對比
          1. 有節(jié)點位置發(fā)生改變
            A -> B -> C
            A -> C -> B
            // 將 B 節(jié)點 effect 標記為 Placement
            
          2. 中途出現(xiàn)增刪的節(jié)點

commit 階段

  1. Before Mutation 前
    1. flushPassiveEffects => 觸發(fā) useEffect 回調(diào)與其他同步任務测秸,由于這些任務可能觸發(fā)新的渲染,所以要一直遍歷到?jīng)]有任務
    2. commitRoot 是同步完成的灾常,清除 callbackNode 以允許安排新的回調(diào)
    3. reset workInProgress
    4. 獲取 Effect List
  2. Before Mutation
    1. 循環(huán)遍歷 Effect List
    2. 處理 DOM 節(jié)點渲染或刪除后的 autoFocus/blur 邏輯
    3. call getSnapshotBeforeUpdate
  3. Mutation => 執(zhí)行 DOM 操作
    1. 循環(huán)遍歷 Effect List
    2. reset text
    3. 更新 ref
    4. 根據(jù) flags 分別處理
      • Deletion => commitDeletion
        1. 遞歸的刪除節(jié)點
        2. 清除 ref
        3. 調(diào)用 componentWillUnmount => ClassComponent
        4. 調(diào)度 destroy 函數(shù) => FunctionComponent
      • Update => commitWork
        1. 遞歸調(diào)度 effect destroy 函數(shù) => FunctionComponent
      • Placement => commitPlacement
        1. 獲取父級 DOM 節(jié)點
        2. insertOrAppendPlacementNodeIntoContainer | insertOrAppendPlacementNode
  4. Layout => DOM 渲染完成霎冯,觸發(fā)生命周期鉤子和 hooks
    1. 循環(huán)遍歷 Effect List
    2. FunctionComponent => useLayout callback + 調(diào)度 useEffect destroy + callback
    3. ClassComponent => (componentDidMount | componentDidUpdate) + 執(zhí)行 this.setState 的 callback
    4. 更新 ref
  5. Layout 之后
    1. 處理 passive effect
    2. 檢查 root 上是否有其余的工作
    3. ensureRootIsScheduled => 保證任何在 root 上附加任務被 schedule
    4. flushSyncCallbackQueue => 執(zhí)行同步任務

React 合成事件

React 將所有的事件都綁定到了根元素上,自動實現(xiàn)了事件委托钞瀑,此時不需要使用 addEventListener 為已經(jīng)創(chuàng)建的 DOM 添加事件監(jiān)聽器

合成事件和原生事件區(qū)別

React 合成事件 原生事件
命名 小駝峰(onClick) 純小寫(onclick)
事件處理函數(shù) 函數(shù) 字符串
阻止默認行為 event.preventDefault() return false

虛擬 DOM 的原理是什么沈撞?

  1. 虛擬 DOM 就是虛擬節(jié)點。React 使用 JS 對象來模擬 DOM 節(jié)點雕什,之后將其渲染成真實的 DOM 節(jié)點
  2. 使用 JSX 語法寫出來的 div 其實就是一個虛擬節(jié)點
    <div className="container">
       <span className="red">hi</span>
    </div>
    
  3. 上面的代碼其實是調(diào)用了 React.createElement 函數(shù)缠俺,之后生成了一個 JS 對象
    {
      tag: 'div',
      props: {className: 'container'},
      children: [
        {
          tag: 'span',
          props: {
            className: 'red'
          },
          children: [
            'hi'
          ]
        }
      ]
    }
    
  4. 之后會根據(jù) JS 對象信息即虛擬節(jié)點渲染為真實節(jié)點
  5. 如果節(jié)點發(fā)生改變,并不會把新的虛擬節(jié)點直接重新渲染為真實節(jié)點贷岸,而是要先經(jīng)過 diff 算法得到一個 patch 再更新到真實節(jié)點上
  6. 虛擬 DOM 大大提升了 DOM 操作性能問題壹士。通過虛擬 DOM 和 diff 算法減少不必要的 DOM 操作,保證性能偿警。
  7. 之前 DOM 操作并不是很方便躏救,但是現(xiàn)在只需要 setState 即可
  8. 但是 React 為虛擬 DOM 創(chuàng)造了合成事件,和原生 DOM 事件不太一樣螟蒸。所有 React 事件都綁定到了根元素落剪,自動實現(xiàn)事件委托,如果混用合成事件和原生 DOM 事件尿庐,可能會出現(xiàn) bug

React DOM diff 算法

  1. DOM diff 就是對比兩顆虛擬 DOM 樹的算法
  2. 當組件變化時忠怖,會 render 出一個新的虛擬 DOM,diff 算法對比新舊虛擬 DOM 之后抄瑟,得到一個 patch凡泣,然后 React 用 patch 來更新真實 DOM
  3. 首先對比兩棵樹的根節(jié)點
    • 如果根節(jié)點類型改變了枉疼,比如 div 變?yōu)榱?p,那么直接認為整顆樹都變了鞋拟,不再對比子節(jié)點骂维。此時直接刪除對應的真實 DOM 樹,創(chuàng)建新的真實 DOM 樹
    • 如果根節(jié)點類型沒有改變贺纲,就看看屬性變了沒有
      1. 屬性沒變航闺,保留對應的真實節(jié)點
      2. 屬性變了,就只更新該節(jié)點的屬性猴誊,不重新創(chuàng)建節(jié)點 => case: 更新 style 時潦刃,如果多個 css 屬性只有一個改變了,那么 React 只更新改變的
  4. 然后同時遍歷兩棵樹的子節(jié)點懈叹,每個節(jié)點的對比過程如上
  5. case1:React 依次對比 A-A乖杠,B-B,空-C澄成,發(fā)現(xiàn) C 是新增的胧洒,最終會創(chuàng)建真實 C 節(jié)點插入頁面
    <ul>
        <li>A</li>
        <li>B</li>
    </ul>
    
    // updated
    <ul>
        <li>A</li>
        <li>B</li>
        <li>C</li>
    </ul>
    
  6. case2: React 對比 B-A,刪除 B 節(jié)點新建 A 節(jié)點墨状;對比 C-B卫漫,刪除 C 節(jié)點新建 B 節(jié)點(注意:并不是邊對比邊刪除新建,而是把操作匯總到 patch 里在進行 DOM 操作肾砂,會進行標記)列赎;對比 空-C,新建 C 節(jié)點
    <ul>
        <li>B</li>
        <li>C</li>
    </ul>
    
    // updated
    <ul>
        <li>A</li>
        <li>B</li>
        <li>C</li>
    </ul>
    
  7. case2 其實只需要創(chuàng)建 A 節(jié)點通今,保留 B C 節(jié)點即可粥谬,此時 React 需要你添加 key
    <ul>
        <li key="b">B</li>
        <li key="c">C</li>
    </ul>
    
    // updated
    <ul>
        <li key="a">A</li>
        <li key="b">B</li>
        <li key="c">C</li>
    </ul>
    
  8. 此時 React 先對比 key,發(fā)現(xiàn) key 增加了 a辫塌,此時保留 B C漏策,新建 A 節(jié)點

Vue Dom diff

Vue 雙端交叉對比

  1. 頭頭對比 => 對比兩個數(shù)組的頭部,如果找到臼氨,把新節(jié)點 patch 到舊節(jié)點掺喻,頭指針后移
  2. 尾尾對比 => 對比兩個數(shù)組的尾部,如果找到储矩,把新節(jié)點 patch 到舊節(jié)點感耙,尾指針前移
  3. 舊尾新頭對比 => 交叉對比,舊尾新頭持隧,如果找到即硼,把新節(jié)點 patch 到舊節(jié)點,舊尾指針前移屡拨,新頭指針后移
  4. 舊頭新尾對比 => 交叉對比只酥,舊頭新尾褥实,如果找到,把新節(jié)點 patch 到舊節(jié)點裂允,新尾指針前移损离,舊頭指針后移
  5. 利用 Key 對比 => 用新指針對應節(jié)點的 key 去舊數(shù)組中尋找對應的節(jié)點
    • 沒有對應的 key => 創(chuàng)建新節(jié)點
    • 有 key 并且是相同的節(jié)點 => 把新節(jié)點 patch 到舊節(jié)點
    • 有 key 但是不是相同的節(jié)點 => 創(chuàng)建新節(jié)點

React 有哪些聲明周期鉤子函數(shù)?數(shù)據(jù)請求放在哪個鉤子里绝编?

React Lifecycle
  1. 掛載時調(diào)用 constructor僻澎,更新時不調(diào)用
  2. 更新時調(diào)用 shouldComponentUpdate 和 getSnapshotBeforeUpdate,掛載時不調(diào)用
  3. shouldComponentUpdate 在 render 前調(diào)用十饥,getSnapshotBeforeUpdate 在 render 后調(diào)用
  4. 請求放置在 componentDidMount 里

React 如何實現(xiàn)組件間通信

  1. 父子組件通信 => props + 函數(shù)
  2. 爺孫組件通信 => 兩層父子通信 | Context.Provider 和 Context.Consumer
  3. 任意組件通信 => 狀態(tài)管理 => Redux | Mobx

如何理解 Redux

  1. Redux 就是一個狀態(tài)管理庫窟勃,可以實現(xiàn)任意組件之間的通信,其實就是將信息放置在頂部绷跑,如有需要其余組件去頂部獲取即可
  2. Redux 核心概念
    1. store => 信息/狀態(tài)存放的地方
    2. action => 可以更改 state 的唯一途徑拳恋,根據(jù) type 和 payload 進行更改 state
    3. dispatch => 用于派發(fā)事件
    4. reducer => 就是一個函數(shù)凡资,傳遞給 reducer 一個舊的 state + action砸捏,他會返回一個新的 state
    5. Middleware => 中間件
  3. Redux 常與 ReactRedux 聯(lián)合使用,ReactRedux 提供了以下 Api
    1. connect()(Component)
    2. mapStateToProps
    3. mapDispatchToProps
  4. Redux 常用中間件:
    1. redux-thunk => 擴展 redux 可以支持異步 action隙赁,如果 action 是一個函數(shù)垦藏,就直接調(diào)用它,否則就進入下一個中間件
    2. redux-promise => 如果 payload 是一個 Promise伞访,就執(zhí)行個這個 Promise掂骏,并在 then 里面去 dispatch

什么是高階組件 HOC

  1. 參數(shù)是組件,返回值也是組件的函數(shù)
  2. React.forwardRef
    const FancyButton = React.forwardRef((props, ref) => {
      <button ref={ref} className="fancyButton">
        {props.children}
      </button>
    });
    
    // You can now get a ref directly to the DOM button
    const ref = React.createRef();
    <FancyButton ref={ref}>Click me!</FancyButton>
    
  3. ReactRedux 的 connect

React Hooks 如何模擬組件生命周期

  1. 模擬 componentDidMount
  2. 模擬 componentDidUpdate
  3. 模擬 componentWillUnmount

設計模式

設計模式是人們在長久的生產(chǎn)實踐中總結(jié)出來的一套方法論厚掷,它可以提高開發(fā)效率弟灼,降低維護成本。舉例說明:

  1. 發(fā)布訂閱模式 => 定義了對象間的一種一對多的依賴關系冒黑,當一個對象的狀態(tài)發(fā)生改變時田绑,所有依賴于它的對象都將得到通知。case:EventBus
  2. 責任鏈模式 => 將請求沿著所有處理者組成的鏈發(fā)送抡爹,每個處理者都可以決定自己是否處理掩驱,或者是否繼續(xù)處理。實現(xiàn)了請求和處理者之間的解耦冬竟。case:Redux 中間件
  3. 模板方法 => 一個抽象類定義了執(zhí)行它的方法的模板欧穴,子類可以按需要重寫方法實現(xiàn)。case:React 的生命周期函數(shù) + Dart

其他

人崗匹配

離職原因

個人原因:

  1. 期待更大的平臺泵殴,更好的機會
  2. 目前準備成家涮帘,經(jīng)濟壓力比較大

期望薪資

  1. 請您方便介紹一個貴司的薪酬情況和薪酬結(jié)構(gòu)嗎?
  2. 希望有一個符合市場預期的漲幅

規(guī)劃

我是一個穩(wěn)定性很強的人笑诅,我覺得貴司經(jīng)過多年的發(fā)展调缨,穩(wěn)步的一個方式和我是非常匹配的映屋,所以我的規(guī)劃就是在我的本職崗位上做好,1-3年內(nèi)提高我的專業(yè)能力同蜻,并且要對于崗位進行一個深入了解棚点,通過積累3-5年的行業(yè)經(jīng)驗以及學習學習,達到一個高級或者資深的水平湾蔓,后續(xù)的發(fā)展隨著公司同步發(fā)展即可

缺點

  1. 性格比較急躁瘫析,對于事情會比較較真
  2. 后來經(jīng)過同事和領導的溝通與交流
  3. 之后我仔細思考了一下如何解決這個問題
  4. 我從兩個方面做出了努力
    • 在與同事的溝通中,轉(zhuǎn)化自己的語言默责,case:你要這么做 => 你可以考慮一下這樣做
    • 在與同事的溝通中贬循,說話慢慢來,從而控制我急躁的性格桃序,這個效果是非常明顯的
    • 首先考慮這個事情對于整體有沒有影響杖虾,如果有,那必須去較真媒熊,如果對整體沒有影響奇适,那就不去計較
  5. 通過幾個月的調(diào)整,大家也能明顯感覺到我的進步芦鳍,我和同事之間的關系更加緊密了

加班看法

  1. 我不贊同無效的加班
  2. 當公司的重要項目有延期風險嚷往,那么為了保證項目如期上線是可以加班的
  3. 另外,我覺得更重要的是提升自己的工作效率柠衅,降低項目延期的風險

你遇到最難的 Bug 是什么皮仁?

  1. 我們交付了一個打開第三方頁面的功能,該功能在測試的時候沒有出現(xiàn)任何問題菲宴,但是在現(xiàn)場發(fā)現(xiàn)某些頁面空白
  2. 因為我們沒有相關賬號贷祈,所以只能進行遠程調(diào)試,在溝通協(xié)調(diào)了一段時間之后喝峦,我才看到具體的問題
  3. 空白的第三方頁面顯示 md5 is not defined势誊,之后我查看了一下 window 上是否掛載了 md5,發(fā)現(xiàn)沒有正常掛載
  4. 之后便懷疑 md5 這個包沒有正常的下載愈犹,之后查看了一下 network键科,發(fā)現(xiàn) md5.min.js 請求的狀態(tài)碼是 200
  5. 按理說我獲取到 js 文件,之后就會一行一行的執(zhí)行該文件
  6. 之后把 md5.min.js 文件內(nèi)容拷貝出來漩怎,粘貼在控制臺勋颖,之后查看 window 上是否掛載了 md5,發(fā)現(xiàn)也是沒有 md5 這個對象
  7. 但是 md5.min.js 被廣泛使用勋锤,應該不會出現(xiàn)問題饭玲,所以我將上面 md5.min.js 文件粘貼到瀏覽器控制臺中,發(fā)現(xiàn) window 上是正常掛載了 md5
  8. 那么我現(xiàn)在桌面應用和瀏覽器控制臺調(diào)用了相同的 js叁执,表現(xiàn)出不同的結(jié)果茄厘,說明兩者環(huán)境可能有差異
  9. 最終發(fā)現(xiàn)矮冬,桌面端應用在創(chuàng)建窗口是開啟了 Node 功能。之后把 Node 功能置為 false次哈,發(fā)現(xiàn)網(wǎng)頁便正常了
  10. 之后去查看了一下 md5.min.js 文件胎署,看看對于 md5.min.js 兩種環(huán)境差在哪里
  11. md5.min.js 文件最終會查看 define 和 module,之后再兩種環(huán)境中分別查看最終結(jié)果
  12. Node 環(huán)境 module.export = t;瀏覽器是 n.md5 = t
  13. 之后將該問題總結(jié)記錄在了我們公司的 wiki 上,為大家提供解決問題思路

平時是如何學習的

手段有 看書须鼎,看博客,逛論壇基本上就這些巨坊,之后通過 輸入、轉(zhuǎn)化此改、輸出來進行學習

  • 輸入 => 是指閱讀別人分享的知識趾撵,比如看博客、看書
  • 轉(zhuǎn)化 => 是指把知識消化之后變成自己的積累共啃,用自己的知識體系重新闡述一遍新學的概念
  • 輸出 => 是指把自己的理解以博客占调、代碼的形式分享出來

自我介紹

面試官您好,我是XXX勋磕,XX年畢業(yè)妈候,本科學歷敢靡,我來應聘前端工程師崗位挂滓,目前有X年開發(fā)經(jīng)驗。我擅長的語言是 JavaScript啸胧,并且對于 Java 和 Dart 有一定的了解赶站。主要使用的技術棧是 React ,熟練掌握算法和數(shù)據(jù)結(jié)構(gòu)纺念。對于產(chǎn)品研發(fā)自動化流程有一定的實踐贝椿。我在上一份工作中從事前端開發(fā)工作,最近在公司主要負責公共組件開發(fā)項目陷谱。在工作之余我也會積極學習其他方面的知識烙博,如 Java,并且將自己的學到的知識總結(jié)烟逊,目前已經(jīng)寫了接近 200 篇的技術博客渣窜。我覺得我的個人經(jīng)歷是和貴司的崗位是非常匹配的,我也非常喜歡貴司的文化宪躯,非常期待能夠加入貴司和各位一起去做事情

回答問題

  1. 您問的是xxx問題嗎乔宿?
  2. 我覺得這個問題可以從兩個方面考慮
  3. 一是
  4. 二是
  5. 對了,還有一點可以補充
  6. 以上三點就是我對xxx問題的理解

反問問題

  1. 組里目前最大的挑戰(zhàn)是什么访雪?
  2. 咱組在整個部門或者公司的組織架構(gòu)是什么樣的详瑞?
  3. 如果我加入貴司掂林,在我未來的職業(yè)生涯中,有怎樣一個晉升的途徑坝橡?我更加適合哪個方式泻帮?

面試官

  1. 時間 => 把握面試節(jié)奏,根據(jù)需求來決定
  2. 分成幾個部分计寇,每個部分分別考察什么刑顺?=> 標準化(1h)
    1. 自我介紹 => 5min =>
    2. 語言基礎知識(寫代碼/系統(tǒng)設計/算法(1)) => 算法 => 暴力 + 優(yōu)化(準備3道題目)
    3. 框架
    4. 項目 => 考察崗位匹配程度 + 從一個人過去持續(xù)穩(wěn)定的行為推測他能否勝任現(xiàn)在的崗位(可能短期提升嗎?)
    5. 行為問題 => 10 - 15 min => 遇到最難的技術問題是什么饲常?
    6. 反問 => 5min => 不能問結(jié)果蹲堂,好的問題:做什么?挑戰(zhàn)是什么贝淤?發(fā)展是什么柒竞?該部門在公司的組織架構(gòu)上是什么樣的?
  3. 如何評價播聪? => 標準:技能匹配 + 文化匹配 + 潛力 => 評分:不合格 | 合格 | 優(yōu)秀
  4. 記錄優(yōu)缺點

項目中的最大的困難或挑戰(zhàn)? => 舉具體例子
回答的時候要匹配公司文化

httpClient 手寫和使用庫

亞馬遜領導力準則

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末朽基,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子离陶,更是在濱河造成了極大的恐慌稼虎,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,681評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件招刨,死亡現(xiàn)場離奇詭異霎俩,居然都是意外死亡,警方通過查閱死者的電腦和手機沉眶,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,205評論 3 399
  • 文/潘曉璐 我一進店門打却,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人谎倔,你說我怎么就攤上這事柳击。” “怎么了片习?”我有些...
    開封第一講書人閱讀 169,421評論 0 362
  • 文/不壞的土叔 我叫張陵捌肴,是天一觀的道長。 經(jīng)常有香客問我藕咏,道長状知,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 60,114評論 1 300
  • 正文 為了忘掉前任侈离,我火速辦了婚禮试幽,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己铺坞,他們只是感情好起宽,可當我...
    茶點故事閱讀 69,116評論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著济榨,像睡著了一般坯沪。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上擒滑,一...
    開封第一講書人閱讀 52,713評論 1 312
  • 那天腐晾,我揣著相機與錄音,去河邊找鬼丐一。 笑死藻糖,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的库车。 我是一名探鬼主播巨柒,決...
    沈念sama閱讀 41,170評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼柠衍!你這毒婦竟也來了洋满?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 40,116評論 0 277
  • 序言:老撾萬榮一對情侶失蹤珍坊,失蹤者是張志新(化名)和其女友劉穎牺勾,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體阵漏,經(jīng)...
    沈念sama閱讀 46,651評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡驻民,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,714評論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了袱饭。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片川无。...
    茶點故事閱讀 40,865評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖虑乖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情晾虑,我是刑警寧澤疹味,帶...
    沈念sama閱讀 36,527評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站帜篇,受9級特大地震影響糙捺,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜笙隙,卻給世界環(huán)境...
    茶點故事閱讀 42,211評論 3 336
  • 文/蒙蒙 一洪灯、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧竟痰,春花似錦签钩、人聲如沸掏呼。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,699評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽憎夷。三九已至,卻和暖如春昧旨,著一層夾襖步出監(jiān)牢的瞬間拾给,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,814評論 1 274
  • 我被黑心中介騙來泰國打工兔沃, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留蒋得,地道東北人。 一個月前我還...
    沈念sama閱讀 49,299評論 3 379
  • 正文 我出身青樓乒疏,卻偏偏與公主長得像窄锅,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子缰雇,可洞房花燭夜當晚...
    茶點故事閱讀 45,870評論 2 361

推薦閱讀更多精彩內(nèi)容