this指向

this指向

1. this 指向

  1. 函数在调用时,JavaScript 会默认给 this 绑定一个值;
  2. this 的绑定和定义的位置(编写的位置)没有关系;
  3. this 的绑定和调用方式以及调用的位置有关系;
  4. this 是在运行时被绑定的;

2. this 绑定规则

2.1. 默认绑定

独立的函数调用我们可以理解成函数没有被绑定到某个对象上进行调用;

// 定义函数
// 1.普通的函数被独立的调用
function foo() {
  console.log("foo:", this);
}
foo();

// 2.函数定义在对象中, 但是独立调用
var obj = {
  name: "why",
  bar: function () {
    console.log("bar:", this);
  },
};
var baz = obj.bar;
baz();

// 3.高阶函数
function test(fn) {
  fn();
}
test(obj.bar);

// 4.严格模式下, 独立调用的函数中的this指向的是undefined

2.2. 隐式绑定

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

// 隐式绑定
function foo() {
  console.log("foo函数:", this);
}

var obj = {
  bar: foo,
};

obj.bar();

2.3. 显示绑定

/*
  1.创建新的空对象
  2.将this指向这个空对象
  3.执行函数体中的代码
  4.没有显示返回非空对象时, 默认返回这个对象
*/
function foo() {
  this.name = "why";
  console.log("foo函数:", this);
}

new foo();

2.4. new 绑定

// 显式绑定
var obj = {
  name: "why",
};

function foo() {
  console.log("foo函数:", this);
}

// 执行函数, 并且函数中的this指向obj对象
// obj.foo = foo
// obj.foo()

// 执行函数, 并且强制this就是obj对象
foo.call(obj);
foo.call(123);
foo.call("abc");

3. 规则三:显式绑定

隐式绑定有一个前提条件:

  • 必须在调用的对象内部有一个对函数的引用(比如一个属性);
  • 如果没有这样的引用,在进行调用时,会报找不到该函数的错误;
  • 正是通过这个引用,间接的将 this 绑定到了这个对象上;

如果我们不希望在 对象内部 包含这个函数的引用,同时又希望在这个对象上进行强制调用,该怎么做呢?

JavaScript 所有的函数都可以使用 call 和 apply 方法。

  • 第一个参数是相同的,要求传入一个对象;
    • 这个对象的作用是什么呢?就是给 this 准备的。
    • 在调用这个函数时,会将 this 绑定到这个传入的对象上。
  • 后面的参数,apply 为数组,call 为参数列表;

img

因为上面的过程,我们明确的绑定了 this 指向的对象,所以称之为 显式绑定。

3.1. call、apply、bind

通过 call 或者 apply 绑定 this 对象

  • 显示绑定后,this 就会明确的指向绑定的对象

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

  • 使用 bind 方法,bind() 方法创建一个新的绑定函数(bound function,BF);
  • 绑定函数是一个 exotic function object(怪异函数对象,ECMAScript 2015 中的术语)
  • 在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用。

img

4. new 绑定

JavaScript 中的函数可以当做一个类的构造函数来使用,也就是使用 new 关键字。

使用 new 关键字来调用函数是,会执行如下的操作:

  1. 创建一个全新的对象;
  2. 这个新对象会被执行 prototype 连接;
  3. 这个新对象会绑定到函数调用的 this 上(this 的绑定在这个步骤完成);
  4. 如果函数没有返回其他对象,表达式会返回这个新对象;

img

5. 规则优先级

学习了四条规则,接下来开发中我们只需要去查找函数的调用应用了哪条规则即可,但是如果一个函数调用位置应用了多条规则,优先级谁更高呢?

  1. 默认规则的优先级最低
    1. 毫无疑问,默认规则的优先级是最低的,因为存在其他规则时,就会通过其他规则的方式来绑定 this
  2. 显示绑定优先级高于隐式绑定
  3. new 绑定优先级高于隐式绑定
  4. new 绑定优先级高于 bind
    1. new 绑定和 call、apply 是不允许同时使用的,所以不存在谁的优先级更高
    2. new 绑定可以和 bind 一起使用,new 绑定优先级更高

6. this 规则之外–忽略显示绑定

情况一:如果在显示绑定中,我们传入一个 null 或者 undefined,那么这个显示绑定会被忽略,使用默认规则:

img

情况二:创建一个函数的 间接引用,这种情况使用默认绑定规则。

  • 赋值 (obj2.foo = obj1.foo) 的结果是 foo 函数;
  • foo 函数被直接调用,那么是默认绑定;

