vue核心面试题汇总【查缺补漏】_vue的面试问题核心重点

普通input:input中的v-model,最终通过target.addEventListener处理成在节点上监听input事件function($event){msg=$event.target.value}}的形式,当input值变化时msg也跟着改变。

// main.js
const inputBox = {
  template: `<input @input="$emit('input', $event.target.value)">`,
};

new Vue({
  el: "#app",
  template: `<div>
    <input-box v-model="msg"></input-box>
    <p>{{msg}}</p>
  </div>`,
  components: {
    inputBox
  },
  data() {
    return {
      msg: 'hello world!'
    };
  },
});

组件v-model在组件中则通过给点击事件绑定原生事件,当触发到$emit的时候,再进行回调函数ƒunction input($$v) {msg=$$v}的执行,进而达到子组件修改父组件中数据msg的目的。

二十四、v-slot

v-slot产生的主要目的是,在组件的使用过程中可以让父组件有修改子组件内容的能力,就像在子组件里面放了个插槽,让父组件往插槽内塞入父组件中的楔子;并且,父组件在子组件中嵌入的楔子也可以访问子组件中的数据。v-slot的产生让组件的应用更加灵活。

1、具名插槽
let baseLayout = {
  template: `<div class="container">
    <header>
      <slot name="header"></slot>
    </header>
    <main>
      <slot></slot>
    </main>
    <footer>
      <slot name="footer"></slot>
    </footer>
  </div>`,
  data() {
    return {
      url: ""
    };
  }
};

new Vue({
  el: "#app",
  template: `<base-layout>
    <template v-slot:header>
      <h1>title-txt</h1>
    </template>
    <p>paragraph-1-txt</p>
    <p>paragraph-2-txt</p>
    <template v-slot:footer>
      <p>foot-txt</p>
    </template>
  </base-layout>`,
  components: {
    baseLayout
  }
});

引入的组件baseLayout中的template被添加了属性v-slot:headerv-slot:footer,子组件中定义了对应的插槽被添加了属性name="header"name="footer",未被进行插槽标识的内容被插入到了匿名的<slot></slot>中。

2、作用域插槽
let currentUser = {
  template: `<span>
    <slot name="user" v-bind:userData="childData">{{childData.firstName}}</slot>
  </span>`,
  data() {
    return {
      childData: {
        firstName: "first",
        lastName: "last"
      }
    };
  }
};

new Vue({
  el: "#app",
  template: `<current-user>
    <template v-slot:user="slotProps">{{slotProps.userData.lastName}}</template>
  </current-user>`,
  components: {
    currentUser
  }
});

当前例子中作用域插槽通过v-bind:userData="childData"的方式,将childData作为参数,父组件中通过v-slot:user="slotProps"的方式进行接收,为父组件使用子组件中的数据提供了可能。

v-slot的底层实现请参考vue中的v-slot(源码分析)

二十五、Vue.filters

filters类似于管道流可以将上一个过滤函数的结果作为下一个过滤函数的第一个参数,又可以在其中传递参数让过滤器更灵活。

// main.js文件
import Vue from "vue";

Vue.filter("filterEmpty", function(val) {
  return val || "";
});

Vue.filter("filterA", function(val) {
  return val + "平时周末的";
});

Vue.filter("filterB", function(val, info, fn) {
  return val + info + fn;
});

new Vue({
  el: "#app",
  template: `<div>{{msg | filterEmpty | filterA | filterB('爱好是', transformHobby('chess'))}}</div>`,
  data() {
    return {
      msg: "张三"
    };
  },
  methods: {
    transformHobby(type) {
      const map = {
        bike: "骑行",
        chess: "象棋",
        game: "游戏",
        swimming: "游泳"
      };
      return map[type] || "未知";
    }
  }
});

其中我们对msg通过filterEmptyfilterAfilterB('爱好是', transformHobby('chess'))}进行三层过滤。

Vue.filters的底层实现请查看vue中的filters(源码分析)

