JavaScript入门教程笔记(11)-this关键字

╰+哭是因爲堅強的太久メ 2022-04-08 15:49 225阅读 0赞

1 定义

this关键字是一个非常重要的语法点,不理解它的含义,大部分开发任务都很难完成。

无论什么场合,this总是返回一个对象。简单说,this就是属性或方法“当前”所在的对象。

  1. var person = {
  2. name: 'mark',
  3. say: function() {
  4. return 'name: ' + this.name;
  5. }
  6. };
  7. person.say() // "name: mark"

上面代码中,由于this.name是在say方法中调用的,而say方法所在的当前对象是person,因此this指向person,而this. name 就是 person. name。

由于对象的属性可以赋给另一个对象,因此this的指向是可变的。

  1. function f() {
  2. return 'hello: ' + this.name;
  3. }
  4. var A = {
  5. name: 'AAA',
  6. desc: f
  7. };
  8. var B = {
  9. name: 'BBB',
  10. desc: f
  11. };
  12. A.desc() // "hello: AAA"
  13. B.desc() // "hello: BBB"

上面代码中,函数f内部使用了this,随着f所在对象的不同,this的指向也不同。

总结一下,JavaScript语言中一切皆对象,运行环境是对象,函数都是在某个对象之中运行,this就是函数运行时所在的对象(环境)。这本来是很清晰的概念,但是因为JavaScript支持运行环境的动态切换,所以没有办法事先确定this到底指向哪个对象,这才是最容易让人迷惑的地方。

2 实质

由于函数可以在不同的运行环境执行,需要有一种机制,能够在函数体内部获得当前的运行环境(context)。所以,this就出现了,它的设计目的就是在函数体内部,指向函数当前的运行环境。

  1. var f = function() {
  2. console.log(this.x);
  3. }
  4. var x = 1;
  5. var obj = {
  6. f: f,
  7. x: 2,
  8. };
  9. f() // 1
  10. obj.f() // 2

上面代码中,函数f在全局环境里执行,this.x指向全局环境的x,输出1;在obj环境里执行,this.x指向obj.x,输出2。

3 使用注意点

3.1 避免多层this

切勿在函数中包含多层this,因为this的指向不确定。

  1. var obj = {
  2. f1: function() {
  3. console.log(this);
  4. var f2 = function() {
  5. console.log(this);
  6. }();
  7. }
  8. }
  9. obj.f1()
  10. // Object
  11. // Window

上面代码包含两层this,实际运行后,第一层this指向对象obj,第二层this指向全局对象。因为实际执行的类似于下面代码:

  1. var temp = function() {
  2. console.log(this);
  3. };
  4. var obj = {
  5. f1: function() {
  6. console.log(this);
  7. var f2 = temp();
  8. }
  9. }

简单的解决方法是用一个指向外层对象的变量来代替第二层this。

  1. var obj = {
  2. f1: function() {
  3. console.log(this);
  4. var self = this; // 增加一个变量
  5. var f2 = function() {
  6. console.log(self);
  7. }();
  8. }
  9. }
  10. obj.f1()
  11. // Object
  12. // Object

上面代码定义了变量self,固定指向外层的this,就不会发生指向的改变了。

通过一个变量固定this的值,然后内层函数使用这个变量,是非常常见的做法,请务必掌握。

3.2 避免数组处理方法中的this

数组的map和foreach方法,允许提供一个函数作为参数。这个函数内部不应该使用this。

  1. var o = {
  2. v: 'hello',
  3. p: ['a1', 'a2'],
  4. f: function f() {
  5. this.p.forEach(function(item) {
  6. console.log(this.v + ' ' + item);
  7. });
  8. }
  9. }
  10. o.f()
  11. // undefined a1
  12. // undefined a2

上面forEach方法的回调函数中的this,跟上一段的多层this一样,内层this不指向外部,而是指向顶层对象。

解决这个问题的一个简单方法,就是前面提到的,使用中间变量固定this。

3.3 避免回调函数中的this

回调函数中的this往往会改变指向,最好避免使用。

为了解决这个问题,可以采用下面的一些方法对this进行绑定,使this固定指向某个对象,减小不确定性。

4 绑定this的方法

