AngularJS中$watch触发时机及指定函数禁用问题求助
一、为什么$watch获取到的newVal是旧值?
先帮你理清楚AngularJS中$watch的核心逻辑:Angular的digest循环会负责检查所有watch监听的表达式,只有当表达式的值发生可识别的变化时,才会触发对应的回调函数。你遇到newVal仍是旧值的情况,大概率是下面两种原因之一:
1. 监听数组/对象时使用了默认的浅监听
默认情况下,$watch是浅监听——它只会检查变量的引用是否改变,不会递归检查数组元素或对象属性的内部变化。比如你只是给vmHome.widgets数组push新元素、修改数组内对象的属性,这时候数组本身的引用没变化,浅监听就识别不到变化,回调里的newVal和oldVal其实是同一个数组的引用,看起来就像拿到了旧值。
解决方法:给$watch加上第三个参数true,开启深度监听,让Angular递归检查数组/对象的内部变化:
$scope.$watch('vmHome.widgets', function(newVal, oldVal) { if (!!newVal && angular.isArray(newVal) && newVal.length > 0) { vmHome.widgetGridConfig.data.rows = []; normalizeWidgetData(newVal); } else { vmHome.widgetGridConfig.data.rows = []; } // 其他逻辑... }, true); // 开启深度监听
2. 在Angular的digest循环之外修改了变量
如果你用了原生setTimeout、第三方库回调或直接在原生DOM事件里修改vmHome.widgets,Angular不会自动触发digest循环,这时候$watch就察觉不到变化,回调里拿到的自然还是旧值。
解决方法:用Angular提供的$timeout代替setTimeout,或者在修改变量后手动调用$scope.$apply()(注意:如果已经在digest循环中调用会报错,可先判断$scope.$$phase是否存在,或者使用$scope.$safeApply这类封装方法)。
二、如何针对特定函数禁用$watch?
Angular的$watch方法会返回一个注销函数,你只需要把这个函数存起来,在需要禁用的地方调用它就行。举个具体例子:
// 注册watch时,保存返回的注销函数 var unwatchWidgets = $scope.$watch('vmHome.widgets', function(newVal) { // 你的原有逻辑... }, true); // 假设你有一个特定函数,需要在执行它时禁用watch function disableWatchWhenDoingSomething() { // 调用注销函数,这个watch就不会再触发了 unwatchWidgets(); }
需要注意:一旦调用注销函数,这个watch就彻底失效了;如果之后需要重新监听,得再次调用$scope.$watch注册。
如果是需要临时禁用(比如某个函数执行期间暂停watch,执行完再恢复),可以这样处理:
function tempDisableWatch() { // 先注销watch unwatchWidgets(); // 执行你的业务逻辑... vmHome.widgets = [...vmHome.widgets, newWidget]; // 重新注册watch,并更新注销函数的引用 unwatchWidgets = $scope.$watch('vmHome.widgets', function(newVal) { // 原有逻辑... }, true); }
内容的提问来源于stack exchange,提问作者Pooja Thapa

