this的指向
这一节,我们会对JS的this指向
做详细的介绍,给大家介绍不同情况下this是如何指向的,还会给大家补充在es5和es6中我们是如何利用this的,帮助大家更好的理解JS的指向问题。
ES5中的this指向问题
在ES5中,this的指向其实只有一个原理:this永远指向最后调用它的对象
。我们来看几个例子:
// 例1
var name = "windowsName";
function a() {
var name = "Cherry";
console.log(this.name); // windowsName
console.log("inner:" + this); // inner: window
}
a();
console.log("outer:" + this); // outer: window
复制代码
例1中,最终调用函数a的是window对象,这里其实等同于window.a()。所以this指向window对象。
// 例2
var name = "windowsName";
var a = {
name: "Cherry",
fn : function () {
console.log(this.name); // Cherry
}
}
a.fn();
复制代码
例2中,在对象a中声明了一个名为fn的函数,因为调用fn的对象是a,所以这里的this指向对象a。
// 例3
var name = "windowsName";
var a = {
name: "Cherry",
fn : function () {
console.log(this.name); // undefined
}
}
window.a.fn();
复制代码
例3中,最终调用函数fn的对象是a,所以this指向a。
// 例4
var name = "windowsName";
var a = {
name : null,
// name: "Cherry",
fn : function () {
console.log(this.name); // windowsName
}
}
var f = a.fn;
f();
复制代码
例4中,在window上声明了一个变量f,指向函数fn,所以最终指向fn的是window对象。
怎么改变this的指向
改变 this 的指向,总结下来有以下几种方法:
-
使用 ES6 的箭头函数
-
在函数内部使用 _this = this
-
使用 apply、call、bind
-
new 实例化一个对象
// 例5
var name = "windowsName";
var a = {
name : "Cherry",
func1: function () {
console.log(this.name)
},
func2: function () {
setTimeout(function () {
this.func1()
},100);
}
};
a.func2() // this.func1 is not a function
复制代码
例5中,我们调用了对象a上的func2函数,想让它在100毫秒后执行this.func1()
,却得到了this.func1 is not a function
的结果。原因是func2中100毫秒后执行的匿名函数并没有指定它所指向的this,所以默认会指向window对象。而window对象上并没有名为func1的函数,所以报错。
我们以例5作为demo,用上面提到的各种方法进行改造,使得上述代码得到预期的结果。
箭头函数
箭头函数是在ES6出现的,使用它可以避免ES5中使用this的坑。箭头函数的原理其实很简单:箭头函数的this始终指向函数定义时的this,而非执行时的
。
我们来把例5改写一下:
var name = "windowsName";
var a = {
name : "Cherry",
func1: function () {
console.log(this.name)
},
func2: function () {
setTimeout(() => {
this.func1()
},100);
}
};
a.func2() // Cherry
复制代码
上述代码中,我们把func2中的setTimeout函数中的执行函数由原来的匿名函数改为了箭头函数。箭头函数的this始终指向创建定义它的this,即func2,也就是说,箭头函数的this指向就是func2的this指向。这里对象a调用了func2,所以func2的this指向a,箭头函数中的this也指向a。
我们把上面的调用改一下:
...
const funcA = a.func2;
funcA(); // this.func1 is not a function
复制代码
这时我们发现结果又报错了,这是为什么呢?原因就在于func2的this指向。我们知道:箭头函数的this指向与创建它的函数(即func2)的this指向是相同的。但是在刚才的例子中,我们创建了一个变量funcA,指向对象a中的函数func2,然后调用funcA。这时func2的this指向的是window对象,而非对象a。
不信我们再改写一下:
var a = {
name : "Cherry",
func2: function () {
setTimeout(() => {
console.log(this);
},100);
}
};
const funcB = a.func2;
funcB(); // window
复制代码
所以说,箭头函数的this还是需要依赖于创建它时的外层的this。
当箭头函数内部嵌套箭头函数时呢?我们来看一个例子:
var a = {
name : "Cherry",
func1: function () {
console.log(this.name)
},
func2: function () {
setTimeout(() => {
const fn = () => {
this.func1();
};
fn()
},100);
}
};
a.func2(); // Cherry;
复制代码
我们会发现:当箭头函数嵌套时,内层的箭头函数的this指向 与 外层的箭头函数的this指向 相同
。
声明变量指向当前this对象
我们可以通过声明一个变量_this,来保存当前函数的this值,然后在函数调用时使用_this来代替this,从而使得this指向不根据调用它的对象发生变化。
var name = "windowsName";
var a = {
name : "Cherry",
func1: function () {
console.log(this.name)
},
func2: function () {
const _this = this;
setTimeout(function () {
_this.func1()
},100);
}
};
a.func2() // Cherry;
复制代码
这里func2中声明了一个变量_this,_this的指向与func2的this指向相同。当我们调用匿名函数来寻找_this.func1()时,它的指向不再是window对象,而是func2的this指向的对象(即对象a)。
使用call、apply、bind
call、apply、bind是ES5中提供的几种用来修改this指向的方法。他们的效果是相同,但用法存在差异。
- call
fun.call(thisArg[, arg1[, arg2[, …]]])
- apply
fun.apply(thisArg, [argsArray])
- bind(this, arg1, arg2, …)
bind参数接收与call相同,但是返回的是一个函数,而不是去执行这个函数。需要手动调用
我们会发现:其实apply与bind都与call方法相似,apply的不同点在于参数的传递,而bind的不同点在于返回值是一个函数。
我们来用call、apply、bind来分别改写例5,来让它的this指向正确的值:
// call
var a = {
name : "Cherry",
func1: function () {
console.log(this.name)
},
func2: function () {
setTimeout(function () {
this.func1();
}.call(a),100);
}
};
// apply
var a = {
name : "Cherry",
func1: function () {
console.log(this.name)
},
func2: function () {
const _this = this;
setTimeout(function () {
this.func1();
}.apply(a),100);
}
};
// bind
var a = {
name : "Cherry",
func1: function () {
console.log(this.name)
},
func2: function () {
const _this = this;
setTimeout(function () {
this.func1();
}.bind(a)(),100);
}
};
复制代码
我们再来看看这三个方法的参数的传递,下面有一个求和的小例子:
// 例6
var obj = {
init: 10,
fn: function (a,b) {
return a + b + this.init;
}
}
var obj2 = {
init: 100,
}
const fn = obj.fn;
fn(1,2); // NaN
复制代码
小伙伴们应该很快就能想明白:最终调用fn的对象是window,而window上是没有init的,所以会输出NaN。我们来用call、apply、bind来修改this的指向:
// call
fn.call(obj, 1, 2) // 13
fn.call(obj2, 1, 2) // 103
// apply
fn.apply(obj, [1, 2]) // 13
fn.apply(obj2, [1, 2]) // 103
// bind
fn.bind(obj, 1, 2)() // 13
fn.bind(obj2, 1, 2)() // 103
复制代码
通过call、apply、bind,我们就可以指定this的指向,让函数正确执行啦!
使用构造函数调用函数
我们可以通过new关键字来调用构造函数,我们先来了解一下new是如何创建一个新的对象的,即new的创建过程
:
-
创建一个新对象
-
新对象继承原函数的原型
-
将这个新对象绑定到这个函数的this上
-
如果这个函数没有返回其它对象,则返回这个新对象
让我们来手写一个new操作符吧!
// create为一个构造函数
function create(Con, ...arguments) {
// 创建一个新对象
let obj = {};
// 新对象继承原函数的原型
Object.setPrototypeOf(obj, Con.prototype);
// 通过apply修改原函数的this指向,并执行
let result = Con.apply(obj, arguments);
// 如果执行结果为object,则认为函数返回了对象,否则返回新对象obj
return result instanceof Object ? result : obj;
}
function Obj(name, age) {
this.name = name;
this.age = age;
}
Obj.prototype.sayName = function() {
console.log(this.name);
}
const obj = new create(Obj, 'li', 18)
复制代码
总结
在这一节,我们详细介绍了JS中this的指向问题,解释了箭头函数, apply、call、bind的区别及使用。最后,我们还补充了new的过程。接下来我们会继续介绍其他JS核心知识。
我是何以庆余年,如果文章对你起到了帮助,希望可以点个赞,谢谢!
如有问题,欢迎在留言区一起讨论。