this指向问题总结

326 阅读6分钟

this指向问题总结

this就是函数运行时所在的对象(环境)。这本来并不会让用户糊涂,但是 JavaScript 支持运行环境动态切换,也就是说,this的指向是动态的,没有办法事先确定到底指向哪个对象,这才是最让初学者感到困惑的地方。

由于函数可以在不同的运行环境执行,所以需要有一种机制,能够在函数体内部获得当前的运行环境(context)。所以,this就出现了,它的设计目的就是在函数体内部,指代函数当前的运行环境。

普通函数

普通函数中this的指向取决于函数的调用模式

1.普通函数调用中,严格模式下this指向undefind,非严格模式下this指向window

var a = 1;
function fn() {
    console.log(this); //window
    console.log(this.a); //1
}
fn();

非严格模式下方法调用会发生自动装箱。若初始值是 undefined,this 值会被设为全局对象。

function Animal() {}

Animal.prototype.speak = function() {
    console.log('this-原型方法', this);
    return this;
}

Animal.eat = function() {
    console.log('this-静态方法', this);
    return this;
}

let obj = new Animal();
let speak = obj.speak;
speak(); // Window

let eat = Animal.eat;
eat(); // Window

class 体内部的代码总是在严格模式下执行

 class Animal {
	speak() {
        console.log('this-原型方法', this)
        return this;
	}
	static eat() {
        console.log('this-静态方法', this)
        return this;
	}
}
let obj = new Animal();
obj.speak(); //Animal {}
let speak = obj.speak;
speak(); //undefined
Animal.eat(); //构造函数本身
let eat = Animal.eat;
eat(); //undefined


2 对象方法调用中,this指向对象

   const obj = {
        name: "bwf",
        age: 18,
        eat: function() {
            console.log(this); //{name: "bwf", age: 18, eat: ƒ}
            console.log(this.name + "正在吃饭"); //bwf正在吃饭
        }
    }
    obj.eat();
  function foo() {
        console.log(this.a);
    }
    var obj2 = {
        a: 2,
        fn: foo
    };
    var obj1 = {
        a: 1,
        o1: obj2
    };
    obj1.o1.fn(); // 2  fn最终是在obj2的环境中被调用的,所以结果是2
 var o = {
    a: 10,
    b: {
        // a:12,
        fn: function() {
            console.log(this.a); //undefined fn是在b的环境中被调用的
        }
    }
}
o.b.fn();
  • 如果一个函数中有this,但是它没有被上一级的对象所调用,那么this指向的就是window,
  • 如果一个函数中有this,这个函数中包含多个对象,尽管这个函数是被最外层的对象所调用,this指向的也只是它上一级的对象
  • 如果一个函数中有this,这个函数中包含多个对象,尽管这个函数是被最外层的对象所调用,this指向的也只是它上一级的对象
var o = {
    a: 10,
    b: {
        a: 12,
        fn: function() {
            console.log(this.a); //undefined
            console.log(this); //window
        }
    }
}
var j = o.b.fn;
j();

上面例子中,虽然函数fn是被对象b所引用,但是在将fn赋值给变量j的时候并没有执行所以最终指向的是window

3构造函数中,this指向实例对象

 function Person(name, age) {
    this.name = "bwf";
    this.age = 20;
    this.sayHi = function() {
        return this.name;
    }
}
var p = new Person();
console.log(p.name); //bwf
console.log(p.sayHi()); //bwf

console.log(p instanceof Person); //true

补充:

  1. 构造函数中默认返回this
  2. 构造函数中如果返回基本数据类型:number,string,undefined ,boolean则this还是指向构造函数的实例,null也是哦;如果返回是对象,数组,函数引用数据类型时,那么this就指向返回的对象。自己可以试下😯

new关键字做了什么?

4上下文调用,call apply改变this指向

  • call的含义: 是函数的方法 每一个函数都有这个call方法
  • call的作用: 1.call会调用这个函数 2.call 可以改变函数中this的指向
  • call的参数: 第一个参数: 传什么,函数中的this就指向什么;其他参数: 和函数的形参,一一对应;
   function Person() {
        console.log('person-this', this)
        this.name = "bwf";
        this.age = 18;
    }

    function Student() {
        console.log('student-this', this);
        //不调用call Person构造函数中的this指向p实例对象;调用call后Person构造函数中的this指向stu实例对象
        Person.call(this); 
    }
    const p = new Person();
    const stu = new Student();
    console.log('stu', stu)
    
