vue-class-component 中setTimeout setInterval debouce等函数内的this 指向

670 阅读3分钟

背景

  1. 在vue中做本地计时器的场景 需要用到setTimeInteval
  2. 在页面做debouce的场景, debouce的本质上是一个setTimeout

问题的具体场景

在绑定setTimeout 的时候需要绑定执行函数 有以下两种绑定方式

// 方式 1
mounted () {
   const timer = setInteval(() => {
   			this.doSth();
   	}, 1000
   )
}
// 执行结果 this 是 window
//         this.doSth 是undefined
// 方式 2
mounted () {
	const timer = setInteval(this.doSth(), 1000
    )
// 执行结果 this 是 vue 实例
//         this.doSth 是可执行的
}

由于setInterval 内的执行函数已经脱离了当前vue实例的执行环境,这个时候this是window ,window是没有doSth 方法的 如果一定要在执行函数内调起 doSth 怎么办?在方法1外部 const self = this 再在执行函数中 self.doSth()调用即可

延伸问题

抛开Vue 只说timeout 中的this

以下代码引自于 settimeout this试验 并加入了timer5

 let obj = {
    fn2() {
        console.log('我是timer5的this指向:', this)
    },
    fn() {
      console.log('我是fn内的this指向:', this) // obj

      // 示例1
      let timer1 = setTimeout(function() {
      
        console.log('我是timer1的this指向:', this)  // Window
      }, 1000)

      // 示例2 (让定时器函数中的this是obj:使用变量保存的方式)
      let _this = this
      let timer2 = setTimeout(function() {
        console.log('我是timer2的this指向:', _this) // obj
      }, 1000)

      // 示例3 (让定时器函数中的this是obj:使用bind方法改变this指针)
      let timer3 = setTimeout(
        function() {
          console.log('我是timer3的this指向:', this) // obj
        }.bind(this),
        1000
      )

      // 示例4 (让定时器函数中的this是obj:使用箭头函数,箭头函数中的this继承宿主环境(上级作用域中的this))
      let timer4 = setTimeout(() => {
        console.log('我是timer4的this指向:', this) // obj
      }, 1000)

      let timer5 = setTimeout(() => {
          this.fn2()
      }, 1000)


    }
  }
  obj.fn()

执行结果如下

为什么方式2 的doSth执行中的this指向为vue实例,而不是window?

这个问题困扰了我很久,通过阅读源码才恍然大悟 原来Vue的methods里的this是通过bind方法绑定了当前vue实例为this,可看vue源码 state.js vm[key] = typeof methods[key] !== 'function' ? noop : bind(methods[key], vm) 故而即时传入了this.doSomething,在doSomthing中this永远指向了vm 实例

这块我们可以做2个试验 试验1 在vue中主动更改method的指向并执行查看效果

  methods: {
    doSomthing () {
      // const func1 = this.doSomthing1;
      this.doSomthing1.apply(null)
      // setTimeout(func1, 1000);
 
    },
    doSomthing1 () {
      console.log('this------');
      console.log('this------', this);  
    }
  }

试验效果 我们可以看到是没有效果的,因为doSomething1的this已经被指定为了vm

试验2 在Vue中重新绑定this 使用bind 也是没有效果的

试验3 在vanilla js中重复绑定bind 是有效果的

function func1 () {
    console.log(this)
}
const obj1 = {
    a: 1,
    b: 2
}
const obj2 = {
    a: 1,
    b: 2
}

let funcBind = func1.bind(obj1)
funcBind();
funcBind = func1.bind(obj2)
funcBind();

在Vue-class-compoent中的问题

当在vue-class-component中写

debouceFunction1: Function = () => {
        console.log('debouceFunction1 this---------------',this);
        // console.log('debouceFunction1 this.$route---------------',this.$route);
        // console.log('debouceFunction1 router', this.$router);
        // this.debounceMethod();
    };
debouceFunction2: Function = function hey () {
    console.log('debouceFunction2 this---------------',this);
    console.log('debouceFunction2 this.$route---------------',this.$route);
    console.log('debouceFunction2 router', this.$router);
    // this.debounceMethod();
};

执行结果如下

debouceFunction1 this--------------- EventBanner {activeKey: "", routerPre: "", levelConst: {}, tabList: Array(3), eventId: 0}
eventBanner.vue?080d:201 debouceFunction2 this--------------- VueComponent {_uid: 17, _isVue: true, $options: {}, _renderProxy: Proxy, _self: VueComponent}
eventBanner.vue?080d:202 debouceFunction2 this.$route--------------- {name: undefined, meta: {}, path: "/em/detail/517/root", hash: "", query: {}, }
eventBanner.vue?080d:203 debouceFunction2 router VueRouter {app: Vue, apps: Array(1), options: {}, beforeHooks: Array(0), resolveHooks: Array(0)}

Function1作为匿名函数的this在定义时就被指定为了当前的class,而Function2 则是具名函数,是动态加载的时候指定 this 这里需要去理解一下vue-class-component 的原理,理解下它的装饰器是如果起作用的 解读 vue-class-component 源码实现原理

相关阅读