js的this指向总结

一、说明

首先必须要说的是,其实this就是一个指针,它指示的就是当前的一个执行环境,可以用来对当前执行环境进行一些操作。
this的指向在函数定义的时候是确定不了的,只有函数执行的时候才能确定this到底指向谁,实际上this的最终指向的是那个调用它的对象(这句话有些问题,后面会解释为什么会有问题,虽然网上大部分的文章都是这样说的,虽然在很多情况下那样去理解不会出什么问题,但是实际上那样理解是不准确的,所以在你理解this的时候会有种琢磨不透的感觉),那么接下来我会深入的探讨这个问题。

一、全局环境下

在全局环境下,this 始终指向全局对象(window), 无论是否严格模式;

console.log(this.document === document); // true

// 在浏览器中,全局对象为 window 对象:
console.log(this === window); // true

this.a = 37;
console.log(window.a); // 37

二、函数上下文调用

1、普通函数调用

1.1、 情况说明:

如果一个函数中有this,但是它没有被上一级的对象所调用,那么this指向的就是window,这里需要说明的是在js的严格版中this指向的不是window,但是我们这里不探讨严格版的问题,你想了解可以自行上网查找。

非严格模式下,this 默认指向全局对象window

function f1(){
    
    
  return this;
}

f1() === window; // true
而严格模式下, thisundefined

function f2(){
    
    
  "use strict"; // 这里是严格模式
  return this;
}

f2() === undefined; // true

例子1

function a(){
    
    
    var user = "追梦子";
    console.log(this.user); //undefined
    console.log(this); //Window
}
a();

按照我们上面说的this最终指向的是调用它的对象,这里的函数a实际是被Window对象所点出来的,下面的代码就可以证明。

function a(){
    
    
    var user = "追梦子";
    console.log(this.user); //undefined
    console.log(this);  //Window
}
window.a();

和上面代码一样吧,其实alert也是window的一个属性,也是window点出来的

2、作为方法来调用

2.1、情况1

如果一个函数中有this,这个函数有被上一级的对象所调用,那么this指向的就是上一级的对象
例子2:

var o = {
    
    
    user:"追梦子",
    fn:function(){
    
    
        console.log(this.user);  //追梦子
    }
}
o.fn();

这里的this指向的是对象o,因为你调用这个fn是通过o.fn()执行的,那自然指向就是对象o,这里再次强调一点,this的指向在函数创建的时候是决定不了的,在调用的时候才能决定,谁调用的就指向谁,一定要搞清楚这个。

2.2、情况2

如果一个函数中有this,这个函数中包含多个对象,尽管这个函数是被最外层的对象所调用,this指向的也只是它上一级的对象,例子3可以证明,如果不相信,那么接下来我们继续看几个例子。

其实例子1和例子2说的并不够准确,下面这个例子就可以推翻上面的理论。

如果要彻底的搞懂this必须看接下来的几个例子

var o = {
    
    
    user:"追梦子",
    fn:function(){
    
    
        console.log(this.user); //追梦子
    }
}
window.o.fn();

这段代码和上面的那段代码几乎是一样的,但是这里的this为什么不是指向window,如果按照上面的理论,最终this指向的是调用它的对象,这里先说个而外话,window是js中的全局对象,我们创建的变量实际上是给window添加属性,所以这里可以用window点o对象。

这里先不解释为什么上面的那段代码this为什么没有指向window,我们再来看一段代码。

var o = {
    
    
    a:10,
    b:{
    
    
        a:12,
        fn:function(){
    
    
            console.log(this.a); //12
        }
    }
}
o.b.fn();

这里同样也是对象o点出来的,但是同样this并没有执行它,那你肯定会说我一开始说的那些不就都是错误的吗?其实也不是,只是一开始说的不准确,接下来我将补充一句话,我相信你就可以彻底的理解this的指向的问题。

var o = {
    
    
    a:10,
    b:{
    
    
        // a:12,
        fn:function(){
    
    
            console.log(this.a); //undefined
        }
    }
}
o.b.fn();

尽管对象b中没有属性a,这个this指向的也是对象b,因为this只会指向它的上一级对象,不管这个对象中有没有this要的东西。

2.3、 情况3

this永远指向的是最后调用它的对象,也就是看它执行的时候是谁调用的

例子4:

var o = {
    
    
    a:10,
    b:{
    
    
        a:12,
        fn:function(){
    
    
            console.log(this.a); //undefined
            console.log(this); //window
        }
    }
}
var j = o.b.fn;
j();

这里this指向的是window,是不是有些蒙了?其实是因为你没有理解一句话,这句话同样至关重要。