img

7. 箭头函数 Arrow function

箭头函数是 ES6 之后增加的一种编写函数的方法,并且它比函数表达式要更加简洁:

  • 箭头函数不会绑定 this、arguments 属性
  • 箭头函数不能作为构造函数来使用(不能和 new 一起来使用,会抛出错误);

箭头函数如何编写呢?

  • (): 函数的参数
  • {}: 函数的执行体

img

8. this 规则之外–ES6 箭头函数

箭头函数并不绑定 this 对象,那么 this 引用就会从上层作用于中找到对应的 this

img

9. 面试题一

var name = "window";

var person = {
  name: "person",
  sayName: function () {
    console.log(this.name);
  },
};

function sayName() {
  var sss = person.sayName;

  sss(); // 绑定: 默认绑定, window -> window

  person.sayName(); // 绑定: 隐式绑定, person -> person

  person.sayName(); // 绑定: 隐式绑定, person -> person

  (b = person.sayName)(); // 术语: 间接函数引用, window -> window
}

sayName();

10. 面试题二

var name = "window";

var person1 = {
  name: "person1",
  foo1: function () {
    console.log(this.name);
  },
  foo2: () => console.log(this.name),
  foo3: function () {
    return function () {
      console.log(this.name);
    };
  },
  foo4: function () {
    // console.log(this) // 第一个表达式this -> person1
    // console.log(this) // 第二个表达式this -> person2
    // console.log(this) // 第三个表达式this -> person1

    return () => {
      console.log(this.name);
    };
  },
};

var person2 = { name: "person2" };

// 开始题目:
person1.foo1(); // 隐式绑定: person1
person1.foo1.call(person2); // 显式绑定: person2

person1.foo2(); // 上层作用域: window
person1.foo2.call(person2); // 上层作用域: window

person1.foo3()(); // 默认绑定: window
person1.foo3.call(person2)(); // 默认绑定: window
person1.foo3().call(person2); // 显式绑定: person2

person1.foo4()(); // person1
person1.foo4.call(person2)(); // person2
person1.foo4().call(person2); // person1

11. 面试题三

var name = "window";

/*
  1.创建一个空的对象
  2.将这个空的对象赋值给this
  3.执行函数体中代码
  4.将这个新的对象默认返回
*/
function Person(name) {
  this.name = name;
  (this.foo1 = function () {
    console.log(this.name);
  }),
    (this.foo2 = () => console.log(this.name)),
    (this.foo3 = function () {
      return function () {
        console.log(this.name);
      };
    }),
    (this.foo4 = function () {
      return () => {
        console.log(this.name);
      };
    });
}

// person1/person都是对象(实例instance)
var person1 = new Person("person1");
var person2 = new Person("person2");

// 面试题目:
person1.foo1(); // 隐式绑定: person1
person1.foo1.call(person2); // 显式绑定: person2

person1.foo2(); // 上层作用域查找: person1
person1.foo2.call(person2); // 上层作用域查找: person1

person1.foo3()(); // 默认绑定: window
person1.foo3.call(person2)(); // 默认绑定: window
person1.foo3().call(person2); // 显式绑定: person2

person1.foo4()(); // 上层作用域查找: person1(隐式绑定)
person1.foo4.call(person2)(); //  上层作用域查找: person2(显式绑定)
person1.foo4().call(person2); // 上层作用域查找: person1(隐式绑定)

12. 面试题四

var name = "window";

/*
  1.创建一个空的对象
  2.将这个空的对象赋值给this
  3.执行函数体中代码
  4.将这个新的对象默认返回
*/
function Person(name) {
  this.name = name;
  this.obj = {
    name: "obj",
    foo1: function () {
      return function () {
        console.log(this.name);
      };
    },
    foo2: function () {
      return () => {
        console.log(this.name);
      };
    },
  };
}

var person1 = new Person("person1");
var person2 = new Person("person2");

person1.obj.foo1()(); // 默认绑定: window
person1.obj.foo1.call(person2)(); // 默认绑定: window
person1.obj.foo1().call(person2); // 显式绑定: person2

person1.obj.foo2()(); // 上层作用域查找: obj(隐式绑定)
person1.obj.foo2.call(person2)(); // 上层作用域查找: person2(显式绑定)
person1.obj.foo2().call(person2); // 上层作用域查找: obj(隐式绑定)

留下回复

目录