this 可以说是 javascript 中最耐人寻味的一个特性,就像高中英语里各种时态,比如被动时态,过去时,现在时,过去进行时一样,无论弄错过多少次,下一次依然可能弄错。本文启发于《你不知道的JavaScript上卷》,对 javasript 中的 this 进行一个总结。
学习 this 的第一步就是明白 this 既不是指向函数自身也不指向函数的作用域。this 实际上是在函数被调用时发生的绑定,它指向什么地方完全取决于函数在哪里被调用。
默认绑定
在 javascript 中 ,最常用的函数调用类型就是独立函数调用,因此可以把这条规则看作是无法应用其他规则时的默认规则。如果在调用函数的时候,函数不带任何修饰,也就是“光秃秃”的调用,那就会应用默认绑定规则, 默认绑定的指向的是全局作用域。
function sayLocation() { console.log(this.atWhere) } var atWhere = "I am in global" sayLocation() // 默认绑定,this绑定在全局对象,输出 “I am in global”
再看一个例子
var name = "global" function person() { console.log(this.name) // (1) "global" person.name = 'inside' function sayName() { console.log(this.name) // (2) "global" 不是 "inside" } sayName() // 在person函数内部执行sayName函数,this指向的同样是全局的对象 } person()
在这个例子中,person 函数在全局作用域中被调用,因此第(1)句中的 this 就绑定在了全局对象上(在浏览器中是是window,在node中就是global),因此第(1)句自然输出的是一个全局对象的 name 属性,当然就是"global"了。sayName函数在person函数内调用,即使这样第(2)句中的this指代的仍然是全局对象,即使 person 函数设置了 name 属性。
这就是默认绑定规则,它是 javascript 中最常见的一种函数调用模式,this 的绑定规则也是四种绑定规则中最简单的一种,就是绑定在全局作用域上。
默认绑定里的严格模式
在 javascript 中,如果使用了严格模式,则 this 不能绑定到全局对象。还是以第一个例子,只不过这次加上了严格模式声明
'use strict' function sayLocation() { console.log(this.atWhere) } var atWhere = "I am in global" sayLocation() // Uncaught TypeError: Cannot read property 'atWhere' of undefined
可以看出,在严格模式下,把 this 绑定到全局对象上时,实际上绑定的是 undefined ,因此上面这段代码会报错。
隐式绑定
当函数在调用时,如果函数有所谓的“落脚点”,即有上下文对象时,隐式绑定规则会把函数中的 this 绑定到这个上下文对象。如果觉得上面这段话不够直白的话,还是来看代码。
function say() { console.log(this.name) } var obj1 = { name: "zxt", say: say } var obj2 = { name: "zxt1", say: say } obj1.say() // zxt obj2.say() // zxt1
很简单是不是。在上面这段代码中,obj1 , obj2 就是所谓的 say 函数的落脚点,专业一点的说法就是上下文对象,当给函数指定了这个上下文对象时,函数内部的this 自然指向了这个上下文对象。这也是很常见的一种函数调用模式。
隐式绑定时丢失上下文
function say() { console.log(this.name) } var name = "global" var obj = { name: "inside", say: say } var alias = obj.say // 设置一个简写 (1) alias() // 函数调用 输出"global" (2)
可以看到这里输出的是 ”global“ ,为什么就和上例中不一样,我们明明只是给 obj.say 换了个名字而已?
首先我们来看上面第(1)句代码,由于在 javascript 中,函数是对象,对象之间是引用传递,而不是值传递。因此,第(1)句代码只是 alias = obj.say = say ,也就是 alias = say ,obj.say 只是起了一个桥梁的作用,alias 最终引用的是 say 函数的地址,而与 obj 这个对象无关了。这就是所谓的”丢失上下文“。最终执行 alias 函数,只不过简单的执行了say函数,输出"global"。
显式绑定
显式绑定,顾名思义,显示地将this绑定到一个上下文,javascript中,提供了三种显式绑定的方法,apply,call,bind。apply和call的用法基本相似,它们之间的区别是:
apply(obj,[arg1,arg2,arg3,...] 被调用函数的参数以数组的形式给出
call(obj,arg1,arg2,arg3,...) 被调用函数的参数依次给出
而bind函数执行后,返回的是一个新函数。下面以代码说明。
// 不带参数 function speak() { console.log(this.name) } var name = "global" var obj1 = { name: 'obj1' } var obj2 = { name: 'obj2' } speak() // global 等价于speak.call(window) speak.call(window) speak.call(obj1) // obj1 speak.call(obj2) // obj2