这个"this"到底是什么

·  阅读 679
这个"this"到底是什么

前言

this 的值 当前执行上下文(globalfunctioneval)的一个属性,在非严格模式下,总是指向一个对象,在严格模式下可以是任意值。

this 的值是由不同的执行方式决定。通过这篇文章我们依次看一下日常开发中不同情况下使用 this 之间的区别吧,已经学废的同学可以直接跳过看文章结尾总结部分。

普通函数

普通函数的使用方式分为两种情况,一种是直接在全局上下文使用,另一个是作为一个对象的属性或者方式使用。

全局上下文

在函数内部,this 的值取决于函数被调用的方式。 下面的代码不在严格模式下,且 this 的值没有在调用时设置的,所以 this 的值默认指向全局对象,浏览器中就是 window

var name = 'mike';

function getContext() {
  // "use strict"; 在严格模式下,如果进入执行环境时没有设置 this 的值,其会保持为 undefined
  // 如 window.getContext()
	console.log(this);
}

getContext(); 	// Window { ..., name: "mike" }
复制代码

对象方法/属性

当函数作为对象里的方法被调用时,this 被设置为调用该函数的对象。 下面这个示例中 this 被设置为 user 对象。

const user = {
  name: 'mike',
  getName: function() {
    return this.name;
  }
};

console.log(user.getName());	// mike
复制代码

构造函数 / 类

构造函数

  • 当一个函数作为构造函数时(使用 new 关键字),它的 this 被绑定到正在构造的新对象

虽然构造函数返回的默认值是 this 所指的那个对象,但仍可以手动返回其他的对象(如果返回值不是一个对象,则返回 this 对象)

/*
 * 构造函数这样工作:
 *
 * function MyConstructor(){
 *   // 函数实体写在这里
 *   // 根据需要在this上创建属性,然后赋值给它们,比如:
 *   this.name = "Tom";
 *   // 等等...
 *
 *   // 如果函数具有返回对象的 return 语句,
 *   // 则该对象将是 new 表达式的结果。
 *   // 否则,表达式的结果是当前绑定到 this 的对象。
 * }
 */

function Man() {
  this.name = 'Mike';
}

const man = new Man();
console.log(man.name); // Mike


function Woman(){
  this.name = 'Lucy';
  return { name: 'Lisa' };
}

const woman = new Woman();
console.log(woman.name); // Lisa
复制代码
  • 箭头函数不能用作构造器,和 new 一起用会抛出错误。
const Foo = () => {};
const foo = new Foo(); // TypeError: Foo is not a constructor
复制代码

  • this 在类中的表现与在函数中类似,因为类本质上也是函数,但也有一些区别和注意事项。

在类的构造函数中,this 是一个常规对象。类中所有非静态的方法都会被添加到 this 的原型中:

class User {
  constructor() {
    const proto = Object.getPrototypeOf(this);
    console.log(Object.getOwnPropertyNames(proto));
  }
  name() {}
  age() {}
  static gender() {}
}

new User(); // ['constructor', 'name', 'age']
复制代码

注意:静态方法不是 this 的属性,它们只是类自身的属性。

  • 和其他普通函数一样,方法中的 this 值取决于它们如何被调用。有时,改写这个行为,让类中的 this 值总是指向这个类实例会很有用。
class Tom {
  constructor() {
    this.sayBye = this.sayBye.bind(this);
  }
  sayHi() {
    console.log(`Hello from ${this.name}`);
  }
  sayBye() {
    console.log(`Bye from ${this.name}`);
  }
  get name() {
    return 'Tom';
  }
}

class Lucy {
  get name() {
    return 'Lucy';
  }
}

const tom = new Tom();
const lucy = new Lucy();

// this 的值取决于他们的调用者
tom.sayHi();							// Hello from Tom
lucy.sayHi = tom.sayHi;
lucy.sayHi();							// Hello from Lucy

