this的指向问题

91 阅读3分钟

1. 全局对象中的this

全局环境中的this,指向它本身。浏览器中指向window。

2. 函数中的this

在函数中,this的指向由调用者决定。

// demo02
var a = 20;
function fn() {
  function foo() {
    console.log(this.a);
  }
  foo();
}
fn(); // 20

上述代码中,fn在全局中调用,所以fn的this指向全局也就是window,foo在fn中调用,所以foo的this指向fn的this也是全局window。注意!在严格模式下,this是undefined。

再来观察如下代码

// demo03
var a = 20;
var obj = {
  a: 10,
  c: this.a + 20,
  fn: function () {
    return this.a;
  }
}

console.log(obj.c);
console.log(obj.fn());

首先obj.c中的this,因为对象并不是一个作用域,所以指向全局为20+20。关键是这里的fn是在obj中调用的!所以这里我们引出一个概念:函数的调用方式

如果调用函数的家伙被某一个对象拥有,那么函数调用的时候,内部的this指向该对象。即上述代码的this指向obj,this.a就是10。

3. 使用call、apply、bind显示指定this

1.call和apply

call和apply除了参数略有不同,其功能完全一样。

function fn(num1, num2) {
  console.log(this.a + num1 + num2);
}
var obj = {
  a: 20
}

fn.call(obj, 100, 10); // 130
fn.apply(obj, [20, 10]); // 50

2. call和apply的使用场景

2.1 将类数组对象转换为数组

function exam(a, b, c, d, e) {

  // 先看看函数的自带属性 arguments 什么是样子的
  console.log(arguments);

  // 使用call/apply将arguments转换为数组, 返回结果为数组,arguments自身不会改变
  var arg = [].slice.call(arguments); // 利用array原型上的slice方法。

  console.log(arg);
}

exam(2, 8, 9, 10, 3);

// result:
// { '0': 2, '1': 8, '2': 9, '3': 10, '4': 3 }
// [ 2, 8, 9, 10, 3 ]
//
// 也常常使用该方法将DOM中的nodelist转换为数组
// [].slice.call( document.getElementsByTagName('li') );

2.2 通过构造函数实现继承

function  SuperType(){
    this.color=["red", "green", "blue"];
}
function  SubType(){
    // 核心代码,继承自SuperType
    SuperType.call(this);
}

var instance1 = new SubType();
instance1.color.push("black");
console.log(instance1.color);
// ["red", "green", "blue", "black"]

var instance2 = new SubType();
console.log(instance2.color);
// ["red", "green", "blue"]

缺点:

  • 只能继承父类的实例属性和方法,不能继承原型属性/方法
  • 无法实现复用,每个子类都有父类实例函数的副本,影响性能

3. bind

bind和call、apply有些相似,他是把this永久的绑定到了bind的第一个参数上。

var person = {
  name: "axuebin",
  age: 25
};
function say(){
  console.log(this.name+":"+this.age);
}
var f = say.bind(person);
console.log(f());

4. 手写call

5. 手写apply

4. 构造函数与原型方法上的this

在构造函数中我们经常使用this,那么this到底是什么呢?我们先给结论:this被绑定到正在构造的新对象上。

那么在new一个对象时的步骤我们必须要清楚:

  1. 创建一个新对象。
  2. 将这个新对象的[[prototype]]指向构造函数的prototype
  3. 将构造函数的this指向obj
  4. 给对象赋值(属性、方法)
  5. 返回this

可见这个this指向创建的新对象person上。

function Person(name){
  this.name = name;
  this.age = 25;
  this.say = function(){
    console.log(this.name + ":" + this.age);
  }
}
var person = new Person("axuebin");
console.log(person.name); // axuebin
person.say(); // axuebin:25

4.1 手写new

5. 箭头函数

所有的箭头函数都没有自己的this,会捕获其所在上下文的this作为自己的this。

function Person(name){
  this.name = name;
  // 上下文
  this.say = () => {
    var name = "xb";
    return this.name;
  }
  // 上下文
}
var person = new Person("lkh");
console.log(person.say()); // lkh

上述代码可以观察到,箭头函数的上下文处对应的this是构造函数Person的this,当new出来person对象时,this指向person,所以箭头函数的this也指向person对象,所以this.name为lkh.

6. 作为一个DOM事件处理函数

this指向触发事件的元素,也就是始事件处理程序所绑定到的DOM节点。

var ele = document.getElementById("id");
ele.addEventListener("click",function(e){
  console.log(this);
  console.log(this === e.target); // true
})