this永远指向的是最后调用它的对象,也就是看它执行的时候是谁调用的,例子4中虽然函数fn是被对象b所引用,但是在将fn赋值给变量j的时候并没有执行所以最终指向的是window,这和例子3是不一样的,例子3是直接执行了fn。

this讲来讲去其实就是那么一回事,只不过在不同的情况下指向的会有些不同,上面的总结每个地方都有些小错误,也不能说是错误,而是在不同环境下情况就会有不同,所以我也没有办法一次解释清楚,只能你慢慢地的去体会。

3、作为构造函数来调用

3.1、情况1

构造函数版this,new关键字可以改变this的指向

function Fn(){
    
    
    this.user = "追梦子";
}
var a = new Fn();
console.log(a.user); //追梦子

这里之所以对象a可以点出函数Fn里面的user是因为new关键字可以改变this的指向,将这个this指向对象a,为什么我说a是对象,因为用了new关键字就是创建一个对象实例,理解这句话可以想想我们的例子3,我们这里用变量a创建了一个Fn的实例(相当于复制了一份Fn到对象a里面),此时仅仅只是创建,并没有执行,而调用这个函数Fn的是对象a,那么this指向的自然是对象a,那么为什么对象a中会有user,因为你已经复制了一份Fn函数到对象a中,用了new关键字就等同于复制了一份。

3.2、情况2

当this碰到return时,如果返回值是一个对象,那么this指向的就是那个返回的对象,如果返回值不是一个对象那么this还是指向函数的实例。

function fn()  
{
    
      
    this.user = '追梦子';  
    return {
    
    };  
}
var a = new fn;  
console.log(a.user); //undefined

再看一个

function fn()  
{
    
      
    this.user = '追梦子';  
    return function(){
    
    };
}
var a = new fn;  
console.log(a.user); //undefined

再来

function fn()  
{
    
      
    this.user = '追梦子';  
    return 1;
}
var a = new fn;  
console.log(a.user); //追梦子
function fn()  
{
    
      
    this.user = '追梦子';  
    return undefined;
}
var a = new fn;  
console.log(a.user); //追梦子

什么意思呢?

如果返回值是一个对象,那么this指向的就是那个返回的对象,如果返回值不是一个对象那么this还是指向函数的实例。

还有一点就是虽然null也是对象,但是在这里this还是指向那个函数的实例,因为null比较特殊。

function fn()  
{
    
      
    this.user = '追梦子';  
    return null;
}
var a = new fn;  
console.log(a.user); //追梦子

知识点补充:
1.在严格版中的默认的this不再是window,而是undefined。

2.new操作符会改变函数this的指向问题,虽然我们上面讲解过了,但是并没有深入的讨论这个问题,网上也很少说,所以在这里有必要说一下。

function fn(){
    
    
    this.num = 1;
}
var a = new fn();
console.log(a.num); //1

为什么this会指向a?首先new关键字会创建一个空的对象,然后会自动调用一个函数apply方法,将this指向这个空对象,这样的话函数内部的this就会被这个空的对象替代。

4、call,apply,bind

除了上面的这些以外,我们还可以自行改变this的指向,JavaScript中call,apply,bind方法

例子1

box.onclick = function(){
    
    
  function fn(){
    
    
    alert(this); //window
  }
  fn();
};

我们原本以为这里面的this指向的是box,然而却是Window。一般我们这样解决:

box.onclick = function(){
    
    
  var _this = this;
  function fn(){
    
    
    alert(_this);
  }
  fn();
};

将this保存下来。

还有一些情况,有时我们想让伪数组也能够调用数组的一些方法,这时call、apply、bind就派上用场了。

我们先来解决第一个问题修复this指向。

box.onclick = function(){
    
    
  function fn(){
    
    
    alert(this);
  }
  fn();
};

改成如下:

box.onclick = function(){
    
    
  function fn(){
    
    
    console.log(this);
  }
  fn.call(this);
};

很神奇吧,call的作用就是改变this的指向的,第一个参数传的是一个对象,就是你要借用的那个对象。

这里的意思是让this去调用fn这个函数,这里的this是box,这个没有意见吧?如果这个你不清楚,很可能你是javscript的新朋友。box调用fn,这句话非常重要,我们知道this它始终指向一个对象,刚好box就是一个对象。那么fn里面的this就是box。

box.onclick = function(){
    
    
  function fn(){
    
    
    console.log(this);
  }
  fn.call(this);
};

 

这句话在某些情况下是可以简写的,比如:

box.onclick = function(){
    
    
  var fn = function(){
    
    
    console.log(this); //box
  }.call(this);
};

或者这样:

