• 售前

  • 售后

热门帖子
入门百科

JavaScript逐点突破系列之this是什么

[复制链接]
啵啵小奥特 显示全部楼层 发表于 2021-10-26 13:21:24 |阅读模式 打印 上一主题 下一主题
了解this

也许你在其他面向对象的编程语言曾经看过
  1. this
复制代码
,也知道它会指向某个构造器(constructor)所创建的对象。但事实上在JavaScript内里,
  1. this
复制代码
所代表的不但仅是那个被创建的对象。
先来看看ECMAScript 标准规范对this 的界说:
  1. 「The this keyword evaluates to the value of the ThisBinding of the current execution context.」
  2. 「this 这个关键字代表的值为当前执行上下文的ThisBinding。」
复制代码
然后再来看看MDN 对this 的界说:
  1. 「In most cases, the value of this is determined by how a function is called.」
  2. 「在大多数的情况下,this 其值取决于函数的调用方式。」
复制代码
好,如果上面两行就看得懂的话那么就不消再往下看了,Congratulations!
… 我想应该不会,至少我光看这两行还是不懂。
先来看个例子吧:
  1. var getGender = function() {
  2.     return people1.gender;
  3. };
  4. var people1 = {
  5.     gender: 'female',
  6.     getGender: getGender
  7. };
  8. var people2 = {
  9.     gender: 'male',
  10.     getGender: getGender
  11. };
  12. console.log(people1.getGender());    // female
  13. console.log(people2.getGender());    // female
复制代码
what?怎么people2变性了呢,这不是我想要的效果啊,为什么呢?
由于
  1. getGender()
复制代码
返回(return)写死了
  1. people1.gender
复制代码
的关系,效果天然是'female'。
那么,如果我们把
  1. getGender
复制代码
稍改一下:
  1. var getGender = function() {
  2.     return this.gender;
  3. };
复制代码
这个时间,你应该会分别得到
  1. female
复制代码
  1. male
复制代码
两种效果。
所以回到前面讲的重点,从这个例子可以看出,即便
  1. people1
复制代码
  1. people2
复制代码
  1. getGender
复制代码
方法参照的都是同一个getGender function,但由于调用的对象不同,所以执行的效果也会不同
现在我们知道了第一个重点,**this实际上是在函数被调用时发生的绑定,它指向什么完全取决于函数的调用方式。**怎样的区分this呢?
this到底是谁

看完上面的例子,还是有点似懂非懂吧?那接下来我们来看看不同的调用方式对 this 值的影响。
环境一:全局对象&调用普通函数

在全局环境中,this 指向全局对象,在欣赏器中,它就是 window 对象。下面的示例中,无论是否是在严格模式下,this 都是指向全局对象。
  1. var x = 1
  2. console.log(this.x)               // 1
  3. console.log(this.x === x)         // true
  4. console.log(this === window)      // true
复制代码
如果普通函数是在全局环境中被调用,在非严格模式下,普通函数中 this 也指向全局对象;如果是在严格模式下,this 将会是 undefined。ES5 为了使 JavaScript 运行在更有限定性的环境而添加了严格模式,严格模式为了消除安全隐患,克制了 this 关键字指向全局对象。
  1. var x = 1
  2. function fn() {
  3.     console.log(this);   // Window 全局对象
  4.     console.log(this.x);  // 1
  5. }
  6. fn();     
复制代码
利用严格模式后:
  1. "use strict"     // 使用严格模式
  2. var x = 1
  3. function fn() {
  4.     console.log(this);   // undefined
  5.     console.log(this.x);  // 报错 "Cannot read property 'x' of undefined",因为此时 this 是 undefined
  6. }
  7. fn();  
复制代码
环境二:作为对象方法的调用

