数组原型方法调用及函数apply调用时 类数组参数在IE8下的问题

本文详细介绍了JavaScript中Function.prototype.apply方法的使用方式及其在不同浏览器环境下的兼容性问题,特别是IE8及更低版本浏览器中的注意事项。文章还探讨了如何正确地将类数组对象转化为真正的数组,并提供了实用的代码示例。

当函数以 apply 方式调用时, 传参方式是一个由各个参数组成的数组或类数组(一个有length属性的对象),传入参数个数取决于 length 的值,例如,某个对象 args.length=3; apply 会将 args[0],args[1],args[2] 三个参数传入,如果对应的值不存在则传入了undefined.

例如:

function f(a,b,c){ console.log(a,b,c);}
f.apply(null,{0:123,1:456,2:789,length:2});//123 456 undefined ,因为 length 属性值为2,值只传入了 2 个参数
f.apply(null,{10:123,11:456,12:789,length:3});// undefined undefined undefined , 因为参数对象不存在以 0、1、2 为 key 的值

 

但是,在IE8 及低于IE8以下的浏览器需要注意几个问题.

1、 apply 传参不接受类似 {0:'a',1:'b',length:2} 的对象,可以是 数组、arguments、  HTMLCollection 对象 和 Nodelist 对象等节点集合.

在这种情况下你也许想要把传参对象转换成数组.

2、节点集合无法调用数组的原型方法,但是 类似 {0:'a',1:'b',length:2} 的对象可以。

复制代码
var nodes = document.getElementsByTagName("div");
var obj = {0:1,1:2,2:3,length:3};

var args =  Array.prototype.slice.apply(nodes,[0]); //error,"Array.prototype.slice: 'this' 不是 JavaScript 对象",节点集合无法调用数组原型的方法
var args =  Array.prototype.slice.apply(obj,[0]); //ok

var args = Array.prototype.concat.apply([],nodes); //ok
var args = Array.prototype.concat.apply([],obj); //error,"Function.prototype.apply: 缺少 Array 或 arguments 对象",普通对象无法用于 apply 传参 
复制代码

综合以上,可以使用 try-catch 

复制代码
var nodes = document.getElementsByTagName("div");
var obj = {0:1,1:2,2:3,length:3};
var params = nodes;//try nodes and obj
try{
    var args =  Array.prototype.slice.apply(params ,[0]); 
}catch(err){
    var args = Array.prototype.concat.apply([],params);
}

console.log(args);
复制代码

 

另外,也许有这样的:

var args = Array.apply([],params);//应使用,Array.prototype.concat.apply([],params) 
var obj = {0:3,length:1};
var args = Array.apply([],obj); //在 obj[0] 符合数组 length 取值范围时,将得到长度为3 的空数组而不是 [3],这种情况比较容易疏忽

args.length ;//3  ( obj[0]<Math.pow(2,32)-1)

 

 

关于 concat :

数组的 concat 方法 可以被任何对象调用,得到一个数组,但是, 如果调用者不是 数组, 那么 调用者本身 仅仅作为 结果数组的第一个元素 

function f(a,b){
  var args = [].concat.apply(arguments,[111,222,333]);
  console.log(args);
  console.log(args.length);
  console.log(args[0]);
}
 
f("x","y")
 
//执行结果
[Arguments[2], 111, 222, 333]
4
["x", "y"]

转载于:https://www.cnblogs.com/makan/p/4826861.html

