http://javascriptissexy.com/understand-javascripts-this-with-clarity-and-master-it/

https://www.bennadel.com/blog/2265-changing-the-execution-context-of-javascript-functions-using-call-and-apply.htm

this关键字对于javascript初学者,即便是老手可能都是一个比较容易搞晕的东西。本文试图理顺这个问题。

this和自然语言的类比

实际上js中的this和我们自然语言中的代词有类似性。比如英语中我们写"John is running fast because he is trying to catch the train"

注意上面的代词"he",我们当然可以这样写:"John is running fast because John is trying to catch the train" ,这种情况下我们没有使用this去重用代替John。

在js中this关键字就是一个引用的shortcut,他指代一个object,是执行的代码body的context环境。看下面的代码:

var person = {
firstName: "Penelope",
lastName: "Barrymore",
fullName: function () {
​// Notice we use "this" just as we used "he" in the example sentence earlier?:
console.log(this.firstName + " " + this.lastName);
​// We could have also written this:​
console.log(person.firstName + " " + person.lastName);
}
}

如果我们使用person.firstName,person.lastName的话,我们的代码可能会产生歧义。比如,如果有一个全局变量,名字就是person,那么person.firstName可能试图从全局的person变量来访问其属性,这可能导致错误,并且难以调试。因此,我们使用this关键字,不仅为了代码更美(作为以个referent),而且为了更加精确而不会产生歧义。类似于刚刚举例的自然语言中,因为代词"he"使得句意更清晰,因为更加清晰地表明我们正在引用一个特定的John这个人。

正如代词"he"用于引用存于context中无歧义的名词一样,this关键字用于引用一个对象,而这个对象就是function(在该函数中,使用了this指针)所绑定的对象.(the this keyword is similarly used to refer to an object that the function(where this is used) is bound to.) t

this基础

什么是执行上下文(execution context)?

在js中,所有的函数体(function body)都能访问this关键字. this keyword就是函数执行的context.默认情况下,this引用着调用该函数的那个对象(也就是thisObj.thisFunction中的thisObj)(this is a reference to the object on which a particular function is called),在js中,所有的函数都会被绑定到一个object.然而,我们可以使用call(), apply()来在运行时变更这种binding关系。

首先,我们需要澄清的是:js中所有的函数实际上都是"methods".也就是说所有的function都是某个对象的属性。虽然我们可以定义看起来像是独立自由的函数,但是实际上这时候,这些含糊被隐式地被创建为window object的属性properties(在node环境中是其他的object,可能是process)。这也就意味着,即使我们创建一个自由独立的function而没有显然指定其context,我们也可以以window属性的方式来调用该函数。

// Define the free-floating function.
function someMethod(){ ... } // Access it as a property of Window.
window.someMethod();

既然我们知道了js的所有函数度作为一个对象的属性而存在,我们可以具体探讨下执行上下文的默认绑定这个概念了。默认情况下,一个js函数在该函数所属的对象的上下文中执行(js function is executed in the context of the object for which it is a property).也就是说,在函数body中,this关键词就是对父亲对象的引用,看看下面的代码:

//  "this" keyword within the sayHello() method is a reference to the sarah object
sarah.sayHello();
// "this" keyword within the getScreenResolution() function is a reference to the window object (since unbound functions are implicitly bound to the global scope)
getScreenResolution();

以上是默认的情况,除此之外,js提供了一种变更任何一个method的执行上下文的机制: call()或者apply().这两个函数都能用于绑定"this"关键字到一个明确的context(object)上去。

method.call( newThisContext, Param1, ..., Param N )
method.apply( newThisContext, [ Param1, ..., Param N ] );

再看一个复杂一点的代码案例:

