最新的Vue面试题大全含源码级回答,吊打面试官系列(1)

}

分析完源码再一下官网图示,会更清楚:

10.keep-aliva原理


keep-alive是Vue.js的一个内置组件。它能够将不活动的组件实例保存在内存中,而不是直接将其销毁,它是一个抽象组件,不会被渲染到真实DOM中,也不会出现在父组件链中。

include与exclude两个属性,允许组件有条件地进行缓存,max属性确定最多缓存多少组件实例。

keep-alive是一个组件,跟其他组件一样有生命周期和render函数,keep-alive包裹的分析keep-alive就是分析一个组件。

源码再src/core/components/keep-alive,created声明了要缓存的组件对象,和存储的组件keys,keep-alive销毁的时候会用pruneCacheEntry将缓存的所有组件实例销毁,也就是调用组件实例的destroy方法。在挂载完成后监听include和exclude,动态地销毁已经不满足include的组件和满足exclude的组件实例:

created () {

this.cache = Object.create(null) // 存储需要缓存的组件

this.keys = [] // 存储每个需要缓存的组件的key,即对应this.cache对象中的键值

},

// 销毁keep-alive组件的时候,对缓存中的每个组件执行销毁

destroyed () {

for (const key in this.cache) {

pruneCacheEntry(this.cache, key, this.keys)

}

},

mounted () {

this.$watch(‘include’, val => {

pruneCache(this, name => matches(val, name))

})

this.$watch(‘exclude’, val => {

pruneCache(this, name => !matches(val, name))

})

},

接下来是render函数:

render () {

const slot = this.$slots.default

const vnode: VNode = getFirstComponentChild(slot)

// 如果vnode存在就取vnode的选项

const componentOptions: ?VNodeComponentOptions = vnode && vnode.componentOptions

if (componentOptions) {

// check pattern

//获取第一个有效组件的name

const name: ?string = getComponentName(componentOptions)

const { include, exclude } = this

if (

// not included

(include && (!name || !matches(include, name))) ||

// excluded

(exclude && name && matches(exclude, name))

) {

return vnode// 说明不用缓存,直接返回这个组件进行渲染

}

// 匹配到了,开始缓存操作

const { cache, keys } = this // keep-alive组件的缓存组件和缓存组件对应的key

// 获取第一个有效组件的key

const key: ?string = vnode.key == null

// same constructor may get registered as different local components

// so cid alone is not enough (#3269)

? componentOptions.Ctor.cid + (componentOptions.tag ? ::${componentOptions.tag} : ‘’)
vnode.key

if (cache[key]) {

// 这个组件的实例用缓存中的组件实例替换

vnode.componentInstance = cache[key].componentInstance

// make current key freshest

// 更新当前key在keys中的位置

remove(keys, key)

keys.push(key)

} else {

cache[key] = vnode

keys.push(key)

// prune oldest entry

// 如果缓存中的组件个数超过传入的max,销毁缓存中的LRU组件

// LRU: least recently used 最近最少用,缓存淘汰策略

if (this.max && keys.length > parseInt(this.max)) {

pruneCacheEntry(cache, keys[0], keys, this._vnode)

}

}

vnode.data.keepAlive = true

}

// 若第一个有效的组件存在,但其componentOptions不存在,就返回这个组件进行渲染

// 或若也不存在有效的第一个组件,但keep-alive组件的默认插槽存在,就返回默认插槽的第一个组件进行渲染

return vnode || (slot && slot[0])

}

代码做了详细的注释,这里再分析下render做了什么。

通过this.$slots.default拿到插槽组件,也就是keep-alive包裹的组件,getFirstComponentChild获取第一个子组件,获取该组件的name(存在组件名则直接使用组件名,否则会使用tag)。接下来会将这个name通过include与exclude属性进行匹配,匹配不成功(说明不需要进行缓存)则不进行任何操作直接返回vnode(vnode节点描述对象,vue通过vnode创建真实的DOM)

匹配到了就开始缓存,根据key在this.cache中查找,如果存在则说明之前已经缓存过了,直接将缓存的vnode的componentInstance(组件实例)覆盖到目前的vnode上面。否则将vnode存储在cache中。并且通过remove(keys, key),将当前的key从keys中删除再重新keys.push(key),这样就改变了当前key在keys中的位置。这个是为了实现max的功能,并且遵循缓存淘汰策略。