我们知道,在对象里的值如果是原生值(primitive type;比方,字符串、数值、布尔值),我们会把这个新创建的东西称为「属性(property)」;如果对象内里的值是函数(function)的话,我们则会把这个新创建的东西称为「方法(method)」。
如果函数作为对象的一个方法时,而且作为对象的一个方法被调用时,函数中的this指向这个上一级对象
  1. var x = 1
  2. var obj = {
  3.     x: 2,
  4.     fn: function() {
  5.         console.log(this);   
  6.         console.log(this.x);
  7.     }
  8. }
  9. obj.fn()     
  10. // obj.fn()结果打印出;
  11. // Object {x: 2, fn: function}
  12. // 2
  13. var a = obj.fn
  14. a()   
  15. // a()结果打印出:   
  16. // Window 全局对象
  17. // 1
复制代码
在上面的例子中,直接运行 obj.fn() ,调用该函数的上一级对象是 obj,所以 this 指向 obj,得到 this.x 的值是 2;之后我们将 fn 方法起首赋值给变量 a,a 运行在全局环境中,所以此时 this 指向全局对象Window,得到 this.x 为 1。
我们再来看一个例子,如果函数被多个对象嵌套调用,this 会指向什么。
  1. var x = 1
  2. var obj = {
  3.   x: 2,
  4.   y: {
  5.     x: 3,
  6.     fn: function() {
  7.       console.log(this);   // Object {x: 3, fn: function}
  8.       console.log(this.x);   // 3
  9.     }
  10.   }
  11. }
  12. obj.y.fn();      
复制代码
为什么效果不是 2 呢,由于在这种环境下记取一句话:this 始终会指向直接调用函数的上一级对象,即 y,上面例子实际执行的是下面的代码。
  1. var y = {
  2.   x: 3,
  3.   fn: function() {
  4.     console.log(this);   // Object {x: 3, fn: function}
  5.     console.log(this.x);   // 3
  6.   }
  7. }
  8. var x = 1
  9. var obj = {
  10.   x: 2,
  11.   y: y
  12. }
  13. obj.y.fn();   
复制代码
对象可以嵌套,函数也可以,如果函数嵌套,this 会有厘革吗?我们通过下面代码来探究一下。
  1. var obj = {
  2.     y: function() {
  3.         console.log(this === obj);   // true
  4.         console.log(this);   // Object {y: function}
  5.         fn();
  6.         function fn() {
  7.             console.log(this === obj);   // false
  8.             console.log(this);   // Window 全局对象
  9.         }
  10.     }
  11. }
  12. obj.y();  
复制代码
在函数 y 中,this 指向了调用它的上一级对象 obj,这是没有问题的。但是在嵌套函数 fn 中,this 并不指向 obj。嵌套的函数不会从调用它的函数中继承 this,当嵌套函数作为函数调用时,其 this 值在非严格模式下指向全局对象,在严格模式是 undefined,所以上面例子实际执行的是下面的代码。
  1. function fn() {
  2.     console.log(this === obj);   // false
  3.     console.log(this);   // Window 全局对象
  4. }
  5. var obj = {
  6.     y: function() {
  7.         console.log(this === obj);   // true
  8.         console.log(this);   // Object {y: function}
  9.         fn();
  10.     }
  11. }
  12. obj.y();  
复制代码
环境三:作为构造函数调用

我们可以利用 new 关键字,通过构造函数天生一个实例对象。此时,this 便指向这个新对象
  1. var x = 1;
  2. function Fn() {
  3.    this.x = 2;
  4.     console.log(this);  // Fn {x: 2}
  5. }
  6. var obj = new Fn();   // obj和Fn(..)调用中的this进行绑定
  7. console.log(obj.x)   // 2
复制代码
利用
  1. new
复制代码
来调用
  1. Fn(..)
复制代码
时,会构造一个新对象并把它(obj)绑定到
  1. Fn(..)