<!DOCTYPE html>
<html>
<head>
<title>Changing Execution Context In JavaScript</title> <script type="text/javascript">
// Create a global variable for context (this lives in the
// global scope - window).
var context = "Global (ie. window)";
// Create an object.
var objectA = {
context: "Object A"
};
// Create another object.
var objectB = {
context: "Object B"
};
// -------------------------------------------------- //
// -------------------------------------------------- //
// Define a function that uses an argument AND a reference
// to this THIS scope. We will be invoking this function
// using a variety of approaches.
function testContext( approach ){
console.log( approach, "==> THIS ==>", this.context );
}
// -------------------------------------------------- //
// -------------------------------------------------- //
// Invoke the unbound method with standard invocation.
testContext( "testContext()" );
// Invoke it in the context of Object A using call().
testContext.call(
objectA,
".call( objectA )"
);
// Invoke it in the context of Object B using apply().
testContext.apply(
objectB,
[ ".apply( objectB )" ]
);
// -------------------------------------------------- //
// -------------------------------------------------- //
// Now, let's set the test method as an actual property
// of the object A.
objectA.testContext = testContext;
// -------------------------------------------------- //
// -------------------------------------------------- //
// Invoke it as a property of object A.
objectA.testContext( "objectA.testContext()" );
// Invoke it in the context of Object B using call.
objectA.testContext.call(
objectB,
"objectA.testContext.call( objectB )"
);
// Invoke it in the context of Window using apply.
objectA.testContext.apply(
window,
[ "objectA.testContext.apply( window )" ]
);
</script>
</head>
<body>
<!-- Left intentionally blank. -->
</body>
</html>

以上代码的输出如下:

testContext() ==> THIS ==> Global (ie. window)
.call( objectA ) ==> THIS ==> Object A
.apply( objectB ) ==> THIS ==> Object B
objectA.testContext() ==> THIS ==> Object A
objectA.testContext.call( objectB ) ==> THIS ==> Object B
objectA.testContext.apply( window ) ==> THIS ==> Global (ie. window)

为什么this是undefined?

(function () {
"use strict"; this.foo = "bar"; // *this* is undefined, why?
}());
function myConstructor() {
this.a = 'foo';
this.b = 'bar';
} myInstance = new myConstructor(); // all cool, all fine. a and b were created in a new local object
// 如果是strict mode, 则显示 "TypeError: this is undefined"
myBadInstance = myConstructor(); // oh my gosh, we just created a, and b on the window object

在js中有一种所谓沙盒模型"boxing" mechanism. 这个盒子在进入被调用函数执行上下文之前将包裹或者变更this object.在匿名函数中,由于在strict mode下并未以obj.method方式来调用匿名函数,因此this就为undefined(原因是匿名函数就是一个闭包,其作用就是隔离了global scope,因此不会默认到window.method上去).而在非strict mode下则this指向window.

function Request(destination, stay_open) {
this.state = "ready";
this.xhr = null;
this.destination = destination;
this.stay_open = stay_open; this.open = function(data) {
this.xhr = $.ajax({
url: destination,
success: this.handle_response,
error: this.handle_failure,
timeout: 100000000,
data: data,
dataType: 'json',
});
}; /* snip... */ } Request.prototype.start = function() {
if( this.stay_open == true ) {
this.open({msg: 'listen'});
} else { }
};
var o = new Request(destination, stay_open);
o.start()

this object is not set based on declaration, but by invocation. What it means is that if you assign the function property to a variable like x = o.start and call x()this inside start no longer refers to o

var o = new Request(...);
setTimeout(function() { o.start(); }, 1000);

另一个角度来看this-functionObj.this

首先,需要知道的是js中的所有函数都有peroperties,就像objects都有properties一样,因为function本身也是一个object对象。this可以看作是this-functionObj的属性,并且只有当该函数执行时,该函数对象的this属性将会被赋值,"it gets the this property ---- a variable with the value of the object that invokes the function where this is used"

this总是指向或者说引用(并包含了对应的value)一个对象,并且this往往在一个function或者说method中来使用。注意:虽然在global scope中我们可以不在function body中使用this,而是直接在global scope中使用this(实际上指向了window),但是如果我们在strict mode的话,在global function中,或者说没有绑定任何object的匿名函数中,如果使用this, 那么这个this将是undefined值.

