简单了解 with

with 语句详解:使用方法和 this 指向问题

目录

  1. [with 语句基础](#with 语句基础 "#with-%E8%AF%AD%E5%8F%A5%E5%9F%BA%E7%A1%80")
  2. 作用域链查找机制
  3. [this 指向问题(核心)](#this 指向问题(核心) "#this-%E6%8C%87%E5%90%91%E9%97%AE%E9%A2%98%E6%A0%B8%E5%BF%83")
  4. [window 对象的指向](#window 对象的指向 "#window-%E5%AF%B9%E8%B1%A1%E7%9A%84%E6%8C%87%E5%90%91")
  5. 在微前端沙箱中的应用
  6. 实际代码示例
  7. 常见问题和注意事项
  8. 最佳实践

with 语句基础

什么是 with 语句?

with 语句是 JavaScript 的一个特性,它可以将一个对象添加到作用域链的最前端,使得在 with 块内可以直接访问该对象的属性,而不需要使用对象名作为前缀。

基本语法

javascript 复制代码
with (object) {
  // 在这个块内,可以直接访问 object 的属性
  // 不需要写 object.property,直接写 property 即可
}

基本示例

ts 复制代码
const obj = {
  name: "Alice",
  age: 25,
  city: "Beijing",
};

// 使用 with 语句
with (obj) {
  // 可以直接访问 obj 的属性,不需要 obj. 前缀
  console.log(name); // 输出: Alice
  console.log(age); // 输出: 25
  console.log(city); // 输出: Beijing

  // 也可以修改属性
  age = 26;
  console.log(age); // 输出: 26
}

// with 块外,需要正常访问
console.log(obj.age); // 输出: 26

作用域链查找机制

查找顺序

with 块内,当访问一个变量时,JavaScript 引擎会按照以下顺序查找:

  1. 局部变量let/const/var 声明的)
  2. with 指定的对象with(obj) 中的 obj
  3. 外层作用域
  4. 全局作用域

示例:作用域链查找

javascript 复制代码
const globalVar = "全局变量";

function testScope() {
  const localVar = "局部变量";

  const obj = {
    objVar: "对象变量",
    localVar: "对象中的 localVar",
  };

  with (obj) {
    // 1. 查找局部变量 localVar(在函数作用域中)
    // 2. 如果没找到,查找 obj 中的属性
    // 3. 如果还没找到,查找外层作用域
    // 4. 最后查找全局作用域

    console.log(localVar); // 输出: "局部变量"(优先使用函数作用域的)
    console.log(objVar); // 输出: "对象变量"(在 obj 中找到)
    console.log(globalVar); // 输出: "全局变量"(在外层作用域找到)
  }
}

testScope();

this 指向问题(核心)

关键点:this 不受 with 语句影响

这是最重要的知识点! this 的指向完全不受 with 语句的影响。

this 的指向规则

this 的指向遵循 JavaScript 的标准规则:

  • this 指向函数调用时的上下文(caller)
  • 在全局作用域中,this 指向全局对象(浏览器中是 window
  • 在对象方法中,this 指向调用该方法的对象
  • with 语句不会改变 this 的指向

示例 1:this 在 with 块内的行为

javascript 复制代码
const obj = {
  name: "Context Object",
  test: function () {
    console.log("this.name:", this.name);
    return this;
  },
};

const sandboxContext = {
  window: { name: "Sandbox Window" },
  obj: obj,
};

// 使用 Function 构造器创建函数(避免严格模式限制)
const func = new Function(
  "sandboxContext",
  `
  with(sandboxContext) {
    // window 指向 sandboxContext.window
    console.log("window.name:", window.name); // 输出: Sandbox Window
    
    // 但是 this 仍然指向全局对象,不受 with 影响
    // 注意:这里的 window 是 sandboxContext.window(代理对象),不是全局 window
    console.log("this === window:", this === window); // 输出: false(this 是全局 window,window 是代理对象)
    console.log("this === sandboxContext.window:", this === sandboxContext.window); // 输出: false
    
    // 调用 obj.test(),this 指向 obj,不是 sandboxContext
    const result = obj.test(); // 输出: this.name: Context Object
    console.log("obj.test() 返回的 this === obj:", result === obj); // 输出: true
  }
  `
);

func(sandboxContext);

示例 2:对比 window 和 this

javascript 复制代码
const proxyWindow = new Proxy(window, {
  get(target, prop) {
    console.log(`[Proxy] 读取属性: ${String(prop)}`);
    return target[prop];
  },
  set(target, prop, value) {
    console.log(`[Proxy] 设置属性: ${String(prop)} = ${value}`);
    target[prop] = value;
    return true;
  },
});

const sandboxContext = {
  window: proxyWindow,
  document: proxyWindow.document,
  console: proxyWindow.console,
};

const code = `
  // 在 with 块内,window 指向 sandboxContext.window(即 proxyWindow)
  window.myVar = "hello from sandbox";
  console.log("window.myVar:", window.myVar);
  
  // this 仍然指向全局 window(不受 with 影响)
  // 注意:这里的 window 是 sandboxContext.window(代理对象),不是全局 window
  console.log("this === window:", this === window); // 输出: false(this 是全局 window,window 是代理对象)
  console.log("this === sandboxContext.window:", this === sandboxContext.window); // 输出: false
  
  // 但是代码中的 window 指向 proxyWindow
  console.log("代码中的 window === proxyWindow:", window === sandboxContext.window); // 输出: true
`;

const func = new Function(
  "sandboxContext",
  `
  with(sandboxContext) {
    ${code}
  }
  `
);

func(sandboxContext);

为什么 this 不受影响?

this 是 JavaScript 的一个特殊关键字,它的值在函数调用时确定,与作用域链无关。with 语句只影响作用域链的查找,不会影响 this 的绑定。

重要区别:window 和 this 在 with 块内

with(sandboxContext) 块内,需要理解以下关键区别:

  1. window 的指向

    • window 会指向 sandboxContext.window(代理对象)
    • 这是通过作用域链查找实现的
  2. this 的指向

    • this 仍然指向全局对象(不受 with 影响)
    • 这是 JavaScript 的 this 绑定机制决定的
  3. 因此

    javascript 复制代码
    with (sandboxContext) {
      // window 是 sandboxContext.window(代理对象)
      // this 是全局 window
      console.log(this === window); // false(它们不相等!)
    }

关键理解

  • window 标识符通过作用域链查找,在 sandboxContext 中找到,所以指向代理对象
  • this 关键字不受作用域链影响,仍然指向全局对象
  • 这就是为什么在微前端沙箱中,代码里的 window 可以指向代理对象,但 this 仍然指向全局对象

window 对象的指向

关键机制

在微前端沙箱实现中,with 语句的核心作用是替换 window 对象的引用

为什么不能直接用 with(proxyWindow)

javascript 复制代码
// ❌ 错误的方式
const proxyWindow = new Proxy(window, {
  /* ... */
});
with (proxyWindow) {
  window.myVar = "hello"; // window 会去外层作用域查找,找到全局 window
}

问题 :如果直接使用 with(proxyWindow),代码中的 window 标识符会去外层作用域查找,找到全局的 window 对象,而不是 proxyWindow

正确的做法

javascript 复制代码
// ✅ 正确的方式
const proxyWindow = new Proxy(window, {
  /* ... */
});

// 创建一个包含 window 属性的上下文对象
const sandboxContext = {
  window: proxyWindow, // 关键:将 proxyWindow 作为 window 属性
  document: proxyWindow.document,
  console: proxyWindow.console,
};

with (sandboxContext) {
  // 现在 window 会在 sandboxContext 中查找
  // 找到 sandboxContext.window(即 proxyWindow)
  window.myVar = "hello"; // 实际访问的是 proxyWindow.myVar
}

执行流程

ts 复制代码
子应用代码: window.myVar = 'hello'
       ↓
sandboxContext = { window: proxyWindow }
with(sandboxContext) { window.myVar = 'hello' }
       ↓
代码中的 window 在 sandboxContext 中查找
找到 sandboxContext.window(即代理对象 proxyWindow)
       ↓
访问 proxyWindow.myVar,触发 Proxy 的 set 拦截器
       ↓
值被写入 fakeWindow.myVar,而不是真实的 window

在微前端沙箱中的应用

完整的沙箱实现

javascript 复制代码
class WindowProxySandbox {
  private proxy: Window;
  private fakeWindow: Record<string, any> = {};
  private updatedValueSet = new Set<string>();

  constructor() {
    this.fakeWindow = Object.create(null);

    this.proxy = new Proxy(window, {
      get: (_target: Window, prop: string) => {
        // 如果属性在 fakeWindow 中存在,优先返回 fakeWindow 的值
        if (this.updatedValueSet.has(prop)) {
          return this.fakeWindow[prop];
        }
        // 否则返回原始 window 的值
        return (window as any)[prop];
      },

      set: (_target: Window, prop: string, value: any) => {
        // 所有修改都记录到 fakeWindow 中
        this.fakeWindow[prop] = value;
        this.updatedValueSet.add(prop);
        return true;
      },

      has: (_target: Window, prop: string) => {
        return prop in this.fakeWindow || prop in window;
      },

      deleteProperty: (_target: Window, prop: string) => {
        if (this.updatedValueSet.has(prop)) {
          delete this.fakeWindow[prop];
          this.updatedValueSet.delete(prop);
        }
        return true;
      },

      ownKeys: (_target: Window) => {
        const originalKeys = Reflect.ownKeys(window);
        const fakeKeys = Reflect.ownKeys(this.fakeWindow);
        return Array.from(new Set([...originalKeys, ...fakeKeys]));
      },
    });
  }

  /**
   * 执行子应用代码(推荐方式)
   */
  execScriptWith(script: string): any {
    // 创建沙箱上下文,将代理 window 作为属性
    const sandboxContext = {
      window: this.proxy,
      document: (this.proxy as any).document,
      location: (this.proxy as any).location,
      console: (this.proxy as any).console,
    };

    // 使用 Function 构造器创建函数
    const func = new Function(
      "sandboxContext",
      `
      with(sandboxContext) {
        ${script}
      }
    `
    );

    // 执行函数,传入 sandboxContext
    return func(sandboxContext);
  }

  getProxy(): Window {
    return this.proxy;
  }
}

使用示例

javascript 复制代码
const sandbox = new WindowProxySandbox();

// 子应用的代码(模拟从远程加载的代码)
const appCode = `
  // 子应用代码中直接使用 window,不需要任何修改
  window.myApp = 'sub-app-proxy';
  window.myConfig = { version: '1.0.0', env: 'production' };
  
  // 访问 window 的其他属性也会被代理
  console.log('window.location:', window.location);
  
  // 设置全局变量
  window.globalVar = 'hello from sub app';
`;

// 执行子应用代码
sandbox.execScriptWith(appCode);

// 验证隔离性
console.log("代理 window.myApp:", sandbox.getProxy().myApp); // 输出: sub-app-proxy
console.log("真实 window.myApp:", window.myApp); // 输出: undefined(未被污染)

实际代码示例

示例 1:基本 with 使用

javascript 复制代码
function example1_BasicWith() {
  const obj = {
    name: "Alice",
    age: 25,
    city: "Beijing",
  };

  // 使用 Function 构造器(避免严格模式限制)
  const func = new Function(
    "obj",
    `
    with(obj) {
      // 在 with 块内,可以直接访问 obj 的属性
      console.log("name:", name);        // 输出: Alice
      console.log("age:", age);          // 输出: 25
      console.log("city:", city);        // 输出: Beijing
      
      // 也可以修改属性
      age = 26;
      console.log("修改后的 age:", age); // 输出: 26
    }
    
    // with 块外,需要正常访问
    console.log("with 块外访问:", obj.age); // 输出: 26
  `
  );

  func(obj);
}

示例 2:this 指向演示

javascript 复制代码
function example2_ThisBinding() {
  const obj = {
    name: "Context Object",
    test: function () {
      console.log("this.name:", this.name);
      return this;
    },
  };

  const sandboxContext = {
    window: { name: "Sandbox Window" },
    obj: obj,
  };

  const func = new Function(
    "sandboxContext",
    `
    with(sandboxContext) {
      // window 指向 sandboxContext.window
      console.log("window.name:", window.name); // 输出: Sandbox Window
      
      // 但是 this 仍然指向全局对象,不受 with 影响
      // 注意:这里的 window 是 sandboxContext.window(代理对象),不是全局 window
      console.log("this === window:", this === window); // 输出: false(this 是全局 window,window 是代理对象)
      console.log("this === sandboxContext.window:", this === sandboxContext.window); // 输出: false
      
      // 调用 obj.test(),this 指向 obj,不是 sandboxContext
      const result = obj.test(); // 输出: this.name: Context Object
      console.log("obj.test() 返回的 this === obj:", result === obj); // 输出: true
    }
  `
  );

  func(sandboxContext);
}

示例 3:微前端沙箱应用

javascript 复制代码
function example3_MicroFrontendSandbox() {
  // 创建代理 window
  const proxyWindow = new Proxy(window, {
    get(target, prop) {
      console.log(`[Proxy] 读取属性: ${String(prop)}`);
      return target[prop];
    },
    set(target, prop, value) {
      console.log(`[Proxy] 设置属性: ${String(prop)} = ${value}`);
      target[prop] = value;
      return true;
    },
  });

  const sandboxContext = {
    window: proxyWindow,
    document: proxyWindow.document,
    console: proxyWindow.console,
  };

  const code = `
    // 在 with 块内,window 指向 sandboxContext.window(即 proxyWindow)
    window.myVar = "hello from sandbox";
    console.log("window.myVar:", window.myVar);
    
    // this 仍然指向全局 window(不受 with 影响)
    // 注意:这里的 window 是 sandboxContext.window(代理对象),不是全局 window
    console.log("this === window:", this === window); // 输出: false(this 是全局 window,window 是代理对象)
    console.log("this === sandboxContext.window:", this === sandboxContext.window); // 输出: false
    
    // 但是代码中的 window 指向 proxyWindow
    console.log("代码中的 window === proxyWindow:", window === sandboxContext.window); // 输出: true
  `;

  const func = new Function(
    "sandboxContext",
    `
    with(sandboxContext) {
      ${code}
    }
  `
  );

  func(sandboxContext);

  console.log("\n验证结果:");
  console.log("proxyWindow.myVar:", proxyWindow.myVar);
  console.log("真实 window.myVar:", window.myVar);
}

示例 4:with 内外对比

javascript 复制代码
function example4_CompareInsideAndOutside() {
  const sandboxContext = {
    window: { name: "Proxy Window", myVar: "sandbox value" },
    globalVar: "I'm in sandbox",
  };

  const func = new Function(
    "sandboxContext",
    `
    // with 块外
    console.log("=== with 块外 ===");
    console.log("window:", typeof window); // 输出: object(全局 window)
    console.log("globalVar:", typeof globalVar); // 输出: undefined(未定义)
    
    // with 块内
    with(sandboxContext) {
      console.log("=== with 块内 ===");
      // window 现在指向 sandboxContext.window
      console.log("window.name:", window.name); // 输出: Proxy Window
      console.log("window.myVar:", window.myVar); // 输出: sandbox value
      
      // globalVar 现在指向 sandboxContext.globalVar
      console.log("globalVar:", globalVar); // 输出: I'm in sandbox
      
      // this 仍然指向全局对象(不受 with 影响)
      // 注意:这里的 window 是 sandboxContext.window(代理对象),不是全局 window
      console.log("this === window:", this === window); // 输出: false(this 是全局 window,window 是代理对象)
      console.log("this === sandboxContext.window:", this === sandboxContext.window); // 输出: false
    }
    
    // with 块外,恢复原状
    console.log("=== with 块外(恢复)===");
    console.log("window:", typeof window); // 输出: object(全局 window)
    console.log("globalVar:", typeof globalVar); // 输出: undefined
  `
  );

  func(sandboxContext);
}

常见问题和注意事项

1. 严格模式限制

问题with 语句在严格模式下不能直接使用。

javascript 复制代码
"use strict";
with (obj) {
  // ❌ SyntaxError: Strict mode code may not include a with statement
  // ...
}

解决方案 :使用 Function 构造器动态创建函数(不在严格模式下执行)。

javascript 复制代码
// ✅ 正确的方式
const func = new Function(
  "sandboxContext",
  `
  with(sandboxContext) {
    // 代码在这里执行
  }
  `
);

2. 性能考虑

with 语句会影响 JavaScript 引擎的优化,因为引擎无法在编译时确定变量的作用域。但在微前端沙箱场景中,这是必要的权衡。

3. 调试困难

使用 with 语句会让代码调试变得困难,因为变量的实际来源不明确。建议:

  • 添加详细的日志
  • 使用清晰的变量命名
  • 在开发环境中禁用 with,使用其他方式

4. 作用域污染

with 语句会将对象的所有属性添加到作用域链中,可能导致意外的变量覆盖。

javascript 复制代码
const obj = {
  console: "这不是 console 对象",
  log: "这也不是 log 方法",
};

with (obj) {
  console.log("这可能会出错!"); // ❌ 因为 console 被覆盖了
}

解决方案 :只将必要的属性添加到 sandboxContext 中。


最佳实践

1. 始终使用 sandboxContext 对象

javascript 复制代码
// ❌ 错误:不要直接用 with(proxyWindow)
with (proxyWindow) {
  window.myVar = "hello"; // window 会去外层作用域查找
}

// ✅ 正确:使用 sandboxContext 对象
const sandboxContext = {
  window: proxyWindow,
  document: proxyWindow.document,
  console: proxyWindow.console,
};

with (sandboxContext) {
  window.myVar = "hello"; // window 指向 sandboxContext.window
}

2. 处理全局对象

除了 window,还要处理其他全局对象:

javascript 复制代码
const sandboxContext = {
  window: proxyWindow,
  document: proxyWindow.document,
  location: proxyWindow.location,
  console: proxyWindow.console,
  // 根据需要添加其他全局对象
};

3. 错误处理

子应用代码执行可能出错,需要添加错误处理:

javascript 复制代码
try {
  sandbox.execScriptWith(appCode);
} catch (error) {
  console.error("执行子应用代码时出错:", error);
  // 记录错误日志,便于调试
}

4. 性能优化

  • 避免频繁创建和销毁沙箱
  • 缓存常用的全局对象引用
  • 只在必要时使用 with 语句

5. 兼容性考虑

  • 检查浏览器是否支持 Proxy(现代浏览器都支持)
  • 不支持时降级到快照沙箱(SnapshotSandbox)

6. 安全性

  • 只加载可信的子应用代码
  • 限制子应用访问某些敏感 API
  • 使用 CSP(Content Security Policy)限制代码执行

总结

核心要点

  1. with 语句的作用

    • 将对象添加到作用域链的最前端
    • with 块内可以直接访问对象的属性
  2. this 的指向(最重要)

    • this 不受 with 语句影响
    • this 始终指向函数调用时的上下文
    • with 块内,this 仍然指向原来的对象(通常是全局对象)
  3. window 的指向

    • 不能直接用 with(proxyWindow)
    • 应该用 sandboxContext = { window: proxyWindow } 然后 with(sandboxContext)
    • 这样代码中的 window 会指向 sandboxContext.window(即代理对象)
  4. 作用域链查找顺序

    • 局部变量 → with 指定的对象 → 外层作用域 → 全局作用域
  5. 实际应用

    • 微前端沙箱实现的核心机制
    • 通过 with 语句替换 window 引用
    • 结合 Proxy 实现完全隔离

关键代码模式

javascript 复制代码
// 1. 创建代理 window
const proxyWindow = new Proxy(window, {
  /* ... */
});

// 2. 创建沙箱上下文
const sandboxContext = {
  window: proxyWindow,
  document: proxyWindow.document,
  // ... 其他全局对象
};

// 3. 使用 with 语句执行代码
const func = new Function(
  "sandboxContext",
  `
  with(sandboxContext) {
    ${appCode}
  }
  `
);

func(sandboxContext);

参考资料

相关推荐
越努力越幸运50834 分钟前
webpack的学习打包工具
前端·学习·webpack
IT古董36 分钟前
微前端的新纪元:Vite + Module Federation 最强指南(2025 全面技术解析)
前端
小小弯_Shelby41 分钟前
vue项目源码泄露漏洞修复
前端·javascript·vue.js
兔子零102442 分钟前
CSS 视口单位进化论:从 100vh 的「骗局」到 dvh 的救赎
前端·css
q***87601 小时前
项目升级Sass版本或升级Element Plus版本遇到的问题
前端·rust·sass
k***12171 小时前
【Nginx 】Nginx 部署前端 vue 项目
前端·vue.js·nginx
看晴天了1 小时前
手势操控 Three.js!效果炸裂!
前端
喝咖啡的女孩1 小时前
Promise × 定时器全场景手写
前端
h***34631 小时前
MS SQL Server 实战 排查多列之间的值是否重复
android·前端·后端