Skip to content

This 指向

1. 调用位置

  • 作用域跟在哪里定义有关,与在哪里执行无关
  • this指向跟在哪里定义无关,跟如何调用,通过什么样的形式调用有关
  • this(这个) 这个函数如何被调用(方便记忆)
  • 为了方便理解,默认情况下不开启严格模式

2. 绑定规则

  上面我们介绍了,this的指向主要跟通过什么样的形式调用有关。接下来我就给大家介绍一下调用规则,没有规矩不成方圆,大家把这几种调用规则牢记于心就行了,没有什么难的地方。

  • 你必须找到调用位置,然后判断是下面四种的哪一种绑定规则
  • 其次你要也要晓得,这四种绑定规则的优先顺序
  • 这两点你都知道了,知道 this 的指向对于你来说易如反掌

2.1 默认绑定

   函数最常用的调用方式,调用函数的类型:独立函数调用

js
function bar() {
  console.log(this); // window
}
  • bar 是不带任何修饰符的直接调用 所以为默认绑定 为window
  • 在严格模式下 这里的thisundefined

2.2 隐式绑定

  用最通俗的话表示就是:对象拥有某个方法,通过这个对象访问方法且直接调用(注:箭头函数特殊,下面会讲解)

js
const info = {
  fullName: "ice",
  getName: function () {
    console.log(this.fullName);
  },
};

info.getName(); // 'ice'
  • 这个函数被info发起调用,进行了隐式绑定,所以当前的thisinfo,通过this.fullName毫无疑问的就访问值为ice

隐式丢失 普通

  有些情况下会进行隐式丢失,被隐式绑定的函数会丢失绑定对象,也就是说它变为默认绑定,默认绑定的this值,为window还是undefined取决于您当前所处的环境,是否为严格模式。

js
const info = {
  fullName: "ice",
  getName: function () {
    console.log(this.fullName);
  },
};

const fn = info.getName;

fn(); //undefined

  这种情况下就进行了隐式丢失,丢失了绑定的对象,为什么会产生这样的问题呢?如果熟悉内存的小伙伴,就会很容易理解。

  • 这里并没有直接调用,而是通过info找到了对应getName的内存地址,赋值给变量fn
  • 然后通过fn 直接进行了调用
  • 其实这里的本质就是独立函数调用也就是为window,从window中取出fullName属性,必定为undefined

隐式丢失进阶
  这里大家首先要理解什么是回调函数。其实可以这样理解,就是我现在不调用它,把他通过参数的形式传入到其他地方,在别的地方调用它。

js
// 申明变量关键字必须为var
var fullName = "panpan";

const info = {
  fullName: "ice",
  getName: function () {
    console.log(this.fullName);
  },
};

function bar(fn) {
  //fn = info.getName
  fn(); // panpan
}

bar(info.getName);
  • 首先bar中的fn为一个回调函数
  • fn = info.getName 参数传递就是一种隐式赋值,其实跟上面的隐式丢失是一个意思,他们都是指向的getName引用,也就是它的的内存地址
  • 因为他们的this丢失,this为全局的window对象
  • 注意: 为什么申明必须为var呢?
    • 因为只有var申明的变量才会加入到全局window对象上
    • 如果采用let\const 则不是,具体的后续介绍一下这两个申明变量的关键字
  • 但是有些场景,我不想让隐式丢失怎么办,下面就来给大家介绍一下显示绑定,也就是固定调用。

2.3 显示绑定

  但是在某些场景下,this的改变都是意想不到的,实际上我们无法控制回调函数的执行方式,因此没有办法控制调用位置已得到期望的绑定即 this 指向。

接下来的显示绑定就可以用来解决这一隐式丢失问题。

call / apply / bind

  js 中的 ”所有“函数都有一些有用的特性,这个跟它的原型链有关系,后续我会在原型介绍,通过原型链 js 中变相实现继承的方法,其中call/apply/bind这三个方法就是函数原型链上的方法,可以在函数中调用它们。

2.3.1 call

  • call() 方法使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数。
    • 第一个参数为固定绑定的this对象
    • 第二个参数以及二以后的参数,都是作为参数进行传递给所调用的函数
    • 该方法的语法和作用与  apply()  方法类似,只有一个区别,就是  call()  方法接受的是一个参数列表,而  apply()  方法接受的是一个包含多个参数的数组
js
var fullName = "panpan";

const info = {
  fullName: "ice",
  getName: function (age, height) {
    console.log(this.fullName, age, height);
  },
};

function bar(fn) {
  fn.call(info, 20, 1.88); //ice 20 1.88
}

bar(info.getName);

2.3.2 apply

  • call的方法类似,只是参数列表有所不同
    • call 参数为单个传递
    • apply 参数为数组传递
js
var fullName = "panpan";

const info = {
  fullName: "ice",
  getName: function (age, height) {
    console.log(this.fullName, age, height);
  },
};

function bar(fn) {
  fn.apply(info, [20, 1.88]); //ice 20 1.88
}

bar(info.getName);

2.3.3 bind

  • bindapply/call之间有所不同,bind传入this,则是返回一个this绑定后的函数,调用返回后的函数,就可以拿到期望的 this。
  • 参数传递则是
    • 调用bind时,可以传入参数
    • 调用bind返回的参数也可以进行传参