假设this在一个function A中被使用,那么this就将引用着调用 function A的那个对象。我们需要这个this来访问调用function A对象的method和property.特别地,有些情况下我们不知道调用者对象的名称,甚至有时候调用者对象根本没有名字,这时就必须用this关键字了!

    var person = {
firstName :"Penelope",
lastName :"Barrymore",
// Since the "this" keyword is used inside the showFullName method below, and the showFullName method is defined on the person object,
// "this" will have the value of the person object because the person object will invoke showFullName ()
showFullName:function () {
console.log (this.firstName + " " + this.lastName);
} }
person.showFullName (); // Penelope Barrymore

再看一个jquery事件处理函数中使用this关键字的常见例子:

    // A very common piece of jQuery code

    $ ("button").click (function (event) {
// $(this) will have the value of the button ($("button")) object
// because the button object invokes the click () method, this指向button
console.log ($ (this).prop ("name"));
});

The use of $(this), which is jQuery’s syntax for the this keyword in JavaScript, is used inside an anonymous function, and the anonymous function is executed in the button’s click () method. The reason $(this) is bound to the button object is because the jQuery library binds$(this) to the object that invokes the click method. Therefore, $(this) will have the value of the jQuery button ($(“button”)) object, even though $(this) is defined inside an anonymous function that cannot itself access the “this” variable on the outer function.

深入一步理解this

我们先抛出一个心法: this不会有value,直到一个object invoke了这个函数(this在这个函数中使用).为了行文方便,我们将使用this关键字的函数称为thisFunction.

虽然默认情况下,this都会引用定义了this(也就是有this引用)的对象,但是只到一个对象调用了thisFunction,这个this指针才会被赋值。而这个this value只决定于调用了thisFunction的对象。尽管默认情况下this的值就是invoking ojbect(xxObj.thisFunction),但是我们也可以通过xxObj.thisFunction.call(yyObj,parameters), apply()等方式来修改this的默认值!~

在global scope中使用this

在global scope中,当代码在浏览器中执行时,所有的全局variable和function都被定义在window object上,因此,在一个全局函数中当使用this时,this是引用了全局的window对象的(注意必须是非stric mode哦),而window对象则是整个js应用或者说web page的容器

最让人头疼和误解的this使用场景

有以下几个场景,this会变得非常易于令人误解:

1.当我们借用一个使用了this的方法method;

2.当我们将使用了this的method给到一个变量时;

3.当一个使用了this的函数被作为回调函数参数时;

4.当在一个闭包closure里面的函数中使用this时

下面我们将一一探讨这些情况下this的正确取值是什么

继续下文前,再聊一聊"context"

javascript中context的概念和自然语言中的主语有类似性。“John is the winner who returned the money”.本句的主语是John, 我们可以说本剧的context上下文就是John,因为本句此时的焦点就在他身上,甚至who这个代词指代的也是前面的这个主语John.正如我们可以通过使用一个分号   "  ; " 来更改句子的主语一样,我们也可以通过使用另外一个object来调用这个function,从而改变context.

var person = {
firstName :"Penelope",
lastName :"Barrymore",
showFullName:function () {
// The "context"
console.log (this.firstName + " " + this.lastName);
}
} // The "context", when invoking showFullName, is the person object, when we invoke the showFullName () method on the person object.
// And the use of "this" inside the showFullName() method has the value of the person object,
person.showFullName (); // Penelope Barrymore // If we invoke showFullName with a different object:
var anotherPerson = {
firstName :"Rohit",
lastName :"Khan"
}; // We can use the apply method to set the "this" value explicitly—more on the apply () method later.
// "this" gets the value of whichever object invokes the "this" Function, hence:
person.showFullName.apply (anotherPerson); // Rohit Khan // So the context is now anotherPerson because anotherPerson invoked the person.showFullName () method by virtue of using the apply () method

1. 当方法作为callback方式的传入时,如何fix住里面的this值

    // We have a simple object with a clickHandler method that we want to use when a button on the page is clicked
