Skip to content

Instantly share code, notes, and snippets.

@justinbmeyer
Last active November 18, 2015 02:47
Show Gist options
  • Save justinbmeyer/18e80ed9f3893c03a573 to your computer and use it in GitHub Desktop.
Save justinbmeyer/18e80ed9f3893c03a573 to your computer and use it in GitHub Desktop.
Proxies solve core framework problems.

In short, observables and computed values are something that every framework struggles with. Frameworks that have explicit observables and computed values (Ember, Knockout, CanJS, etc) can provide high performance updates and, imo, a more natural development experience. However, frameworks like Angular and React, that can use plain JS objects, lend themselves to being much more popular, but accomplish "computes" with either unnecessary dirty checking or diffing. Proxies will be able to provide a best of all worlds solutions.

Frameworks with explict observables

Ember, Knockout, CanJS, and many other frameworks have explicit observables and computed values or properties. For example, in CanJS (which I'm most familiar), the following creates a fullName compute that updates whenever any of its source observables change:

var person = new can.Map({first: "Justin", last: "Meyer"});

var fullName = can.compute(function(){
  return person.attr("first")+" "+person.attr("last")
});

fullName.bind("change", function(ev, newVal, oldVal){
  // newVal => "Vyacheslav Egorov"
  // oldVal => "Justin Meyer"
})

// causes change event above
person.attr({first: "Vyacheslav", last: "Egorov"});

This works by having .attr(prop) tell the computed system that some observable value is being read. The computed system listens for those changes.

Knockout is similar:

function AppViewModel() {
    this.firstName = ko.observable('Bob');
    this.lastName = ko.observable('Smith');
    this.fullName = ko.computed(function() {
        return this.firstName() + " " + this.lastName();
    }, this);
}

The developer doesn't have to explicitly tell the compute what is being read. This information is infered as the function is run. This allows a great deal of flexibility. For instance, plain JavaScript functions can be composed:

var person = new can.Map({first: "Justin", last: "Meyer"});
var hobbiesList = new can.List(["basketball","programming"]);

var fullName = function(){
  return person.attr("first")+" "+person.attr("last")
};

var hobbies = function(){
  return hobbiesList.join(",")
}

var info = can.compute(function(){
  return fullName()+" likes "+hobbies();
});

Direct observables and computes allow frameworks to only update exactly what is needed without having to dirty check or signal an update and diff.

Frameworks without explicit observables

Frameworks without explicit observables, like Angular and React are far more popular. Users like Object's API. But it comes at a cost.

Angular

In Angular, you can do something like:

$scope.fullName = function() {
  return $scope.firstName + " " + $scope.lastName;
}

This will use dirty checking to continuously read the value of fullName. You could do something like:

var fullName = function() {
  $scope.fullName = $scope.firstName + " " + $scope.lastName;
}
$scope.$watch('firstName', fullName, true);
$scope.$watch('lastName', fullName, true);

To use Object.observe behind the scenes, but you have to write out twice what values you care about:

  1. To compose the value,
  2. To list your observable properties.

React

With react/jsx, (which I'm least familiar with), you might do something like:


render () {
  var fullName = this.props.firstName + " " + this.props.lastName;
  return (
      <div>
        <h2>{fullName}</h2>
      </div>
    );
}

React, instead of dirty checking, requires the user to re-render when state changes, and only updates what it needs to by diffing. Diffing is O(n) at best. There's no diff necessary with CanJS / Knockout.

Enter proxies

Proxies would allow frameworks to provide still-explicit observables with an Object-like API that uses the DOT (.) operator. For example:

var person = can.observable();
person.first = "Justin";
person.last = "Meyer";

var fullName = can.compute(function(){
  return person.first+" "+person.last
});

// causes change event above
person.first = "Vyacheslav";
person.last = "Egorov";

I showed it this way because users do not want to define all properties up front, making Object.defineProperty and getter/setters an unviable option.

With Proxies, Angular could remove the need for dirty checking (assuming $scope is a proxy) and React's render function could know when to call itself.

With Proxies, Knockout, CanJS, and Ember users could use the familiar DOT(.) operator.

I think V8 adding proxies would have as big of an impact as any other ES6 feature to an average developer. They wouldn't use it directly, but it would be an invaluable tool to library and framework developers.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment