前端面試總結(jié)

HTML/CSS

1. 盒模型

  1. 標(biāo)準(zhǔn)盒模型
    • width 和 height 是內(nèi)容區(qū)域即 content 的 width 和 height。
    • 盒子總寬度= width + margin(左右) + padding(左右) + border(左右)
  2. IE 盒模型或怪異盒模型
    • width 和 height 除了 content 區(qū)域外,還包含 padding 和 border
    • 盒子寬度 = width + margin(左右)
  3. 通過(guò) box-sizing切換 border-boxcontent-box

2. 隱藏一個(gè)元素的方式

  1. display: none
  2. visibility: hidden
  3. opacity: 0
  4. 高度為 0
  5. 定位 position: absolute; left: 100%;| top: -100%
  6. text-indent 設(shè)置一個(gè)足夠大的負(fù)值

3. display: none 和 visibility: hidden 的區(qū)別

  1. display: none 不占位
  2. visibility: hidden 占位 (內(nèi)部盒子也不會(huì)顯示)

4. BFC 模式

5. flex 1 代表什么

6. align-self 屬性

align-self 屬性允許單個(gè)項(xiàng)目有與其他項(xiàng)目不一樣的對(duì)齊方式,可覆蓋 align-items 屬性。默認(rèn)值為 auto,表示繼承父元素的 align-items 屬性,如果沒(méi)有父元素,則等同于 stretch。

JavaScript

1. 數(shù)據(jù)類(lèi)型

Number String Boolean Undefined Null Symbol Object BigInt

2. var let const 區(qū)別

  1. var 變量聲明提升
  2. let const 區(qū)別
    1. let 變量
    2. const 常量
    3. let 可以直接修改值(或者引用)
    4. const 只可以改變屬性值
  3. let const 暫時(shí)性死區(qū)

3. 箭頭函數(shù)

4. 原型、原型鏈

5. 作用域、作用域鏈

6. 為什么 0.1+0.2 不等于 0.3

01. + 0.2 = 0.3 // false

7. 小數(shù)相加、整數(shù)相加

8. 實(shí)現(xiàn) new 操作符

function Person(name, age) {
  this.name = name
  this.age = age
}

方式一:

function _new(func, ...args) {
  if (typeof func !== 'function') throw new Error('func is not a function')

  const context = Object.create(func.prototype)
  const rest = func.apply(context, args)

  return typeof rest === 'object' && rest !== null ? rest : context
}

const p = _new(Person, 'mike', 12)

方式二:借助 Reflect.construct

Reflect.construct 相當(dāng)于 new target()

const p = Reflect.construct(Person, ['jack', 11])

9. 實(shí)現(xiàn) instanceof

function instanceofX(obj, constructor) {
  const proto = Object.getPrototypeOf(obj)

  if (proto === null) return false
  
  if (proto  === constructor.prototype) return true

  return instanceofX(proto , constructor)
}

10. splice 的使用及返回值

const arr = [1, 2, 3, 4, 5]
  1. 刪除:返回所刪除的元素組成的數(shù)組
arr.splice(1, 2) // 返回 [2, 3]
  1. 增加:
arr.splice(1, 0, 9, 9) // 返回空數(shù)組 []
  1. 替換:返回所有替換后的元素組成的數(shù)組
arr.splice(1, 2, 7, 8) // [2, 3]

11. 類(lèi)型轉(zhuǎn)換

123 instanceof Number // false
new Number(123) instanceof Number // true
Number(123) instanceof Number // false

Number === 123
// Number 就是強(qiáng)轉(zhuǎn)換

12. arguments 參數(shù)

function sidEffecting(ary) {
  ary[0] = ary[2]
}

function bar(a, b, c) {
  c = 10
  sidEffecting(arguments)
  return a + b + c
}

bar(1, 1, 1) // 21

說(shuō)明:考察 arguments 參數(shù)。arguments 內(nèi)部屬性及其函數(shù)形參創(chuàng)建 getter 和 setter 方法,因此改變形參的值會(huì)影響到函數(shù) arguments 的值,反過(guò)來(lái)也一樣。

13. 深拷貝(只考慮數(shù)組和對(duì)象)

  1. 方式一
function deepClone(value) {
  if (Array.isArray(value)) {
    return value.map(deepClone)
  } else if (value && typeof value === 'object' && value !== null) {
    const res = {}

    for (const key in value) {
      if (value.hasOwnProperty(key)) {
          res[key] = deepClone(value[key])
      }
    }

    return res
  }

  return value
}

方式二: 借助reduce

function deepClone(value) {
    if (Array.isArray(value)) {
      return value.map(deepClone)
    } else if (typeof value === 'object' && value !== null) {
      return Object.keys(value).reduce((pre, key) => {
        return {
          ...pre,
          [key]: deepClone(value[key]),
        }
      }, {})
    }

    return value
  }

以上兩種方式的弊端是只能克隆屬性為對(duì)象自身的、可枚舉、且不為Symbol類(lèi)型的屬性。

方式三: JSON.stringify()

JSON.parse(JSON.stringify(value))

缺點(diǎn):

  • 會(huì)忽略值為undefined、函數(shù)類(lèi)型的屬性
  • 忽略值為Symbol類(lèi)型的屬性
  • 日期對(duì)象的值會(huì)通過(guò)toJSON()轉(zhuǎn)成字符串
  • 存在循環(huán)引用會(huì)報(bào)錯(cuò)

比如:

const obj = {
    a: [1, 2, 3],
    b: { c: 1 }
  }

  obj.a.push(obj.b)
  obj.b.j = obj.a
  // JSON.stringify(obj)
  1. 實(shí)現(xiàn)值為undefined,key為symbol類(lèi)型的情況
function clone(obj) {
  if (typeof obj === 'object' && obj !== null) {
    let map = new WeakMap()

    function deep(data) {
      const result = {}
      // const keys = [...Object.getOwnPropertyNames(data), ...Object.getOwnPropertySymbols(data)]
      // Or
      const keys = Reflect.ownKeys(data)

      if (!keys.length) return result

      const exist = map.get(data)

      if (exist) return exist

      map.set(data, result)
      keys.forEach(key => {
        let item = data[key]
        if (typeof item === 'object' && item) {
          result[key] = deep(item)
        } else {
          result[key] = item
        }
      })
      return result
    }
    return deep(obj)
  }
  return obj
}

14. 克隆數(shù)組

1. 一維數(shù)組

  1. 擴(kuò)展運(yùn)算 (Shallow copy)
;[...arr]
  1. for 循環(huán) (Shallow copy)
const newArr = []

for (let i = 0; i < arr.length; i++) {
  newArr[i] = arr[i]
}
  1. while 循環(huán) (Shallow copy)
let i = -1
const newArr = []

while (++i < arr.length) {
  newArr[i] = arr[i]
}
  1. Array.map (Shallow copy)
const newArr = arr.map(item => item)
  1. Array.filter (Shallow copy)
const newArr = arr.filter(Boolean)

不足之處就是:數(shù)組中有選項(xiàng)是 0 ,就會(huì)丟失0,最終結(jié)果就是錯(cuò)的。因?yàn)?code>Boolean(0) --> false

  1. Array.reduce (Shallow copy)
arr.reduce((newArr, item) => newArr.concat(item), [])
  1. Array.slice (Shallow copy)
const newArr = arr.slice()
  1. Array.concat (Shallow copy)
const newArr = arr.concat()
// Or
const newArr = arr.concat([])
  1. Array.from (Shallow copy)
const newArr = Array.from(arr)
  1. JSON.stringify & JSON.parse (Deep copy)
const newArr = JSON.parse(JSON.stringify(arr))

2. 多維數(shù)組

  1. 借助 Array.from()
const deepCloneArr = value =>
  Array.isArray(value) ? Array.from(value, deepCloneArr) : value
  1. 借助 Array.prototype.map()
const deepCloneArr = value =>
  Array.isArray(value) ? value.map(deepCloneArr) : value

15. 平鋪數(shù)組

ES6 方法

參數(shù)默認(rèn)是 1,多維用 Infinity 參數(shù)

Array.prototype.flat()

js 實(shí)現(xiàn)

  1. 二維數(shù)組
const arr = [1, 2, [3, 4], [5, 6]]
const result = [].concat(...arr)
  1. 多維數(shù)組
function spread(arr) {
  const result = [].concat(...arr)

  return result.some(item => Array.isArray(item)) ? spread(result) : result
}
  1. 可以定義平鋪的層級(jí)數(shù)
function spread(arr, count = 1) {
    const result = [].concat(...arr)

    if (1 < count) {
      return result.some(item => Array.isArray(item))
        ? spread(result, --count)
        : result
    }
    return result
  }

16. 實(shí)現(xiàn) Promise.all

Promise.allX = function (promises) {
  return new Promise((resolve, reject) => {
    promises = [...promises]
    const result = []
    const length = promises.length

    promises.forEach((promise, index) => {
      Promise.resolve(promise)
        .then(res => {
          result[index] = res

          if (--length === 0) resolve(result)
        })
        .catch(err => {
          reject(err)
        })
    })
  })
}

17. 實(shí)現(xiàn) Promise.allSettled

Promise.allSettled 接收一組 Promise 實(shí)例作為參數(shù),返回新的 Promise 實(shí)例,只有等到所有 Promise 實(shí)例的狀態(tài)都改變之后才 resolve。
所以 Promise.allSettled 的狀態(tài)總是 fulfilled。其返回值為每個(gè) Promise 實(shí)例的 statusvalue/reason。