如果没匹配到,说明没缓存过,这时候需要进行缓存,并且判断当前缓存的个数是否超过max指定的个数,如果超过,则销毁keys里的最后一个组件,并从keys中移除,这个就是LRU(Least Recently Used :最近最少使用 )缓存淘汰算法。

最后返回vnode或者默认插槽的第一个组件进行DOM渲染。

12.虚拟dom和diff算法


虚拟DOM是对DOM的描述,用对象属性来描述节点,本质上是JavaScript对象。它有几个意义:

  1. 具备跨平台的优势

由于 Virtual DOM 是以 JavaScript 对象为基础而不依赖真实平台环境,所以使它具有了跨平台的能力,比如说浏览器、小程序、Node、原生应用、服务端渲染等等。

  1. 提升渲染性能

频繁变动DOM会造成浏览器的回流或者重回,而通过将大量的DOM操作搬运到Javascript中,运用patching算法来计算出真正需要更新的节点,可以减少真实DOM的操作次数,从而提高性能。

  1. 代码可维护性更高

通过虚拟 DOM 的抽象能力,可以用声明式写 UI 的方式,大大提高了我们的工作效率。

在vue中template最终会转成render函数,而render函数最终是执行的createElement,生成vnode,vnode正是 vue中用来表示虚拟DOM的类,看下vnode:

class VNode {

tag: string | void;

data: VNodeData | void;

children: ?Array;

text: string | void;

elm: Node | void;

ns: string | void;

context: Component | void; // rendered in this component’s scope

key: string | number | void;

componentOptions: VNodeComponentOptions | void;

componentInstance: Component | void; // component instance

parent: VNode | void; // component placeholder node

// strictly internal

raw: boolean; // contains raw HTML? (server only)

isStatic: boolean; // hoisted static node

isRootInsert: boolean; // necessary for enter transition check

isComment: boolean; // empty comment placeholder?

isCloned: boolean; // is a cloned node?

isOnce: boolean; // is a v-once node?

asyncFactory: Function | void; // async component factory function

asyncMeta: Object | void;

isAsyncPlaceholder: boolean;

ssrContext: Object | void;

fnContext: Component | void; // real context vm for functional nodes

fnOptions: ?ComponentOptions; // for SSR caching

devtoolsMeta: ?Object; // used to store functional render context for devtools

fnScopeId: ?string; // functional scope id support

constructor (

tag?: string,

data?: VNodeData,

children?: ?Array,

text?: string,

elm?: Node,

context?: Component,

componentOptions?: VNodeComponentOptions,

asyncFactory?: Function

) {

this.tag = tag

this.data = data

this.children = children

this.text = text

this.elm = elm

this.ns = undefined

this.context = context

this.fnContext = undefined

this.fnOptions = undefined

this.fnScopeId = undefined

this.key = data && data.key

this.componentOptions = componentOptions

this.componentInstance = undefined

this.parent = undefined

this.raw = false

this.isStatic = false

this.isRootInsert = true

this.isComment = false

this.isCloned = false

this.isOnce = false

this.asyncFactory = asyncFactory

this.asyncMeta = undefined

this.isAsyncPlaceholder = false

}

// DEPRECATED: alias for componentInstance for backwards compat.

/* istanbul ignore next */

get child (): Component | void {

return this.componentInstance

}

}

看下其中关键的几个属性:

  • tag: 当前节点的标签名

  • data: 表示节点上的class,attribute,style以及绑定的事件

  • children: 当前节点的子节点,是一个数组

  • text: 当前节点的文本

  • elm: 当前虚拟节点对应的真实dom节点

  • key: 节点的key属性,被当作节点的标志,用以优化

  • componentOptions: 组件的option选项

  • componentInstance: 当前节点对应的组件的实例

  • parent: 当前节点的父节点

  • isStatic: 是否为静态节点

children和parent是指当前的vnode的子节点和父节点,这样一个个vnode就形成了DOM树。

diff算法发生在视图更新的时候,也就是数据更新的时候,diff算法会将新旧虚拟DOM作对比,将变化的地方转换为DOM

当某个数据被修改的时候,依赖对应的watcher会通知更新,执行渲染函数会生成新的vnode,vnode再去与旧的vnode进行对比更新,这就是vue中的虚拟dom diff算法触发的流程。

