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渲染大量数据时应该怎么优化?说下你的思路!
分析
企业级项目中渲染大量数据的情况比较常见,因此这是一道非常好的综合实践题目。
回答
- 在大型企业级项目中经常需要渲染大量数据,此时很容易出现卡顿的情况。比如大数据量的表格、树
- 处理时要根据情况做不同处理:
- 可以采取分页的方式获取,避免渲染大量数据
- vue-virtual-scroller (opens new window)等虚拟滚动方案,只渲染视口范围内的数据
- 如果不需要更新,可以使用v-once方式只渲染一次
- 通过v-memo (opens new window)可以缓存结果,结合v-for使用,避免数据变化时不必要的VNode创建
- 可以采用懒加载方式,在用户需要的时候再加载数据,比如tree组件子树的懒加载
- 还是要看具体需求,首先从设计上避免大数据获取和渲染;实在需要这样做可以采用虚表的方式优化渲染;最后优化更新,如果不需要更新可以v-once处理,需要更新可以v-memo进一步优化大数据更新性能。其他可以采用的是交互方式优化,无线滚动、懒加载等方案
scoped样式穿透
scoped虽然避免了组件间样式污染,但是很多时候我们需要修改组件中的某个样式,但是又不想去除scoped属性
- 使用/deep/
<!-- Parent --><template><divclass="wrap"><Child /></div></template><stylelang="scss"scoped>.wrap /deep/ .box{ background: red; } </style><!-- Child --><template><divclass="box"></div></template>复制代码
- 使用两个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需求分析
- 如何实现这些需求
回答范例
- 官方说vuex是一个状态管理模式和库,并确保这些状态以可预期的方式变更。可见要实现一个vuex
- 要实现一个Store存储全局状态
- 要提供修改状态所需API:commit(type, payload), dispatch(type, payload)
- 实现Store时,可以定义Store类,构造函数接收选项options,设置属性state对外暴露状态,提供commit和dispatch修改属性state。这里需要设置state为响应式对象,同时将Store定义为一个Vue插件
- 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的区别
相同点:
- Virtual DOM。其中最大的一个相似之处就是都使用了Virtual DOM。(当然Vue是在Vue2.x才引用的)也就是能让我们通过操作数据的方式来改变真实的DOM状态。因为其实Virtual DOM的本质就是一个JS对象,它保存了对真实DOM的所有描述,是真实DOM的一个映射,所以当我们在进行频繁更新元素的时候,改变这个JS对象的开销远比直接改变真实DOM要小得多。
- 组件化的开发思想。第二点来说就是它们都提倡这种组件化的开发思想,也就是建议将应用分拆成一个个功能明确的模块,再将这些模块整合在一起以满足我们的业务需求。
- Props。Vue和React中都有props的概念,允许父组件向子组件传递数据。
- 构建工具、Chrome插件、配套框架。还有就是它们的构建工具以及Chrome插件、配套框架都很完善。比如构建工具,React中可以使用CRA,Vue中可以使用对应的脚手架vue-cli。对于配套框架Vue中有vuex、vue-router,React中有react-router、redux。
不同点
- 模版的编写。最大的不同就是模版的编写,Vue鼓励你去写近似常规HTML的模板,React推荐你使用JSX去书写。
- 状态管理与对象属性。在React中,应用的状态是比较关键的概念,也就是state对象,它允许你使用setState去更新状态。但是在Vue中,state对象并不是必须的,数据是由data属性在Vue对象中进行管理。
- 虚拟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是“路由实例”对象包括了路由的跳转方法,钩子函数等
为什么要使用异步组件
- 节省打包出的结果,异步组件分开打包,采用jsonp的方式进行加载,有效解决文件过大的问题。
- 核心就是包组件定义变成一个函数,依赖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 } 复制代码
函数式组件优势和原理
函数组件的特点
- 函数式组件需要在声明组件是指定 functional:true
- 不需要实例化,所以没有this,this通过render函数的第二个参数context来代替
- 没有生命周期钩子函数,不能使用计算属性,watch
- 不能通过$emit 对外暴露事件,调用事件只能通过context.listeners.click的方式调用外部传入的事件
- 因为函数式组件是没有实例化的,所以在外部通过ref去引用组件时,实际引用的是HTMLElement
- 函数式组件的props可以不用显示声明,所以没有在props里面声明的属性都会被自动隐式解析为prop,而普通组件所有未声明的属性都解析到$attrs里面,并自动挂载到组件根元素上面(可以通过inheritAttrs属性禁止)
优点
- 由于函数式组件不需要实例化,无状态,没有生命周期,所以渲染性能要好于普通组件
- 函数式组件结构比较简单,代码结构更清晰
使用场景:
- 一个简单的展示组件,作为容器组件使用 比如 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