JavaScript中的this关键字,如何工作

时间:2021-07-18 19:15:52

ECMAScript Standard中定义this为

evaluates to the value of the ThisBinding of the current execution context;//当前执行的上下文中的ThisBinding值

ThisBinding是JavaScript解释器在评估JavaScript代码时维护的东西,就像一个特殊的CPU寄存器,它保存对一个对象的引用。解释器在仅在三种不同情况之一中建立执行上下文时更新ThisBinding:

  1. 初始全局执行上下文

    对于遇到<script>元素时评估的JavaScript代码,就是这种情况

    <script type="text/javascript">//<![CDATA[
    alert("I'm evaluated in the initial global execution context!");

    setTimeout(function () {
    alert("I'm NOT evaluated in the initial global execution context.");
    }, 1);
    //]]></script>

    当在初始全局执行上下文中评估代码时,ThisBinding被设置为全局对象window

  2. 输入eval代码

    • ...通过直接调用eval()

      此绑定保持不变; 它与调用执行上下文的ThisBinding具有相同的值。

    • ...如果不是直接调用eval()

      ThisBinding设置为全局对象就像在初始全局执行上下文中执行一样。

    基本上,eval(...)是一个直接调用,而类似(0, eval)(...)或者var indirectEval = eval; indirectEval(...);是间接调用eval()。JavaScript中看到chuckj(1,eval)('this')vs eval('this')的回答?这个博客由Dmitry Soshnikov,当你可能使用间接的eval()调用。

  3. 输入功能代码

    这在调用函数时发生。如果在一个对象上调用一个函数,例如in obj.myMethod()或等价函数obj["myMethod"](),那么ThisBinding被设置为对象(obj在例子中;§13.2.1)。在大多数其他情况下,ThisBinding设置为全局对象(§10.4.3)。

    编写“在大多数其他情况下”的原因是因为有八个ECMAScript 5内置函数,允许在参数列表中指定ThisBinding。这些特殊函数采用所谓的thisArg,它在调用函数(§10.4.3)时成为ThisBinding。

    这些特殊的内置函数有:

    • Function.prototype.apply(
      thisArg, argArray )
    • Function.prototype.call( thisArg [ , arg1 [ , arg2, ... ] ] )
    • Function.prototype.bind( thisArg [ , arg1 [ , arg2, ... ] ] )
    • Array.prototype.every( callbackfn [ , thisArg ] )
    • Array.prototype.some( callbackfn [ , thisArg ] )
    • Array.prototype.forEach( callbackfn [ , thisArg ] )
    • Array.prototype.map( callbackfn [ , thisArg ] )
    • Array.prototype.filter( callbackfn [ , thisArg ] )


    在Function.prototype函数的情况下,它们在函数对象上被调用,但是不是将ThisBinding设置为函数对象,而是将ThisBinding设置为thisArg

    在Array.prototype函数的情况下,给定的callbackfn在执行上下文中被调用,其中如果提供的话,ThisBinding设置为thisArg ; 否则,到全局对象。

这些是纯JavaScript的规则。当你开始使用JavaScript库(例如jQuery),你可能会发现某些库函数操作的值this这些JavaScript库的开发人员这样做是因为它倾向于支持最常见的用例,并且库的用户通常发现这种行为更方便。当传递引用this函数的回调函数时,您应参考文档,以了解在this调用函数时,什么的任何保证

如果你想知道一个JavaScript库如何操作的值this,该库只是使用一个内置的JavaScript函数接受一个thisArg你也可以编写自己的函数接受一个回调函数和thisArg

function doWork(callbackfn, thisArg) {
//...
if (callbackfn != null) callbackfn.call(thisArg);
}

编辑:

我忘了一个特殊情况。当通过new运算符构造一个新对象时,JavaScript解释器创建一个新的空对象,设置一些内部属性,然后调用新对象的构造函数。因此,当在构造器上下文中调用函数时,值this是解释器创建的新对象:

function MyType() {
this.someData = "a string";
}

var instance = new MyType();
// Kind of like the following, but there are more steps involved:
// var instance = {};
// MyType.call(instance);

