「源碼級回答」大廠高頻Vue面試題(上)

2021-03-02 前端森林
寫在前面(不看錯過一個億)

最近一直在讀Vue源碼,也寫了一系列的源碼探秘文章。

但,收到很多朋友的反饋都是:源碼晦澀難懂,時常看著看著就不知道我在看什麼了,感覺缺乏一點動力,如果你可以出點面試中會問到的源碼相關的面試題,通過面試題去看源碼,那就很棒棒。

看到大家的反饋,我絲毫沒有猶豫:安排!!

我通過三篇文章整理了大廠面試中會經常問到的一些Vue面試題,通過源碼角度去回答,拋棄純概念型回答,相信一定會讓面試官對你刮目相看。

請說一下響應式數據的原理?

Vue實現響應式數據的核心API是Object.defineProperty。

其實默認Vue在初始化數據時,會給data中的屬性使用Object.defineProperty重新定義所有屬性,當頁面取到對應屬性時。會進行依賴收集(收集當前組件的watcher) 如果屬性發生變化會通知相關依賴進行更新操作。

這裡,我用一張圖來說明Vue實現響應式數據的流程:

首先,第一步是初始化用戶傳入的data數據。這一步對應源碼src/core/instance/state.js的 112 行
function initData (vm: Component) {
  let data = vm.$options.data
  data = vm._data = typeof data === 'function'
    ? getData(data, vm)
    : data || {}
  if (!isPlainObject(data)) {
    // ...
  }
  // proxy data on instance
  const keys = Object.keys(data)
  const props = vm.$options.props
  const methods = vm.$options.methods
  let i = keys.length
  while (i--) {
   // ...
  }
  // observe data
  observe(data, true /* asRootData */)
}

第二步是將數據進行觀測,也就是在第一步的initData的最後調用的observe函數。對應在源碼的src/core/observer/index.js的 110 行
/**
 * Attempt to create an observer instance for a value,
 * returns the new observer if successfully observed,
 * or the existing observer if the value already has one.
 */
export function observe (value: any, asRootData: ?boolean): Observer | void {
  if (!isObject(value) || value instanceof VNode) {
    return
  }
  let ob: Observer | void
  if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
    ob = value.__ob__
  } else if (
    shouldObserve &&
    !isServerRendering() &&
    (Array.isArray(value) || isPlainObject(value)) &&
    Object.isExtensible(value) &&
    !value._isVue
  ) {
    ob = new Observer(value)
  }
  if (asRootData && ob) {
    ob.vmCount++
  }
  return ob
}

這裡會通過new Observer(value)創建一個Observer實例,實現對數據的觀測。

第三步是實現對對象的處理。對應源碼src/core/observer/index.js的 55 行。
/**
 * Observer class that is attached to each observed
 * object. Once attached, the observer converts the target
 * object's property keys into getter/setters that
 * collect dependencies and dispatch updates.
 */
export class Observer {
  value: any;
  dep: Dep;
  vmCount: number; // number of vms that have this object as root $data

  constructor (value: any) {
    this.value = value
    this.dep = new Dep()
    this.vmCount = 0
    def(value, '__ob__', this)
    if (Array.isArray(value)) {
      if (hasProto) {
        protoAugment(value, arrayMethods)
      } else {
        copyAugment(value, arrayMethods, arrayKeys)
      }
      this.observeArray(value)
    } else {
      this.walk(value)
    }
  }

   /**
   * Walk through all properties and convert them into
   * getter/setters. This method should only be called when
   * value type is Object.
   */
  walk (obj: Object) {
    const keys = Object.keys(obj)
    for (let i = 0; i < keys.length; i++) {
      defineReactive(obj, keys[i])
    }
  }
  // ...
}

第四步就是循環對象屬性定義響應式變化了。對應源碼src/core/observer/index.js的 135 行。
/**
 * Define a reactive property on an Object.
 */