Promise.allSettled = function (promises) {
    return new Promise(resolve => {
      promises = [...promises]
      let time = 0
      const length = promises.length
      const result = []

      promises.forEach((promise, index) => {
        Promise.resolve(promise)
          .then(value => {
            result[index] = {
              status: 'fulfilled',
              value,
            }

            if (++time === length) resolve(result)
          })
          .catch(reason => {
            result[index] = {
              status: 'rejected',
              reason,
            }
            if (++time === length) resolve(result)
          })
      })
    })
  }

18. 實(shí)現(xiàn) Promise.any

接收一組 Promise 實(shí)例作為參數(shù),返回 Promise 實(shí)例,其中只要有一個(gè) Promise 的狀態(tài)變成 fulfilled,則返回的 Promise 的狀態(tài)就為 fulfilled,只有所有的 Promise 都為 rejected狀態(tài)則為 rejected

Promise.any = function (promises) {
    return new Promise((resolve, reject) => {
      promises = [...promises]
      let time = 0
      const length = promises.length
      const result = []

      promises.forEach((promise, index) => {
        Promise.resolve(promise)
        .then(res => {
          resolve(res)
        })
        .catch(err => {
          result[index] = err

          if(++time === length) reject(result)
        })
      })
    })
  }

實(shí)現(xiàn)Promise.race()

function race(promises) {
    return new Promise((resolve, reject) => {
      promises = [...promises]

      promises.forEach(p => {
        Promise.resolve(p).then(
          res => {
            resolve(res)
          },
          err => {
            reject(err)
          }
        )
      })
    })
  }

19. 防抖、節(jié)流實(shí)現(xiàn)方式

20. 多種方式實(shí)現(xiàn) call/apply/bind

21. js 借助 async/await 實(shí)現(xiàn)休眠效果

function sleep(interval) {
  return new Promise(resolve => {
    setTimeout(resolve, interval)
  })
}

// 用法
async function one2FiveInAsync() {
  for (let i = 1; i <= 5; i++) {
    console.log(i)
    await sleep(1000)
  }
}
one2FiveInAsync()

22. 實(shí)現(xiàn)多次重復(fù)嘗試,比如多次請(qǐng)求一個(gè)接口,最多請(qǐng)求 3 次

const COUNT = 3

async function request(url) {
  for (let i = 0; i < COUNT; ++i) {
    try {
      await fetch(url)
      break // 跳出循環(huán)
    } catch {}
  }
}

23. 實(shí)現(xiàn) pipe

pipe 執(zhí)行順序從左向右

  1. 單個(gè)參數(shù)
const pipe = (...fns) => val => fns.reduce((pre, cur) => cur(pre), val)
  1. 多個(gè)參數(shù)
const pipes = (...fns) =>
  fns.reduce((pre, cur) => (...args) => cur(pre(...args)))

24. 實(shí)現(xiàn) compose

執(zhí)行順序從右向左 第一個(gè)執(zhí)行函數(shù)可以接收多個(gè)參數(shù),后面的執(zhí)行函數(shù)參數(shù)都是單個(gè)的。所有函數(shù)的執(zhí)行都是同步的。

  1. 單個(gè)參數(shù)
const compose = (...fns) => val => fns.reduceRight((pre, cur) => cur(pre), val)
  1. 多個(gè)參數(shù)
const composes = (...fns) =>
  fns.reduceRight((pre, cur) => (...args) => cur(pre(...args)))

// 或
const composesX = (...fns) =>
  fns.reduce((pre, cur) => (...args) => pre(cur(...args)))

實(shí)現(xiàn)無(wú)限柯里化

// sum(1)(2)(3)(4)(5)(6)...()
function sum(a) {
    return function (b) {
      if (b != null) {
        return sum(a + b)
      }

      return a
    }
  }

25. 實(shí)現(xiàn) trim()

function trim(str) {
  return str.replace(/^\s+|\s+$/g, '')
  // 或
  return str.replace(/^\s*/, '').replace(/\s*$/, '')
}

26. requestAnimationFrame

27. 本地存儲(chǔ)

  1. localStorage
    1. 持久型
    2. 存儲(chǔ)容量大 大約 5M
  2. sessionStorage 會(huì)話(huà)
    1. 會(huì)話(huà)型
    2. 存儲(chǔ)容量大
  3. cookie
    1. 同域下往返于客戶(hù)端和服務(wù)端
    2. 存儲(chǔ)容量小 大概 4k

28. 交換兩個(gè)值為 number 類(lèi)型的變量

let a = 3
let b = 5
  1. 解構(gòu)
;[a, b] = [b, a]
  1. 加減法
a = a + b
b = a - b
a = a - b
  1. 交換變量
let c = a
a = b
b = c
  1. 對(duì)象
a = { a, b }
b = a.a
a = a.b
  1. 托夢(mèng)做出來(lái)的吧?
a = [b, (b = a)][0]
  1. 位運(yùn)算
a = a ^ b
b = b ^ a
a = a ^ b

29. isNaN 和 Number.isNaN 的區(qū)別?

  1. isNaN 在調(diào)用時(shí)會(huì)將傳入的參數(shù)轉(zhuǎn)換為數(shù)字類(lèi)型,所以非數(shù)字傳入也有可能返回 true
  2. Number.isNaN 首先會(huì)判斷傳入的參數(shù)是否為數(shù)字,如果為非數(shù)字,直接返回 false

30. js 垃圾回收機(jī)制

  1. 極客時(shí)間-垃圾是如何自動(dòng)回收的
  2. 垃圾回收

31. 實(shí)現(xiàn)圖片懶加載

<div class="img-area">
  <img class="my-pic" alt="loading" data-src="./img/img1.png" />
</div>
//圖片什么時(shí)候出現(xiàn)在可視區(qū)域內(nèi)
function isInSight(el) {
  const rect = el.getBoundingClientRect()
  //這里我們只考慮向下滾動(dòng)
  const clientHeight = window.innerHeight
  // 這里加50為了讓其提前加載
  return rect.top <= clientHeight + 50
}
//data-src的屬性值代替src屬性值
function loadImg(el) {
  if (!el.src) {
    const source = el.dataset.src
    el.src = source
  }
}
let index = 0
function checkImgs() {
  const imgs = document.querySelectorAll('.my-pic')
  for (let i = index; i < imgs.length; i++) {
    if (isInSight(imgs[i])) {
      loadImg(imgs[i])
      index = i
    }
  }
}

Element.getBoundingClientRect

Tips:Element.getBoundingClientRect 方法返回一個(gè) rect 對(duì)象,提供當(dāng)前元素節(jié)點(diǎn)的大小、位置等信息,基本上就是 CSS 盒狀模型的所有信息,如下:

  • x:元素左上角相對(duì)于視口的橫坐標(biāo)
  • y:元素左上角相對(duì)于視口的縱坐標(biāo)
  • height:元素高度
  • width:元素寬度
  • left:元素左上角相對(duì)于視口的橫坐標(biāo),與 x 屬性相等
  • right:元素右邊界相對(duì)于視口的橫坐標(biāo)(等于 x + width)
  • top:元素頂部相對(duì)于視口的縱坐標(biāo),與 y 屬性相等
  • bottom:元素底部相對(duì)于視口的縱坐標(biāo)(等于 y + height)

32. 實(shí)現(xiàn) thunk

將數(shù)組(array)拆分成多個(gè) size 長(zhǎng)度的區(qū)塊,并將這些區(qū)塊組成一個(gè)新數(shù)組。 如果 array 無(wú)法被分割成全部等長(zhǎng)的區(qū)塊,那么最后剩余的元素將組成一個(gè)區(qū)塊。

例如:

chunk(['a', 'b', 'c', 'd'], 2)
// => [['a', 'b'], ['c', 'd']]

chunk(['a', 'b', 'c', 'd'], 3)
// => [['a', 'b', 'c'], ['d']]

方法1

function chunk(array, size = 1) {
  const length = array == null ? 0 : array.length
  let start = 0

  if (!length || size < 1) return []

  const result = []

  while (start < length) {
    result.push(array.slice(start, (start += size)))
  }

  return result
}

方法2

function chunk(arr, limit) {
  if (!arr.length || limit < 1) return []

  return arr.reduce(
    (pre, cur) => {
      const temp = pre[pre.length - 1]
      if (temp.length < limit) {
        temp.push(cur)
      } else {
        pre.push([cur])
      }
      return pre
    },
    [[]]
  )
}

33. 實(shí)現(xiàn)render函數(shù) 將VNode轉(zhuǎn)成真是VNode,并插入文檔

VNode

const VNode = {
    tagName: 'ul',
    props: {
      class: 'list'
    },
    children: [
      {
        tagName: 'li',
        props: {
          class: 'item'
        },
        children: ['item1']
      },
      {
        tagName: 'li',
        props: {
          style: 'color: red'
        },
        children: ['item2']
      },
      {
        tagName: 'li',
        children: ['item3'],
        props: {
          style: 'cursor: pointer'
        },
        on: {
          click: () => {
            console.log('click item3')
          }
        }
      }
    ]
  }

實(shí)現(xiàn)如下:

 <div id="app"></div>

  // 元素節(jié)點(diǎn)
  function createEle(tag, props = {}, on = {}) {
    const ele = document.createElement(tag)
    const keys = Object.keys(props)
    const events = Object.keys(on)

    keys.length && keys.forEach(key => {
      ele.setAttribute(key, props[key])
    })

    events.length && events.forEach(event => {
      ele.addEventListener(event, on[event])
    })

    return ele
  }

  // 文本節(jié)點(diǎn)
  function createTextNode(text) {
    return document.createTextNode(text)
  }

  function render(VNode) {
    const { tagName, props, children, on } = VNode
    const ele = createEle(tagName, props, on)

    if (children.length) {
      const fragment = document.createDocumentFragment()

      children.forEach(child => {
        if (typeof child === 'object' && child !== null) {
          fragment.appendChild(render(child))
        } else {
          fragment.appendChild(createTextNode(child))
        }
      })
      ele.appendChild(fragment)
    }

    return ele
  }
  document.querySelector('#app').appendChild(render(VNode))

