「JavaScript」轻松拿捏this

92 阅读4分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 5 天,点击查看活动详情

this是什么

JavaScript 中,this 是一个动态型变量指针,this的指向是在函数被调用的时候确定的,动态指向当前函数的运行环境,这就导致this的指向会让人迷惑,可以说this具有运行期绑定的特性。

1. 全局

全局环境中的 this 指向它本身,在浏览器里面 this 等价于window对象, var === this === winodw,如果你声明一些全局变量,这些变量都会作为this的属性。

a = 1
var b = 2;
this.c = 3;
window.d = 4;

console.log(window === this); // true
console.log(a,b,c,d); // 1,2,3,4

2. 函数

this的指向,是在函数被调用的时候确定的。 来两个栗子瞅瞅

// 栗子一

var a = 1
function fn() {
  let a = 2
  console.log(this.a)
}

fn() // 1

调用 fn()相当于window.fn()fn可以说是被window所拥有,所以this指向window全局对象。

// 栗子二

var a = 1
function fn() {
  let a = 2
  function foo() {
    console.log(this.a)
  }
  foo()
}

fn() // 1

在一个函数上下文中,this由调用者提供,由调用函数的方式来决定,如果函数独立调用,那么该函数内部的this则指向undefined。但是在非严格模式中,当this指向undefined时,它会被自动指向全局对象。

3. 作为对象的一个方法

如果调用者函数,被某一个对象所拥有,那么该函数在调用时,内部的this指向该对象。

var a = 1
var obj = {
  a: 2,
  c: this.a + 10,
  fn: function () {
    return this.a
  }
}

console.log(obj.c)    // 11
console.log(obj.fn()) // 2

注意:单独的{}不会形成新的作用域,计算obj.c,因为没有作用域限制,此时this还是指向全局,即打印11。调用fn()obj对象所拥有,this即指向obj

特殊栗子

var num = 10;
var obj = {
  num: 20,
  getNum: function(){
    return this.num;
  }
}
var test = obj.getNum;
console.log(test()); // 10

注意,test是独立调用的,因此this指向undefined,在非严格模式,自动转向全局window

4. 作为一个构造函数

构造函数的this永远指向实例化对象

why?我们一起来看看,通过构造函数创建一个实例对象的几个步骤:

一个构造函数使用new操作符调用的时候,会生成一个具有构造函数相同属性的新对象。

  1. 创建一个新的对象
  2. 将构造函数的this指向这个新对象
  3. 给新对象赋值(属性、方法)
  4. 返回这个对象
function Member(name, age = 18) {
  this.name = name;
  this.age = age;
  this.info = function () {
    console.log(this.name + "," + this.age);
  }
}
var member = new Member("xiaoyu");
console.log(member); // Member { name: 'xiaoyu', age: '18',info: ƒ () }
member.info() // xiaoyu,18

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

this指向触发事件的元素

var el = document.getElementById("box");

el.addEventListener("click",function(e){
  console.dir(this); // duv#box
  console.log(this === e.target); // true
})

6. 箭头函数

所有的箭头函数都没有自己的this,它不会创建自己的this,只能捕获其所在上下文的this值

栗子

var name = '123';
var obj = {
    name: '456',
    print: function() {
        function a() {
            console.log(this.name);
        }
        a();
    }
}

obj.print();  // 123

这里是普通函数调用,调用 a 相当于 window.fn(),获取全局的 name 所以打印 123,但是我们把它改造成箭头函数

var name = '123';
var obj = {
    name: '456',
    print: function() {
        const a = () => {
            console.log(this.name);
        }
        a();
    }
}

obj.print();  // 456

可以看到结果是456,因为箭头函数不会创建自己的this,所以它没有自己的this,它只会从自己的作用域链的上一层继承this,所以是 obj

7. apply()、call()、bind()

this绑定到指向的对象上。

apply() 和 call() 的第一个参数都是 this,区别在于通过 apply 调用时实参是放到数组中的,而通过 call 调用时实参是逗号分隔的。

function fn(sex, age) {
  console.log(this.name , sex , age);
}
var obj = {
  name: 'xiaoyu'
}

fn.call(obj, '男', 20); // xiaoyu 男 20
fn.apply(obj, ['女', 30]); // xiaoyu 女 30

可以看到,fn并非属于对象 obj 的方法,但是通过 call/apply 我们可以将 fn 内部的 this 绑定为 obj,因此就可以使用 this.name 访问 objname 属性了。

bindcallapply有些相似,只是绑定方式不同, bind() 方法创建一个新的函数, 当被调用时,将其this 关键字设置为提供的值,在调用新函数时,在任何提供之前提供一个给定的参数序列,需要手动调用

function say(){
  console.log(this.name);
}
var f = say.bind(obj);
console.log(f()); // xiaoyu

总结

都放在每一个点的第一句话了,总结完毕。

image.png