export function defineReactive (
  obj: Object,
  key: string,
  val: any,
  customSetter?: ?Function,
  shallow?: boolean
) {
  const dep = new Dep()

  const property = Object.getOwnPropertyDescriptor(obj, key)
  if (property && property.configurable === false) {
    return
  }

  // cater for pre-defined getter/setters
  const getter = property && property.get
  const setter = property && property.set
  if ((!getter || setter) && arguments.length === 2) {
    val = obj[key]
  }

  let childOb = !shallow && observe(val)
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {
      const value = getter ? getter.call(obj) : val
      if (Dep.target) {
        dep.depend()  // 收集依賴
        // ...
      }
      return value
    },
    set: function reactiveSetter (newVal) {
      // ...
      dep.notify()  // 通知相關依賴進行更新
    }
  })
}

第五步其實就是使用defineReactive方法中的Object.defineProperty重新定義數據。在get中通過dep.depend()收集依賴。當數據改變時,攔截屬性的更新操作,通過set中的dep.notify()通知相關依賴進行更新。Vue 中是如何檢測數組變化?

Vue中檢測數組變化核心有兩點:

Vue 將 data 中的數組,進行了原型鏈重寫。指向了自己定義的數組原型方法,這樣當調用數組 api 時,就可以通知依賴更新。如果數組中包含著引用類型,會對數組中的引用類型再次進行觀測。

這裡用一張流程圖來說明:

這裡第一步和第二步和上題請說一下響應式數據的原理?是相同的,就不展開說明了。

❞第一步同樣是初始化用戶傳入的 data 數據。對應源碼src/core/instance/state.js的 112 行的initData函數。第二步是對數據進行觀測。對應源碼src/core/observer/index.js的 124 行。第三步是將數組的原型方法指向重寫的原型。對應源碼src/core/observer/index.js的 49 行。
if (hasProto) {
  protoAugment(value, arrayMethods)
} else {
  // ...
}

也就是protoAugment方法:

/**
 * Augment a target Object or Array by intercepting
 * the prototype chain using __proto__
 */
function protoAugment (target, src: Object) {
  /* eslint-disable no-proto */
  target.__proto__ = src
  /* eslint-enable no-proto */
}

第四步進行了兩步操作。首先是對數組的原型方法進行重寫,對應源碼src/core/observer/array.js。
/*
 * not type checking this file because flow doesn't play well with
 * dynamically accessing methods on Array prototype
 */

import { def } from '../util/index'

const arrayProto = Array.prototype
export const arrayMethods = Object.create(arrayProto)

const methodsToPatch = [  // 這裡列舉的數組的方法是調用後能改變原數組的
  'push',
  'pop',
  'shift',
  'unshift',
  'splice',
  'sort',
  'reverse'
]

/**
 * Intercept mutating methods and emit events
 */
methodsToPatch.forEach(function (method) {  // 重寫原型方法
  // cache original method
  const original = arrayProto[method]  // 調用原數組方法
  def(arrayMethods, method, function mutator (...args) {
    const result = original.apply(this, args)
    const ob = this.__ob__
    let inserted
    switch (method) {
      case 'push':
      case 'unshift':
        inserted = args
        break
      case 'splice':
        inserted = args.slice(2)
        break
    }
    if (inserted) ob.observeArray(inserted)  // 進行深度監控
    // notify change
    ob.dep.notify()  // 調用數組方法後,手動通知視圖更新
    return result
  })
})

為什麼Vue採用異步渲染?

我們先來想一個問題:如果Vue不採用異步更新,那麼每次數據更新時是不是都會對當前組件進行重寫渲染呢?

答案是肯定的,為了性能考慮,會在本輪數據更新後,再去異步更新視圖。

通過一張圖來說明Vue異步更新的流程:

第一步調用dep.notify()通知watcher進行更新操作。對應源碼src/core/observer/dep.js中的 37 行。
notify () {  // 通知依賴更新
  // stabilize the subscriber list first
  const subs = this.subs.slice()
  if (process.env.NODE_ENV !== 'production' && !config.async) {
    // subs aren't sorted in scheduler if not running async
    // we need to sort them now to make sure they fire in correct
    // order
    subs.sort((a, b) => a.id - b.id)
  }
  for (let i = 0, l = subs.length; i < l; i++) {
    subs[i].update()  // 依賴中的update方法
  }
}

第二步其實就是在第一步的notify方法中,遍歷subs,執行subs[i].update()方法,也就是依次調用watcher的update方法。對應源碼src/core/observer/watcher.js的 164 行
/**
 * Subscriber interface.
 * Will be called when a dependency changes.
 */
