首先恭喜你发现了宝藏🚀🚀🚀,读完本文章大约耗费十分钟,相信你看完后会得到你想要的,如果感觉写的好的话,请给一个宝贵的👍
关于this绑定的问题是我们在面试中常常被问到的问题,这一篇文章带你彻底弄懂!👍
this到底指向什么
首先我们先来看一个令人困惑的问题🤔
定义一个函数,我们采用三种不同的方式对它进行调用,它产生了三种不同的结果
function foo() {
console.log(this);
}
foo(); //window
var obj = {
bar: foo
}
obj.bar(); //obj
obj.bar.apply(123); //Number
我们可以看到同一个函数,用三种不同方式的调用方式,打印出的this的值不同。
从这里我们可以得到什么指示呢?🤔
- 函数在调用的时候,javascript会给它绑定一个特定的值
- this的绑定和定义的位置(编写的位置)无关
- this的绑定和调用方式以及调用位置有关
- this是在运行的时候绑定的
this的绑定规则
this的绑定规则有三种
- 默认绑定
- 隐式绑定
- 显示绑定
- new绑定
默认绑定
什么是默认绑定?🤨
默认绑定是JavaScript 中的一种函数 this 绑定行为,它适用于函数在全局作用域中调用时(即不通过对象调用),或者是没有明确通过 call、apply 或 bind 指定 this 时。
什么情况在使用默认绑定?默认绑定发生在什么时候?😄 我们来看下面的代码
// 1.1独立函数调用
function foo() {
// 这里的this是window
console.log(this);
}
foo();
在 独立函数调用 中(即 foo()),this 通常会指向 全局对象(在浏览器中是 window,在 Node.js 中是 global)。
// 1.2函数定义在对象中,但是是独立调用
var obj = {
name: 'obj',
foo: function () {
// 这里的this是window
console.log(this);
}
}
var foo = obj.foo;
foo();
-
虽然
foo最初是obj的一个方法,但将方法提取出来并独立调用时,this的指向仍然是全局对象window(而不是obj)。 -
只是定义在对象中的方法,如果被独立调用(即从对象中提取出来并直接调用),会丢失对象上下文,
this不再指向对象,丢失了对obj的引用。
这里我们可以再次加强理解
this是在调用时绑定的,而不是在定义时绑定⚠️!!。
// 1.3高阶函数
function foo1(fn) {
// 这里的this是window
fn();
}
foo1(obj.foo);
-
在 高阶函数 中(
foo1调用fn),当obj.foo作为参数传递给foo1并在foo1中执行时,fn()仍然是一个独立调用,导致this指向window(而不是obj)。 -
高阶函数在传递方法作为参数时,也会导致
this指向全局对象,而不是原来定义该方法的对象。 -
即使
obj.foo原本是obj的方法,传递到foo1函数并作为回调执行时,this不会指向obj,而是全局对象(除非明确绑定this,也就是显示绑定)
从这里我们可以得到什么启示呢?🤨
默认绑定 是 JavaScript 中关于 this 的一种绑定规则,它发生在独立函数调用时。具体来说,当函数没有明确指定 this 的值(例如,没有使用 call()、apply() 或 bind() 方法),并且该函数是作为一个普通函数被调用时,this 将根据 调用环境 来决定。
总结
何时发生默认绑定?
默认绑定发生在以下情况下:
- 函数被直接调用(即函数名后加
())。 - 独立函数调用的时候,
this会丢失上下文对象,发生默认绑定。 - 函数没有明确的上下文(比如
call()、apply()或bind())或不是作为对象的方法调用。
注意,这是在非严格模式下,如果在严格模式下,this指向的是undefined!!⚠️
严格模式是什么?就是在你的js代码上加"use strict"这一句话😄
隐式绑定
隐式绑定是什么?
隐式绑定指的是当一个函数作为对象的方法被调用时,
this会绑定到该对象。换句话说,this的值取决于函数调用的上下文,尤其是函数是如何被调用的。如果一个方法是作为对象的属性被调用,this就会指向那个对象。
我们看下面一段代码
function foo() {
console.log(this);
}
var obj = {
bar: foo
}
obj.bar(); // obj
调用obj.bar()会执行foo()函数。在 JavaScript 中,函数内部的 this 是根据调用该函数的上下文来决定的。当我们调用 obj.bar() 时,bar 方法是 foo 的引用,因此 foo 被调用时,this 会指向 obj,因为 foo 是作为 obj 的方法被调用的。
function foo() {
console.log(this);
}
var obj1 = {
a: 1,
foo: foo
}
var obj2 = {
a: 2,
obj1: obj1
}
obj2.obj1.foo(); //{ a: 1, foo: [Function: foo] }
这里,obj2.obj1 是 obj1 对象,foo 是 obj1 中的一个方法。所以,foo() 函数的调用实际上是通过 obj1 来触发的,尽管 obj2 包含 obj1,但调用 obj2.obj1.foo() 时,this 并不会指向 obj2,而是指向 obj1,
这里给我们什么启示呢?🤨
- 方法调用时,
this会指向调用该方法的对象。 - 嵌套对象的影响:嵌套的对象并不影响
this的绑定,因为this总是会绑定到方法的实际调用对象。 - 隐式绑定只会在函数被对象方法调用时生效。
this的绑定是由调用方式决定的。
总结
什么时候发生隐式绑定?
- 当函数作为对象的方法被调用时:
- 函数调用的上下文决定了
this的绑定 - 当对象方法内部调用另一个对象方法时
何时不发生隐式绑定
- 方法丢失上下文(丢失
this),比如将一个对象的方法赋值给一个变量或者传递给其他函数 - 函数调用时不在对象上下文中,比如独立调用
注意隐式绑定有一个前提条件⚠️!!
-
必须在调用的对象内部有一个对函数的引用(比如一个属性);
-
如果没有这样的引用,在进行调用时,会报找不到该函数的错误;
-
正是通过这个引用,间接的将this绑定到了这个对象上;
显示绑定
在介绍显示绑定的时候,我们先思考一个问题🤔
var obj = {
name: '张三',
}
function fn() {
console.log(this);
}
我们如何让这串代码中的this绑定到obj上呢?🤨 根据我们之前学过的,这里可以隐式绑定!😄
var obj = {
name: '张三',
}
function fn() {
console.log(this);
}
// 执行函数,并且将函数中this指向obj
// 1,.通过隐式调用函数,将函数内部的this指向obj
obj.fn = fn
obj.fn()
如果我们不希望在对象内部包含这个函数的引用,同时又希望在这个对象上进行强制调用,该怎么做呢?🤨
这里就要隆重请出我们的显示绑定了👏👏
什么是显示绑定?🤨
显示绑定是指在 JavaScript 中,使用 call、apply 或 bind 方法显式地指定函数调用时的 this 值。通过这些方法,我们可以强制 this 指向某个特定的对象,而不依赖默认绑定或隐式绑定的规则。
这里就要使用显示绑定 显示绑定有三种方法
- call
- apply
- bind
call和apply
function.apply(thisArg,[argsArray]
function.call(thisArg,argl,arg2,...)
第一个参数是相同的,要求传入一个对象
- 这个对象的作用是什么呢?就是给this准备的。
- 在调用这个函数时,会将this绑定到这个传入的对象上。
后面的参数,apply为数组,call为参数列表;
我们来看例子
function fn(a, b) {
console.log(this, a, b);
}
fn.call('abc', 1, 2); //1.String 1 2
function fn(a, b) {
console.log(this, a, b);
}
fn.apply('abc', [1, 2]);//1.String 1 2
'abc' 作为 this 的值(即 this = 'abc'),1 和 2 作为函数参数 a 和 b。
注意,打印出来的是string,这是为什么?🤔
由于 this 是 'abc',它会在打印时被转化为原始的字符串值。'abc' 是一个原始字符串类型,它不是一个对象,所以可以直接当作原始值显示。
bind
function greet(name) {
console.log(`Hello, ${name}! I am ${this.name}.`);
}
const person = { name: 'Alice' };
// 使用 bind 来绑定 this 为 person 对象
const greetPerson = greet.bind(person);
// 调用新函数 greetPerson,this 会始终指向 person 对象
greetPerson('Bob'); // 输出: "Hello, Bob! I am Alice."
greet.bind(person) 创建了一个新函数 greetPerson,它的 this 被永久绑定到 person 对象。这样,即使我们在 greetPerson 中传入不同的参数(如 'Bob'),this 仍然会指向 person。
new绑定
JavaScript中的函数可以当做一个类的构造函数来使用,也就是使用new关键字。 使用new关键字来调用函数时,会执行如下的操作:
-
1.创建一个全新的对象;
-
2.这个新对象会被执行prototype连接,将this指向这个空对象
-
3.执行函数体中的代码,这个新对象会绑定到函数调用的this上(this的绑定在这个步骤完成);
-
4.如果函数没有返回其他对象,表达式会返回这个新对象;
function Person(name) {
console.log(this); // Person {}
this.name = name; // Person {name: "张三"}
}
var p1 = new Person('张三');
console.log(p1);
使用 new Person('张三') 创建了一个新的 Person 对象 p1,并自动将 this 绑定到这个新对象上。
总结
call,bind,apply,new绑定都能用于显示绑定this,将this设置为你指定的值.
this绑定规则优先级
上面提到了this的四种绑定规则,如果在函数调用时,使用到了多种绑定规则,最终函数的this指向什么呢?那么这里就涉及到this绑定的优先级,优先级高的规则决定this的最终绑定。
this四种绑定规则优先级如下:
-
默认绑定优先级最低:函数调用存在其它规则时,就会遵循其它规则来绑定其this;
-
显示绑定优先级高于隐式绑定:
const obj1 = { name: 'obj1', foo: function() { console.log(this) } } const obj2 = { name: 'obj2' } obj1.foo.call(obj2) // obj2对象 obj1.foo.apply(obj2) // obj2对象 const newFoo = obj1.foo.bind(obj2) newFoo() // obj2对象 -
显示绑定中bind高于call和apply:
function foo() { console.log(this) } const obj1 = { name: 'curry', age: 30 } const obj2 = { name: 'kobe', age: 24 } const newFoo = foo.bind(obj1) newFoo() // obj1对象 newFoo.call(obj2) // obj1对象 newFoo.apply(obj2) // obj1对象 -
new绑定优先级高于隐式绑定和显示绑定:
// 1.new绑定高于隐式绑定 const obj1 = { foo: function() { console.log(this) } } const f = new obj1.foo() // foo函数对象 // 2.new绑定高于显示绑定 function foo(name) { console.log(this) } const obj2 = { name: 'obj2' } const newFoo = foo.bind(obj2) const nf = new newFoo(123) // foo函数对象
总结:
- new绑定 > 显示绑定(apply/call/bind) > 隐式绑定 > 默认绑定;
- 注意new关键字不能和apply、call一起使用,所以不太好进行比较,默认为new绑定是优先级最高的;
4.特殊情况下的this绑定
在特殊情况下,this的绑定不一定满足上面的绑定规则,主要有以下特殊情况:
-
显示绑定的忽略:在显示绑定中,如果给call、apply和bind第一个参数传入null或者undefined,那么这样的显示绑定会被忽略,最终使用默认绑定,也就是全局的window;
function foo() { console.log(this) } foo.call(null) // window foo.call(undefined) // window foo.apply(null) // window foo.apply(undefined) // window const newFoo1 = foo.bind(null) const newFoo2 = foo.bind(undefined) newFoo1() // window newFoo2() // window -
间接函数的引用:创建一个函数的间接引用,该情况也使用默认绑定。如下代码中给obj2创建一个bar属性并赋值为obj1中foo函数时直接进行调用;
const obj1 = { name: 'obj1', foo: function() { console.log(this) } } const obj2 = { name: 'obj2' } // 被认为是独立函数调用 ;(obj2.bar = obj1.foo)() // window -
ES6中的箭头函数:箭头函数不会使用上面的四种绑定规则,也就是说不绑定this,箭头函数的this是根据它外层作用域中的this绑定来决定的;
-
数组内置方法中回调使用箭头函数:
const names = ['curry', 'kobe', 'klay'] const obj = { name: 'obj' } names.map(() => { console.log(this) // window }, obj) -
多层对象中的方法属性使用箭头函数:
const obj1 = { name: 'obj1', obj2: { name: 'obj2', foo: () => { console.log(this) } } } obj1.obj2.foo() // window -
函数的返回值为箭头函数:
function foo() { return () => { console.log(this) } } const obj = { name: 'curry', age: 30 } const bar = foo.call() bar() // obj对象
-
5.node环境下全局this的指向
以上提到的内容都是在浏览器环境中进行测试的,在浏览器环境下的this是指向全局window的,那么在node环境中全局this指向什么呢?