复制代码
调用中的this。另有值得一提的是,如果构造函数返回了非引用范例(string,number,boolean,null,undefined),this 仍旧指向实例化的新对象。
  1. var x = 1
  2. function Fn() {
  3.   this.x = 2
  4.   return {
  5.     x: 3
  6.   }
  7. }
  8. var a = new Fn()
  9. console.log(a.x)      // 3
复制代码
由于Fn()返回(return)的是一个对象(引用范例),this 会指向这个return的对象。如果return的是一个非引用范例的值呢?
  1. var x = 1
  2. function Fn() {
  3.   this.x = 2
  4.   return 3
  5. }
  6. var a = new Fn()
  7. console.log(a.x)      // 2
复制代码
环境四:call 和 apply 方法调用

如果你想改变 this 的指向,可以利用 call 或 apply 方法。它们的第一个参数都是指定函数运行时此中的
  1. this
复制代码
指向
。如果第一个参数不传(参数为空)或者传 null 、undefined,默认 this 指向全局对象(非严格模式)或 undefined(严格模式)。
  1. var x = 1;
  2. var obj = {
  3.   x: 2
  4. }
  5. function fn() {
  6.     console.log(this);
  7.     console.log(this.x);
  8. }
  9. fn.call(obj)
  10. // Object {x: 2}
  11. // 2
  12. fn.apply(obj)     
  13. // Object {x: 2}
  14. // 2
  15. fn.call()         
  16. // Window 全局对象
  17. // 1
  18. fn.apply(null)   
  19. // Window 全局对象
  20. // 1
  21. fn.call(undefined)   
  22. // Window 全局对象
  23. // 1
复制代码
利用 call 和 apply 时,如果给 this 传的不是对象,JavaScript 会利用相干构造函数将其转化为对象,比如传 number 范例,会举行
  1. new Number()
复制代码
利用,如传 string 范例,会举行
  1. new String()
复制代码
利用,如传 boolean 范例,会举行new Boolean()利用。
  1. function fn() {
  2.   console.log(Object.prototype.toString.call(this))
  3. }
  4. fn.call('love')      // [object String]
  5. fn.apply(1)          // [object Number]
  6. fn.call(true)          // [object Boolean]
复制代码
call 和 apply 的区别在于,call 的第二个及后续参数是一个参数列表,apply 的第二个参数是数组。参数列表和参数数组都将作为函数的参数举行执行。
  1. var x = 1
  2. var obj = {
  3.   x: 2
  4. }
  5. function Sum(y, z) {
  6.   console.log(this.x + y + z)
  7. }
  8. Sum.call(obj, 3, 4)       // 9
  9. Sum.apply(obj, [3, 4])    // 9
复制代码
环境五:bind 方法调用

调用 f.bind(someObject) 会创建一个与 f 具有雷同函数体和作用域的函数,但是在这个新函数中,新函数的 this 会永久的指向 bind 传入的第一个参数,无论这个函数是怎样被调用的。
  1. var x = 1
  2. var obj1 = {
  3.     x: 2
  4. };
  5. var obj2 = {
  6.     x: 3
  7. };
  8. function fn() {
  9.     console.log(this);
  10.     console.log(this.x);
  11. };
  12. var a = fn.bind(obj1);
  13. var b = a.bind(obj2);
  14. fn();
  15. // Window 全局对象
  16. // 1
  17. a();
  18. // Object {x: 2}
  19. // 2
  20. b();
  21. // Object {x: 2}
  22. // 2
  23. a.call(obj2);
  24. // Object {x: 2}
  25. // 2
复制代码
在上面的例子中,固然我们尝试给函数 a 重新指定 this 的指向,但是它仍旧指向第一次 bind 传入的对象,纵然是利用 call 或 apply 方法也不能改变这一事实,即永久的指向 bind 传入的第一次参数。
环境六:箭头函数中this指向

值得一提的是,从ES6 开始新增了箭头函数,先来看看MDN 上对箭头函数的阐明
  1. An arrow function expression has a shorter syntax than a function expression and does notbind its own[code]this
