小知识,大挑战!本文正在参与“[程序员必备小知识]
在开发中你有没有感觉this指向和自己想的不一样,这里让你不在感觉this指的方向没那么的神秘,让我们一起揭开this的神秘面纱。
前文
我之前在大学的时候学习c++、java、python面向对象的语言,当我从这些面向对象的思维中转到JS十分的难以理解这个这个JS的this,在我的思维里this是指向对象本身的,但是在JS中this可以随时改变指向,下面讲讲我们思维上的误区。
误区
指向自身
先来段代码:
var name = 'window'
function sbThis() {
console.log(this.name); // window
}
sbThis.name = '我是sbThis'
sbThis()
这个就是在我们在我们的思维中认为this应该是自己的自身,但是这里的this指向window。
作用域
再来段代码:
var name = 'window'
function sbThis() {
var name = '我是sbThis'
this.sbThisNo2()
}
function sbThisNo2() {
console.log(this.name); // window
}
sbThis()
我们前面说sbThis是指向window,所以我们就可以调用了sbThisNo2函数。当调用sbThisNo2函数,在我们认知中又一种认为sbThisNo2的函数this指向sbThis,又错误了。但是,这种认知呢有时候是对的,又有时候是错误的,看这个的sbThis就是指向window,这个就是我们思维中的认知。所以我们要拿一套this的规则来学习它。
内容
开始学习内容篇章this真正的指向。记住一句话: 观察函数被使用的位置,进行一下判断。
四项法则
默认绑定
一段代码:
var name = 'window'
function sbThis() {
console.log(this.name); // window
}
sbThis.name = '我是sbThis'
sbThis()
这个就是一种默认绑定,当我们在全局调用这个函数,因此this就是指向window。
隐式绑定
看一段代码:
var name = 'window'
function sbThis() {
console.log(this.name); // 我是OBJ
}
var obj = {
name: '我是OBJ',
fn: sbThis
}
obj.fn()
这里我们是调用obj.fn。这里我们可以看是是通过obj调用的fn。下面再来一段代码:
var name = '我的window'
function sbThis() {
console.log(this.name); // 我的window
}
var obj = {
name: '我是OBJ',
fn: sbThis
}
var falseFn = obj.fn
falseFn()
这里是不是看的有点糊涂,我再看一段代码:
var name = '我的window'
function sbThis() {
console.log(this.name); // 我的window
}
var obj = {
name: '我是OBJ',
fn: sbThis
}
var falseFn = obj.fn
falseFn()
console.log(obj.fn === sbThis); // true
console.log(falseFn === sbThis); //true
这里就是相当于把sbThis的引用改成了falseFn,这里表示sbThis函数在运行。赋值引用this的指向是不会赋值过去,只是把this这个词赋值了而已。
有一点需要注意点 隐藏的赋值。
var name = '我的window'
function sbThis() {
console.log(this.name); // 我的window
}
function sbThisNo2(fn) {
fn()
}
var obj = {
name: '我是OBJ',
fn: sbThis
}
sbThisNo2(obj.fn)
函数作为参数也是一种赋值引用的操作。
硬绑定
这里指的就是call、apply、bind。强行的把this绑定上去。
这里我们经常会忘记它们的使用区别。
首先他们的第一个参数都是要绑定的this。我记忆方式是: call是打电话,所以我们需要一句一句的说,对应参数就是一个一个的传。apply是应用我想到了app,当我们填写个人信息就是把全部的信息一次性的填写上去,对应就是第二参数是一个数组。bind是绑定,跟一个人绑定起来,形成了一个新个体,第二个参数嘛,之前记住只有一个是数组其他的都是一个个传就可以了。这个是我的记忆方式,一般是不适用大家,你们大家看到这几个单词想到什么就使用这个场景进行记忆。
var name = '我的window'
function sbThis() {
console.log(this.name); // 我是OBJ * 3
}
var obj = {
name: '我是OBJ',
fn: sbThis
}
sbThis.call(obj)
sbThis.apply(obj)
sbThis.bind(obj)()
New
在JavaScript中函数就可以当作一个对象,当使用new来使用一个函数,函数就会变成构造函数,产生出一个新的对象。
function sbThis(name) {
this.name = name
console.log(this.name); // 我是OBJ
}
const obj = new sbThis('我是OBJ')
优先级
一句话记住:new > 硬绑定 > 隐式绑定 > 默认绑定 (详情看参考书籍)
规则例外
规则当中凡事都是有些例外的。你以为按照规则走的,其实是默认绑定。
被忽略的this
var name = '我是window'
function sbThis() {
console.log(this.name); // 我是window
}
sbThis.call(null)
当我们使用了硬绑定第一个参数为null或者是undefined,硬绑定失效就是变成了默认绑定。
匿名函数的this
var name = '我是window'
function sbThis() {
setTimeout(function () {
console.log(this.name); // 我是window
},1000)
}
var obj = {
name: '我是obj',
fn: sbThis
}
obj.fn()
匿名函数的this都是指向window的。
箭头函数
把上面的代码修改成箭头函数:
var name = '我是window'
function sbThis() {
setTimeout(() => {
console.log(this.name); // 我是obj
},1000)
}
var obj = {
name: '我是obj',
fn: sbThis
}
obj.fn()
因为箭头函数没有this,所以指向上级作用域的this。
实践
var className = 'window'
function Page(callBack) {
callBack('callBack') // window callBack
this.className = 'Page'
callBack('callBack') // Page callBack
this.MessageCallBack = callBack //
this.MessageCallBack('MessageCallBack') // Page MessageCallBack
}
function PageA() {
this.className = 'PageA'
this.handleMessage = function (msg) {
console.log(this.className, msg)
}
Page(this.handleMessage)
}
new PageA()
- 我们是先new了一个
PageA对象。 - 在PageA对象中使用Page(
默认绑定)。 - 传值this.handleMessage (
隐藏的赋值)赋值给callBack。 - 所以
Page 和 callBack都是指向window。
改变点代码:
var className = 'window'
function Page(callBack) {
callBack('callBack') // window callBack
this.className = 'Page'
callBack('callBack') // window callBack
this.MessageCallBack = callBack //
this.MessageCallBack('MessageCallBack') // Page MessageCallBack
}
function PageA() {
this.className = 'PageA'
this.handleMessage = function (msg) {
console.log(this.className, msg)
}
new Page(this.handleMessage)
}
new PageA()
这里把Page调用改成了new Page。
逻辑还是之前那样但是使用了new Page所以Page里面的this就是指向Page。
然而callBack(隐式赋值)还是指向window。
所以this.className = 'Page'对后面 的打印是无效的。
结论
抛开以前的其他语言和自我的认知。
遵循四项规则。
多看。
参考
《你不知道的JavaScript》