​ this指针是前端面试过程中最常考的问题之一,也是比较坑的问题之一,为了避免多次被坑先将this指针的相关问题记录如下,主要包括判断this指针的几类常见的方法、改变this指针的几种方法以及一些面试真题。

如何判断this的指向

在使用 this 时,为了避坑,你要谨记以下四点:

  1. 当函数作为对象的方法调用时,函数中的 this 就是该对象;
  2. 当函数被正常调用时,在严格模式下,this 值是 undefined,非严格模式下 this 指向的是全局对象 window;
  3. 嵌套函数中的 this 不会继承外层函数的 this 值。
  4. 我们还提了一下箭头函数,因为箭头函数没有自己的执行上下文,所以箭头函数的 this 就是它外层函数的 this。
  5. 当this遇到return时,如果返回值是一个对象,那么this指向的是构造函数的实例但是并没有被返回,如果返回值不是一个对象,那么this还是指向构造函数创建的实例。

下面从几道例题中去验证:

  1. 直接使用的函数this指向window

    1
    2
    3
    4
    5
    6
    7
    8
    function a(){
    var user= "呵呵"
    console.log(this.user),
    console.log(this),
    }
    a();
    // undefined
    // window
  2. 嵌套函数中的 this 不会继承外层函数的 this 值

    1
    2
    3
    4
    5
    6
    7
    8
    function o(){
    var user = "呵呵";
    function fn(){
    console.log(this.user);
    }
    fn();
    }
    o();
  3. 当函数作为对象的方法调用时,函数中的 this 就是该对象,且永远指向最后调用它的对象

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    //题目一
    var o={
    a:10,
    b:{
    a=12,
    fn:function(){
    console.log(this.a)
    }
    }
    }
    o.b.fn() //12
    //题目二
    var o = {
    a:10,
    b:{
    a:12
    fn:function(){
    console.log(this.a);
    console.log(this);
    }
    }
    }
    var j = o.b.fn;
    j(); // undefined //window
  4. 构造函数中的this指向

    1
    2
    3
    4
    5
    function Fn(){
    this.user = '呵呵'
    }
    var p = new Fn()
    console.log(p.user) // 呵呵
  5. 当this遇到return时

    如果返回值是一个对象,那么this指向的是构造函数的实例但是并没有被返回,如果返回值不是一个对象,那么this还是指向构造函数创建的实例。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    function fn(){
    this.user = "呵呵"
    return {}
    }
    var a = new fn()
    console.log(a.user) //undefined
    ==============================
    function fn(){
    this.user = "呵呵"
    return function(){}
    }
    var a = new fn()
    console.log(a.user) //undefined
    ==============================
    function fn(){
    this.user = "呵呵"
    return 1
    }
    var a = new fn()
    console.log(a.user) //呵呵
    ==============================
    function fn(){
    this.user = "呵呵"
    return undefined
    }
    var a = new fn()
    console.log(a.user) //呵呵
  6. 箭头函数中的this指向

    箭头函数是ES6中的特性,箭头函数没有执行上下文本所以没有this,它会沿用/捕获外部环境的this。也就是说,箭头函数内部与外部的this是保持一致的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    /* 题目一 */
    this.a = 20
    var test = {
    a:40,
    init:()=>{
    console.log(this.a)
    function go(){
    this.a = 60
    console.log(this.a)
    }
    go.prototype.a = 50
    return go
    }
    }
    var p = test.init()
    p()
    new (test.init())()
    /* 题目二 */
    this.a = 20
    var test = {
    a:40,
    init:function(){
    console.log(this.a)
    function go(){
    this.a = 60
    console.log(this.a)
    }
    go.prototype.a = 50
    return go
    }
    }
    var p = test.init()
    p()
    new (test.init())()

如何改变this的指向

1、call()和apply()

​ **call():**第一个参数表示要把this指向的新目标,第二个之后的参数其实相当于传参,参数以逗号隔开(性能较apply略好)。

​ 用法:a.call(b,1,2);表示要把a函数的this指向修改为b的this指向,并运行a函数,传入参数是(1,2)

apply():第一个参数同上,第二个参数接收一个数组,里面也是传参,只是以数组的方式,相当于arguments

用法:a.apply(b,[1,2]);表示要把a函数的this指向修改为b的this指向,并运行a函数,传进去的参数是(1,2);

*注意:即使只有一个参数的话,也要是数组的形式

1
2
3
4
5
6
7
8
9
10
11
12
//call 的传参和apply的传参
function say(arg1,arg2){
console.log(this.name,arg1,arg2);
};
var obj = {
name : 'tom',
say : function(){
console.log(this.name);
}
}
say.call(obj,'one','two');//tom one two
say.spply(obj,['one','two']);//tom one two 效果一样

2、bind()

​ 作用:bind()方法会创建一个新的函数,称为绑定函数,当调用这个绑定函数时,绑定函数会以创建它时传入bind()方法的第一个参数作为this,传入bind()的第二个及以后的参数加上绑定函数运行时本身的参数按照顺序作为原函数的参数来调用原函数。

​ 用法:

1
2
3
4
5
6
7
8
9
var foo = {
bar : 1,
eventBind: function(){
$('.someClass').on('click',function(event) {
/* Act on the event */
console.log(this.bar); //1
}.bind(this));//这里的this是eventBind的this,即指向的是foo
}
}

bind 是返回对应函数,便于稍后调用;apply 、call 则是立即调用 。

3、new

new的运行原理:

1
2
3
4
5
6
7
8
9
new Animal('cat') = {//类似这样
var obj = {};//先定义一个空对象
obj.__proto__ = Animal.prototype;
//把 obj 的__proto__ 指向构造函数 Animal 的原型对象 prototype,
//此时便建立了 obj 对象的原型链:
//obj->Animal.prototype->Object.prototype->null
var result = Animal.call(obj,"cat");//改变this指向,从Animal改变到obj上
return typeof result === 'object'? result : obj; //返回
}

用法:

1
2
3
4
5
function Fn(){
this.user = "追梦子";
}
var a = new Fn();//this指向a
console.log(a.user); //追梦子

4、return

​ 在构造函数的时候,使用return进行返回一个Object的时候,当去new一个实例对象的时候,会将this指向改变为return的Object;

​ 用法:

1
2
3
4
5
6
7
8
9
function fn()  
{
this.user = '追梦子';
return {
"user" : "111"
};
}
var a = new fn;
console.log(a.user); //111

使用fn.apply(this,arr)和fn(…arr)的区别

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
当然,在这道题目中用fn(...arr)也能成功的输出
/*
题目描述
将数组 arr 中的元素作为调用函数 fn 的参数
*/
//示例1
//输入
function test(greeting, name, punctuation) {
return greeting + ', ' + name + (punctuation || '!');
}

['Hello', 'Ellie', '!']

//代码
function argsAsArray(fn, arr) {
return fn.apply(this, arr);
//return fn(...arr)
}

答案:https://segmentfault.com/q/1010000004566233

面试真题

  1. this的指向(from:字节跳动技术中台一面)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    let a = {
    x: 1,
    print () {
    console.log(this.x);
    }
    }
    a.print(); //1
    let print = a.print()
    print(); //undefined
    //问:如何改变this的指向
    let print = a.print.bind(a);
    print()