子视图模型函数触发父视图模型函数的原因及绑定函数异常触发疑问
其实这是KnockoutJS依赖追踪系统的特性,你之前误以为只有computed会在依赖变化时重新执行,是因为没搞清楚它对普通函数的作用逻辑,我给你拆解下:
初始化时的必然执行
不管是普通函数还是computed,当Knockout把绑定应用到DOM元素(比如你说的父div)时,都会立刻执行一次函数,目的是获取初始值来渲染DOM——这就是文档加载时func触发的原因。依赖变化时的自动重执行
重点来了:如果你的func在执行过程中访问了任何Knockout的可观察对象(observable/observableArray),Knockout的依赖追踪器会悄悄把这个函数加入到该可观察对象的订阅列表里。之后只要这个可观察对象的值发生变化(比如子视图模型的按钮点击操作修改了父VM里的某个observable),Knockout就会自动重新调用func,用来更新绑定的DOM内容。
举个实际的例子你就懂了:
假设父视图模型是这样的:
function ParentVM() { this.counter = ko.observable(0); // 父VM的可观察对象 this.func = function() { console.log("父func触发了"); return `当前计数:${this.counter()}`; // 这里访问了counter这个observable }; } function ChildVM(parent) { this.parentVM = parent; this.onButtonClick = function() { // 点击按钮修改父VM的counter值 this.parentVM.counter(this.parentVM.counter() + 1); }; }
对应的HTML:
<div data-bind="text: func()"> <button data-bind="click: onButtonClick">点我加计数</button> </div>
这里每次点击按钮,父VM的counter会变化,而func因为依赖了counter,就会被重新执行,控制台就会打印"父func触发了",同时div的文本也会更新。
反过来,如果你的func完全不依赖任何可观察对象,比如只是返回固定文本:
this.func = function() { console.log("父func触发了"); return "我是固定内容"; };
那除了页面加载时的初始化执行,点击子按钮时func绝对不会被触发——因为没有可观察对象的变化能触发它的重执行。
所以核心逻辑就是:只要绑定中的普通函数访问了可观察对象,Knockout就会为它建立依赖追踪,当依赖的可观察对象变化时,函数就会被重新调用,这和computed的依赖追踪逻辑是同源的,只是computed会把结果缓存起来,而普通绑定函数每次都是直接执行来更新DOM。
内容的提问来源于stack exchange,提问作者OfirD

