vue2.x(data&watcher&dep)

本文深入解析Vue2.x中的响应式数据处理,包括initData、observe、newObserver、defineReactive$$1以及Watcher的工作流程。通过代理data、创建Observer、定义属性的getter和setter实现依赖收集和变更通知,从而实现数据驱动视图的更新。文章还提供了简易Vue实例和代码示例,帮助理解响应式核心机制。

如何理解vue2.x中的响应式数据?

    1. 执行initData方法
    2.                -> 作“data” & “props” & “methods”属性名称是否重名判断 且调用proxy方法再作this.data proxy代理到 this._data上面
    3.                -> 调用observe全局监听函数 准备作响应式处理
    4. 执行observe方法
    5.               -> 通过__ob__判断是否已经作了监听 (若未进行监听 则实例化Observer来进行监听创建)
    6. 执行new Observer() 实例化 (我们可以叫他为大目标者)
    7.                         -> 这里需要注意的是这个Observer监听者是针对该组件下整个data进行存储 data -> this.value & this.dep = new Dep() 
                                  并对该value值(data)上增加__ob__ 指向当前Observer 等于说就是 data上有个__ob__隐藏属性
    8. 最后执行Observer大目标者原型上walk方法
    9.                                    -> for循环遍历调用defineReactive$$1方法来进行data上每个属性进行响应式设置
    10. 给每个属性执行new Dep() 实例化 (我们可以叫他为小监听者或实际目标收集者)若存在嵌套则重复调用observe方法
                          -> 其实这个dep才是真正的观察者模式中的叫做依赖收集者或者叫目标 因为dep.subs上会收集相关的监听者
                          -> 再使用Object.defineProperty定义每个data里面的key值的getter&setter 这里也利用了闭包特性缓存了每个key所对应的dep对象信息
                          -> 当getter时 判断全局下是否有Watcher 若有则调用当前dep.depend -> 再调用Watcher的addDep方法收集当前watcher到Watcher实例上的newDepId&newDep
                             针对Set格式的newDepId进行排除重复使用的data数据 比如我在多个地方同时使用data下数据
                          -> 当setter时 调用当前dep下的notify方法 再循环执行subs(Watcher)的update方法
    11. 将当前Watcher压入queue队列(这里压进队列是为了比如一个数据的修改可能对应多个地方的应用 采用收集完所有的更改变动后 一次性触发每个的生命钩子函数)
    12. 调用nextTick
    13. 触发‘beforeUpdate’钩子
    14. 调用Watcher的run方法
    15. 调用Watcher的get方法
                           -> 设置当前Watcher实例为全局Target
                           -> 调用Wathcer的getter方法 (一般情况下就是updateComponent方法)
                           -> 执行vm._update(vm._render(),hydrating)代码
    16. 调用render方法 (而这里的render其实就是经过编译过后形成的with包裹的匿名函数)
    17. 一旦执行with代码 则会触发属性的getter方法 (获取最新的值) 最后返回最新执行下的vnode (执行render方法后 获得新的vnode)
    18. 调用vm._update (这里就是走对比新老vnode 走diff算法 更新DOM)
    19. 将全局Target置为空
    20. 清空当前Watcher上面的newDepIds&newDep 等待下一次的更新                                           
    21. 若存在activedQueue 则触发钩子 'activated' 生命周期钩子函数
    22. 执行更新updateQueue队列 则触发每个vm实例下的 ‘update’ 生命周期钩子函数

要讲响应式我们只有从data上入手 先本地起一个简易vue

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    #app span{
      color:red;
    }
  </style>
</head>
<body>
  <div id="app">
    <span>我是data数据{{message}}</span>
    <!-- <span>我是data数据{{list.name}}</span> -->
    <!-- <base-show /> -->
    <!-- <span>我是一模一样的data数据{{message}}</span> -->
    <p>我是数据依赖message{{`${message}依赖数据`}}</p>
  </div>
</body>
<script src="./vue.dist.js"></script>
<script>
  const vm = new Vue({
    data:{
      message:'test',
    },
    // computed:{
    //   getDep(){
    //     return this.message + '依赖数据'
    //   }
    // }
  })
  // 改变data值测试
  setTimeout(function(){
    vm.message = '2秒后变值'
  },2000)
  // 注册组件测试
  // Vue.component('base-show',{
  //   name:'baseShow',
  //   template:`<h3>{{myMessage}}</h3>`,
  //   data(){
  //     return {
  //       myMessage:'我是baseShow组件'
  //     }
  //   }
  // })
  vm.$mount('#app')