34. (鏈?zhǔn)秸{(diào)用)要求設(shè)計(jì) LazyMan 類(lèi),實(shí)現(xiàn)以下功能

new LazyMan('Tony')
// Hi I am Tony

new LazyMan('Tony').sleep(10).eat('lunch')
// Hi I am Tony
// 等待了10秒...
// I am eating lunch

new LazyMan('Tony').eat('lunch').sleep(10).eat('dinner')
// Hi I am Tony
// I am eating lunch
// 等待了10秒...
// I am eating diner

new LazyMan('Tony').eat('lunch').eat('dinner').sleepFirst(5).sleep(10).eat('junk food')
// Hi I am Tony
// 等待了5秒...
// I am eating lunch
// I am eating dinner
// 等待了10秒...
// I am eating junk food

35. 實(shí)現(xiàn) Object.is()

比較兩個(gè)值是否相等。

在這里NaN等于NaN,+0不等于-0。

+0 === -0 //true
NaN === NaN // false

Object.is(+0, -0) // false
Object.is(NaN, NaN) // true

實(shí)現(xiàn)思路
如果兩個(gè)值相等,就要進(jìn)一步排除0的情況;如果不相等就需要排除NaN的情況。

Object.defineProperty(Object, 'is', {
  value(x, y) {
    if (x === y) {
      // 針對(duì)+0 不等于 -0的情況
      return x !== 0 || 1 / x === 1 / y
    }
    // 針對(duì)NaN的情況
    return x !== x && y !== y
  },
  configurable: true,
  enumerable: false,
  writable: true
})

36. a 在什么情況下會(huì)打印 1?

 const a = {
    i: 1,
    toString() {
      return a.i++
    }
  }
  
  // 進(jìn)行隱式轉(zhuǎn)換 Object == Primitive,需要Object轉(zhuǎn)為Primitive(具體通過(guò)valueOf和toString方法)
  if (a == 1 && a == 2 && a == 3) {
    console.log(1)
  }

  const a = [1, 2, 3]
  // 移除第一個(gè)元素,返回值為移除后的元素
  a.toString = a.shift 
 // 對(duì)象和數(shù)字比較,會(huì)調(diào)用對(duì)象的toString方法
  if (a == 1 && a == 2 && a == 3) {
    console.log(1)
  }

Vue

1. vue

1. vue響應(yīng)式和依賴(lài)收集

2. nextTick 原理


let timerFunc
const noop = () => {}

// 有、且是原生Promise
if (typeof Promise !== 'undefined' && isNative(Promise)) { // Promise
  const p = Promise.resolve()
  timerFunc = () => {
    p.then(flushCallbacks)
    if (isIOS) setTimeout(noop)
  }
  isUsingMicroTask = true
} else if (!isIE && typeof MutationObserver !== 'undefined' && (
  isNative(MutationObserver) ||
  // PhantomJS and iOS 7.x
  MutationObserver.toString() === '[object MutationObserverConstructor]'
)) {
  let counter = 1
  const observer = new MutationObserver(flushCallbacks)
  const textNode = document.createTextNode(String(counter))
  observer.observe(textNode, {
    characterData: true
  })
  timerFunc = () => {
    counter = (counter + 1) % 2
    textNode.data = String(counter)
  }
  isUsingMicroTask = true
} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
  
  timerFunc = () => {
    setImmediate(flushCallbacks)
  }
} else {
  timerFunc = () => {
    setTimeout(flushCallbacks, 0)
  }
}


// 傳入回調(diào)和返回Promise實(shí)例只能二選一
export function nextTick (cb?: Function, ctx?: Object) {
  let _resolve

  callbacks.push(() => {
    // 如果傳入了回調(diào),則執(zhí)行回調(diào)。反之執(zhí)行Promise的resolve方法
    if (cb) {
      try {
        cb.call(ctx)
      } catch (e) {
        handleError(e, ctx, 'nextTick')
      }
    } else if (_resolve) {
      _resolve(ctx) 
    }
  })
  if (!pending) {
    pending = true
    timerFunc() // 該函數(shù)內(nèi)部的flushCallbacks是異步的,所以不會(huì)阻塞下面的流程
  }
  // $flow-disable-line
  // 如果沒(méi)有傳入回調(diào),并且支持Promise的話(huà),返回一個(gè)Promise的實(shí)例
  if (!cb && typeof Promise !== 'undefined') {
    return new Promise(resolve => {
      _resolve = resolve
    })
  }
}

3. keep-alive 原理

4. key 的作用

  1. key的作用主要是為了更高效的更新虛擬DOM。
  2. vue在patch過(guò)程中判斷兩個(gè)節(jié)點(diǎn)是否是相同節(jié)點(diǎn)的一個(gè)必要條件就是key。在渲染一組列表時(shí),key往往是唯一標(biāo)識(shí),所以如果不定義key的話(huà),vue只能認(rèn)為比較的兩個(gè)節(jié)點(diǎn)是同一個(gè),哪怕它們實(shí)際上不是,這導(dǎo)致了頻繁更新元素,使得整個(gè)patch過(guò)程比較低效,影響性能。
  3. 實(shí)際使用中在渲染一組列表時(shí)key必須設(shè)置,而且必須是唯一標(biāo)識(shí),應(yīng)該避免使用數(shù)組索引作為key,這可能導(dǎo)致一些隱蔽的bug;
  4. vue中在使用相同標(biāo)簽元素過(guò)渡切換時(shí),也會(huì)使用key屬性,其目的也是為了讓vue可以區(qū)分它們,否則vue只會(huì)替換其內(nèi)部屬性而不會(huì)觸發(fā)過(guò)渡效果。

從源碼中可以知道,vue判斷兩個(gè)節(jié)點(diǎn)是否相同時(shí)主要判斷兩者的key和元素類(lèi)型等,因此如果不設(shè)置key,它的值就是undefined,則可能永遠(yuǎn)認(rèn)為這是兩個(gè)相同節(jié)點(diǎn),只能去做更新操作,這造成了大量的dom更新操作,明顯是不可取的。

5. VNode有哪些好處?

  1. 優(yōu)化性能 。DOM 操作是比較耗時(shí)的,對(duì)于大量、頻繁的 DOM 操作,如果先在 JavaScript 中模擬進(jìn)行,然后再通過(guò)計(jì)算比對(duì),找到真正需要更新的節(jié)點(diǎn),這樣就有可能減少不必要的 DOM 操作,從而提升渲染性能。
  2. 跨平臺(tái) 。由于虛擬 DOM 以 JavaScript 對(duì)象為基礎(chǔ),所以可根據(jù)不同的運(yùn)行環(huán)境進(jìn)行代碼轉(zhuǎn)換(比如瀏覽器、服務(wù)端、原生應(yīng)用等),這使得它具有了跨平臺(tái)的能力。

一定比真實(shí)DOM的性能高嗎?
并不是所有的 DOM 操作都能通過(guò)虛擬 DOM 提升性能,比如單次刪除某個(gè)節(jié)點(diǎn),直接操作 DOM 肯定比虛擬 DOM 計(jì)算比對(duì)之后再刪除要快??傮w而言, 虛擬 DOM 提升了 DOM 操作的性能下限,降低了 DOM 操作的性能上限。 所以會(huì)看到一些對(duì)渲染性能要求比較高的場(chǎng)景,比如在線(xiàn)文檔、表格編輯,還是會(huì)使用原生 DOM 操作。

6. 組件通信

  1. 父?jìng)髯?props
  2. 子傳父 emit/on
  3. 事件總線(xiàn) eventBus
  4. 跨組件之間 provide/inject 常見(jiàn)于組件庫(kù)的封裝,實(shí)際項(xiàng)目很少會(huì)用到
  5. parent獲取父組件實(shí)例children/ref獲取子組件實(shí)例
  6. attrs/listeners
  7. vuex

7. 生命周期

8. diff 算法

9. vue 中優(yōu)化策略

  1. 路由懶加載
  2. keep-alive
  3. 頻繁切換顯隱式的使用v-show
  4. 靜態(tài)列表通過(guò)Object.freeze()不開(kāi)啟響應(yīng)式
  5. 無(wú)狀態(tài)組件使用函數(shù)式組件
  6. 長(zhǎng)列表使用虛擬列表
  7. 事件代理
  8. 圖片懶加載
  9. 三方組件按需引入
  10. SSR

vue-interview

2. vuex

1. actions 和 mutations 區(qū)別

  1. 必須通過(guò)提交 mutation 的方式修改 state
  2. mutation 中只可以做同步操作
  3. action 中可同步可異步

2. vuex 原理

Vuex框架原理與源碼分析

3. vuex 如何實(shí)現(xiàn)數(shù)據(jù)的響應(yīng)式

new Vue({
  data: state
})

4. mapState 實(shí)現(xiàn)方式

3. vue-router

1. 導(dǎo)航守衛(wèi)

  1. 全局守衛(wèi)

    1. beforeEach 全局前置守衛(wèi)
    2. beforeResolve 全局解析守衛(wèi)
    3. afterEach 全局后置
  2. 路由獨(dú)享守衛(wèi)

    1. beforeEnter
  3. 組件守衛(wèi)

    1. beforeRouteEnter
    2. beforeRouteUpdate
    3. beforeRouteLeave

2. 模式

  1. hash
  2. history

3. 原理

