JS高级 this的那些事儿

120 阅读5分钟

this的那些事儿

this指向什么

this在全局作用域指向window

console.log(this); // window
var name = "hello";//给window添加了一个name属性,值为 hello
console.log(this.name); // hello
console.log(window.name); // hello

但是我们在开发中很少在全局作用域下去使用this,通常都是在函数中使用

this绑定规则

规则1:默认绑定

独立函数调用时,使用默认绑定规则

1.1 普通函数调用

  • 该函数直接被调用,并没有进行任何的对象关联;
  • 这种独立的函数调用会使用默认绑定,通常默认绑定时,函数中的this指向全局对象(window);
function foo() {
  console.log(this); // window
}
foo();

1.2 函数调用链(一个函数又调用另外一个函数)

  • 所有的函数调用都没有绑定到某个对象上
function test1() {
  console.log(this); // window
  test2();
}
function test2() {
  console.log(this); // window
  test3()
}
function test3() {
  console.log(this); // window
}
test1();

1.3 函数作为参数,传入到另一个函数中

function foo(func) {
  func()
}
function bar() {
  console.log(this); // window
}
foo(bar);

变形案例

function foo(func) {
  func()
}
var obj = {
  name"why",
  barfunction() {
    console.log(this); // window
  }
}
foo(obj.bar);

解析:调用obj.bar,本质上是调用一个“匿名”函数,这个函数没有绑定啥对象,虽然是在obj内部,但他依旧没有绑定,他还作为函数的参数传入,在另一个函数体内被调用,但是还是没有绑定啥对象,依旧孑然一身,所以在只能指向(投靠)window

规则2 隐式绑定

通过某个对象进行调用的,也就是它的调用位置中,是通过某个对象发起的函数调用。

2.1 通过对象调用函数

foo的调用位置是obj.foo()方式进行调用的,那么foo调用时this会隐式的被绑定到obj对象上

function foo() {
  console.log(this); // obj对象
}
var obj = {
  name"why",
  foo: foo
}
obj.foo();

2.2 复杂的函数调用

this就近原则绑定obj1

function foo() {
    console.log(this); // {"name":"obj1"}
}
var obj1 = {
    name: "obj1",
    foo: foo
}
var obj2 = {
    name: "obj2",
    obj1: obj1
}
obj2.obj1.foo();

2.3 特殊案例-隐式丢失

本质上是吧obj1的foo函数体赋值给bar变量,bar执行的时候没有任何绑定,所以隐式绑定丢失,变成默认绑定,指向window

function foo() {
  console.log(this);
}
var obj1 = {
  name"obj1",
  foo: foo
}
// 把obj1的foo赋值给bar
var bar = obj1.foo;
bar();

规则3 显示绑定

利用apply和call函数明确的绑定了this指向的对象,所以称之为 显示绑定。

3.1 通过call或者apply绑定this对象

call 和apply的使用区别:
第一个参数都是一个对象,即this指向的对象,后面的参数调用略有不同
call后面是一个个参数(call一个个小弟来帮忙)
apply第二个参数是存放要使用的参数的数组[]
function foo() {
  console.log(this);
}
foo.call(window); // window
foo.call({name"why"}); // {name: "why"}
foo.call(123); // Number对象,存放时123

3.2 如果我们希望一个函数总是显示的绑定到一个对象上,可以怎么做呢?

方案一:自己手写一个辅助函数(了解)

  • 我们手动写了一个bind的辅助函数
  • 这个辅助函数的目的是在执行foo时,总是让它的this绑定到obj对象上
function foo() {
  console.log(this);
}
var obj = {
  name: "why"
}
function bind(func, obj) {
  return function() {
    return func.apply(obj, arguments);
  }
}
var bar = bind(foo, obj);
bar(); // obj对象
bar(); // obj对象
bar(); // obj对象

方案二:使用Function.prototype.bind 函数用法:function.bind({obj})

function foo() {
  console.log(this);
}
var obj = {
  name: "why"
}
var bar = foo.bind(obj);
bar(); // obj对象
bar(); // obj对象
bar(); // obj对象

4 规则优先级

1.默认规则的优先级最低

毫无疑问,默认规则的优先级是最低的,因为存在其他规则时,就会通过其他规则的方式来绑定this

2.显示绑定优先级高于隐式绑定

显示绑定和隐式绑定哪一个优先级更高呢?这个我们可以测试一下:

  • 结果是obj2,说明是显示绑定生效了
function foo() {
  console.log(this);
}
var obj1 = {
  name"obj1",
  foo: foo
}
var obj2 = {
  name"obj2",
  foo: foo
}
// 隐式绑定
obj1.foo(); // obj1
obj2.foo(); // obj2
// 隐式绑定和显示绑定同时存在
obj1.foo.call(obj2); // obj2, 说明显式绑定优先级更高

3.new绑定优先级高于隐式绑定

  • 结果是foo,说明是new绑定生效了
function foo() {
  console.log(this);
}
var obj = {
  name"why",
  foo: foo
}
new obj.foo(); // {} foo对象, 说明new绑定优先级更高

4.new绑定优先级高于bind

new绑定和call、apply是不允许同时使用的,所以不存在谁的优先级更高

function foo() {
  console.log(this);
}
var obj = {
  name: "obj"
}
// var foo = new foo.call(obj);
var bar = foo.bind(obj);
var foo = new bar(); // 打印{} foo, 说明使用的是new绑定

优先级总结:

  • new绑定 > 显示绑定(bind)> 隐式绑定 > 默认绑定

5. ES6箭头函数

在ES6中新增一个非常好用的函数类型:箭头函数 箭头函数不使用this的四种标准规则(也就是不绑定this),而是根据外层作用域来决定this。 我们来看一个模拟网络请求的案例:

  • 这里我使用setTimeout来模拟网络请求,请求到数据后如何可以存放到data中呢?
  • 我们需要拿到obj对象,设置data;
  • 但是直接拿到的this是window,我们需要在外层定义:var _this = this
  • 在setTimeout的回调函数中使用_this就代表了obj对象
var obj = {
  data: [],
  getData: function() {
    var _this = this;
    setTimeout(function() {
      // 模拟获取到的数据
      var res = ["abc""cba""nba"];
      _this.data.push(...res);
    }, 1000);
  }
}
obj.getData();

上面的代码在ES6之前是我们最常用的方式,从ES6开始,我们会使用箭头函数:

  • 为什么在setTimeout的回调函数中可以直接使用this呢?
  • 因为箭头函数并不绑定this对象,那么this引用就会从上层作用域中找到对应的this
var obj = {
  data: [],
  getData: function() {
    setTimeout(() => {
      // 模拟获取到的数据
      var res = ["abc""cba""nba"];
      this.data.push(...res);
    }, 1000);
  }
}
obj.getData();

思考:如果getData也是一个箭头函数,那么setTimeout中的回调函数中的this指向谁呢?

  • 答案是window;
  • 依然是不断的从上层作用域找,那么找到了全局作用域;
  • 在全局作用域内,this代表的就是window
var obj = {
  data: [],
  getData() => {
    setTimeout(() => {
      console.log(this); // window
    }, 1000);
  }
}
obj.getData();