JS重点---this 指向问题

this 的指向问题一直是JavaScript 中的重点难点,下面从几个方面来帮助大家理解我们经常面临的指向难题。

this的指向在函数定义的时候是确定不了的,只有函数执行的时候才能确定this到底指向谁,实际上this的指向的是那个最后调用它的对象

在之前的文章中讲到执行上下文的时候,我们提到过,函数执行上下文在函数调用的时候才被创建出来,压入栈中等待执行。与此同时,会对函数内部的变量进行收集和分析,也在此时确定了函数内部的 this 指向,所以我们说 this 指向是在调用的时候才确定的,那我们目前面临的问题就是,如何找到谁调用的函数。

1. 普通函数中的 this

// 例子 1
function fn(){
    var content = "函数中的内容";
    console.log(this); // Window
    console.log(this.content); // undefined
}

fn()

全局环境下创建的函数,其调用对象就是Window,函数 fn 其实就是挂载在 Window 对象上的,即

window.fn()

那么也不难理解,函数 fn 中的 this 就是指向的是调用 fn 的对象---Window,而 this.content 就是要在 Window 对象上找 content 变量,对象.xxx 就是 undefined。

那么如果函数是在某个对象中呢?

// 例子 2
var obj = {
    content:"对象中的变量",
    fn:function(){
        console.log(this); // {content: '对象中的变量', fn: ƒ}
        console.log(this.content);  // 对象中的变量
    }
}
obj.fn();

从打印的结果可以看到,这次 fn 中的 this 指向了对象 obj,那么 this.content 也就指的 对象 obj 中的属性 content。

只从代码层面来看,fn 是有 obj 调用的,所以 this 指向调用它的对象,没有问题;但是我们之前说过的,全局环境下,所有的变量都是给 Window对象添加属性。那么obj.fn()也可以写成:

window.obj.fn()

这样来看,最后调用 fn 的对象还是Window,那么为什么 this 不指向Window 对象了呢?这是因为 fn 有上一级对象 obj,所以 this 就指向了它的上一级对象

再用一个稍微复杂点的例子来解释下这个问题:

// 例子 3
var obj = {
    x: 1,
    y: {
        x: 2,
        fn: function(){
            console.log(this); // {x:2,fn:f}
            console.log(this.x) // 2
        }
    }
}

obj.y.fn();

从最后一行代码的调用关系来看,fn 最终是被 obj 调用的,但是 this 却没有指向obj 对象,而是它的上一级 y 对象,总结一下就是

  1. 如果函数中有 this,但是没有被上一级对象调用,那么 this 指向Window;
  2. 如果函数中有 this,且被它的上一级对象调用了,那么 this 就指向这个上一级对象;
  3. 如果函数中有 this,这个函数中包含多个对象,尽管这个函数是被最外层的对象所调用,this 的指向也只是它上一级的对象(不管这个对象中有没有 this 要的东西)

掌握了这个口诀,下次找 this指向的时候就一定不会出错么?还有坑等着你

// 例子 4
var obj = {
    x: 1,
    y: {
        x: 2,
        fn: function () {
          console.log(this); //window
          console.log(this.x); //undefined
        },
    },
};

var re = obj.y.fn;
re();

这段代码和例子3一样,只是最后把她赋值给了一个变量 re,再调用 re,怎么 this 的指向就变成 Window 了呢?这是因为给变量 re 进行赋值的时候没有调用,最后调用的是 re,它是挂载 Window 对象下的,所以this 最后指向了 Window。

2. 构造函数中的 this

首先,来说下构造函数普通函数有什么区别呢?

相同点:

        构造函数也是一个普通的函数

不同点:

  • 调用方式不同
function Fn(){}
// 普通函数调用
Fn()

// 构造函数调用
var obj = new Fn()
  •  首字母大小写习惯不同
  • 作用不同(构造函数是用来创建实例对象)
  • 函数中的 this 指向不同(关键字 new 会将 this 指向对象 obj )

  这里引申出来一个问题,关键字 new 到底做了哪些操作? 

