探索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修改的方法获取的值大不一样