看下组件更新的_update方法:

Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {

const vm: Component = this

const prevEl = vm.$el

const prevVnode = vm._vnode

const restoreActiveInstance = setActiveInstance(vm)

vm._vnode = vnode

// Vue.prototype.patch is injected in entry points

// based on the rendering backend used.

if (!prevVnode) {

// initial render

vm.KaTeX parse error: Expected group after '_' at position 9: el = vm._̲_patch__(vm.el, vnode, hydrating, false /* removeOnly */)

} else {

// updates

vm.$el = vm.patch(prevVnode, vnode)

}

}

vm.$el = vm._patch(),这个就是最终渲染的DOM元素,patch就是vue中diff算法的函数,在key的作用章节有提过。patch将新旧虚拟DOM节点比较后,最终返回真实的DOM节点。

patch

看下patch代码(部分):

function patch (oldVnode, vnode, hydrating, removeOnly, parentElm, refElm) {

/vnode不存在则直接调用销毁钩子/

if (isUndef(vnode)) {

if (isDef(oldVnode)) invokeDestroyHook(oldVnode)

return

}

let isInitialPatch = false

const insertedVnodeQueue = []

if (isUndef(oldVnode)) {

// empty mount (likely as component), create new root element

isInitialPatch = true

createElm(vnode, insertedVnodeQueue, parentElm, refElm)

} else {

/标记旧的VNode是否有nodeType/

/Github:https://github.com/answershuto/

const isRealElement = isDef(oldVnode.nodeType)

if (!isRealElement && sameVnode(oldVnode, vnode)) {

// patch existing root node

/是同一个节点的时候直接修改现有的节点/

patchVnode(oldVnode, vnode, insertedVnodeQueue, removeOnly)

return vnode.elm

首先是判断是否有新的vnode,没有代表是要销毁旧的vnode,调用销毁组件的钩子。

然后判断是否有旧的vnode,没有代表是新增,也就是新建root节点。

接下来判断旧的vnode是否是真实的元素,而不是组件,如果是组件并且用someVnode判断新旧节点是否是相同的节点(sameVnode在key的作用章节有做解析),是进行patchVnode,这时候进行真正的新老节点的diff。只有相同的节点才会进行diff算法!!!

patchVnode

function patchVnode (

oldVnode,

vnode,

insertedVnodeQueue,

ownerArray,

index,

removeOnly

) {

// 两个vnode相同,说明不需要diff,直接返回

if (oldVnode === vnode) {

return

}

// 如果传入了ownerArray和index,可以进行重用vnode,updateChildren里用来替换位置

if (isDef(vnode.elm) && isDef(ownerArray)) {

// clone reused vnode

vnode = ownerArray[index] = cloneVNode(vnode)

}

const elm = vnode.elm = oldVnode.elm

// 如果oldVnode的isAsyncPlaceholder属性为true时,跳过检查异步组件,return

if (isTrue(oldVnode.isAsyncPlaceholder)) {

if (isDef(vnode.asyncFactory.resolved)) {

hydrate(oldVnode.elm, vnode, insertedVnodeQueue)

} else {

vnode.isAsyncPlaceholder = true

}

return

}

/*

如果新旧VNode都是静态的,同时它们的key相同(代表同一节点),

并且新的VNode是clone或者是标记了once(标记v-once属性,只渲染一次),

那么只需要替换elm以及componentInstance即可。

*/

if (isTrue(vnode.isStatic) &&

isTrue(oldVnode.isStatic) &&

vnode.key === oldVnode.key &&

(isTrue(vnode.isCloned) || isTrue(vnode.isOnce))

) {

vnode.componentInstance = oldVnode.componentInstance

return

}

let i

const data = vnode.data

if (isDef(data) && isDef(i = data.hook) && isDef(i = i.prepatch)) {

i(oldVnode, vnode)

}

const oldCh = oldVnode.children

const ch = vnode.children

if (isDef(data) && isPatchable(vnode)) {

for (i = 0; i < cbs.update.length; ++i) cbs.update[i](oldVnode, vnode)

if (isDef(i = data.hook) && isDef(i = i.update)) i(oldVnode, vnode)

}

/如果这个VNode节点没有text文本时/

if (isUndef(vnode.text)) {

if (isDef(oldCh) && isDef(ch)) {

// 两个vnode都定义了子节点,并且不相同,就对子节点进行diff

if (oldCh !== ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly)

} else if (isDef(ch)) {

// 如果只有新的vnode定义了子节点,则进行添加子节点的操作

if (process.env.NODE_ENV !== ‘production’) {

checkDuplicateKeys(ch)

}

if (isDef(oldVnode.text)) nodeOps.setTextContent(elm, ‘’)

addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue)

} else if (isDef(oldCh)) {

// 如果只有旧的vnode定义了子节点,则进行删除子节点的操作

removeVnodes(oldCh, 0, oldCh.length - 1)

} else if (isDef(oldVnode.text)) {

nodeOps.setTextContent(elm, ‘’)

}

} else if (oldVnode.text !== vnode.text) {

nodeOps.setTextContent(elm, vnode.text)

}

if (isDef(data)) {

if (isDef(i = data.hook) && isDef(i = i.postpatch)) i(oldVnode, vnode)

}

}

通过代码可知,patchVnode分为多种情况,分析下子节点的diff过程 (oldCh 为 oldVnode的子节点,ch 为 Vnode的子节点)

  1. oldCh、ch都定义了调用updateChildren再进行diff

  2. 若 oldCh不存在,ch 存在,首先清空 oldVnode 的文本节点,同时调用 addVnodes 方法将 ch 添加到elm真实 dom 节点当中

  3. 若 oldCh存在,ch不存在,则删除 elm 真实节点下的 oldCh 子节点

  4. 若 oldVnode 有文本节点,而 vnode 没有,那么就清空这个文本节点

updateChildren是子节点diff的函数,也是最重要的环节。

updateChildren

function updateChildren (parentElm, oldCh, newCh, insertedVnodeQueue, removeOnly) {

// 声明oldCh和newCh的头尾索引和头尾的vnode,

let oldStartIdx = 0

let newStartIdx = 0

let oldEndIdx = oldCh.length - 1

let oldStartVnode = oldCh[0]

let oldEndVnode = oldCh[oldEndIdx]

let newEndIdx = newCh.length - 1

let newStartVnode = newCh[0]

let newEndVnode = newCh[newEndIdx]

let oldKeyToIdx, idxInOld, vnodeToMove, refElm

const canMove = !removeOnly

if (process.env.NODE_ENV !== ‘production’) {

checkDuplicateKeys(newCh)

}

while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {

if (isUndef(oldStartVnode)) {

oldStartVnode = oldCh[++oldStartIdx] // Vnode has been moved left

} else if (isUndef(oldEndVnode)) {

oldEndVnode = oldCh[–oldEndIdx]

// 判断两边的头是不是相同节点

} else if (sameVnode(oldStartVnode, newStartVnode)) {

patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)

oldStartVnode = oldCh[++oldStartIdx]

newStartVnode = newCh[++newStartIdx]

// 判断尾部是不是相同节点

} else if (sameVnode(oldEndVnode, newEndVnode)) {

patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue, newCh, newEndIdx)

oldEndVnode = oldCh[–oldEndIdx]

newEndVnode = newCh[–newEndIdx]

// 判断旧节点头部是不是与新节点的尾部相同,相同则把头部往右移

} else if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right

patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue, newCh, newEndIdx)

canMove && nodeOps.insertBefore(parentElm, oldStartVnode.elm, nodeOps.nextSibling(oldEndVnode.elm))

oldStartVnode = oldCh[++oldStartIdx]

newEndVnode = newCh[–newEndIdx]

// 判断旧节点尾部是不是与新节点的头部相同,相同则把头部往左移

} else if (sameVnode(oldEndVnode, newStartVnode)) { // Vnode moved left

patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)

canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm)

oldEndVnode = oldCh[–oldEndIdx]

newStartVnode = newCh[++newStartIdx]

} else {

/*

生成一个key与旧VNode的key对应的哈希表

*/

if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx)

idxInOld = isDef(newStartVnode.key)

? oldKeyToIdx[newStartVnode.key]
findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx)

if (isUndef(idxInOld)) { // New element

createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx)

} else {

vnodeToMove = oldCh[idxInOld]

if (sameVnode(vnodeToMove, newStartVnode)) {

patchVnode(vnodeToMove, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)

oldCh[idxInOld] = undefined

canMove && nodeOps.insertBefore(parentElm, vnodeToMove.elm, oldStartVnode.elm)

} else {

// same key but different element. treat as new element

createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx)

}

}

newStartVnode = newCh[++newStartIdx]

}

}

// oldCh或者newCh遍历完,说明剩下的节点不是新增就是删除

if (oldStartIdx > oldEndIdx) {

refElm = isUndef(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1].elm

addVnodes(parentElm, refElm, newCh, newStartIdx, newEndIdx, insertedVnodeQueue)

} else if (newStartIdx > newEndIdx) {

removeVnodes(oldCh, oldStartIdx, oldEndIdx)

}

}

首先给startIndex和endIndex来作为遍历的索引,在遍历的时候会先判断头尾节点是否相同,没有找到相同节点后再按照通用方式遍历查找;查找结束再按情况处理剩下的节点;借助key通常可以非常精确找到相同节点。

