JavaScript this指向问题深入理解

JavaScript this指向问题深入理解

文章目录

    • JavaScript this指向问题深入理解
      • 一.前言
      • 二.this指向
        • 1.一般函数调用模式
        • 2.对象调用模式
          • 1.示例1
          • 2.示例2
          • 3.示例3
          • 4.示例4
        • 3.构造函数调用模式
          • 1.示例1
          • 2.示例2
        • 4.构造函数中指明返回值
          • 1.返回复杂对象
          • 2.返回值类型
          • 3.返回null
        • 5.事件绑定调用模式
        • 6.定时器函数调用模式
        • 7.箭头函数的this指向
          • 1.示例1
          • 2.示例2
          • 3.示例3
          • 4.示例4
        • 8.保存this指向
      • 三,强制改变this指向
        • 1.call()
          • 1.示例1
          • 2.示例2
          • 3.应用场景
        • 2.apply()
          • 1.示例1
          • 2.示例2
          • 3.应用场景
        • 3.bind()
          • 1.示例1
          • 2.示例2
          • 3.应用场景
        • 4.apply()和call()和bind()区别
          • 1.相同点
          • 2.不同点

一.前言

this指向,是前端开发人员一直比较头疼的问题,一般称为万恶的this指向,我们去面试,经常也会遇到面试官给你的this指向的有关题目,有些基本靠猜,希望经过本文的学习,会对this指向有个很直观的判断!

注意:

普通函数this指向由调用方式决定,跟定义环境无关,也就是说,普通函数的this指向,实际上是动态作用域确定的

箭头函数this指向由定义环境决定的,与调用方式无关,也不可以强制改变它的this指向,实际上箭头函数是没有this指向的,指向的是外层.

二.this指向

this代表着函数运行时,自动生成的一个内部对象,只能在函数内部使用,随着函数使用场合不同,this的值会发生变化,但是有个总的原则:this指的是函数调用对象或事件的调用对象,找不到调用对象时,this指向window对象

严格模式下,全局作用域下的this指向undefined

非严格模式下,全局作用域下的this指向window

下面所有内容均为非严格模式的条件下

1.一般函数调用模式

function foo() {
    console.log(this)//Window
}
foo();

结论:

(1) 非严格模式下,this指向window

(2) 严格模式下,this指向undefined

2.对象调用模式

1.示例1
var obj={
    a:1,
    foo:function(){
        console.log(this.a);//1
    }
}
obj.foo();//1

结论:this指向obj这个调用它的对象

另外,我们再对它加以改造下

2.示例2
var a=10;
var obj={
    a:1,
    foo:function(){
        console.log(this.a);//10
    }
}
var b=obj.foo;
b();//10

这个时候,打印的是10,我们来分析下原因:

这里是将obj.foo赋值给了变量b,此时b是一个函数,只是赋值而已,并没有调用,所以this指向并不确定,后面b()调用了,是普通函数调用的模式,此时this指向的是window,所以打印结果应该是外面的a值

3.示例3
var a = 100;
var obj = {
    a: 1,
    c: {
        foo: function () {
            console.log(this.a);
        }
    }
}
obj.c.foo();

这里是obj.c来调用的这个函数,所以此时this指向应该指向的是 c这个对象,而c对象是没有a这个值的,所以打印undefined

4.示例4
var a = 100;
var obj = {
    a: 10,
    b: {
        a: 1,
        c: function () {
            console.log(this.a);
        }
    }
}
obj.c = obj.b.c;
obj.c()//10

这里相当于动态给obj家里一个方法,此时是obj调用,所以this指向是obj

总结:对象调用方式下,this指向为调用它的对象

3.构造函数调用模式

如果在一个函数前面加一个new关键字来调用,那么就会创建一个连接到该函数的prototype成员的新对象,同时,this会绑定到这个新对象上.此时,这个函数就可以成为此对象的构造函数

1.示例1
var name='liuqiao';
function Person(){
    this.name='wangmiao';
}
var p1=Person()
console.log(p1.name);//报错 Cannot read property 'name' of undefined

这个地方不要被坑了,只是普通的调用函数而已,我们都知道,函数没有返回值,默认返回undefined,p1接收一个值为undefined,那肯定是没有name属性的.所以这里会报错

