javascriptknockout.jsknockout-2.0knockout-3.0

Get definition of Knockout.js computed observable evaluator function


I would like to programmatically access the string representation of an evaluator function definition of a KO computed field. For example, in the case of:

self.field = ko.computed(function () {
    return 'HELLO WORLD';
});

I would like to retrieve the string:

"function () {
    return 'HELLO WORLD';
}"

I've tried several things with Object.keys() and JSON.stringify() but I can't seem to get to the function definition.


Solution

  • Bad news

    The source code for ko.computed seems to try its best to hide this from consumers.

    if (DEBUG) {
      // #1731 - Aid debugging by exposing the computed's options
      computedObservable["_options"] = options;
    }
    

    Source

    var computedState = ko.utils.createSymbolOrString('_state');
    
    // ...
    
    computedObservable[computedState] = state;
    

    Source

    Possible hacky solutions:

    If you're willing to either:

    1. Reference a debug version of knockout, or
    2. Wrap ko.computed in a custom constructor between ko's initialization & the first external calls to it, or
    3. Nuke Symbol from global this / use a browser that does not support symbols, (see here)

    you can still make things work.

    My preferred option here would be option 2. See the interactive snippets below for some proofs of concept of each approach.

    Using a DEBUG version of KO:

    const msg = ko.observable("Hello world");
    
    const computedWithDependencies = ko.computed(function() {
      return msg();
    });
    
    const computedWithoutDependencies = ko.computed(function() {
      return "Hello world";
    });
    
    console.log(computedWithDependencies._state.readFunction);
    console.log(computedWithDependencies._options.read.toString());
    
    // This one won't work because ko auto disposes the computed
    console.log(computedWithoutDependencies._state.readFunction);
    console.log(computedWithoutDependencies._options.read.toString());
    <script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.5.1/knockout-latest.debug.js"></script>

    Replacing ko.computed

    const ogComputed = ko.computed;
    
    ko.computed = function(f) {
      // TODO: support other argument signatures like `options` and `context`
      
      const c = ogComputed(f);
      c._exposed = f;
      
      return c;
    }
    
    const myComputed = ko.computed(function() {
      return "Hello world";
    });
    
    console.log(myComputed._exposed.toString());
    <script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.5.1/knockout-latest.min.js"></script>

    Nuking Symbol (not recommended)

    const msg = ko.observable("Hello world");
    const c = ko.computed(function() {
      return msg();
    });
    
    // The read function should be the only function in state...
    console.log(Object.values(c._state).find(f => typeof f === "function").toString());
    <script>
      window._Symbol = Symbol;
      window.Symbol = undefined;
    </script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.5.1/knockout-latest.min.js"></script>
    <script>
      window.Symbol = window._Symbol;
      delete window._Symbol;
    </script>