<think>我们想要在每次调用 JavaScript 函数自动执行一个 Java 方法。这可以通过在 V8 中设置函数调用拦截器(Interceptor)来实现。具体来说,我们可以使用 V8 的 `FunctionTemplate` 来设置调用处理器(call handler),这样每次通过该模板创建的函数调用,都会先执行我们指定的 Java 回调。 ### 实现步骤 1. **创建函数模板**:使用 `V8FunctionTemplate` 定义一个函数模板,并设置其调用处理器。 2. **实现调用处理器**:在调用处理器中,先执行我们指定的 Java 方法,然后再执行原始的函数逻辑。 3. **将模板设置为全局函数**:将创建的函数模板设置为全局对象的一个属性,这样 JavaScript 代码就可以调用它。 ### 代码示例 以下是一个完整的示例,展示了如何在 J2V8 中实现每次调用 JavaScript 函数默认执行一个 Java 方法: ```java import com.eclipsesource.v8.*; public class FunctionInterceptorExample { public static void main(String[] args) { // 创建 V8 运行 V8 v8 = V8.createV8Runtime(); // 创建函数模板 V8FunctionTemplate functionTemplate = new V8FunctionTemplate(v8, (receiver, parameters) -> { // 在函数调用前执行的 Java 方法 System.out.println("Before calling JavaScript function"); // 获取原始函数 V8Function originalFunction = (V8Function) receiver; // 执行原始函数逻辑 Object result = originalFunction.call(receiver, parameters); // 在函数调用后执行的 Java 方法 System.out.println("After calling JavaScript function"); return result; }); // 将函数模板设置为全局对象的一个函数 v8.add("myFunction", functionTemplate); // 在 JavaScript 中定义函数 v8.executeVoidScript("function originalFunction(a, b) { return a + b; }"); // 将原始函数包装到我们的拦截器函数中 v8.executeVoidScript("function interceptedFunction(...args) { return myFunction(originalFunction, ...args); }"); // 调用被拦截的函数 int result = v8.executeIntegerFunction("interceptedFunction", new Object[]{3, 4}); System.out.println("Result: " + result); // 输出: 7 // 清理资源 functionTemplate.release(); v8.release(); } } ``` ### 关键点说明 1. **函数模板**:我们创建了一个 `V8FunctionTemplate`,它定义了函数调用行为。在调用处理器中,我们可以插入任意的 Java 代码。 2. **包装原始函数**:在 JavaScript 中,我们创建了一个新的函数 `interceptedFunction`,它使用 `myFunction`(即我们的拦截器)来调用原始函数 `originalFunction`。这样,每次调用 `interceptedFunction` ,都会先执行 Java 中的拦截器逻辑。 3. **参数传递**:在调用处理器中,我们通过 `receiver` 获取到原始函数(即 `originalFunction`),然后使用 `parameters` 获取参数,并调用原始函数。 ### 更通用的方法 如果我们想要拦截任意函数调用(而不仅仅是特定的函数),我们可以修改全局的 `Function` 原型,但这可能会影响性能且不推荐。另一种方法是使用代理(Proxy): ```javascript const originalFunction = function(a, b) { return a + b; }; const interceptedFunction = new Proxy(originalFunction, { apply: function(target, thisArg, argumentsList) { // 调用 Java 方法(这里通过一个全局注册的 Java 方法) __javaInterceptor.beforeCall(); const result = target.apply(thisArg, argumentsList); __javaInterceptor.afterCall(); return result; } }); // 然后使用 interceptedFunction 代替 originalFunction ``` 在 Java 中,我们需要注册一个对象 `__javaInterceptor`,它包含 `beforeCall` 和 `afterCall` 方法: ```java V8Object interceptor = new V8Object(v8); interceptor.registerJavaMethod((receiver, parameters) -> { System.out.println("Before call"); return null; }, "beforeCall"); interceptor.registerJavaMethod((receiver, parameters) -> { System.out.println("After call"); return null; }, "afterCall"); v8.add("__javaInterceptor", interceptor); ``` ### 注意事项 1. **性能影响**:每次函数调用都执行 Java 方法会带来一定的性能开销,尤其是在频繁调用函数上。 2. **错误处理**:确保在拦截器中正确处理异常,避免导致 JavaScript 引擎崩溃。 3. **资源管理**:使用完毕后,及释放 V8 对象,避免内存泄漏。 ### 引用说明 V8 引擎提供了高性能的 JavaScript 执行环境[^1],通过即编译(JIT)技术将 JavaScript 代码编译成本地机器码[^2]。在 Java 中,我们可以通过 J2V8 库来利用 V8 引擎[^4]。 §§相关问题§§ 1. 如何在 J2V8 中实现函数调用的性能监控? 2. 如何避免函数拦截带来的性能开销? 3. J2V8 中如何安全地传递异常信息? 4. 如何动态地为所有函数添加拦截器? 5. J2V8 与 Nashorn 引擎在函数拦截方面的区别是什么?[^3]</think>在 J2V8 中实现每次调用 JavaScript 函数自动执行 Java 方法,可以通过 **函数代理(Proxy)** 或 **全局函数包装器** 两种方式实现。以下是具体实现方案: ### 方法 1:使用 JavaScript Proxy 代理 ```java import com.eclipsesource.v8.*; public class FunctionInterceptor { public static void main(String[] args) { V8 v8 = V8.createV8Runtime(); // 注册 Java 拦截方法 v8.registerJavaMethod((receiver, parameters) -> { System.out.println("【前置拦截】函数调用开始"); return null; }, "beforeCall"); v8.registerJavaMethod((receiver, parameters) -> { System.out.println("【后置拦截】函数调用结束"); return null; }, "afterCall"); // 创建代理函数 v8.executeVoidScript( "function createProxy(fn) {" + " return new Proxy(fn, {" + " apply: function(target, thisArg, args) {" + " beforeCall();" + // 调用 Java 前置方法 " const result = target.apply(thisArg, args);" + " afterCall();" + // 调用 Java 后置方法 " return result;" + " }" + " });" + "}"); // 原始函数 v8.executeVoidScript("function add(a, b) { return a + b; }"); // 创建代理版本 v8.executeVoidScript("const proxiedAdd = createProxy(add);"); // 调用代理函数 int result = v8.executeIntegerFunction("proxiedAdd", new Object[]{3, 5}); System.out.println("计算结果: " + result); v8.release(); } } ``` **输出**: ``` 【前置拦截】函数调用开始 【后置拦截】函数调用结束 计算结果: 8 ``` ### 方法 2:全局函数包装器 ```java // 在 V8 中注册函数包装器 v8.executeVoidScript( "const originalSetTimeout = setTimeout;" + "setTimeout = function(...args) {" + " beforeCall();" + // 前置拦截 " const result = originalSetTimeout.apply(this, args);" + " afterCall();" + // 后置拦截 " return result;" + "};" ); // JS 调用示例 v8.executeVoidScript("setTimeout(() => console.log('Hello'), 1000);"); ``` ### 执行流程 ```mermaid sequenceDiagram JS->>+Proxy: 调用函数 Proxy->>Java: 执行 beforeCall() Java-->>Proxy: 返回 Proxy->>原始函数: 执行实际逻辑 原始函数-->>Proxy: 返回结果 Proxy->>Java: 执行 afterCall() Java-->>Proxy: 返回 Proxy-->>JS: 返回最终结果 ``` ### 关键特性对比 | 特性 | Proxy 代理 | 全局包装器 | |------|------------|------------| | 拦截范围 | 指定函数 | 所有同名函数 | | 性能影响 | 较低 (V8优化) | 中等 | | 实现复杂度 | 中等 | 简单 | | 是否修改原函数 | 否 | 是 | | 支持异步函数 | 是 | 是 | ### 高级应用:带参数的拦截器 ```java // 注册带参数的拦截器 v8.registerJavaMethod((receiver, parameters) -> { String funcName = parameters.getString(0); System.out.println("拦截函数: " + funcName); return null; }, "logCall"); // Proxy 增强版 v8.executeVoidScript( "function createEnhancedProxy(fn, name) {" + " return new Proxy(fn, {" + " apply: function(target, thisArg, args) {" + " logCall(name);" + // 传递函数名 " return target.apply(thisArg, args);" + " }" + " });" + "}" ); ``` ### 性能优化建议 1. **减少跨语言调用**: ```java // 批量处理拦截逻辑 v8.registerJavaMethod((receiver, parameters) -> { V8Array calls = (V8Array) parameters.get(0); for (int i = 0; i < calls.length(); i++) { // 批量处理函数调用记录 } return null; }, "batchLog"); ``` 2. **选择性拦截**: ```javascript // JS 侧配置拦截名单 const monitoredFunctions = ['saveData', 'processPayment']; ``` 3. **使用 V8 快照**: ```java // 预编译代理函数 V8Script compiled = v8.compileScript("(function(fn){...})"); v8.add("createProxy", compiled); ``` > ⚠️ **注意事项**: > 1. 避免在拦截器中执行耗操作,否则会阻塞 JS 执行线程 > 2. 使用 `try-finally` 确保后置拦截必执行 > 3. 异步函数需特殊处理(如返回 Promise ) V8 引擎的即编译(JIT)技术能有效优化这类拦截模式[^1],但频繁的 Java-JS 上下文切换仍会有性能开销。对于高性能场景,建议在 V8 内部实现拦截逻辑[^2]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值