JavaScript提供了call、apply、bind三个方法,来切换/固定this的指向。

4.1 Function.prototype.call()

函数实例的call方法,可以指定this的运行环境(即函数执行时所在的作用域),然后在所指定的作用域中,调用该函数。

call方法的参数,是一个对象。

  1. var obj = {};
  2. var f = function() {
  3. return this;
  4. };
  5. f() === window // true
  6. f.call(obj) === obj // true

上面代码中,直接运行函数f时,this指向全局环境(window对象)。而call方法可以改变this指向对象obj,然后在对象obj的作用域中运行函数f。

如果call方法的参数为空、null和undefined,则默认传入全局对象。

  1. var n = 123;
  2. var obj = { n: 456 };
  3. function a() {
  4. console.log(this.n);
  5. }
  6. a.call() // 123
  7. a.call(null) // 123
  8. a.call(undefined) // 123
  9. a.call(window) // 123
  10. a.call(obj) // 456

call方法还可以接受多个参数,后面的参数是函数调用时所需的参数。

  1. function add(a, b) {
  2. return a + b;
  3. }
  4. add.call(this, 1, 2) // 3

4.2 Function.prototype.apply()

apply方法的作用与call方法类似,也是改变this指向,然后再调用该函数。唯一的区别是,它接收一个数组作为函数执行的参数,使用格式如下:

  1. func.apply(thisValue, [arg1, arg2, ...])

原函数的参数,在call方法中必须一个个添加,但是在apply方法中,必须以数组形式添加。

  1. function f(x, y) {
  2. console.log(x + y);
  3. }
  4. f.call(null, 1, 2) // 3
  5. f.apply(null, [1, 2]) // 3

5.3 Function.prototype.bind()

bind方法用于将函数体内的this绑定到某个对象,然后返回一个新函数。

  1. var d = new Date();
  2. d.getTime()
  3. var print = d.getTime;
  4. print() // Error

上面代码中,print报错是因为getTime方法内部的this,绑定了Date对象的实例,赋值给变量print后,内部的this已经不指向Date对象的实例了。

bind方法可以解决这个问题

  1. var print = d.getTime.bind(d);
  2. print() // ok

上面代码中,bind将getTime方法内部的this绑定到d对象,因此就可以安全地将getTime赋值给其它变量了。

使用bind还可以将this绑定到其它对象。

  1. var counter = {
  2. count: 0,
  3. inc: function() {
  4. this.count++;
  5. }
  6. };
  7. var obj = {
  8. count: 100
  9. };
  10. var func = counter.inc.bind(obj);
  11. func();
  12. obj.count // 101

上面代码中,bind将inc内部的this绑定到obj对象,因此调用func后,递增的就是obj内部的count属性。

使用bind方法有一些注意事项。

(1)每一次返回一个新函数

bind方法每运行一次,就会返回一个新函数,这可能产生一些问题。

  1. node.addEventListener('click', obj.m.bind(obj));
  2. node.removeEventListener('click', obj.m.bind(obj)); // Error

上面代码无法取消绑定,因为后一个bind返回的是一个匿名函数,和前一个add事件并不是同一个函数。

正确的定法是下面这样:

  1. var listener = obj.m.bind(obj);
  2. node.addEventListener('click', listener);
  3. node.removeEventListener('click', listener);
(2)结合回调函数使用

回调函数是JavaScript最常用的模式之一,我们应用使用bind方法将目标函数固定到目标对象。

(3)结合call方法使用

以数组的slice方法为例。

  1. [1, 2, 3].slice(0, 1) // [1]
  2. 等同于
  3. Array.prototype.slice.call([1, 2, 3], 0, 1) // [1]

上面代码的两种调用方法,得到同样的结果。

注:本文适用于ES5规范,原始内容来自 JavaScript 教程,有修改。

发表评论

表情:
评论列表 (有 0 条评论,225人围观)

还没有评论,来说两句吧...

相关阅读

    相关 JavaScript this关键字

    介绍 在这篇文章里,我们将讨论跟执行上下文直接相关的更多细节。讨论的主题就是this关键字。实践证明,这个主题很难,在不同执行上下文中this的确定经常会发生问题。 许