【面试题】2023前端vue面试题及答案(一)

简介: 【面试题】2023前端vue面试题及答案(一)

Vue3.0 为什么要用 proxy?

Vue2 中, 0bject.defineProperty 会改变原始数据,而 Proxy 是创建对象的虚拟表示,并提供 set 、get 和 deleteProperty 等处理器,这些处理器可在访问或修改原始对象上的属性时进行拦截,有以下特点∶

  • 不需用使用 Vue.set或Vue.set 或 Vue.delete 触发响应式
  • 全方位的数组变化检测,消除了Vue2 无效的边界情况。
  • 支持 Map,Set,WeakMap 和 WeakSet。

Proxy 实现的响应式原理与 Vue2的实现原理相同,实现方式大同小异∶

  • get 收集依赖
  • Set、delete 等触发依赖
  • 对于集合类型,就是对集合对象的方法做一层包装:原方法执行后执行依赖相关的收集或触发逻辑。

大厂面试题分享 面试题库

后端面试题库 (面试必备) 推荐:★★★★★

地址:前端面试题库

说说你对slot的理解?slot使用场景有哪些

一、slot是什么

在HTML中 slot 元素 ,作为 Web Components 技术套件的一部分,是Web组件内的一个占位符

该占位符可以在后期使用自己的标记语言填充

举个栗子

<templateid="element-details-template"><slotname="element-name">Slot template</slot></template><element-details><spanslot="element-name">1</span></element-details><element-details><spanslot="element-name">2</span></element-details>复制代码

template不会展示到页面中,需要用先获取它的引用,然后添加到DOM中,

customElements.define('element-details',
  classextendsHTMLElement {
    constructor() {
      super();
      const template = document
        .getElementById('element-details-template')
        .content;
      const shadowRoot = this.attachShadow({mode: 'open'})
        .appendChild(template.cloneNode(true));
  }
})
复制代码

在Vue中的概念也是如此

Slot 艺名插槽,花名“占坑”,我们可以理解为solt在组件模板中占好了位置,当使用该组件标签时候,组件标签里面的内容就会自动填坑(替换组件模板中slot位置),作为承载分发内容的出口

二、使用场景

通过插槽可以让用户可以拓展组件,去更好地复用组件和对其做定制化处理

如果父组件在使用到一个复用组件的时候,获取这个组件在不同的地方有少量的更改,如果去重写组件是一件不明智的事情

通过slot插槽向组件内部指定位置传递内容,完成这个复用组件在不同场景的应用

比如布局组件、表格列、下拉选、弹框显示内容等

使用vue渲染大量数据时应该怎么优化?说下你的思路!

分析

企业级项目中渲染大量数据的情况比较常见,因此这是一道非常好的综合实践题目。

回答

  1. 在大型企业级项目中经常需要渲染大量数据,此时很容易出现卡顿的情况。比如大数据量的表格、树
  2. 处理时要根据情况做不同处理:
  • 可以采取分页的方式获取,避免渲染大量数据
  • vue-virtual-scroller (opens new window)等虚拟滚动方案,只渲染视口范围内的数据
  • 如果不需要更新,可以使用v-once方式只渲染一次
  • 通过v-memo (opens new window)可以缓存结果,结合v-for使用,避免数据变化时不必要的VNode创建
  • 可以采用懒加载方式,在用户需要的时候再加载数据,比如tree组件子树的懒加载
  1. 还是要看具体需求,首先从设计上避免大数据获取和渲染;实在需要这样做可以采用虚表的方式优化渲染;最后优化更新,如果不需要更新可以v-once处理,需要更新可以v-memo进一步优化大数据更新性能。其他可以采用的是交互方式优化,无线滚动、懒加载等方案

scoped样式穿透

scoped虽然避免了组件间样式污染,但是很多时候我们需要修改组件中的某个样式,但是又不想去除scoped属性
  1. 使用/deep/
