this 的指向问题一直是JavaScript 中的重点难点,下面从几个方面来帮助大家理解我们经常面临的指向难题。
this的指向在函数定义的时候是确定不了的,只有函数执行的时候才能确定this到底指向谁,实际上this的指向的是那个最后调用它的对象
在之前的文章中讲到执行上下文的时候,我们提到过,函数执行上下文在函数调用的时候才被创建出来,压入栈中等待执行。与此同时,会对函数内部的变量进行收集和分析,也在此时确定了函数内部的 this 指向,所以我们说 this 指向是在调用的时候才确定的,那我们目前面临的问题就是,如何找到谁调用的函数。
1. 普通函数中的 this
// 例子 1
function fn(){
var content = "函数中的内容";
console.log(this); // Window
console.log(this.content); // undefined
}
fn()
全局环境下创建的函数,其调用对象就是Window,函数 fn 其实就是挂载在 Window 对象上的,即
window.fn()
那么也不难理解,函数 fn 中的 this 就是指向的是调用 fn 的对象---Window,而 this.content 就是要在 Window 对象上找 content 变量,对象.xxx 就是 undefined。
那么如果函数是在某个对象中呢?
// 例子 2
var obj = {
content:"对象中的变量",
fn:function(){
console.log(this); // {content: '对象中的变量', fn: ƒ}
console.log(this.content); // 对象中的变量
}
}
obj.fn();
从打印的结果可以看到,这次 fn 中的 this 指向了对象 obj,那么 this.content 也就指的 对象 obj 中的属性 content。
只从代码层面来看,fn 是有 obj 调用的,所以 this 指向调用它的对象,没有问题;但是我们之前说过的,全局环境下,所有的变量都是给 Window对象添加属性。那么obj.fn()也可以写成:
window.obj.fn()
这样来看,最后调用 fn 的对象还是Window,那么为什么 this 不指向Window 对象了呢?这是因为 fn 有上一级对象 obj,所以 this 就指向了它的上一级对象
再用一个稍微复杂点的例子来解释下这个问题:
// 例子 3
var obj = {
x: 1,
y: {
x: 2,
fn: function(){
console.log(this); // {x:2,fn:f}
console.log(this.x) // 2
}
}
}
obj.y.fn();
从最后一行代码的调用关系来看,fn 最终是被 obj 调用的,但是 this 却没有指向obj 对象,而是它的上一级 y 对象,总结一下就是:
- 如果函数中有 this,但是没有被上一级对象调用,那么 this 指向Window;
- 如果函数中有 this,且被它的上一级对象调用了,那么 this 就指向这个上一级对象;
- 如果函数中有 this,这个函数中包含多个对象,尽管这个函数是被最外层的对象所调用,this 的指向也只是它上一级的对象(不管这个对象中有没有 this 要的东西)
掌握了这个口诀,下次找 this指向的时候就一定不会出错么?还有坑等着你
// 例子 4
var obj = {
x: 1,
y: {
x: 2,
fn: function () {
console.log(this); //window
console.log(this.x); //undefined
},
},
};
var re = obj.y.fn;
re();
这段代码和例子3一样,只是最后把她赋值给了一个变量 re,再调用 re,怎么 this 的指向就变成 Window 了呢?这是因为给变量 re 进行赋值的时候没有调用,最后调用的是 re,它是挂载 Window 对象下的,所以this 最后指向了 Window。
2. 构造函数中的 this
首先,来说下构造函数和普通函数有什么区别呢?
相同点:
构造函数也是一个普通的函数
不同点:
- 调用方式不同
function Fn(){}
// 普通函数调用
Fn()
// 构造函数调用
var obj = new Fn()
- 首字母大小写习惯不同
- 作用不同(构造函数是用来创建实例对象)
- 函数中的 this 指向不同(关键字 new 会将 this 指向对象 obj )
这里引申出来一个问题,关键字 new 到底做了哪些操作?
在创建实例对象时,主要经历了4步:
- 先创建一个空对象
- 实例对象obj 的隐式原型链与 Fn 构造函数的显式原型链指向相同
- 将构造函数 Fn 内部的 this指向实例对象 obj ,并对构造函数内部的值做赋值操作
- 根据构造函数的返回值类型确定新创建的实例对象的具体值。
由此可知,构造函数中 this 指向的就是新创建的这个对象 obj,但是由于函数内部的返回值不同,导致对象 obj 的内容也不同。
上面说了「构造函数的返回值类型确定新创建的实例对象的具体值」,那么它是怎么具体确定的呢?
- 若构造函数没有返回值,那么就默认返回调用构造函数的代码;
- 若构造函数的返回值是引用类型的,那么我们新创建的实例对象就是该返回值;
- 若返回值存在但不为引用类型,那么返回该新创建的对象
第一种:没有返回值的情况
// 没有返回值时
function Person(name,age){
this.perName = name;
this.age = age;
}
var person1 = new Person("张三",18)
console.log(person1) // Person {name: '张三', age: 18}
console.log(person1.perName) // 张三
第二种:返回值是引用类型
// 返回值是{}
function Person(name, age) {
this.perName = name;
this.age = age;
return {}
}
let person2 = new Person("李四", 12);
console.log(person2); // {}
console.log(person2.perName); // undefined
// 返回值是 function
function Person(name, age) {
this.perName = name;
this.age = age;
return function(){}
}
let person2 = new Person("李四", 12);
console.log(person2); // f(){}
console.log(person2.perName); // undefined
第三种:返回值不是引用类型
// 返回值是number
function Person(name, age) {
this.perName = name;
this.age = age;
return 123
}
let person3 = new Person("王五", 25);
console.log(person3); // Person {perName: '王五', age: 25}
console.log(person3.perName); // 王五
// 返回值是 undefined || null
function Person(name, age) {
this.perName = name;
this.age = age;
return undefined
// return null
}
let person3 = new Person("王五", 25);
console.log(person3); // Person {perName: '王五', age: 25}
console.log(person3.perName); // 王五
3. JS中的call、apply和 bind 方法(改变this 指向)
call、apply 和 bind 都是用来改变函数/方法中的 this 指向的,那么他们三个之间又有什么区别呢?接下来我们分别说下他们的用法和适用环境。
call() & apply()
这两个方法差别不大,只在参数传递上有一点区别:
第一个参数都是指向 this 要指向的对象,而第二个参数call接受的参数是单个单个传递的,apply接受的参数则需要一个数组
Function.call(obj, param1, param2,....)
Function.apply(obj, [param1, param2,...])
var a = {
user:"法外狂徒",
fn:function(e,ee){
console.log(this.user); //法外狂徒
console.log(e+ee); //3
}
}
var b = a.fn;
b.call(a,1,2);
var a = {
user:"法外狂徒",
fn:function(e,ee){
console.log(this.user); //法外狂徒
console.log(e+ee); //30
}
}
var b = a.fn;
b.apply(a,[10,20]);
bind()
bind方法,参数传递第一个是this要指向的对象,接着就是方法所需要的参数了,bind方法并不会立即调用改变指向的方法,而是把方法复制一份,并且改变了this指向,然后把它返回,所以我们还需要调用一下。
var a = {
user:"法外狂徒",
fn:function(e,d,f){
console.log(this.user); // 法外狂徒
console.log(e,d,f); // 10 1 2
}
}
var b = a.fn;
var c = b.bind(a,10);
// bind也可以有多个参数,并且参数可以执行的时候再次添加;且参数是按照形参的顺序进行的
c(1,2);
注意:
如果这三个方法没有第一个参数,或者参数为null、 undefined 或者 this,则等同于指向全局对象。
4. 箭头函数中的 this
箭头函数不会创建自己的 this,它只会从自己的作用域链的上一层继承 this