For AI agents: the complete documentation index is available at /tc39-atlas/en/llms.txt, the full documentation bundle is available at /tc39-atlas/en/llms-full.txt, and this page is available as Markdown at /tc39-atlas/en/proposals/year/pending/proposal-extensions.md.
  • English
  • Extensions S1

    Proposal details
    Proposal overview

    This proposal introduces a :: operator for defining and using extension methods, getters, and setters on objects without polluting the global namespace. It aims to improve code readability by allowing ad-hoc extensions and reuse of built-in prototypes, with a syntax like obj::method() and obj::ext:name.`. The proposal includes experimental implementations and design documents.

    Note

    The README below comes from the upstream repository and may contain outdated stage or status metadata. Use the proposal details above as the current source of truth.

    Extensions and :: operator

    Proposal status

    This is an ECMAScript (JavaScript) proposal in stage 1.

    Note: The proposal could be seen as the reshape of the "virtual method" part of old bind operator proposal, see https://github.com/tc39/proposal-bind-operator/issues/56.

    Simple examples

    Example of ad-hoc extension methods and accessors

    // define two extension methods
    const ::toArray = function () { return [...this] }
    const ::toSet = function () { return new Set(this) }
    
    // define a extension accessor
    const ::allDivs = {
    	get() { return this.querySelectorAll('div') }
    }
    
    // reuse built-in prototype methods and accessors
    const ::flatMap = Array.prototype.flatMap
    const ::size = Object.getOwnPropertyDescriptor(Set.prototype, 'size')
    
    // Use extension methods and accesors to calculate
    // the count of all classes of div element.
    let classCount = document::allDivs
    	::flatMap(e => e.classList::toArray())
    	::toSet()::size

    roughly equals to:

    // define two extension methods
    const $toArray = function () { return [...this] }
    const $toSet = function () { return new Set(this) }
    
    // define a extension accessor
    const $allDivs = {
    	get() { return this.querySelectorAll('div') }
    }
    
    // reuse built-in prototype method and accessor
    const $flatMap = Array.prototype.flatMap
    const $size = Object.getOwnPropertyDescriptor(Set.prototype, 'size')
    
    // Use extension methods and accesors to calculate
    // the count of all classes of div element.
    let $
    $ = $allDivs.get.call(document)
    $ = $flatMap.call($, e => $toArray.call(e.classList))
    $ = $toSet.call($)
    $ = $size.get.call($)
    let classCount = $

    Example of using constructors or namespace object as extensions

    // util.js
    export const toArray = iterable => [...iterable]
    export const toSet = iterable => new Set(iterable)
    import * as util from './util.js'
    
    const ::allDivs = {
    	get() { return this.querySelectorAll('div') }
    }
    
    let classCount = document::allDivs
    	::Array:flatMap(
    		e => e.classList::util:toArray())
    	::util:toSet()
    	::Set:size

    roughly equals to:

    import * as util from './util.js'
    
    const $allDivs = {
    	get() { return this.querySelectorAll('div') }
    }
    
    let $
    $ = $allDivs.get.call(document)
    $ = Array.prototype.flatMap.call($,
    	e => util.toArray(e.classList))
    $ = util.toSet($)
    $ = Object.getOwnPropertyDescriptor(Set.prototype, 'size').get.call($)
    let classCount = $

    Changes of the old bind operator proposal

    • keep obj::foo() syntax for extension methods
    • repurpose obj::foo as extension getters and add obj::foo = as extension setters
    • separate namespace for ad-hoc extension methods and accessors, do not pollute normal binding names
    • add obj::ext:name syntax
    • change operator precedence to same as .
    • remove ::obj.foo (use cases can be solved by custom extension + library, or other proposals)

    Other matrials