box.onclick = function(){
    
    
  (function(){
    
    
    console.log(this);
  }.call(this)); //box
};

又或者这样:

var objName = {
    
    name:'JS2016'};
var obj = {
    
    
  name:'0 _ 0',
  sayHello:function(){
    
    
    console.log(this.name);
  }.bind(objName)
};
obj.sayHello();//JS2016

call和apply、bind都是用来改变this的指向的,但也有一些小小的差别。下面我们来看看它们的差别在哪。

function fn(a,b,c,d){
    
    
  console.log(a,b,c,d);
}

//call
fn.call(null,1,2,3);

//apply
fn.apply(null,[1,2,3]);

//bind
var f = fn.bind(null,1,2,3);
f(4);

 

结果如下:


1 2 3 undefined
1 2 3 undefined
1 2 3 4

前面说过第一个参数传的是一个你要借用的对象,但这么我们不需要,所有就传了一个null,当然你也可以传其他的,反正在这里没有用到,除了第一个参数后面的参数将作为实际参数传入到函数中。

call就是挨个传值,
apply传一个数组,
bind也是挨个传值,
但bind和call和apply有什么不同,使用call和apply会直接执行这个函数,而bind并不会,而是将绑定好的this重新返回一个新函数,什么时候调用由你自己决定。


var objName = {
    
    name:'JS2016'};
var obj = {
    
    
  name:'0 _ 0',
  sayHello:function(){
    
    
    console.log(this.name);
  }.bind(objName)
};
obj.sayHello();//JS2016

这里也就是为什么我要用bind的原因,如果用call的话就会报错了。自己想想这个sayHello在obj都已经执行完了,就根本没有sayHello这个函数了。

这几个方法使用的好的话可以帮你解决不少问题比如:

正常情况下Math.max只能这样用

Math.max(10,6)

但如果你想传一个数组的话你可以用apply

var arr = [1,2,30,4,5];
console.log(Math.max.apply(null,arr));

又或者你想让伪数组调用数组的方法

function fn(){
    
    
  [].push.call(arguments,3);
  console.log(arguments); //[1, 2, 3]
}
fn(1,2);

再者:

var arr = ['aaabc'];
console.log(''.indexOf.call(arr,'b')); //3

牛逼不,简直偷梁换柱,当然还有很多可以利用的,自己尽情花辉去吧。

简单说一下这种偷梁换柱的原理吧,实际上浏览器内部根本就不在乎你是谁,它只关心你传给我的是不是我能够运行的,如下:

正常情况


var str = 'aaabc';
console.log(str.indexOf('b'));
而这种情况其实做的事情和上面一模一样,看我来拆解。

var arr = ['aaabc'];
''.indexOf.call(arr);

这句话就是说让arr调用字符串的indexOf方法,前面说过了浏览器内部不在乎你是谁,所以谁都可以来调用

''.indexOf.call(arr,'b')

这里的arr就是['aaabc'],内部很可能拆成了'aaabc',因此就成了下面的这段代码。

'aaabc'.indexOf('b');

这就是它们的秘密。

5、Eval函数

该函数执行的时候,this绑定到当前作用域的对象上

 var name="XL";
    var person={
    
    
        name:"xl",
        showName:function(){
    
    
            eval("console.log(this.name)");
        }
    }
    
    person.showName();  //输出  "xl"
    
    var a=person.showName;
    a();  //输出  "XL"

6、DOM 事件处理函数中的 this & 内联事件中的 this

1、DOM事件处理函数

当函数被当做监听事件处理函数时, 其 this 指向触发该事件的元素 (针对于addEventListener事件)
  // 被调用时,将关联的元素变成蓝色
	function bluify(e){
    
    
	  //在控制台打印出所点击元素
	  console.log(this);
	  //阻止事件冒泡
	  e.stopPropagation();
	  //阻止元素的默认事件
	  e.preventDefault();      
	  this.style.backgroundColor = '#A5D9F3';
	}

	// 获取文档中的所有元素的列表
	var elements = document.getElementsByTagName('*');

	// 将bluify作为元素的点击监听函数,当元素被点击时,就会变成蓝色
	for(var i=0 ; i<elements.length ; i++){
    
    
	  elements[i].addEventListener('click', bluify, false);
	}

2、内联事件

内联事件中的this指向分两种情况:

1、当代码被内联处理函数调用时,它的this指向监听器所在的DOM元素
2、当代码被包括在函数内部执行时,其this指向等同于 函数直接调用的情况,即在非严格模式指向全局对象window, 在严格模式指向undefined
在这里插入图片描述

3、setTimeout & setInterval

对于延时函数内部的回调函数的this指向全局对象window(当然我们可以通过bind方法改变其内部函数的this指向)

