浅析JavaScript中的 this 指向

作者 : 开心源码 本文共4825个字,预计阅读时间需要13分钟 发布时间: 2022-05-12 共174人阅读

前言

this指向是学习JavaScript必需了解的知识点,在ES6没有出现时,this是动态绑定的,或者称为运行期绑定的,这就导致this关键字有能力具有多重含义,带来灵活性的同时,也为初学者带来不少困惑。

JavaScript中的this

JavaScript中的this含义很丰富,他可以是全局对象、当前对象或者者任意对象,这完全取决于函数的调用方式。

JavaScript中函数的调用有以下几种方式:
作为对象方法调用
作为函数调用
作为构造函数调用
使用apply或者call调用、bind调用

下面分别来看一下上面说的几种调用方式

作为对象方法调用

var point = {  x:0,  y:0,  moveTo: function(x,y){    this.x = this.x + x;    this.y = this.y + y;  }};point.moveTo(1,1)  // this 绑定到当前对象,即point对象

上面这段代码我们定义了一个point对象,其有一个moveTo方法,当该方法作为对象方法被调用时,此时this绑定到当前对象,及point对象。

作为函数调用

普通函数内部的this分两种情况,严格模式和非严格模式
非严格模式下,this 默认指向全局对象window

function f1(){  return this;}f1() === window; // true

而严格模式下, this为undefined

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

我们常见的是非严格模式,函数可以直接被调用,此时this绑定到全局对象。在浏览器中,全局对象就是window对象,在下面的例子中,函数被调用时,this被绑定到全局对象,接下来执行赋值语句,相当于公告了一个全局变量,这显然是不应该的。

function setX(x) {  this.x = x;}setX(6);x; //x 已经是一个全局变量

对于内部函数(即公告在一个函数体内的函数),这种绑定到全局对象的方式会产生另一个问题,我们依然以前面提到的point为例,这次我们希望在 moveTo 方法内定义两个函数,分别将 x,y 坐标进行平移。结果可能出乎大家预料,不仅 point 对象没有移动,反而多出两个全局变量 x,y。

