JavaScript中的this其实是一个很好理解的概念,只不过JavaScript对初学者隐藏了一些细节,促使初学者在理解this的时候对概念会特别模糊,这里来解释一下JavaScript中的this到底是什么东西。
函数调用中的this
不知道你有没有发现,在把对象和函数联系起来用的时候,会出现下面这种情况:
let obj = { name: 'yanggb', age: 18, hello: function(){ console.log(`你好,我叫${this.name},我今年${this.age}岁了`) } } let sayHi = obj.hello sayHi() // 你好,我叫,我今年undefined岁了 obj.hello() // 你好,我叫yanggb,我今年18岁了
sayHi函数和obj.hello函数都是相同的打印逻辑(sayHi也是obj.hello赋值给它的),但是打印的结果却不一样,这就是this的妙用了,因为两者的this不是同一个this,所以才会打印出不同的结果。
要知道它们的this指向,可以在函数调用的时候用call或者apply。
// 沿用上面的代码 sayHi.call(undefined) // 你好,我叫,我今年undefined岁了 obj.hello.call(obj) // 你好,我叫yanggb,我今年18岁了
可以看到,加上call后并不影响结果。事实上,在函数调用的后面加call才是最完全的写法,不加call的函数调用其实是JavaScript的一个语法糖。call的第一个参数就是这个函数所指向的this,在这里有个公式:
对象.函数名.call(对象, arguments)
谁引用了函数,谁就代表this,但如果像sayHi没有对象引用就直接调用的,它默认的this为null或undefined,那么浏览器就会自动把它的this变成window对象(window对象是浏览器内置的全局对象,因为函数是在全局上下文中执行的,this也就被替换成了window全局对象,而window全局对象的name属性是空字符串,window全局对象没有age属性,也就产生了相应的打印结果)。
[]语法中的this
var length = 10 // 用var把length属性挂载到window全局对象上作为全局属性 function fn() { console.log(this.length) } let obj = { length: 5, long: function(fn) { fn() arguments[0]() } } obj.long(fn, 1)
先别想这段代码是什么逻辑,我们先把所有的函数调用都加上call。
// 沿用上面代码 fn.call(undefined) // 打印出10 obj.long.call(obj,fn,1)
在fn和obj.long的调用加上call让结果一目了然,然后我们再给argument[0]调用加上call,直接套公式虽然不符合规范,但可以让我们更好地理解。
arguments[0].call(arguments) // 打印出 2
因为在这里的arguments[0]就是fn,this被替换为arguments对象(使用[]语法的对象会作为函数调用方),而arguments的长度是2,所以this.length的结果为2,是不是没想到还能这么用?this实在是太灵活了,其取值决定于函数被谁引用!
箭头函数
新出的ES6语法里的箭头函数没有this,所以在你需要用到this的时候最好使用ES5的函数语法。
而正是因为箭头函数里没有this,所以它才会继承上一层函数的this。这一点要格外注意,它与上一层函数之间并不存在谁指向谁。
总结
当你调用一个函数的时候,最好是call一下这个函数,它的第一个参数就代表this,这样有助于你理解代码。具体就是当你遇到一个函数而不清楚它的this到底指向谁的时候,你可以套用公式【对象.函数名.call(对象,arguments)】来帮助理解。
除了call以外还有apply,和call的功能以及使用方法基本一致,区别只在于call方法接受的是参数列表,而apply方法接受的是一个参数数组。
"因为我知道的比你多,我比你更懂事,所以日子让我比你更难过。"