var user = {
data:[
{name:"T. Woods", age:37},
{name:"P. Mickelson", age:43}
],
clickHandler:function (event) {
var randomNum = ((Math.random () * 2 | 0) + 1) - 1; // random number between 0 and 1 // This line is printing a random person's name and age from the data array
console.log (this.data[randomNum].name + " " + this.data[randomNum].age);
}
} // The button is wrapped inside a jQuery $ wrapper, so it is now a jQuery object
// And the output will be undefined because there is no data property on the button object
$ ("button").click (user.clickHandler); // Cannot read property '0' of undefined

在上面的代码中,($('button'))自己是一个对象,我们将user.clickHandler method方法作为callback函数参数传入该jquery对象的click()方法中,我们知道user.clickHandler()中的this不再指向user对象了。this将指向user.clickMethod运行地所在的对象--因为this在user.clickHandler方法中定义。而invoking这个user.Handler方法的对象则是button object,---user.clickHandler将在button对象的click方法中被执行。

需要说明的是即使我们通过user.clickHandler()方式来调用(实际上我们也必须这么做,因为clickHandler本身就作为user的一个method来定义的,因此必须这么去调用), clickHandler()方法也将以button对象作为上下文去执行,也就是说this现在将指向这个button context对象($('button')).

到这里,我们可以下一个结论:

At this point, it should be apparent that when the context changes—when we execute a method on some other object than where the object was originally defined, the this keyword no longer refers to the original object where “this” was originally defined, but it now refers to the object that invokes the method where this was defined.

如何能解决这类问题,并且fix住this指向呢?

在上面的例子中,既然我们总是希望this.data就是指向到user object的data属性,我们可以使用Bind(),Apply()或者Call()方法去特别设定this的value.

 $ ("button").click (user.clickHandler); // 这个clickHandler this指向button jquery对象
$("button").click (user.clickHandler.bind (user)); // P. Mickelson 43 通过bind指定这个clickHandler中的this就是指user

2. 如何在一个闭包的inner function中(或匿名函数)fix住this的值

正如上面提及,当我们使用一个inner method(a closure)时,this也是非常容易搞混淆的。非常重要一点是:closures闭包不能访问外部函数(outer function)的this值,因为this变量只能由函数本身来访问,而不是inner function(的this)

var user = {
tournament:"The Masters",
data :[
{name:"T. Woods", age:37},
{name:"P. Mickelson", age:43}
], clickHandler:function () {
// the use of this.data here is fine, because "this" refers to the user object, and data is a property on the user object. this.data.forEach (function (person) {
// But here inside the anonymous function (that we pass to the forEach method), "this" no longer refers to the user object.
// This inner function cannot access the outer function's "this" console.log ("What is This referring to? " + this); //[object Window] console.log (person.name + " is playing at " + this.tournament);
// T. Woods is playing at undefined
// P. Mickelson is playing at undefined
})
} }
user.clickHandler(); // What is "this" referring to? [object Window]

上面代码中,由于匿名函数中的this不能访问外部函数的this,因此当在非strict mode时,this将绑定到global window对象。

同样地,也可以对匿名函数调用bind来fix住this

var object = {

  property: function() {

    this.id = 'abc'; // 'this' binds to the object

    aFunctionWithCallback(this.id, function(data) {
console.log(this); // null
});
aFunctionWithCallbackOK(this.id, function(data) {
console.log(this);
}.bind(this));
}
};

维护在匿名函数中使用的this值方案:

var user = {
tournament:"The Masters",
data :[
{name:"T. Woods", age:37},
{name:"P. Mickelson", age:43}
],
clickHandler:function (event) {
// To capture the value of "this" when it refers to the user object, we have to set it to another variable here:
// We set the value of "this" to theUserObj variable, so we can use it later
var that = theUserObj = this;
this.data.forEach (function (person) {
// Instead of using this.tournament, we now use theUserObj.tournament
console.log (person.name + " is playing at " + theUserObj.tournament);
})
}
}
user.clickHandler();
// T. Woods is playing at The Masters
// P. Mickelson is playing at The Masters

