跳到主要内容

如何使用this

这是 2024 年 11 月 2 日更新的文章,尽力做到 11 月每天都更新一篇较高质量的文章

important

本文参考了大佬的文章 https://juejin.cn/post/6844904083707396109

回想一下什么时候会用到 this,现在 react 中普遍使用了函数式组件,弱化了 this 的使用,但是面试还是很喜欢考察 this 的使用的,我做个整理准备 this 相关的使用,this 有哪些考点呢 在学习闭包的时候,经常使用到 this,这也是红宝书中第 10 章 this 的一个部分,在使用 Symbol 的时候,也会用到 this

总结 this 的使用

下面总的介绍一下 this 的使用

  • 默认绑定,这是最常见的绑定了,在非严格模式下 this 指向的式全局对象,在浏览器中就是 window 在 node 中是 global
  • 隐式绑定,例如函数的引用有上下文的对象,obj.foo() 这个时候 this 指向的是 obj
  • 显示绑定使用 call apply bind,这个时候 this 指向的是传入的对象
  • new 绑定,使用 new 关键字调用函数,这个时候 this 指向的是新创建的对象

箭头函数和标准函数中的 this

在标准函数中 this 引用的是把函数当成方法调用的上下文对象,在网页的全局上下文中 this 指向的是 windows

在箭头函数中 this 的引用时定义箭头函数的上下文,箭头函数时没有自己的 this 的 举例子

window.col = "red";
let obj = {
color: "blue",
};
function sayColor() {
console.log(this.color);
}
sayColor(); // red
obj.sayColor = sayColor;
obj.sayColor(); // blue
window.col = "red";
let obj = {
color: "blue",
};
let sayColor = () => {
console.log(this.color);
};
sayColor(); // red
obj.sayColor = sayColor;
obj.sayColor(); // red

上面的例子是上下文的例子,下面的是箭头函数的例子,这里先将箭头函数的例子,箭头函数的 this 是定义箭头函数的上下文,所以这里的 this 指向的是 window

默认绑定

1.1 直接调用函数

var color = "red";
function sayColor() {
console.log(this.color);
}
sayColor(); // red

在之前讲过的 let 和 const 中说过,var 是全局变量定义的变量会挂到 window 上所以这里的 this 指向的是 window 输出就是 red

1.2 严格模式下的默认绑定

"use strict";
var a = 10;
function foo() {
console.log("this:", this);
console.log("this.a:", this.a);
}
console.log(window.foo);
//ƒ foo () {...}
foo(); // this: undefined
// this.a: Uncaught TypeError: Cannot read property 'a' of undefined

1.3 let 和 const 的默认绑定

let a = 10;
const b = 20;
function foo() {
console.log("this:", this.a);
console.log("this.b:", this.b);
}
foo(); // this.a: undefined this.b: undefined
console.log(window.foo); // undefined

这里都是 undefined,因为使用了 let,let 是不会挂载到 window 上的

1.4 函数套函数

var color = "red";
function foo() {
var color = "blue";
function bar() {
console.log(this.color);
}
bar();
}
foo(); // red

为什么会是这样的呢?首先我们使用了foo(),这样我们进入到了foo函数中,然后我们在foo函数中调用了bar函数,这个时候bar函数是一个普通函数,所以this指向的是全局对象,所以输出的是red

隐式绑定

2.1 this 永远指向最后一个调用它的对象

function foo() {
console.log(this.a);
}
var obj = {
a: 2,
foo: foo,
};
var a = "oops, global";
obj.foo(); // 2

2.2 隐式绑定的丢失问题

function foo() {
console.log(this.a);
}
var obj = {
a: 2,
foo: foo,
};
var a = "oops, global";
var bar = obj.foo;
bar(); // oops, global
var obj2 = {
a: 3,
foo2: obj.foo,
};
console.log(obj2.foo2); // 3

这里的bar函数是一个普通函数,所以this指向的是全局对象,所以输出的是oops, global obj2.foo2是一个方法,调用的是 obj2 所以this指向的是obj2,所以输出的是3

