This

this的重要性

this是JavaScript中最为关键的语法点,一般在简单项目里,this可有可无,但大部分的开发任务中,this起着关键作用。

this的由来

以下面JS代码为例,调用该自身对象的其它属性的值。

1
2
3
4
5
6
7
var obj = { 
name: 'allen',
say: function(){
console.log('你好,'+obj.name)
}
}
obj.say() // 'allen'

假如我们要在控制台打印出你好,allen,通过上面的JS代码虽然用变量获取可以实现,但通过写obj.name,这样严重依赖了变量名,如果变量名obj变了,那么里面用到变量名的地方都得改变,上面代码的写法显然不可取。
为了不依赖变量名,可以给obj.say()传入一个参数,

1
2
3
4
5
6
7
var obj = {
name: 'allen',
say: function(person,问候语){
console.log(问候语+','+person.name)
}
}
obj.say(obj,'早') // '早,allen'

这样的写法算是比较友好的,没有依赖变量名。但是得传入两个参数,那能不能只传一个参数?
如果只传一个参数便是这种形式obj.say('早'),但obj.say(obj,'早')obj.say('早')这二者很难区分,所以JS之父布兰登·艾克把第一个参数隐藏起来,但隐藏起来之后,怎么知道person.name是什么,于是就引入了this作为它的第一个参数。那么上面的代码就变成了这样,

1
2
3
4
5
6
7
var 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
2
3
4
5
6
7
8
var obj = {
foo: function(){
console.log(this)
}
}
var bar = obj.foo
obj.foo()
bar()

上面代码里,调用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
4
var fn = function(p1){
console.log(this)
console.log(p1)
}

这是你无法确认this和p1是什么,因为他们只是参数。当你调用之后,便可以指定参数了。

this的确定

既然我们知道,函数传的第一参数就是this,那我们该如何确定它?

  1. 通过console.log(this)
    这是最简单的方法,但是缺乏说服力。
  2. 读DOM或jQuery的源代码
    水平不够,读不懂
  3. 查对应API的文档
    这是最可靠的方法,也是学院派的做法。
    1
    2
    3
    4
    5
    6
    7
    html:
    <button id="xxx">点我</button>
    js:
    xxx.addEventListener('click',function(){
    console.log(this)
    })
    点击'点我'

通过查EventTarger.addEventListener文档,可以知道this值是触发事件元素的引用。所以,this === xxx,即等于button元素。

改变this的值

1
2
3
4
5
var obj = {
fn : function(){
console.log(this)}
}
setTimeout(obj.fn,1000) //window对象

当向setTimeout()传递一个函数时,该函数的this会指向一个错误的值,在非严格模式下,this是window对象,而在严格模式下,this是undefined。那我们该如何让this为obj这个对象呢?有三种方法。

  1. 用包装函数
    setTimeout(function(){obj.fn()},1000),这样强制指定this为obj
  2. bind
    setTimeout(obj.fn.bind(obj),1000),obj.fn.bind(obj)会返回一个新的函数,在形式上等于function(){obj.fn()},可以理解为在obj.fn外套一层,重新写个call覆盖原来的this。

    this的高级用法

  3. 用别人的方法调用自己
    1
    2
    3
    4
    5
    6
    var 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
9
var 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
4
var 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]

  1. 打出想要的this
    1
    2
    3
    4
    5
    6
    7
    8
    9
    html:
    <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
    8
    var 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
    7
    var 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
    6
    var obj = {
    name: 'allen',
    say: function(){
    xxx.onclick = ( ) => {console.log(this.name)}
    }
    obj.say() // 'allen'

箭头函数没有隐藏的this,它自身并不会传入this,所以箭头函数里的this不是被点击的元素,而是它外面的this。

this的特殊用法

在new关键字调用构造函数时,构造函数里的this表示的是一个实例对象。

1
2
3
4
function Arr(id){
this.id = id
}
var s = new Arr(1)

此时这个this就是对象`s’。