//默认情况下代码
function Person() {
    
      
    this.age = 0;  
    setTimeout(function() {
    
    
        console.log(this);
    }, 3000);
}

var p = new Person();//3秒后返回 window 对象
==============================================
//通过bind绑定
function Person() {
    
      
    this.age = 0;  
    setTimeout((function() {
    
    
        console.log(this);
    }).bind(this), 3000);
}

var p = new Person();//3秒后返回构造函数新生成的对象 Person{...}

7、箭头函数

es6里面this指向固定化,始终指向外部对象,因为箭头函数没有this,因此它自身不能进行new实例化,同时也不能使用call, apply, bind等方法来改变this的指向

由于箭头函数不绑定this, 它会捕获其所在(即定义的位置)上下文的this值, 作为自己的this值,

所以 call() / apply() / bind() 方法对于箭头函数来说只是传入参数,对它的 this 毫无影响。
考虑到 this 是词法层面上的,严格模式中与 this 相关的规则都将被忽略。(可以忽略是否在严格模式下的影响)

箭头函数的this: 指向箭头函数定义时所处的对象(定义该函数时所在的作用域指向的对象),而不是箭头函数使用时所在的对象,默认使用父级的this

箭头函数不会创建自己的this,它只会从自己的作用域链的上一层继承this

下面是普通函数的列子:

var name = 'window'; // 其实是window.name = 'window'

var A = {
    
    
   name: 'A',
   sayHello: function(){
    
    
      console.log(this.name)
   }
}

A.sayHello();// 输出A

var B = {
    
    
  name: 'B'
}

A.sayHello.call(B);//输出B

A.sayHello.call();//不传参数指向全局window对象,输出window.name也就是window

从上面可以看到,sayHello这个方法是定义在A对象中的,当当我们使用call方法,把其指向B对象,最后输出了B;可以得出,sayHello的this只跟使用时的对象有关。

改造一下:

var name = 'window'; 

var A = {
    
    
   name: 'A',
   sayHello: () => {
    
    
      console.log(this.name)
   }
}

A.sayHello();// 还是以为输出A ? 错啦,其实输出的是window

我相信在这里,大部分同学都会出错,以为sayHello是绑定在A上的,但其实它绑定在window上的,那到底是为什么呢?

一开始,我重点标注了“该函数所在的作用域指向的对象”,作用域是指函数内部,这里的箭头函数,也就是sayHello,所在的作用域其实是最外层的js环境,因为没有其他函数包裹;然后最外层的js环境指向的对象是winodw对象,所以这里的this指向的是window对象。

那如何改造成永远绑定A呢:

var name = 'window'; 

var A = {
    
    
   name: 'A',
   sayHello: function(){
    
    
      var s = () => console.log(this.name)
      return s//返回箭头函数s
   }
}

var sayHello = A.sayHello();
sayHello();// 输出A 

var B = {
    
    
   name: 'B';
}

sayHello.call(B); //还是A
sayHello.call(); //还是A

OK,这样就做到了永远指向A对象了,我们再根据“该函数所在的作用域指向的对象”来分析一下:

该函数所在的作用域:箭头函数s 所在的作用域是sayHello,因为sayHello是一个函数。
作用域指向的对象:A.sayHello指向的对象是A。

三、总结

1、函数外面的this,即全局作用域的this指向window。

2、函数里面的this总是指向直接调用者。如果没有直接调用者,隐含的调用者是window。

3、使用new调用一个函数,这个函数即为构造函数。构造函数里面的this是和实例对象沟通的桥梁,他指向实例对象。

4、箭头函数里面的this在它申明时确定,跟他当前作用域的this一样。

5、DOM事件回调里面,this指向绑定事件的对象(currentTarget),而不是触发事件的对象(target)。当然这两个可以是一样的。如果回调是箭头函数,请参考上一条,this是它申明时作用域的this。

6、严格模式下,函数里面的this指向undefined,函数外面(全局作用域)的this还是指向window。

7、call和apply可以改变this,这两个方法会立即执行原方法,他们的区别是参数形式不一样。

8、bind也可以修改this,但是他不会立即执行,而是返回一个修改了this的函数。

参考文章
https://www.cnblogs.com/lisha-better/p/5684844.html
https://www.cnblogs.com/pssp/p/5787116.html
https://www.cnblogs.com/dongcanliang/p/7054176.html
https://zhuanlan.zhihu.com/p/57204184
https://mp.weixin.qq.com/s/j7Kg8vzADFiqywuIv9uLgA

猜你喜欢

转载自blog.csdn.net/qq_41880073/article/details/121536590