function foo() {
console.log(this.a);
}
var a = 2;
function reFunc(fn) {
console.log(this);
fn();
}
var obj = {
a: 3,
foo: foo,
};
reFunc(obj.foo);
//window{}
//2
function foo() {
console.log(this.a);
}
function reFunc(fn) {
console.log(this);
fn();
}
var obj = {
a: 1,
foo: foo,
};
var a = 2;
var obj1 = {
a: 3,
reFunc: reFunc,
};
obj1.reFunc(obj.foo);
//obj1
//2

有大佬总结了这个东西,如果你把一个函数当成参数传递到另一个函数的时候,会发生隐式丢失,且与包裹着它的函数无关

2.3 内置函数

setTimeout(function () {
console.log(this); // window
});

var a = 0;
function fn() {
console.log(this.a);
}
var obj = {
a: 1,
reFn: fn,
};
setTimeout(obj.reFn, 1000); // 0;

显示绑定

显示绑定其实是最常见的绑定方式了可以使用 call apply bind 来显示绑定 this 的指向 使用.call()和.apply()方法是直接执行函数的,而.bind()方法是返回一个新的函数,不会立即执行

3.1 call 和 apply

function foo() {
console.log(this.a);
}
var obj = {
a: 2,
};
var a = 1;
foo(); //1
foo.call(obj); //2
foo.apply(obj); //2

3.2 setTimeout 中的显示绑定

var obj1 = {
a: 1,
};
var obj2 = {
a: 2,
foo1: function () {
console.log(this.a);
},
foo2: function () {
setTimeout(function () {
console.log(this);
console.log(this.a);
}, 0);
},
};
var a = 3;

obj2.foo1(); //2 因为是隐式绑定绑定到了obj2
obj2.foo2(); //setTimeout中的this指向的是window

那我想要 foo2 输出是 1 怎么办

var obj1 = {
a: 1,
};
var obj2 = {
a: 2,
foo1: function () {
console.log(this.a);
},
foo2: function () {
setTimeout(
function () {
console.log(this);
console.log(this.a);
}.call(obj1),
0
);
},
};
var a = 3;

obj2.foo1(); //2 因为是隐式绑定绑定到了obj2
obj2.foo2(); //1

这样的写法可以吗? obj2.foo2.call(obj1) 这里改变的是 foo2 钟 this 的指向,foo2 中的指向和 setTimeout 中的指向是不一样的

3.3 面试题

function foo() {
console.log(this.a);
}
var obj = { a: 1 };
var a = 2;

foo(); //2
foo.call(obj); //1
foo().call(obj); //2

在这里 foo.call() 是显示绑定了 obj,所以输出是 1,而下面的 foo().call(obj)是先执行 foo()foo()是一个普通函数,然后对于 foo() 的返回值进行操作可是 foo()函数的返回值是 undefined,因此就会报错了。

function foo() {
console.log(this.a);
return function () {
console.log(this.a);
};
}
var obj = { a: 1 };
var a = 2;

foo(); //2
foo.call(obj); //1;
foo().call(obj); //2 1

这里我增加了返回值,所以输出就很容易看出来是什么了

3.4 bind

bind 的作用是什么,bind 是返回一个新的函数,不会立刻执行

function foo() {
console.log(this.a);
return function () {
console.log(this.a);
};
}
var obj = { a: 1 };
var a = 2;

foo(); //2
foo.bind(obj);
//不输出;
foo().bind(obj); //2

3.5 面试提升

var obj = {
a: "obj",
foo: function () {
console.log("foo:", this.a);
return function () {
console.log("inner:", this.a);
};
},
};
var a = "window";
var obj2 = { a: "obj2" };

obj.foo()(); //foo:obj inner:window
obj.foo.call(obj2)(); //foo:obj2 inner:window
obj.foo().call(obj2); //foo:obj inner:obj2
var obj = {
a: 1,
foo: function (b) {
b = b || this.a;
return function (c) {
console.log(this.a + b + c);
};
},
};
var a = 2;
var obj2 = { a: 3 };

obj.foo(a).call(obj2, 1); //3+2+1
obj.foo.call(obj2)(1); //b=3 匿名函数this是window 2+3+1=6