二十六、Vue.use

  • 作用:Vue.use被用来安装Vue.js插件,例如vue-routervuexelement-ui
  • install方法:如果插件是一个对象,必须提供 install 方法。如果插件是一个函数,它会被作为install方法。install方法调用时,会将Vue作为参数传入。
  • 调用时机:该方法需要在调用 new Vue() 之前被调用。
  • 特点:当 install 方法被同一个插件多次调用,插件将只会被安装一次。

二十七、Vue.extend和选项extends

1、Vue.extend

Vue.extend使用基础Vue构造器创建一个“子类”,参数是一个包含组件选项的对象,实例化的过程中可以修改其中的选项,为实现功能的继承提供了思路。

new Vue({
  el: "#app",
  template: `<div><div id="person1"></div><div id="person2"></div></div>`,
  mounted() {
    // 定义子类构造函数
    var Profile = Vue.extend({
      template: '<p @click="showInfo">{{name}} 喜欢 {{fruit}}</p>',
      data: function () {
        return {
          name: '张三',
          fruit: '苹果'
        }
      },
      methods: {
        showInfo() {
          console.log(`${this.name}喜欢${this.fruit}`)
        }
      }
    })
    // 实例化1,挂载到`#person1`上
    new Profile().$mount('#person1')
    // 实例化2,并修改其`data`选项,挂载到`#person2`上
    new Profile({
      data: function () {
        return {
          name: '李四',
          fruit: '香蕉'
        }
      },
    }).$mount('#person2')
  },
});

在当前例子中,通过Vue.extend构建了子类构造函数Profile,可以通过new Profile的方式实例化无数个vm实例。我们定义初始的templatedatamethodsvm进行使用,如果有变化,在实例的过程中传入新的选项参数即可,比如例子中实例化第二个vm的时候就对data进行了调整。

2、选项extends

extends允许声明扩展另一个组件 (可以是一个简单的选项对象或构造函数),而无需使用 Vue.extend。这主要是为了便于扩展单文件组件,以实现组件继承的目的。

const common = {
  template: `<div>{{name}}</div>`,
  data() {
    return {
      name: '表单'
    }
  }
}

const create = {
  extends: common,
  data() {
    return {
      name: '新增表单'
    }
  }
}

const edit = {
  extends: common,
  data() {
    return {
      name: '编辑表单'
    }
  }
}

new Vue({
  el: "#app",
  template: `<div>
    <create></create>
    <edit></edit>
  </div>`,
  components: {
    create,
    edit,
  }
});

当前极简demo中定义了公共的表单common,然后又在新增表单组件create和编辑表单组件edit中扩展了common

二十八、Vue.mixin和选项mixins

全局混入和局部混入视情况而定,主要区别在全局混入是通过Vue.mixin的方式将选项混入到了Vue.options中,在所有获取子组件构建函数的时候都将其进行了合并,是一种影响全部组件的混入策略。

而局部混入是将选项通过配置mixins选项的方式合并到当前的子组件中,只有配置了mixins选项的组件才会受到混入影响,是一种局部的混入策略。

二十九、Vue.directivedirectives

1、使用场景

主要用于对于DOM的操作,比如:文本框聚焦,节点位置控制、防抖节流、权限管理、复制操作等功能

2、钩子函数
  • bind:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。
  • inserted:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。
  • update:所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新。
  • componentUpdated:指令所在组件的 VNode 及其子 VNode 全部更新后调用。
  • unbind:只调用一次,指令与元素解绑时调用。
3、钩子函数参数
  • el:指令所绑定的元素,可以用来直接操作 DOM。
  • binding:一个对象,包含以下 property:
    • name:指令名,不包括 v- 前缀。
    • value:指令的绑定值,例如:v-my-directive="1 + 1" 中,绑定值为 2
    • oldValue:指令绑定的前一个值,仅在 update 和 componentUpdated 钩子中可用。无论值是否改变都可用。
    • expression:字符串形式的指令表达式。例如 v-my-directive="1 + 1" 中,表达式为 "1 + 1"
    • arg:传给指令的参数,可选。例如 v-my-directive:foo 中,参数为 "foo"
    • modifiers:一个包含修饰符的对象。例如:v-my-directive.foo.bar 中,修饰符对象为 { foo: true, bar: true }
  • vnode:Vue 编译生成的虚拟节点。
  • oldVnode:上一个虚拟节点,仅在 update 和 componentUpdated 钩子中可用。