<!-- Parent --><template><divclass="wrap"><Child /></div></template><stylelang="scss"scoped>.wrap /deep/ .box{
    background: red;
}
</style><!-- Child --><template><divclass="box"></div></template>复制代码
  1. 使用两个style标签
<!-- Parent --><template><divclass="wrap"><Child /></div></template><stylelang="scss"scoped>/* 其他样式 */</style><stylelang="scss">.wrap.box{
  background: red;
}
</style><!-- Child --><template><divclass="box"></div></template>复制代码

Vue中v-html会导致哪些问题

  • 可能会导致 xss 攻击
  • v-html 会替换掉标签内部的子元素
let template = require('vue-template-compiler'); 
let r = template.compile(`<div v-html="'<span>hello</span>'"></div>`) 
// with(this){return _c('div',{domProps: {"innerHTML":_s('<span>hello</span>')}})} console.log(r.render);
// _c 定义在core/instance/render.js // _s 定义在core/instance/render-helpers/index,jsif (key === 'textContent' || key === 'innerHTML') { 
    if (vnode.children) vnode.children.length = 0if (cur === oldProps[key]) continue// #6601 work around Chrome version <= 55 bug where single textNode // replaced by innerHTML/textContent retains its parentNode property if (elm.childNodes.length === 1) { 
        elm.removeChild(elm.childNodes[0]) 
    } 
}
复制代码

如果让你从零开始写一个vuex,说说你的思路

思路分析

这个题目很有难度,首先思考vuex解决的问题:存储用户全局状态并提供管理状态API。

  • vuex需求分析
  • 如何实现这些需求

回答范例

  1. 官方说vuex是一个状态管理模式和库,并确保这些状态以可预期的方式变更。可见要实现一个vuex
  • 要实现一个Store存储全局状态
  • 要提供修改状态所需API:commit(type, payload), dispatch(type, payload)
  1. 实现Store时,可以定义Store类,构造函数接收选项options,设置属性state对外暴露状态,提供commit和dispatch修改属性state。这里需要设置state为响应式对象,同时将Store定义为一个Vue插件
  2. commit(type, payload)方法中可以获取用户传入mutations并执行它,这样可以按用户提供的方法修改状态。 dispatch(type, payload)类似,但需要注意它可能是异步的,需要返回一个Promise给用户以处理异步结果

实践

Store的实现:

classStore {
    constructor(options) {
        this.state = reactive(options.state)
        this.options = options
    }
    commit(type, payload) {
        this.options.mutations[type].call(this, this.state, payload)
    }
}
复制代码

vuex简易版

/**
 * 1 实现插件,挂载$store
 * 2 实现store
 */letVue;
classStore {
  constructor(options) {
    // state响应式处理// 外部访问: this.$store.state.***// 第一种写法// this.state = new Vue({//   data: options.state// })// 第二种写法:防止外界直接接触内部vue实例,防止外部强行变更this._vm = newVue({
      data: {
        $$state: options.state
      }
    })
    this._mutations = options.mutationsthis._actions = options.actionsthis.getters = {}
    options.getters && this.handleGetters(options.getters)
    this.commit = this.commit.bind(this)
    this.dispatch = this.dispatch.bind(this)
  }
  get state () {
    returnthis._vm._data.$$state
  }
  set state (val) {
    returnnewError('Please use replaceState to reset state')
  }
  handleGetters (getters) {
    Object.keys(getters).map(key => {
      Object.defineProperty(this.getters, key, {
        get: () => getters[key](this.state)
      })
    })
  }
  commit (type, payload) {
    let entry = this._mutations[type]
    if (!entry) {
      returnnewError(`${type} is not defined`)
    }
    entry(this.state, payload)
  }
  dispatch (type, payload) {
    let entry = this._actions[type]
    if (!entry) {
      returnnewError(`${type} is not defined`)
    }
    entry(this, payload)
  }
}
constinstall = (_Vue) => {
  Vue = _Vue
  Vue.mixin({
    beforeCreate () {
      if (this.$options.store) {
        Vue.prototype.$store = this.$options.store
      }
    },
  })
}
exportdefault { Store, install }
复制代码