function foo() {
    console.log(this);
}

function bar() {
    console.log(this);
}
foo.call({
    name: '小米'
}); // {name: "小米"}

const bar1 = bar.bind({
    num: 123
})
bar1() // {num: 123}

补充:call,apply , bind的比较

  1. 都会改变函数执行时的上下文
  2. apply 、call 是立即调用;bind返回对应的函数,调用后才会执行
  3. call的第二个参数是参数列表的形式;apply的第二个参数是数组
  4. call,apply,bind的应用场景见下方:
//求数组中的最大值
const arr = [1, 9, 3, 4]
const res1 = Math.max(...arr)
const res2 = Math.max.apply(null, arr)
const res3 = Math.max.call(null, ...arr)


//判断变量类型 
const obj = {
    name: 'bwf'
}
console.log(typeof obj) //object
console.log(typeof arr) //object
console.log(typeof null) //object
console.log(Object.prototype.toString.call(obj)) //[object Object]
console.log(Object.prototype.toString.call(arr)) //[object Array]
console.log(Object.prototype.toString.call(null)) //[object Null]
        
        
//继承

function Person(name, age) {
    this.name = name;
    this.age = age;
}

function Student(name, age) {
    Person.call(this, name, age)

}
const s = new Student('bwf', 18)

手写bind函数:

   Function.prototype.mybind = function(ctx, ...args) {
          // fix
        const fn = this;
        return function() {
            fn.apply(ctx, args);
        };
    };

    function foo(x, y) {
        console.log(this, x, y);
    }

    const foo1 = foo.mybind({
        name: "zhangsan"
    }, 10, 20);
    foo1();

5 事件绑定中的this

事件绑定共有三种方式:行内绑定、动态绑定、事件监听;

<input type="button" value="按钮1" onclick="clickFun()">

  function clickFun() {
      console.log('input-this', this) // 此函数的运行环境在全局window对象下,因此this指向window;
     }
 <input type="button" value="按钮2" onclick="console.log('行内this',this)">
 上面this指向input dom元素
 <input type="button" value="按钮3" id="btn">
 var btn = document.getElementById('btn');
 btn.onclick = function() {
    console.log('动态事件绑定this', this)  //this指向input dom元素
 }

6 window定时器中的this

var obj = {
    fun: function() {
        console.log('定时器中的this',this);
    }
}​
setInterval(obj.fun, 1000); // this指向window对象
setInterval('obj.fun()', 1000); // this指向obj对象

在上面的代码中,setInterval(obj.fun,1000) 的第一个参数是obj对象的fun ,因为 JS 中函数可以被当做值来做引用传递,实际就是将这个函数的地址当做参数传递给了 setInterval 方法,换句话说就是 setInterval 的第一参数接受了一个函数,那么此时1000毫秒后,函数的运行就已经是在window对象下了,也就是函数的调用者已经变成了window对象,所以其中的this则指向的全局window对象;obj对象中的fun并没有调用

而在 setInterval('obj.fun()',1000) 中的第一个参数,实际则是传入的一段可执行的 JS 代码;1000毫秒后当 JS 引擎来执行这段代码时,则是通过 obj 对象来找到 fun 函数并调用执行,那么函数的运行环境依然在 对象 obj 内,所以函数内部的this也就指向了 obj 对象;

那如何让定时器中对象方法中的this始终指向对象呢?

 setTimeout(() => {
      obj.fun()
    }, 1000)

上面代码中,obj.fun放在一个匿名函数之中,这使得obj.funobj的作用域执行,而不是在全局作用域内执行,所以能够显示正确的值。

另一种解决方法是,使用bind方法,将obj.fun这个方法绑定在obj上面。

   setTimeout(obj.fun.bind(obj), 1000)

箭头函数

箭头函数中没有this

 var obj = {
    name: "bwf",
    foo() {
        setTimeout(() => {
            console.log(this);
        }, 1000);
    },
};

obj.foo() // obj

总结:

  1. 箭头函数中没有this,如果用到了this,就按照变量的查找规则沿着作用域链向上查找
  2. 普通函数中this的指向,需要通过确定调用模式来找
  3. 而箭头函数中的this指向,只需要按照变量的查找规则向上查找就行