Skip to content

Instantly share code, notes, and snippets.

@edef1c
Last active May 4, 2017 13:55
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save edef1c/5229013 to your computer and use it in GitHub Desktop.
Save edef1c/5229013 to your computer and use it in GitHub Desktop.
Multiple inheritance with ES6 proxies, in both ES6 and CoffeeScript. Literate.
%.coffee: %.coffee.md
sed -n -e '/^ \{4\}/s/^ \{4\}//p' $< > $@
%.js: %.js.md
sed -n -e '/^ \{4\}/s/^ \{4\}//p' $< > $@
%.js: %.coffee
coffee -cb $<
all: multi-inherit.coffee multi-inherit.future.js
clean:
rm -f *.coffee *.js
.PHONY: all clean
module.exports =

Multiple inheritance in CoffeeScript using ES6 proxies

We accept a base prototype to wrap and a set of additional base classes to inherit from.

(obj, Bases...) ->

For each of our base classes, we take their prototype, since that is what we actually want to inherit from. They are preceded by the original base prototype.

  bases = [obj, (Base.prototype for Base in Bases)...]

We instantiate an ES6 proxy, which will be our new, wrapped prototype.

  return proxy = Proxy.create

When a property descriptor is requested, we iterate through our base prototypes to find it.

    getPropertyDescriptor: (name) ->
      for base in bases
        return prop if prop = Object.getPropertyDescriptor base, name

The set of property names of the wrapped prototype is simply the union of the property names of all our base prototypes.

    getPropertyNames: ->
      names = []
      for base in bases 
        names.push name for name in Object.getPropertyNames base when name not in names
      return names

Access to own properties is simply forwarded.

    getOwnPropertyDescriptor: (name) ->
      Object.getOwnPropertyDescriptor obj, name
    getOwnPropertyNames: ->
      Object.getOwnPropertyNames obj
    defineProperty: (name, prop) ->
      Object.defineProperty obj, name, prop
      return proxy
    delete: (name) ->
      delete obj[name]
    fix: ->
      return if not Object.isFrozen obj
      result = {}
      result[name] = Object.getOwnPropertyDescriptor obj, name for name in Object.getOwnPropertyNames obj
      return result

Polyfills

Since some ES6 convenience methods are not available in ES5, we polyfill them.

To find a property descriptor, we simply walk up the prototype tree until we find it.

Object.getPropertyDescriptor ||= (obj, name) ->
  while obj
    return prop if prop = Object.getOwnPropertyDescriptor obj, name
    obj = Object.getPrototypeOf obj

The set of property names of an object is simply the union of all own property names of the object and its prototype chain.

Object.getPropertyNames ||= (obj) ->
  names = []
  while obj
    names.push name for name in Object.getOwnPropertyNames obj, name when name not in names
    obj = Object.getPrototypeOf obj
  return names
module.exports =

Multiple inheritance in ES6

We accept a base prototype to wrap and a set of additional base classes to inherit from.

function(obj, ...Bases) {

For each of our base classes, we take their prototype, since that is what we actually want to inherit from. They are preceded by the original base prototype.

  let bases = [obj, ...[Base.prototype for (Base of Bases)]]

We instantiate an ES6 proxy, which will be our new, wrapped prototype.

  let proxy = Proxy.create(

When a property descriptor is requested, we iterate through our base prototypes to find it.

  { getPropertyDescriptor: function(name) {
      for (let base of bases)
        if (let prop = Object.getPropertyDescriptor(base, name))
          return prop
    }

The set of property names of the wrapped prototype is simply the union of the property names of all our base prototypes.

  , getPropertyNames: function() {
      let names = []
      for (let base of bases) for (let name of Object.getPropertyNames(base))
        if (!~names.indexOf(name)) names.push(name)
      return names
    }

Access to own properties is simply forwarded.

  , getOwnPropertyDescriptor: function(name) {
      return Object.getOwnPropertyDescriptor(obj, name)
    }
  , getOwnPropertyNames: function() {
      return Object.getOwnPropertyNames(obj)
    }
  , defineProperty: function(name, prop) {
      Object.defineProperty(obj, name, prop)
      return proxy
    }
  , 'delete': function(name) {
      return delete obj[name]
    }
  , fix: function() {
      if (!Object.isFrozen(obj)) return
      let result = {}
      for (let name of Object.getOwnPropertyNames(obj))
        result[name] = Object.getOwnPropertyDescriptor(obj, name))
      return result
    }
  })

Finally, we return our newly created proxy.

  return proxy
}

Polyfills

Since some ES6 convenience methods are not available in ES5, we polyfill them.

To find a property descriptor, we simply walk up the prototype tree until we find it.

if (!Object.getPropertyDescriptor) Object.getPropertyDescriptor = function(obj, name) {
  do {
    if (let prop = Object.getOwnPropertyDescriptor(obj, name))
      return prop
  } while (obj = Object.getPrototypeOf(obj))
}

The set of property names of an object is simply the union of all own property names of the object and its prototype chain.

if (!Object.getPropertyNames) Object.getPropertyNames = function(obj) {
  let names = []
  do {
    for (let name of Object.getOwnPropertyNames(obj, name))
      if (!~names.indexOf(name)) names.push(name)
  } while (obj = Object.getPrototypeOf(obj))
  return names
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment