this的指向

this 的指向

前面我们学习 JavaScript 基础的对象章节时,曾经接触过 this 关键字。对象里面的 this 关键字指向当前对象。在学习 DOM 编程中的事件章节时,也遇到过 this 关键字,指向绑定事件的元素。

在本小节中,我们就一起来总结一下 JavaScript 中 this 指向的几种情况,同时来看一下如何对 this 的指向进行修改。

this 指向的默认情况

1. this 指向当前对象

如果是在对象里面使用 this,则指向当前对象。this 可以用在方法内,获取对对象属性的访问。示例如下:

const person = {
   name: 'xiejie',
   age: 18,
   intro: function () {
       console.log(this); // { name: 'xiejie', age: 18, intro: [Function: intro] }
       console.log(`My name is ${this.name},I'm ${this.age} years old`); // My name is xiejie,I'm 18 years old
  }
}
person.intro();

再来看一个构造函数中的 this 指向,也是同样指向当前对象,示例如下:

const Computer = function (name, price) {
   this.name = name;
   this.price = price;
}
Computer.prototype.showSth = function () {
   console.log(`这是一台${this.name}电脑`);
}
let apple = new Computer('苹果', 15000);
let asus = new Computer('华硕', 4500);
apple.showSth(); // 这是一台苹果电脑
asus.showSth(); // 这是一台华硕电脑

2. 普通函数中的 this 指向

如果是普通调用的函数,this 则指向全局对象,如下:

const test = function () {
   console.log(this); // global 对象
   // 在 node 里面,全局对象就是 global 对象
   // 如果是在浏览器里面,那么全局对象代表 window 对象
}
test(); // 普通函数调用,this 将会指向全局对象

需要说明的是,以普通函数的方式调用的时候,无论嵌套多少层函数,this 都始终指向全局对象,如下:

const test = function () {
   const test2 = function () {
       const test3 = function () {
           console.log(this); // global 对象
      }
       test3();
  }
   test2();
}
test();

接下来我们再来看一个例子,如下:

var name = "PHP";
const obj = {
   name: "JavaScript"
}
const show = function () {
   console.log("Hello," + this.name);
}
obj.fn = show; // 将 show() 方法赋值给 obj 对象的 fn 属性
obj.fn(); // Hello,JavaScript
show(); // Hello,undefined 如果是浏览器环境则是 Hello,PHP

这里,我们将show()方法赋值给 obj 对象的 fn 属性,所以在执行obj.fn()时是以对象的形式来调用的,this 将会指向 obj 对象。而后面的show()方法则是单纯的以普通函数的形式来进行调用的。所以 this 指向全局对象。

3. this 指向绑定事件的元素

关于这一种 this 的指向,我们在学习事件章节时也接触过。DOM 元素绑定事件时,事件处理函数里面的 this 指向绑定了事件的元素。这个地方一定要注意它和 target 的区别,target 是指向触发事件的元素。

示例如下:

<body>
   <ul id="color-list">
       <li>red</li>
       <li>yellow</li>
       <li>blue</li>
       <li>green</li>
       <li>black</li>
       <li>white</li>
   </ul>
   <script>
       // this 是绑定事件的元素
       // target 是触发事件的元素 和 srcElememnt 等价
       let colorList = document.getElementById("color-list");
       colorList.addEventListener("click", function (event) {
           console.log('this:', this);
           console.log('target:', event.target);
           console.log('srcElement:', event.srcElement);
      })
   </script>
</body>

当我点击如下位置时打印出来的信息如下:

-w623

有些时候我们会遇到一些困扰,比如在 div 节点的事件函数内部,有一个局部的 callback 方法,该方法被作为普通函数调用时,callback 内部的 this 是指向全局对象 window 的,示例如下:

<body>
   <div id="div1">我是一个div</div>
   <script>
       window.id = 'window';
       document.getElementById('div1').onclick = function(){
           console.log(this.id); // div1
           const callback = function(){
               console.log(this.id); // 因为是普通函数调用,所以 this 指向 window
          }
           callback();
      }
   </script>
</body>

此时有一种简单的解决方案,可以用一个变量保存 div 节点的引用,如下:

<body>
   <div id="div1">我是一个div</div>
   <script>
       window.id = 'window';
       document.getElementById('div1').onclick = function(){
           console.log(this.id); // div1
           const that = this; // 保存当前 this 的指向
           const callback = function(){
               console.log(that.id); // div1
          }
           callback();
      }
   </script>
</body>

在 ECMAScript 5 的严格模式下,这种情况下的 this 已经被规定为不会指向全局对象,而是 undefined,如下:

<body>
   <div id="div1">我是一个div</div>
   <script>
       window.id = 'window';
       document.getElementById('div1').onclick = function(){
           console.log(this); // <div id="div1">我是一个div</div>
           const callback = function(){
               "use strict"
               console.log(this); // undefined
          }
           callback();
      }
   </script>
</body>

改变 this 的指向

在前面的小节中,我们已经总结出了在 JavaScript 中关于 this 指向的几种情况。但是,这个 this 的指向,我们是可以进行修改的。

接下来,我们就来看一看几种修改 this 指向的方法。

1. 方法借用函数修改 this 指向

前面我们有介绍过 JavaScript 中的方法借用模式,使用call()或者apply()可以借用其他对象的方法而不用通过继承。

在借用的同时,call()apply()也算是间接修改了 this 的指向。示例如下:

const Test = function () {
   this.name = "JavaScript";
   this.say = function () {
       console.log(`这是${this.name}`);
  }
}
const test = new Test();
test.say(); // 这是JavaScript
const a = {
   name: "PHP"
};
test.say.call(a); // 这是PHP

这里我们借用了 test 对象的say()方法。本来对于 test 对象的say()方法来讲,this 是指向 test 对象的,但是现在 a 对象借用了say()方法以后,this 就指向了 a 对象,所以打印出了"这是PHP"。

2. bind 方法绑定 this 指向

第二种方式是可以通过bind()方法来绑定 this 的指向。bind()方法的语法如下:

fun.bind(thisArg[, arg1[, arg2[, ...]]])

参数:

thisArg:当绑定函数被调用时,该参数会作为原函数运行时的 this 指向。
arg1,arg2,...:当绑定函数被调用时,这些参数将置于实参之前传递给被绑定的方法。

具体的示例如下:

const a = {
   name: "PHP"
};
const Test = function () {
   this.name = "JavaScript";
   this.say = function () {
       console.log(`这是${this.name}`);
  }.bind(a);
}
const test = new Test();
test.say(); // 这是 PHP

这里我们在say()方法后面使用了bind()方法,并将 a 对象作为参数传递进去。这个时候,当我们以 test 对象的身份来调用say()方法时,打印出来的仍然是"这是PHP",这是因为bind()方法在这里将 this 的指向绑定到了 a 对象上。

3. 箭头函数的 this 指向

当我们的 this 是以函数的形式调用时,this 指向的是全局对象。不过对于箭头函数来讲,却比较特殊。箭头函数的 this 指向始终为外层的作用域。

先来看一个普通函数作为对象的一个方法被调用时,this 的指向,如下:

const obj = {
   x : 10,
   test : function(){
       console.log(this); // 指向 obj 对象
       console.log(this.x); // 10
  }
}
obj.test();
// { x: 10, test: [Function: test] }
// 10

可以看到,普通函数作为对象的一个方法被调用时,this 指向当前对象。在上面的例子中,就是 obj 这个对象,this.x 的值为 10。

接下来是箭头函数以对象的方式被调用的时候的 this 的指向,如下:

var x = 20;
const obj = {
   x: 10,
   test: () => {
       console.log(this); // {}
       console.log(this.x); // undefined
  }
}
obj.test();
// {}
// undefined

这里的结果和上面不一样,this 打印出来为{},而 this.x 的值为 undefined。

为什么呢?实际上刚才我们有讲过,箭头函数的 this 指向与普通函数不一样,它的 this 指向始终是指向的外层作用域。所以这里的 this 实际上是指向的全局对象。

如果证明呢?方法很简单,将这段代码放入浏览器运行,在浏览器中用 var 所声明的变量会成为全局对象 window 的一个属性,如下:

-w639

接下来我们再来看一个例子,来证明箭头函数的this指向始终是指向的外层作用域。

var name = "JavaScript";
const obj = {
   name: "PHP",
   test: function () {
       const i = function () {
           console.log(this.name);
           // i 是以函数的形式被调用的,所以 this 指向全局
           // 在浏览器环境中打印出 JavaScript,node 里面为 undeifned
      }
       i();
  }
}
obj.test(); // JavaScript

接下来我们将 i 函数修改为箭头函数,如下:

var name = "JavaScript";
const obj = {
   name : "PHP",
   test : function(){
       const i = ()=>{
           console.log(this.name);
           // 由于 i 为一个箭头函数,所以 this 是指向外层的
           // 所以 this.name 将会打印出 PHP
      }
       i();
  }
}
obj.test();// PHP

最后需要说一点的就是,箭头函数不能作为构造函数,如下:

const Test = (name, age) => {
   this.name = name;
   this.age = age;
};
const test = new Test("xiejie", 18);
// TypeError: Test is not a constructor

总结

  1. 所谓编程范式,指的是计算机编程的基本风格或典范模式。大致可以分为命令式编程和声明式编程。

  2. 面向对象的程序设计是站在哲学的角度上,将人类思维融入到了程序里面的一种编程范式。

  3. 描述对象的时候可以通过两个方面来进行描述,分别是对象的外观和功能。在程序中与之对象的就是属性和方法。

  4. JavaScript 中的对象都是基于原型的,从一个原型对象中可以克隆出一个新的对象。

  5. 在 JavaScript 中每个对象都有一个原型对象。可以通过proto属性来找到一个对象的原型对象。

  6. 在其他语言中,对象从类中产生。而在 JavaScript 中,通过构造函数来模拟其他语言中的类。

  7. 类与对象的关系可以总结为,类是对对象的一种概括,而对象是类的一种具体实现。

  8. 面向对象的 3 大特征为封装,继承和多态。

  9. 封装是指对象的属性或者方法隐藏于内部,不暴露给外部。

  10. 继承是指一个子类继承一个父类。在继承了父类以后,子类就拥有了父类所有的属性和方法。

  11. 多态是指不同的对象可以拥有相同的方法,不过是以不同的方式来实现它。

  12. this 的指向根据使用的地方不同分为好几种情况,但是我们可以通过一些方式来修改 this 的指向。

  13.  

 

原文地址:https://www.cnblogs.com/liqiang95950523/p/13447758.html