【补环境框架】序

补环境框架的核心问题与优化方案

最近在研究补环境框架的实现,发现了一些有意思的东西。现有的框架虽然能用,但代码量大得离谱。本文会深入分析现有方案的工作原理和致命缺陷,最后提出一个基于V8魔改的优化思路。

一、现有框架怎么工作的

调用链路分析

navigator.webdriver 举例,看看一次属性访问要经过多少层:

javascript 复制代码
// 用户代码
console.log(navigator.webdriver);

// 实际执行路径
navigator                                    // 1. 访问对象
  → Proxy.get handler                        // 2. 代理拦截
    → Object.getOwnPropertyDescriptor().get  // 3. 属性 getter
      → dispatch("Navigator_webdriver_get")  // 4. 分发路由
        → envFuncs.Navigator_webdriver_get() // 5. 环境函数
          → jsdomNavigator.webdriver (这是个demo 不从jsdom取 )        // 6. 真实对象
            → return undefined               // 7. 返回结果

整个链路的核心是 dispatch 函数,它就像一个路由器,把所有 API 调用转发到对应的处理函数。

javascript 复制代码
khBox.toolsFunc.dispatch = function(funcName, thisArg, args, defaultValue) {
    const envFunc = khBox.envFuncs[funcName];
    return envFunc ? envFunc.apply(thisArg, args) : defaultValue;
};

toString 的坑

用 Proxy 代理对象后,toString 会暴露问题:

javascript 复制代码
// 真实浏览器
Object.prototype.toString.call(navigator);
// "[object Navigator]"

// 被代理的对象
const proxyNav = new Proxy(navigator, {});
Object.prototype.toString.call(proxyNav);
// "[object Object]"  

网站的反爬代码会这么检测:

javascript 复制代码
const toString = Object.prototype.toString;
if (toString.call(navigator) !== '[object Navigator]') {
    console.log('检测到异常环境!');
}

框架的解决办法是重写 Symbol.toStringTag

javascript 复制代码
Object.defineProperty(Navigator.prototype, Symbol.toStringTag, {
    value: 'Navigator',
    enumerable: false,
    configurable: true,
    writable: false
});

这个操作要为每个类都做一遍,后面会看到这有多麻烦。

webdriver 属性的处理

navigator.webdriver 是用来检测自动化工具的:

javascript 复制代码
// Selenium/Puppeteer 环境
navigator.webdriver === true

// 真实浏览器
navigator.webdriver === undefined

// 网站检测
if (navigator.webdriver) {
    alert('检测到机器人!');
}

框架的处理分三步:

javascript 复制代码
// Step 1: 定义属性描述符
Object.defineProperty(Navigator.prototype, 'webdriver', {
    configurable: true,
    enumerable: true,
    get: function() {
        return khBox.toolsFunc.dispatch(
            "Navigator_webdriver_get", 
            this, 
            arguments, 
            undefined
        );
    }
});

// Step 2: 实现环境函数
khBox.envFuncs.Navigator_webdriver_get = function() {
    console.log('{khBox|dispatch} -> Navigator_webdriver_get');
    return khBox.memory.jsdomNavigator.webdriver;
};

// Step 3: 用户访问时自动经过这两层
console.log(navigator.webdriver); // undefined

二、现有框架的问题

问题1:暴力穷举所有API

AnalyserNode.js 这个文件为例:

javascript 复制代码
// 定义 fftSize 属性
khBox.toolsFunc.defineProperty(AnalyserNode.prototype, "fftSize", {
    configurable: true,
    enumerable: true,
    get: function() {
        return khBox.toolsFunc.dispatch(
            "AnalyserNode_fftSize_get", 
            this, 
            arguments, 
            undefined
        );
    },
    set: function() {
        return khBox.toolsFunc.dispatch(
            "AnalyserNode_fftSize_set", 
            this, 
            arguments
        );
    }
});

// 定义 frequencyBinCount 属性
khBox.toolsFunc.defineProperty(AnalyserNode.prototype, "frequencyBinCount", {
    configurable: true,
    enumerable: true,
    get: function() {
        return khBox.toolsFunc.dispatch(
            "AnalyserNode_frequencyBinCount_get", 
            this, 
            arguments, 
            undefined
        );
    }
});

// ... 还有 minDecibels、maxDecibels、smoothingTimeConstant
// ... 还有 getByteFrequencyData、getFloatFrequencyData
// ... 总共定义了十几个属性和方法

AnalyserNode 只是 Web API 中的一个小角色,整个项目的规模:

目前的框架 ,加起来四万多行 。。。

关键是这些代码都是重复的模板:

javascript 复制代码
// 模板 A:属性 getter
get: function() {
    return khBox.toolsFunc.dispatch(
        "ClassName_propName_get", 
        this, 
        arguments, 
        undefined
    );
}

// 模板 B:属性 setter
set: function() {
    return khBox.toolsFunc.dispatch(
        "ClassName_propName_set", 
        this, 
        arguments
    );
}

// 模板 C:方法调用
value: function() {
    return khBox.toolsFunc.dispatch(
        "ClassName_methodName", 
        this, 
        arguments
    );
}

三万行代码,就是这三件事的排列组合。

问题2:toString 保护的代价

