在Javascript中,是否有一个等价于 .apply 的方法,而不改变 this 的值?

48

看起来很容易,我想调用一个带有参数数组的函数。当然,我可以这样说func.apply(this, ['some', 'arguments']); 但这会改变func内部的this值。你有什么办法可以在不改变它的情况下完成吗?


1
你可以将 null 作为第一个参数传递。但是,在 func 中,this 应该指代什么? - jAndy
2
问题不够清晰,请提供更多的代码。 - artyom.stv
2
听起来你正在函数内部以不应该使用的方式使用 this - Lightness Races in Orbit
1
好的,我现在明白了。在我的情况下,我有一个对象的原型函数,我想用.apply()来调用它。我以为函数会以某种方式与它的对象绑定在一起,所以我不想像apply()那样改变它,但是显然没有这样的联系,apply()只是处理被调用的函数本身。 - JussiR
5个回答

30

由于 JavaScript 中 this 的工作方式,你无法实现该操作。请参阅以下内容:

根据 ECMAScript 规范的 “进入执行上下文” 部分:当您调用一个函数时,this 的值由其左侧的对象(称为激活对象)决定。让我们创建一个名为 Steve 的函数,并将它放在一个对象中:

function steve(){}
var obj = { method: steve };

当我们调用obj.method()时,他的thisobj,因为obj是激活对象。

最棘手的情况是当函数调用左侧没有任何东西的时候:

steve(); // Who am I ?!

函数调用左边没有任何东西——实际上相当于null——因此this的值被设置为全局对象的默认值(window在Web浏览器中,global在Node.js中等)。

因此,每次调用函数时,实际上都会设置它的this

P.S. 调用steve.apply(null, [])相当于调用steve()


使用ES5,这并不是真的。this的值为未定义(而不是null),并且不会更改为全局对象(实际上,在with语句中这并不是真的,但现在我会忽略它们)。对于ES3和ES5引擎,在with语句之外,steve.apply(undefined, [])steve()是等效的,尽管它们的行为在ES3和ES5之间有所不同。 - gsnedders
@gsnedders,谢谢!我在规范中找到的(第10.4.3节)描述了将this设置为全局对象,并且null/undefined是等效的。你能告诉我新行为在哪里描述吗? - s4y
喜欢那些参考规范提供上下文的答案。 - Dwight Gunning
当我想调用已经绑定到对象的函数对象而不改变'this'时会发生什么?例如,var handler = MyFunction.bind(myObject); 在这里,我已经创建了一个函数对象,它已经绑定到myObject作为'this'对象;现在我想从代码的另一个部分调用handler,该部分甚至不知道myObject的存在。handler.call(null, params)会破坏this,对吗?如果我将其作为handler.call(undefined, params)调用会发生什么? - Thanasis Ioannidis
@ThanasisIoannidis:绑定函数应该忽略调用时获取的this值。this仍将是您传递给.bind()的内容。 - s4y

15

请注意,在ES6中,您也可以编写以下代码:

func(...someArray)

func 内部,this 变成了 Window,但这没有什么用处。但是如果你这样写:

obj.func(...someArray)

thisfunc内部将是obj

这可能是我5年前正在寻找的功能,但那是5年前的事了,所以谁还记得 :)


4

当调用函数时,this 的值并不是什么神秘的东西,它取决于函数的“正常”调用方式(不使用 callapply):

func(); // `this` is the global object, or undefined in ES5 strict mode
obj.func(); // `this` is `obj`

为了避免“改变”this的值,只需根据通常的调用方式,将正确的值传递给apply即可:
func.apply(undefined, []); // global object, or undefined in ES5 strict mode
obj.func.apply(obj, []);

对于实现ES5的浏览器,null对于全局对象不起作用,因为在被调用的函数中,“this”将是null值。 - gsnedders
@gsnedders,当你说es5时要小心,因为这只在严格模式下才是正确的。尽管如此,我已经改变了我的答案,传递未定义以获得最佳兼容性。干杯。 - David Tang
嗯,如果你深入遵循算法,Function.prototype.apply下的注释会有些误导性...“thisArg值作为this值传递而不进行修改。这是从第3版开始的变化,其中未定义或空的thisArg将被替换为全局对象,并且ToObject将应用于所有其他值,并将该结果作为this值传递。” - gsnedders
@gsnedders,我不明白你的意思。能解释一下吗? - David Tang

2
如果您单独调用一个函数,请传递null
func.apply(null, ['some', 'arguments']);

如果您调用的函数是对象的一个方法,请传递该对象。
var arr = [];
arr.push.apply(arr, ['some', 'arguments']);

为了理解如何匹配this的工作原理,以下是相关的IT技术内容。

0

你想要的没有太多意义。

在 ECMAScript 标准中,this 是这样定义的:

11.1.1 关键字 this - 关键字 this 的求值结果为当前执行上下文的 ThisBinding 值。

函数执行上下文中的 ThisBinding 解析方式如下:

10.4.3 输入函数代码 当控制进入包含在函数对象F中的函数代码的执行上下文时,将执行以下步骤:
  1. 如果函数代码是严格模式代码,则将ThisBinding设置为thisArg。
  2. 否则,如果thisArg为null或undefined,则将ThisBinding设置为全局对象。
  3. 否则,如果Type(thisArg)不是Object,则将ThisBinding设置为ToObject(thisArg)。
  4. 否则,将ThisBinding设置为thisArg。
  5. 让localEnv成为调用NewDeclarativeEnvironment的结果,将F的[[Scope]]内部属性的值作为参数传递。
  6. 将LexicalEnvironment设置为localEnv。
  7. 将VariableEnvironment设置为localEnv。
  8. 让code成为F的[[Code]]内部属性的值。
  9. 使用函数代码code和argumentList执行声明绑定实例化,如10.5所述。
换句话说,无论是你自己指定还是自动设置为全局对象,它都不会从其父范围继承this绑定。因此,将父作用域的this绑定传递给它的子元素的唯一方法是:
  1. 在父作用域中将其保存在本地变量中,例如var self = this
  2. 使用call/apply注入,就像你在问题中提供的代码一样。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接