验证方式

importVuefrom'vue'importVuexfrom'./vuex'// this.$storeVue.use(Vuex)
exportdefaultnewVuex.Store({
  state: {
    counter: 0
  },
  mutations: {
    // state从哪里来的
    add (state) {
      state.counter++
    }
  },
  getters: {
    doubleCounter (state) {
      return state.counter * 2
    }
  },
  actions: {
    add ({ commit }) {
      setTimeout(() => {
        commit('add')
      }, 1000)
    }
  },
  modules: {
  }
})
复制代码

参考 前端进阶面试题详细解答

Vue与Angular以及React的区别?

Vue与AngularJS的区别
  • Angular采用TypeScript开发, 而Vue可以使用javascript也可以使用TypeScript
  • AngularJS依赖对数据做脏检查,所以Watcher越多越慢;Vue.js使用基于依赖追踪的观察并且使用异步队列更新,所有的数据都是独立触发的。
  • AngularJS社区完善, Vue的学习成本较小
Vue与React的区别

相同点:

  1. Virtual DOM。其中最大的一个相似之处就是都使用了Virtual DOM。(当然Vue是在Vue2.x才引用的)也就是能让我们通过操作数据的方式来改变真实的DOM状态。因为其实Virtual DOM的本质就是一个JS对象,它保存了对真实DOM的所有描述,是真实DOM的一个映射,所以当我们在进行频繁更新元素的时候,改变这个JS对象的开销远比直接改变真实DOM要小得多。
  2. 组件化的开发思想。第二点来说就是它们都提倡这种组件化的开发思想,也就是建议将应用分拆成一个个功能明确的模块,再将这些模块整合在一起以满足我们的业务需求。
  3. Props。Vue和React中都有props的概念,允许父组件向子组件传递数据。
  4. 构建工具、Chrome插件、配套框架。还有就是它们的构建工具以及Chrome插件、配套框架都很完善。比如构建工具,React中可以使用CRA,Vue中可以使用对应的脚手架vue-cli。对于配套框架Vue中有vuex、vue-router,React中有react-router、redux。

不同点

  1. 模版的编写。最大的不同就是模版的编写,Vue鼓励你去写近似常规HTML的模板,React推荐你使用JSX去书写。
  2. 状态管理与对象属性。在React中,应用的状态是比较关键的概念,也就是state对象,它允许你使用setState去更新状态。但是在Vue中,state对象并不是必须的,数据是由data属性在Vue对象中进行管理。
  3. 虚拟DOM的处理方式不同。Vue中的虚拟DOM控制了颗粒度,组件层面走watcher通知,而组件内部走vdom做diff,这样,既不会有太多watcher,也不会让vdom的规模过大。而React走了类似于CPU调度的逻辑,把vdom这棵树,微观上变成了链表,然后利用浏览器的空闲时间来做diff

Vue项目中你是如何解决跨域的呢

一、跨域是什么

跨域本质是浏览器基于同源策略的一种安全手段

同源策略(Sameoriginpolicy),是一种约定,它是浏览器最核心也最基本的安全功能

所谓同源(即指在同一个域)具有以下三个相同点

  • 协议相同(protocol)
  • 主机相同(host)
  • 端口相同(port)

反之非同源请求,也就是协议、端口、主机其中一项不相同的时候,这时候就会产生跨域

一定要注意跨域是浏览器的限制,你用抓包工具抓取接口数据,是可以看到接口已经把数据返回回来了,只是浏览器的限制,你获取不到数据。用postman请求接口能够请求到数据。这些再次印证了跨域是浏览器的限制。

Class 与 Style 如何动态绑定

Class 可以通过对象语法和数组语法进行动态绑定

对象语法:

<div v-bind:class="{ active: isActive, 'text-danger': hasError }"></div>
data: {
  isActive: true,
  hasError: false
}
复制代码

数组语法:

<div v-bind:class="[isActive ? activeClass : '', errorClass]"></div>
data: {
  activeClass: 'active',
  errorClass: 'text-danger'
}
复制代码

Style 也可以通过对象语法和数组语法进行动态绑定

对象语法:

<div v-bind:style="{ color: activeColor, fontSize: fontSize + 'px' }"></div>
data: {
  activeColor: 'red',
  fontSize: 30
}
复制代码

数组语法:

<div v-bind:style="[styleColor, styleSize]"></div>
data: {
  styleColor: {
     color: 'red'
   },
  styleSize:{
     fontSize:'23px'
  }
}
复制代码

了解history有哪些方法吗?说下它们的区别

history 这个对象在html5的时候新加入两个api history.pushState() 和 history.repalceState() 这两个API可以在不进行刷新的情况下,操作浏览器的历史纪录。唯一不同的是,前者是新增一个历史记录,后者是直接替换当前的历史记录。

从参数上来说:

window.history.pushState(state,title,url)
//state:需要保存的数据,这个数据在触发popstate事件时,可以在event.state里获取//title:标题,基本没用,一般传null//url:设定新的历史纪录的url。新的url与当前url的origin必须是一样的,否则会抛出错误。url可以时绝对路径,也可以是相对路径。//如 当前url是 https://www.baidu.com/a/,执行history.pushState(null, null, './qq/'),则变成 https://www.baidu.com/a/qq/,//执行history.pushState(null, null, '/qq/'),则变成 https://www.baidu.com/qq/window.history.replaceState(state,title,url)
//与pushState 基本相同,但她是修改当前历史纪录,而 pushState 是创建新的历史纪录复制代码

另外还有:

  • window.history.back() 后退
  • window.history.forward()前进
  • window.history.go(1) 前进或者后退几步

从触发事件的监听上来说:

  • pushState()和replaceState()不能被popstate事件所监听
  • 而后面三者可以,且用户点击浏览器前进后退键时也可以

在Vue中使用插件的步骤

  • 采用ES6的import ... from ...语法或CommonJS的require()方法引入插件
  • 使用全局方法Vue.use( plugin )使用插件,可以传入一个选项对象Vue.use(MyPlugin, { someOption: true })

route和route和router的区别

  • $route是“路由信息对象”,包括path,params,hash,query,fullPath,matched,name等路由信息参数。
  • 而$router是“路由实例”对象包括了路由的跳转方法,钩子函数等

为什么要使用异步组件

  1. 节省打包出的结果,异步组件分开打包,采用jsonp的方式进行加载,有效解决文件过大的问题。
  2. 核心就是包组件定义变成一个函数,依赖import() 语法,可以实现文件的分割加载。
components:{ 
  AddCustomerSchedule:(resolve)=>import("../components/AddCustomer") // require([]) 
}
复制代码

原理

exportfunction ( Ctor: Class<Component> | Function | Object | void, data: ?VNodeData, context: Component, children: ?Array<VNode>, tag?: string ): VNode | Array<VNode> | void { 
    // async component let asyncFactory 
    if (isUndef(Ctor.cid)) { 
        asyncFactory = CtorCtor = resolveAsyncComponent(asyncFactory, baseCtor) // 默认调用此函数时返回 undefiend // 第二次渲染时Ctor不为undefined if (Ctor === undefined) { 
            returncreateAsyncPlaceholder( // 渲染占位符 空虚拟节点 
                asyncFactory, 
                data, 
                context, 
                children, 
                tag 
            ) 
        } 
    } 
}
functionresolveAsyncComponent ( factory: Function, baseCtor: Class<Component> ): Class<Component> | void { 
    if (isDef(factory.resolved)) { 
        // 3.在次渲染时可以拿到获取的最新组件 return factory.resolved 
    }
    const resolve = once((res: Object | Class<Component>) => { 
        factory.resolved = ensureCtor(res, baseCtor) 
        if (!sync) { 
            forceRender(true) //2. 强制更新视图重新渲染 
        } else { 
            owners.length = 0 
        } 
    })
    const reject = once(reason => { 
        if (isDef(factory.errorComp)) { 
            factory.error = trueforceRender(true) 
        } 
    })
    const res = factory(resolve, reject)// 1.将resolve方法和reject方法传入,用户调用 resolve方法后 
    sync = falsereturn factory.resolved 
}
复制代码