3. 当method被赋值给一个变量时,如何fix住this指向

// This data variable is a global variable
var data = [
{name:"Samantha", age:12},
{name:"Alexis", age:14}
]; var user = {
// this data variable is a property on the user object
data :[
{name:"T. Woods", age:37},
{name:"P. Mickelson", age:43}
],
showData:function (event) {
var randomNum = ((Math.random () * 2 | 0) + 1) - 1; // random number between 0 and 1 // This line is adding a random person from the data array to the text field
console.log (this.data[randomNum].name + " " + this.data[randomNum].age);
} } // Assign the user.showData to a variable
var showUserData = user.showData; // When we execute the showUserData function, the values printed to the console are from the global data array, not from the data array in the user object
//
showUserData (); // Samantha 12 (from the global data array)

解决方案是特别地通过使用bind方法来指定this值

 // Bind the showData method to the user object
var showUserData = user.showData.bind (user); // Now we get the value from the user object, because the this keyword is bound to the user object
showUserData (); // P. Mickelson 43

4. 当借用一个定义了this的method方法时

在js开发中,借用方法是一个非常常见的实例,作为js开发者,我们一定会经常遇到。

// We have two objects. One of them has a method called avg () that the other doesn't have
// So we will borrow the (avg()) method
var gameController = {
scores :[20, 34, 55, 46, 77],
avgScore:null,
players :[
{name:"Tommy", playerID:987, age:23},
{name:"Pau", playerID:87, age:33}
]
} var appController = {
scores :[900, 845, 809, 950],
avgScore:null,
avg :function () { var sumOfScores = this.scores.reduce (function (prev, cur, index, array) {
return prev + cur;
}); this.avgScore = sumOfScores / this.scores.length;
}
}
//If we run the code below,
// the gameController.avgScore property will be set to the average score from the appController object "scores" array
// Don't run this code, for it is just for illustration; we want the appController.avgScore to remain null
gameController.avgScore = appController.avg();

avg方法的"this"不再指向gameController object,它将指向到appController对象,因为该avg()方法是在appController对象上执行的。

解决方法:

// Note that we are using the apply () method, so the 2nd argument has to be an array—the arguments to pass to the appController.avg () method.
appController.avg.apply (gameController, gameController.scores); // The avgScore property was successfully set on the gameController object, even though we borrowed the avg () method from the appController object
console.log (gameController.avgScore); // 46.4 // appController.avgScore is still null; it was not updated, only gameController.avgScore was updated
console.log (appController.avgScore); // null

gameController对象借用了appController's avg()方法,在appController.avg()中的this value会被设定为gameContrller对象,因为我们使用了apply()方法。

最后,需要牢记:

Always remember that this is assigned the value of the object that invoked the this Function

