不得不承认JS中存在一些“历史的遗产”,比如基于全局变量的编程模型。
如果写一个函数,如:
function test(name){
var namex = name
console.log(this.namex)
}
test('jack')
方法调用之后,this.namex=undefined
,为何呢?因为此处的this是全局对象window
,这就令人比较头大。
不过别着急,慢慢往下看,关于this
还有很多魔幻的现实,我们来一一解锁。
1、完整的函数调用
还是上面的代码,其实他的完整调用写法是这样的:
function test(name){
var namex = name
console.log(this.namex)
}
test.call(undefined, 'jack')
test
是一个函数对象--即Function
对象,Function.prototype
有call
方法。call()
会有两个参数,第一个参数是this上下文对象,第二个参数是函数入参列表。
如果call()
传入的this
上下文是undefined
或null
,那么window
对象将成为默认的this
上下文。这也就解释了开头例子中this为啥为window的原因了
2、对象中的this
const obj = {
name: 'Jack',
greet: function() {
console.log(this.name)
}
}
obj.greet() //简写调用
obj.greet.call(obj) //完整调用
obj.greet()
中的this
无疑就是obj
对象
3、对象方法中嵌套函数的this
对2中的代码进行修改:
const obj = {
name: 'Jack',
greet: function() {
retufn function(){console.log(this.name)}
}
}
obj.greet()() //输出undefined
需要注意的是嵌套函数中的this
依然是window
,为什么呢?可以拆分来看:
var greet = obj.greet()
greet() // = greet.call(undefined)
嵌套函数被调用的时候,真实的调用者上下文是undefined
,也就是window
4、原型与this
function Clt() {
}
Clt.prototype.x = 10
Clt.prototype.test = function () {
console.log(this)
this.y = this.x + 1
}
let bean = new Clt()
bean.test();
console.log(bean.y)
test方法中输出的this是一个Clt对象。
这里需要引入一个新的概念:构造器函数。
new关键字就是构造器函数,它的作用是:
一旦函数被new来调用,就会创建一个链接到该函数的prototype属性的新对象,同时this会被绑定到那个新对象上
理解了构造器函数,我们就理解了原型与this的关系了,因为new会重新指定this上下文
5、箭头函数与this
ECMAScript6出现了箭头函数的用法,关于箭头函数中的this,需要先记住一句话:
函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。
看代码(引自阮一峰老师的教程:https://es6.ruanyifeng.com/#docs/function#%E7%AE%AD%E5%A4%B4%E5%87%BD%E6%95%B0):
function foo() {
setTimeout(() => {
console.log('id:', this.id);
}, 100);
}
var id = 21;
foo.call({ id: 42 });
// id: 42
此处的this指向了foo的this上下文,即定义时的this对象。
箭头函数与非箭头函数的this区别:
function Timer() {
this.s1 = 0;
this.s2 = 0;
// 箭头函数
setInterval(() => this.s1++, 1000);
// 普通函数
setInterval(function () {
this.s2++;
}, 1000);
}
var timer = new Timer();
setTimeout(() => console.log('s1: ', timer.s1), 3100);
setTimeout(() => console.log('s2: ', timer.s2), 3100);
// s1: 3
// s2: 0
箭头函数的this指向了定义时所在对象的this;非箭头函数的this指向了运行时的作用域,即全局域window
this的指向固定化,也算是解决了一些历史旧账,无疑带来的很大好处,比如封装调用:
var handler = {
id: '123456',
init: function() {
document.addEventListener('click',
event => this.doSomething(event.type), false);
},
doSomething: function(type) {
console.log('Handling ' + type + ' for ' + this.id);
}
};
一个嵌套的例子:
function foo() {
return () => {
return () => {
return () => {
console.log('id:', this.id);
};
};
};
}
var f = foo.call({id: 1});
var t1 = f.call({id: 2})()(); // id: 1
var t2 = f().call({id: 3})(); // id: 1
var t3 = f()().call({id: 4}); // id: 1
最里层的this也绑定到了定义时所在对象的—即foo的this
总结
ECMAScript6的箭头函数也算是解决了一些历史问题。不过正如HTML5离不开HTML4一样,因为只有保持了继承与兼容,新技术的推广才会更加的迅速。
享受语言新特性之余,也要搞清楚旧版本的一些细节,这有助于更全面的掌握知识。
希望这篇文章对您有用!