• 滴滴前端二面vue相关面试题


    computed 的实现原理

    computed 本质是一个惰性求值的观察者。

    computed 内部实现了一个惰性的 watcher,也就是 computed watcher,computed watcher 不会立刻求值,同时持有一个 dep 实例。

    其内部通过 this.dirty 属性标记计算属性是否需要重新求值。

    当 computed 的依赖状态发生改变时,就会通知这个惰性的 watcher,

    computed watcher 通过 this.dep.subs.length 判断有没有订阅者,

    有的话,会重新计算,然后对比新旧值,如果变化了,会重新渲染。 (Vue 想确保不仅仅是计算属性依赖的值发生变化,而是当计算属性最终计算的值发生变化时才会触发渲染 watcher 重新渲染,本质上是一种优化。)

    没有的话,仅仅把 this.dirty = true。 (当计算属性依赖于其他数据时,属性并不会立即重新计算,只有之后其他地方需要读取属性的时候,它才会真正计算,即具备 lazy(懒计算)特性。)

    Vue-router跳转和location.href有什么区别

    • 使用 location.href= /url 来跳转,简单方便,但是刷新了页面;
    • 使用 history.pushState( /url ) ,无刷新页面,静态跳转;
    • 引进 router ,然后使用 router.push( /url ) 来跳转,使用了 diff 算法,实现了按需加载,减少了 dom 的消耗。其实使用 router 跳转和使用 history.pushState() 没什么差别的,因为vue-router就是用了 history.pushState() ,尤其是在history模式下。

    $nextTick 原理及作用

    Vue 的 nextTick 其本质是对 JavaScript 执行原理 EventLoop 的一种应用。

    nextTick 的核心是利用了如 Promise 、MutationObserver、setImmediate、setTimeout的原生 JavaScript 方法来模拟对应的微/宏任务的实现,本质是为了利用 JavaScript 的这些异步回调任务队列来实现 Vue 框架中自己的异步回调队列。

    nextTick 不仅是 Vue 内部的异步队列的调用方法,同时也允许开发者在实际项目中使用这个方法来满足实际应用中对 DOM 更新数据时机的后续逻辑处理

    nextTick 是典型的将底层 JavaScript 执行原理应用到具体案例中的示例,引入异步更新队列机制的原因∶

    • 如果是同步更新,则多次对一个或多个属性赋值,会频繁触发 UI/DOM 的渲染,可以减少一些无用渲染
    • 同时由于 VirtualDOM 的引入,每一次状态发生变化后,状态变化的信号会发送给组件,组件内部使用 VirtualDOM 进行计算得出需要更新的具体的 DOM 节点,然后对 DOM 进行更新操作,每次更新状态后的渲染过程需要更多的计算,而这种无用功也将浪费更多的性能,所以异步渲染变得更加至关重要

    Vue采用了数据驱动视图的思想,但是在一些情况下,仍然需要操作DOM。有时候,可能遇到这样的情况,DOM1的数据发生了变化,而DOM2需要从DOM1中获取数据,那这时就会发现DOM2的视图并没有更新,这时就需要用到了nextTick了。

    由于Vue的DOM操作是异步的,所以,在上面的情况中,就要将DOM2获取数据的操作写在$nextTick中。

    this.$nextTick(() => {
           // 获取数据的操作...})
    
    
    • 1
    • 2
    • 3

    所以,在以下情况下,会用到nextTick:

    • 在数据变化后执行的某个操作,而这个操作需要使用随数据变化而变化的DOM结构的时候,这个操作就需要方法在nextTick()的回调函数中。
    • 在vue生命周期中,如果在created()钩子进行DOM操作,也一定要放在nextTick()的回调函数中。

    因为在created()钩子函数中,页面的DOM还未渲染,这时候也没办法操作DOM,所以,此时如果想要操作DOM,必须将操作的代码放在nextTick()的回调函数中。

    Vue3.0 和 2.0 的响应式原理区别

    Vue3.x 改用 Proxy 替代 Object.defineProperty。因为 Proxy 可以直接监听对象和数组的变化,并且有多达 13 种拦截方法。

    相关代码如下

    import {
        mutableHandlers } from "./baseHandlers"; // 代理相关逻辑
    import {
        isObject } from "./util"; // 工具方法
    
    export function reactive(target) {
       
      // 根据不同参数创建不同响应式对象
      return createReactiveObject(target, mutableHandlers);
    }
    function createReactiveObject(target, baseHandler) {
       
      if (!isObject(target)) {
       
        return target;
      }
      const observed = new Proxy(target, baseHandler);
      return observed;
    }
    
    const get = createGetter();
    const set = createSetter();
    
    function createGetter() {
       
      return function get(target, key, receiver) {
       
        // 对获取的值进行放射
        const res = Reflect.get(target, key, receiver);
        console.log("属性获取", key);
        if (isObject(res)) {
       
          // 如果获取的值是对象类型,则返回当前对象的代理对象
          return reactive(res);
        }
        return res;
      };
    }
    function createSetter() {
       
      return function set(target, key, value, receiver) {
       
        const oldValue = target[key];
        const hadKey = hasOwn(target, key);
        const result = Reflect.set(target, key, value, receiver);
        if (!hadKey) {
       
          console.log("属性新增", key, value);
        } else if (hasChanged(value, oldValue)) {
       
          console.log("属性值被修改", key, value);
        }
        return result;
      };
    }
    export const mutableHandlers = {
       
      get, // 当获取属性时调用此方法
      set, // 当修改属性时调用此方法
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60

    vue3中 watch、watchEffect区别

    • watch是惰性执行,也就是只有监听的值发生变化的时候才会执行,但是watchEffect不同,每次代码加载watchEffect都会执行(忽略watch第三个参数的配置,如果修改配置项也可以实现立即执行)
    • watch需要传递监听的对象,watchEffect不需要
    • watch只能监听响应式数据:ref定义的属性和reactive定义的对象,如果直接监听reactive定义对象中的属性是不允许的(会报警告),除非使用函数转换一下。其实就是官网上说的监听一个getter
    • watchEffect如果监听reactive定义的对象是不起作用的,只能监听对象中的属性

    看一下watchEffect的代码

    <template>
    <div>
      请输入firstName:
      <input type="text" v-model="firstName">
    div>
    <div>
      请输入lastName:
      <input type="text" v-model="lastName">
    div>
    <div>
      请输入obj.text:
      <input type="text" v-model="obj.text">
    div>
     <div>
     【obj.text】 {
      {obj.text}}
     div>
    template>
    
    <script>
    import {
         ref, reactive, watch, watchEffect} from 'vue'
    export default {
         
      name: "HelloWorld",
      props: {
         
        msg: String,
      },
      setup(props,content){
         
        let firstName = ref('')
        let lastName = ref('')
        let obj= reactive({
         
          text:'hello'
        })
        watchEffect(()=>{
         
          console.log('触发了watchEffect');
          console.log(`组合后的名称为:${
           firstName.value}${
           lastName.value}`)
        })
        return{
         
          obj,
          firstName,
          lastName
        }
      }
    };
    script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53

    改造一下代码

    watchEffect(()=>{
       
      console.log('触发了watchEffect');
      // 这里我们不使用firstName.value/lastName.value ,相当于是监控整个ref,对应第四点上面的结论
      console.log(`组合后的名称为:${
         firstName}${
         lastName}`)
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    watchEffect(()=>{
       
      console.log('触发了watchEffect');
      console.log(obj);
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5

    稍微改造一下

    let obj = reactive({
       
      text:'hello'
    })
    watchEffect(()=>{
       
      console.log('触发了watchEffect');
      console.log(obj.text);
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    再看一下watch的代码,验证一下

    let obj= reactive({
       
      text:'hello'
    })
    // watch是惰性执行, 默认初始化之后不会执行,只有值有变化才会触发,可通过配置参数实现默认执行
    watch(obj, (newValue, oldValue) => {
       
      // 回调函数
      console.log('触发监控更新了new',  newValue);
      console.log('触发监控更新了old',  oldValue);
    },{
       
      // 配置immediate参数,立即执行,以及深层次监听
      immediate: true,
      deep: true
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    • 监控整个reactive对象,从上面的图可以看到 deep 实际默认是开启的,就算我们设置为false也还是无效。而且旧值获取不到。
    • 要获取旧值则需要监控对象的属性,也就是监听一个getter,看下图

    总结

    • 如果定义了reactive的数据,想去使用watch监听数据改变,则无法正确获取旧值,并且deep属性配置无效,自动强制开启了深层次监听。
    • 如果使用 ref 初始化一个对象或者数组类型的数据,会被自动转成reactive的实现方式,生成proxy代理对象。也会变得无法正确取旧值。
    • 用任何方式生成的数据,如果接收的变量是一个proxy代理对象,就都会导致watch这个对象时,watch回调里无法正确获取旧值。
    • 所以当大家使用watch监听对象时,如果在不需要使用旧值的情况,可以正常监听对象没关系;但是如果当监听改变函数里面需要用到旧值时,只能监听 对象.xxx`属性 的方式才行

    watch和watchEffect异同总结

    体验

    watchEffect立即运行一个函数,然后被动地追踪它的依赖,当这些依赖改变时重新执行该函数

    const count = ref(0)watchEffect(() => console.log(count.value))
    // -> logs 0
    ​
    count.value++
    // -> logs 1
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    watch侦测一个或多个响应式数据源并在数据源变化时调用一个回调函数

    const state = reactive({
        count: 0 })
    watch(
      () => state.count,
      (count, prevCount) => {
       
        /* ... */
      }
    )
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    回答范例

    1. watchEffect立即运行一个函数,然后被动地追踪它的依赖,当这些依赖改变时重新执行该函数。watch侦测一个或多个响应式数据源并在数据源变化时调用一个回调函数
    2. watchEffect(effect)是一种特殊watch,传入的函数既是依赖收集的数据源,也是回调函数。如果我们不关心响应式数据变化前后的值,只是想拿这些数据做些事情,那么watchEffect就是我们需要的。watch更底层,可以接收多种数据源,包括用于依赖收集的getter函数,因此它完全可以实现watchEffect的功能,同时由于可以指定getter函数,依赖可以控制的更精确,还能获取数据变化前后的值,因此如果需要这些时我们会使用watch
    3. watchEffect在使用时,传入的函数会立刻执行一次。watch默认情况下并不会执行回调函数,除非我们手动设置immediate选项
    4. 从实现上来说,watchEffect(fn)相当于watch(fn,fn,{immediate:true})

    watchEffect定义如下

    export function watchEffect(
      effect: WatchEffect,
      options?: WatchOptionsBase
    ): WatchStopHandle {
       
      return doWatch(effect, null, options)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    watch定义如下

    export function watch<T = any, Immediate extends Readonly<boolean> = false>(
      source: T | WatchSource<T>,
      cb: any,
      options?: WatchOptions<Immediate>
    ): WatchStopHandle {
       
      return doWatch(source as any, cb, options)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    很明显watchEffect就是一种特殊的watch实现。

    nextTick 使用场景和原理

    nextTick 中的回调是在下次 DOM 更新循环结束之后执行的延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。主要思路就是采用微任务优先的方式调用异步方法去执行 nextTick 包装的方法

    相关代码如下

    let callbacks = [];
    let pending = false;
    function flushCallbacks() {
       
      pending = false; //把标志还原为false
      // 依次执行回调
      for (let i = 0; i < callbacks.length; i++) {
       
        callbacks[i]();
      }
    }
    let timerFunc; //定义异步方法  采用优雅降级
    if (typeof Promise !== "undefined") {
       
      // 如果支持promise
      const p = Promise.resolve();
      timerFunc = () => {
       
        p.then(flushCallbacks);
      };
    } else if (typeof MutationObserver !== "undefined") {
       
      // MutationObserver 主要是监听dom变化 也是一个异步方法
      let counter = 1;
      const observer = new MutationObserver(flushCallbacks);
      const textNode = document.createTextNode(String(counter));
      observer.observe(textNode, {
       
        characterData: true,
      });
      timerFunc = () => {
       
        counter = (counter + 1) % 2;
        textNode.data = String(counter);
      };
    } else if (typeof setImmediate !== "undefined") {
       
      // 如果前面都不支持 判断setImmediate
      timerFunc = () => {
       
        setImmediate(flushCallbacks);
      };
    } else {
       
      // 最后降级采用setTimeout
      timerFunc = () => {
       
        setTimeout(flushCallbacks, 0);
      };
    }
    
    export function nextTick(cb) {
       
      // 除了渲染watcher  还有用户自己手动调用的nextTick 一起被收集到数组
      callbacks.push(cb);
      if (!pending) {
       
        // 如果多次调用nextTick  只会执行一次异步 等异步队列清空之后再把标志变为false
        pending = true;
        timerFunc();
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62

    vue2.x详细

    1. 分析

    首先找到vue的构造函数

    源码位置:src\core\instance\index.js

    function Vue (options) {
       
      if (process.env.NODE_ENV !== 'production' &&
        !(this instanceof Vue)
      ) {
       
        warn('Vue is a constructor and should be called with the `new` keyword')
      }
      this._init(options)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    options是用户传递过来的配置项,如data、methods等常用的方法

    vue构建函数调用_init方法,但我们发现本文件中并没有此方法,但仔细可以看到文件下方定定义了很多初始化方法

    initMixin(Vue);     // 定义 _init
    stateMixin(Vue);    // 定义 $set $get $delete $watch 等
    eventsMixin(Vue);   // 定义事件  $on  $once $off $emit
    lifecycleMixin(Vue);// 定义 _update  $forceUpdate  $destroy
    renderMixin(Vue);   // 定义 _render 返回虚拟dom
    
    • 1
    • 2
    • 3
    • 4
    • 5

    首先可以看initMixin方法,发现该方法在Vue原型上定义了_init方法

    源码位置:src\core\instance\init.js

    Vue.prototype._init = function (options?: Object) {
       
        const vm: Component = this
        // a uid
        vm._uid = uid++
        let startTag, endTag
        /* istanbul ignore if */
        if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
       
          startTag = `vue-perf-start:${
         vm._uid}`
          endTag = `vue-perf-end:${
         vm._uid}`
          mark(startTag)
        }
    
        // a flag to avoid this being observed
        vm._isVue = true
        // merge options
        // 合并属性,判断初始化的是否是组件,这里合并主要是 mixins 或 extends 的方法
        if (options && options._isComponent) {
       
          // optimize internal component instantiation
          // since dynamic options merging is pretty slow, and none of the
          // internal component options needs special treatment.
          initInternalComponent(vm, options)
        } else {
        // 合并vue属性
          vm.$options = mergeOptions(
            resolveConstructorOptions(vm.constructor),
            options || {
       },
            vm
          )
        }
        /* istanbul ignore else */
        if (process.env.NODE_ENV !== 'production') {
       
          // 初始化proxy拦截器
          initProxy(vm)
        } else {
       
          vm._renderProxy = vm
        }
        // expose real self
        vm._self = vm
        // 初始化组件生命周期标志位
        initLifecycle(vm)
        // 初始化组件事件侦听
        initEvents(vm)
        // 初始化渲染方法
        initRender(vm)
        callHook(vm, 'beforeCreate')
        // 初始化依赖注入内容,在初始化data、props之前
        initInjections(vm) // resolve injections before data/props
        // 初始化props/data/method/watch/methods
        initState(vm)
        initProvide(vm) // resolve provide after data/props
        callHook(vm, 'created')
    
        /* istanbul ignore if */
        if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
       
          vm._name = formatComponentName(vm, false)
          mark(endTag)
          measure(`vue ${
         vm._name} init`, startTag, endTag)
        }
        // 挂载元素
        if (vm.$options.el) {
       
          vm.$mount(vm.$options.el)
        }
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74

    仔细阅读上面的代码,我们得到以下结论:

    • 在调用beforeCreate之前,数据初始化并未完成,像dataprops这些属性无法访问到
    • 到了created的时候,数据已经初始化完成,能够访问dataprops这些属性,但这时候并未完成dom的挂载,因此无法访问到dom元素
    • 挂载方法是调用vm.$mount方法

    initState方法是完成props/data/method/watch/methods的初始化

    源码位置:src\core\instance\state.js

    export function initState (vm: Component) {
       
      // 初始化组件的watcher列表
      vm._watchers = []
      const opts = vm.$options
      // 初始化props
      if (opts.props) initProps(vm, opts.props)
      // 初始化methods方法
      if (opts.methods) initMethods(vm, opts.methods)
      if (opts.data) {
       
        // 初始化data  
        initData(vm)
      } else {
       
        observe(vm._data = {
       }, true /* asRootData */)
      }
      if (opts.computed) initComputed(vm, opts.computed)
      if (opts.watch && opts.watch !== nativeWatch) {
       
        initWatch(vm, opts.watch)
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    我们和这里主要看初始化data的方法为initData,它与initState在同一文件上

    function initData (vm: Component) {
       
      let data = vm.$options.data
      // 获取到组件上的data
      data = vm._data = typeof data === 'function'
        ? getData(data, vm)
        : data || {
       }
      if (!isPlainObject(data)) {
       
        data = {
       }
        process.env.NODE_ENV !== 'production' && warn(
          'data functions should return an object:\n' +
          'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',
          vm
        )
      }
      // proxy data on instance
      const keys = Object.keys(data)
      const props = vm.$options.props
      const methods = vm.$options.methods
      let i = keys.length
      while (i--) {
       
        const key = keys[i]
        if (process.env.NODE_ENV !== 'production') {
       
          // 属性名不能与方法名重复
          if (methods && hasOwn(methods, key)) {
       
            warn(
              `Method "${
         key}" has already been defined as a data property.`,
              vm
            )
          }
        }
        // 属性名不能与state名称重复
        if (props && hasOwn(props, key)) {
       
          process.env.NODE_ENV !== 'production' && warn(
            `The data property "${
         key}" is already declared as a prop. ` +
            `Use prop default value instead.`,
            vm
          )
        } else if (!isReserved(key)) {
        // 验证key值的合法性
          // 将_data中的数据挂载到组件vm上,这样就可以通过this.xxx访问到组件上的数据
          proxy(vm, `_data`, key)
        }
      }
      // observe data
      // 响应式监听data是数据的变化
      observe(data, true /* asRootData */)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57

    仔细阅读上面的代码,我们可以得到以下结论:

    • 初始化顺序:propsmethodsdata
    • data定义的时候可选择函数形式或者对象形式(组件只能为函数形式)

    关于数据响应式在这就不展开详细说明

    上文提到挂载方法是调用vm.$mount方法

    源码位置:

    Vue.prototype.$mount = function (
      el?: string | Element,
      hydrating?: boolean
    ): Component {
       
      // 获取或查询元素
      el = el && query(el)
    
      /* istanbul ignore if */
      // vue 不允许直接挂载到body或页面文档上
      if (el === document.body || el === document.documentElement) {
       
        process.env.NODE_ENV !== 'production' && warn(
          `Do not mount Vue to  or  - mount to normal elements instead.`
        )
        return this
      }
    
      const options = this.$options
      // resolve template/el and convert to render function
      if (!options.render) {
       
        let template = options.template
        // 存在template模板,解析vue模板文件
        if (template) {
       
          if (typeof template === 'string') {
       
            if (template.charAt(0) === '#') {
       
              template = idToTemplate(template)
              /* istanbul ignore if */
              if (process.env.NODE_ENV !== 'production' && !template) {
       
                warn(
                  `Template element not found or is empty: ${
         options.template}`,
                  this
                )
              }
            }
          } else if (template.nodeType) {
       
            template = template.innerHTML
          } else {
       
            if (process.env.NODE_ENV !== 'production') {
       
              warn('invalid template option:' + template, this)
            }
            return this
          }
        } else if (el) {
       
          // 通过选择器获取元素内容
          template = getOuterHTML(el)
        }
        if (template) {
       
          /* istanbul ignore if */
          if (process.env.NODE_ENV !== 'production' && config.performance &am
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
  • 相关阅读:
    SpringBoot进阶学习(二)---配置高级
    【Vue】vue.js a标签href里添加参数--20220628
    python基础语法15-网络编程理论
    【售货系统的Web测试】
    Qwt开发笔记(二):Qwt基础框架介绍、折线图介绍、折线图Demo以及代码详解
    <图像处理> 空间滤波基础
    Windows下编译android版ijkplayer
    STM32物联网项目-高级定时器软件仿真输出互补PWM信号
    C++学习之动态内存
    【MyBatis-Plus】之queryWrapper.apply用法
  • 原文地址:https://blog.csdn.net/bb_xiaxia1998/article/details/127381284