this 的指向永远是最后调用它的对象 匿名函数的 this 指向的是 window call apply bind 接收到的第一个参数是 null 或者 undefined,那么 this 指向的是 window forEach map filter reduce 这些函数的第二个参数是 this 的指向

new 绑定

4.1 new 绑定

var color = "red";
function foo(color) {
this.color = color;
}
var col1 = new foo("blue");
console.log(col1.color); //blue

4.2 new 结合显示绑定

var name = "window";
function Person(name) {
this.name = name;
this.foo = function () {
console.log(this.name);
return function () {
console.log(this.name);
};
};
}
var person1 = new Person("person1");
var person2 = new Person("person2");

person1.foo.call(person2)(); //person2 window
person1.foo().call(person2); //person1 person2

箭头函数

首先介绍一下箭头函数怎么写

之前的普通函数写法

function foo() {}
var foo1 = function () {};

箭头函数的完整写法

var foo3 = (name, age) => {
console.log(name, age);
};

var name = [1, 2, 3].map((item) => {
return item + 1;
});

箭头函数的简写

//如果只有一个参数可以省略括号
var foo4 = (name) => {
console.log(name);
};
var nums = [1, 2, 3];
// nums.forEach(item => console.log(item)); 这样写是可以的
nums.forEach((item) => console.log(item)); //这样写也是可以的

对于箭头函数它的 this 是定义箭头函数的上下文,所以箭头函数没有自己的 this,如果箭头函数被非箭头函数包裹那么 this 绑定的是最近一层非箭头函数的 this

5.1 箭头函数的 this

var obj = {
name: "obj",
foo1: () => {
console.log(this.name);
},
foo2: function () {
console.log(this.name);
return () => {
console.log(this.name);
};
},
};
var name = "window";
obj.foo1(); //window
obj.foo2()(); //obj obj

5.2 嵌套关系的箭头函数

var name = "window";
var obj1 = {
name: "obj1",
foo: function () {
console.log(this.name);
return function () {
console.log(this.name);
};
},
};
var obj2 = {
name: "obj2",
foo: function () {
console.log(this.name);
return () => {
console.log(this.name);
};
},
};
var obj3 = {
name: "obj3",
foo: () => {
console.log(this.name);
return function () {
console.log(this.name);
};
},
};
var obj4 = {
name: "obj4",
foo: () => {
console.log(this.name);
return () => {
console.log(this.name);
};
},
};

obj1.foo()(); //obj1 window
obj2.foo()(); //obj2 obj2
obj3.foo()(); //window window
obj4.foo()(); //window window

5.3 箭头函数+call

var name = "window";
var obj1 = {
name: "obj1",
foo1: function () {
console.log(this.name);
return () => {
console.log(this.name);
};
},
foo2: () => {
console.log(this.name);
return function () {
console.log(this.name);
};
},
};
var obj2 = {
name: "obj2",
};
obj1.foo1.call(obj2)(); //obj2 obj2
obj1.foo1().call(obj2); //obj1 obj1
obj1.foo2.call(obj2)(); //window window
obj1.foo2().call(obj2); //window obj2

挑战

1

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 () {
return () => {
console.log(this.name);
};
},
};
var person2 = { name: "person2" };

person1.foo1(); //1
person1.foo1.call(person2); //2

person1.foo2(); //window
person1.foo2.call(person2); //window

person1.foo3()(); //window
person1.foo3.call(person2)(); //window
person1.foo3().call(person2); //2

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

2

var name = "window";
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);
};
});
}
var person1 = new Person("person1");
var person2 = new Person("person2");

person1.foo1(); //1
person1.foo1.call(person2); //2

person1.foo2(); //1
person1.foo2.call(person2); //1

person1.foo3()(); //window
person1.foo3.call(person2)(); //window
person1.foo3().call(person2); //2

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

3

function foo() {
console.log(this.a);
}
var a = 2;
(function () {
"use strict";
foo();
})();

2 注意到这里的调用foo()依旧是 window

4

var name = "window";
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); //2

person1.obj.foo2()(); //obj
person1.obj.foo2.call(person2)(); //2
person1.obj.foo2().call(person2); //obj

后续的计划

后续还会出关于 call bind apply 的手写,promise 等也在计划中