JavaScript中的This指向汇总

413 阅读5分钟

关于js中this的处理情况汇总

  1. 给当前元素的某个时间行为绑定方法,当事件行为触发,方法被执行,方法中的this一般都是当前操作的元素 (排除IE6 - 8 中给予attachEvent进行的DOM2事件绑定,方法中的this是window)

    document.body.onclick = function(){
        console.log(this) // <body>...</body>
    }
    document.body.addEventListener('click', function(){
        console.log(this) // <body>...</body>
    })
    
  2. 函数执行,看函数前面是否有点.,有点.,点前面是谁this就是谁,没有点.this就是window

    • js严格模式下,没有点.this就是undefined
    • 匿名函数(自执行函数/回调函数)一般this也是window/undefined,除非有特殊处理的
    • 括号表达式中有特殊的处理
    const fn = function(){
        console.log(this)
    }
    let obj = {
        name: 'obj',
        fn: fn
    }
    fn();  // 根据第一条,没有点 this => window
    obj.fn() // 有点,点前面使obj, this => obj
    

    严格模式下

    "use strict"
     const fn = function(){
    	console.log(this)
    }
    let obj = {
        name: 'obj',
        fn: fn
    }
    fn();  // 根据严格模式下第一条,没有点 this => undefined
    obj.fn() // 有点,点前面使obj, this => obj
    

    自执行函数

    (function(){
        console.log(this)  // this => window
    })()
    

    严格模式自执行函数

    "use strict"
    (function(){
        console.log(this)  // this => undefined
    })()
    

    回调函数

    [1, 2].sort(function(a, b){
        console.log(this) // this => window
    })
    

    注意:下面的这个将会发生改变

    [1].forEach(function(item, index){
       console.log(this) // this => obj
    }, obj)
    

    说明:看到这个结果并不符合我们上面的总结,这是因为forEach内部做了特殊的处理,传递的第二个参数是为了改变回调函数中的this指向的

    括号表达式

    let obj = {
        name: 'obj',
        fn: fn
    }
    
     function fn(){
        console.log(this)
    }
    
    (obj.fn)() // this => obj
    

    说明:括号表达式中只包含一项时与不加括号是没有任何区别的

    let obj = {
        name: 'obj',
        fn: fn
    }
    function fn(){
        console.log(this)
    }
    (10, 20, obj.fn)();  // this => window
    

    说明:括号表达式中存在多项时,也只取最后一项,但是此时的this变为window。可以理解为将括号表达式中的最后一项克隆一份,然后自执行。

  3. 构造函数执行new xx,函数体中的this是当前类的实例

    function Fn(){
        this.x = 100;
    } 
    Fn()  // this => window
    let f = new Fn(); // this => f
    f.sum(); // this => f
    f.__proto__.sum(); // this => f.__proto__
    

    说明:上面的例子并不适用直接粘贴复制,只是为了说明

  4. ES6中的箭头函数(或者基于{}形成的块级上下文)里面没有this,如果代码中遇到this也不是自己的,而是它上下文中的this

     let obj = {
        name: 'obj',
        fn() {
            // this => obj
            setTimeout(function(){
                console.log(this); // this => window (回调函数)
                this.name = 'joe'; // 这里改变的window.name 
            }, 1000)
        }
      }
      obj.fn();
    

    需求:1s后执行函数,把obj.name改为'joe'

    // 方法一 将 this指向obj时进行存储
     let obj = {
        name: 'obj',
        fn() {
            // this => obj
            let that = this;
            setTimeout(function(){
                that.name = 'joe'
            }, 1000)
        }
      }
      obj.fn();
      
     // 方法二 使用箭头函数
     let obj = {
        name: 'obj',
        fn() {
            // this => obj
            setTimeout(() => {
                // this => 用的使上级上下文中的this,也就是obj
                this.name = 'joe';
            }, 1000)
        }
      }
      obj.fn();
    
  5. 我们可以基于Function.prototype上的call/apply/bind方法强制改变 函数中的this指向

    • 对箭头函数没用,因为不存在this
    // 1
    const fn = function fn(){
        console.log(this.name)
    }
    window.name = 'joe';
    let obj = {
        name: 'obj',
    }
    fn(); 	// 'joe'
    fn.call(obj)  // 'obj'
    obj.fn()  // Uncaught TypeError: obj.fn is not a function 
    
    
    // 2
    const fn = function fn(x, y){
        this.total = x + y;
        console.log(this);
        return this;
    }
    window.name = 'joe';
    let obj = {
        name: 'obj',
    }
    let res = fn.call(obj, 10, 20)  // this => obj res => obj
    

    以下的语法分析则是根据上例中的 2 说明

    分析call语法

    函数.call([context], params1, params2, ...)

    简单说明:把函数中的this指向[context],并把params1/params2...作为实参传递给函数

    详细说明

    • 首先fn基于原型链__proto__,找到Function.prototype.call方法,并把call方法执行

    • call方法中的this就是当前操作的实例 fn,传递给call方法的第一个实参是改变fn中的this指向,剩余的实参都是未来要依次传递给fn的参数信息

    • call方法执行的过程中,实现了这样的处理:把 fn[call中的this]指向[context],并且把params1/params2...作为实参传递给fn,依次来达到最后的效果

    • 最后接受fn的返回结果,作为返回值返给外部

    结论

    • 执行fn.call(10, 20)此时this则为10, 说明第一个参数传递的是什么则this指向就是什么

    • 执行fn.call()无参数时thiswindow,如果第一个参数是null或者undefined,在JavaScript非严格模式下,最后fn中的this都是window(严格模式下,不传thisundefined,传递nullundefinedthis也会改为对应的值)

    apply

    函数.call([context], [params1, params2, ...])

    callapply的唯一区别:执行函数的时候,需要传递给函数的参数信息,在最开始传递给call/apply的时候,形式不一样

    • call需要把参数一个个传递给callcall方法内部再一个个传递给函数

    • apply是需要把参数放在一个数组中传递给apply,但是apply内部也会帮我们把接受数组中的每一项一项一项的传递给函数

    bind

    call/apply在执行的时候,都会立即把要操作的函数执行,并且改变它的this指向

    bind预先处理:执行bind只是把函数中需要改变的this等信息存储起来,但是此时函数并不会被执行,执行bind会返回一个匿名函数,当后期执行匿名函数的时候,再去把之前需要执行的函数执行,并且改变this为预设的值

    此时执行fn.bind(obj, 10, 20)bind执行但是fn是不会执行的。

    let anonymous = fn.bind(obj, 10, 20);
    console.log(anonymous) 
    // ƒ fn(x, y){
    //    this.total = x + y;
    //    console.log(this);
    //    return this;
    // }
    

    说明:anonymous的值为一个函数,也就是说后期把返回的函数anonymous在执行,在里面才会把fn执行,并且按照预设的信息改变this

    需求:1s后执行fn, 并且让fn中的this变为obj,传递10,20

    // 错误答案 请思考一下原因
    
    // 1.
    setTimeout(fn, 1000); // 1000ms 后执行fn,但是this => window, x/y都是undefined
    
    // 2
    setTimeout(fn.call(obj, 10, 20), 1000)  // 因为设置定时器时,基于call方法,就已经把fn执行了,虽然this和参数都是我们想要的,但是并不是1s后执行。就是把fn的返回结果邦洞给定时器,1000 ms后执行的使返回结果 
    
    // 正确答案
    // 1. 
    setTimeout(function(){
    	fn.call(obj, 10, 20)
    }, 1000)
    
    // 2.
    setTimeout(fn.bind(obj, 10, 20), 1000) 
    

最后

biu biu biu ~ ❤️❤️❤️❤️ 点关注 🙈🙈 不迷路 点个赞哦😘