zoukankan      html  css  js  c++  java
  • 探索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修改的方法获取的值大不一样

  • 相关阅读:
    fileupload直接获得
    ajax分页
    jquery.cookie.js
    DataTable分页
    C#字串与Unicode互相转换方法
    Linq测试
    滚动条加载数据
    创建.PDF文件【1】
    小问题【6】
    小问题【4】
  • 原文地址:https://www.cnblogs.com/dengxiaoning/p/12309583.html
Copyright © 2011-2022 走看看