this的重要性
this是JavaScript中最为关键的语法点,一般在简单项目里,this可有可无,但大部分的开发任务中,this起着关键作用。
this的由来
以下面JS代码为例,调用该自身对象的其它属性的值。1
2
3
4
5
6
7var obj = {
name: 'allen',
say: function(){
console.log('你好,'+obj.name)
}
}
obj.say() // 'allen'
假如我们要在控制台打印出你好,allen,通过上面的JS代码虽然用变量获取可以实现,但通过写obj.name,这样严重依赖了变量名,如果变量名obj变了,那么里面用到变量名的地方都得改变,上面代码的写法显然不可取。
为了不依赖变量名,可以给obj.say()传入一个参数,
1 | var obj = { |
这样的写法算是比较友好的,没有依赖变量名。但是得传入两个参数,那能不能只传一个参数?
如果只传一个参数便是这种形式obj.say('早'),但obj.say(obj,'早')和obj.say('早')这二者很难区分,所以JS之父布兰登·艾克把第一个参数隐藏起来,但隐藏起来之后,怎么知道person.name是什么,于是就引入了this作为它的第一个参数。那么上面的代码就变成了这样,1
2
3
4
5
6
7var obj = {
name: 'allen',
say: function(问候语){
console.log(问候语+','+this.name)
}
}
obj.say('早') // '早,allen'
其中obj,say('早')形式上等价于obj.say.call(obj,'早'),前者是一种语法糖的形式,后面这种才是计算机真正认为的,浏览器认为obj === this,所以我们可以认为this就是函数call的第一个参数。
this的概念
this的数据类型
1 | var obj = { |
上面代码里,调用obj.foo()打印出来的是obj这个对象自身,因为obj.foo()形式上等于obj.foo.call(obj),而call的第一个参数就是this,所以我们知道了this就是一个对象,即使你指定的this是数字1,它也会变为一种对象的形式。
在调用bar()时,并没有指定this,形式上相当于bar.call(undefined),这时浏览器会默认this等于window对象。但在nodejs里,this === global,global是个全局对象。
不要以为bar = obj.foo,就认为bar() === obj.foo(),函数是独立存在的,不会进行补全。
this这个参数
当函数未调用时,this只是一个参数,无法确认this是什么。1
2
3
4var fn = function(p1){
console.log(this)
console.log(p1)
}
这是你无法确认this和p1是什么,因为他们只是参数。当你调用之后,便可以指定参数了。
this的确定
既然我们知道,函数传的第一参数就是this,那我们该如何确定它?
- 通过console.log(this)
这是最简单的方法,但是缺乏说服力。 - 读DOM或jQuery的源代码
水平不够,读不懂 - 查对应API的文档
这是最可靠的方法,也是学院派的做法。1
2
3
4
5
6
7html:
<button id="xxx">点我</button>
js:
xxx.addEventListener('click',function(){
console.log(this)
})
点击'点我'
通过查EventTarger.addEventListener文档,可以知道this值是触发事件元素的引用。所以,this === xxx,即等于button元素。
改变this的值
1 | var obj = { |
当向setTimeout()传递一个函数时,该函数的this会指向一个错误的值,在非严格模式下,this是window对象,而在严格模式下,this是undefined。那我们该如何让this为obj这个对象呢?有三种方法。
- 用包装函数
setTimeout(function(){obj.fn()},1000),这样强制指定this为obj - bind
setTimeout(obj.fn.bind(obj),1000),obj.fn.bind(obj)会返回一个新的函数,在形式上等于function(){obj.fn()},可以理解为在obj.fn外套一层,重新写个call覆盖原来的this。this的高级用法
- 用别人的方法调用自己
1
2
3
4
5
6var a = [1,2,3]
var b = a.join()
var c = [4,5,6]
var d = a.join.call(c)
console.log(b) // '1,2,3'
console.log(d) // '4,5,6'
array.join()是个数组的API,是将数组所有元素连接到字符串中,默认以逗号隔开。那么a.join(),里面的join是怎么获取到数组a的,它的原理是什么?我们可以做出以下猜想。1
2
3
4
5
6
7
8
9var a = [1,2,3]
var a.join = function(){
var result = ''
for(var i=0;i<this.length;i++){
if(i !== this.lenght -1){ //最后一个不用加逗号
result += this[i] + ','
}else{
result +=this[i] }
}
我们做出猜想,join函数里有用了this,而这个this等于数组a。如果把this的传入改为数组c,那么join函数会把数组c变成字符串d。也就是说数组c调用了数组a的方法。
如果c不是一个数组,而是一个伪数组,同样可以用a的方法。a的slice方法操作了this,把c当作this给a的slice操作就行了。1
2
3
4var a = [1,2,3]
var c = {0:4,1:5,2:6,length:3}
var e = a.slice.call(c,0,2)
console.log(e) // [4,5]
webkit可以将object重写,调用Object.value()这个API,将对象c的value提取出来变成[4,5,6]
- 打出想要的this
1
2
3
4
5
6
7
8
9html:
<button id=xxx name="curry"
var obj = {
name: 'allen',
say: function(){
xxx.onclick = function(){console.log(this.name)}
}
}
obj.say() //xxx.name
此时this是xxx这个按钮,我们想打出’allen’,怎么办?
古老方法
将函数外的this赋值给一个变量,然后把函数里的this改成那个变量就行了。1
2
3
4
5
6
7
8var obj = {
name: 'allen',
var _this = this
say: function(){
xxx.onclick = function(){console.log(_this.name)}
}
}
obj.say() // 'allen'bind
1
2
3
4
5
6
7var obj = {
name: 'allen',
say: function(){
xxx.onclick = function(){console.log(_this.name)}
}.bind(this)
}
obj.say() // 'allen'
此时,bind传入的this是函数外的this,可以理解为bind()是最里面的this传入,当然以它传入为准。
- 箭头函数
1
2
3
4
5
6var obj = {
name: 'allen',
say: function(){
xxx.onclick = ( ) => {console.log(this.name)}
}
obj.say() // 'allen'
箭头函数没有隐藏的this,它自身并不会传入this,所以箭头函数里的this不是被点击的元素,而是它外面的this。
this的特殊用法
在new关键字调用构造函数时,构造函数里的this表示的是一个实例对象。1
2
3
4function Arr(id){
this.id = id
}
var s = new Arr(1)
此时这个this就是对象`s’。