4. vue3

1、vue2 和 vue3 響應(yīng)式原理對(duì)比,及具體實(shí)現(xiàn)思路

vue2響應(yīng)式的缺陷

  1. 初始化時(shí)需要遍歷對(duì)象所有key,如果對(duì)象層級(jí)較深,性能不好
  2. 不能監(jiān)聽(tīng)數(shù)組的變化,需要重寫(xiě)數(shù)組的變異方法
  3. 通知更新過(guò)程需要維護(hù)大量dep實(shí)例和watcher實(shí)例,額外占用內(nèi)存較多
  4. 動(dòng)態(tài)新增、刪除對(duì)象屬性無(wú)法攔截,只能用特定set/delete api代替
  5. 不支持新的Map、Set等數(shù)據(jù)結(jié)構(gòu)

vue3使用Proxy

  1. 可以同時(shí)支持object和array
  2. 動(dòng)態(tài)屬性增、刪都可以攔截,新增數(shù)據(jù)結(jié)構(gòu)均支持
  3. 對(duì)象嵌套屬性運(yùn)行時(shí)遞歸,也就是說(shuō)只有用到時(shí)才代理,也不需要維護(hù)特別多的依賴(lài)關(guān)系,極大的降低性能
    缺點(diǎn)就是兼容性問(wèn)題,因?yàn)閜roxy是js引擎提供的API,所以無(wú)法完全polyfill。

2、vue3 做了哪些優(yōu)化

  1. proxy
  2. 靜態(tài)節(jié)點(diǎn)標(biāo)記
  3. VNode 重構(gòu)

3、composition API 有哪些特性

setup()

React

1. React

  1. React 如何區(qū)分 class 和 functional?
  2. 為什么要寫(xiě) super(props)?
  3. 為什么在編寫(xiě)組件時(shí)沒(méi)有用到 React 還要引入 React?
  4. JSX 原理
  5. setState() 是同步還是異步的?
  6. 高階組件 HOC
    1. 解決了什么問(wèn)題
  7. render props
  8. HOOKS
    1. useEffect 如何實(shí)現(xiàn) class 組件的 componentDidMounted 和 componentDidUnMounted
    2. useCallback() 和 useMemo() 的區(qū)別

2. Redux

  1. 實(shí)現(xiàn)簡(jiǎn)易版 redux
function createStore(reducer) {
  let state = reducer(undefined, {})
  let callback = []

  return {
    getState() {
      return state
    },
    dispatch(action) {
      state = reducer(state, action)
      callback.forEach(fn => fn())
    },
    subscribe(handler) {
      callback.push(handler)
    },
    unsubscribe(l) {
      callback = callback.filter(fn => fn !== l)
    }
  }
}
  1. 實(shí)現(xiàn) bindActionCreators
/**
 * @param {Object | Function} actionCreators (Function: action creator,Object:value為action creator的對(duì)象)
 * @param {Function} dispatch 由store提供的dispatch函數(shù)
 * @returns {Object | Function}
 */

const bindActionCreators = (actionCreators, dispatch) => {
  if (typeof actionCreators === 'function') {
    return (...args) => {
      dispatch(actionHandle(...args))
    }
  }
  return Object.keys(actionCreators).reduce((key, creators) => {
    creators[key] = (...args) => dispatch(creators[key](...args))
    return handle
  }, {})
}
  1. 實(shí)現(xiàn) combineReducers
const combineReducers = reducers => {
  return (state = {}, action) => {
    return Object.keys(reducers).reduce((nextState, key) => {
      nextState[key] = reducers[key](state[key], action)
      return nextState
    }, {})
  }
}
  1. redux 中間件實(shí)現(xiàn)原理

中間件本質(zhì)上就是一個(gè)函數(shù)。 因?yàn)?reducer 為純函數(shù),且派發(fā)出 dispatch 之后,redux 內(nèi)部會(huì)立即調(diào)用 reducer,所以只能在發(fā)出 action 和執(zhí)行 reducer 之間做操作。

// 保存store原本的dispatch方法
const dispatch = store.dispatch

// 重寫(xiě)store.dispatch
store.dispatch = action => {
  setTimeout(() => {
    console.log('異步執(zhí)行完成,派發(fā)action')
    dispatch(action)
  }, 2000)
}

store.dispatch({ type: 'ADD' })
  1. Redux從設(shè)計(jì)到源碼

Node

1. Node

  1. http
  2. fs
  3. path.join() 和 path.resolve()區(qū)別?
  4. 啟動(dòng)一個(gè) http 服務(wù)

2. Express

3. Koa2

Koa 總結(jié)

瀏覽器

1. 輸入 URL 到頁(yè)面展示做了什么

  1. 用戶(hù)輸入關(guān)鍵詞,地址欄判斷是搜索內(nèi)容還是 url 地址。
    如果是搜索內(nèi)容,會(huì)使用瀏覽器默認(rèn)搜索引擎加上搜索內(nèi)容合成 url;
    如果是域名會(huì)加上協(xié)議(如 https)合成完整的 url。

  2. 然后按下回車(chē)。瀏覽器進(jìn)程通過(guò) IPC(進(jìn)程間通信)把 url 傳給網(wǎng)絡(luò)進(jìn)程(網(wǎng)絡(luò)進(jìn)程接收到 url 才發(fā)起真正的網(wǎng)絡(luò)請(qǐng)求)。

  3. 網(wǎng)絡(luò)進(jìn)程接收到 url 后,先查找有沒(méi)有緩存。
    有緩存并且緩存還在生存期內(nèi),直接返回緩存的資源。
    緩存過(guò)期或沒(méi)有緩存。(進(jìn)入真正的網(wǎng)絡(luò)請(qǐng)求)。如果緩存過(guò)期了,瀏覽器則會(huì)繼續(xù)發(fā)起網(wǎng)絡(luò)請(qǐng)求,并且在 HTTP 請(qǐng)求頭中帶上If-None-Match: "XXX"

首先獲取域名的 IP,系統(tǒng)會(huì)首先自動(dòng)從 hosts 文件(DNS 緩存)中尋找域名對(duì)應(yīng)的 IP 地址,一旦找到,和服務(wù)器建立 TCP 連接;如果沒(méi)有找到,則系統(tǒng)會(huì)將網(wǎng)址提交 DNS 域名解析服務(wù)器進(jìn)行 IP 地址的解析。

  1. 利用 IP 地址和服務(wù)器建立 TCP 連接(3 次握手)。

  2. 建立連接后,瀏覽器網(wǎng)絡(luò)進(jìn)程構(gòu)建數(shù)據(jù)包(包含請(qǐng)求行,請(qǐng)求頭,請(qǐng)求正文,并把該域名相關(guān) Cookie 等數(shù)據(jù)附加到請(qǐng)求頭),然后向服務(wù)器發(fā)送請(qǐng)求消息。

  3. 服務(wù)器接收到消息后根據(jù)請(qǐng)求信息構(gòu)建響應(yīng)數(shù)據(jù)(包括響應(yīng)行,響應(yīng)頭,響應(yīng)正文),然后發(fā)送回網(wǎng)絡(luò)進(jìn)程。

  4. 網(wǎng)絡(luò)進(jìn)程接收到響應(yīng)數(shù)據(jù)后進(jìn)行解析。

