this在函数调用内部根据函数的调用方式设置。this设置的主要方式有六种。
普通函数调用: 在普通函数调用中,例如foo(),this设置为全局对象(window在浏览器或globalnodejs 中)或 to undefined(在 JavaScript 的严格模式下)。
方法调用: 如果一个方法被调用,例如obj.foo()该方法foo是使用function关键字的普通方法声明或使用 a 的常规方法声明语法class,则在函数内部this设置为obj。
.apply() 或 .call(): 如果使用.apply()或.call(),则this根据传递给.apply()or 的内容进行设置.call()。例如,您可以为该特定函数调用dofoo.call(myObj)和 causethis设置为myObjinside of foo()。
使用 new: 如果使用new诸如调用函数new foo(),则会创建一个新对象,并foo使用thisset 为新创建的对象调用构造函数。
使用 .bind(): 当使用.bind()新的存根函数时,该调用会返回内部用于.apply()设置this传递给的指针的函数.bind()。仅供参考,这并不是真正的不同情况,因为.bind()可以使用.apply().
使用ES6 Fat Arrow 函数在 ES6+ 中通过箭头语法定义一个函数将绑定当前的词法值this到它。因此,无论在其他地方如何调用该函数(使用之前的任何调用方式),this解释器都会将该值设置this为定义该函数时的值。这与所有其他函数调用完全不同。
有第七种方法,通过回调函数,但它实际上并不是它自己的方案,而是调用回调的函数使用上述方案之一,并确定this调用回调时的值。您必须查阅调用函数的文档或代码,或者自己测试以确定this将在回调中设置的内容。
在 JavaScript 中理解的重要一点是 JavaScript 中的每个函数或方法调用都为this. 并且,设置哪个值取决于函数的调用方式。
因此,如果您将方法作为普通回调传递,默认情况下该方法不会被调用obj.method(),因此不会this为其设置正确的值。您可以使用.bind()来解决该问题。
了解某些回调函数(例如 DOM 事件处理程序)使用this调用回调函数的基础结构设置的特定值 也很有用。在内部,他们都使用.call()或者.apply()所以这不是一个新的规则,而是一个需要注意的。回调函数的“契约”可能包括它如何设置 的值this。如果它没有明确设置 的值this,那么它将根据规则#1 设置。
在 ES6 中,通过箭头函数调用函数,维护 的当前词法值this。这是维护this MDN词法的数组函数示例:
function Person(){
this.age = 0;
setInterval(() => {
this.age++; // |this| properly refers to the person object
}, 1000);
}
var p = new Person();
您的示例obj.prepareRandomFunction();是上面的规则 #2,因此this将设置为obj.
你的例子randomFunction(this.sumData.bind(this))是上面的规则#1,所以thisinside ofrandomFunction将被设置为全局对象或undefined(如果在严格模式下)。
由于randomFunction正在呼叫它本身使用的回调函数.bind(),则值this时它被称为将被设置为的值的回调函数内this传递给.bind()在this.sumData.bind(this)经由上述规则#5。 .bind()实际上创建了一个新函数,它的工作是在设置自定义值后调用原始函数this。
以下是有关该主题的其他一些参考资料:
如何避免“this”引用DOM元素,并引用对象
更好地理解这一点
“this”关键字如何工作?
请注意,通过使用.apply()or.call()或.bind(),您可以创建各种有点奇怪的东西,有时甚至是非常有用的东西,这些东西在C++ 之类的东西中永远无法完成。您可以使用世界上的任何函数或方法,并像调用其他对象的方法一样调用它。
例如,这通常用于将arguments对象中的项目复制到数组中:
var args = Array.prototype.slice.call(arguments, 0);
或类似:
var args = [].slice.call(arguments, 0);
这需要数组的.slice()方法并调用它,但为它提供一个参数对象作为this指针。该arguments对象(虽然不是实际数组)具有足够的类似数组的功能,该.slice()方法可以对其进行操作,并且最终将arguments项目的副本复制到实际数组中,然后可以直接使用实际数组操作对其进行操作。这种诡计不能随意完成。如果数组.slice()方法依赖于arguments对象上不存在的其他数组方法,那么这个技巧将不起作用,但由于它只依赖于[]和.length,这两个arguments对象都有,所以它确实有效。
因此,这个技巧可用于从任何对象“借用”方法并将它们应用到另一个对象,只要您应用它们的对象支持该方法实际使用的任何方法或属性。这不能在 C++ 中完成,因为方法和属性在编译时是“硬绑定”的(甚至 C++ 中的虚拟方法都绑定到在编译时建立的特定 v-table 位置),但可以在 JavaScript 中轻松完成,因为属性和方法在运行时通过它们的实际名称实时查找,因此任何包含正确属性和方法的对象都可以与对这些进行操作的任何方法一起使用。