复制代码
,
  1. arguments
复制代码
,
  1. super
复制代码
, or
  1. new.target
复制代码
. Arrow functions are always anonymous. These function expressions are best suited for non-method functions, and they cannot be used as constructors.[/code]这里已经清楚了阐明了,箭头函数没有自己的
  1. this
复制代码
绑定。箭头函数中利用的
  1. this
复制代码
,其实是直接包含它的那个函数或函数表达式中的
  1. this
复制代码
。在前面环境二中函数嵌套函数的例子中,被嵌套的函数不会继承上层函数的 this,如果利用箭头函数,会发生什么厘革呢?
  1. var obj = {
  2.   y: function() {
  3.         console.log(this === obj);   // true
  4.         console.log(this);           // Object {y: function}
  5.       var fn = () => {
  6.           console.log(this === obj);   // true
  7.           console.log(this);           // Object {y: function}
  8.       }
  9.       fn();
  10.   }
  11. }
  12. obj.y()
复制代码
和普通函数不一样,箭头函数中的 this 指向了 obj,这是由于它从上一层的函数中继承了 this,你可以理解为箭头函数修正了 this 的指向。所以箭头函数的this不是调用的时间决定的,而是在界说的时间处在的对象就是它的this
换句话说,箭头函数的this看外层的是否有函数,如果有,外层函数的this就是内部箭头函数的this,如果没有,则this是window
  1. var obj = {
  2.   y: () => {
  3.         console.log(this === obj);   // false
  4.         console.log(this);           // Window 全局对象
  5.       var fn = () => {
  6.           console.log(this === obj);   // false
  7.           console.log(this);           // Window 全局对象
  8.       }
  9.       fn();
  10.   }
  11. }
  12. obj.y()
复制代码
上例中,固然存在两个箭头函数,其实this取决于最外层的箭头函数,由于obj是个对象而非函数,所以this指向为Window全局对象。
同 bind 一样,箭头函数也很“顽固”,我们无法通过 call 和 apply 来改变 this 的指向,即传入的第一个参数被忽略
  1. var x = 1
  2. var obj = {
  3.     x: 2
  4. }
  5. var a = () => {
  6.     console.log(this.x)
  7.     console.log(this)
  8. }
  9. a.call(obj)      
  10. // 1
  11. // Window 全局对象
  12. a.apply(obj)      
  13. // 1
  14. // Window 全局对象
复制代码
上面的文字描述过多可能有点干涩,那么就看以下的这张流程图吧,我以为这个图总结的很好,图中的流程只针对于单个规则。

小结

本篇文章先容了 this 指向的几种环境,不同的运行环境和调用方式都会对 this 产生影响。总的来说,函数 this 的指向取决于当前调用该函数的对象,也就是执行时的对象。在这一节中,你必要把握:
       
  • this 指向全局对象的环境;   
  • 严格模式和非严格模式下 this 的区别;   
  • 函数作为对象的方法调用时 this 指向的几种环境;   
  • 作为构造函数时 this 的指向,以及是否 return 的区别;   
  • 利用 call 和 apply 改变调用函数的对象;   
  • bind 创建的函数中 this 的指向;   
  • 箭头函数中的 this 指向。
到此这篇关于JavaScript逐点突破系列之this是什么的文章就先容到这了,更多相干JavaScript this内容请搜索草根技术分享从前的文章或继承欣赏下面的相干文章盼望大家以后多多支持草根技术分享!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?立即注册

x

帖子地址: 

回复

使用道具 举报

分享
推广
火星云矿 | 预约S19Pro,享500抵1000!
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

草根技术分享(草根吧)是全球知名中文IT技术交流平台,创建于2021年,包含原创博客、精品问答、职业培训、技术社区、资源下载等产品服务,提供原创、优质、完整内容的专业IT技术开发社区。
  • 官方手机版

  • 微信公众号

  • 商务合作