update () {
  /* istanbul ignore else */
  if (this.lazy) {  // 計算屬性
    this.dirty = true
  } else if (this.sync) {  // 同步watcher
    this.run()
  } else {
    queueWatcher(this)  // 當數據發生變化時會將watcher放到一個隊列中批量更新
  }
}

第三步是執行update函數中的queueWatcher方法。對應源碼src/core/observer/scheduler.js的 164 行。

/**
 * Push a watcher into the watcher queue.
 * Jobs with duplicate IDs will be skipped unless it's
 * pushed when the queue is being flushed.
 */
export function queueWatcher (watcher: Watcher) {
  const id = watcher.id  // 過濾watcher,多個屬性可能會依賴同一個watcher
  if (has[id] == null) {
    has[id] = true
    if (!flushing) {
      queue.push(watcher)  // 將watcher放到隊列中
    } else {
      // if already flushing, splice the watcher based on its id
      // if already past its id, it will be run next immediately.
      let i = queue.length - 1
      while (i > index && queue[i].id > watcher.id) {
        i--
      }
      queue.splice(i + 1, 0, watcher)
    }
    // queue the flush
    if (!waiting) {
      waiting = true

      if (process.env.NODE_ENV !== 'production' && !config.async) {
        flushSchedulerQueue()
        return
      }
      nextTick(flushSchedulerQueue)  // 調用nextTick方法,在下一個tick中刷新watcher隊列
    }
  }
}

第四步就是執行nextTick(flushSchedulerQueue)方法,在下一個tick中刷新watcher隊列談一下nextTick的實現原理?

Vue.js在默認情況下,每次觸發某個數據的 setter 方法後,對應的 Watcher 對象其實會被 push 進一個隊列 queue 中,在下一個 tick 的時候將這個隊列 queue 全部拿出來 run( Watcher 對象的一個方法,用來觸發 patch 操作) 一遍。

因為目前瀏覽器平臺並沒有實現 nextTick 方法,所以 Vue.js 源碼中分別用 Promise、setTimeout、setImmediate 等方式在 microtask(或是task)中創建一個事件,目的是在當前調用棧執行完畢以後(不一定立即)才會去執行這個事件。

nextTick方法主要是使用了宏任務和微任務,定義了一個異步方法.多次調用nextTick 會將方法存入隊列中,通過這個異步方法清空當前隊列。

所以這個 nextTick 方法是異步方法。

通過一張圖來看下nextTick的實現:

首先會調用nextTick並傳入cb。對應源碼src/core/util/next-tick.js的 87 行。
export function nextTick (cb?: Function, ctx?: Object) {
  let _resolve
  callbacks.push(() => {
    if (cb) {
      try {
        cb.call(ctx)
      } catch (e) {
        handleError(e, ctx, 'nextTick')
      }
    } else if (_resolve) {
      _resolve(ctx)
    }
  })
  if (!pending) {
    pending = true
    timerFunc()
  }
  // $flow-disable-line
  if (!cb && typeof Promise !== 'undefined') {
    return new Promise(resolve => {
      _resolve = resolve
    })
  }
}

接下來會定義一個callbacks 數組用來存儲 nextTick,在下一個 tick 處理這些回調函數之前,所有的 cb 都會被存在這個 callbacks 數組中。下一步會調用timerFunc函數。對應源碼src/core/util/next-tick.js的 33 行。
let timerFunc

if (typeof Promise !== 'undefined' && isNative(Promise)) {
  timerFunc = () => {
    // ...
  }
  isUsingMicroTask = true
} else if (!isIE && typeof MutationObserver !== 'undefined' && (
  isNative(MutationObserver) ||
  // PhantomJS and iOS 7.x
  MutationObserver.toString() === '[object MutationObserverConstructor]'
)) {

  timerFunc = () => {
    // ...
  }
  isUsingMicroTask = true
} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
  timerFunc = () => {
    setImmediate(flushCallbacks)
  }
} else {
  // Fallback to setTimeout.
  timerFunc = () => {
    setTimeout(flushCallbacks, 0)
  }
}

來看下timerFunc的取值邏輯:

1、 我們知道異步任務有兩種,其中 microtask 要優於 macrotask ,所以優先選擇 Promise 。因此這裡先判斷瀏覽器是否支持 Promise。

2、 如果不支持再考慮 macrotask 。對於 macrotask 會先後判斷瀏覽器是否支持 MutationObserver 和 setImmediate 。

3、 如果都不支持就只能使用 setTimeout 。這也從側面展示出了 macrotask 中 setTimeout 的性能是最差的。

nextTick中 if (!pending) 語句中 pending 作用顯然是讓 if 語句的邏輯只執行一次,而它其實就代表 callbacks 中是否有事件在等待執行。

這裡的flushCallbacks函數的主要邏輯就是將 pending 置為 false 以及清空 callbacks 數組,然後遍歷 callbacks 數組,執行裡面的每一個函數。

if (!cb && typeof Promise !== 'undefined') {
  return new Promise(resolve => {
    _resolve = resolve
  })
}

這裡 if 對應的情況是我們調用 nextTick 函數時沒有傳入回調函數並且瀏覽器支持 Promise ,那麼就會返回一個 Promise 實例,並且將 resolve 賦值給 _resolve。回到nextTick開頭的一段代碼:

let _resolve
callbacks.push(() => {
  if (cb) {
    try {
      cb.call(ctx)
    } catch (e) {
      handleError(e, ctx, 'nextTick')
    }
  } else if (_resolve) {
    _resolve(ctx)
  }
})

當我們執行 callbacks 的函數時,發現沒有 cb 而有 _resolve 時就會執行之前返回的 Promise 對象的 resolve 函數。

你知道Vue中computed是怎麼實現的嗎?

這裡先給一個結論:計算屬性computed的本質是 computed Watcher,其具有緩存。

一張圖了解下computed的實現:

首先是在組件實例化時會執行initComputed方法。對應源碼src/core/instance/state.js的 169 行。
const computedWatcherOptions = { lazy: true }

function initComputed (vm: Component, computed: Object) {
  // $flow-disable-line
  const watchers = vm._computedWatchers = Object.create(null)
  // computed properties are just getters during SSR
  const isSSR = isServerRendering()

  for (const key in computed) {
    const userDef = computed[key]
    const getter = typeof userDef === 'function' ? userDef : userDef.get
    if (process.env.NODE_ENV !== 'production' && getter == null) {
      warn(
        `Getter is missing for computed property "${key}".`,
        vm
      )
    }

    if (!isSSR) {
      // create internal watcher for the computed property.
      watchers[key] = new Watcher(
        vm,
        getter || noop,
        noop,
        computedWatcherOptions
      )
    }

    // component-defined computed properties are already defined on the
    // component prototype. We only need to define computed properties defined
    // at instantiation here.
    if (!(key in vm)) {
      defineComputed(vm, key, userDef)
    } else if (process.env.NODE_ENV !== 'production') {
      if (key in vm.$data) {
        warn(`The computed property "${key}" is already defined in data.`, vm)
      } else if (vm.$options.props && key in vm.$options.props) {
        warn(`The computed property "${key}" is already defined as a prop.`, vm)
      }
    }
  }
}

initComputed 函數拿到 computed 對象然後遍歷每一個計算屬性。判斷如果不是服務端渲染就會給計算屬性創建一個 computed Watcher 實例賦值給watchers[key](對應就是vm._computedWatchers[key])。然後遍歷每一個計算屬性調用 defineComputed 方法,將組件原型,計算屬性和對應的值傳入。

defineComputed定義在源碼src/core/instance/state.js210 行。
// src/core/instance/state.js
export function defineComputed(
  target: any,
  key: string,
  userDef: Object | Function
) {
  const shouldCache = !isServerRendering();
  if (typeof userDef === "function") {
    sharedPropertyDefinition.get = shouldCache
      ? createComputedGetter(key)
      : createGetterInvoker(userDef);
    sharedPropertyDefinition.set = noop;
  } else {
    sharedPropertyDefinition.get = userDef.get
      ? shouldCache && userDef.cache !== false
        ? createComputedGetter(key)
        : createGetterInvoker(userDef.get)
      : noop;
    sharedPropertyDefinition.set = userDef.set || noop;
  }
  if (
    process.env.NODE_ENV !== "production" &&
    sharedPropertyDefinition.set === noop
  ) {
    sharedPropertyDefinition.set = function () {
      warn(
        `Computed property "${key}" was assigned to but it has no setter.`,
        this
      );
    };
  }
  Object.defineProperty(target, key, sharedPropertyDefinition);
}