函数式组件优势和原理

函数组件的特点

  1. 函数式组件需要在声明组件是指定 functional:true
  2. 不需要实例化,所以没有this,this通过render函数的第二个参数context来代替
  3. 没有生命周期钩子函数,不能使用计算属性,watch
  4. 不能通过$emit 对外暴露事件,调用事件只能通过context.listeners.click的方式调用外部传入的事件
  5. 因为函数式组件是没有实例化的,所以在外部通过ref去引用组件时,实际引用的是HTMLElement
  6. 函数式组件的props可以不用显示声明,所以没有在props里面声明的属性都会被自动隐式解析为prop,而普通组件所有未声明的属性都解析到$attrs里面,并自动挂载到组件根元素上面(可以通过inheritAttrs属性禁止)

优点

  1. 由于函数式组件不需要实例化,无状态,没有生命周期,所以渲染性能要好于普通组件
  2. 函数式组件结构比较简单,代码结构更清晰

使用场景:

  • 一个简单的展示组件,作为容器组件使用 比如 router-view 就是一个函数式组件
  • “高阶组件”——用于接收一个组件作为参数,返回一个被包装过的组件

例子

Vue.component('functional',{ // 构造函数产生虚拟节点的functional:true, // 函数式组件 // data={attrs:{}}render(h){
        returnh('div','test')
    }
})
const vm = newVue({
    el: '#app'
})
复制代码

源码相关

// functional componentif (isTrue(Ctor.options.functional)) { // 带有functional的属性的就是函数式组件returncreateFunctionalComponent(Ctor, propsData, data, context, children)
}
// extract listeners, since these needs to be treated as// child component listeners instead of DOM listenersconst listeners = data.on// 处理事件// replace with listeners with .native modifier// so it gets processed during parent component patch.
data.on = data.nativeOn// 处理原生事件// install component management hooks onto the placeholder nodeinstallComponentHooks(data) // 安装组件相关钩子 (函数式组件没有调用此方法,从而性能高于普通组件)复制代码

Vue.set的实现原理


  • 给对应和数组本身都增加了dep属性
  • 当给对象新增不存在的属性则触发对象依赖的watcher去更新
  • 当修改数组索引时,我们调用数组本身的splice去更新数组(数组的响应式原理就是重新了splice等方法,调用splice就会触发视图更新)

基本使用

以下方法调用会改变原始数组:push(), pop(), shift(), unshift(), splice(), sort(), reverse(),Vue.set( target, key, value )
  • 调用方法:Vue.set(target, key, value )
  • target:要更改的数据源(可以是对象或者数组)
  • key:要更改的具体数据
  • value :重新赋的值
<divid="app">{{user.name}} {{user.age}}</div><divid="app"></div><script>// 1. 依赖收集的特点:给每个属性都增加一个dep属性,dep属性会进行收集,收集的是watcher// 2. vue会给每个对象也增加一个dep属性const vm = newVue({
        el: '#app',
        data: { // vm._data  user: {name:'poetry'}
        }
    });
    // 对象的话:调用defineReactive在user对象上定义一个age属性,增加到响应式数据中,触发对象本身的watcher,ob.dep.notify()更新 // 如果是数组 通过调用 splice方法,触发视图更新
    vm.$set(vm.user, 'age', 20); // 不能给根属性添加,因为给根添加属性 性能消耗太大,需要做很多处理// 修改肯定是同步的 -> 更新都是一步的  queuewatcher</script>复制代码

相关源码

// src/core/observer/index.js 44exportclassObserver { // new Observer(value)value: any;
  dep: Dep;
  vmCount: number; // number of vms that have this object as root $dataconstructor (value: any) {
    this.value = value
    this.dep = newDep() // 给所有对象类型增加dep属性
  }
}
复制代码
// src/core/observer/index.js 201exportfunctionset (target: Array<any> | Object, key: any, val: any): any {
  // 1.是开发环境 target 没定义或者是基础类型则报错if (process.env.NODE_ENV !== 'production' &&
    (isUndef(target) || isPrimitive(target))
  ) {
    warn(`Cannot set reactive property on undefined, null, or primitive value: ${(target: any)}`)
  }
  // 2.如果是数组 Vue.set(array,1,100); 调用我们重写的splice方法 (这样可以更新视图)if (Array.isArray(target) && isValidArrayIndex(key)) {
    target.length = Math.max(target.length, key)
    // 利用数组的splice变异方法触发响应式  
    target.splice(key, 1, val)
    return val
  }
  // 3.如果是对象本身的属性,则直接添加即可if (key in target && !(key inObject.prototype)) {
    target[key] = val // 直接修改属性值  return val
  }
  // 4.如果是Vue实例 或 根数据data时 报错,(更新_data 无意义)const ob = (target: any).__ob__if (target._isVue || (ob && ob.vmCount)) {
    process.env.NODE_ENV !== 'production' && warn(
      'Avoid adding reactive properties to a Vue instance or its root $data ' +
      'at runtime - declare it upfront in the data option.'
    )
    return val
  }
  // 5.如果不是响应式的也不需要将其定义成响应式属性if (!ob) {
    target[key] = val
    return val
  }
  // 6.将属性定义成响应式的defineReactive(ob.value, key, val)
  // 通知视图更新
  ob.dep.notify()
  return val
}
复制代码

大厂面试题分享 面试题库

后端面试题库 (面试必备) 推荐:★★★★★

地址:前端面试题库

【面试题】2023前端vue面试题及答案(二):https://developer.aliyun.com/article/1414418

相关文章
|
2天前
|
前端开发 JavaScript 网络协议
前端最常见的JS面试题大全
【4月更文挑战第3天】前端最常见的JS面试题大全
54 5
|
1天前
|
前端开发 JavaScript 开发工具
4(1),阿里面试官,前端开发面试题目
4(1),阿里面试官,前端开发面试题目
|
1天前
|
消息中间件 缓存 架构师
2024年阿里Android高级面试题分享,附学习笔记+面试整理+进阶书籍
2024年阿里Android高级面试题分享,附学习笔记+面试整理+进阶书籍
|
2天前
|
存储 缓存 安全
兄弟面试了百度,面试题分享一波
兄弟面试了百度,面试题分享一波
44 0
|
2天前
|
存储 缓存 安全
java锁优化高频面试题(真实面试经历总结)
java锁优化高频面试题(真实面试经历总结)
|
2天前
|
消息中间件 Java 关系型数据库
Java经常被问得面试题最常见的200+面试题
Java经常被问得面试题最常见的200+面试题
40 1
|
2天前
|
存储 缓存 监控
StarRocks面试题及答案整理,最新面试题
StarRocks面试题及答案整理,最新面试题
117 0
|
2天前
|
SQL 存储 监控
Navicat 面试题及答案整理,最新面试题
Navicat 面试题及答案整理,最新面试题
69 0
|
2天前
|
SQL 监控 大数据
DataGrip 面试题及答案整理,最新面试题
DataGrip 面试题及答案整理,最新面试题
77 0
|
2天前
|
存储 Java
面试官:素有Java锁王称号的‘StampedLock’你知道吗?我:这什么鬼?
面试官:素有Java锁王称号的‘StampedLock’你知道吗?我:这什么鬼?
44 23