今天遇到一道很有意思的this指向的题目,通过对这道题的逐步剖析和延伸,能对匿名函数、this指向、作用域、箭头函数的指向等有一个更加深刻的认识。我们先来看代码
var number = 2;
var obj = {
number: 3,
db1: (function(){
console.log(this);
this.number *= 4;
return function(){
console.log(this);
this.number *= 5;
}
})()
}
var db1 = obj.db1;
db1();
obj.db1();
console.log(obj.number);
console.log(window.number);
下面我们讲逐步剖析这段代码
一、首先明确:对象的括号{}无法形成单独的作用域。
var obj={
fn:()=>{
console.log(this);
}
}
obj.fn() //windows
我们知道,箭头函数的this是捕捉的上下文的this。若对象的大括号有单独的作用域,那么此时this是会指向obj的。而此时返回windows,说明箭头函数的执行环境是在全局作用域中。为了方便理解箭头函数的这一特性,我们再看一段代码。
var obj = {
fn: function () {
console.log(this); //obj
return () => {
console.log(this); //obj
}
}
}
let f = obj.fn()()
此时箭头函数被包裹在obj对象的外层函数里,obj.fn()执行的是最外层函数,此时的this代表obj这个对象,obj.fn()的返回值是一个箭头函数,所以通过obj.fn()()调用。通过打印箭头函数里的this,发现此时也等于obj。这便是对上文箭头函数的this是捕捉的上下文的this的理解。
通过这两个demo,相信你能更好的理解:对象的大括号{}无法形成单独作用域这段话。
二、匿名函数立即执行的特性与闭包的理解
匿名函数在创建后会立即调用执行,即使是写在对象里面。为啥呢?其实上文已经告诉了答案。对象的大括号{}无法形成单独作用域,所以匿名函数写在对象里面和写在全局实际上是等效的。还是通过代码举例
var obj={
fn:(function(){
console.log("我会自动执行");
})()
}
这里我们并没有调用fn这个方法,但是控制台已经输出了“我会自动执行”这段文字,并且只执行了一次。那么如果匿名函数里再嵌套函数,此时会变成什么样呢?
var i = 1;
var obj = {
fn: (function () {
console.log("我会自动执行");
let i=10
return function () {
console.log(++i);
}
})()
}
obj.fn()
obj.fn()
obj.fn()
此时运行结果如下:
重点来了!!此时return前的语句只在创建匿名函数时执行了一次,后续每一次调用obj.fn()时只执行了return后的函数。回顾一下闭包的作用(解决全局变量污染以及局部变量不可重用的问题)这玩意不就是个闭包吗?作为对比,我们来看一下如果此时的fn是普通函数的时候情形是怎样。我们将代码作一个修改
var i = 1;
var obj = {
fn: function () {
console.log("我不会自动执行"); //obj
let i=10
return () => {
console.log(++i);
}
}
}
obj.fn()()
obj.fn()()
obj.fn()()
结果如下
对比匿名函数的调用,这种写法在每次调用时都会优先执行最外层的函数,按照从外到内的顺序来执行。每一次都会将变量i初始化,所以这里的闭包也不存在了。那是不是说采取这种普通函数的写法就无法实现跟匿名函数一样的效果呢?其实也很简单,只需要修改一下函数的调用方式就好了。
var f=obj.fn()
f()
f()
f()
当采取这种写法时,相当于将obj.fn()这个函数从对象中抽离了出来,抽离后的f()长这个样子:
function fn() {
console.log("我不会自动执行");
let i = 10
return () => {
console.log(++i);
}
}
var f= fn
这个方式是不是看起来更直观一些呢?这就是一个最简单的闭包。
三、题目剖析
如果对上文提到的东西都掌握了,再来回顾这道题是不是就很简单了。(很遗憾笔者在第一次
遇到这题时,对这些概念掌握得不透彻所以错得一塌糊涂。。)。
var number = 2;
var obj = {
number: 3,
db1: (function(){
console.log(this); //语句1
this.number *= 4; //语句2
return function(){
console.log(this);
this.number *= 5;
}
})()
}
var db1 = obj.db1;
db1();
obj.db1();
console.log(obj.number);
console.log(window.number);
1.先分析obj.db1的结构,它是一个立即执行函数。在这个函数内部,又封装了一个函数,这里同样是一个闭包。
2.匿名函数的外层会自动执行并仅执行一次。也就是说,语句1和语句2不需要任何调用,待页面渲染解析完毕后便会自动执行。此时的this指向window对象,this.number=2*4=8
3.执行db1(),此时相当于在全局作用域中调用db1内部嵌套的函数,内部的this自然也指向windows,this.number=8*5=40
4.执行obj.db1(),此时通过obj调用db1(),与上一个调用的区别仅在于闭包内部的代码。
此时的this指向的是obj对象,this.number*=5改变的是obj.number的值。
所以最后输出结果如下
最后来一个自己总结的解题技巧,按照这规律来就完事了:
1.若obj里的函数只有一层,那么谁调用this就指向谁
2.若obj里的函数有2层,且第二层的函数是通过return的形式调用。若是箭头函数就指向obj,若是普通函数就指向全局
3.若obj里的函数有2层,且第二层的函数不通过return,而是直接调用,此时this指向全局,
感兴趣的可以自己写几个demo来测试一下这个规律,
看到最后的小伙伴们,非常感谢~希望能对大家有帮助