如果發(fā)現(xiàn)響應(yīng)行的返回的狀態(tài)碼為 301,302,說(shuō)明服務(wù)器需要瀏覽器重定向到其他 URL,這時(shí)網(wǎng)絡(luò)進(jìn)程會(huì)找響應(yīng)頭中的 Location 字段的 URL,重新發(fā)起 HTTP 或者 HTTPS 請(qǐng)求。
如果返回的狀態(tài)碼為 200,說(shuō)明服務(wù)器返回了數(shù)據(jù)。
如果狀態(tài)碼是 304,則說(shuō)明緩存內(nèi)容在服務(wù)端沒(méi)有更新,服務(wù)器不會(huì)再返回內(nèi)容,讓瀏覽器從緩存中讀取。

  1. 網(wǎng)絡(luò)進(jìn)程將響應(yīng)頭和響應(yīng)行傳給瀏覽器進(jìn)程,瀏覽器進(jìn)程根據(jù)響應(yīng)頭中的 Content-Type 告訴瀏覽器服務(wù)器返回的數(shù)據(jù)類(lèi)型。如果返回的類(lèi)型是 text/html,則告訴瀏覽器返回的是 HTML,如果是 application/octet-stream 則為下載類(lèi)型,那么請(qǐng)求會(huì)交給瀏覽器的下載管理器,同時(shí) URL 的導(dǎo)航到此結(jié)束,如果是 HTML,那么瀏覽器會(huì)繼續(xù)進(jìn)行導(dǎo)航流程。(導(dǎo)航:用戶(hù)發(fā)出 URL 請(qǐng)求到頁(yè)面開(kāi)始解析的這個(gè)過(guò)程,就叫做導(dǎo)航)

  2. 瀏覽器進(jìn)程接收到網(wǎng)絡(luò)進(jìn)程的響應(yīng)頭數(shù)據(jù)后,向渲染進(jìn)程發(fā)出“提交導(dǎo)航”(CommitNavigation)的消息(帶著響應(yīng)頭等消息)。

  3. 渲染進(jìn)程接收到提交導(dǎo)航的消息后,準(zhǔn)備渲染進(jìn)程。創(chuàng)建新的渲染進(jìn)程還是復(fù)用,規(guī)則如下:
    如果是新打開(kāi)的頁(yè)面,會(huì)單獨(dú)使用一個(gè)渲染進(jìn)程;
    如果是A頁(yè)面打開(kāi)“同一站點(diǎn)”的B頁(yè)面,則會(huì)復(fù)用A頁(yè)面的渲染進(jìn)程;
    如果不是“同一站點(diǎn)”,則單獨(dú)創(chuàng)建新的渲染進(jìn)程;

  4. 渲染進(jìn)程準(zhǔn)備好后便和網(wǎng)絡(luò)進(jìn)程建立數(shù)據(jù)傳輸?shù)摹肮艿馈薄?/p>

  5. 等文檔數(shù)據(jù)傳輸完成后,渲染進(jìn)程會(huì)返回“確認(rèn)提交”的消息給瀏覽器進(jìn)程。意思是告訴瀏覽器進(jìn)程我已經(jīng)準(zhǔn)備好接收網(wǎng)絡(luò)進(jìn)程的數(shù)據(jù)和解析頁(yè)面數(shù)據(jù)了。

  6. 瀏覽器進(jìn)程接收到“確認(rèn)提交”消息后移除舊的文檔,然后更新瀏覽器界面,包括 web 頁(yè)面(空白頁(yè))、導(dǎo)航按鈕、URL 地址欄、網(wǎng)絡(luò)安全狀態(tài)。瀏覽器的加載按鈕還是加載中狀態(tài)。至此,導(dǎo)航流程就完成了。接下來(lái)就是解析渲染階段了。

    1. 構(gòu)建 DOM
      1. 輸入 html --> 解析器 ---> DOM Tree
    2. 樣式計(jì)算
      1. css 來(lái)源:行內(nèi)、style、外部(link 引入)
      2. css ---> styleSheets
      3. css 轉(zhuǎn)成 styleSheets
      4. 屬性標(biāo)準(zhǔn)化
      5. 計(jì)算 DOM 樹(shù)中每個(gè)節(jié)點(diǎn)的具體樣式 (繼承、層疊)
    3. 布局 layout DOM 樹(shù)&styleSheets ---> 布局樹(shù)
      1. 創(chuàng)建布局樹(shù)(只包含可見(jiàn)元素)
      2. 布局計(jì)算
    4. 分層 特定節(jié)點(diǎn)生成專(zhuān)用圖層,并生成圖層樹(shù)

    圖層生成的條件:

    1. 擁有層疊上下文屬性的元素
    2. 需要剪裁的元素
    1. 在主線(xiàn)程上生成圖層的繪制列表,將繪制列表提交給合成線(xiàn)程

    2. 柵格化

      1. 主線(xiàn)程將繪制列表提交給合成線(xiàn)程,合成線(xiàn)程將圖層分塊,生成圖塊(圖塊是柵格化的最小單位)
      2. 光柵化線(xiàn)程池和GPU生成位圖
      3. 圖塊被光柵化完成后,合成線(xiàn)程就會(huì)生成一個(gè)繪制圖塊的命令——“DrawQuad”,然后將該命令提交給瀏覽器進(jìn)程。
    3. 合成

      1. 合成線(xiàn)程提交命令 ---> 瀏覽器進(jìn)程 ---> viz 組件接收命令 ---> 合成圖片 ---> 顯示
      2. 合成的圖層會(huì)被提交給瀏覽器進(jìn)程,瀏覽器進(jìn)程里會(huì)執(zhí)行顯示合成(Display Compositor),也就是將所有的圖層合成為可以顯示的頁(yè)面圖片。 最終顯示器顯示的就是瀏覽器進(jìn)程中合成的頁(yè)面圖片。
    4. 渲染進(jìn)程開(kāi)始頁(yè)面解析和加載子資源(邊下載邊解析),一旦資源加載、渲染完畢,渲染進(jìn)程會(huì)發(fā)送一個(gè)消息給瀏覽器進(jìn)程,瀏覽器接收到這個(gè)消息后會(huì)停止標(biāo)簽圖標(biāo)的加載動(dòng)畫(huà)。

至此,一個(gè)完整的頁(yè)面形成了。

2. 重繪和重排

為什么 DOM 操作耗費(fèi)性能和時(shí)間?

  1. 渲染引擎和 js 引擎切換,(俗稱(chēng)上下文切換)耗費(fèi)性能。
  2. 重繪/重排(元素及樣式變化引起的再次渲染),而且重排耗時(shí)明顯高于重繪
  3. 重排一定引起重繪,而重繪不一定引起重排

引起重繪的操作:

  1. 修改元素樣式,比如顏色、bgc、border-color

引起重排的操作:

  1. 內(nèi)容改變
  2. 增、刪 DOM
  3. DOM 幾何屬性變化,如:寬高、位置、邊框、padding、margin
  4. DOM 樹(shù)結(jié)構(gòu)發(fā)生改變
  5. 瀏覽器窗口尺寸改變
  6. 頁(yè)面一開(kāi)始渲染(不可避免的)
  7. 獲取某些布局屬性時(shí)。當(dāng)獲取一些屬性值時(shí),瀏覽器為了保證獲取到正確的值也會(huì)引起重排。如:
    1. offsetTop,offsetLeft,offsetHeight,offsetWidth
    2. scrollTop,scrollWidth,scrollLeft,scrollHeight
    3. clientWidth,clientHeight,clientLeft,clientTop
    4. getComputedStyle()
    5. getBoundingClientRect()
    6. 更多參考這里

如何減少重排和重繪?

  1. 批量操作 DOM
    1. 在循環(huán)外操作元素
    2. 拼接字符串 --> innerHTML
    3. DocumentFragment 文檔片段
    4. 緩存元素集合
  2. 復(fù)雜動(dòng)畫(huà),使用絕對(duì)定位讓其脫離文檔流
  3. CSS3 硬件加速(GPU 加速)
    1. transform
    2. opacity
    3. filters
    4. will-change

硬件加速

  • 使用硬件加速可以讓 transform、opacity、filters 不會(huì)引起重繪和回流,但是對(duì)于 bgc 這些屬性還是會(huì)引起重繪
  • 硬件加速不可濫用,因?yàn)闀?huì)占用內(nèi)存較大,會(huì)影響性能

CSS 加載阻塞情況

  1. CSS 加載不會(huì)阻塞 DOM 樹(shù)生成
  2. CSS 加載會(huì)阻塞 DOM 樹(shù)的渲染。主要是瀏覽器出于性能考慮,避免渲染完成后又有樣式變動(dòng),造成回流和重繪
  3. CSS 加載會(huì)阻塞后面 js 的執(zhí)行

縮短白屏?xí)r間,盡可能加快 CSS 加載速度

  1. 使用 CDN
  2. 壓縮 CSS
  3. 合理使用緩存
  4. 減少 HTTP 請(qǐng)求數(shù)(合并 CSS)

原理:

  1. HTML 解析和 CSS 解析是并行的過(guò)程,所以 CSS 加載不會(huì)阻塞 DOM 樹(shù)生成
  2. render Tree 的形成依賴(lài)于 DOM Tree 和 CSSOM Tree,所以必須等到 CSS 加載完成才渲染
  3. 因?yàn)?js 可能會(huì)操作 DOM 節(jié)點(diǎn)和 CSS 樣式,因此樣式表會(huì)在加載完畢后執(zhí)行后面的 js

跨域

原因:
瀏覽器同源策略的限制。

解決方案:

  1. jsonp
  2. cors
  3. Iframe
  4. postMessage
  5. node middleware
  6. nginx
  7. webSocket

網(wǎng)絡(luò)

應(yīng)用層

1. HTTP

1.1 狀態(tài)碼

  1. 1xx 收到請(qǐng)求,需要請(qǐng)求繼續(xù)執(zhí)行操作
  2. 2xx 成功
    1. 200 ok
    2. 204 沒(méi)有資源可返回
    3. 206 返回部分資源,(請(qǐng)求范圍資源)比如:音視頻文件
  3. 3xx 重定向、瀏覽器需要執(zhí)行某些特殊處理以完成正常請(qǐng)求
    1. 301 永久重定向 表示舊地址資源被永久地移除了(這個(gè)資源不可訪問(wèn)了),搜索引擎在抓取新內(nèi)容的同時(shí)也將舊的網(wǎng)址交換為重定向之后的網(wǎng)址
    2. 302 臨時(shí)重定向 表示舊地址 A 的資源還在(仍然可以訪問(wèn)),這個(gè)重定向只是臨時(shí)地從舊地址 A 跳轉(zhuǎn)到地址 B,搜索引擎會(huì)抓取新的內(nèi)容而保存舊的網(wǎng)址
    3. 304 協(xié)商緩存
  4. 4xx 客戶(hù)度錯(cuò)誤
    1. 400 請(qǐng)求報(bào)文中存在語(yǔ)法錯(cuò)誤
    2. 401 表示發(fā)送的請(qǐng)求需要有通過(guò) HTTP 認(rèn)證的認(rèn)證信息
    3. 403 Forbidden(禁止) 請(qǐng)求被服務(wù)器拒絕了
    4. 404 Not Found
    5. 405 Method Not Allowed(不允許使用的方法)
    6. 406 請(qǐng)求的 content-Type 和相應(yīng)的 content-type 不一致。說(shuō)白了就是后臺(tái)返回的資源前臺(tái)無(wú)法解析
    7. 416 所請(qǐng)求的范圍無(wú)法滿(mǎn)足(讀取文件時(shí)設(shè)置的 Range 有誤造成的)
  5. 5xx 服務(wù)端錯(cuò)誤
    1. 500 表示服務(wù)器在執(zhí)行請(qǐng)求時(shí)發(fā)生了錯(cuò)誤
    2. 503 表示服務(wù)器暫時(shí)處于超負(fù)載或正在進(jìn)行停機(jī)維護(hù)狀態(tài),現(xiàn)在無(wú)法處理請(qǐng)求

