《JavaScript》彻底理解this指向
简介
在学习编程的过程中,this这个关键字是无论如何都避免不了的一个坎,如果还没有使用到this,那么可以暂时忽略这篇文章中的内容,标个记号,等以后回过头来再来了解;
关键字this
(本文仅在非严格模式下进行讨论)
this是一个JS的保留关键字,这个单词在JS中有特殊的含义,其意义代表:当前执行代码的环境对象,并且有一个默认值,默认值指向的是window,如果有新指向那么,新指向的优先级将高于默认指向;
既然是代表当前执行代码的环境,那么换个说法是不是可以理解成很多网上以及书籍上的说法,this的指向在定义的时候确定不了,只有在使用的时候才能确定;因为定义且尚未使用的时候,你无法确定当前执行代码是处于什么环境中;
数据结构
在了解this之前,再明确一点,对象,函数这些的数据类型都是****引用类型,新建的时候会在内存中开辟一个位置存储这些引用类型的值,之后,赋值给a,赋值给b这些操作,都只是将内存中的地址给赋值过去,因此,修改一个的时候才会出现所有引用这个地址的值同步发生了改变;
let obj = {a:1} //在内存中生成了一个对象{a:1},并且将这个对象的地址赋值给了obj
let obj2 = obj; //相当于obj做了个中介,让obj2这个变量指向的地址是内存中的对象{a:1}
理解及示例
普通函数中的this
function demo(){
let name = "你好";
console.log(this.name); //undefined
console.log(this); //Window
}
demo();
小结:函数普通调用中的this,在没有外界干扰的情况下(也就是call,apply等),默认指向全局对象window
对象方法中的this
var obj = {
name: '你好',
func(){
console.log(this.name) //你好
console.log(this) //{name:'你好',func:f}
}
}
obj.func()
小结:当函数作为对象里的方法被调用时,this默认指向的是调用该函数的对象,示例中,是obj这个对象调用了函数func(),因此指向了obj;
为了进一步确认,对象obj再多层嵌套对象
var obj = {
name: '你好',
func(){
console.log(this.name) //你好
console.log(this) //{name:'你好',func:f}
},
b:{
name:'大家好',
func(){
console.log(this.name) //大家好
console.log(this) //{name:'大家好',func:f}
}
}
}
obj.func()
obj.b.func()
按照上例的结论,this指向的是调用该函数的对象,因此,第一个是obj调用的func()函数,因此this指向的是obj,第二个是obj.b调用的func()函数,因此this指向的是obj.b,实验符合结论,因此小结成立;
小结
- 函数普通调用中的this,在没有外界干扰的情况下(也就是call,apply等),默认指向全局对象window;
- 当函数作为对象里的方法被调用时,this默认指向的是调用该函数的对象;
为了验证这个结论,在做一些更复杂的实验:
其他示例
经典案例-对象上的方法赋值给了变量
var obj = {
name: '你好',
func(){
console.log(this.name) //undefined
console.log(this) //window
}
}
var obj2 = obj.func;
obj2()
将对象中的函数赋值给一个变量,然后运行,发现其this的值是window,而不是obj?
个人理解:其实是相同的,前文“数据结构”这一篇幅内说了,对象和函数是存在内存中的,变量只是引用了他们的地址,函数名obj.func引用的是存在内存中的函数地址,之后通过赋值操作,将obj.func引用的地址复制给了obj2,结果就是变成变量obj2直接和函数对应了起来,最终执行的时候其实和obj.func没关系,就是普通调用了函数obj2
这个和上面得到的结论不冲突,最终调用的时候就是一个普通调用的函数,这对象方法没有关系,那么this指向的就是window;
经典案例-对象上的方法内包含普通函数
var obj = {
name: '你好',
func(){
function demo(){
console.log(this.name) //你好
console.log(this) //{name:'你好',func:f}
}
demo()
}
}
obj.func()
这里面的函数执行的时候也没有指向对象obj,这中该怎么理解?
个人理解是:还是看函数最终的调用方式,在函数demo调用的时候,demo是作为对象上的方法被调用的吗,显然不是,只是普通调用,那么其函数体内的作用域的this就是指向的全局对象window;
同理,如果函数内包含了一个自执行的函数,那么其内的this也会因为没有被谁调用,而采取默认指向window;
经典案例-匿名函数中的this
var p3 = {
sayName(){
return function() {
console.log(this) //window对象
}
}
}
p3.sayName()()
在《高程-第三版》中写道“匿名函数的执行环境具有全局性,因此其this对象通常指向window”,那么在正常情况下(没有什么骚操作改变指向),可以认为匿名函数中的this指向的就是window;
同理,如果是setTimeout()()之类的使用的时候,其匿名函数内this指向的也就是window
构造函数
根据上面的理解函数内部的this,普通调用的时候指向的是window,但是通过构造函数创建的普通调用的却指向的是当前函数,这是为什么?
先不急解答,分析一下
function Person(name,age){
this.name = name;
this.age = age;
this.sayName = function(){
console.log(this)
}
}
var p1 = new Person('oliver',18);
var p2 = Person('oliver',18);
console.log(p1)
console.log(p1.sayName())
console.log(p2)
p1和p2的区别在于p1有用到关键字new,按理来说函数Person并没有return一个值,那么打印的p1和p2都应该是undefined,但结果是
p1是一个对象,p2则是undefined,由此可见,关键字new在其中产生了某些操作,使得其有了一个返回值,并且返回值的类型是一个对象
//通过new关键字,创建了一个新对象,
Person:{
name:'oliver',
age:18,
sayName(){
console.log(this)
}
}
实际上,new执行的过程中,它会主动将其作用域内的this指向新创建的实例对象,因此,当执行到this.name,this.age等代码时相当于在执行Person.name,Person.age;
console.log(p1.sayName())
之后,新创建的实例对象上的方法sayName自然指向的就是调用sayName的Person实例对象
说到构造函数,不得不说一种特殊情况,就是上例中Person中并没有return,其return是由new提供的,那么,假如定义了一个return,会怎么样?
function Person(name,age){
this.name = name;
this.age = age;
this.sayName = function(){
console.log(this)
}
return{ }
}
var p1 = new Person('oliver',18);
console.log(p1) //{}
如果手动设置了返回对象,那么由new创建的默认对象就会被丢弃;
DOM中的事件处理函数
当函数被作为事件处理时,this指向的时触发事件的元素
// 被调用时,将关联的元素变成蓝色
function bluify(e){
console.log(this === e.currentTarget); // 总是 true
// 当 currentTarget 和 target 是同一个对象时为 true
console.log(this === e.target);
this.style.backgroundColor = '#A5D9F3';
}
// 获取文档中的所有元素的列表
var elements = document.getElementsByTagName('*');
// 将bluify作为元素的点击监听函数,当元素被点击时,就会变成蓝色
for(var i=0 ; i<elements.length ; i++){
elements[i].addEventListener('click', bluify, false);
}