4、动态指令参数

指令的参数可以是动态的。例如,在 v-mydirective:[argument]="value" 中,argument 参数可以根据组件实例数据进行更新!这使得自定义指令可以在应用中被灵活使用。

三十、vue中的原生事件

vue中可以通过@或者v-on的方式绑定事件,也可为其添加修饰符。

new Vue({
  el: '#app',
  template: `<div @click='divClick'><a @clickt='aClick' href=''>点击</a></div>`,
  methods: {
    divClick() {
      console.log('divClick')
    },
    aClick() {
      console.log('aClick')
    },
  }
})

以上例子如果点击a会触发其默认行为,如果href不为空还会进行跳转。除此之外,点击还会继续触发div上绑定的点击事件。

如果通过@click.stop.prevent='aClick'的方式为a标签的点击事件添加修饰符stopprevent,那么就不会触发其a的默认行为,即使href不为空也不会进行跳转,同时,div上的点击事件也不会进行触发。

模板的渲染一般分为编译生成render函数、render函数执行生成vNodepatch进行渲染。下面按照这步骤进行简单分析。

1、render

通过编译生成的render函数:

with(this) {
    return _c('div', {
        on: {
            "click": divClick
        }
    }, [_c('a', {
        attrs: {
            "href": "http://www.baidu.com"
        },
        on: {
            "click": function ($event) {
                $event.stopPropagation();
                $event.preventDefault();
                return aClick($event)
            }
        }
    }, [_v("点击")])])
}

其中divon作为div事件描述。a标签的attrs作为属性描述,on作为事件描述,在描述中.stop被编译成了$event.stopPropagation()来阻止事件冒泡,.prevent被编译成了$event.preventDefault()用来阻止a标签的默认行为。

2、vNode

通过执行Vue.prototype._renderrender函数转换成vNode

3、patch

patch的过程中,当完成$el节点的渲染后会执行invokeCreateHooks(vnode, insertedVnodeQueue)逻辑,其中,针对attrs会将其设置为$el的真实属性,当前例子中会为a标签设置herf属性。针对on会通过target.addEventListener的方式将其处理过的事件绑定到$el上,当前例子中会分别对diva中的click进行处理,再通过addEventListener的方式进行绑定。

小结

vue中的事件,从编译生成render再通过Vue.prototype._render函数执行render到生成vNode,主要是通过on作为描述。在patch渲染阶段,将on描述的事件进行处理再通过addEventListener的方式绑定到$el上。

三十一、常用修饰符

1、表单修饰符
(1).lazy

在默认情况下,v-model 在每次 input 事件触发后将输入框的值与数据进行同步 ,可以添加 lazy 修饰符,从而转为在 change 事件之后进行同步:

<input v-model.lazy="msg">

(2).number

如果想自动将用户的输入值转为数值类型,可以给 v-model 添加 number 修饰符:

<input v-model.number="age" type="number">

(3).trim

如果要自动过滤用户输入的首尾空白字符,可以给 v-model 添加 trim 修饰符:

<input v-model.trim="msg">

2、事件修饰符
(1).stop

阻止单击事件继续传播。

<!--这里只会触发a-->
<div @click="divClick"><a v-on:click.stop="aClick">点击</a></div>

(2).prevent

阻止标签的默认行为。

<a href="http://www.baidu.com" v-on:click.prevent="aClick">点击</a>

(3).capture

事件先在有.capture修饰符的节点上触发,然后在其包裹的内部节点中触发。

<!--这里先执行divClick事件,然后再执行aClick事件-->
<div @click="divClick"><a v-on:click="aClick">点击</a></div>

(4).self

只当在 event.target 是当前元素自身时触发处理函数,即事件不是从内部元素触发的。

<!--在a标签上点击时只会触发aClick事件,只有点击phrase的时候才会触发divClick事件-->
<div @click.self="divClick">phrase<a v-on:click="aClick">点击</a></div>

(5).once