1.2 request

  1. 請(qǐng)求行 method、URI、HTTP 版本
  2. 請(qǐng)求頭
  3. 請(qǐng)求體

1.3 response

  1. 狀態(tài)行 狀態(tài)碼、原因短語(yǔ)、服務(wù)器HTTP版本
  2. 響應(yīng)頭
  3. 響應(yīng)體

1.4 http 頭

connection: keep-alive 長(zhǎng)連接
google chrome 默認(rèn)同時(shí)最多可以建立6個(gè)TCP連接,TCP上http傳輸是串行的。(HTTP1.1)
HTTP2 只建立一個(gè)TCP,HTTP并行,理論上沒(méi)有數(shù)量限制。

http 緩存

http緩存分為強(qiáng)緩存和協(xié)商緩存。詳細(xì)內(nèi)容查看 HTTP 緩存

2. FTP

文件傳輸協(xié)議

3. DNS

域名解析系統(tǒng)。域名和 IP 的映射,域名查 IP,IP 反查域名。

2. 傳輸層

1. TCP

面向連接的、可靠的、字節(jié)流服務(wù)。

  1. 面向連接 ---> TCP 連接
  2. 可靠的
    1. 具有重發(fā)機(jī)制,不會(huì)丟包
    2. TCP 三次握手
    3. 數(shù)據(jù)包上標(biāo)記序號(hào),到達(dá)接收方可以重組
  3. 字節(jié)流服務(wù) --> 大數(shù)據(jù)包切割成報(bào)文段的小數(shù)據(jù)包

缺陷:
傳輸速度慢,不如 UDP

2. UDP

  1. 無(wú)連接的
  2. 易丟包
  3. 無(wú)序無(wú)法重組
  4. 傳輸速度快
  5. 場(chǎng)景:直播、視頻會(huì)議等

3. 網(wǎng)絡(luò)層

IP 協(xié)議 負(fù)責(zé)傳輸

4. 鏈路層

操作系統(tǒng)、顯卡等物理器件

HTTPS

HTTP 缺點(diǎn):

1. 明文傳輸,不加密,內(nèi)容容易被竊聽(tīng)

解決:

  1. 內(nèi)容(報(bào)文)加密,仍不可靠,容易被篡改
  2. SSL 通信加密 建立安全的通信線(xiàn)路,http 在該線(xiàn)路上傳輸

2. 無(wú)法驗(yàn)證身份,容易被偽裝(通信雙方無(wú)法確認(rèn)對(duì)方身份)

解決:

SSL/TLS 證書(shū)認(rèn)證

3. 無(wú)法保證內(nèi)容的完整性,容易被篡改(明文傳輸,無(wú)法驗(yàn)證身份,導(dǎo)致內(nèi)容容易被篡改)

解決:

SSL/TLS

HTTPS

HTTP + 通信加密 + 身份認(rèn)證 + 內(nèi)容完整性保護(hù) = HTTPS

SSL/TLS 不僅提供了通信加密,還提供了證書(shū)認(rèn)證,及內(nèi)容完整性保護(hù)。

缺點(diǎn):

使用 SSL/TLS 處理速度變慢

  1. SSL/TLS 建立連接耗費(fèi)時(shí)間
  2. 通信慢: SSL/TLS 通信部分消耗網(wǎng)絡(luò)資源
  3. 通信雙方進(jìn)行加解密處理,消耗大量的 CPU 和內(nèi)存等資源

HTTP1.1

HTTP/1.1 為網(wǎng)絡(luò)效率做了大量的優(yōu)化,最核心的有如下三種方式:

  1. 增加了持久連接
  2. 瀏覽器為每個(gè)域名最多同時(shí)維護(hù) 6 個(gè) TCP 持久連接
  3. 使用 CDN 的實(shí)現(xiàn)域名分片機(jī)制

HTTP/1.1 的主要問(wèn)題

對(duì)帶寬的利用率不理想

原因:

  1. TCP 的慢啟動(dòng)
  2. 同時(shí)開(kāi)啟了多條 TCP 連接,那么這些連接會(huì)競(jìng)爭(zhēng)固定的帶寬
  3. HTTP/1.1 隊(duì)頭阻塞的問(wèn)題(一個(gè)TCP同時(shí)可以進(jìn)行一個(gè)HTTP傳輸)

慢啟動(dòng)和 TCP 連接之間相互競(jìng)爭(zhēng)帶寬是由于 TCP 本身的機(jī)制導(dǎo)致的,而隊(duì)頭阻塞是由于 HTTP/1.1 的機(jī)制導(dǎo)致的。

HTTP2 特性

  1. 多路復(fù)用機(jī)制:
  • 一個(gè)域名建立一個(gè) TCP 連接
  • 一個(gè)TCP可以同時(shí)進(jìn)行多個(gè)HTTP的請(qǐng)求和響應(yīng)
  1. 可以設(shè)置請(qǐng)求的優(yōu)先級(jí):在發(fā)送請(qǐng)求時(shí),可以標(biāo)上該請(qǐng)求的優(yōu)先級(jí)
  2. 服務(wù)器推送:提前將靜態(tài)資源推送到客戶(hù)端
  3. 頭部壓縮

算法

1. 排序

1.1 冒泡排序

思路:

雙層遍歷,第二層遍歷,當(dāng)前值和下一個(gè)值比較,根據(jù)大小交換位置 修改的是原數(shù)組
時(shí)間復(fù)雜度 O(n^2) 空間復(fù)雜度 O(1)

function sort(array) {
  for (let i = 0; i < array.length; i++) {
    for (let j = 0; j < array.length - i; j++) {
      const temp = array[j + 1]

      if (array[j] > array[j + 1]) {
        array[j + 1] = array[j]
        array[j] = temp
      }
    }
  }
  return array
}

1.2 快速排序

思路:
選取數(shù)組第一項(xiàng)作為基準(zhǔn)值,從數(shù)組第二項(xiàng)開(kāi)始比較,小的放在左邊數(shù)組,大的放在右邊數(shù)組。
最終結(jié)束時(shí)機(jī)是當(dāng)數(shù)組長(zhǎng)度為 1 時(shí)。最終就是 arr.length 個(gè)長(zhǎng)度為 1 的數(shù)組的合并。
返回的是新數(shù)組。
時(shí)間復(fù)雜度: O(nlogn)

function quickSort(arr) {
  if (arr.length <= 1) return arr

  const base = arr[0]
  const left = []
  const right = []

  for (let i = 1; i < arr.length; i++) {
    const temp = arr[i]

    if (base >= temp) {
      left.push(temp)
    } else {
      right.push(temp)
    }
  }

  return [...quickSort(left), base, ...quickSort(right)]
}

2. 反轉(zhuǎn)數(shù)組

直接修改原數(shù)組

1. 遍歷

function reverse(arr) {
   const mid = arr.length / 2

    for (let i = 0; i < mid; i++) {
      ;[arr[arr.length - 1 - i], arr[i]] = [arr[i], arr[arr.length - 1 - i]]
    }

    return arr
}

2. 借助 reduce

function reverseReduce(arr) {
  return arr.reduce((pre, cur) => [cur, ...pre], [])
}

3. 借助 reduceRight

function reverseReduceRight(arr) {
  return arr.reduceRight((pre, cur) => [...pre, cur], [])
}

3. 二分查找

前提條件:一定是有序數(shù)組。

查找有序數(shù)組中的某一項(xiàng),以下實(shí)現(xiàn)方式假設(shè)數(shù)組是遞增的。

  1. 非遞歸方式
function binarySearch(arr, target) {
  let start = 0
  let end = arr.length - 1
 
  while (start <= end) {
    let mid = Math.floor((start + end) / 2)
    const midVal = arr[mid]

    if (target === midVal) return midVal 
    if (target > midVal) {
      start = mid + 1
    } else {
      end = mid - 1
    }
  }
  return false
 }
}
  1. 遞歸方式
function binary(arr, target, start = 0, end = arr.length - 1) {
  if (start > end) return false
  const mid = Math.floor((start + end) / 2)
  const val = arr[mid]

  if (val === target) return mid
  if (val > target) return binary(arr, target, start, mid - 1)
  return binary(arr, target, mid + 1, end)
}

4. 斐波那契數(shù)列

寫(xiě)一個(gè)函數(shù),輸入 n ,求斐波那契(Fibonacci)數(shù)列的第 n 項(xiàng)。斐波那契數(shù)列的定義如下:

F(0) = 0, F(1) = 1
F(N) = F(N - 1) + F(N - 2), 其中 N > 1.
斐波那契數(shù)列由 0 和 1 開(kāi)始,之后的斐波那契數(shù)就是由之前的兩數(shù)相加而得出。

答案需要取模 1e9+7(1000000007),如計(jì)算初始結(jié)果為:1000000008,請(qǐng)返回 1。

思路:

斐波那契數(shù)是由前兩數(shù)相加而得,需對(duì)初始值 0 和 1 做單獨(dú)判斷。
之后只需做一次 for 循環(huán)遍歷,需要注意得是,為避免時(shí)間復(fù)雜度的消耗,需要緩存前兩數(shù)的結(jié)果,最后按題要求,所得的數(shù)需要經(jīng)過(guò)取模運(yùn)算。

var fib = function (n) {
  if (n <= 1) return n

  // n-1
  let prev1 = 1
  // n-2
  let prev2 = 0

  let sum = 0

  for (let i = 2; i <= n; i++) {
    sum = (prev1 + prev2) % 1000000007
    prev2 = prev1
    prev1 = sum
  }
  return sum
}

5. 青蛙跳臺(tái)階