当oldCh 或者 newCh 遍历完后(遍历完的条件就是 oldCh 或者 newCh 的 startIndex >= endIndex ),说明剩下的节点为新增或者删除,这时候停止oldCh 和 newCh 的 diff。

13.Vuex原理


vuex是什么,先看下官方的原话:

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化

这段话可以得出几个结论:Vuex是为vue.js服务的,而像redux与react是解耦的,然后vuex是状态管理模式,所有的状态以一种可预测的方式发生变化。

设计思想:

Vuex的设计思想,借鉴了Flux、Redux,将数据存放到全局的store,再将store挂载到每个vue实例组件中,利用Vue.js的细粒度数据响应机制来进行高效的状态更新。

原理可以从使用方式开始分析。

Vue.use(Vuex); // 1. vue的插件机制,安装vuex

let store = new Vuex.Store({ // 2.实例化store,调用install方法

state,

getters,

modules,

mutations,

actions,

plugins

});

new Vue({ // 3.注入store, 挂载vue实例

store,

render: h=>h(app)

}).$mount(‘#app’);

Vue.use是vue中的插件机制,内部会调用插件的install方法,vuex的install方法:

export function install (_Vue) {

if (Vue) {

if (process.env.NODE_ENV !== ‘production’) {

console.error(

‘[vuex] already installed. Vue.use(Vuex) should be called only once.’

)

}

return

}

/保存Vue,同时用于检测是否重复安装/

Vue = _Vue

/将vuexInit混淆进Vue的beforeCreate(Vue2.0)或_init方法(Vue1.0)/

applyMixin(Vue)

}

vuex是个全局的状态管理,全局有且只能有一个store实例,所以在install的时候会判断是否已经安装过了,这个就是单例模式,确保一个类只有一个实例。在第一次install的时候会applyMixin,applyMixin是/src/mixin导入的方法:

function (Vue) {

const version = Number(Vue.version.split(‘.’)[0])

if (version >= 2) {

Vue.mixin({ beforeCreate: vuexInit })

} else {

// override init and inject vuex init procedure

// for 1.x backwards compatibility.

const _init = Vue.prototype._init

Vue.prototype._init = function (options = {}) {

options.init = options.init

? [vuexInit].concat(options.init)
vuexInit

_init.call(this, options)

}

}

/**

  • Vuex init hook, injected into each instances init hooks list.

*/

function vuexInit () {

const options = this.$options

// store injection

if (options.store) {

this.$store = typeof options.store === ‘function’

? options.store()
options.store

} else if (options.parent && options.parent.$store) {

this. s t o r e = o p t i o n s . p a r e n t . store = options.parent. store=options.parent.store

}

}

}

先是判断下vue的版本,这边分析vue2的逻辑。利用Vue.mixin混入的机制,在组件实例的beforeCreate调用vuexInit方法,首先判断options是否有store,没有代表是root节点,这时候要进行store初始化,没有的话就取父组件的$store赋值,这样就实现了全局共用唯一的store实例。

store实现的源码在src/store.js,其中最核心的是响应式的实现,通过resetStoreVM(this, state)调用,看下这个方法:

function resetStoreVM (store, state, hot) {

const oldVm = store._vm

// bind store public getters

store.getters = {}

// reset local getters cache

store._makeLocalGettersCache = Object.create(null)

const wrappedGetters = store._wrappedGetters

const computed = {}

forEachValue(wrappedGetters, (fn, key) => {

// use computed to leverage its lazy-caching mechanism

// direct inline function use will lead to closure preserving oldVm.

// using partial to return function with only arguments preserved in closure environment.

computed[key] = partial(fn, store)

Object.defineProperty(store.getters, key, {

get: () => store._vm[key],

enumerable: true // for local getters

})

})

// use a Vue instance to store the state tree

// suppress warnings just in case the user has added

// some funky global mixins

const silent = Vue.config.silent

Vue.config.silent = true

store._vm = new Vue({

data: {

$$state: state

},

computed

})

Vue.config.silent = silent

// enable strict mode for new vm

if (store.strict) {

enableStrictMode(store)

}

if (oldVm) {

if (hot) {

// dispatch changes in all subscribed watchers

// to force getter re-evaluation for hot reloading.

store._withCommit(() => {

oldVm._data.$$state = null

})

}

Vue.nextTick(() => oldVm.$destroy())

}

}

resetStoreVM首先会遍历wrappedGetters,使用Object.defineProperty方法对store.getters的每一个getter定义get方法,这样访问this.$store.getter.test就等同于访问store._vm.test。

state是通过new一个Vue对象来实现数据的“响应式化”,运用Vue的data属性来实现数据与视图的同步更新,computed实现getters的计算属性。最终访问store.state也就是访问store._vm.state。

最后

=============================================================

在面试前花了三个月时间刷了很多大厂面试题,最近做了一个整理并分类,主要内容包括html,css,JavaScript,ES6,计算机网络,浏览器,工程化,模块化,Node.js,框架,数据结构,性能优化,项目等等。

包含了腾讯、字节跳动、小米、阿里、滴滴、美团、58、拼多多、360、新浪、搜狐等一线互联网公司面试被问到的题目,涵盖了初中级前端技术点。

无偿分享给大家,算是一个感恩回馈吧,详细内容可以在文末自行获取哈!

HTML


  • HTML5新特性,语义化

  • 浏览器的标准模式和怪异模式

  • xhtml和html的区别

  • 使用data-的好处

  • meta标签

  • canvas

  • HTML废弃的标签

  • IE6 bug,和一些定位写法

  • css js放置位置和原因

  • 什么是渐进式渲染

  • html模板语言

  • meta viewport原理

CSS


  • 盒模型,box-sizing

  • CSS3新特性,伪类,伪元素,锚伪类

  • CSS实现隐藏页面的方式

  • 如何实现水平居中和垂直居中。

  • 说说position,display

  • 请解释*{box-sizing:border-box;}的作用,并说明使用它的好处

  • 浮动元素引起的问题和解决办法?绝对定位和相对定位,元素浮动后的display值

  • link和@import引入css的区别

  • 解释一下css3的flexbox,以及适用场景

  • inline和inline-block的区别

  • 哪些是块级元素那些是行级元素,各有什么特点

  • grid布局

  • table布局的作用

  • 实现两栏布局有哪些方法?

  • css dpi

  • 你知道attribute和property的区别么

  • css布局问题?css实现三列布局怎么做?如果中间是自适应又怎么做?

  • 流式布局如何实现,响应式布局如何实现

  • 移动端布局方案

  • 实现三栏布局(圣杯布局,双飞翼布局,flex布局)

  • 清除浮动的原理

  • overflow:hidden有什么缺点?

  • padding百分比是相对于父级宽度还是自身的宽度

  • css3动画,transition和animation的区别,animation的属性,加速度,重力的模拟实现

  • CSS 3 如何实现旋转图片(transform: rotate)

  • sass less

  • 对移动端开发了解多少?(响应式设计、Zepto;@media、viewport、JavaScript 正则表达式判断平台。)

  • 什么是bfc,如何创建bfc?解决什么问题?

  • CSS中的长度单位(px,pt,rem,em,ex,vw,vh,vh,vmin,vmax)

  • CSS 选择器的优先级是怎样的?

  • 雪碧图

  • svg

  • 媒体查询的原理是什么?

  • CSS 的加载是异步的吗?表现在什么地方?

  • 常遇到的浏览器兼容性问题有哪些?常用的hack的技巧

  • 外边距合并

  • 解释一下“::before”和“:after”中的双冒号和单冒号的区别

JS


  • js的基本类型有哪些?引用类型有哪些?null和undefined的区别。

  • 如何判断一个变量是Array类型?如何判断一个变量是Number类型?(都不止一种)

  • Object是引用类型嘛?引用类型和基本类型有什么区别?哪个是存在堆哪一个是存在栈上面的?

  • JS常见的dom操作api

  • 解释一下事件冒泡和事件捕获

  • 事件委托(手写例子),事件冒泡和捕获,如何阻止冒泡?如何组织默认事件?

  • 对闭包的理解?什么时候构成闭包?闭包的实现方法?闭包的优缺点?

  • this有哪些使用场景?跟C,Java中的this有什么区别?如何改变this的值?

  • call,apply,bind

  • 显示原型和隐式原型,手绘原型链,原型链是什么?为什么要有原型链

  • 创建对象的多种方式

  • 实现继承的多种方式和优缺点

  • new 一个对象具体做了什么

  • 手写Ajax,XMLHttpRequest

  • 变量提升

  • 举例说明一个匿名函数的典型用例

  • 指出JS的宿主对象和原生对象的区别,为什么扩展JS内置对象不是好的做法?有哪些内置对象和内置函数?

  • attribute和property的区别

  • document load和document DOMContentLoaded两个事件的区别

  • === 和 == , [] === [], undefined === undefined,[] == [], undefined == undefined

  • typeof能够得到哪些值

  • 什么是“use strict”,好处和坏处

  • 函数的作用域是什么?js 的作用域有几种?

  • JS如何实现重载和多态

  • 常用的数组api,字符串api

  • 原生事件绑定(跨浏览器),dom0和dom2的区别?

  • 给定一个元素获取它相对于视图窗口的坐标

  • 如何实现图片滚动懒加载

  • js 的字符串类型有哪些方法? 正则表达式的函数怎么使用?

  • 深拷贝

  • 编写一个通用的事件监听函数

  • web端cookie的设置和获取

  • setTimeout和promise的执行顺序

  • JavaScript 的事件流模型都有什么?

  • navigator对象,location和history

  • js的垃圾回收机制

  • 内存泄漏的原因和场景

  • DOM事件的绑定的几种方式

  • DOM事件中target和currentTarget的区别

  • typeof 和 instanceof 区别,instanceof原理

  • js动画和css3动画比较

  • JavaScript 倒计时(setTimeout)

  • js处理异常

  • js的设计模式知道那些

  • 轮播图的实现,以及轮播图组件开发,轮播10000张图片过程

  • websocket的工作原理和机制。

  • 手指点击可以触控的屏幕时,是什么事件?

  • 什么是函数柯里化?以及说一下JS的API有哪些应用到了函数柯里化的实现?(函数柯里化一些了解,以及在* 函数式编程的应用,最后说了一下JS中bind函数和数组的reduce方法用到了函数柯里化。)

  • JS代码调试

框架


  • 使用过哪些框架?

  • zepto 和 jquery 是什么关系,有什么联系么?

  • jquery源码如何实现选择器的,为什么$取得的对象要设计成数组的形式,这样设计的目的是什么

  • jquery如何绑定事件,有几种类型和区别

  • 什么是MVVM,MVC,MVP

  • Vue和Angular的双向数据绑定原理

  • Vue,Angular组件间通信以及路由原理

  • react和vue的生命周期

  • react和vue的虚拟dom以及diff算法

  • vue的observer,watcher,compile

  • react和angular分别用在什么样的业务吗?性能方面和MVC层面上的区别

  • jQuery对象和JS的Element有什么区别

  • jQuery对象是怎么实现的

  • jQuery除了它封装了一些方法外,还有什么值得我们去学习和使用的?

  • jQuery的$(‘xxx’)做了什么事情

  • 介绍一下bootstrap的栅格系统是如何实现的

浏览器相关


  • 跨域,为什么JS会对跨域做出限制

  • 前端安全:xss,csrf…

  • 浏览器怎么加载页面的?script脚本阻塞有什么解决方法?defer和async的区别?

  • 浏览器强缓存和协商缓存

  • 浏览器的全局变量有哪些

  • 浏览器同一时间能够从一个域名下载多少资源

  • 按需加载,不同页面的元素判断标准

  • web存储、cookies、localstroge等的使用和区别

  • 浏览器的内核

  • 如何实现缓存机制?(从200缓存,到cache到etag再到)

  • 说一下200和304的理解和区别

  • 什么是预加载、懒加载

  • 一个 XMLHttpRequest 实例有多少种状态?

  • dns解析原理,输入网址后如何查找服务器

  • 服务器如何知道你?

  • 浏览器渲染过程

  • ie的某些兼容性问题

  • session

  • 拖拽实现

  • 拆解url的各部分

ES6


  • 谈一谈 promise

  • 所有的 ES6 特性你都知道吗?如果遇到一个东西不知道是 ES6 还是 ES5, 你该怎么区分它

  • es6的继承和es5的继承有什么区别

  • promise封装ajax

  • let const的优点

  • es6 generator 是什么,async/await 实现原理

  • ES6和node的commonjs模块化规范区别

  • 箭头函数,以及它的this

计算机网络


  • HTTP协议头含有哪些重要的部分,HTTP状态码

  • 网络url输入到输出怎么做?

  • 性能优化为什么要减少 HTTP 访问次数?

  • Http请求的过程与原理

  • https(对是https)有几次握手和挥手?https的原理。

  • http有几次挥手和握手?TLS的中文名?TLS在哪一网络层?

  • TCP连接的特点,TCP连接如何保证安全可靠的?

  • 为什么TCP连接需要三次握手,两次不可以吗,为什么

  • 为什么tcp要三次握手四次挥手?

  • tcp的三次握手和四次挥手画图(当场画写ack 和 seq的值)?

  • tcp与udp的区别

  • get和post的区别?什么情况下用到?

  • http2 与http1 的区别?

  • websocket

  • 什么是tcp流,什么是http流

  • babel是如何将es6代码编译成es5的

  • http2的持久连接和管线化

  • 域名解析时是tcp还是udp

  • 域名发散和域名收敛

  • Post一个file的时候file放在哪的?

  • HTTP Response的Header里面都有些啥?

工程化


  • 对webpack,gulp,grunt等有没有了解?对比。

  • webpack的入口文件怎么配置,多个入口怎么分割。

  • webpack的loader和plugins的区别

  • gulp的具体使用。

  • 前端工程化的理解、如何自己实现一个文件打包,比如一个JS文件里同时又ES5 和ES6写的代码,如何编译兼容他们

模块化


  • 对AMD,CMD,CommonJS有没有了解?

  • 为什么要模块化?不用的时候和用RequireJs的时候代码大概怎么写?

  • 说说有哪些模块化的库,有了解过模块化的发展的历史吗?

  • 分别说说同步和异步模块化的应用场景,说下AMD异步模块化实现的原理?

  • 如何将项目里面的所有的require的模块语法换成import的ES6的语法?

  • 使用模块化加载时,模块加载的顺序是怎样的,如果不知道,根据已有的知识,你觉得顺序应该是怎么样的?

Nodejs


  • 对nodejs有没有了解

  • Express 和 koa 有什么关系,有什么区别?

  • nodejs适合做什么样的业务?

  • nodejs与php,java有什么区别

  • Nodejs中的Stream和Buffer有什么区别?

  • node的异步问题是如何解决的?

  • node是如何实现高并发的?

  • 说一下 Nodejs 的 event loop 的原理

数据结构


  • 基本数据结构:(数组、队列、链表、堆、二叉树、哈希表等等)

  • 8种排序算法,原理,以及适用场景和复杂度

  • 说出越多越好的费波拉切数列的实现方法?

性能优化


  • cdn的用法是什么?什么时候用到?

  • 浏览器的页面优化?

  • 如何优化 DOM 操作的性能

  • 单页面应用有什么SEO方案?

  • 单页面应用首屏显示比较慢,原因是什么?有什么解决方案?

其他


  • 正则表达式

  • 前端渲染和后端渲染的优缺点

  • 数据库的四大特性,什么是原子性,表的关系

  • 你觉得前端体系应该是怎样的?

  • 一个静态资源要上线,里面有各种资源依赖,你如何平稳上线

  • 如果要你去实现一个前端模板引擎,你会怎么做

  • 知道流媒体查询吗?

ES6

  • 列举常用的ES6特性:

  • 箭头函数需要注意哪些地方?

  • let、const、var

  • 拓展:var方式定义的变量有什么样的bug?

  • Set数据结构

  • 拓展:数组去重的方法

  • 箭头函数this的指向。

  • 手写ES6 class继承。

开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】

微信小程序

  • 简单描述一下微信小程序的相关文件类型?

  • 你是怎么封装微信小程序的数据请求?

  • 有哪些参数传值的方法?

  • 你使用过哪些方法,来提高微信小程序的应用速度?

  • 小程序和原生App哪个好?

  • 简述微信小程序原理?

  • 分析微信小程序的优劣势

  • 怎么解决小程序的异步请求问题?

  • 23
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值