为了让对象看起来像原生对象,需要做这些:

javascript 复制代码
// 1. 设置 toStringTag
Object.defineProperty(Navigator.prototype, Symbol.toStringTag, {
    value: 'Navigator',
    enumerable: false,
    configurable: true,
    writable: false
});

// 2. 重写 toString
Object.defineProperty(Navigator.prototype, 'toString', {
    value: function() {
        return '[object Navigator]';
    },
    writable: false,
    enumerable: false,
    configurable: false
});

// 3. 重写 valueOf
Object.defineProperty(Navigator.prototype, 'valueOf', {
    value: function() {
        return this;
    },
    writable: false,
    enumerable: false
});

// 4. 修正 constructor
Object.defineProperty(Navigator.prototype, 'constructor', {
    value: Navigator,
    writable: false,
    enumerable: false,
    configurable: false
});
//这里也能优化一点点 ,也就是一点点,不都直接写在具体的函数上。 重写defineProperty 。

每个类都要写一遍这些防御性代码。

问题3:性能损失

每次属性访问要经过六层:

plain 复制代码
用户代码
  → Proxy.get           (第1层)
    → defineProperty    (第2层)
      → getter 函数     (第3层)
        → dispatch      (第4层)
          → envFunc     (第5层)
            → 真实对象  (第6层)

当然,还是有一些优化的点

  1. 四万多行的基础代码可以用模板去从浏览器取,但是又不能全取,有的node自带的,就没有必要去重写。写了还会导致原本node的方法失效。比如json,math,proxy等。
  2. 另外优化的点是 分发器套用domino ,或者jsdom ,能省一些代码,但是要做映射。不用自己全补。

参考 https://www.bilibili.com/video/BV19dSCBqEFe/

以及github https://github.com/xuxiaobo-bobo/boda_jsEnv

三、有没有优化的思路

有。ai给了一个思路,从v8开始改。将这些放c层。

实现路线图:
第一步:理解 V8 属性访问机制

V8 如何处理 obj.prop 这样的属性访问?涉及哪些 C++ 函数?如何在不破坏原有逻辑的情况下插入 Hook?
第二步:添加 Hook 注册接口

在 V8 isolate 中添加 Hook 函数的存储和调用机制。如何从 C++ 调用 JavaScript 函数?如何处理参数传递和返回值?
第三步:修改属性访问流程

在 Runtime_GetProperty、Runtime_SetProperty 等关键函数中插入 Hook 调用点。如何保证性能?如何处理异常?
第四步:暴露 Node.js API

在 Node.js 层面暴露 v8.registerPropertyHook() 接口,让 JavaScript 代码可以注册 Hook 函数。
第五步:实现 Native 对象标记

让 V8 自动识别 DOM/BOM 对象,为 toString、instanceof 等操作提供正确的行为。
第六步:优化与测试

性能测试、边界情况处理、与现有代码的兼容性测试。

emm

由于空缺比较大,会从c++基础开始,同时夹杂一些其他内容,先开一个坑,慢慢填

更多文章,敬请关注gzh:零基础爬虫第一天

相关推荐
vx_biyesheji00016 小时前
豆瓣电影推荐系统 | Python Django 协同过滤 Echarts可视化 深度学习 大数据 毕业设计源码
大数据·爬虫·python·深度学习·django·毕业设计·echarts
深蓝电商API7 小时前
爬虫IP封禁后的自动切换与检测机制
爬虫·python
喵手8 小时前
Python爬虫实战:公共自行车站点智能采集系统 - 从零构建生产级爬虫的完整实战(附CSV导出 + SQLite持久化存储)!
爬虫·python·爬虫实战·零基础python爬虫教学·采集公共自行车站点·公共自行车站点智能采集系统·采集公共自行车站点导出csv
喵手8 小时前
Python爬虫实战:地图 POI + 行政区反查实战 - 商圈热力数据准备完整方案(附CSV导出 + SQLite持久化存储)!
爬虫·python·爬虫实战·零基础python爬虫教学·地区poi·行政区反查·商圈热力数据采集
芷栀夏9 小时前
从 CANN 开源项目看现代爬虫架构的演进:轻量、智能与统一
人工智能·爬虫·架构·开源·cann
喵手1 天前
Python爬虫实战:HTTP缓存系统深度实战 — ETag、Last-Modified与requests-cache完全指南(附SQLite持久化存储)!
爬虫·python·爬虫实战·http缓存·etag·零基础python爬虫教学·requests-cache
喵手1 天前
Python爬虫实战:容器化与定时调度实战 - Docker + Cron + 日志轮转 + 失败重试完整方案(附CSV导出 + SQLite持久化存储)!
爬虫·python·爬虫实战·容器化·零基础python爬虫教学·csv导出·定时调度
喵手1 天前
Python爬虫实战:全站 Sitemap 自动发现 - 解析 sitemap.xml → 自动生成抓取队列的工业级实现!
爬虫·python·爬虫实战·零基础python爬虫教学·sitemap·解析sitemap.xml·自动生成抓取队列实现
iFeng的小屋1 天前
【2026年新版】Python根据小红书关键词爬取所有笔记数据
笔记·爬虫·python
Love Song残响1 天前
揭秘Libvio爬虫:动态接口与逆向实战
爬虫