在创建实例对象时,主要经历了4步:

  1. 先创建一个空对象
  2. 实例对象obj 的隐式原型链与 Fn 构造函数的显式原型链指向相同
  3. 将构造函数 Fn 内部的 this指向实例对象 obj ,并对构造函数内部的值做赋值操作
  4. 根据构造函数的返回值类型确定新创建的实例对象的具体值。

由此可知,构造函数中 this 指向的就是新创建的这个对象 obj,但是由于函数内部的返回值不同,导致对象 obj 的内容也不同。

上面说了「构造函数的返回值类型确定新创建的实例对象的具体值」,那么它是怎么具体确定的呢?

  1. 若构造函数没有返回值,那么就默认返回调用构造函数的代码;
  2. 若构造函数的返回值是引用类型的,那么我们新创建的实例对象就是该返回值;
  3. 若返回值存在但不为引用类型,那么返回该新创建的对象

第一种:没有返回值的情况

// 没有返回值时
function Person(name,age){
    this.perName = name;
    this.age = age;
}

var person1 = new Person("张三",18)
console.log(person1) // Person {name: '张三', age: 18}
console.log(person1.perName) // 张三

第二种:返回值是引用类型

// 返回值是{}
function Person(name, age) {
    this.perName = name;
    this.age = age;
    return {}
}
    
let person2 = new Person("李四", 12);
console.log(person2); // {}
console.log(person2.perName); // undefined


// 返回值是 function
function Person(name, age) {
    this.perName = name;
    this.age = age;
    return function(){}
}
    
let person2 = new Person("李四", 12);
console.log(person2); // f(){}
console.log(person2.perName); // undefined

第三种:返回值不是引用类型

// 返回值是number
function Person(name, age) {
    this.perName = name;
    this.age = age;
    return 123
}
    
let person3 = new Person("王五", 25);
console.log(person3); // Person {perName: '王五', age: 25}
console.log(person3.perName); // 王五

// 返回值是 undefined || null

function Person(name, age) {
    this.perName = name;
    this.age = age;
    return undefined
    // return null
}
    
let person3 = new Person("王五", 25);
console.log(person3); // Person {perName: '王五', age: 25}
console.log(person3.perName); // 王五

3. JS中的call、apply和 bind 方法(改变this 指向)

call、apply 和 bind 都是用来改变函数/方法中的 this 指向的,那么他们三个之间又有什么区别呢?接下来我们分别说下他们的用法和适用环境。

call() & apply()

这两个方法差别不大,只在参数传递上有一点区别:

第一个参数都是指向 this 要指向的对象,而第二个参数call接受的参数是单个单个传递的,apply接受的参数则需要一个数组

Function.call(obj, param1, param2,....)

Function.apply(obj, [param1, param2,...])

var a = {
    user:"法外狂徒",
    fn:function(e,ee){
        console.log(this.user); //法外狂徒
        console.log(e+ee); //3
    }
}
var b = a.fn;
b.call(a,1,2);


var a = {
    user:"法外狂徒",
    fn:function(e,ee){
        console.log(this.user); //法外狂徒
        console.log(e+ee); //30
    }
}
var b = a.fn;
b.apply(a,[10,20]);

bind()

bind方法,参数传递第一个是this要指向的对象,接着就是方法所需要的参数了,bind方法并不会立即调用改变指向的方法,而是把方法复制一份,并且改变了this指向,然后把它返回,所以我们还需要调用一下。

var a = {
    user:"法外狂徒",
    fn:function(e,d,f){
        console.log(this.user); // 法外狂徒
        console.log(e,d,f); // 10 1 2
    }
}
var b = a.fn;
var c = b.bind(a,10);
// bind也可以有多个参数,并且参数可以执行的时候再次添加;且参数是按照形参的顺序进行的
c(1,2); 

注意:

如果这三个方法没有第一个参数,或者参数为null、 undefined 或者 this,则等同于指向全局对象。

4. 箭头函数中的 this

箭头函数不会创建自己的 this,它只会从自己的作用域链的上一层继承 this

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值