一只青蛙一次可以跳上 1 級(jí)臺(tái)階,也可以跳上 2 級(jí)臺(tái)階。求該青蛙跳上一個(gè) n 級(jí)的臺(tái)階總共有多少種跳法。

答案需要取模 1e9+7(1000000007),如計(jì)算初始結(jié)果為:1000000008,請(qǐng)返回 1。

示例 1:

輸入:n = 2
輸出:2
示例 2:

輸入:n = 7
輸出:21
示例 3:

輸入:n = 0
輸出:1

思路:

和斐波那契一樣的思路,區(qū)別是斐波那契的 0 項(xiàng)為 0,而爬樓梯的 0 項(xiàng)是 1

空間復(fù)雜度: O(1) 時(shí)間復(fù)雜度: O(n)

function numWays(n) {
  if (n <= 1) return 1

  let a = 1
  let b = 2
  let sum

  for (let i = 3; i <= n; i++) {
    sum = (a + b) % 1000000007

    a = b
    b = sum
  }
  return b
}

遞歸實(shí)現(xiàn)方式

function bar(n) {
  if (n <= 1) return 1
  if (n < 3) return n
  return f(n - 1) + f(n - 2)
}

6. 有序數(shù)組合并 排序

假設(shè)是升序的

function concatSort(a, b) {
let i = 0
    let j = 0
    const arr = []

    while (i < a.length && j < b.length) {
      if (a[i] > b[j]) {
        arr.push(b[j++])
      } else {
        arr.push(a[i++])
      }
    }

    while (i < a.length) {
      arr.push(a[i++])
    }

    while (j < b.length) {
      arr.push(b[j++])
    }

    return arr
}

7. 刪除數(shù)組中的重復(fù)項(xiàng)

給定一個(gè)排序數(shù)組,需要在 原地 刪除重復(fù)出現(xiàn)的元素,使得每個(gè)元素只出現(xiàn)一次,返回移除后數(shù)組的新長(zhǎng)度。
不要使用額外的數(shù)組空間,你必須在 原地 修改輸入數(shù)組 并在使用 O(1) 額外空間的條件下完成。

方法一:雙指針?lè)ǎ熘羔槪?/h3>

思路:
數(shù)組完成排序后,我們可以放置兩個(gè)指針 i 和 j,其中 i 是慢指針,而 j 是快指針。慢指針初始值為 0,快指針初始值為 1.
如果 arr[i] === arr[j],跳過(guò),快指針加 1。反之,慢指針加 1,快指針的值賦值給慢指針。

function removeDuplicates(arr) {
  let i = 0

  for (let j = 1; j < arr.length; j++) {
    if (arr[i] !== arr[j]) {
      arr[++i] = arr[j]
    }
  }

  return i + 1
}

方法二: 計(jì)數(shù)排序思想

思路:

因?yàn)轭}目是要求返回去重后數(shù)組的長(zhǎng)度,并不關(guān)心去重后的數(shù)組,所以找出重復(fù)出現(xiàn)的個(gè)數(shù)即可。

由于數(shù)組是有序排列的,定義一個(gè)變量 count = 0; 從位置 1 開(kāi)始遍歷,判斷當(dāng)前元素是否和上一個(gè)元素相等,如果相等 count+1,反之跳過(guò)。

最后 count 就是所有重復(fù)元素出現(xiàn)的個(gè)數(shù)。那么不重復(fù)元素組成的數(shù)組就是 數(shù)組長(zhǎng)度 - count

function removeItem(arr) {
  let count = 0

  for (let i = 1; i < arr.length; i++) {
    if (arr[i] === arr[i - 1]) ++count
  }

  return arr.length - count
}

8. 組中的第 K 個(gè)最大元素

function findKthLargest(nums, k) {
  // 快速排序
  function sort(nums) {
    if (nums.length <= 1) return nums

    let base = nums[0]
    let left = []
    let right = []

    for (let i = 1; i < nums.length; i++) {
      if (nums[i] > base) {
        right.push(nums[i])
      } else {
        left.push(nums[i])
      }
    }
    return [...sort(left), base, ...sort(right)]
  }
  nums = sort(nums)
  const len = nums.length

  return nums[len - k]
}

9. 兩數(shù)之和

找出數(shù)組中 兩個(gè)元素加起來(lái)等于 target 的元素的索引

1. 雙層循環(huán)

適用情況:對(duì)數(shù)組的排序無(wú)要求,最后返回元素的索引

function twoSum(arr, target) {
   for (let i = 0; i < arr.length; i++) {
    for (let j = i + 1; j < arr.length; j++) {
      if (arr[i] + arr[j] === target) {
        return [i, j]
      }
    }
  }
  return []
}

2. 雙指針對(duì)撞

適用情況: 數(shù)組必須是有序的。

時(shí)間復(fù)雜度 O(n)
空間復(fù)雜度 O(1)

假設(shè)有序遞增數(shù)組

function search(arr, target) {
  let left = 0
  let right = arr.length - 1

  while (left < right) {
    const i = arr[left]
    const j = arr[right]
    if (i + j === target) return [i, j]
    if (i + j > target) {
      right--
    } else {
      left++
    }
  }

  return []
}

10. 二維數(shù)組的查找

在一個(gè) n * m 的二維數(shù)組中,每一行都按照從左到右遞增的順序排序,每一列都按照從上到下遞增的順序排序。請(qǐng)完成一個(gè)函數(shù),輸入這樣的一個(gè)二維數(shù)組和一個(gè)整數(shù),判斷數(shù)組中是否含有該整數(shù)。

示例:

現(xiàn)有矩陣 matrix 如下:

;[
  [1, 4, 7, 11, 15],
  [2, 5, 8, 12, 19],
  [3, 6, 9, 16, 22],
  [10, 13, 14, 17, 24],
  [18, 21, 23, 26, 30]
]

給定 target = 5,返回 true。

給定 target = 20,返回 false。

時(shí)間復(fù)雜度 O(n + m)
空間復(fù)雜度 O(1)

function findNumberIn2DArray(matrix, target) {
  if (matrix.length < 1 || matrix[0].length < 1) return false

  let row = 0
  let col = matrix[0].length - 1

  while (row < matrix.length && col >= 0) {
    const current = matrix[row][col]

    if (current === target) return true
    if (current > target) {
      col--
    } else {
      row++
    }
  }

  return false
}

11. 第一個(gè)只出現(xiàn)一次的字符

在字符串 s 中找出第一個(gè)只出現(xiàn)一次的字符。如果沒(méi)有,返回一個(gè)單空格。 s 只包含小寫(xiě)字母。

示例:

s = "abaccdeff"
返回 "b"

s = ""
返回 " "

1. Set + 正則

function firstUniqChar(s) {
  for (let char of new Set(s)) {
    // 正則匹配變量
    if (s.match(new RegExp(char, 'g')).length === 1) {
      return char
    }
  }
  return ' '
}

2. Map 的 keys 可以保證順序

function search(s) {
  const map = s.split('').reduce((pre, cur) => {
    const temp = pre.get(cur)

    pre.set(cur, temp + 1 || 1)

    return pre
  }, new Map())

  for (let key of map.keys()) {
    if (map.get(key) === 1) return key
  }
  return ' '
}

12. 打印出從 1 到最大的 n 位數(shù)

輸入數(shù)字 n,按順序打印出從 1 到最大的 n 位十進(jìn)制數(shù)。比如輸入 3,則打印出 1、2、3 一直到最大的 3 位數(shù) 999。

示例 1:

輸入: n = 1
輸出: [1,2,3,4,5,6,7,8,9]

function printNumbers(n) {
  const length = Math.pow(10, n) - 1

  return Array.from({ length }, (_, index) => index + 1)
}

13. 數(shù)組中重復(fù)的數(shù)字

找出數(shù)組中重復(fù)的數(shù)字。

在一個(gè)長(zhǎng)度為 n 的數(shù)組 nums 里的所有數(shù)字都在 0 ~ n-1 的范圍內(nèi)。數(shù)組中某些數(shù)字是重復(fù)的,但不知道有幾個(gè)數(shù)字重復(fù)了,也不知道每個(gè)數(shù)字重復(fù)了幾次。請(qǐng)找出數(shù)組中任意一個(gè)重復(fù)的數(shù)字。

示例 1:

輸入:
[2, 3, 1, 0, 2, 5, 3]
輸出:2 或 3

function findRepeatNumber(arr) {
  const set = new Set()

  for (let item of arr) {
    if (set.has(item)) return item
    set.add(item)
  }
}

14. 查找出數(shù)組中出現(xiàn)次數(shù)最多的元素

const arr = [1, 2, 3, 2, 2, 1, 3, 3, 3, 3, 4, 4332, 21, 12, 2, 2, 2, 2, 2]

function maxTime(arr) {
 if (!arr.length) return
  if (arr.length === 1) return arr[0]

  let maxEle = arr[0]
  let maxCount = 0

  arr.reduce((pre, cur) => {
    pre = {
      ...pre,
      [cur]: pre[cur] + 1 || 1,
    }

    if (pre[cur] > maxCount) {
      maxCount = pre[cur]
      maxEle = cur
    }

    return pre
  }, {})

  return maxEle
}
maxTime(arr) // 2

15. 輸出以奇數(shù)結(jié)尾的字符串拼接

題:[任意字符][一位數(shù)字]_,拼接的字符串,輸出以奇數(shù)結(jié)尾的字符串拼接。
輸入:str1_key2_val3_d4_e5
輸出:str1val3e5

let str = 'str1_key2_val3_d4_e5'
  1. charAt()