不像其它只能对原生的 DOM 事件起作用的修饰符,.once 修饰符还能被用到自定义的组件事件上,表示当前事件只触发一次。

<a v-on:click.once="aClick">点击</a>

(6).passive

.passive 修饰符尤其能够提升移动端的性能

<!-- 滚动事件的默认行为 (即滚动行为) 将会立即触发 -->  
<!-- 而不会等待 `onScroll` 完成 -->  
<!-- 这其中包含 `event.preventDefault()` 的情况 -->  
<div v-on:scroll.passive="onScroll">...</div>

3、其他修饰符

除了表单和事件的修饰符,Vue还提供了很多其他修饰符,在使用的时候可以查阅文档。

小结

Vue中提供了很多好用的功能和api,那么修饰符的出现就为功能和api提供了更为丰富的扩展属性和更大的灵活度。

三十二、vue-router

vue路由是单页面中视图切换的方案,有三种mode:

  • hash,#后的仅仅作为参数,不属于url部分
  • history,路径作为请求url请求资源链接,如果找不到会出现404错误
  • abstract,服务端渲染场景 hash场景下,会出现url链接,再修改其view-router中对应的值。

了解vue-router的底层实现请参考vue2视图切换:vue-router

三十三、vuex

vuex是状态管理仓库,一般使用的场景为:多个视图依赖于同一状态,来自不同视图的行为需要变更同一状态。其管理的状态是响应式的,修改也只能显式提交mutation的方式修改。vuexstategettermutationactionmodule五个核心,并且通过module实现了vuex树的管理。

了解vuex的底层实现请参考vue2状态管理:vuex

三十四、eventBus

使用场景:兄弟组件传参

const eventBus = new Vue();

const A = {
  template: `<div @click="send">component-a</div>`,
  methods: {
    send() {
      eventBus.$emit('sendData', 'data from A')
    }
  },
}

const B = {
  template: `<div>component-b</div>`,
  created() {
    eventBus.$on('sendData', (args) => {
      console.log(args)
    })
  },
}

new Vue({
  el: '#app',
  components: {
    A,
    B,
  },
  template: `<div><A></A><B></B></div>`,
})

在当前例子中,A组件和B组件称为兄弟组件,A组件通过事件总线eventBus中的$emit分发事件,B组件则通过$on来监听事件。

实现原理:eventsMixin

export function eventsMixin (Vue: Class<Component>) {
  const hookRE = /^hook:/
  Vue.prototype.$on = function (event: string | Array<string>, fn: Function): Component {
    const vm: Component = this
    if (Array.isArray(event)) {
      for (let i = 0, l = event.length; i < l; i++) {
        vm.$on(event[i], fn)
      }
    } else {
      (vm._events[event] || (vm._events[event] = [])).push(fn)
      // optimize hook:event cost by using a boolean flag marked at registration
      // instead of a hash lookup
      if (hookRE.test(event)) {
        vm._hasHookEvent = true
      }
    }
    return vm
  }

  Vue.prototype.$once = function (event: string, fn: Function): Component {
    const vm: Component = this
    function on () {
      vm.$off(event, on)
      fn.apply(vm, arguments)
    }
    on.fn = fn
    vm.$on(event, on)
    return vm
  }

  Vue.prototype.$off = function (event?: string | Array<string>, fn?: Function): Component {
    const vm: Component = this
    // all
    if (!arguments.length) {
      vm._events = Object.create(null)
      return vm
    }
    // array of events
    if (Array.isArray(event)) {
      for (let i = 0, l = event.length; i < l; i++) {
        vm.$off(event[i], fn)
      }
      return vm
    }
    // specific event
    const cbs = vm._events[event]
    if (!cbs) {
      return vm
    }
    if (!fn) {
      vm._events[event] = null
      return vm
    }
    // specific handler
    let cb
    let i = cbs.length
    while (i--) {
      cb = cbs[i]
      if (cb === fn || cb.fn === fn) {
        cbs.splice(i, 1)
        break
      }
    }
    return vm
  }

  Vue.prototype.$emit = function (event: string): Component {
    const vm: Component = this
    if (process.env.NODE_ENV !== 'production') {
      const lowerCaseEvent = event.toLowerCase()
      if (lowerCaseEvent !== event && vm._events[lowerCaseEvent]) {
        tip(
          `Event "${lowerCaseEvent}" is emitted in component ` +
          `${formatComponentName(vm)} but the handler is registered for "${event}". ` +
          `Note that HTML attributes are case-insensitive and you cannot use ` +
          `v-on to listen to camelCase events when using in-DOM templates. ` +
          `You should probably use "${hyphenate(event)}" instead of "${event}".`
        )
      }
    }
    let cbs = vm._events[event]
    if (cbs) {
      cbs = cbs.length > 1 ? toArray(cbs) : cbs
      const args = toArray(arguments, 1)
      const info = `event handler for "${event}"`
      for (let i = 0, l = cbs.length; i < l; i++) {
        invokeWithErrorHandling(cbs[i], vm, args, vm, info)
      }
    }
    return vm
  }
}