</script>
</html>
源码4998行 生命周期钩子第一个“beforeCreate”
源码5000执行initState(vm) 说明在beforeCreate钩子里面时拿不到被处理过的data
源码4635行 按顺序处理props -> methods -> data -> computed -> watch
function initState(vm) {
  vm._watchers = [];
  var opts = vm.$options;
  // TODO:vue先处理props -> methods -> data -> computed -> watch
  if (opts.props) {
    initProps(vm, opts.props);
  }
  if (opts.methods) {
    initMethods(vm, opts.methods);
  }
  if (opts.data) {
    // TODO:开始初始化data 传入vue实例
    initData(vm);
  } else {
    // TODO: 万一没设置 一般根点没设置data数据 这里容错处理赋值了个空{}
    // 后面来看这个oberve函数到底做了哪些事情
    observe((vm._data = {}), true /* asRootData */);
  }
  if (opts.computed) {
    initComputed(vm, opts.computed);
  }
  if (opts.watch && opts.watch !== nativeWatch) {
    initWatch(vm, opts.watch);
  }
}
源码4699行 初始化data数据 其实此时这个时候的data已被处理
源码4983行 mergeOptions 把非组件类属性进行进一步处理
data = function mergedInstanceDataFn () {
    // instance merge
    var instanceData = typeof childVal === 'function'
    ? childVal.call(vm, vm)
    : childVal;
    var defaultData = typeof parentVal === 'function'
    ? parentVal.call(vm, vm)
    : parentVal;
    if (instanceData) {
        return mergeData(instanceData, defaultData)
    } else {
        return defaultData
    }
}
// 0.1 初始化data
function initData(vm) {
    var data = vm.$options.data;
    // TODO: 这里来一个判断 根结点data可以是一个对象
    // 而子组件data为函数 而且调用了全局getData 入参data及vue实例 来看0.1.5 getData意义
    // 这里在执行getData后 将我们声明的数据对象赋值给了vm实例中的_data属性及局部data变量
    data = vm._data = typeof data === 'function' ? getData(data, vm) : data || {};
    if (!isPlainObject(data)) {
        data = {};
        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
    // TODO:拿到声明data的key值
    var keys = Object.keys(data);
    // TODO: 这里拿的props及methods都是初始化数据未修饰
    var props = vm.$options.props;
    var methods = vm.$options.methods;
    var i = keys.length;
    // TODO: 发现挺多处使用while循环  学到了 嘻嘻
    while (i--) {
        var key = keys[i];
        // TODO: 这里就是作一个data跟props及methods是否重名判断
        // 为什么只判断这2个 我觉得是在0处 执行的顺序相关 先处理prop再methods 再data
        {
            if (methods && hasOwn(methods, key)) {
                warn('Method "' + key + '" has already been defined as a data property.', vm);
            }
        }
        if (props && hasOwn(props, key)) {
            warn('The data property "' + key + '" is already declared as a prop. ' + 'Use prop default value instead.', vm);
            // TODO:这里再判断下key 见0.1.1解析
        } else if (!isReserved(key)) {
            // TODO: 这里开始作一层_data的代理
            proxy(vm, '_data', key);
        }
    }
    // observe data
    // TODO: 这里调用全局observe函数 开始监听data
    observe(data, true /* asRootData */);
}
// 0.1.1 判断是否使用以特殊字符开头的变量 比如$ 或者_开头
function isReserved(str) {
  var c = (str + '').charCodeAt(0);
  return c === 0x24 || c === 0x5f;
}
// 0.1.2 作一层_data的代理
function proxy(target, sourceKey, key) {
  // TODO: 这里就是第一次见开始使用Object.defineProperty 来重新定义vm里面data数据
  // 意味着我们每次访问this.xxx属性 都会去访问this._data.xxx属性
  // 因为在0.1里面我们将data函数里面的值赋值给了_data
  // 好 这里意思搞清楚后 我们回到0.1
  sharedPropertyDefinition.get = function proxyGetter() {
    return this[sourceKey][key];
  };
  sharedPropertyDefinition.set = function proxySetter(val) {
    this[sourceKey][key] = val;
  };
  Object.defineProperty(target, key, sharedPropertyDefinition);
}
// 0.1.3 定义分享属性对象
var sharedPropertyDefinition = {
  enumerable: true,
  configurable: true,
  get: noop,
  set: noop,
};
// 0.1.4 空函数 很多地方使用了
function noop(a, b, c) {}
// 0.1.5 获取data
function getData(data, vm) {
  // #7573 disable dep collection when invoking data getters
  // TODO:这里跟之前分析computed在Watcher的原型方法get中调用了pushTarget
  // 唯一的区别就是在那里传了this(Watcher实例)而这里没传值过去
  pushTarget();
  try {
    // TODO:这里以vm实例调用data 即我们在data函数里 this指向vm实例
    // 这里就是作了执行data() 返回我们声明的data值对象{}
    return data.call(vm, vm);
  } catch (e) {
    handleError(e, vm, 'data()');
    return {};
  } finally {
    popTarget();
  }
}
源码4738执行observe(data,true) 此时这个data为根结点数据 添加为响应 见下面
function observe(value, asRootData) {
    // TODO: 这里给值创建一个监听 若已到了最后一层 则返回
    // 比如 return {
    //    name:'test',
    //    list:{
    //      xxx:"data"
    //    }
    // } 初始化进来是第一层 则继续执行
    // 当到了name的时候 已经为最后一层 则return
    // 当到了list还未停止 继续 直到xxx
    if (!isObject(value) || value instanceof VNode) {
        return;
    }
    var ob;
    // TODO: __ob__ 这个属性是在Observer里面才创建的 创建过的就不需要创建了
    if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
        ob = value.__ob__;
    } else if (shouldObserve && !isServerRendering() && (Array.isArray(value) || isPlainObject(value)) && Object.isExtensible(value) && !value._isVue) {
        // TODO: 为值创建响应式  这个很关键
        ob = new Observer(value);
    }
    if (asRootData && ob) {
        ob.vmCount++;
    }
    return ob;
}
var Observer = function Observer(value) {
  this.value = value;
  // TODO:new 一个收集者
  this.dep = new Dep();
  this.vmCount = 0;
  // TODO: 定义value里面的__ob__绑定一个监测类实例 但是不能枚举出来 隐式属性
  def(value, '__ob__', this);
  // TODO: 判断当前value是否为数组
  if (Array.isArray(value)) {
    if (hasProto) {
      protoAugment(value, arrayMethods);
    } else {
      copyAugment(value, arrayMethods, arrayKeys);
    }
    this.observeArray(value);
  } else {
    // TODO: 我们一般的data函数返回对象 走walk方法
    this.walk(value);
  }
};
//  给value定义一个隐藏属性__ob__ 值为监测类实例
function def(obj, key, val, enumerable) {
  Object.defineProperty(obj, key, {
    value: val,
    enumerable: !!enumerable,
    writable: true,
    configurable: true,
  });
}
// Observer原型上walk方法
Observer.prototype.walk = function walk(obj) {
  var keys = Object.keys(obj);
  for (var i = 0; i < keys.length; i++) {
      // 定义每个属性的响应式
    defineReactive$$1(obj, keys[i]);
  }
};
// 对一个对象 定义一系列响应式属性
function defineReactive$$1(obj, key, val, customSetter, shallow) {
  // TODO: 这里new一个收集者
  var dep = new Dep();
  // TODO:这里拿到当前data的自有属性描述符 比如value&configurable&get&set等等
  var property = Object.getOwnPropertyDescriptor(obj, key);
  if (property && property.configurable === false) {
    return;
  }
  // cater for pre-defined getter/setters
  // TODO: 去拿data函数中设置的数据值的get set
  var getter = property && property.get;
  var setter = property && property.set;
  // TODO: 这里很有趣了 我们在初始化得时候 正常设置data值时 是没得get set方法的
  // 而且在7.2中初始化调用defineReactive$$1方法 入参就是2个 所以进入这个判断
  if ((!getter || setter) && arguments.length === 2) {
    // TODO:将当前当前data中key值给给val
    val = obj[key];
  }
  // TODO: 初始化 这个childOb为undefined
  // TODO:0118 这个childOb 其实就是当我们的data数据为对象时
  /**
   *    比如:data(){
   *        return {
   *          message:'test',
   *          list:{
   *            xxx:'msg'
   *          }
   * 
   *        }    
   *    }
   *    这种在给getter的时候会触发childOb的依赖收集
   */
  var childOb = !shallow && observe(val);
  // TODO:响应式核心来了 为每个data的数据进行defineProperty设置 get&set
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter() {
        // 在访问该属性时 这里有闭包的特性 此时的val还是之前的值 包括dep也是第一次初始化的对象
      var value = getter ? getter.call(obj) : val;
      if (Dep.target) {
          // 收集依赖
        dep.depend();
        if (childOb) {
          childOb.dep.depend();
          if (Array.isArray(value)) {
            dependArray(value);
          }
        }
      }
      return value;
    },
    set: function reactiveSetter(newVal) {
      var value = getter ? getter.call(obj) : val;
      /* eslint-disable no-self-compare */
      // TODO: 这里判断一手值是否变化
      if (newVal === value || (newVal !== newVal && value !== value)) {
        return;
      }
      /* eslint-enable no-self-compare */
      if (customSetter) {
        customSetter();
      }
      // #7981: for accessor properties without setter
      if (getter && !setter) {
        return;
      }
      if (setter) {
        setter.call(obj, newVal);
      } else {
        val = newVal;
      }
      childOb = !shallow && observe(newVal);
        // 通知 发布-订阅模式 关键-》发布
      dep.notify();
    },
  });
}
// 4. 全局函数pushTarget&popTarget&全局变量targetStack
// The current target watcher being evaluated.
// This is globally unique because only one watcher
// can be evaluated at a time.
Dep.target = null;
var targetStack = [];
function pushTarget(target) {
  // TODO: 在前面的3中调用了pushTarget 这里将从3中的this(Watcher实例)压进targetStack
  targetStack.push(target);
  // TODO: 给Dep的属性target挂载Watcher实例 接下来分析下Dep
  Dep.target = target;
}
function popTarget() {
  targetStack.pop();
  Dep.target = targetStack[targetStack.length - 1];
}
依赖收集中间站(发布者)
var uid = 0;
/**
 * A dep is an observable that can have multiple
 * directives subscribing to it.
 */