首先定義了 shouldCache 表示是否需要緩存值。接著對 userDef 是函數或者對象分別處理。這裡有一個 sharedPropertyDefinition ,我們來看它的定義:

// src/core/instance/state.js
const sharedPropertyDefinition = {
  enumerable: true,
  configurable: true,
  get: noop,
  set: noop,
};

sharedPropertyDefinition其實就是一個屬性描述符。

回到 defineComputed 函數。如果 userDef 是函數的話,就會定義 getter 為調用 createComputedGetter(key) 的返回值。

因為 shouldCache 是 true

而 userDef 是對象的話,非服務端渲染並且沒有指定 cache 為 false 的話,getter 也是調用 createComputedGetter(key) 的返回值,setter 則為 userDef.set 或者為空。

所以 defineComputed 函數的作用就是定義 getter 和 setter ,並且在最後調用 Object.defineProperty 給計算屬性添加 getter/setter ,當我們訪問計算屬性時就會觸發這個 getter。

對於計算屬性的 setter 來說,實際上是很少用到的,除非我們在使用 computed 的時候指定了 set 函數。

❞無論是userDef是函數還是對象,最終都會調用createComputedGetter函數,我們來看createComputedGetter的定義:
function createComputedGetter(key) {
  return function computedGetter() {
    const watcher = this._computedWatchers && this._computedWatchers[key];
    if (watcher) {
      if (watcher.dirty) {
        watcher.evaluate();
      }
      if (Dep.target) {
        watcher.depend();
      }
      return watcher.value;
    }
  };
}

我們知道訪問計算屬性時才會觸發這個 getter,對應就是computedGetter函數被執行。

computedGetter 函數首先通過 this._computedWatchers[key] 拿到前面實例化組件時創建的 computed Watcher 並賦值給 watcher 。

在new Watcher時傳入的第四個參數computedWatcherOptions的lazy為true,對應就是watcher的構造函數中的dirty為true。在computedGetter中,如果dirty為true(即依賴的值沒有發生變化),就不會重新求值。相當於computed被緩存了。

接著有兩個 if 判斷,首先調用 evaluate 函數:

/**
 * Evaluate the value of the watcher.
 * This only gets called for lazy watchers.
 */
evaluate () {
  this.value = this.get()
  this.dirty = false
}

首先調用 this.get() 將它的返回值賦值給 this.value ,來看 get 函數:

// src/core/observer/watcher.js
/**
 * Evaluate the getter, and re-collect dependencies.
 */
get () {
  pushTarget(this)
  let value
  const vm = this.vm
  try {
    value = this.getter.call(vm, vm)
  } catch (e) {
    if (this.user) {
      handleError(e, vm, `getter for watcher "${this.expression}"`)
    } else {
      throw e
    }
  } finally {
    // "touch" every property so they are all tracked as
    // dependencies for deep watching
    if (this.deep) {
      traverse(value)
    }
    popTarget()
    this.cleanupDeps()
  }
  return value
}

get 函數第一步是調用 pushTarget 將 computed Watcher 傳入:

// src/core/observer/dep.js
export function pushTarget(target: ?Watcher) {
  targetStack.push(target);
  Dep.target = target;
}

可以看到 computed Watcher 被 push 到 targetStack 同時將 Dep.target 置為 computed Watcher 。而 Dep.target 原來的值是渲染 Watcher ,因為正處於渲染階段。回到 get 函數,接著就調用了 this.getter 。

回到 evaluate 函數:

evaluate () {
  this.value = this.get()
  this.dirty = false
}

執行完get函數,將dirty置為false。

回到computedGetter函數,接著往下進入另一個if判斷,執行了depend函數:

// src/core/observer/watcher.js
/**
 * Depend on all deps collected by this watcher.
 */
depend () {
  let i = this.deps.length
  while (i--) {
    this.deps[i].depend()
  }
}