Vue构造函数定义完执行的eventsMixin函数中,在Vue.prototype上分别定义了$on$emit$off$once的方法易实现对事件的绑定、分发、取消和只执行一次的方法。eventBus就是利用了当new Vue实例化后实例上的$on$emit$off$once进行数据传递。

三十五、ref

使用场景: 父组件获取子组件数据或者执行子组件方法

const A = {
  template: `<div>{{childData.age}}</div>`,
  data() {
    return {
      childData: {
        name: 'qb',
        age: 30
      },
    }
  },
  methods: {
    increaseAge() {
      this.childData.age++;
    }
  }
}

new Vue({
  el: '#app',
  components: {
    A,
  },
  template: `<A ref='childRef' @click.native='changeChildData'></A>`,
  methods: {
    changeChildData() {
      // 执行子组件的方法
      this.$refs.childRef.increaseAge()
      // 获取子组件的数据
      console.log(this.$refs.childRef.childData);
    },
  }
})

在当前例子中,通过ref='childRef'的方式在当前组件中定义一个ref,可以通过this.$refs.childRef的方式获取到子组件A。可以通过this.$refs.childRef.increaseAge()的方式执行子组件中age增加的方法,也可以通过this.$refs.childRef.childData的方式获取到子组件中的数据。

三十六、props

使用场景: 父子传参

const A = {
  template: `<div @click='emitData'>{{childData}}</div>`,
  props: ['childData'],
  methods: {
    emitData() {
      this.$emit('emitChildData', 'data from child')
    }
  },
}

new Vue({
  el: '#app',
  components: {
    A
  },
  template: `<A :childData='parentData' @emitChildData='getChildData'></A>`,
  data() {
    return {
      parentData: 'data from parent'
    }
  },
  methods: {
    getChildData(v) {
      console.log(v);
    }
  }
})

从当前例子中可以看出,数据父传子是通过:childData='parentData'的方式,数据子传父是通过this.$emit('emitChildData', 'data from child')的方式,然后,父组件通过@emitChildData='getChildData'的方式进行获取。

1、父组件render函数

new Vue中传入的模板template经过遍历生成的render函数如下:

with(this) {
    return _c('A', {
        attrs: {
            "childData": parentData
        },
        on: {
            "emitChildData": getChildData
        }
    })
}

其中data部分有attrson来描述属性和方法。

在通过createComponent创建组件vnode的过程中,会通过const propsData = extractPropsFromVNodeData(data, Ctor, tag)的方式获取props,通过const listeners = data.on的方式获取listeners,最后将其作为参数通过new VNode(options)的方式实例化组件vnode

2、子组件渲染

在通过const child = vnode.componentInstance = createComponentInstanceForVnode( vnode, activeInstance )创建组件实例的过程中,会执行到组件继承自Vue._init方法,通过initEvents将事件处理后存储到vm._events中,通过initPropschildData赋值到子组件Avm实例上,并进行响应式处理,让其可以通过vm.childData的方式访问,并且数据发生变化时视图也可以发生改变。

组件模板编译后对应的render函数是:

with(this) {
    return _c('div', {
        on: {
            "click": emitData
        }
    }, [_v(_s(childData))])
}

