别低估自己,但,这道题,真的有点难

今天一个朋友转给我一道题,让我帮忙解释解释。

当我看到题目的时候,第一眼觉得贼简单,但是看提问越到后面越懵逼了,然后在琢磨着能不能猜对了

…………

题目如下:

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。

 
 
  1. var obj = {

  2. foo: function () {}

  3. };

  4. var foo = obj.foo;

  5. // 写法一

  6. obj.foo()

  7. // 写法二

  8. foo()

上面代码中,虽然obj.foofoo指向同一个函数,但是执行结果可能不一样。请看下面的例子。

 
 
  1. var obj = {

  2. foo: function () { console.log(this.bar) },

  3. bar: 1

  4. };

  5. var foo = obj.foo;

  6. var bar = 2;

  7. obj.foo() // 1

  8. foo() // 2

这种差异的原因,就在于函数体内部使用了this关键字。很多教科书会告诉你,this指的是函数运行时所在的环境。对于obj.foo()来说,foo运行在obj环境,所以this指向obj;对于foo()来说,foo运行在全局环境,所以this指向全局环境。所以,两者的运行结果不一样。

这种解释没错,但是教科书往往不告诉你,为什么会这样?也就是说,函数的运行环境到底是怎么决定的?举例来说,为什么obj.foo()就是在obj环境执行,而一旦var foo = obj.foofoo()就变成在全局环境执行?

 

强制改变 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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值