For AI agents: the complete documentation index is available at /tc39-atlas/llms.txt, the full documentation bundle is available at /tc39-atlas/llms-full.txt, and this page is available as Markdown at /tc39-atlas/proposals/stage/0/proposal-bind-operator.md.
  • 简体中文
  • Function bind syntax S0

    中文标题:函数绑定语法

    提案概览
    提案速览

    本提案引入了一个新的 :: 运算符,用于 this 绑定和方法提取,为常见模式(如使用特定 this 调用函数或提取方法)提供语法糖。该运算符具有二元和一元前缀形式,并且在严格模式下绑定函数立即调用时可以进行优化。提案包括运行时语义、早期错误以及未来扩展(绑定构造函数)。目前它仍处于稻草人阶段,已有 Babel 和 esdown 中的早期原型实现。

    Note

    以下 README 来自上游仓库,其中的阶段或状态标注可能滞后;当前信息以提案概览为准。

    ECMAScript 这个绑定语法

    本提案引入了一个新的运算符 ::,用于执行 this 绑定和方法提取。

    这是绑定运算符稻草人提案的更详细描述。

    示例

    使用一个以“虚拟方法”模块实现的迭代器库:

    import { map, takeWhile, forEach } from "iterlib";
    
    getPlayers()
    ::map(x => x.character())
    ::takeWhile(x => x.strength > 100)
    ::forEach(x => console.log(x));

    使用一个类似于 jQuery 的虚拟方法库:

    // 只为我们需要的方法创建绑定
    let { find, html } = jake;
    
    // 查找所有 class="myClass" 的 div,然后获取所有 "p" 元素并
    // 替换其内容。
    document.querySelectorAll("div.myClass")::find("p")::html("hahaha");

    使用方法提取将 promise 的最终值打印到控制台:

    Promise.resolve(123).then(::console.log);

    使用方法提取在 DOM 事件发生时调用对象方法:

    $(".some-link").on("click", ::view.reset);

    动机与概述

    随着 ECMAScript 6 中箭头函数的引入,显式地将闭包绑定到词法 this 值的需求已大幅减少,从而显著提高了语言可用性。然而,仍有两个用例中,显式的 this 绑定或注入既常见又笨拙。

    使用提供的 this 参数调用已知函数:

    let hasOwnProp = Object.prototype.hasOwnProperty;
    let obj = { x: 100 };
    hasOwnProp.call(obj, "x");

    从对象中提取方法:

    Promise.resolve(123).then(console.log.bind(console));

    本提案引入了一个新的运算符 ::,可以作为这些用例的语法糖。

    在其二元形式中,:: 运算符创建一个绑定函数,使得运算符左侧的值绑定为右侧目标函数的 this 变量。

    在其一元前缀形式中,:: 运算符创建一个绑定函数,使得所提供的引用的基值绑定为目标函数的 this 变量。

    如果绑定函数立即被调用,并且目标函数是严格模式,那么绑定函数本身是不可观察的,绑定运算符可以被优化为高效且直接的函数调用。

    注意:如果目标函数不是严格的,那么它可能会使用 function.calleearguments.callee,这将导致行为的可观察差异。

    通过为这些用例提供语法糖,我们将启用一类新的“虚拟方法”库,这将比当今使用的标准适配器模式具有可用性优势。

    原型实现

    语法

    LeftHandSideExpression[Yield] :
        NewExpression[?Yield]
        CallExpression[?Yield]
        BindExpression[?Yield]
    
    BindExpression[Yield] :
        LeftHandSideExpression[?Yield] :: [lookahead ≠ new] MemberExpression[?Yield]
        :: MemberExpression[?Yield]
    
    CallExpression[Yield] :
        MemberExpression[?Yield] Arguments[?Yield]
        super Arguments[?Yield]
        CallExpression[?Yield] Arguments[?Yield]
        CallExpression[?Yield] [ Expression[In, ?Yield] ]
        CallExpression[?Yield] . IdentifierName
        CallExpression[?Yield] TemplateLiteral[?Yield]
        BindExpression[?Yield] Arguments[?Yield]

    早期错误

    BindExpression :
        :: MemberExpression
    • 如果派生的 MemberExpression 不是 MemberExpression : MemberExpression . IdentifierMemberExpression : MemberExpression [ Expression ]SuperProperty,则这是一个语法错误。

    • 如果派生的 NewExpressionPrimaryExpression : CoverParenthesizedExpressionAndArrowParameterList,并且 CoverParenthesizedExpressionAndArrowParameterList 最终派生的短语,如果用在 NewExpression 的位置,根据这些规则会产生语法错误,则这是一个语法错误。此规则递归应用。

    注意:最后一条规则意味着,如下表达式:

    ::(((foo)))

    由于第一条规则的递归应用会产生早期错误。

    抽象操作:InitializeBoundFunctionProperties ( F, target )

    使用参数 Ftarget 的抽象操作 InitializeBoundFunctionProperties 用于设置绑定函数 F 的 "length" 和 "name" 属性。它执行以下步骤:

    • targetHasLength 为 HasOwnProperty(target, "length")。
    • 如果 targetHasLengthtrue,则
      • targetLen 为 ? Get(target, "length")。
      • 如果 Type(targetLen) 不是 Number,则令 L0
      • 否则,令 L 为 ToInteger(targetLen)。
    • 否则令 L0
    • status 为 ? DefinePropertyOrThrow(F, "length", PropertyDescriptor {[[Value]]: L, [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: true})。
    • targetName 为 ? Get(target, "name")。
    • 如果 Type(targetName) 不是 String,则令 targetName 为空字符串。
    • status 为 ? SetFunctionName(F, targetName, "bound")。
    • 返回 F

    运行时语义

    BindExpression :
        LeftHandSideExpression :: [lookahead ≠ new] MemberExpression
    • baseReference 为求值 LeftHandSideExpression 的结果。
    • baseValue 为 GetValue(baseReference)。
    • targetReference 为求值 MemberExpression 的结果。
    • target 为 GetValue(targetReference)。
    • 如果 IsCallable(target) 为 false,抛出 TypeError 异常。
    • F 为 ? BoundFunctionCreate(target, baseValue, «»)。
    • 返回 InitializeBoundFunctionProperties(F, target)。

    BindExpression :
        :: MemberExpression
    • targetReference 为求值 MemberExpression 的结果。
    • 断言:IsPropertyReference(targetReference) 为 true
    • thisValue 为 GetThisValue(targetReference)。
    • target 为 GetValue(targetReference)。
    • 如果 IsCallable(target) 为 false,抛出 TypeError 异常。
    • F 为 ? BoundFunctionCreate(target, thisValue, «»)。
    • 返回 ? InitializeBoundFunctionProperties(F, target)。

    未来扩展:绑定构造函数

    此语法可以通过引入 绑定构造函数 进行扩展。当二元 :: 运算符后跟 new 关键字时,左侧的构造函数被包装在一个可调用函数中。

    class User {
        constructor(name) {
            this.name = name;
        }
    }
    
    let users = ["userA", "userB"].map(User::new);
    
    console.log(users);
    
    /*
    [ (User) { name: "userA" },
      (User) { name: "userB" }]
    */
    运行时语义
    BindExpression:
        LeftHandSideExpression :: new
    • targetReference 为求值 LeftHandSideExpression 的结果。
    • target 为 GetValue(targetReference)。
    • 如果 IsConstructor(target) 为 false,抛出 TypeError 异常。
    • F 为一个新的内置函数,如绑定构造函数包装函数中所定义。
    • F 的 [[BoundTargetConstructor]] 内部槽设置为 target
    • 返回 ? InitializeBoundFunctionProperties(F, target)。
    绑定构造函数包装函数

    绑定构造函数包装函数是一个匿名的内置函数,具有 [[BoundTargetConstructor]] 内部槽。

    当调用一个绑定构造函数包装函数 F 并传递零个或多个 args 时,它执行以下步骤:

    • 断言:F 具有 [[BoundTargetConstructor]] 内部槽。
    • targetF 的 [[BoundTargetConstructor]] 内部槽的值。
    • 断言:IsConstructor(target)。
    • args 为传递给此函数的所有参数组成的列表。
    • 返回 ? Construct(target, args)。