createElm完成节点的创建后,在invokeCreateHooks(vnode, insertedVnodeQueue)阶段,给DOM原生节点节点绑定emitData

3、this.$emit

在点击执行this.$emit时,会通过var cbs = vm._events[event]取出_events中的事件进行执行。

至此,父组件中的传递的数据就在子组件中可以通过this.xxx的方式获得,也可以通过this.$emit的方式将子组件中的数据传递给父组件。

prop数据发生改变引起视图变化的底层逻辑请参考vue2从数据变化到视图变化:props引起视图变化详解

三十七、$attrs$listeners

使用场景: 父子组件非props属性和非native方法传递

// main.js文件
import Vue from "vue";

const B = {
  template: `<div @click="emitData">{{ formParentData }}</div>`,
  data() {
    return {
      formParentData: ''
    }
  },
  inheritAttrs: false,

  created() {
    this.formParentData = this.$attrs;
    console.log(this.$attrs, '--------------a-component-$attrs')
    console.log(this.$listeners, '--------------b-component-$listeners')
  },
  methods: {
    emitData() {
      this.$emit('onFun', 'form B component')
    }
  },
}

const A = {
  template: `<B v-bind='$attrs' v-on='$listeners'></B>`,
  components: {
    B,
  },
  props: ['propData'],
  inheritAttrs: false,
  created() {
    console.log(this.$attrs, '--------------b-component-$attrs')
    console.log(this.$listeners, '--------------b-component-$listeners')
  }
}

new Vue({
  el: '#app',
  components: {
    A,
  },
  template: `<A :attrData='parentData' :propData='parentData' @click.native="nativeFun" @onFun="onFun"></A>`,
  data() {
    return {
      parentData: 'msg'
    }
  },
  methods: {
    nativeFun() {
      console.log('方法A');
    },
    onFun(v) {
      console.log('方法B', v);
    },
  }
})

当前例子中,new Vuetemplate模板中有attrDatapropDataclick.nativeonFun在进行传递。实际运行后,在A组件中this.$attrs{attrData: 'msg'}this.$listeners{onFun:f(...)}。在A组件中通过v-bind='$attrs'v-on='$listeners'的方式继续进行属性和方法的传递,在B组件中就可以获取到A组件中传入的$attrs$listeners

当前例子中完成了非props属性和非native方法的传递,并且通过v-bind='$attrs'v-on='$listeners'的方式实现了属性和方法的跨层级传递。

同时通过this.$emit的方法触发了根节点中onFun事件。

关于例子中的inheritAttrs: false,默认情况下父作用域的不被认作propsattribute绑定将会“回退”且作为普通的HTML属性应用在子组件的根元素上。当撰写包裹一个目标元素或另一个组件的组件时,这可能不会总是符合预期行为。通过设置inheritAttrsfalse,这些默认行为将会被去掉。

三十八、$parent$children

使用场景: 利用父子关系进行数据的获取或者方法的调用

const A = {
  template: `<div @click="changeParentData">{{childRandom}}</div>`,
  data() {
    return {
      childRandom: Math.random()
    }
  },
  mounted() {
    console.log(this.$parent.parentCount, '--child-created--'); // 获取父组件中的parentCount
  },
  methods: {
    changeParentData() {
      console.log(this.$parent); // 打印当前实例的$parent
      this.$parent.changeParentData(); // 调用当前父级中的方法`changeParentData`
    },
    changeChildData() {
      this.childRandom = Math.random();
    }
  }
}
const B = {
  template: `<div>b-component</div>`,
}

new Vue({
  el: '#app',
  components: {
    A,
    B,
  },
  template: `<div><A></A><B></B><p>{{parentCount}}</p><button  @click="changeChildrenData">修改子组件数据</button></div>`,
  data() {
    return {
      parentCount: 1
    }
  },
  mounted() {
    console.log(this.$children[0].childRandom, '--parent-created--'); // 获取第一个子组件中的childRandom
  },
  methods: {
    changeParentData() {
      this.parentCount++;
    },
    changeChildrenData() {
      console.log(this.$children); // 此时有两个子组件
      this.$children[0].changeChildData(); // 调起第一个子组件中的'changeChildData'方法
    }
  }
})