this相对于其他语言JavaScript中关键字的行为不同。在面向对象语言中,this关键字引用类的当前实例。在JavaScript中,value的值this主要由函数(context.function()的调用上下文和调用它的地方确定。

1.在全局上下文中使用

this在全局上下文中使用时,它被绑定到全局对象(window在浏览器中)

document.write(this);  //[object Window]

当你使用this在全局上下文中定义的函数内部时,this它仍然绑定到全局对象,因为函数实际上是一个全局上下文的方法。

function f1()
{
return this;
}
document.write(f1()); //[object Window]

上面f1是一个全局对象的方法。因此我们也可以在window对象上调用它,如下所示:

function f()
{
return this;
}

document.write(window.f()); //[object Window]

2.当在对象方法内使用时

当您this在对象方法中使用关键字时,this会绑定到“立即”封闭对象。

var obj = {
name: "obj",
f: function () {
return this + ":" + this.name;
}
};
document.write(obj.f()); //[object Object]:obj

上面我把双引号中的单词立即。它是要指出,如果你嵌套在另一个对象内的对象,然后this绑定到直接父。

var obj = {
name: "obj1",
nestedobj: {
name:"nestedobj",
f: function () {
return this + ":" + this.name;
}
}
}

document.write(obj.nestedobj.f()); //[object Object]:nestedobj

即使你将函数显式地添加到对象作为方法,它仍然遵循上面的规则,这this仍然指向直接父对象。

var obj1 = {
name: "obj1",
}

function returnName() {
return this + ":" + this.name;
}

obj1.f = returnName; //add method to object
document.write(obj1.f()); //[object Object]:obj1

3.调用上下文无关函数

当你使用this没有任何上下文(即不在任何对象上)被调用的内部函数时,它绑定到全局对象(window在浏览器中)(即使函数在对象内部被定义)。

var context = "global";

var obj = {
context: "object",
method: function () {
function f() {
var context = "function";
return this + ":" +this.context;
};
return f(); //invoked without context
}
};

document.write(obj.method()); //[object Window]:global

尝试所有与功能

我们可以尝试以上点与功能。但是有一些差异。

  • 上面我们使用对象字面符号将成员添加到对象。我们可以通过使用来向函数添加成员this以指定它们。
  • 对象字面符号创建一个对象的实例,我们可以立即使用。使用函数,我们可能需要首先使用new运算符创建其实例
  • 同样在对象字面量方法中,我们可以使用点运算符向已定义的对象显式添加成员。此操作将仅添加到特定实例。然而,我已经添加了变量到函数原型,以便它反映在函数的所有实例。

4.当在构造函数中使用时

当函数用作构造函数时(即使用new关键字调用时),this函数体内部指向正在构建的新对象。

var myname = "global context";
function SimpleFun()
{
this.myname = "simple function";
}

var obj1 = new SimpleFun(); //adds myname to obj1
//1. `new` causes `this` inside the SimpleFun() to point to the
// object being constructed thus adding any member
// created inside SimipleFun() using this.membername to the
// object being constructed
//2. And by default `new` makes function to return newly
// constructed object if no explicit return value is specified

document.write(obj1.myname); //simple function

5.当在原型链上定义的函数内使用时

如果方法是在对象的原型链上,this在这种方法内部引用该方法被调用的对象,就像方法是在对象上定义的。

var ProtoObj = {
fun: function () {
return this.a;
}
};
//Object.create() creates object with ProtoObj as its
//prototype and assigns it to obj3, thus making fun()
//to be the method on its prototype chain

var obj3 = Object.create(ProtoObj);
obj3.a = 999;
document.write(obj3.fun()); //999

//Notice that fun() is defined on obj3's prototype but
//`this.a` inside fun() retrieves obj3.a

6.内部调用(),apply()和bind()函数

  • 所有这些方法都定义了Function.prototype
  • 这些方法允许写一个函数一次,并在不同的上下文中调用它。换句话说,它们允许指定在this执行函数时将使用的值它们还接受在调用时传递给原始函数的任何参数。
  • fun.apply(obj1 [, argsArray])设置obj1this内部的值,fun()并将fun()传递元素的调用argsArray作为其参数。
  • fun.call(obj1 [, arg1 [, arg2 [,arg3 [, ...]]]])- 设置obj1this内部的值fun()作为其参数fun()传递arg1, arg2, arg3, ...的调用。
  • fun.bind(obj1 [, arg1 [, arg2 [,arg3 [, ...]]]])- 返回函数的引用funthis内部有fun绑定到参数obj1fun绑定到指定的参数arg1, arg2, arg3,...
  • 由现在的差applycallbind必须变得明显。apply允许指定函数作为数组类对象的参数,即具有数字length属性和对应的非负整数属性的对象call允许直接指定函数的参数。双方applycall立即调用函数中指定的上下文和用指定的参数。另一方面,bind只返回绑定到指定this值和参数的函数。我们可以通过将它赋给一个变量来捕获对这个返回函数的引用,随后我们可以随时调用它。
function add(inc1, inc2){    return this.a + inc1 + inc2;}var o = { a : 4 };document.write(add.call(o, 5, 6)+"<br />"); //15      //above add.call(o,5,6) sets `this` inside      //add() to `o` and calls add() resulting:      // this.a + inc1 + inc2 =       // `o.a` i.e. 4 + 5 + 6 = 15document.write(add.apply(o, [5, 6]) + "<br />"); //15      // `o.a` i.e. 4 + 5 + 6 = 15var g = add.bind(o, 5, 6);       //g: `o.a` i.e. 4 + 5 + 6document.write(g()+"<br />");    //15var h = add.bind(o, 5);          //h: `o.a` i.e. 4 + 5 + ?document.write(h(6) + "<br />"); //15      // 4 + 5 + 6 = 15document.write(h() + "<br />");  //NaN      //no parameter is passed to h()      //thus inc2 inside add() is `undefined`      //4 + 5 + undefined = NaN</code>

7. this内部事件处理程序

  • 当您直接将函数分配给一个元素的事件处理程序时,this直接使用事件处理函数指的是相应的元素。这样的直接函数分配可以使用addeventListener方法或通过传统的事件注册方法来完成onclick
  • 类似地,当this直接在<button onclick="...this..." >元素的event属性(like 内部使用时,它引用元素。
  • 但是使用this间接通过其他函数调用的内部事件处理函数或事件属性解析为全局对象window
  • 当我们使用Microsoft的事件注册模型方法将函数附加到事件处理程序时,实现了相同的上述行为attachEvent代替将函数分配给事件处理程序(并且因此使得元素的函数方法),它调用事件上的函数(有效地在全局上下文中调用它)。
更多详见

http://www.digital-web.com/articles/scope_in_javascript/

http://www.quirksmode.org/js/this.html