// TODO: 声明全局Dep构造函数也就是全局对象 (发布者)
var Dep = function Dep() {
  this.id = uid++;
  // TODO: 构造函数上挂载subs属性为数组集合 里面存的是每个订阅者也就是watcher
  this.subs = [];
};
// TODO:压入subs方法
Dep.prototype.addSub = function addSub(sub) {
  this.subs.push(sub);
};
// TODO:清除sub方法
Dep.prototype.removeSub = function removeSub(sub) {
  // TODO: 这里调用了个全局工具方法 清除数组指定项
  /**
   *   function remove (arr, item) {
          if (arr.length) {
            var index = arr.indexOf(item);
            if (index > -1) {
              return arr.splice(index, 1)
            }
          }
        }
   * 
   */
  remove(this.subs, sub);
};
// TODO:建立“依赖”方法
Dep.prototype.depend = function depend() {
  // TODO: 这里在初始化的时候 Dep.target值为null
  // 但是我们在前面的3中调用了pushTarget方法 而pushTarget方法中将Dep.target
  // 设置为了那个时间段的Watcher实例
  if (Dep.target) {
    // TODO: 这里又切调用了Watcher实例的addDep方法 接下来分析下Watcher上面的addDep方法
    // 并传入了Dep这个当前实例(上面挂载了id和subs集合)
    Dep.target.addDep(this);
  }
};
// TODO: 建立“通知”方法
Dep.prototype.notify = function notify() {
  // stabilize the subscriber list first
  var subs = this.subs.slice();
  if (!config.async) {
    // subs aren't sorted in scheduler if not running async
    // we need to sort them now to make sure they fire in correct
    // order
    subs.sort(function (a, b) {
      return a.id - b.id;
    });
  }
  for (var i = 0, l = subs.length; i < l; i++) {
      // 关键的步骤 执行订阅者的update 
    subs[i].update();
  }
};
(观察者&订阅者)
var uid$2 = 0;
var Watcher = function Watcher(vm, expOrFn, cb, options, isRenderWatcher) {
  this.vm = vm;
  if (isRenderWatcher) {
    vm._watcher = this;
  }
  // TODO: 将当前this指向的Watcher实例压进vue实例下私有属性_watchers
  vm._watchers.push(this);
  if (options) {
    this.deep = !!options.deep;
    this.user = !!options.user;
    this.lazy = !!options.lazy;
    this.sync = !!options.sync;
    this.before = options.before;
  } else {
    this.deep = this.user = this.lazy = this.sync = false;
  }
  this.cb = cb;
  this.id = ++uid$2; // uid for batching
  this.active = true;
  this.dirty = this.lazy; // for lazy watchers
  this.deps = [];
  this.newDeps = [];
  // TODO: 这里判断是否支持Set不支持就模拟Set(并提供has&add&clear内部方法)
  this.depIds = new _Set();
  this.newDepIds = new _Set();
  // TODO: 这里将方法字符串化
  this.expression = expOrFn.toString();
  if (typeof expOrFn === 'function') {
    // TODO: 这里将执行方法挂载到Watcher实例的getter属性上
    this.getter = expOrFn;
  } else {
    this.getter = parsePath(expOrFn);
    if (!this.getter) {
      this.getter = noop;
      warn('Failed watching path: "' + expOrFn + '" ' + 'Watcher only accepts simple dot-delimited paths. ' + 'For full control, use a function instead.', vm);
    }
  }
  // TODO: 初始化是当前value值均为undefined 后面调用Watcher实例的get()方法
  this.value = this.lazy ? undefined : this.get();
};
// 3. Watcher原型上的get方法
Watcher.prototype.get = function get() {
  // TODO: 调用了全局函数pushTarget 这里跟另一个模块Dep有关了
  // 顺势找到Dep分析下这个响应式
  pushTarget(this);
  // TODO:执行这步的意义何在
  // 就是为了给全局对象Dep的target属性挂载上当前的Watcher实例
  var value;
  var vm = this.vm;
  try {
    // TODO: 执行前端编写的函数 内部this指向vue实例 传参vue实例
    // TODO: 0118 这个地方的getter还不是这么简单的理解
    // 多数情况下他是一个updateComponent函数
    value = this.getter.call(vm, vm);
  } catch (e) {
    if (this.user) {
      handleError(e, vm, 'getter for watcher "' + this.expression + '"');
    } else {
      throw e;
    }
  } finally {
    if (this.deep) {
      traverse(value);
    }
    // TODO: 每次执行完this.getter后 释放全局变量targetStack 且置
    // Dep.target为null 这里就是每次作update时保持一个watcher执行
    popTarget();
    this.cleanupDeps();
  }
  return value;
};
// 3.1 清理依赖
Watcher.prototype.cleanupDeps = function cleanupDeps () {
  var i = this.deps.length;
  while (i--) {
    var dep = this.deps[i];
    if (!this.newDepIds.has(dep.id)) {
      dep.removeSub(this);
    }
  }
  var tmp = this.depIds;
  this.depIds = this.newDepIds;
  this.newDepIds = tmp;
  this.newDepIds.clear();
  tmp = this.deps;
  this.deps = this.newDeps;
  this.newDeps = tmp;
  this.newDeps.length = 0;
};
Watcher.prototype.addDep = function addDep(dep) {
  var id = dep.id;
  // TODO: 判断当前id是否已在Watcher实例的newDepIds属性里面
  // 这里的newDepIds是一个Set数据结构属性
  if (!this.newDepIds.has(id)) {
    // TODO: 没得就add进去
    this.newDepIds.add(id);
    // TODO: 这里newDeps是数组结构 将当前dep push进去
    this.newDeps.push(dep);
    if (!this.depIds.has(id)) {
      // TODO:同理这里调用的是当前dep实例上的addSub 也就是将当前的Watcher实例
      // 压进dep的subs数组里面
      dep.addSub(this);
    }
  }
  // TODO:这步完了后 又回到了3里面的pushTarget后续逻辑
};
Watcher.prototype.update = function update() {
  /* istanbul ignore else */
  if (this.lazy) {
    this.dirty = true;
  } else if (this.sync) {
    this.run();
  } else {
    queueWatcher(this);
  }
};
Watcher.prototype.run = function run() {
  if (this.active) {
    var value = this.get();
    if (
      value !== this.value ||
      // Deep watchers and watchers on Object/Arrays should fire even
      // when the value is the same, because the value may
      // have mutated.
      isObject(value) ||
      this.deep
    ) {
      // set new value
      var oldValue = this.value;
      this.value = value;
      if (this.user) {
        try {
          this.cb.call(this.vm, value, oldValue);
        } catch (e) {
          handleError(e, this.vm, 'callback for watcher "' + this.expression + '"');
        }
      } else {
        this.cb.call(this.vm, value, oldValue);
      }
    }
  }
};
watcher队列处理
/**
 * Push a watcher into the watcher queue.
 * Jobs with duplicate IDs will be skipped unless it's
 * pushed when the queue is being flushed.
 */
function queueWatcher(watcher) {
  var id = watcher.id;
  if (has[id] == null) {
    has[id] = true;
    if (!flushing) {
      queue.push(watcher);
    } else {
      // if already flushing, splice the watcher based on its id
      // if already past its id, it will be run next immediately.
      var i = queue.length - 1;
      while (i > index && queue[i].id > watcher.id) {
        i--;
      }
      queue.splice(i + 1, 0, watcher);
    }
    // queue the flush
    if (!waiting) {
      waiting = true;

      if (!config.async) {
        flushSchedulerQueue();
        return;
      }
      nextTick(flushSchedulerQueue);
    }
  }
}
/**
 * Flush both queues and run the watchers.
 */
function flushSchedulerQueue() {
  currentFlushTimestamp = getNow();
  flushing = true;
  var watcher, id;

  // Sort queue before flush.
  // This ensures that:
  // 1. Components are updated from parent to child. (because parent is always
  //    created before the child)
  // 2. A component's user watchers are run before its render watcher (because
  //    user watchers are created before the render watcher)
  // 3. If a component is destroyed during a parent component's watcher run,
  //    its watchers can be skipped.
  queue.sort(function (a, b) {
    return a.id - b.id;
  });

  // do not cache length because more watchers might be pushed
  // as we run existing watchers
  for (index = 0; index < queue.length; index++) {
    watcher = queue[index];
    if (watcher.before) {
      watcher.before();
    }
    id = watcher.id;
    has[id] = null;
    watcher.run();
    // in dev build, check and stop circular updates.
    if (has[id] != null) {
      circular[id] = (circular[id] || 0) + 1;
      if (circular[id] > MAX_UPDATE_COUNT) {
        warn('You may have an infinite update loop ' + (watcher.user ? 'in watcher with expression "' + watcher.expression + '"' : 'in a component render function.'), watcher.vm);
        break;
      }
    }
  }

  // keep copies of post queues before resetting state
  var activatedQueue = activatedChildren.slice();
  var updatedQueue = queue.slice();

  resetSchedulerState();

  // call component updated and activated hooks
  callActivatedHooks(activatedQueue);
  callUpdatedHooks(updatedQueue);

  // devtool hook
  /* istanbul ignore if */
  if (devtools && config.devtools) {
    devtools.emit('flush');
  }
}

