# This

# 分辨 this 指向

综述

  1. 在函数体中,非显式或隐式地简单掉用函数时,严格模式下,函数内的 this 指向 undefined;非严格模式下,指向全局对象 window/global 上。
  2. 通过上下文对象调用函数时,函数体内的 this 指向调用函数的对象。
  3. 通过 call/apply/bind 方法显式调用函数时,函数体内的 this 指向指定的参数对象上。
  4. 使用 new 方法调用构造函数时,构造函数内的 this 会指向新创建的对象上。
  5. 箭头函数中, this 指向由外层(函数或全局)作用域来决定。

1. 在函数体中,非显式或隐式地简单掉用函数时,严格模式下,函数内的 this 指向 undefined;非严格模式下,指向全局对象 window/global 上。

function fn1() {
  console.log(this);
}

function fn2() {
  "use strict";
  console.log(this);
}

fn1(); // window

fn2(); // undefined

2. 通过上下文对象调用函数时,函数体内的 this 指向调用函数的对象。

const o1 = {
  text: "o1",
  fn: function () {
    return this.text;
  },
};

const o2 = {
  text: "o2",
  fn: function () {
    return o1.fn();
  },
};

const o3 = {
  text: "o3",
  fn: function () {
    var fn = o1.fn;
    return fn();
  },
};

console.log(o1.fn());
console.log(o2.fn());
console.log(o3.fn());

// 输出1: o1对象调用自己的函数,输出 o1
// 输出2: o2调用fn,但内部实际仍是 o1调用自己的函数f,输出 o1
// 输出3: o3调用fn,内部 声明表达式将 o1.fn 赋值给了 fn,调用 fn的其实是 window,window没有 text,所以是 undefined

3. 通过 bind、call、apply 改变 this 指向

区别

  1. call 与 apply 是直接进行相关函数调用。fn1.call(target, args)
  2. call 与 apply 主要区别是传参形式不同,apply 参数是数组。
  3. bind 不执行函数,而是返回一个新的函数,this 已经指向了新的函数,需要开发者手动调用新函数才行。fn1.bind(target, args)()
// call
const target = {};
fn.call(target, "arg1", "arg2");

// apply
const target = {};
fn.apply(target, ["arg1", "arg2"]);

// bind
const target = {};
fn.bind(target, "arg1", "arg2")();
const foo = {
  name: "foo",
  logName: function () {
    console.log(this.name);
  },
};

const bar = {
  name: "bar",
};

console.log(foo.logName.call(bar)); // bar

4. 使用 new 方法调用构造函数时,构造函数内的 this 会指向新创建的对象上。

function Foo() {
  this.bar = "foo-bar";
}
const instance = new Foo();
console.log(instance.bar); // foo-bar

new 时做了什么?

  • 创建一个新的对象
  • 将构造函数的 this 指向新的对象
  • 给这个对象添加属性、方法、原型链等
  • 返回新的对象

new 一个构造函数过程如下:

var obj = {};
obj.__proto__ = Foo.prototype;
Foo.call(obj);

构造函数踩坑点

  1. 如果构造函数中显式的 return 了一个对象(复杂类型),this 就指向这个返回的对象
function Foo() {
  this.user = "user";
  const o = {
    user: "o-user",
  };
  return o;
}

const instance = new Foo();
console.log(instance.user); // o-user, this 指向了返回的对象 o
  1. 如果构造函数返回的不是一个对象(返回基本类型),this 指向当前实例
function Foo() {
  this.user = "user";

  return 1;
}

const instance = new Foo();

console.log(instance.user); // user

# this 指向优先级

通常 call、apply、bind、new 这些对 this 进行绑定的情况称为 显式绑定;根据调用关系来确定 this 指向的情况称为隐式绑定。

综述

  1. call、apply、bind 优先级高于普通调用方式
  2. new 一个构造函数的方式优先级高于 call、apply、bind 的方式
  3. 箭头函数的 this 指向无法改变

另外,如果一个构造函数已经被 bind 方法改变过了 this 指向,再调用 call 方法也是无法改变 this 指向的。 所以,bind 方法的独特之处在于它创建了一个永久绑定的 this 值。

function greet() {
  return `你好,我是 ${this.name}`;
}

let person1 = { name: "Alice" };
let person2 = { name: "Bob" };

// 创建一个与 `person1` 绑定的函数
let greetPerson1 = greet.bind(person1);

console.log(greetPerson1()); // 你好,我是 Alice

// 尝试使用 `call` 方法更改上下文;但是,它仍然使用 `person1` 作为 `this` 上下文
console.log(greetPerson1.call(person2)); // 你好,我是 Alice

// 相比之下,正常函数调用允许使用 `call` 方法设置 `this` 上下文
console.log(greet.call(person2)); // 你好,我是 Bob

1. call、apply、bind 优先级高于普通调用方式

function foo(a) {
  console.log(this.a)
}

const obj1 = {
  a: 1,
  foo: foo
}

const obj2 = {
  a: 2
  foo: foo
}


obj1.foo.call(obj2)  // 2
obj2.foo.call(obj1) // 1

2. new 一个构造函数的方式优先级高于 call、apply、bind 的方式

function foo(a) {
  this.a = a;
}

const obj1 = {};

var bar = foo.bind(obj1);

bar(2);

console.log(obj1.a); // 2, bind 之后 obj1 就变成了 {a:xx} 的形式了

// new 开始调用

var baz = new bar(3);

console.log(baz.a); // 3

bar 函数通过 bind 方法生成了新函数,将 this 指向了 obj1。当新的函数被 new 调用后,返回的实例已经与 obj1 解绑了。也就是说,new 改变了 bind 的 this 指向

3. 箭头函数的 this 指向无法改变

function foo() {
  return (a) => {
    console.log(this.a);
  };
}

const obj1 = {
  a: "obj1",
};

const obj2 = {
  a: "obj2",
};

const bar = foo.call(obj1);

console.log(bar.call(obj2)); // obj1

foo.call(obj1) 执行之后,bar 函数就是内部的箭头函数,它的 this 指向在函数上下文环境中是 obj1

后面虽然通过 call 绑定了 obj2,但是箭头函数的 this 指向不能改变,所以 this 仍然是 obj1

看个简单的

var a = "全局的a";

const foo = () => {
  return (a) => {
    console.log(this.a);
  };
};

const obj1 = {
  a: "obj1-a",
};

const obj2 = {
  a: "obj2-a",
};

const bar = foo.call(obj1);

console.log(bar.call(obj2)); // 全局的 a

因为 foo 的 this 是 window,a 默认是 window 的属性。所以,就找到了全局的 a

给这个简单的,挖一个坑:

const a = "全局的a";

const foo = () => {
  return (a) => {
    console.log(this.a);
  };
};

const obj1 = {
  a: "obj1-a",
};

const obj2 = {
  a: "obj2-a",
};

const bar = foo.call(obj1);

console.log(bar.call(obj2)); // undefined

因为,const 声明的变量不会挂载到 window 的全局变量上

# 自我作业:手写 bind