在当前例子中,父组件可以通过this.$children获取所有的子组件,这里有A组件和B组件,可以通过this.$children[0].childRandom的方式获取子组件A中的数据,也可以通过this.$children[0].changeChildData()的方式调起子组件A中的方法。

子组件可以通过this.$parent的方式获取父组件,可以通过this.$parent.parentCount获取父组件中的数据,也可以通过this.$parent.changeParentData()的方式修改父组件中的数据。

Vue$parent$children父子关系的底层构建请参考杂谈:������/parent/children的底层逻辑

三十九、injectprovide

使用场景:嵌套组件多层级传参

const B = {
  template: `<div>{{parentData1}}{{parentData2}}</div>`,
  inject: ['parentData1', 'parentData2'],
}

const A = {
  template: `<B></B>`,
  components: {
    B,
  },
}

new Vue({
  el: '#app',
  components: {
    A,
  },
  template: `<A></A>`,
  provide: {
    parentData1: {
      name: 'name-2',
      age: 30
    },
    parentData2: {
      name: 'name-2',
      age: 29
    },
  }
})

例子中在new Vue的时候通过provide提供了两个数据来源parentData1parentData2,然后跨了一个A组件在B组件中通过inject注入了这两个数据。

1、initProvide

在执行组件内部的this._init初始化方法时,会执行到initProvide逻辑:

export function initProvide (vm: Component) {
  const provide = vm.$options.provide
  if (provide) {
    vm._provided = typeof provide === 'function'
      ? provide.call(vm)
      : provide
  }
}

如果在当前vm.$options中存在provide,会将其执行结果赋值给vm._provided

2、initInjections
function initInjections (vm: Component) {
  const result = resolveInject(vm.$options.inject, vm)
  if (result) {
    toggleObserving(false)
    Object.keys(result).forEach(key => {
      /* istanbul ignore else */
      if (process.env.NODE_ENV !== 'production') {
        defineReactive(vm, key, result[key], () => {
          warn(
            `Avoid mutating an injected value directly since the changes will be ` +
            `overwritten whenever the provided component re-renders. ` +
            `injection being mutated: "${key}"`,
            vm
          )
        })
      } else {
        defineReactive(vm, key, result[key])
      }
    })
    toggleObserving(true)
  }
}
function resolveInject (inject: any, vm: Component): ?Object {
  if (inject) {
    // inject is :any because flow is not smart enough to figure out cached
    const result = Object.create(null)
    const keys = hasSymbol
      ? Reflect.ownKeys(inject)
      : Object.keys(inject)

    for (let i = 0; i < keys.length; i++) {
      const key = keys[i]
      // #6574 in case the inject object is observed...
      if (key === '__ob__') continue
      const provideKey = inject[key].from
      let source = vm
      while (source) {
        if (source._provided && hasOwn(source._provided, provideKey)) {
          result[key] = source._provided[provideKey]
          break
        }
        source = source.$parent
      }
      if (!source) {
        if ('default' in inject[key]) {
          const provideDefault = inject[key].default
          result[key] = typeof provideDefault === 'function'


#### 专业技能

一般来说,面试官会根据你的简历内容去提问,但是技术基础还有需要自己去准备分类,形成自己的知识体系的。简单列一下我自己遇到的一些题

* HTML+CSS

* JavaScript

* 前端框架

* 前端性能优化

* 前端监控

* 模块化+项目构建

* 代码管理

* 信息安全

* 网络协议

* 浏览器

* 算法与数据结构

* 团队管理


最近得空把之前遇到的面试题做了一个整理,包括我本人自己去面试遇到的,还有其他人员去面试遇到的,还有网上刷到的,我都统一的整理了一下,希望对大家有用。



**其中包含HTML、CSS、JavaScript、服务端与网络、Vue、浏览器等等**

**由于文章篇幅有限,仅展示部分内容**

![](https://img-blog.csdnimg.cn/img_convert/ac0b1c2376da47d727e0dc8a77e76478.png)
  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值