lucy.sayBye = tom.sayBye;
lucy.sayBye();						// Bye from Tom
复制代码

改变 this

call/apply/bind

call/apply/bind 这几种都可以改变 this 的值,其区别在于入参和执行时机的不同。我们重点来看一下关于改变 this 的用法。 非严格模式下,则指定为 null 或 undefined 时会自动替换为指向全局对象,原始值会被包装(window/global 对象)

// 对象可以作为 bind 或 apply 的第一个参数传递,并且该参数将绑定到该对象。
var lucy = { name: 'Lucy' };

// 声明一个变量,并将该变量作为全局对象 window 的属性。
var name = 'Mike';

var tom = {
    name: 'Tom',
    getName: function() {
        return this.name;  // this 的值取决于函数被调用的方式
    }
}

var getName = tom.getName

getName();            // 'Mike' 因为在这个函数中 this 没有被设定,所以它默认为 全局 / window 对象
tom.getName();        // 'Tom' 因为是对象调用方法,this 值为调用的对象
getName.call(lucy);   // 'Lucy' 因为函数中的 this 被设置为 lucy
getName.apply(lucy);  // 'Lucy' 因为函数中的 this 被设置为 lucy

var getLucyName = getName.bind(lucy)		// 'Lucy' 因为函数中的 this 被设置为 lucy
var getTomName = getLucyName.bind(tom)	// 'Lucy' bind 只生效一次!
var regainTomName = getName.bind(tom)		// 'Tom' 因为函数中的 this 被设置为 tom

getLucyName()
getTomName()
regainTomName()
复制代码

注意:箭头函数没有自己的 this 指针,通过 call/apply/bind 方法调用一个函数时,只能传递参数,他们的第一个参数会被忽略,不能绑定 this,第一个参数(thisArg)应该设置为 null

setTimeout/setInterval

在默认情况下,使用 window.setTimeout() 时,this 关键字会指向 window/global 对象。如果要让 this 正确赋值时,需要显式地把 this 绑定到回调函数。

function User() {
  this.age = Math.ceil(Math.random() * 100);
}

User.prototype.getAge = function() {
  window.setTimeout(this.showAge.bind(this), 1000);	// this 关键字会指向 window/global 对象
};

User.prototype.showAge = function() {
  console.log(`已经 ${this.age} 岁了`);
};

var Tom = new User();
Tom.getAge();  // 一秒钟后, 调用 'showAge' 方法
复制代码

箭头函数

箭头函数不提供自身的 this 绑定(this 的值将保持为闭合词法上下文的值)。 箭头函数不会创建自己的 this, 它只会从自己的作用域链的上一层继承 this

function Man() {
  this.age = 0;

  setInterval(() => {
    this.age++;		// this 的值为 man 实例
  }, 1000);
}

const man = new Man();

setTimeout(() => {
    console.log(man.age)	// 3
}, 3000)
复制代码

事件监听

DOM 事件处理函数

当使用 addEventListener() 为一个元素注册事件的时候,句柄里的 this 值是该元素的引用。其与传递给句柄的 event 参数的 currentTarget 属性的值一样。

// 被调用时,将关联的元素变成蓝色
function bluify(e) {
  console.log(this === e.currentTarget); // 总是 true

  // 当 currentTarget 和 target 是同一个对象时为 true
  // currentTarget 是当前事件监听的目标元素,target 是事件的触发元素
  console.log(this === e.target);
  this.style.backgroundColor = '#a5d9f3';
}

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

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

内联事件处理函数

当代码被内联 (on-event 处理函数,类似 onclickonkeypressonfocus)调用时,它的 this 的值为监听器所在的 DOM 元素

<button onclick="alert(this.tagName.toLowerCase());">
  Show this
</button>
复制代码

总结

this 的值.png

分类:
前端
标签:
分类:
前端
标签:
收藏成功!
已添加到「」, 点击更改