以上就是vue响应式核心部分(设计模式:发布-订阅模式

  1. data(作一层proxy代理访问_data)

  2. observe(为data主动构建监测数据)

  3. new Observer() (主动绑定__ob__属性 调用walk方法 )

  4. 遍历每个Object对象 (为对象属性进行defineReactive$$1方法)

  5. Object.defineProperty (每个属性生成一次收集者且会判断是否多层对象嵌套再次调用b步骤直到每个属性均可响应式;定义属性get(利用函数闭包特性 收集当前依赖项)定义属性set(判断值是否完全变化,且给新值再次添加b步骤响应式属性 最后发布依赖))

  6. 手动调用挂载方法vm.$mount(’#app’) (执行mountComponent方法->触发beforeMount钩子函数->生成updateComponent方法->vm._update(vm._render(),hydrating)

  7. 创建观察者Watcher(加入brefore->钩子回调触发beforeUpdate钩子函数->绑定getter方法为updateComponent方法)

  8. 触发Watcher get方法(pushTarget置全局依赖标识Dep.target为当前watcher实例)

  9. 调用步骤g中的getter方法(就是调用vm._update(vm._render(),hydrating)

    ////////// 次部分代码涉及关于VNode等渲染问题 是另一个值得研究的问题

  10. 调用vm._render() (其实就是执行以with包括的方法体)

  11. 调用vm._update()

  12. 触发拿值属性步骤e中的get (收集依赖)

  13. 虚拟dom到真实dom渲染完毕

    //////////

  14. 调用popTarget (置当前全局依赖标识Dep.target为undefined)

  15. 调用当前观察者watcher清除依赖方法(将newDepIds、newDeps赋值给depIds、deps后再置为空以便接收下一次更新)

  16. 触发生命周期钩子“mounted”

    1.png
分析下面报错 font.js:45 GET http://localhost:8080/api/font/list 422 (UNPROCESSABLE ENTITY) dispatchXhrRequest @ xhr.js:187 xhrAdapter @ xhr.js:13 dispatchRequest @ dispatchRequest.js:53 Promise.then request @ Axios.js:88 Axios.<computed> @ Axios.js:129 wrap @ bind.js:9 eval @ font.js:45 fetchUserFonts @ font.js:43 wrappedActionHandler @ vuex.esm.js:862 dispatch @ vuex.esm.js:527 boundDispatch @ vuex.esm.js:417 created @ index.js??clonedRuleSet-40.use[0]!./node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./src/views/CarvingForm.vue?vue&type=script&lang=js:49 invokeWithErrorHandling @ vue.runtime.esm.js:3083 callHook$1 @ vue.runtime.esm.js:4098 Vue._init @ vue.runtime.esm.js:5763 VueComponent @ vue.runtime.esm.js:5896 createComponentInstanceForVnode @ vue.runtime.esm.js:4613 init @ vue.runtime.esm.js:4475 merged @ vue.runtime.esm.js:4630 createComponent @ vue.runtime.esm.js:6638 createElm @ vue.runtime.esm.js:6592 updateChildren @ vue.runtime.esm.js:6887 patchVnode @ vue.runtime.esm.js:6980 patch @ vue.runtime.esm.js:7148 Vue._update @ vue.runtime.esm.js:3835 updateComponent @ vue.runtime.esm.js:3941 Watcher.get @ vue.runtime.esm.js:3512 Watcher.run @ vue.runtime.esm.js:3588 flushSchedulerQueue @ vue.runtime.esm.js:4191 eval @ vue.runtime.esm.js:3209 flushCallbacks @ vue.runtime.esm.js:3131 Promise.then timerFunc @ vue.runtime.esm.js:3156 nextTick @ vue.runtime.esm.js:3221 queueWatcher @ vue.runtime.esm.js:4277 Watcher.update @ vue.runtime.esm.js:3579 Dep.notify @ vue.runtime.esm.js:792 reactiveSetter @ vue.runtime.esm.js:1025 proxySetter @ vue.runtime.esm.js:5425 Message @ element-ui.common.js:3379 main_Message.<computed> @ element-ui.common.js:3379 eval @ index.js??clonedRuleSet-40.use[0]!./node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./src/views/Login.vue?vue&type=script&lang=js:49 Promise.then eval @ index.js??clonedRuleSet-40.use[0]!./node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./src/views/Login.vue?vue&type=script&lang=js:48 eval @ element-ui.common.js:2686 eval @ element-ui.common.js:2740 complete @ index.js:95 eval @ index.js:231 next @ util.js:161 next @ util.js:129 cb @ index.js:179 required @ required.js:11 eval @ index.js:222 next @ util.js:127 asyncSerialArray @ util.js:133 eval @ util.js:167 asyncMap @ util.js:164 validate @ index.js:147 validate @ element-ui.common.js:2740 eval @ element-ui.common.js:2686 validate @ element-ui.common.js:2686 handleLogin @ index.js??clonedRuleSet-40.use[0]!./node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./src/views/Login.vue?vue&type=script&lang=js:45 invokeWithErrorHandling @ vue.runtime.esm.js:3083 invoker @ vue.runtime.esm.js:1884 invokeWithErrorHandling @ vue.runtime.esm.js:3083 Vue.$emit @ vue.runtime.esm.js:3782 handleClick @ element-ui.common.js:1118 invokeWithErrorHandling @ vue.runtime.esm.js:3083 invoker @ vue.runtime.esm.js:1884 original_1._wrapper @ vue.runtime.esm.js:7547 axios.js:85 ===== 响应拦截器捕获错误 ===== eval @ axios.js:85 Promise.then request @ Axios.js:88 Axios.<computed> @ Axios.js:129 wrap @ bind.js:9 eval @ font.js:45 fetchUserFonts @ font.js:43 wrappedActionHandler @ vuex.esm.js:862 dispatch @ vuex.esm.js:527 boundDispatch @ vuex.esm.js:417 created @ index.js??clonedRuleSet-40.use[0]!./node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./src/views/CarvingForm.vue?vue&type=script&lang=js:49 invokeWithErrorHandling @ vue.runtime.esm.js:3083 callHook$1 @ vue.runtime.esm.js:4098 Vue._init @ vue.runtime.esm.js:5763 VueComponent @ vue.runtime.esm.js:5896 createComponentInstanceForVnode @ vue.runtime.esm.js:4613 init @ vue.runtime.esm.js:4475 merged @ vue.runtime.esm.js:4630 createComponent @ vue.runtime.esm.js:6638 createElm @ vue.runtime.esm.js:6592 updateChildren @ vue.runtime.esm.js:6887 patchVnode @ vue.runtime.esm.js:6980 patch @ vue.runtime.esm.js:7148 Vue._update @ vue.runtime.esm.js:3835 updateComponent @ vue.runtime.esm.js:3941 Watcher.get @ vue.runtime.esm.js:3512 Watcher.run @ vue.runtime.esm.js:3588 flushSchedulerQueue @ vue.runtime.esm.js:4191 eval @ vue.runtime.esm.js:3209 flushCallbacks @ vue.runtime.esm.js:3131 Promise.then timerFunc @ vue.runtime.esm.js:3156 nextTick @ vue.runtime.esm.js:3221 queueWatcher @ vue.runtime.esm.js:4277 Watcher.update @ vue.runtime.esm.js:3579 Dep.notify @ vue.runtime.esm.js:792 reactiveSetter @ vue.runtime.esm.js:1025 proxySetter @ vue.runtime.esm.js:5425 Message @ element-ui.common.js:3379 main_Message.<computed> @ element-ui.common.js:3379 eval @ index.js??clonedRuleSet-40.use[0]!./node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./src/views/Login.vue?vue&type=script&lang=js:49 Promise.then eval @ index.js??clonedRuleSet-40.use[0]!./node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./src/views/Login.vue?vue&type=script&lang=js:48 eval @ element-ui.common.js:2686 eval @ element-ui.common.js:2740 complete @ index.js:95 eval @ index.js:231 next @ util.js:161 next @ util.js:129 cb @ index.js:179 required @ required.js:11 eval @ index.js:222 next @ util.js:127 asyncSerialArray @ util.js:133 eval @ util.js:167 asyncMap @ util.js:164 validate @ index.js:147 validate @ element-ui.common.js:2740 eval @ element-ui.common.js:2686 validate @ element-ui.common.js:2686 handleLogin @ index.js??clonedRuleSet-40.use[0]!./node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./src/views/Login.vue?vue&type=script&lang=js:45 invokeWithErrorHandling @ vue.runtime.esm.js:3083 invoker @ vue.runtime.esm.js:1884 invokeWithErrorHandling @ vue.runtime.esm.js:3083 Vue.$emit @ vue.runtime.esm.js:3782 handleClick @ element-ui.common.js:1118 invokeWithErrorHandling @ vue.runtime.esm.js:3083 invoker @ vue.runtime.esm.js:1884 original_1._wrapper @ vue.runtime.esm.js:7547 axios.js:86 请求地址: /font/list eval @ axios.js:86 Promise.then request @ Axios.js:88 Axios.<computed> @ Axios.js:129 wrap @ bind.js:9 eval @ font.js:45 fetchUserFonts @ font.js:43 wrappedActionHandler @ vuex.esm.js:862 dispatch @ vuex.esm.js:527 boundDispatch @ vuex.esm.js:417 created @ index.js??clonedRuleSet-40.use[0]!./node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./src/views/CarvingForm.vue?vue&type=script&lang=js:49 invokeWithErrorHandling @ vue.runtime.esm.js:3083 callHook$1 @ vue.runtime.esm.js:4098 Vue._init @ vue.runtime.esm.js:5763 VueComponent @ vue.runtime.esm.js:5896 createComponentInstanceForVnode @ vue.runtime.esm.js:4613 init @ vue.runtime.esm.js:4475 merged @ vue.runtime.esm.js:4630 createComponent @ vue.runtime.esm.js:6638 createElm @ vue.runtime.esm.js:6592 updateChildren @ vue.runtime.esm.js:6887 patchVnode @ vue.runtime.esm.js:6980 patch @ vue.runtime.esm.js:7148 Vue._update @ vue.runtime.esm.js:3835 updateComponent @ vue.runtime.esm.js:3941 Watcher.get @ vue.runtime.esm.js:3512 Watcher.run @ vue.runtime.esm.js:3588 flushSchedulerQueue @ vue.runtime.esm.js:4191 eval @ vue.runtime.esm.js:3209 flushCallbacks @ vue.runtime.esm.js:3131 Promise.then timerFunc @ vue.runtime.esm.js:3156 nextTick @ vue.runtime.esm.js:3221 queueWatcher @ vue.runtime.esm.js:4277 Watcher.update @ vue.runtime.esm.js:3579 Dep.notify @ vue.runtime.esm.js:792 reactiveSetter @ vue.runtime.esm.js:1025 proxySetter @ vue.runtime.esm.js:5425 Message @ element-ui.common.js:3379 main_Message.<computed> @ element-ui.common.js:3379 eval @ index.js??clonedRuleSet-40.use[0]!./node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./src/views/Login.vue?vue&type=script&lang=js:49 Promise.then eval @ index.js??clonedRuleSet-40.use[0]!./node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./src/views/Login.vue?vue&type=script&lang=js:48 eval @ element-ui.common.js:2686 eval @ element-ui.common.js:2740 complete @ index.js:95 eval @ index.js:231 next @ util.js:161 next @ util.js:129 cb @ index.js:179 required @ required.js:11 eval @ index.js:222 next @ util.js:127 asyncSerialArray @ util.js:133 eval @ util.js:167 asyncMap @ util.js:164 validate @ index.js:147 validate @ element-ui.common.js:2740 eval @ element-ui.common.js:2686 validate @ element-ui.common.js:2686 handleLogin @ index.js??clonedRuleSet-40.use[0]!./node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./src/views/Login.vue?vue&type=script&lang=js:45 invokeWithErrorHandling @ vue.runtime.esm.js:3083 invoker @ vue.runtime.esm.js:1884 invokeWithErrorHandling @ vue.runtime.esm.js:3083 Vue.$emit @ vue.runtime.esm.js:3782 handleClick @ element-ui.common.js:1118 invokeWithErrorHandling @ vue.runtime.esm.js:3083 invoker @ vue.runtime.esm.js:1884 original_1._wrapper @ vue.runtime.esm.js:7547 axios.js:87 原始错误信息: Error: Request failed with status code 422 at createError (createError.js:16:15) at settle (settle.js:17:12) at XMLHttpRequest.onloadend (xhr.js:54:7) eval @ axios.js:87 Promise.then request @ Axios.js:88 Axios.<computed> @ Axios.js:129 wrap @ bind.js:9 eval @ font.js:45 fetchUserFonts @ font.js:43 wrappedActionHandler @ vuex.esm.js:862 dispatch @ vuex.esm.js:527 boundDispatch @ vuex.esm.js:417 created @ index.js??clonedRuleSet-40.use[0]!./node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./src/views/CarvingForm.vue?vue&type=script&lang=js:49 invokeWithErrorHandling @ vue.runtime.esm.js:3083 callHook$1 @ vue.runtime.esm.js:4098 Vue._init @ vue.runtime.esm.js:5763 VueComponent @ vue.runtime.esm.js:5896 createComponentInstanceForVnode @ vue.runtime.esm.js:4613 init @ vue.runtime.esm.js:4475 merged @ vue.runtime.esm.js:4630 createComponent @ vue.runtime.esm.js:6638 createElm @ vue.runtime.esm.js:6592 updateChildren @ vue.runtime.esm.js:6887 patchVnode @ vue.runtime.esm.js:6980 patch @ vue.runtime.esm.js:7148 Vue._update @ vue.runtime.esm.js:3835 updateComponent @ vue.runtime.esm.js:3941 Watcher.get @ vue.runtime.esm.js:3512 Watcher.run @ vue.runtime.esm.js:3588 flushSchedulerQueue @ vue.runtime.esm.js:4191 eval @ vue.runtime.esm.js:3209 flushCallbacks @ vue.runtime.esm.js:3131 Promise.then timerFunc @ vue.runtime.esm.js:3156 nextTick @ vue.runtime.esm.js:3221 queueWatcher @ vue.runtime.esm.js:4277 Watcher.update @ vue.runtime.esm.js:3579 Dep.notify @ vue.runtime.esm.js:792 reactiveSetter @ vue.runtime.esm.js:1025 proxySetter @ vue.runtime.esm.js:5425 Message @ element-ui.common.js:3379 main_Message.<computed> @ element-ui.common.js:3379 eval @ index.js??clonedRuleSet-40.use[0]!./node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./src/views/Login.vue?vue&type=script&lang=js:49 Promise.then eval @ index.js??clonedRuleSet-40.use[0]!./node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./src/views/Login.vue?vue&type=script&lang=js:48 eval @ element-ui.common.js:2686 eval @ element-ui.common.js:2740 complete @ index.js:95 eval @ index.js:231 next @ util.js:161 next @ util.js:129 cb @ index.js:179 required @ required.js:11 eval @ index.js:222 next @ util.js:127 asyncSerialArray @ util.js:133 eval @ util.js:167 asyncMap @ util.js:164 validate @ index.js:147 validate @ element-ui.common.js:2740 eval @ element-ui.common.js:2686 validate @ element-ui.common.js:2686 handleLogin @ index.js??clonedRuleSet-40.use[0]!./node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./src/views/Login.vue?vue&type=script&lang=js:45 invokeWithErrorHandling @ vue.runtime.esm.js:3083 invoker @ vue.runtime.esm.js:1884 invokeWithErrorHandling @ vue.runtime.esm.js:3083 Vue.$emit @ vue.runtime.esm.js:3782 handleClick @ element-ui.common.js:1118 invokeWithErrorHandling @ vue.runtime.esm.js:3083 invoker @ vue.runtime.esm.js:1884 original_1._wrapper @ vue.runtime.esm.js:7547 axios.js:97 响应状态码: 422 eval @ axios.js:97 Promise.then request @ Axios.js:88 Axios.<computed> @ Axios.js:129 wrap @ bind.js:9 eval @ font.js:45 fetchUserFonts @ font.js:43 wrappedActionHandler @ vuex.esm.js:862 dispatch @ vuex.esm.js:527 boundDispatch @ vuex.esm.js:417 created @ index.js??clonedRuleSet-40.use[0]!./node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./src/views/CarvingForm.vue?vue&type=script&lang=js:49 invokeWithErrorHandling @ vue.runtime.esm.js:3083 callHook$1 @ vue.runtime.esm.js:4098 Vue._init @ vue.runtime.esm.js:5763 VueComponent @ vue.runtime.esm.js:5896 createComponentInstanceForVnode @ vue.runtime.esm.js:4613 init @ vue.runtime.esm.js:4475 merged @ vue.runtime.esm.js:4630 createComponent @ vue.runtime.esm.js:6638 createElm @ vue.runtime.esm.js:6592 updateChildren @ vue.runtime.esm.js:6887 patchVnode @ vue.runtime.esm.js:6980 patch @ vue.runtime.esm.js:7148 Vue._update @ vue.runtime.esm.js:3835 updateComponent @ vue.runtime.esm.js:3941 Watcher.get @ vue.runtime.esm.js:3512 Watcher.run @ vue.runtime.esm.js:3588 flushSchedulerQueue @ vue.runtime.esm.js:4191 eval @ vue.runtime.esm.js:3209 flushCallbacks @ vue.runtime.esm.js:3131 Promise.then timerFunc @ vue.runtime.esm.js:3156 nextTick @ vue.runtime.esm.js:3221 queueWatcher @ vue.runtime.esm.js:4277 Watcher.update @ vue.runtime.esm.js:3579 Dep.notify @ vue.runtime.esm.js:792 reactiveSetter @ vue.runtime.esm.js:1025 proxySetter @ vue.runtime.esm.js:5425 Message @ element-ui.common.js:3379 main_Message.<computed> @ element-ui.common.js:3379 eval @ index.js??clonedRuleSet-40.use[0]!./node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./src/views/Login.vue?vue&type=script&lang=js:49 Promise.then eval @ index.js??clonedRuleSet-40.use[0]!./node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./src/views/Login.vue?vue&type=script&lang=js:48 eval @ element-ui.common.js:2686 eval @ element-ui.common.js:2740 complete @ index.js:95 eval @ index.js:231 next @ util.js:161 next @ util.js:129 cb @ index.js:179 required @ required.js:11 eval @ index.js:222 next @ util.js:127 asyncSerialArray @ util.js:133 eval @ util.js:167 asyncMap @ util.js:164 validate @ index.js:147 validate @ element-ui.common.js:2740 eval @ element-ui.common.js:2686 validate @ element-ui.common.js:2686 handleLogin @ index.js??clonedRuleSet-40.use[0]!./node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./src/views/Login.vue?vue&type=script&lang=js:45 invokeWithErrorHandling @ vue.runtime.esm.js:3083 invoker @ vue.runtime.esm.js:1884 invokeWithErrorHandling @ vue.runtime.esm.js:3083 Vue.$emit @ vue.runtime.esm.js:3782 handleClick @ element-ui.common.js:1118 invokeWithErrorHandling @ vue.runtime.esm.js:3083 invoker @ vue.runtime.esm.js:1884 original_1._wrapper @ vue.runtime.esm.js:7547 axios.js:98 响应数据: {msg: 'Subject must be a string'}msg: "Subject must be a string"[[Prototype]]: Object eval @ axios.js:98 Promise.then request @ Axios.js:88 Axios.<computed> @ Axios.js:129 wrap @ bind.js:9 eval @ font.js:45 fetchUserFonts @ font.js:43 wrappedActionHandler @ vuex.esm.js:862 dispatch @ vuex.esm.js:527 boundDispatch @ vuex.esm.js:417 created @ index.js??clonedRuleSet-40.use[0]!./node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./src/views/CarvingForm.vue?vue&type=script&lang=js:49 invokeWithErrorHandling @ vue.runtime.esm.js:3083 callHook$1 @ vue.runtime.esm.js:4098 Vue._init @ vue.runtime.esm.js:5763 VueComponent @ vue.runtime.esm.js:5896 createComponentInstanceForVnode @ vue.runtime.esm.js:4613 init @ vue.runtime.esm.js:4475 merged @ vue.runtime.esm.js:4630 createComponent @ vue.runtime.esm.js:6638 createElm @ vue.runtime.esm.js:6592 updateChildren @ vue.runtime.esm.js:6887 patchVnode @ vue.runtime.esm.js:6980 patch @ vue.runtime.esm.js:7148 Vue._update @ vue.runtime.esm.js:3835 updateComponent @ vue.runtime.esm.js:3941 Watcher.get @ vue.runtime.esm.js:3512 Watcher.run @ vue.runtime.esm.js:3588 flushSchedulerQueue @ vue.runtime.esm.js:4191 eval @ vue.runtime.esm.js:3209 flushCallbacks @ vue.runtime.esm.js:3131 Promise.then timerFunc @ vue.runtime.esm.js:3156 nextTick @ vue.runtime.esm.js:3221 queueWatcher @ vue.runtime.esm.js:4277 Watcher.update @ vue.runtime.esm.js:3579 Dep.notify @ vue.runtime.esm.js:792 reactiveSetter @ vue.runtime.esm.js:1025 proxySetter @ vue.runtime.esm.js:5425 Message @ element-ui.common.js:3379 main_Message.<computed> @ element-ui.common.js:3379 eval @ index.js??clonedRuleSet-40.use[0]!./node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./src/views/Login.vue?vue&type=script&lang=js:49 Promise.then eval @ index.js??clonedRuleSet-40.use[0]!./node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./src/views/Login.vue?vue&type=script&lang=js:48 eval @ element-ui.common.js:2686 eval @ element-ui.common.js:2740 complete @ index.js:95 eval @ index.js:231 next @ util.js:161 next @ util.js:129 cb @ index.js:179 required @ required.js:11 eval @ index.js:222 next @ util.js:127 asyncSerialArray @ util.js:133 eval @ util.js:167 asyncMap @ util.js:164 validate @ index.js:147 validate @ element-ui.common.js:2740 eval @ element-ui.common.js:2686 validate @ element-ui.common.js:2686 handleLogin @ index.js??clonedRuleSet-40.use[0]!./node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./src/views/Login.vue?vue&type=script&lang=js:45 invokeWithErrorHandling @ vue.runtime.esm.js:3083 invoker @ vue.runtime.esm.js:1884 invokeWithErrorHandling @ vue.runtime.esm.js:3083 Vue.$emit @ vue.runtime.esm.js:3782 handleClick @ element-ui.common.js:1118 invokeWithErrorHandling @ vue.runtime.esm.js:3083 invoker @ vue.runtime.esm.js:1884 original_1._wrapper @ vue.runtime.esm.js:7547 axios.js:99 响应头: {connection: 'close', content-length: '35', content-type: 'application/json', date: 'Mon, 04 Aug 2025 16:08:48 GMT', server: 'Werkzeug/3.1.3 Python/3.11.8', …} eval @ axios.js:99 Promise.then request @ Axios.js:88 Axios.<computed> @ Axios.js:129 wrap @ bind.js:9 eval @ font.js:45 fetchUserFonts @ font.js:43 wrappedActionHandler @ vuex.esm.js:862 dispatch @ vuex.esm.js:527 boundDispatch @ vuex.esm.js:417 created @ index.js??clonedRuleSet-40.use[0]!./node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./src/views/CarvingForm.vue?vue&type=script&lang=js:49 invokeWithErrorHandling @ vue.runtime.esm.js:3083 callHook$1 @ vue.runtime.esm.js:4098 Vue._init @ vue.runtime.esm.js:5763 VueComponent @ vue.runtime.esm.js:5896 createComponentInstanceForVnode @ vue.runtime.esm.js:4613 init @ vue.runtime.esm.js:4475 merged @ vue.runtime.esm.js:4630 createComponent @ vue.runtime.esm.js:6638 createElm @ vue.runtime.esm.js:6592 updateChildren @ vue.runtime.esm.js:6887 patchVnode @ vue.runtime.esm.js:6980 patch @ vue.runtime.esm.js:7148 Vue._update @ vue.runtime.esm.js:3835 updateComponent @ vue.runtime.esm.js:3941 Watcher.get @ vue.runtime.esm.js:3512 Watcher.run @ vue.runtime.esm.js:3588 flushSchedulerQueue @ vue.runtime.esm.js:4191 eval @ vue.runtime.esm.js:3209 flushCallbacks @ vue.runtime.esm.js:3131 Promise.then timerFunc @ vue.runtime.esm.js:3156 nextTick @ vue.runtime.esm.js:3221 queueWatcher @ vue.runtime.esm.js:4277 Watcher.update @ vue.runtime.esm.js:3579 Dep.notify @ vue.runtime.esm.js:792 reactiveSetter @ vue.runtime.esm.js:1025 proxySetter @ vue.runtime.esm.js:5425 Message @ element-ui.common.js:3379 main_Message.<computed> @ element-ui.common.js:3379 eval @ index.js??clonedRuleSet-40.use[0]!./node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./src/views/Login.vue?vue&type=script&lang=js:49 Promise.then eval @ index.js??clonedRuleSet-40.use[0]!./node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./src/views/Login.vue?vue&type=script&lang=js:48 eval @ element-ui.common.js:2686 eval @ element-ui.common.js:2740 complete @ index.js:95 eval @ index.js:231 next @ util.js:161 next @ util.js:129 cb @ index.js:179 required @ required.js:11 eval @ index.js:222 next @ util.js:127 asyncSerialArray @ util.js:133 eval @ util.js:167 asyncMap @ util.js:164 validate @ index.js:147 validate @ element-ui.common.js:2740 eval @ element-ui.common.js:2686 validate @ element-ui.common.js:2686 handleLogin @ index.js??clonedRuleSet-40.use[0]!./node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./src/views/Login.vue?vue&type=script&lang=js:45 invokeWithErrorHandling @ vue.runtime.esm.js:3083 invoker @ vue.runtime.esm.js:1884 invokeWithErrorHandling @ vue.runtime.esm.js:3083 Vue.$emit @ vue.runtime.esm.js:3782 handleClick @ element-ui.common.js:1118 invokeWithErrorHandling @ vue.runtime.esm.js:3083 invoker @ vue.runtime.esm.js:1884 original_1._wrapper @ vue.runtime.esm.js:7547 axios.js:119 处理后的错误信息: {error: '请求失败 (422)', status: 422} eval @ axios.js:119 Promise.then request @ Axios.js:88 Axios.<computed> @ Axios.js:129 wrap @ bind.js:9 eval @ font.js:45 fetchUserFonts @ font.js:43 wrappedActionHandler @ vuex.esm.js:862 dispatch @ vuex.esm.js:527 boundDispatch @ vuex.esm.js:417 created @ index.js??clonedRuleSet-40.use[0]!./node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./src/views/CarvingForm.vue?vue&type=script&lang=js:49 invokeWithErrorHandling @ vue.runtime.esm.js:3083 callHook$1 @ vue.runtime.esm.js:4098 Vue._init @ vue.runtime.esm.js:5763 VueComponent @ vue.runtime.esm.js:5896 createComponentInstanceForVnode @ vue.runtime.esm.js:4613 init @ vue.runtime.esm.js:4475 merged @ vue.runtime.esm.js:4630 createComponent @ vue.runtime.esm.js:6638 createElm @ vue.runtime.esm.js:6592 updateChildren @ vue.runtime.esm.js:6887 patchVnode @ vue.runtime.esm.js:6980 patch @ vue.runtime.esm.js:7148 Vue._update @ vue.runtime.esm.js:3835 updateComponent @ vue.runtime.esm.js:3941 Watcher.get @ vue.runtime.esm.js:3512 Watcher.run @ vue.runtime.esm.js:3588 flushSchedulerQueue @ vue.runtime.esm.js:4191 eval @ vue.runtime.esm.js:3209 flushCallbacks @ vue.runtime.esm.js:3131 Promise.then timerFunc @ vue.runtime.esm.js:3156 nextTick @ vue.runtime.esm.js:3221 queueWatcher @ vue.runtime.esm.js:4277 Watcher.update @ vue.runtime.esm.js:3579 Dep.notify @ vue.runtime.esm.js:792 reactiveSetter @ vue.runtime.esm.js:1025 proxySetter @ vue.runtime.esm.js:5425 Message @ element-ui.common.js:3379 main_Message.<computed> @ element-ui.common.js:3379 eval @ index.js??clonedRuleSet-40.use[0]!./node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./src/views/Login.vue?vue&type=script&lang=js:49 Promise.then eval @ index.js??clonedRuleSet-40.use[0]!./node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./src/views/Login.vue?vue&type=script&lang=js:48 eval @ element-ui.common.js:2686 eval @ element-ui.common.js:2740 complete @ index.js:95 eval @ index.js:231 next @ util.js:161 next @ util.js:129 cb @ index.js:179 required @ required.js:11 eval @ index.js:222 next @ util.js:127 asyncSerialArray @ util.js:133 eval @ util.js:167 asyncMap @ util.js:164 validate @ index.js:147 validate @ element-ui.common.js:2740 eval @ element-ui.common.js:2686 validate @ element-ui.common.js:2686 handleLogin @ index.js??clonedRuleSet-40.use[0]!./node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./src/views/Login.vue?vue&type=script&lang=js:45 invokeWithErrorHandling @ vue.runtime.esm.js:3083 invoker @ vue.runtime.esm.js:1884 invokeWithErrorHandling @ vue.runtime.esm.js:3083 Vue.$emit @ vue.runtime.esm.js:3782 handleClick @ element-ui.common.js:1118 invokeWithErrorHandling @ vue.runtime.esm.js:3083 invoker @ vue.runtime.esm.js:1884 original_1._wrapper @ vue.runtime.esm.js:7547 axios.js:120 ==================================== eval @ axios.js:120 Promise.then request @ Axios.js:88 Axios.<computed> @ Axios.js:129 wrap @ bind.js:9 eval @ font.js:45 fetchUserFonts @ font.js:43 wrappedActionHandler @ vuex.esm.js:862 dispatch @ vuex.esm.js:527 boundDispatch @ vuex.esm.js:417 created @ index.js??clonedRuleSet-40.use[0]!./node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./src/views/CarvingForm.vue?vue&type=script&lang=js:49 invokeWithErrorHandling @ vue.runtime.esm.js:3083 callHook$1 @ vue.runtime.esm.js:4098 Vue._init @ vue.runtime.esm.js:5763 VueComponent @ vue.runtime.esm.js:5896 createComponentInstanceForVnode @ vue.runtime.esm.js:4613 init @ vue.runtime.esm.js:4475 merged @ vue.runtime.esm.js:4630 createComponent @ vue.runtime.esm.js:6638 createElm @ vue.runtime.esm.js:6592 updateChildren @ vue.runtime.esm.js:6887 patchVnode @ vue.runtime.esm.js:6980 patch @ vue.runtime.esm.js:7148 Vue._update @ vue.runtime.esm.js:3835 updateComponent @ vue.runtime.esm.js:3941 Watcher.get @ vue.runtime.esm.js:3512 Watcher.run @ vue.runtime.esm.js:3588 flushSchedulerQueue @ vue.runtime.esm.js:4191 eval @ vue.runtime.esm.js:3209 flushCallbacks @ vue.runtime.esm.js:3131 Promise.then timerFunc @ vue.runtime.esm.js:3156 nextTick @ vue.runtime.esm.js:3221 queueWatcher @ vue.runtime.esm.js:4277 Watcher.update @ vue.runtime.esm.js:3579 Dep.notify @ vue.runtime.esm.js:792 reactiveSetter @ vue.runtime.esm.js:1025 proxySetter @ vue.runtime.esm.js:5425 Message @ element-ui.common.js:3379 main_Message.<computed> @ element-ui.common.js:3379 eval @ index.js??clonedRuleSet-40.use[0]!./node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./src/views/Login.vue?vue&type=script&lang=js:49 Promise.then eval @ index.js??clonedRuleSet-40.use[0]!./node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./src/views/Login.vue?vue&type=script&lang=js:48 eval @ element-ui.common.js:2686 eval @ element-ui.common.js:2740 complete @ index.js:95 eval @ index.js:231 next @ util.js:161 next @ util.js:129 cb @ index.js:179 required @ required.js:11 eval @ index.js:222 next @ util.js:127 asyncSerialArray @ util.js:133 eval @ util.js:167 asyncMap @ util.js:164 validate @ index.js:147 validate @ element-ui.common.js:2740 eval @ element-ui.common.js:2686 validate @ element-ui.common.js:2686 handleLogin @ index.js??clonedRuleSet-40.use[0]!./node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./src/views/Login.vue?vue&type=script&lang=js:45 invokeWithErrorHandling @ vue.runtime.esm.js:3083 invoker @ vue.runtime.esm.js:1884 invokeWithErrorHandling @ vue.runtime.esm.js:3083 Vue.$emit @ vue.runtime.esm.js:3782 handleClick @ element-ui.common.js:1118 invokeWithErrorHandling @ vue.runtime.esm.js:3083 invoker @ vue.runtime.esm.js:1884 original_1._wrapper @ vue.runtime.esm.js:7547 font.js:58 字体列表请求失败: 请求失败 (422) 状态码: 422 下面是对应的代码模块 axios.js代码 import axios from 'axios' import { Message, MessageBox } from 'element-ui' import store from '../store' const service = axios.create({ baseURL: process.env.VUE_APP_API_URL, timeout: 60000 }) service.interceptors.request.use( config => { // 1. 打印拦截到的请求基本信息 console.log('===== 请求拦截器捕获请求 ====='); console.log('请求地址:', config.baseURL + config.url); // 完整 URL console.log('请求方法:', config.method.toUpperCase()); // 请求方法(GET/POST等) console.log('超时时间:', config.timeout + 'ms'); // 2. 打印请求参数(区分 params 和 data) if (config.method === 'get') { console.log('请求参数 (params):', config.params || '无参数'); } else { console.log('请求体 (data):', config.data || '无数据'); } // 3. 打印原始请求头(处理前) console.log('原始请求头 (处理前):', { ...config.headers }); // 4. Token 处理逻辑(原有逻辑保留) const publicUrls = ['/auth/register', '/auth/login']; if (store.getters.isAuthenticated && !publicUrls.includes(config.url)) { const token = store.state.user.token; console.log('request', token) if (!token) { console.error('⚠️ Token 为空,无法传递'); } else { config.headers['Authorization'] = `Bearer ${token}`; console.log('✅ Token 处理结果:'); console.log('传递的 Token:', token); console.log('更新后的 Authorization 头:', config.headers['Authorization']); } } else { if (publicUrls.includes(config.url)) { console.log('ℹ️ 公开接口,无需传递 Token:', config.url); } else { console.log('ℹ️ 未满足 Token 传递条件(用户未登录)'); } } // 5. 打印处理后的完整请求配置(可选,用于调试) console.log('===== 请求拦截器处理完成 =====', { url: config.baseURL + config.url, method: config.method, finalHeaders: config.headers, finalParams: config.params, finalData: config.data }); return config; }, error => { // 6. 打印请求拦截器中的错误(如配置错误) console.error('===== 请求拦截器捕获错误 ====='); console.error('错误类型:', error.name); console.error('错误信息:', error.message); console.error('错误详情:', error); return Promise.reject(error); } ); service.interceptors.response.use( response => { // 打印成功响应信息 console.log('===== 响应拦截器捕获成功响应 ====='); console.log('请求地址:', response.config.url); console.log('响应状态码:', response.status); console.log('响应状态文本:', response.statusText); console.log('响应头:', response.headers); console.log('响应数据:', response.data); console.log('===================================='); return response.data; }, error => { console.error('===== 响应拦截器捕获错误 ====='); console.error('请求地址:', error.config?.url || '未知地址'); console.error('原始错误信息:', error); // 强制初始化错误对象,确保 status 存在 let errorData = { error: '未知错误', status: error.response?.status ?? null // 关键:用 null 替代 undefined }; if (error.response) { // 后端有响应时,提取错误信息和状态码 console.error('响应状态码:', error.response.status); console.error('响应数据:', error.response.data); console.error('响应头:', error.response.headers); errorData.error = error.response.data?.error || `请求失败 (${error.response.status})`; errorData.status = error.response.status; // 确保状态码为数字 } else if (error.request) { // 无响应(如网络错误) console.error('错误类型: 无响应(可能是网络错误)'); console.error('请求信息:', error.request); errorData.error = '网络错误,未收到服务器响应'; errorData.status = 0; // 用 0 标识网络错误 } else { // 请求配置错误 console.error('错误类型: 请求配置错误'); errorData.error = error.message || '请求配置错误'; errorData.status = -1; // 用 -1 标识客户端错误 } console.error('处理后的错误信息:', errorData); console.error('===================================='); Message.error(errorData.error); return Promise.reject(errorData); // 确保返回的 error 一定有 status } ); // 辅助函数:根据状态码返回描述文本 function getStatusText(status) { const statusMap = { 400: '参数错误', 403: '权限不足', 404: '接口不存在', 500: '服务器内部错误', 502: '网关错误' } return statusMap[status] || '未知错误' } export default service fonts.js代码 import axios from '../../utils/axios' const state = { fonts: [] } const getters = { userFonts: state => state.fonts } const mutations = { SET_FONTS(state, fonts) { state.fonts = fonts } } const actions = { // 上传字体文件 uploadFont({ dispatch }, file) { const formData = new FormData() formData.append('font_file', file) return new Promise((resolve, reject) => { // 不手动添加Authorization头,依赖axios拦截器自动处理 axios.post('/font/upload', formData, { headers: { 'Content-Type': 'multipart/form-data', 'subject': 'font_upload' } }) .then(data => { dispatch('fetchUserFonts') resolve(data) }) .catch(error => { reject(error) }) }) }, // 获取用户上传的字体 fetchUserFonts({ commit }) { return new Promise((resolve, reject) => { // 参考user.js的写法,不手动传递token,由拦截器统一处理 axios.get('/font/list', { headers: { 'subject': 'font_list' } }) .then(data => { // 直接使用响应数据(axios响应拦截器已处理为response.data) commit('SET_FONTS', data) resolve(data) }) .catch(error => { // 错误处理与user.js保持一致 const errorMsg = error.error || error.message || '获取字体列表失败' console.error('字体列表请求失败:', errorMsg, '状态码:', error.status) reject({ error: errorMsg, status: error.status }) }) }) }, // 删除字体文件 deleteFont({ dispatch }, fontId) { return new Promise((resolve, reject) => { axios.delete(`/font/${fontId}`, { headers: { 'subject': 'font_delete' } }) .then(data => { dispatch('fetchUserFonts') resolve(data) }) .catch(error => { reject(error) }) }) } } export default { state, getters, mutations, actions } 后端接口代码 @font_bp.route('/list', methods=['GET']) @jwt_required() def list_fonts(): """获取用户上传的所有字体""" user_id = get_jwt_identity() fonts = Font.query.filter_by(user_id=user_id).order_by(Font.uploaded_at.desc()).all() print(1) print(fonts) return jsonify([font.to_dict() for font in fonts]), 200
最新发布
08-06
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值