JavaScript中的this的指向问题(包括箭头函数)

前言

了解this的指向问题,是一个前端小白的必经之路。在最近老是被this的问题所困扰,就在各种博客收集总结,然后自己来写一篇,在以后忘记的过程中,我可以拿出来看看,加深自己对this的理解。

其中可能有一定的错误,但是请你们谅解一下小白的痛苦。

普通函数

理解this的关键

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

1、默认绑定规则

在全局下,this就是执行window

//现象一
console.log(this === window);   //true

//现象二  函数的独立调用
function a(){
    console.log(this === window);   //true
}
a() //  等价于 window.a()

2、隐式绑定规则

谁调用this就指向谁

这里一般是对象的形式来调用函数。

每个函数执行时,内部都有一个this的存在(可能指向相同)。

//现象一
let obj = {
    name: 'james',
    getName: function(){
        console.log(this);  //obj
        function b(){
           console.log(this);
        }
        b() // window  原因: window.b()
    }
}
obj.getName()


//现象二   立即执行函数
(function(){
    console.log(this);  //window
})()
//解释(相当于下面的函数,定义函数体之后,下步就执行)
function b(){
    console.log(this);
}
b() // 原因: window.b()


//想象三
var o = {
    a:10,
    b:{
        a:12,
        fn:function(){
            console.log(this.a); //undefined
            console.log(this); //window
        }
    }
}
var j = o.b.fn;    //这里函数并没有被执行,所以this的指向没有确定
j();  //window.j()   这里是window调用,所以this只想问window

3、显示绑定

call / apply / bind 改变this的指向

他们的第一个参数都是对象,如果不是对象,会进行相应的转化

obj.call(1)    //number
obj.call(false)    //boolean
//特殊
obj.call(null)    //window
obj.call(undefined)   //window

4、new绑定

构造函数中的this指向实例化对象

function Person(){
    this.name = 'james'
}
new Person()
//等价于
function Person(){
    let this = {}
    this.name = 'james'
    return this
}
//返回一个this的对象,当创建实例对象的时候。this就是指向他

对于new绑定,如果return的是基本数据类型,不会改变this的指向,如果返回的是引用数据类型,就会改变this的指向

function fn()  
{  
    this.user = 'james';  
    return function(){};   //返回一个 函数、对象、数组,改变this的指向,指向返回的新的对象地址
}
var a = new fn;  
console.log(a.user); //undefined


function fn()  
{  
    this.user = 'james';  
    return 1;     //如果返回的基本数据类型, 不会改变this的指向
}
var a = new fn;  
console.log(a.user); //james

回调函数中的this默认是指向window的,因为本质上是在函数内callback,并没有.前的对象调用

优先级

new绑定 > 显示绑定 > 隐式绑定 > 默认绑定

证明显示绑定优先于隐式绑定

function foo(){
    console.log(this.a);
}
let obj1 = {
    a: 2,
    foo
}
let obj2 = {
    a: 3,
    foo
}
obj1.foo()   // 2
obj2.foo()   // 3

//证明显示绑定优先于隐式绑定
obj1.foo.call(obj2)   //3

从上面代码可以知道, 当隐式绑定调用的时候,this分别指向obj1obj2,各自打印出23,对obj1.foo.call(obj2)来说, 打印出 3 ,就说明this的指向是obj2(简单理解,就是改变了this的指向),从而就说明显示绑定优先于隐式绑定

证明new绑定优先于显示绑定

function foo(a){
    this.a = a
}
var obj = {}
//使用bind函数改变this的指向为obj,(bind()返回一个新的函数)
var bar = foo.bind(obj)
bar(2)  
//执行bar(2),从而打印出obj.a = 2
console.log(obj.a);    //2

//重点: new绑定优先于显示绑定
var baz = new bar(3)
console.log(obj.a);   //2
console.log(baz.a);   //3

最后打印的解释: var bar = foo.bind(obj)是通过bind来绑定的,就是显示绑定,这时候的this应该是指向obj的, var baz = new bar(3)这是通过new绑定,来创建一个实例对象,并且传递的参数为3,所以console.log(baz.a)打印出来为3, 但这时的obj.a呢?该是多少呢? bar是通过bind()显示绑定的,其中的this指向的是obj,,, 但是执行 new bar(3), 传递的参数是3,如果this还是指向obj的话,那么打印出来 console.log(obj.a) 应该是3,但是结果显示的是2,从而就说明了其中的this发生了变化,因此打印出obj.a依旧是2 , 因为其中的this指向了baz了,就不会改变obj原来的值,这也就说了new绑定的优先级高于显示绑定

箭头函数

在ES6中的箭头函数中是没有this存在的。

箭头函数中的this指向,是父级作用域中的this,它们是相同的。(取决于父环境中的函数的this的指向,在定义时就已经确定了,跟普通的函数的this有点不一样。)

function a(){
    b = 2
    console.log(this);   //window
    var c = () => {
        console.log(this);  //window
    }
    c()
}
a()

这里的箭头函数是没有this的,从而就会从上一级找,就会找到a函数,a函数中是有this的,并且this是指向window, 从而箭头函数中的this也是指向window的。

let a = () => {
    console.log(this);   //window
}
a()
//这里this又会继续的往上找,那么就会找到window的顶级对象上去,从而是window

对于上面的四种改变this的指向,对于箭头函数来说,都是无效的,并且对于new绑定来说,还会报错,因为箭头函数不能作为构造函数。

面试题

function Foo() {
    getName = function(){
        console.log(1);
    }
}
Foo.getName = function() {
    console.log(2);
}
Foo.prototype.getName = function() {
    console.log(3);
}
var getName = function() {
    console.log(4);
}
function getName() {
    console.log(5);
}

//输出
Foo.getName()   // 2
getName()   // 4
Foo().getName()  // 1
getName()     // 1
new Foo.getName()  // 2
new Foo().getName()  // 3
new new Foo().getName()  //3

解析:

第一问: 可以看出这是构造函数的静态成员,就直接调用即可,所以打印出 2

第二问: 这是一个全局的函数调用,由于存在预编译,function 和 var都会变量提升, 当代码执行下来,getName重新变量赋值,所以打印出 4

第三问: 执行Foo(),函数体getName()没有是var定义,所以是一个全局函数,从而覆盖掉以前的getName函数,从而打印出1

第四问: 全局执行getName, 所以打印出 1

第五问: 这里开始,我们就需要理解优先级,看下面的优先级表

优先级

从图可以看出来, .的优先级高于 new(无参数列表), 所以上面的表达式可以看成 new (Foo.getName)(), 从而可以看出这是 Foo的静态成员,打印出 2

第六问: new Foo().getName() 这里 .new(带参数聊表)是同级,所以从左到右执行,先进行实例化对象,从而在原型上找到getName(),从而打印出 3

第七问: new new Foo().getName(), 这里new的优先级比较低, new (new Foo().getName() ), 从而也等于 3

参考博客:

https://www.cnblogs.com/pssp/p/5216085.html

https://www.jianshu.com/p/412ccd8c386e

posted @ 2020-10-31 11:28  coder_xyf  阅读(289)  评论(2编辑  收藏  举报