var point = {  x:0,  y:0,   moveTo: function(x,y){    var moveX = function(x){      this.x = x;    //this 绑定在哪里    }    var moveY = function(y){      this.y = y;    }    moveX(x);    moveY(y);  }  point.moveTo(5,5);  console.log(point.x);   //0  console.log(point.y);   //0  console.log(x);  //5  console.log(y);  //5}

这属于JavaScript的设计缺陷,正确的设计方式是内部函数的this应该绑定到其外层函数对应的对象上,为例避免这一设计缺陷,聪明的JavaScript程序员想出了变量替代的方法,商定俗称,该变量一般被命名为that。上面的代码更改如下:

var point = { x : 0, y : 0, moveTo : function(x, y) {     //绑定内部函数的this为外层函数对应的对象    var that = this;     // 内部函数    var moveX = function(x) {       that.x = x;     };     // 内部函数    var moveY = function(y) {       that.y = y;     }     moveX(x);     moveY(y);     } }; point.moveTo(5, 5); console.log(point.x); //==>5console.log(point.y); //==>5//Uncaught ReferenceError: x is not definedconsole.log(x);

作为构造函数调用

JavaScript 支持面向对象式编程,与主流的面向对象式编程语言不同,JavaScript 并没有类(class)的概念,而是使用基于原型(prototype)的继承方式。相应的,JavaScript 中的构造函数也很特殊,假如不使用 new调用,则和普通函数一样。作为又一项商定俗成的原则,构造函数以大写字母开头,提示调用者使用正确的方式调用。假如调用正确,this 绑定到新创立的对象上。

function Point(x, y){    this.x = x;    this.y = y; }var point1 = new Point(1,1);  //this 指向point1

使用 apply 或者 call 调用

让我们再一次重申,在 JavaScript 中函数也是对象,对象则有方法,apply 和 call 就是函数对象的方法。这两个方法异常强大,他们允许切换函数执行的上下文环境(context),即 this 绑定的对象。很多 JavaScript 中的技巧以及类库都用到了该方法。让我们看一个具体的例子:

function Point(x, y){    this.x = x;    this.y = y;    this.moveTo = function(x, y){        this.x = x;        this.y = y;    } } var p1 = new Point(1,1);var p2 = {x:2, y:2};p1.moveTo(3,3);p1.moveTo.apply(p2, [10, 10]);// call使用// p1.moveTo.call(p2, 10,10);

在上面的代码中,我们使用了构造函数生成了对象p1,该对象同时具备moveTo方法。而后我们使用字面量创立了对象p2,我们看到使用 apply 可以将 p1 的方法应用到 p2 上,这时候 this 也被绑定到对象 p2 上。另一个方法 call 也具有同样功能,不同的是最后的参数不是作为一个数组统一传入,而是分开传入的。
apply和call使用

使用bind调用

bind是在EcmaScript5中扩展的方法(IE6,7,8不支持)
bind() 方法与 apply 和 call 很类似,也是可以改变函数体内 this 的指向。

MDN的解释是:bind()方法会创立一个新函数,称为绑定函数,当调用这个绑定函数时,绑定函数会以创立它时传入 bind()方法的第一个参数作为 this,传入 bind() 方法的第二个以及以后的参数加上绑定函数运行时本身的参数按照顺序作为原函数的参数来调用原函数。

[注意:bind方法的返回值是函数]

var bar=function(){     console.log(this.x);   }var foo={ x:3 }   bar();   //此时this指向全局变量,x不存在bar.bind(foo)(); /*或者*/var func=bar.bind(foo);   //此时this指向foo对象,运行函数后打印结果为3func();输出:undefined3

我们再来看一个多参数的例子:
传入多个参数时,this指向第一个参数({x:1}这个对象),第二个参数2,按照顺序作为原函数的参数传入,故y=2,bind方法产生的新的函数m中传入的参数3传给原函数的z,故最终答案为6.

function f(y, z){    return this.x + y + z;}var m = f.bind({x : 1}, 2);console.log(m(3));//6

另外有个有趣的问题,假如连续 bind() 两次,亦或者者是连续 bind() 三次那么输出的值是什么呢?像这样:

var bar = function(){    console.log(this.x);}var foo = {x:3}var sed = {x:4}var func = bar.bind(foo).bind(sed);func(); //? var fiv = {x:5}var func = bar.bind(foo).bind(sed).bind(fiv);func(); //?

答案是,两次都仍将输出 3 ,而非期待中的 4 和 5 。起因是,在Javascript中,屡次 bind() 是无效的。更深层次的起因, bind() 的实现,相当于使用函数在内部包了一个 call / apply ,第二次 bind() 相当于再包住第一次 bind() ,故第二次以后的 bind 是无法生效的。

apply、call、bind比较

1、apply 、 call 、bind 三者都是用来改变函数的this对象的指向的;
2、apply 、 call 、bind 三者第一个参数都是this要指向的对象,也就是想指定的上下文;
3、apply 、 call 、bind 三者都可以利用后续参数传参;
4、bind 是返回对应函数,便于稍后调用;apply 、call 则是立即调用 。

DOM 事件解决函数中的 this & 内联事件中的 this

1.当函数被当做监听事件解决函数时, 其 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。

内联事件中的this

ES6中 this指向

因为箭头函数不绑定this, 它会捕获其所在(即定义的位置)上下文的this值, 作为自己的this值。
1.所以 call() / apply() / bind() 方法对于箭头函数来说只是传入参数,对它的 this 毫无影响。
2.考虑到 this 是词法层面上的,严格模式中与 this 相关的规则都将被忽略。(可以忽略能否在严格模式下的影响)

我们来看一个例子:

//定义一个对象var obj = {  x:100, //属性x  show(){    //推迟500毫秒,输出x的值    setTimeout(      //匿名函数      function(){console.log(this.x);}, 500);   }};obj.show();//打印结果:undefined

为什么结果打印是undefined呢,问题出在了this上,当代码执行到了setTimeout( )的时候,此时的this已经变成了window对象(setTimeout( )是window对象的方法),已经不再是obj对象了,所以我们用this.x获取的时候,获取的不是obj.x的值,而是window.x的值,再加上window上没有定义属性x,所以得到的结果就是:undefined。

假如使用箭头函数来编写同样的一段代码,得到的this.x又是另一番景象。

//定义一个对象var obj = {  x:100,//属性x  show(){    //推迟500毫秒,输出x的值    setTimeout(   //不同处:箭头函数      () => { console.log(this.x)},      500    );  }};obj.show();//打印结果:100

为什么会是这样呢?
箭头函数的this绑定看的是this所在的函数定义在哪个对象下,绑定到哪个对象则this就指向哪个对象 。


持续升级…

说明
1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长!
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系管理员处理!
6. 本站资源售价只是摆设,本站源码仅提供给会员学习使用!
7. 如遇到加密压缩包,请使用360解压,如遇到无法解压的请联系管理员
开心源码网 » 浅析JavaScript中的 this 指向

发表回复