彻底理解javascript中的this指针的更多相关文章

  1. 深入理解JavaScript中创建对象模式的演变(原型)

    深入理解JavaScript中创建对象模式的演变(原型) 创建对象的模式多种多样,但是各种模式又有怎样的利弊呢?有没有一种最为完美的模式呢?下面我将就以下几个方面来分析创建对象的几种模式: Objec ...

  2. 深入认识JavaScript 中的this指针

    深入认识JavaScript 中的this指针this指针是面向对象程序设计中的一项重要概念,它表示当前运行的对象.在实现对象的方法时,可以使用this指针来获得该对象自身的引用.和传统意义的面向对象 ...

  3. 转载 深入理解JavaScript中的this关键字

    转载原地址: http://www.cnblogs.com/rainman/archive/2009/05/03/1448392.html 深入理解JavaScript中的this关键字   1. 一 ...

  4. 理解javascript中的回调函数(callback)【转】

    在JavaScrip中,function是内置的类对象,也就是说它是一种类型的对象,可以和其它String.Array.Number.Object类的对象一样用于内置对象的管理.因为function实 ...

  5. 理解JavaScript中的原型继承(2)

    两年前在我学习JavaScript的时候我就写过两篇关于原型继承的博客: 理解JavaScript中原型继承 JavaScript中的原型继承 这两篇博客讲的都是原型的使用,其中一篇还有我学习时的错误 ...

  6. 深入理解JavaScript中的属性和特性

    深入理解JavaScript中的属性和特性 JavaScript中属性和特性是完全不同的两个概念,这里我将根据自己所学,来深入理解JavaScript中的属性和特性. 主要内容如下: 理解JavaSc ...

  7. 深入理解javascript中执行环境(作用域)与作用域链

    深入理解javascript中执行环境(作用域)与作用域链 相信很多初学者对与javascript中的执行环境与作用域链不能很好的理解,这里,我会按照自己的理解同大家一起分享. 一般情况下,我们把执行 ...

  8. 【干货理解】理解javascript中实现MVC的原理

    理解javascript中的MVC MVC模式是软件工程中一种软件架构模式,一般把软件模式分为三部分,模型(Model)+视图(View)+控制器(Controller); 模型:模型用于封装与应用程 ...

  9. 理解javascript中的策略模式

    理解javascript中的策略模式 策略模式的定义是:定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换. 使用策略模式的优点如下: 优点:1. 策略模式利用组合,委托等技术和思想,有效 ...

随机推荐

  1. Python实现斐波那契数列,九九乘法表,金字塔方法。

    斐波那契数列普通函数实现 #普通函数 def fb(max): a,b=0,1 while a<max: print(a) a,b=b,a+b fb(100) 递归实现方法1 def fb1(m ...

  2. Codeforces A. Playlist(暴力剪枝)

    题目描述: Playlist time limit per test 2 seconds memory limit per test 256 megabytes input standard inpu ...

  3. 使用async进行结构化并发程序开发

    异步风格的函数: 继续来学习async相关的东东,对于它其实可以用到函数上,也就是用它可以定义一个异步风格的函数,然后在该函数中再来调用普通的函数,下面来瞅一下: 其实“GlobalScope.asy ...

  4. windows下apache + mod_wsgi + python部署flask接口服务

    windows下apache + mod_wsgi + python部署flask接口服务 用python3安装虚拟环境 为啥要装虚拟环境? 原因1:安装虚拟环境是为了使项目的环境和全局环境隔离开,在 ...

  5. wordpress添加post_type自定义文章类型

    wordpress很强大,能当博客也能进行二次开发出很完善的内容管理系统满足企业运营需求,比如可以添加products产品模型.汽车模型等,如何实现呢?添加post_type自定义文章类型就可以了 p ...

  6. IE6兼容性bug汇总

    1.终极方法:条件注释 <!--[if lte IE 6]> 这段文字仅显示在 IE6及IE6以下版本. <![endif]--> <!--[if gte IE 6]&g ...

  7. ESA2GJK1DH1K升级篇: 远程升级准备工作: 安装Web服务器

    前言 大家可以安装Apache,Tomcat,nginx 等Web服务器软件,这篇文章安装 OpenResty 作为Web服务器软件,该软件安装在云端电脑,如果想 安装到自己本地电脑实现该功能,可使用 ...

  8. SDSC 2018 day2解题报告

    目录 10.12考试总结 T1 最近公共祖先 错误原因 T2 即时战略 T3 欧皇 10.12考试总结 T1 最近公共祖先 预估得分: 100 实际得分: 20 最大得分: 100 用时:1小时10分 ...

  9. Eclipse启动发生的错误:An internal error occurred during: "Initializing Java Tooling".

    1.启动Eclipse时,初始化异常:An internal error occurred during: "Initializing Java Tooling". 解决方案:wi ...

  10. linux quota磁盘限额,引发的rename系统调用 errno:18 - Invalid cross-device link

    起因: log4j日志滚动失败,debug发现jvm调用native方法rename失败,也就是系统调用rename失败. 自己写c程序系统调用rename,证实确实是这个问题. 日志打在容器里,日志 ...