别低估本身,但,这道题,真的有点难

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

当我看到题目的时候,第一眼以为贼简单,可是看提问越到后面越懵逼了,在琢磨着能不能猜对了…………web

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));
复制代码

果不其然,我答错了好多……微信

这道题目,题简单的不能再简单了,就是对象,函数,变量,可是问的很深,没有扎实的知识,很难确切的回答上这些问题。app

要回答这些问题,关键仍是要深刻了解 this 和 逗号表达式。编辑器

首先咱们简单回顾下这两个很是重要的知识。最后再看看文末的综合题目。函数

this

this 在 js 中很是重要。在笔试的时候,若是有考察基础知识,那么出现 this 的几率那可不是通常的高。学习

普通函数

一句话:谁调用就指向谁。ui

var person={
   age:20,
   getAge(){
       var age = 30;
       return this.age;
    },
};
person.getAge(); // 20
复制代码

这个的 getAge 方法是 person 调用的,因此 this 指向 person,person.age 输出为 20;this

箭头函数

一句话:调用者指向谁,则指向谁。spa

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。

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

这些题,确实很难!若是有不理解的,多看几篇解答,相信你必定会豁然开朗!或者关注公众号,加做者微信,一对一解答。

欢迎关注个人微信公众号,一块儿作靠谱前端!

follow-me
相关文章
相关标签/搜索