2.示例2
var name='liuqiao';
function Person(){
    this.name='wangmiao';
    console.log(this);//Person {name: "wangmiao"}
}
var p1=new Person()
console.log(p1.name);//wangmiao

在构造函数中,new出一个对象时,this指向这个构造函数.new关键字会改变这个this指向

4.构造函数中指明返回值

原则上构造函数不应该有返回值,但是如果真的写了返回值,this指向就会有不一样的表现了

1.返回复杂对象
var name='liuqiao';
function Person(){
    this.name='wangmiao';
    return {
        name:'yingbin'
    };
}
var p1=new Person()
console.log(p1.name);//输出yingbin

当返回复杂对象时,this指向新对象,也就是new Person()返回的新对象

2.返回值类型
var name='liuqiao';
function Person(){
    this.name='wangmiao';
    return 1;
}
var p1=new Person()
console.log(p1.name);//wangmiao

当返回值类型时,this指向不变,this对象实例本身

3.返回null
var name='liuqiao';
function Person(){
    this.name='wangmiao';
    return null;
}
var p1=new Person()
console.log(p1.name);//wangmiao

虽然typeof null返回的是object,但是说到底,它还是值类型的,所以还是返回this对象实例本身

5.事件绑定调用模式

var oBtn = document.getElementById("btn");
oBtn.onclick = function() {
    console.log(this); // btn
}

oBtn调用了这个匿名函数,所以指向此时this指向了oBtn

6.定时器函数调用模式

 setInterval(function () {
   console.log(this); // window
}, 1000);

定时器函数调用时,this指向window

7.箭头函数的this指向

箭头函数内部的this指向,取决于箭头函数定义的位置,而非箭头函数的调用对象.

当我们使用箭头函数时,函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象,并不是因为箭头函数内部有绑定this的机制.实际原因是:箭头函数根本没有自己的this,它的this是继承外面的,因此内部的this就是外层代码块的this.

1.示例1
var name = 'liuqiao';
var obj = {
    name: 'zhangsan',
    o: () => {
        console.log(this.name);
    }
}
obj.o(); //liuqiao
2.示例2
var obj = {
  foo() {
    console.log(this);
  },
  bar: () => {
    console.log(this);
  }
}

obj.foo() // {foo: ƒ, bar: ƒ}
obj.bar() // window
3.示例3
 var name = 'liuqiao';
 var obj = {
     name: 'zhangsan',
     fn1: function () {
         console.log(this.name);
     },
     fn2: function () {
         setTimeout(function () {
             console.log(this.name);//liuqiao
         },0);
     }
 }
 obj.fn1();//zhangsan
 obj.fn2();//liuqiao

上面的这段示例,因为setTimeout里面的this指向的是window,所以,fn1和fn2this不一样,下面示例4,我们将其改造一下即可

4.示例4
var name = 'liuqiao';
var obj = {
    name: 'zhangsan',
    fn1: function () {
        console.log(this.name);
    },
    fn2: function () {
        setTimeout(() => {
            console.log(this.name);
        }, 0);
    }
}
obj.fn1();//zhangsan
obj.fn2();//zhangsan

使用箭头函数,将其改造一下,即可得到我们想要的结果

8.保存this指向

当我们想用到某一个层的this,但是到了另一层之后,this会发生改变,这个时候,我们会使用一个变量将this给存起来,然后进行使用,如var _this=this;

var name = 'liuqiao';
 var obj = {
     name: 'zhangsan',
     fn1: function () {
         console.log(this.name);
     },
     fn2: function () {
         var _this=this;
         setTimeout(function () {
             console.log(_this.name);//zhangsan
         },0);
     }
 }
 obj.fn1();//zhangsan
 obj.fn2();//zhangsan

三,强制改变this指向

call(),apply(),bind()这是那种都是强制改变this指向的,作用都是一样相同的,只是传参方式不一样

1.call()

/*call()方法*/
function.call(thisObj[, arg1[, arg2[, [,...argN]]]]);
1.示例1
var name = 'liuqiao';
var obj = {
    name: 'zhangsan',
    fn1: function () {
        console.log(this.name);
    },
    fn2: function () {
        setTimeout(function () {
            console.log(this.name);
        }.call(obj), 0);
    }
}
obj.fn1();//zhangsan
obj.fn2();//zhangsan

