今天一个朋友转给我一道题,让我帮忙解释解释。
当我看到题目的时候,第一眼觉得贼简单,但是看提问越到后面越懵逼了,然后在琢磨着能不能猜对了
…………
题目如下:
var age = 10;
var person={
age:20,
getAge(){
var age = 30;
return this.age;
},
};
alert(age,age*2);
person.getAge();
var b = person.getAge;
b();
(person.getAge)();
(1,person.getAge)();
(1,person.getAge.bind(person))();
(person.getAge,person.getAge)();
(person.getAge=person.getAge)();
person.getAge.call();
person.getAge.call(person);
function getAge2() {
this.age = 40;
console.log(person.getAge());
};
getAge2();
console.log(age);
function getAge3(){
this.age = 50;
this.getAge4 = ()=>{
console.log(person.getAge.call(this));
}
}
new getAge3().getAge4();
console.log(age);
function getAge4(){
this.age = 60;
this.getAge5 = ()=>{
console.log(person.getAge.call(this));
}
}
new getAge4().getAge5();
console.log(age);
var age2 = 10;
var person2={
age2:20,
getAge2:()=>{
var age2 = 30;
return this.age2;
},
};
console.log(person2.getAge2.call());
console.log(person2.getAge2.call(person2));
【另一篇不同题型的姊妹篇:别高估自己,这道题,有点难!】
果不其然,我答错了好多……
这道题目,题简单的不能再简单了,就是对象,函数,变量,但是问的很深,没有扎实的知识,很难确切的回答上这些问题。
要回答这些问题,关键还是要深入了解 this 和 逗号表达式。
首先我们简单回顾下这两个非常重要的知识。最后再看看文末的综合题目。
this
this 在 js 中非常重要。在笔试的时候,如果有考察基础知识,那么出现 this 的概率那可不是一般的高。
普通函数
一句话:谁调用就指向谁。
var person={
age:20,
getAge(){
var age = 30;
return this.age;
},
};
person.getAge(); // 20
这个的 getAge 方法是 person 调用的,所以 this 指向 person,person.age 输出为 20;
箭头函数
一句话:调用者指向谁,则指向谁。
var age = 10;
var person={
age:20,
getAge:()=>{
var age = 30;
return this.age;
},
};
person.getAge(); // 10
这个的 getAge 方法是 person 调用的,则 getAge 和 person 的指向一致,person 是 window 调用的(参照上述普通函数),所以 person 指向 window,因此 getAge 也指向 window,输出 10。
var obj = {
foo: function () {}
};
var foo = obj.foo;
// 写法一
obj.foo()
// 写法二
foo()
上面代码中,虽然obj.foo
和foo
指向同一个函数,但是执行结果可能不一样。请看下面的例子。
var obj = {
foo: function () { console.log(this.bar) },
bar: 1
};
var foo = obj.foo;
var bar = 2;
obj.foo() // 1
foo() // 2
这种差异的原因,就在于函数体内部使用了this
关键字。很多教科书会告诉你,this
指的是函数运行时所在的环境。对于obj.foo()
来说,foo
运行在obj
环境,所以this
指向obj
;对于foo()
来说,foo
运行在全局环境,所以this
指向全局环境。所以,两者的运行结果不一样。
这种解释没错,但是教科书往往不告诉你,为什么会这样?也就是说,函数的运行环境到底是怎么决定的?举例来说,为什么obj.foo()
就是在obj
环境执行,而一旦var foo = obj.foo
,foo()
就变成在全局环境执行?
强制改变 this 指向
一句话:你说指向谁就指向谁。
改变 this 指向,有 call,apply,bind 这几种方法。
var age = 10;
var person={
age:20,
getAge:function(){
var age = 30;
return this.age;
},
};
person.getAge.call(person);
这里在执行 getAge 方法的时候,传入了 person,那么 getAge 的 this 指向 person,所以输出 20。
逗号表达式
逗号表达式(Comma Operator)
(http://www.ecma-international.org/ecma-262/#sec-abstract-operations)
逗号表达式 可以用于分割任何一个表达式,可以用于分割函数参数等。
function test(){
let a=1;
return ++a,a++,a++;
}
console.log(test());
这里逗号用于分割表达式,等价于:
function test(){
let a=1;
++a;
a++;
return a++;
}
console.log(test());
自然,答案不用多说,是 3 ,因为 return 后面的是 a++,如果是 ++a ,那结果是 4(这里不太明白的,自行学习 “++运算符”)。
再看原题
接下来我们来一一解答上面的问题,我再贴一下题目。
var age = 10;
var person={
age:20,
getAge(){
var age = 30;
return this.age;
},
};
alert(age,age*2)
这里逗号分隔的是参数列表(Argument Lists),因为 alert 只接受一个参数,即第一个参数,后面的都忽略。因此弹窗是 10。
var b = person.getAge; b();
这个输出结果是 10,主要是要区别 person.getAge(),差别在于当前是先赋值给一个变量 b,然后执行 b()。
等价于:
var b = person.getAge;
window.b();
回到最前面的知识点,[谁调用,指向谁],这里赋值变量之后,调用 b 方法的是 window,所以 this 指向 window,答案是 10。
(person.getAge)();
这里括号只是起到一个分割的作用,并没有实际意义,等价于 person.getAge()。所以答案是 20;
(1,person.getAge)();
这里和上一个题目的差别是引入了逗号表达式。我们知道逗号表达式返回的是最后一个值,即 person.getAge,注意这里是表达式返回值。
等价于:
var a = (1,person.getAge);
a();
或者:
var a = (false||person.getAge);
a();
显然,这里 a 调用方是 window,所以答案是 10。注意这里是非严格模式下。
后面题目如果牵涉到模式区别的时候通常都是指非严格模式,非严格模式与严格模式下的重要区别是当 this 为 null 或者 undefined 的时候,是否会改为指向 window。非严格模式下会改为指向 window,严格模式就仍然为保持 null 或者 undefined。
"use strict";
var age = 10;
var person={
age:20,
getAge(){
var age = 30;
return this.age;
},
};
console.log(person.getAge());// 输出仍然是 20,this 指针指向 person
var a = (1,person.getAge);
a();// Cannot read property 'age' of undefined , this 是 undefined
(1,person.getAge.bind(person))();
这道题是 逗号表达式 和 bind 知识点,参照上面的分析,等价于:
var a =(1,person.getAge.bind(person));
a();
这里与上一道的区别是,返回的是一个 bind 之后的函数,a 方法已经强制指向 person 了,所以等价于:
person.getAge.bind(person)()
答案为 20,this 指向 person。
(person.getAge,person.getAge)();
这个其实就是一个逗号表达式,返回最后一个项的值,这里连续设置两个 person.getAge,只是一个陷阱,前面的 person.getAge 对结果没影响。咬定青山山不放松,坚定排除陷阱。
等价于:
(1,person.getAge)();
答案与上面一致,为 10。
(person.getAge=person.getAge)();
这里有点不一样,意思为:对象 person,给它设置了一个属性 getAge(如果有 getAge 属性,则重新赋值),将这个属性 getAge 用 person.getAge 赋值。
括号里面是一个赋值表达式,表达式的返回值,就是这个重新被赋值了 person.getAge 的 person 对象下面的 getAge 属性。
等价于:
var person.getAge = person.getAge // 赋值
var a = person.getAge; // 表达式返回值
a();
上面已经解释,所以输出为 10。
person.getAge.call();person.getAge.call(person);
这里使用 call 函数改变 this 指针。在不传作用域参数的时候,在严格模式下 this 是 undefined,这里是非严格模式。
call,apply,bind 都可以改变 this 指针,所以:
// 10 call 为空,this 为 undefined,重新指向为 window
person.getAge.call();
// 20 this 指针指向 person
person.getAge.call(person);
末尾大题
题目 1
function getAge2() {
this.age = 40;
console.log(person.getAge());// 20
};
getAge2();
console.log(age);// 40
里面的 person.getAge 仍然是谁调用执行谁,person.getAge 方法的 this 是指向 person,输出是 20。但是里面有一个 this.age = 40 的赋值。
那么这个 this 是啥?当然也是谁调用指向谁,这里的 getAge2 是 window 调用,所以这里的 this 是 window。
那么这相当于将外层的 age 已经修改为 40 了,所以 console.log(age) ,已经变成 40 了。
题目 2
function getAge3(){
this.age = 50;
this.getAge4 = ()=>{
console.log(person.getAge.call(this));// 50
}
}
new getAge3().getAge4();
console.log(age); // 40
在 getAge3 里面,定义了一个公有方法 getAge4,但是这里是一个箭头函数,里面使用 call 修改函数 person.getAge 的 this 指针指向当前的 this,那么当前 this 指向哪里呢?
仍然是看谁调用了 getAge4,这里是 new getAge3() 这个实例调用了 getAge4,所以 call 传进去的 this 是指向 new getAge3(),这里 this.age=50 被赋值为 50 了,所以输出为 50。
但是全局的 age 并没有被修改,与 题1 不一样,这里的 this 指向了实例对象,并不是指向 window,所以全局的 age 仍然为 40。
题 3
当读到这里的时候,你或许已经豁然开朗,觉得很理解了,那么这一题你会做吗?
var age2 = 10;
var person2={
age2:20,
getAge2:()=>{
var age2 = 30;
return this.age2;
},
};
console.log(person2.getAge2.call()); // 10
console.log(person2.getAge2.call(person2)); // 10
这里的核心差别是 getAge2 函数是使用了箭头函数。按上面理解的,箭头函数的 this 指向 person2 ?
错了!不是如此。
实际上是箭头函数没有自己的 this,既然没有,那 call 怎么能改变它自身的 this 指针呢?
参照文档:(https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions)
所以上面不管是 call(),还是 call(person2),都无法修改 this 指针,所以两者都输出全局的 10。
End
我们再来看一道题,箭头函数的 this 。
var age = 10;
var person={
age:20,
child:{
age:40,
getAge:function(){
return this.age;
},
},
child2:{
age:40,
getAge:()=>{
return this.age;
},
},
child3:function(){
this.getAge = ()=>{
return this.age;
}
}
};
console.log(person.child.getAge());// 40
console.log(person.child2.getAge()); // 10
console.log((new person.child3()).getAge()); // undfined
根据我们上面的知识,可以知道 person.child.getAge() 的 this 就是谁调用指向谁,这里是 child 调用,所以指向 child ,输出 40 。
我们知道,箭头函数是没有自己的 this,那么它的 this 是啥呢?
就是当前箭头函数逐级向上查找,找到函数作用域的 this,则为当前箭头函数的 this。
所以,这里 person.child2.getAge 函数的父级调用方是 child2,但是 child2 是对象,也没有自己的 this 和 作用域,所以继续向上查找 person,然后发现 person 也是对象,再继续向上查找,找到 window 这个大 Boss 了,所以 this 就指向 window ,输出为 10。
(new person.child3()).getAge() 的 this ,同理向上一级查找,发现 new person.child3() 是个函数实例,所以 this 指向 child3 的这个实例,然而 child3 实例没有 age 属性,所以输出 undefined。
结论如下:
箭头函数的 this 是一个普通变量,指向了父级函数的 this,且这个指向永远不会改变,也不能改变。
但是如果需要修改箭头函数的 this ,可以通过修改父级的 this 指针,来达到子箭头函数 this 的修改。(根本原因是箭头函数没有 this,而是在运行时使用父级的 this)。
————————————————
版权声明:本文为CSDN博主「程序me」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/lqyygyss/article/details/106346253