this 指向
在全局环境中,this 指向顶层对象;在函数中,this 的指向取决于函数的调用方式。
- 直接调用:this 指向顶层对象。
var name = 'superman';
function showName() {
console.log(this); // Window {window: Window, self: Window, document: document, …}
console.log(this.name); // superman
}
showName(); // 直接调用, this 指向顶层对象; 严格模式下, this 为 undefiend
- 通过指定对象调用:this 指向该对象。
const obj = {
name: 'superman',
objFun() {
console.log(this); // {name: 'superman', objFun: ƒ}
console.log(this.name); // superman
},
};
obj.objFun(); // 通过 obj 调用, this 指向 obj
function fun() {
console.log(this); // {name: 'superman', objFun: ƒ}
console.log(this.name); // superman
}
const obj = { name: 'superman' };
obj.objFun = fun;
obj.objFun(); // 通过 obj 调用, this 指向 obj
- 通过 new 调用:this 指向新创建的实例对象:
function Person(name) {
this.name = name;
}
const person = new Person('superman'); // new 调用, this 指向实例对象
console.log(person); // Person { name: 'superman' }
console.log(person.name); // superman
修改 this 指向
call & apply
Function.prototype.call 和 Function. prototype.apply 用于改变函数内部的 this 指向,并立即调用该函数。
window.name = 'window';
const obj1 = { name: 'sven' };
const obj2 = { name: 'anne' };
const getName = function () {
console.log(this.name);
};
getName(); // window
getName.call(obj1); // sven
getName.call(obj2); // anne
call 和 apply 的区别在于传入参数的形式不同:
apply 接受两个参数,第一个参数指定了函数体内 this 的指向,第二个参数为一个带下标的集合,这个集合可以为数组,也可以为类数组,apply 方法把这个集合中的元素作为参数传递给被调用的函数。
call 传入的参数数量不固定,跟 apply 相同的是,第一个参数也是代表函数体内的 this 指向,从第二个参数开始往后,每个参数被依次传入函数。
function sum(a, b) {
console.log(this);
return a + b;
}
sum(10, 20); // [object Window]
const obj = { name: 'obj' };
sum.call(obj, 10, 20); // { name: 'obj' }
sum.apply(obj, [10, 20]); // { name: 'obj' }
对于 call 和 apply 方法的第 1 个参数:
如果是 Number
、String
、Boolean
,包装类会将其转为对象。
如果是 undefined
、null
,它们没有包装类,无法转成对象,this
指向 window
。
function fun() {
console.log(this);
}
fun.call(true); // Boolean { true }
fun.call(null); // [object Window]
当使用 call 或者 apply 的时候,如果我们传入的第一个参数为 null
,函数体内的 this 会指向顶层对象:
var func = function (a, b, c) {
alert(this === window); // true
};
func.apply(null, [1, 2, 3]);
但如果是在严格模式下,函数体内的 this 会为 null
:
var func = function (a, b, c) {
'use strict';
alert(this === null); // true
};
func.apply(null, [1, 2, 3]);
有时候我们使用 call 或者 apply 的目的不在于指定 this 指向,而是另有用途,比如借用其他对象的方法。那么我们可以传入 null 来代替某个具体的对象:
Math.max.apply(null, [1, 2, 5, 3, 4]); // 5
bind
Function.prototype.bind 用于硬性绑定函数的 this 指向,且不可更改。
bind 不会执行函数,而是修改 this 指向后,返回一个新的函数。
const newFun = fun.bind(obj);
通过 bind 方法获取的新函数,无法修改 this 指向:
function fun() {
console.log('this', this);
}
const obj1 = { name: 'obj1' };
const biFun = fun.bind(obj1); // 使用 bind 硬绑定
biFun(); // this { name: 'obj1' }
const obj2 = { name: 'obj2' };
biFun.call(obj2); // this { name: 'obj1' }
此时就算使用 call
、apply
方法也修改不了函数 biFun
的 this 指向。
this 指向的优先级
this 指向的优先级:① 通过 new 调用 > ② 显示绑定 (bind > call、apply) > ③ 通过指定函数调用 > ④ 直接调用
function Person(name) {
this.name = name;
console.log(this);
}
const obj = {};
const biPerson = Person.bind(obj);
biPerson('obj'); // { name: 'obj' } -- 硬绑定
const newObj = new biPerson('newObj'); // Person {name: 'newObj'} -- 构造函数
特殊函数内的 this 指向
立即执行函数 IIFE
立即执行函数 IIFE 说白了就是直接调用一个函数,this 指向顶层对象。
const obj = {
outer() {
console.log('outer', this); // outer { outer: [Function: outer] }
// 立即执行函数 -- 直接调用, this 指向顶层对象
(function () {
console.log('inner', this); // inner [object Window]
})();
},
};
obj.outer();
闭包
其实就是把内部函数赋值到外部了,说白了也是直接调用一个函数,this 指向顶层对象。
const obj = {
outer() {
console.log('outer', this); // outer { outer: [Function: outer] }
return function () {
console.log('inner', this); // inner [object Window]
};
},
};
// 闭包 -- 直接调用, this 指向顶层对象
obj.outer()();
箭头函数
箭头函数会使用上一级的 this。
const obj = {
// 箭头函数 -- 使用上一级的 this
objFun: () => {
console.log('this', this); // this [object Window]
},
};
obj.objFun();
因为箭头函数没有自己的 this,所以 call & apply、bind 方法对箭头函数都不管用。⭐
// 创建箭头函数
const arrFun = () => {
console.log('this', this);
};
const obj = {};
const newArrFun = arrFun.bind(obj); // 使用 bind 硬绑定
newArrFun(); // this [object Window]
面试题
var name = 'window';
var obj1 = {
name: 'obj1',
fn1() {
console.log(this.name);
},
// 箭头函数, 使用上一级的 this
fn2: () => console.log(this.name),
fn3() {
// 闭包
return function () {
console.log(this.name);
};
},
fn4() {
// 箭头函数, 使用上一级的 this
return () => console.log(this.name);
},
};
var obj2 = { name: 'obj2' };
obj1.fn1(); // 通过 obj1 调用, this 指向 obj1
obj1.fn1.call(obj2); // 通过 call 使 this 指向 obj2
obj1.fn2(); // 箭头函数, 此时上一级的 this 指向 window
obj1.fn2.call(obj2); // call 对箭头函数无效, this 仍然指向 window
obj1.fn3()(); // 直接调用闭包传递出来的函数, this 指向 window
obj1.fn3().call(obj2); // 通过 call 使 this 指向 obj2
obj1.fn3.call(obj2)(); // 直接调用闭包传递出来的函数, this 指向 window
obj1.fn4()(); // 箭头函数, 此时上一级的 this 指向 obj1
obj1.fn4().call(obj2); // call 对箭头函数无效, this 仍然指向 obj1
obj1.fn4.call(obj2)(); // 使用 call 使箭头函数上一级的 this 指向 obj2
function Foo() {
getName = function () {
console.log(1);
};
return this;
}
Foo.getName = function () {
console.log(2);
};
Foo.prototype.getName = function () {
console.log(3);
};
var getName = function () {
console.log(4);
};
function getName() {
console.log(5);
}
Foo.getName(); // 2
getName(); // 4 -- 变量 getName 覆盖了函数 getName
Foo().getName(); // 1 -- 这里重写了 window.getName 方法
getName(); // 1
new Foo.getName(); // 2
new Foo().getName(); // 3
new new Foo().getName(); // 3