前言
this是学习JS时需要面对的三座大山之一,只有将它理解之后学习框架才不会产生疑惑,本文是我对this的一些理解和总结
假设没有this
不用this,想要通过变量获取name,应该怎么做?
let person = {
name: 'jack',
sayHi(){
console.log(`你好,我叫` + person.name)
}
}
可以用直接保存了对象地址的变量获取'name' 我们把这种办法简称为引用
但这个方法存在两个潜在的问题:
- 这个函数和变量绑定在了一起,如下
let sayHi = function(){
console.log(`你好,我叫` + ???.name)
}
let person = {
name: 'frank',
'sayHi': sayHi
}
如果是先声明了函数,再声明变量的话,我们在写这个函数的时候,怎么能确定后面的变量叫person呢?
而且这么定义的话,
- 如果变量中的person改名了,函数中的person.name也得跟着一起改名
- 如果函数和变量不在一个文件里面,就无法执行 所以我们不希望sayHi函数里面出现person引用
- 如果用类的话,问题就更大了
class Person{
constructor(name){
this.name = name
// 这里的 this 是 new 强制指定的
}
sayHi(){
console.log(???)
}
}
我们在声明类的时候,还没有生成任何一个对象,故不可能获取对象的引用,所以???是什么根本写不了
所以现在的问题是,需要一种办法拿到未来的对象,这样才能获取对象的name属性
有一种办法,用参数:
可以参考Python的解决方法
class Person:
def __init__(self, name): # 构造函数
self.name = name
def sayHi(self):
print('Hi, I am ' + self.name)
person = Person('jack')
person.sayHi()
它的特点:
- 每个函数都接收一个额外的self
- 这个self就是未来会传进来的对象
- 在执行
person.sayHi()时,其实就等价于执行person.sayHi(person) - 在执行之后,person就会被传给了self
把这种思路转移到JS中,就是
对象:
let person = {
name: 'jack',
sayHi(p){
console.log(`你好,我叫` + p.name)
}
}
person.sayHi(person)
类:
class Person{
constructor(name){ this.name = name }
sayHi(p){
console.log(`你好,我叫` + p.name)
}
}
这里的p就相当于Python中的self 但这样的方法并不美观,于是JS并没有模仿Python的办法,而是走了另一条路——this
JS在每一个函数里加了this
let person = {
name: 'jack',
sayHi(){
console.log(`你好,我叫` + this.name)
}
}
person.sayHi()
我们可以用python的方式理解:
JS在函数sayHi()的括号里面加了一个隐藏的this,用来接受未来会被传入的值,
当执行person.sayHi()时,相当于执行person.sayHi(person),
这个括号里的person(是一个地址)就被传入sayHi()里,一个隐藏的this接受了它,这样sayHi就可以通过this引用person
进一步地,可以更直接理解为:
person.sayHi()会隐式地把person作为this传给函数sayHi(),即person.sayHi()括号里本来就是一个隐藏的this,只不过JS在执行的时候,会自动把"this=person"了之后,再进行传参
通过this,再看上述的问题1
即使先声明了函数,再声明变量,最终也能将完美执行
问题2亦是如此,
但有个问题,现在我们已经习惯用这种隐式传递的调用方式来执行函数了,而根据上述,这样的调用方式并不能方便我们清晰直接地理解this
两种调用
person.sayHi()普通写法,隐式传递- 会自动把person传到函数里,作为this
person.sayHi.call(person)call写法,显示传递- 需要自己手动把person传到函数里,作为this
- 用这种方法括号里面传什么,this就是什么
尝试使用call调用:
例1
不用this的情况
function add(x,y){
return x+y
}
add.call(undefined, 1,2)
会得到3
为什么用call的时候,要多写一个undefined?
- 因为在call中,第一个参数要作为this
- 但代码中没有this
- 所以需要用undefined来占位
例2
Array.prototype.forEach2 = function(fn){
for(let i=0;i<this.length;i++){
fn(this[i], i, this)
}
}
这其实就是forEach的原代码,
我们定义一个数组let array = [1,2,3],
用call写法array.forEach2.call(array,(item)=>console.log(item))
和普通写法array.forEach2((item)=>console.log(item))调用的结果都是一样的
只不过call写法显示地指定了array是this,而普通写法隐式地指定了array是this,
且call里的this也是传什么就是什么,
所以,我们大部分情况下可以这样理解,this根本就只是一个可以任意指定的隐藏的参数而已,只不过在普通写法里我们不能指定它的值
绑定this
使用.bind可以让this不被改变
function f1(p1, p2){
console.log(this, p1, p2)
}
let f2 = f1.bind({name:'frank'})
// 那么 f2 就是 f1 绑定了 this 之后的新函数
f2() // 等价于 f1.call({name:'frank'})
.bind还以绑定其他参数
let f3 = f1.bind({name:'frank'}, 'hi')
f3() // 等价于 f1.call({name:'frank'}, hi)