使用call方法,强制性的将setTimeout中的this,改变为obj

2.示例2
var Person = {
    uname: "liuqiao",
    age: 27
}
function fn(x, y) {
    console.log(x + "," + y);
    console.log(this);
    console.log(this.uname);
    console.log(this.age);
}
fn('lisi', 30);
//lisi,30
//Window
//undefined
//undefined
fn.call(Person, "zhangsan", 28);
//zhangsan 28
//Person{uname: "liuqiao", age: 27}
//zhangsan
//28

对比一下,当fn方法没有调用call时,this指向是window,当使用call强制改变this指向为Person之后,this指向发生了变化

3.应用场景

经常做继承

2.apply()

/*apply()方法*/
function.apply(thisObj[, argArray])
1.示例1
var name = 'liuqiao';
var obj = {
    name: 'zhangsan',
    fn1: function () {
        console.log(this.name);
    },
    fn2: function () {
        setTimeout(function () {
            console.log(this.name);
        }.apply(obj), 0);
    }
}
obj.fn1();//zhangsan
obj.fn2();//zhangsan

使用apply()方法,强制性的将setTimeout中的this,改变为obj

2.示例2
var Person = {
    uname: "liuqiao",
    age: 27
}
function fn(x, y) {
    console.log(x + "," + y);
    console.log(this);
    console.log(this.uname);
    console.log(this.age);
}
fn('lisi', 30);
//lisi,30
//Window
//undefined
//undefined
fn.apply(Person, ["zhangsan", 28]);
//zhangsan 28
//Person{uname: "liuqiao", age: 27}
//zhangsan
//28

call()方法一样,apply()方法同样具备改变this指向的功能,但是与call()不同的是,apply()方法的第二个参数是一个数组,数组里面可以是多项成员,而call()方法的第二个参数以后可以是多个

3.应用场景

经常跟数组有关系

3.bind()

/*bind()方法*/
function.bind(thisArg[, arg1[, arg2[, ...]]])

bind()方法,同样也能改变this的指向,与call()apply()不同的是,bind()不会立即执行,相当于是预设置,把this的指向预设置了

1.示例1
var Person = {
    uname: "liuqiao",
    age: 27
}
function fn(x, y) {
    console.log(x + "," + y);
    console.log(this);
    console.log(this.uname);
    console.log(this.age);
}
fn('lisi', 30);
//lisi,30
//Window
//undefined
//undefined
var foo=fn.bind(Person, "zhangsan", 28);
foo();
//zhangsan 28
//Person{uname: "liuqiao", age: 27}
//zhangsan
//28
2.示例2
 {/*
  2.自定义函数写法 
   会存在this指向问题,需要使用bind强制改变this指向
   注意,必须使用bind才可以,call和 apply不行
   原因是call和apply改变this指向后,会立即执行函数
*/}
<button onClick={this.submitLogin.bind(this)}>提交(自定义函数写法)</button>

在React中.我们绑定事件的时候,就会有this指向的问题,但是又不想立即执行这个监听事件的方法,所以使用bind()最合适不过了.

3.应用场景

不调用函数,但是还想改变this指向

4.apply()和call()和bind()区别

1.相同点

作用都是能够强制改变this的指向,并且第一个参数,都是新的this对象,官方点说就是:将一个函数的对象上下文从初始的上下文改变为由thisObj指定的新对象

2.不同点

(1) call()可以接受多个参数,第一个参数与另外两个(apply(),bind()一样),后面则是一串参数列表

(2) apply()最多只能接受两个参数,第一个参数与另外两个(call(),bind()一样),后面则是一个数组argArray,如果给该方法传递多个参数,则把参数都写在数组里,即使只有一个参数,也要写进数组里.

(3) bind()可以接受多参数,第一个参数与另外两个(apply(),call()一样),后面则是一串参数列表

(4) call()和apply()是会立即执行的函数,而bind()是预设值,不会立即执行

(5) bind()会返回一个新的函数,称为绑定函数

(6) apply和call是一次性传入参数,而bind可以分为多次传入。

(7) 应用场景:call()经常用作继承,apply()经常跟数组有关系,bind()不调用函数,但还是想改变this指向

你可能感兴趣的:(JavaScript)