js
var fullName = "panpan";

const info = {
  fullName: "ice",
  getName: function (age, height) {
    console.log(this.fullName, age, height); //ice 20 1.88
  },
};

function bar(fn) {
  let newFn = fn.bind(info, 20);
  newFn(1.88);
}

bar(info.getName);

2.4 new 绑定

  谈到new关键字,就不得不谈构造函数,也就是 JS 中的 "类",后续原型篇章在跟大家继续探讨这个 new 关键字,首先要明白以下几点,new Fn()的时候发生了什么,有利于我们理解this的指向。

  1. 创建了一个空对象
  2. 将 this 指向所创建出来的对象
  3. 把这个对象的[[prototype]] 指向了构造函数的 prototype 属性
  4. 执行代码块代码
  5. 如果没有明确返回一个非空对象,那么返回的对象就是这个创建出来的对象
js
function Person(name, age) {
  this.name = name;
  this.age = age;
}

const p1 = new Person("ice", 20);

console.log(p1); // {name:'ice', age:20}
  • 当我调用new Person()的时候,那个 this 所指向的其实就是p1对象

3. 绑定优先级

3.1 隐式绑定 > 默认绑定

js
function bar() {
  console.log(this); //info
}

const info = {
  bar: bar,
};

info.bar();
  • 虽然这样比较有些勉强,有些开发者会认为这是默认绑定的规则不能直接的显示谁的优先级高
  • 但是从另外一个角度来看,隐式绑定的 this 丢失以后 this 才会指向 widonw 或者 undefined,变相的可以认为隐式绑定 > 默认绑定

3.2 显示绑定 > 隐式绑定

js
var fullName = "global ice";
const info = {
  fullName: "ice",
  getName: function () {
    console.log(this.fullName);
  },
};

info.getName.call(this); //global ice
info.getName.apply(this); //global ice
info.getName.bind(this)(); //global ice
  • 通过隐式绑定和显示绑定的一起使用很明显 显示绑定 > 隐式绑定

3.3 bind(硬绑定) > apply/call

js
function bar() {
  console.log(this); //123
}

const newFn = bar.bind(123);
newFn.call(456);

3.4 new 绑定 > bind 绑定

首先我们来说一下,为什么是和bind比较,而不能对callapply比较,思考下面代码

js
const info = {
  height: 1.88,
};

function Person(name, age) {
  this.name = name;
  this.age = age;
}

const p1 = new Person.call("ice", 20);

//报错: Uncaught TypeError: Person.call is not a constructor

new 绑定和 bind 绑定比较

js
const info = {
  height: 1.88,
};

function Person(name, age) {
  this.name = name;
  this.age = age;
}

const hasBindPerson = Person.bind(info);

const p1 = new hasBindPerson("ice", 20);

console.log(info); //{height: 1.88}
  • 我们通过bindPerson进行了一次劫持,硬绑定了 this 为info对象
  • new 返回的固定 this 的函数
  • 但是我们发现并不能干预 this 的指向

3.5 总结

new关键字 > bind > apply/call > 隐式绑定 > 默认绑定

4. 箭头函数 (arrow function)

首先箭头函数是ES6新增的语法

JS
const foo = () => {}

4.1 箭头函数 this

js
var fullName = "global ice";

const info = {
  fullName: "ice",
  getName: () => {
    console.log(this.fullName);
  },
};

info.getName(); //global ice
  • 你会神奇的发现? 为什么不是默认绑定,打印结果为ice
  • 其实这是ES6的新特性,箭头函数不绑定this,它的this是上一层作用域,上一层作用域为window
  • 所以打印的结果是 global ice

4.2 箭头函数的应用场景 进阶

  • 需求: 在getInternalName通过this拿到info中的fullName (值为icefullName)
js
const info = {
  fullName: "ice",
  getName: function () {
    let _this = this;
    return {
      fullName: "panpan",
      getInternalName: function () {
        console.log(this); // obj
        console.log(_this.fullName);
      },
    };
  },
};

const obj = info.getName();
obj.getInternalName();
  1. 当我调用 info.getName() 返回了一个新对象
  2. 当我调用返回对象的getInternalName方法时,我想拿到最外层的fullName,我通过,getInternalName的 this 访问,拿到的 this 却是obj,不是我想要的结果
  3. 我需要在调用info.getName() 把 this 保存下来,info.getName() 是通过隐式调用,所以它内部的 this 就是 info 对象
  4. getInternalName是 obj 对象,因为也是隐式绑定,this 必定是 obj 对象,绕了一大圈我只是想拿到上层作用域的 this 而已,恰好箭头函数解决了这一问题
js
const info = {
  fullName: "ice",
  getName: function () {
    return {
      fullName: "panpan",
      getInternalName: () => {
        console.log(this.fullName);
      },
    };
  },
};

const obj = info.getName();
obj.getInternalName();

5. 总结

5.1 this 的四种绑定规则

  1. 默认绑定
  2. 隐式绑定
  3. 显示绑定 apply/call/bind(也称硬绑定)
  4. new 绑定

5.2 this 的优先级 从高到低

  1. new 绑定
  2. bind
  3. call/apply
  4. 隐式绑定
  5. 默认绑定

转载请注明出处,违者必究