探索JS中this的最终指向

js 中的this 指向 一直是前端开发人员的一个痛点难点,项目中有很多bug往往是因为this指向不明确(this指向在函数定义时无法确定,只有在函数被调用时,才确定该this的指向为最终调用它的对象)而错误引起的,接下来就根据两个简单案例来深刻认识哈

【注】本案例使用vue 搭建的项目进行测试

一、创建 replaceContextInClass.js

先创建一个vue项目,在components下新增一个util目录在里面新建一个class


	class ReplaceContextInClass {
	  constructor(name) {
	    this.name = name
	    window.golableGetName = this.getName
	    window.golableGetName2 = this.getName
	    window.golableGetName2 = window.golableGetName2.call(this)
	  }
	  getName() {
	    return this.name
	  }
	}
	
	export default ReplaceContextInClass

二、再建个understanderContextInES5.js

再新增一个understanderContextInES5.js


	window.pName = 'George'
	var objDeclare = {
	  pName: 'Jeffery',
	  fun: function () {
	    var pName = 'Mars'
	    /**
	     * 此处没有使用this关键字,所以就近原则此处的pName就是 Mars
	     */
	    console.log(pName) // Mars
	    /**
	     * 在understandThisScope.vue使用understandCEs5.fun()调用的fun方法
	     * 所以this为objDeclare他的pName值当然为 Jeffery了
	     */
	    console.log(this.pName) // Jeffery
	
	    function innerFun() {
	      var pName = 'Spark'
	      /**
	       * 此处没有使用this关键字,所以就近原则此处的pName就是 Spark
	       */
	      console.log(pName) // Spark
	      /**
	       * 下面的innerFun()调用时由于没有使用任何对象,所以默认使用顶层对象window调用,
	       *  即可以理解为window.innerFun();故这里的this自然指向了window,
	       *  于是他的值;自然是最上面定义的George了
	       */
	      console.log(this.pName) // George
	    }
	    innerFun()
	  }
	}
	module.exports = objDeclare

三、最后加个 understandThisScope.vue

然后在components目录下新增understandThisScope.vue


	<template>
	  <div class="page-container">
	    <img src="../assets/logo.png">
	    <div>
	      <button @click="replaceThis">点击改变this</button>
	    </div>
	  </div>
	</template>
	
	<script type="text/ecmascript-6">
	  import ReplaceContextInClass from './util/replaceContextInClass'
	  const understandCEs5 = require('./util/understanderContextInES5')
	  export default {
	    name: "testClassCall",
	    methods: {
	      replaceThis() {
	        let re = new ReplaceContextInClass('Evan')
	        /**
	         * 定义一个新对象
	         * 用于替换this指向
	         */
	        let duplicateObj = {
	          name: 'Frank'
	        }
	       let res = re.getName.call(duplicateObj)
	        /**
	         * 使用call將class的this指向為duplicateObj所以值為 Frank
	         */
	        console.log(res) // Frank
	        let gRes = window.golableGetName()
	        /**
	         *  該window調用時自身并沒有name,所以為空
	         */
	        console.log(gRes) // ' '
	        let duplicateObj2 = {
	          name: 'Eric'
	        }
	        let gRes2 = window.golableGetName.call(duplicateObj2)
	        /**
	         * 使用call將class的this指向為duplicateObj2所以值為 Eric
	         */
	        console.log(gRes2) // Eric
	        let gRes3 = window.golableGetName2
	        /**
	         * 其中使用call將對象this指向了類自己,所以該值為 Evan
	         */
	        console.log(gRes3) // Evan
	        console.log('<-----------------------------------华丽分隔线-------------------------------------------->')
	        // eslint-disable-next-line
	        understandCEs5.fun() // 调用obj的fun方法
	      }
	    }
	  }
	</script>
	
	<style scoped>
	  .page-container {
	    font-family: 'Avenir', Helvetica, Arial, sans-serif;
	    -webkit-font-smoothing: antialiased;
	    -moz-osx-font-smoothing: grayscale;
	    text-align: center;
	    color: #2c3e50;
	    margin-top: 60px;
	  }
	</style>


四、修改main.js

我们修改哈main.js


	// The Vue build version to load with the `import` command
	// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
	import Vue from 'vue'
	import App from './components/understandThisScope'
	import router from './router'
	
	Vue.config.productionTip = false
	
	/* eslint-disable no-new */
	/* no-unused-vars:0 */
	 new Vue({
	  el: '#app',
	  router,
	  components: { App },
	  template: '<App/>'
	})

最后完整的项目目录是果汁的在这里插入图片描述

现在来运行起来验证哈,结果是酱紫的
在这里插入图片描述

最后看运行结果如图;这里就不做过多解释了,因为代码中有详细的解释

五、call和 bind的区别

这里还有一个值得注意的是在replaceContextInClass.js的构造中,我们使用了call来将绑定到window上的方法的this更改为当前类,但是会有一个问题,该call在类实例初始化时就会完成方法的调用(获取到getName()的返回值);这样如果后期该name发生改变那么取值就会不同步;

修改下replaceContextInClass.js


	class ReplaceContextInClass {
	  constructor(name) {
	    this.name = name
	    window.golableGetName = this.getName
	    window.golableGetName2 = this.getName
	    window.golableGetName2 = window.golableGetName2.call(this)
	    window.golableGetName3 = this.getName
	    window.golableGetName3 = window.golableGetName3.bind(this)
	  }
	  getName() {
	    return this.name
	  }
	  changeName() {
	    this.name = 'Loren'
	  }
	}
	
	export default ReplaceContextInClass


再修改下understandThisScope.vue


	<template>
	  <div class="page-container">
	    <img src="../assets/logo.png">
	    <div>
	      <button @click="replaceThis">点击改变this</button>
	    </div>
	  </div>
	</template>
	
	<script type="text/ecmascript-6">
	  import ReplaceContextInClass from './util/replaceContextInClass'
	  const understandCEs5 = require('./util/understanderContextInES5')
	  export default {
	    name: "testClassCall",
	    methods: {
	      replaceThis() {
	        let re = new ReplaceContextInClass('Evan')
	        re.changeName() // 调用change修改name的值
	        let gRes3 = window.golableGetName2
	        let gRes4 = window.golableGetName3()
	        /**
	         * 而的他使用call將對象this指向了類自己
	         * 并且在定义定义时就会调用getName()方法获取到name
	         * 将其保存到golableGetName2中,所以到再次
	         * re.changeName()修改name时它将不再发生改变
	         */
	        console.log(gRes3) // Evan
	        /**
	         * 这个只是使用bind修改this指向
	         * 但并未立即调用 getName()
	         * 所以在 re.changeName()修改name
	         * 这里会获取到最新的Loren
	         */
	        console.log(gRes4) // Loren
	      }
	    }
	  }
	</script>
	
	<style scoped>
	  .page-container {
	    font-family: 'Avenir', Helvetica, Arial, sans-serif;
	    -webkit-font-smoothing: antialiased;
	    -moz-osx-font-smoothing: grayscale;
	    text-align: center;
	    color: #2c3e50;
	    margin-top: 60px;
	  }
	</style>

最后运行在这里插入图片描述

会发现call与bind修改的方法获取的值大不一样

原文地址:https://www.cnblogs.com/dengxiaoning/p/12309583.html