let arr = str.split('_')
const result = arr.reduce((pre, cur) => {
  if (cur.charAt(cur.length - 1) % 2 !== 0) {
    return pre.concat(cur)
  } else {
    return pre
  }
}, '')
  1. slice()
const result = str.split('_').reduce((pre, cur) => {
  if (cur.slice(-1) % 2) {
    return (pre += cur)
  }
  return pre
}, '')

16. 二維數(shù)組的排列組合

設(shè)計(jì)模式

1. 單例模式

class Person {
  static getInstance() {
    if (!Person.instance) {
      console.log('只創(chuàng)建一次')
      Person.instance = new Person()
    }
    return Person.instance
  }
  constructor(name, age) {}

  // 原型方法
  getSkill() {
    console.log('吃飯')
  }

  // 靜態(tài)方法
  static myWork() {
    console.log('睡覺(jué)')
  }
}

// 單例模式。實(shí)例只創(chuàng)建一次
const p1 = Person.getInstance()
const p2 = Person.getInstance()

2. 發(fā)布訂閱模式

class EventEmitter {
    constructor() {
      this.events = {}
    }
    emit(eventName, ...args) {
      const events = this.events[eventName]

      if (events) {
        events.forEach(event => {
          event.apply(null, args)
        })
      }
    }
    on(eventName, fn) {
      if (this.events[eventName]) {
        this.events[eventName].push(fn)
      } else {
        this.events[eventName] = [fn]
      }
    }
  }

3. 觀察者模式

性能優(yōu)化

1. webpack 構(gòu)建優(yōu)化

首先造成 webpack 構(gòu)建速度慢的因素就是重復(fù)的編譯、代碼壓縮

方案

  1. 緩存
    1. 大部分 loader 提供了緩存選項(xiàng)
    2. 或者使用 cache-loader;需要定義在所有 loader 的最前面
  2. js 壓縮
    1. 開(kāi)啟緩存
    2. 開(kāi)啟 parallel(并行編譯)
  3. happypack 多核編譯 (多線(xiàn)程)
    1. MiniCssExtractPlugin 無(wú)法與 happypack 共存
    2. MiniCssExtractPlugin 必須置于 cache-loader執(zhí)行之后,否則無(wú)法生效
  4. 通過(guò) DllPlugin 抽離靜態(tài)依賴(lài)包,避免反復(fù)編譯,比如 lodash 等,或者通過(guò) externals CDN 引入。
  5. Tree shaking
  6. Scope hoisting 構(gòu)建后的代碼會(huì)存在大量閉包,造成體積增大,運(yùn)行代碼時(shí)創(chuàng)建的函數(shù)作用域變多,內(nèi)存開(kāi)銷(xiāo)變大。Scope hoisting 將所有模塊的代碼按照引用順序放在一個(gè)函數(shù)作用域里,然后適當(dāng)?shù)闹孛恍┳兞恳苑乐棺兞棵麤_突

提升構(gòu)建體驗(yàn)

  1. progress-bar-webpack-plugin 構(gòu)建進(jìn)度提示
  2. webpack-build-notifier 構(gòu)建完成后提示,還有提示音
  3. webpack-dashboard 構(gòu)建界面

2. 資源優(yōu)化

圖片

  1. 減小 favicon.ico 的體積 設(shè)置強(qiáng)緩存,過(guò)期時(shí)間設(shè)置幾個(gè)月
  2. 壓縮圖片
    1. webpack
    2. 在線(xiàn)壓縮工具
  3. 雪碧圖
    1. 橫向排列會(huì)更小
  4. 禁止在 HTML 中縮放圖片
  5. PNG logo
  6. WebP 格式 注意降級(jí)處理
    1. 多后綴方式兼容
    2. 指定 Accept 頭支持 WebP 格式
  7. 小圖標(biāo) base64 直接嵌入 HTML 文檔
  8. 懶加載
  9. 圖片漸進(jìn)顯示

CSS

  1. css 放在頂部
  2. 使用 link 而不是@import 加載樣式
  3. css 通過(guò)文件的方式引入,不要寫(xiě)在 html 文檔,以減小文檔的大小,此外 css 文件可以被緩存
  4. 壓縮 css
  5. 硬件加速
    1. transform: translateZ(0); // 僅開(kāi)啟硬件加速
    2. transform: translate3d(0, 0, 0); // 3d 變換開(kāi)啟硬件加速
    3. perspective: 1000

注意:使用 3D 硬件加速提升動(dòng)畫(huà)性能時(shí),最好給元素增加一個(gè) z-index 屬性,人為干擾復(fù)合層的排序,可以有效減少 chrome 創(chuàng)建不必要的復(fù)合層,提升渲染性能,移動(dòng)端優(yōu)化效果尤為明顯。
硬件加速最好只用在 animation 或者 transform 上。不要濫用硬件加速,因?yàn)檫@樣會(huì)增加性能的消耗(內(nèi)存的使用),如果濫用反而會(huì)使動(dòng)畫(huà)變得更加卡,就得不償失了。

JavaScript

  1. 腳本放在底部
  2. js 和 css 通過(guò)文件的方式引入,不要寫(xiě)在 html 中,原因:
    1. 減小了 HTML 文檔的大小
    2. js 和 css 會(huì)被緩存
    3. 壓縮 js
  3. 刪除重復(fù)的腳本
  4. 減少 DOM 的訪問(wèn)
    1. 緩存頻繁訪問(wèn)的 DOM
    2. 在 DOM 樹(shù)外更新節(jié)點(diǎn),然后添加到 DOM 樹(shù),比如 DocumentFragment
    3. 動(dòng)畫(huà)能用 CSS 實(shí)現(xiàn)的不用 js 實(shí)現(xiàn)
  5. 防抖/節(jié)流
  6. 減少重繪重排

3. cookie 優(yōu)化

  1. 消除不必要的 cookie
  2. 盡可能減小 cookie 的大小
  3. 注意設(shè)置 cookie 到合適的域名級(jí)別,則其它子域名不會(huì)被影響
  4. 正確設(shè)置 Expires 日期
  5. 靜態(tài)資源請(qǐng)求沒(méi)有 cookie,比如將靜態(tài)資源放在全新的域下

4. HTTP 優(yōu)化

  1. 最小化請(qǐng)求數(shù)
  2. 預(yù)加載資源
  3. 緩存策略

5. performance API 的使用

window.performance

const timingInfo = window.performance.timing

// TCP連接耗時(shí)
timingInfo.connectEnd - timingInfo.connectStart

// DNS查詢(xún)耗時(shí)
timingInfo.domainLookupEnd - timingInfo.domainLookupStart

// 獲得首字節(jié)耗費(fèi)時(shí)間,也叫TTFB
timingInfo.responseStart - timingInfo.navigationStart

// domReady時(shí)間(與前面提到的DomContentLoad事件對(duì)應(yīng))
timingInfo.domContentLoadedEventStart - timingInfo.navigationStart

// DOM資源下載
timingInfo.responseEnd - timingInfo.responseStart

web 安全

XSS(跨站腳本攻擊)

跨站腳本攻擊(XSS):為什么 Cookie 中有 HttpOnly 屬性?

CSRF(跨站偽造攻擊)

CSRF 攻擊:陌生鏈接不要隨便點(diǎn)

錯(cuò)誤監(jiān)控及上報(bào)

一、類(lèi)型及解決方式

1. 運(yùn)行時(shí)錯(cuò)誤

  1. try...catch
  2. window.onerror
  3. window.addEventListener('error')

2. 資源加載錯(cuò)誤(圖片)

  1. img.onerror

3. script Error(跨域代碼)

原因:
跨域訪問(wèn)的 js 內(nèi)部報(bào)錯(cuò),瀏覽器處于安全考慮,不會(huì)報(bào)告具體的錯(cuò)誤堆棧和行號(hào),只拋出 script error 錯(cuò)誤

解決:

  1. script 添加 crossorigin
  2. 服務(wù)端設(shè)置 Access-Control-allow-origin 為 * 或 訪問(wèn)域

二、錯(cuò)誤上報(bào)

  1. ajax
  2. image 的 src((new Image()).src = '錯(cuò)誤上報(bào)的請(qǐng)求地址') 使用圖片發(fā)送 get 請(qǐng)求,上報(bào)信息,由于瀏覽器對(duì)圖片有緩存,同樣的請(qǐng)求,圖片只會(huì)發(fā)送一次,避免重復(fù)上報(bào)

Webpack

loader 和 plugin 區(qū)別及原理

Git

git pull 和 git fetch 的區(qū)別?

  1. fetch:相當(dāng)于是從遠(yuǎn)程獲取最新版本到本地,不會(huì)自動(dòng) merge
  2. git pull:相當(dāng)于是從遠(yuǎn)程獲取最新版本并 merge 到本地

2. git rebase

你真的懂 git rebase 嗎?

場(chǎng)景題

1. 音樂(lè)播放器

2. 虛擬列表

3. 數(shù)據(jù)預(yù)加載

4. 幾十萬(wàn)條數(shù)據(jù)渲染

<ul id="list"></ul>
// 總數(shù)據(jù)
  const total = 100000

  // 每幀渲染的數(shù)量
  const once = 20

  // 總共渲染的次數(shù)
  const count = Math.ceil(total / once)

  // 父容器
  const ul = document.querySelector('#list')

  let loopTime = 1

  function render() {
    const fragment = document.createDocumentFragment()

    for (let i = 0; i < once; i++) {
      const li = document.createElement('li')

      li.innerText = Math.random() * total

      fragment.appendChild(li)
    }
    ul.appendChild(fragment)

    loop()
  }

  function loop() {
    if (loopTime++ < count) {
      window.requestAnimationFrame(render)
    }
  }

loop()

未完待續(xù)...

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容