this指向浅析
前言
最近在笔试的时候第一道大题就是这个,但是确实不会写捏🤡,只有一个比较浅的印象,故写一篇小作文简单记录一下。此处的内容主要来自《JavaScript设计模式与开发实践》和网上的其他内容。
this
JavaScript的this总是指向一个对象,而具体指向哪个对象是在运行时基于函数的执行环境动态绑定的,而非函数被声明时的环境。
this的指向
具体到实际应用中,this的指向大致可以分为以下 4 种:
- 作为对象的方法调用
- 作为普通函数调用
- 构造器调用
- Function.prototype.call 或 Function.prototype.apply 调用 下面分别介绍一下:
- 作为对象的方法调用
当函数作为对象的方法被调用时,
this指向该对象:
const obj = {
a: 1,
getA: function () {
console.log(this === obj); // true
console.log(this.a); // 1
}
};
obj.getA();
我们使用箭头函数进行定义时,此时this指向的并不是对象,而是全局的window。
const obj1 = {
b: 2,
getB: () => {
console.log(this === obj1); // false
console.log(this); // 浏览器环境下为 window
console.log(this.b); // undefined
}
};
obj1.getB();
- 作为普通函数调用
当函数不作为对象的属性被调用时,此时的
this总是指向全局对象。在浏览器的JavaScript里,这个全局对象是window对象。
window.name = 'globalName';
const getName = function () {
return this.name;
};
console.log(getName()); // globalName
const myObject = {
name: 'sven',
getName: function () {
return this.name;
}
};
console.log(myObject.getName()); // globalName
有时候我们需要将某个节点的事件函数内部,有一个局部callback方法,callback被作为普通函数调用时,callback内部的this指向了window,我们可以使用that = this保存引用。
window.id = 'window';
document.getElementById('div1').onclick = function () {
const that = this; // 保存引用
console.log(this.id); // div1
const callback = function () {
console.log(this.id); // window
console.log(that.id); // div1
}
callback();
};
但是严格模式strict模式下,这种情况下的this是undefined。
- 构造器调用
除了宿主提供的一些内置函数,大部分JavaScript函数都可以当作构造器使用。构造器的外表跟普通函数一模一样,它们的区别在于被调用的方式。当用
new运算符调用函数时,该函数总会返回一个对象,通常情况下,构造器里的this就指向返回的这个对象。
const myClass = function () {
this.name = 'jack';
// 显式地返回一个对象
return {
name: 'anne'
}
};
const obj = new myClass();
console.log(obj.name); // 原先是 'jack',显式后返回 'anne'
如果构造器显式地返回了一个object类型的对象,那么此次运算结果最终会返回这个对象,而不是我们之前期待的this;但是返回的是一个非对象类型的数据,就不会造成上述的结果。
- Function.prototype.call 或 Function.prototype.apply 调用
跟普通的函数调用相比,用
call或apply可以动态地改变传入函数的this。
const obj1 = {
name: 'steve',
getName: function () {
return this.name;
}
};
const obj2 = {
name: 'anne'
};
console.log(obj1.getName()); // steve
console.log(obj1.getName.call(obj2)); // anne
console.log(obj1.getName.apply(obj2)); // anne
call 和 apply
在实际开发中,特别是在一些函数式风格的代码编写中,call和apply方法尤为有用。能否熟练地运用这两个方法,是我们真正成为一名合格的JavaScript程序员的重要一步。(至少以前是🤦♂️)
区别
call和apply的作用一模一样,区别仅在于传入参数形式的不同。
apply接受两个参数,第一个参数指定了函数体内this对象的指向,第二个参数为一个带下标的集合,这个集合可以为数组,也可以为类数组,apply方法把这个集合中的元素作为参数传递给被调用的函数:
var func = function( a, b, c ) {
alert([a, b, c]); // [1, 2, 3]
};
func.apply(null, [1, 2, 3]);
call传入的参数数量不固定,跟apply相同的是,第一个参数也是代表函数体内的this指向,从第二个参数开始往后,每个参数被依次传入函数:
var func = function(a, b, c) {
alert([ a, b, c ]); // [1, 2, 3]
};
func.call(null, 1, 2, 3);
也可以这么说:call是包装在apply上的一颗语法糖,如果我们明确了函数接受多少参数,也可以使用call来传送参数。
值得一提的是,如果我们传入的第一个参数为
null时,函数体内的this会指向默认的宿主环境,在浏览器环境中就是window。但是严格模式下就是undefined。
用途
- 改变
this指向 我们不仅仅可以像上面一样,使用let that = this来保存事件内部的引用,还可以使用call/apply来改变this指向:
document.getElementById('div1').onclick = function() {
var func = function () {
console.log(this.id);
};
func(); // 输出:undefined
func.call(this); // 输出: div1
}
- Function.prototype.bind
bind()方法来创建一个新的函数,当调用该新函数时,它会调用原始函数并将其this关键字设定为给定的值。同时,还可以像call一样传入一系列指定的参数。
var name = 'xiaoWang', age = 17;
var obj = {
name: 'xiaoZhang',
objAge: this.age,
myFunc: function () {
console.log(this.name + ' ' + this.age);
}
};
var db = { name: 'Dema', age: 99 };
obj.myFunc.call(db); // Dema 99
obj.myFunc.apply(db); // Dema 99
obj.myFunc.bind(db)(); // Dema 99
bind方法后面多了个()外,返回结果和前两者都一致,不过bind返回的是一个新的函数,我们必须调用它才会被执行。
书内第二章有关于
Function.prototype.bind方法的实现,如果感兴趣的话可以去看看🧐。
- 借用其他对象的方法
借用方法有很多场景,此处提一下这两个:“借用构造函数”、“借用操作方法”。
我们可以通过借用构造函数来实现一些类似继承的效果,不过现在Typescript也提供了
extends关键字,我们使用类继承更加方便且规范了。
var A = function (name) {
this.name = name;
};
var B = function () {
A.apply(this, arguments);
};
B.prototype.getName = function () {
return this.name;
};
var b = new B('sven');
console.log(b.getName()); // 输出: 'sven'
借用的第二种场景则是借用其他对象上的操作方法,比如我们会借用Array.prototype对象上的方法;比如想往arguments中添加一个新的元素,通常会借用Array.prototype.push:
(function(){
Array.prototype.push.call(arguments, 3);
console.log(arguments); // [1, 2, 3];
})(1, 2);
小结
本篇简单地讲述了this的指向问题。其中apply/call/bind都可以改变函数的this对象指向,如果没有指定则默认为全局window。三者都可以传参,但是apply传入数组;call传入参数列表;bind可以分为多次传入参数(前两个为一次性传入)。apply和call立即执行函数,bind返回绑定this之后的函数。