You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何移除bindValue方法中的with语句?

如何移除bindValue方法中的with语句?

先聊聊你的场景和现有问题

你现在做的是Web组件模板的绑定逻辑,把类似这样的模板表达式:

<div>{{name == 'Homer' ? 'No Homers Allowed' : name}}</div>

转换成能在组件属性作用域内执行的函数,核心的bindValue方法里用了with(scope)来让表达式直接访问作用域属性,但with已经被废弃了,而且你还得保证只有模板里实际用到的属性的getter才会触发——要是直接把作用域的属性当变量传给Function构造函数,整个scope的getter都会跑一遍,这就破坏了你做的依赖追踪逻辑。

先看看你现在的核心代码:

/**
* @el [Element] Target HTML Element 
* @attr [String] attribute to bind
* @match [Object] Result from regex.exec
*/ 
const bindValue = (el, attr, match) => {
  let binding = getBinding(el, attr);

  if (attr === "textContent") {
    let fn = new Function(
      `scope`,
      `with(scope) {
        return ${match[1]}
      }`,
    );

    binding.attrs[attr].bindings.push((ctx, values) => {
      return ctx.replace(match[0], fn(values));
    });
  }
};

咱们可以这么改:把表达式里的变量都换成scope.xxx

核心思路是:不用with来注入作用域,而是把模板表达式里的所有用户自定义标识符(比如name)都替换成scope.name,这样既不需要with,又只有实际用到的属性会被访问,对应的getter才会触发。

具体实现步骤

  1. 用正则匹配表达式里的JS标识符,同时排除JS自带的关键字(比如if、else这些不能当成属性)
  2. 把表达式里的每个有效标识符替换成scope.xxx
  3. 用这个处理后的表达式创建Function,完全抛弃with

修改后的bindValue代码如下:

const bindValue = (el, attr, match) => {
  let binding = getBinding(el, attr);

  if (attr === "textContent") {
    const expr = match[1];
    // 匹配JS标识符,同时排除关键字
    const identifierRegex = /\b([a-zA-Z_$][a-zA-Z0-9_$]*)\b/g;
    const jsKeywords = new Set([
      'if', 'else', 'return', 'true', 'false', 'null', 'undefined',
      'typeof', 'instanceof', 'this', 'function', 'class', 'let',
      'const', 'var', 'for', 'while', 'do', 'switch', 'case', 'break',
      'continue', 'try', 'catch', 'finally', 'throw', 'async', 'await'
    ]);

    // 把表达式里的所有自定义标识符替换成 scope.xxx
    const scopedExpr = expr.replace(identifierRegex, (matchedId) => {
      return jsKeywords.has(matchedId) ? matchedId : `scope.${matchedId}`;
    });

    // 创建不需要with的函数
    const fn = new Function(
      'scope',
      `return ${scopedExpr};`
    );

    binding.attrs[attr].bindings.push((ctx, values) => {
      return ctx.replace(match[0], fn(values));
    });
  }
};

为什么这个方案能满足你的需求?

  • 没有with了:完全符合规范,不会再有废弃语法的警告
  • 仅触发用到的属性getter:比如表达式里只有name,那只会执行scope.name,只有这个属性的getter会跑,其他属性完全不受影响,完美适配你的依赖追踪逻辑
  • 功能完全兼容:处理后的表达式和原来用with的逻辑完全一致,模板里的三元运算、比较逻辑都能正常执行

举个实际例子

原来的表达式是name == 'Homer' ? 'No Homers Allowed' : name,处理后会变成:
scope.name == 'Homer' ? 'No Homers Allowed' : scope.name
生成的Function就是:

new Function('scope', `return scope.name == 'Homer' ? 'No Homers Allowed' : scope.name;`)

执行的时候只会访问scope.name,完全符合你的预期。

进阶:复杂表达式用AST解析(可选)

如果你的模板表达式支持复杂语法(比如对象链user.name、方法调用getGreeting()),上面的正则可能会有遗漏,这时候可以用AST解析器(比如acorn)来精准遍历表达式的语法树,替换所有用户自定义的标识符。举个简化示例:

import * as acorn from 'acorn';
import { generate } from 'escodegen';

const getScopedExpr = (expr) => {
  // 解析表达式成AST
  const ast = acorn.parse(expr, { ecmaVersion: 'latest' });
  // 遍历AST替换标识符
  const traverseNode = (node) => {
    if (node.type === 'Identifier') {
      const jsKeywords = new Set([/* 关键字集合同上 */]);
      if (!jsKeywords.has(node.name)) {
        node.name = `scope.${node.name}`;
      }
    }
    // 递归处理所有子节点
    Object.values(node).forEach(child => {
      if (typeof child === 'object' && child !== null) {
        traverseNode(child);
      }
    });
  };
  traverseNode(ast.body[0].expression);
  // 把AST转回代码
  return generate(ast).slice(0, -1); // 去掉自动添加的分号
};

然后在bindValue里用const scopedExpr = getScopedExpr(expr);替换正则处理的部分就行,复杂场景下更可靠。


备注:内容来源于stack exchange,提问作者hobberwickey

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.15 03:43:04