浮力不要白不要:前端面试真题前辈的经验荟萃
31 Vue.extend 作用和原理
官方解释:Vue.extend 使用基础 Vue 构造器,创建一个“子类”。参数是一个包含组件选项的对象。
其实就是一个子类构造器 是 Vue 组件的核心 api 实现思路就是使用原型继承的方法返回了 Vue 的子类 并且利用 mergeOptions 把传入组件的 options 和父类的 options 进行了合并
相关代码如下
export default function initExtend(Vue) { let cid = 0; //组件的唯一标识 // 创建子类继承Vue父类 便于属性扩展 Vue.extend = function (extendOptions) { // 创建子类的构造函数 并且调用初始化方法 const Sub = function VueComponent(options) { this._init(options); //调用Vue初始化方法 }; Sub.cid = cid++; Sub.prototype = Object.create(this.prototype); // 子类原型指向父类 Sub.prototype.constructor = Sub; //constructor指向自己 Sub.options = mergeOptions(this.options, extendOptions); //合并自己的options和父类的options return Sub; }; }
32 写过自定义指令吗 原理是什么
指令本质上是装饰器,是 vue 对 HTML 元素的扩展,给 HTML 元素增加自定义功能。vue 编译 DOM 时,会找到指令对象,执行指令的相关方法。
自定义指令有五个生命周期(也叫钩子函数),分别是 bind、inserted、update、componentUpdated、unbind
1. bind:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。 2. inserted:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。 3. update:被绑定于元素所在的模板更新时调用,而无论绑定值是否变化。通过比较更新前后的绑定值,可以忽略不必要的模板更新。 4. componentUpdated:被绑定元素所在模板完成一次更新周期时调用。 5. unbind:只调用一次,指令与元素解绑时调用。
「原理」
1.在生成 ast 语法树时,遇到指令会给当前元素添加 directives 属性
2.通过 genDirectives 生成指令代码
3.在 patch 前将指令的钩子提取到 cbs 中,在 patch 过程中调用对应的钩子
4.当执行指令对应钩子函数时,调用对应指令定义的方法
33 Vue 修饰符有哪些
「事件修饰符」
.stop 阻止事件继续传播 .prevent 阻止标签默认行为 .capture 使用事件捕获模式,即元素自身触发的事件先在此处处理,然后才交由内部元素进行处理 .self 只当在 event.target 是当前元素自身时触发处理函数 .once 事件将只会触发一次 .passive 告诉浏览器你不想阻止事件的默认行为 「v-model 的修饰符」 .lazy 通过这个修饰符,转变为在 change 事件再同步 .number 自动将用户的输入值转化为数值类型 .trim 自动过滤用户输入的首尾空格 「键盘事件的修饰符」 .enter .tab .delete (捕获“删除”和“退格”键) .esc .space .up .down .left .right 「系统修饰键」 .ctrl .alt .shift .meta 「鼠标按钮修饰符」 .left .right .middle
34 Vue 模板编译原理
Vue 的编译过程就是将 template 转化为 render 函数的过程 分为以下三步 第一步是将 模板字符串 转换成 element ASTs(解析器) 第二步是对 AST 进行静态节点标记,主要用来做虚拟DOM的渲染优化(优化器) 第三步是 使用 element ASTs 生成 render 函数代码字符串(代码生成器)
相关代码如下
export function compileToFunctions(template) { // 我们需要把html字符串变成render函数 // 1.把html代码转成ast语法树 ast用来描述代码本身形成树结构 不仅可以描述html 也能描述css以及js语法 // 很多库都运用到了ast 比如 webpack babel eslint等等 let ast = parse(template); // 2.优化静态节点 // 这个有兴趣的可以去看源码 不影响核心功能就不实现了 // if (options.optimize !== false) { // optimize(ast, options); // } // 3.通过ast 重新生成代码 // 我们最后生成的代码需要和render函数一样 // 类似_c('div',{id:"app"},_c('div',undefined,_v("hello"+_s(name)),_c('span',undefined,_v("world")))) // _c代表创建元素 _v代表创建文本 _s代表文Json.stringify--把对象解析成文本 let code = generate(ast); // 使用with语法改变作用域为this 之后调用render函数可以使用call改变this 方便code里面的变量取值 let renderFn = new Function(`with(this){return ${code}}`); return renderFn; }
35 生命周期钩子是如何实现的
Vue 的生命周期钩子核心实现是利用发布订阅模式先把用户传入的的生命周期钩子订阅好(内部采用数组的方式存储)然后在创建组件实例的过程中会一次执行对应的钩子方法(发布)
相关代码如下
export function callHook(vm, hook) { // 依次执行生命周期对应的方法 const handlers = vm.$options[hook]; if (handlers) { for (let i = 0; i < handlers.length; i++) { handlers[i].call(vm); //生命周期里面的this指向当前实例 } } } // 调用的时候 Vue.prototype._init = function (options) { const vm = this; vm.$options = mergeOptions(vm.constructor.options, options); callHook(vm, "beforeCreate"); //初始化数据之前 // 初始化状态 initState(vm); callHook(vm, "created"); //初始化数据之后 if (vm.$options.el) { vm.$mount(vm.$options.el); } };
36 函数式组件使用场景和原理
函数式组件与普通组件的区别
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 就是一个函数式组件
“高阶组件”——用于接收一个组件作为参数,返回一个被包装过的组件
相关代码如下
if (isTrue(Ctor.options.functional)) { // 带有functional的属性的就是函数式组件 return createFunctionalComponent(Ctor, propsData, data, context, children); } const listeners = data.on; data.on = data.nativeOn; installComponentHooks(data); // 安装组件相关钩子 (函数式组件没有调用此方法,从而性能高于普通组件) 37 能说下 vue-router 中常用的路由模式实现原理吗
「hash 模式」
location.hash 的值实际就是 URL 中#后面的东西 它的特点在于:hash 虽然出现 URL 中,但不会被包含在 HTTP 请求中,对后端完全没有影响,因此改变 hash 不会重新加载页面。 可以为 hash 的改变添加监听事件 window.addEventListener("hashchange", funcRef, false);
每一次改变 hash(window.location.hash),都会在浏览器的访问历史中增加一个记录利用 hash 的以上特点,就可以来实现前端路由“更新视图但不重新请求页面”的功能了
❝
特点:兼容性好但是不美观
❞
「history 模式」
利用了 HTML5 History Interface 中新增的 pushState() 和 replaceState() 方法。
这两个方法应用于浏览器的历史记录站,在当前已有的 back、forward、go 的基础之上,它们提供了对历史记录进行修改的功能。这两个方法有个共同的特点:当调用他们修改浏览器历史记录栈后,虽然当前 URL 改变了,但浏览器不会刷新页面,这就为单页应用前端路由“更新视图但不重新请求页面”提供了基础。
❝
特点:虽然美观,但是刷新会出现 404 需要后端进行配置