這裡的邏輯就是讓 Dep.target 也就是渲染 Watcher 訂閱了 this.dep 也就是前面實例化 computed Watcher 時候創建的 dep 實例,渲染 Watcher 就被保存到 this.dep 的 subs 中。

在執行完 evaluate 和 depend 函數後,computedGetter 函數最後將 evaluate 的返回值返回出去,也就是計算屬性最終計算出來的值,這樣頁面就渲染出來了。

相關焦點

  • 30 道 Vue 面試題,內含詳細講解
    文章最後一題,歡迎同學們積極回答,分享各自的經驗 ~~~1、說說你對 SPA 單頁面的理解,它的優缺點分別是什麼?SPA( single-page application )僅在 Web 頁面初始化時加載相應的 HTML、JavaScript 和 CSS。
  • 2021全新 最火Vue面試題(一)(附源碼)
    這裡在回答時可以帶出一些相關知識點(比如多層對象是通過遞歸來實現劫持,順帶提出Vue3中是使用proxy來實現響應式數據)補充回答:內部依賴收集是怎樣做到的,每個屬性都擁有自己的dep屬性,存放他所依賴的watcher,當屬性變化後會通知自己對應的watcher去更新 (其實後面會講到每個對象類型自己本身也擁有一個dep屬性,這個在$set面試題中在進行講解)
  • 一個前端自學者從面試被吊打,到拿 offer 的心路歷程
    前言先交代下博主是在二線城市,所以也面不了什麼大廠自然也就沒什麼大廠面經(就算有我也沒有這個實力),昨天面試了一家中小型的公司,這裡就不透露名字了,總共面了兩輪總結後寫下了這篇文章,我相信也是有很多和我在二線城市的小夥伴需要這些常見的面試題的,我是剛畢業出來實習,所以馬上要出去找實習的小夥伴可以看看,參考下。2. 面試之前準備什麼?
  • 你知道現在的面試有多難嗎?不服來看這三道大廠面試題……
    本文轉載自【微信公眾號:網羅燈下黑,ID:wldxh8】經微信公眾號授權轉載,如需轉載與原文作者聯繫快來看看這三道大廠面試題,你知道現在的面試有多難嗎???02「請尋求最優解,不要簡單的synchronized」請用兩個線程交替輸出 A1B2C3D4...,A 線程輸出字母,B 線程輸出數字,要求 A 線程首先執行,B 線程其次執行!
  • 2021年,vue3.0 面試題分析(乾貨滿滿,內容詳盡)
    Vue3.0 對於我們前端人的重要性 —— 2021年面試必備2020年09月18日,vue3.0正式發布。隨著它的發布,Vue.js再次被推上了前端的風口浪尖。同時,面試官的提問也將加入一些有關Vue3.0的新元素(相信近期去面試的小夥伴或多或少都會被問到Vue3.0的知識點)。
  • 20 道必看的 Vue 面試題 | 原力計劃
    npm install:下載 node_modules 資源包的命令npm run dev:啟動 vue-cli 開發環境的 npm 命令npm run build:vue-cli 生成生產環境部署資源的 npm 命令11. 請說出 vue-cli 工程中每個文件夾和文件的用處。
  • 500道Java 必備面試題答案(過後即刪)
    那怎樣才能快速拿到大廠的 Offer 呢?因為我也有過那段「自學」Java 的迷茫時期,所以我深知好的系統學習規劃和生動的老師講解,是事半功倍並且省下我們更多青春的關鍵。從業十多年,我從面試者變成面試官,在 Java 面試上積累了比較豐富的經驗。
  • TA說 | 從「查無此校」到大廠錄取
    相信很多正在準備求職的CS學弟學妹們在看到「亞麻」兩個字的時候會嗤之以鼻:不就是亞麻麼,大家都能進的廠有什麼好炫耀的呢?的確,一年前的我在看到「亞麻上岸經驗」的時候也是這麼想的。畢竟對北美CS大廠有過一定了解的同學,對幾年前「兩輪OA進亞麻」這件事一定不感到陌生。
  • 年末的大廠前端面試總結(20屆雙非二本)-終入字節
    不死心的我又投了第五次,成功約上了面試。至於後來為什麼能再約上猿輔導,百度,58同城,去哪兒網,美團的面試,我猜應該都是因為運氣...又或者,實在很缺人?技術方面該如何準備結合我20多次的面試經驗,總結出一個核心:基礎。即便是大廠,也不會忽略問基礎,反而更重視這個。基礎不穩,地動山搖。基礎穩如狗,大廠隨便走。
  • Vue全家桶&vue-router原理
    原理 Vue全家桶:vue + vue-router(路由)+ vuex(狀態管理)+ axios(請求)本次主要分析並實現簡版vue-router,從源碼中學習借鑑,更好的理解源碼vue-routerVue Router 是 Vue.js 官⽅的路由管理器。
  • Vue.extend API(源碼級詳解)
    前言Vue.extend是 Vue 裡的一個全局 API,它提供了一種靈活的掛載組件的方式,這個 API 在日常開發中很少使用,畢竟只在碰到某些特殊的需求時它才能派上用場,但是我們也要學習它,學習這個 API 可以讓我們對 Vue 更加了解,更加熟悉 Vue 的組件初始化和掛載流程,除此之外,也經常會有面試官問到這個東西。
  • Vue面試題(3)Vue-Router和Vuex
    本文收集和整理了有關Vue-Router和Vuex的面試題,希望對準備面試和深入學習的小夥伴有所幫助!
  • 前端面試題——Vue
    前言前幾天整理了一些 html + css + JavaScript 常見的面試題(https://segmentfault.com/u/youdangde_5c8b208a23f95/articles),然後現在也是找了一些在 Vue 方面經常出現的面試題,留給自己查看消化,也分享給有需要的小夥伴。
  • 解析 BAT 大廠的經典面試題(中篇)
    分享給大家的是 「工具 模塊」- 解析BAT面試題(中篇)。很多人對 BAT 以及其他大廠,也是朝思暮想。也因為一些原因,暫時還未能加入。大廠中有很多經典面試題,直到現在也會用,不要問小編為什麼知道(保密)。
  • 「Spring 全家桶」70 道高頻面試題
    對於那些想面試高級 Java 崗位的同學來說,除了算法屬於比較「天方夜譚
  • 【別笑】手撕吊打面試官系列面試題
    必備面試題js基礎1.用js列印一個乘法表這一題面試官考察的是你關於js的列印相關基礎api的熟悉程度,以及基本的數學常識,送分題console.log(`1*1=12*1=2 2*2=43*1=3 3*2=6 3*3=94*1=4 4*2=8 4*3=12 4*4=16
  • 阿里P8架構師力薦的 Java源碼解析及面試合集
    前言:換工作的 Java 工程師小夥伴們有沒有被大廠的技術虐得體無完膚的經歷?那是因為你還不了解大廠的面試套路。Java 的底層實現是常被問到的,也就是 Java 源碼。如果啃不下來,很可能就與大廠失之交臂。
  • 阿里P8面試官:4道Java必考題,答好3題P6穩,全對考慮P7
    )阿里技術面試有四個重點:源碼考察高可用架構的項目經驗大廠的程式設計師思維面試反套路技巧(價值觀+行為問題)對於P6的面試準備,熟讀並背誦以下源碼基本夠了:JVM底層Spring家族ORM-Hibernate/Mybabit線程池/資料庫連接池源碼面試,真實還原:問:Java 中操作字符串都有哪些類?它們之間有什麼區別?這題熟悉,穩了。
  • 2019 前端秋季社招面試經歷總結(二年多經驗)
    vue 中 computed 與 watch 的內在是如何實現的 ?接下來前端要深入的方向 ?寫一個方法輸出 ABCDEFG 的值(看試題、現場寫程序)。從排好序的兩個鍊表中,找到相同的節點,並輸出鍊表(看試題、現場寫程序)。最後面試官問了句,你沒刷過什麼面試題嗎 😪。
  • Vue 和 React 大廠面試通關指南.pdf
    但大多數讀者都只擅長其中一個框架,當面試涉及到另一個框架的內容時,就答不好了。比如虛擬dom,兩個框架中都有應用,面試官可能會籠統地問一句「如何理解虛擬dom」,如果你單從一個框架的應用層面去回答,就略顯單薄。如何在掌握一個框架的同時,快速理解另一個框架的原理和精髓呢?