换个思路理解Javascript中的this

简介:

在网上很多文章都对 Javascript 中的 this 做了详细的介绍,但大多是介绍各个绑定方式或调用方式下 this 的指向,于是我想有一个统一的思路来更好理解 this 指向,使大家更好判断,以下有部分内容不是原理,而是一种解题思路。

从call方法开始

call 方法允许切换函数执行的上下文环境(context),即 this 绑定的对象。

大多数介绍 this 的文章中都会把 call 方法放到最后介绍,但此文我们要把 call 方法放在第一位介绍,并从 call 方法切入来研究 this ,因为 call 函数是显式绑定 this 的指向,我们来看看它如何模拟实现(不考虑传入 null 、 undefined 和原始值):

 
  1. Function.prototype.call = function(thisArg) { 
  2.     var context = thisArg; 
  3.     var arr = []; 
  4.     var result; 
  5.  
  6.     context.fn = this; 
  7.  
  8.     for (let i = 1, len = arguments.length; i < len; i++) { 
  9.         arr.push('arguments[' + i + ']'); 
  10.     } 
  11.  
  12.     result = eval("context.fn(" + arr + ")"); 
  13.  
  14.     delete context.fn; 
  15.  
  16.     return result; 
  17. }  

从以上代码我们可以看到,把调用 call 方法的函数作为第一个参数对象的方法,此时相当于把第一个参数对象作为函数执行的上下文环境,而 this 是指向函数执行的上下文环境的,因此 this 就指向了第一个参数对象,实现了 call 方法切换函数执行上下文环境的功能。

对象方法中的this

在模拟 call 方法的时候,我们使用了对象方法来改变 this 的指向。调用对象中的方法时,会把对象作为方法的上下文环境来调用。

既然 this 是指向执行函数的上下文环境的,那我们先来研究一下调用函数时的执行上下文情况。

下面我门来看看调用对象方法时执行上下文是如何的:

 
  1. var foo = { 
  2.     x : 1, 
  3.     getX: function(){ 
  4.         console.log(this.x); 
  5.     } 
  6. foo.getX(); 

从上图中,我们可以看出getX方法的调用者的上下文是foo,因此getX方法中的 this 指向调用者上下文foo,转换成 call 方法为foo.getX.call(foo)。

下面我们把其他函数的调用方式都按调用对象方法的思路来转换。

构造函数中的this

 
  1. function Foo(){ 
  2.     this.x = 1; 
  3.     this.getX = function(){ 
  4.         console.log(this.x); 
  5.     } 
  6. var foo = new Foo(); 
  7. foo.getX();  

执行 new 如果不考虑原型链,只考虑上下文的切换,就相当于先创建一个空的对象,然后把这个空的对象作为构造函数的上下文,再去执行构造函数,最后返回这个对象。

 
  1. var newMethod = function(func){ 
  2.     var context = {}; 
  3.     func.call(context); 
  4.     return context; 
  5. function Foo(){ 
  6.     this.x = 1; 
  7.     this.getX = function(){ 
  8.         console.log(this.x); 
  9.     } 
  10. var foo = newMethod(Foo); 
  11. foo.getX(); 

DOM事件处理函数中的this

 
  1. DOMElement.addEventListener('click'function(){ 
  2.  
  3.   console.log(this); 
  4.  
  5. });  

把函数绑定到DOM事件时,可以当作在DOM上增加一个函数方法,当触发这个事件时调用DOM上对应的事件方法。

 
  1. DOMElement.clickHandle = function(){ 
  2.     console.log(this); 
  3. DOMElement.clickHandle(); 

普通函数中的this

 
  1. var x = 1; 
  2. function getX(){ 
  3.     console.log(this.x); 
  4. getX();  

这种情况下,我们创建一个虚拟上下文对象,然后普通函数作为这个虚拟上下文对象的方法调用,此时普通函数中的this就指向了这个虚拟上下文。

那这个虚拟上下文是什么呢?在非严格模式下是全局上下文,浏览器里是 window ,NodeJs里是 Global ;在严格模式下是 undefined 。

 
  1. var x = 1; 
  2. function getX(){ 
  3.     console.log(this.x); 
  4.  
  5. [viturl context].getX = getX; 
  6. [viturl context].getX(); 

闭包中的this

 
  1. var x = 1; 
  2. var foo = { 
  3.     x: 2, 
  4.     y: 3, 
  5.     getXY: function(){ 
  6.         (function(){ 
  7.             console.log("x:" + this.x); 
  8.             console.log("y:" + this.y);  
  9.         })(); 
  10.     } 
  11. foo.getXY();  

这段代码的上下文如下图:

这里需要注意的是,我们再研究函数中的 this 指向时,只需要关注 this 所在的函数是如何调用的, this 所在函数外的函数调用都是浮云,是不需要关注的。因此在所有的图示中,我们只需要关注红色框中的内容。

因此这段代码我们关注的部分只有:

 
  1. (function(){ 
  2.     console.log(this.x); 
  3. })();  

与普通函数调用一样,创建一个虚拟上下文对象,然后普通函数作为这个虚拟上下文对象的方法立即调用,匿名函数中的 this 也就指向了这个虚拟上下文。

参数中的this

 
  1. var x = 1; 
  2. var foo = { 
  3.     x: 2, 
  4.     getX: function(){ 
  5.         console.log(this.x); 
  6.     } 
  7. setTimeout(foo.getX, 1000);  

函数参数是值传递的,因此上面代码等同于以下代码:

 
  1. var getX = function(){ 
  2.     console.log(this.x); 
  3. }; 
  4. setTimeout(getX, 1000);  

然后我们又回到了普通函数调用的问题。

全局中的this

全局中的 this 指向全局的上下文

 
  1. var x = 1; 
  2. console.log(this.x); 

复杂情况下的this

 
  1. var x = 1; 
  2. var a = { 
  3.     x: 2, 
  4.     b: function(){ 
  5.         return function(){ 
  6.             return function foo(){ 
  7.                 console.log(this.x); 
  8.             }         
  9.         } 
  10.     } 
  11. }; 
  12.  
  13. (function(){ 
  14.     var x = 3; 
  15.     a.b()()(); 
  16. })();  

看到上面的情况是有很多个函数,但我们只需要关注 this 所在函数的调用方式,首先我们来简化一下如下:

 
  1. var x = 1; 
  2. (function(){ 
  3.     var x = 3; 
  4.     var foo = function(){ 
  5.         console.log(this.x); 
  6.     } 
  7.     foo(); 
  8. });  

this 所在的函数 foo 是个普通函数,我们创建一个虚拟上下文对象,然后普通函数作为这个虚拟上下文对象的方法立即调用。因此这个 this指向了这个虚拟上下文。在非严格模式下是全局上下文,浏览器里是 window ,NodeJs里是 Global ;在严格模式下是 undefined 。

总结

在需要判断 this 的指向时,我们可以安装这种思路来理解:

  • 判断 this 在全局中OR函数中,若在全局中则 this 指向全局,若在函数中则只关注这个函数并继续判断。
  • 判断 this 所在函数是否作为对象方法调用,若是则 this 指向这个对象,否则继续操作。
  • 创建一个虚拟上下文,并把this所在函数作为这个虚拟上下文的方法,此时 this 指向这个虚拟上下文。
  • 在非严格模式下虚拟上下文是全局上下文,浏览器里是 window ,Node.js里是 Global ;在严格模式下是 undefined 。

图示如下:



作者:佚名

来源:51CTO

相关文章
|
1月前
|
JavaScript 前端开发
javascript中的this
javascript中的this
|
3月前
|
JavaScript 前端开发
错综复杂的this:理清你的JavaScript代码中的指向问题
错综复杂的this:理清你的JavaScript代码中的指向问题
|
1月前
|
JavaScript
JS中改变this指向的六种方法
JS中改变this指向的六种方法
|
6月前
|
JavaScript 前端开发
详解js中的this指向
详解js中的this指向
43 0
|
6月前
|
前端开发 JavaScript
前端基础 - JavaScript之this关键字
前端基础 - JavaScript之this关键字
25 0
|
1月前
|
JavaScript 前端开发
js开发:请解释this关键字在JavaScript中的用法。
JavaScript中的`this`关键字根据执行上下文指向不同对象:全局作用域中指向全局对象(如`window`),普通函数中默认指向全局对象,但作为对象方法时指向该对象。在构造函数中,`this`指向新实例。箭头函数不绑定`this`,而是继承上下文的`this`值。可通过`call`、`apply`、`bind`方法显式改变`this`指向。
10 2
|
1月前
|
JavaScript
JS中call()、apply()、bind()改变this指向的原理
JS中call()、apply()、bind()改变this指向的原理
|
2月前
|
JavaScript 前端开发
JavaScript中this的指向问题
JavaScript中this的指向问题
|
3月前
|
前端开发 JavaScript
揭开`this`的神秘面纱:探索 JavaScript 中的上下文密钥(下)
揭开`this`的神秘面纱:探索 JavaScript 中的上下文密钥(下)
|
3月前
|
前端开发 JavaScript
揭开`this`的神秘面纱:探索 JavaScript 中的上下文密钥(上)
揭开`this`的神秘面纱:探索 JavaScript 中的上下文密钥(上)