zoukankan      html  css  js  c++  java
  • 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

  • 相关阅读:
    webpack+babel+transform-runtime, IE下提示Promise未定义?
    《Create Your own PHP Framework》笔记
    Windows,Mac与Linux哪个更适合开发者?
    微信公众号开发——通过ffmpeg解决amr文件无法播放问题
    Paypal如何实现循环扣款(订阅)?
    软件随想——为什么你需要提高软件的技术水平?
    react-native-image-picker在IOS上总是返回”Can’t find variable:response”的错误?
    解决angular-deckgrid高度不均衡和重加载的问题
    Linux多台服务器间SSH免密码登录配置
    关于macOS Sierra无法使用gdb进行调试的解决方案
  • 原文地址:https://www.cnblogs.com/xyf724/p/13905613.html
Copyright © 2011-2022 走看看