一、关于 this 的指向
当一个函数被调用时,会创建一个执行上下文,它包含函数在哪里被调用(调用栈)、函数的调用方式、传入的参数等信息,this 就是这个记录的一个属性,它会在函数执行的过程中被用到。
一句话比较易懂的:this
指向最后调用它的那个对象。
this 的绑定规则:
- 默认绑定
- 隐式绑定
- 显式绑定(硬绑定)
- new 绑定
this 绑定的优先级:
this 根据优先级来确定指向。
优先级为:new 绑定 > 显示绑定 > 隐式绑定 > 默认绑定
默认绑定
通常是独立函数调用。
请看以下代码:
var name = "windowName";
function test() {
var name = "zhangsan";
console.log(this); // Window对象
console.log(this.name); // windowName
}
test();
console.log(this); // Window对象
test()
实际是 window.test()
,省略了全局对象window
而已。test()
被 window
调用,所以 this
指向 window
。此处是默认绑定。
但是这里未使用严格模式。若使用严格模式,则全局对象是 undefined
是没有定义的,会报错。
隐式绑定
函数是否在某个上下文对象中调用,若是则 this 绑定的是那个上下文对象。
请再看以下代码:
var name = "windowName";
var obj = {
name: "zhangsan",
getName: function () {
console.log(this.name) // zhangsan
}
}
obj.getName()
这里的函数,obj.getName()
,被 obj
调用,所以 this
指向 obj
。
当然,也可 obj
前面也省略了 window
,加上是 window.obj.getName()
,this 指向最后调用它的那个对象,所以 this 还是指向 obj。
若 obj 没有 name 属性呢? this.name
会打印出什么?
答案:会打印 undefined
,表示 this 指向最后调用它的对象,且不会向上层对象寻找属性。(即 this 仅仅寻找上下文)
注意下面这个例子:
var name = "windowName";
var obj = {
name: "zhangsan",
getName: function () {
console.log(this.name) // windowName
}
}
var f = obj.getName;
console.log(f) // ƒ () {...}
f()
var f = obj.getName
将 obj
的 getName
赋值给了 f
,也就是说,f
被赋值成了 函数,调用 f()
,就又是 window
了,而不是 obj
。所以此时 this
指向 window
,打印出 windowName
显式绑定
通过 call、apply、bind 可以修改函数绑定的 this,使其成为我们想指定的对象。通过这些方法的第一个参数我们可以显式地绑定 this。
new 绑定
函数如果作为构造函数使用 new 调用时, this 绑定的是新创建的构造函数的实例。
实际上使用 new 调用构造函数时,会依次执行下面的操作(即 new 的过程)
var yourfood = new Food("面包");
// 上行代码实际内部过程是下面:
new Food {
var obj = {}
obj.__proto__ = Food.prototype
var result = Food.call(obj, "面包")
return typeof result === 'obj'? result : obj
}
解析如下:
- 创建一个新对象obj ;
- 构造函数的
prototype
被赋值给这个新对象的__proto__
; - 使用 call 将新对象赋给当前的
this
并 执行构造函数; - 如果构造函数若返回一个对象,则new 表达式中的函数调用返回该对象。若没有返回对象类型的值,则会返回这个新对象obj 。
二、改变 this 指向 的几种方法:
先看个问题:
var name = "windowName";
var obj = {
name: "zhangsan",
getName: function () {
console.log(this.name); // 报错
},
waitGet: function () {
setTimeout(function () {
this.getName();
}, 500);
},
};
obj.waitGet()
其中 setTimeout
中回调没有使用箭头函数的情况下,因最后调用 setTimeout
的 是 window
所以 this
指向的 是 window
,而 window
中没有 getName()
,所以会报错!!
1、使用 箭头函数
箭头函数中没有 this
绑定,如果 箭头函数 被 非箭头函数 包裹,则 this 指向最近一层的非箭头函数的 this
,否则,this 为 undefined
。
使用 箭头函数 解决上面代码报错问题:
var name = "windowName";
var obj = {
name: "zhangsan",
getName: function () {
console.log(this.name); // zhangsan
},
waitGet: function () {
setTimeout(() => {
this.getName();
}, 500);
},
};
obj.waitGet();
2、使用 that = this
var name = "windowName";
var obj = {
name: "zhangsan",
getName: function () {
console.log(this.name); // zhangsan
},
waitGet: function () {
// 将 that 赋值为 obj对象
var that = this;
setTimeout(function () {
// 此处用that就是obj调用getName。若用this,则在setTimeout中this指向window,而window无getName方法就会报错。
that.getName();
}, 500);
},
};
obj.waitGet();
3、使用 call、apply、bind
var name = "windowName";
var obj = {
name: "zhangsan",
getName: function () {
console.log(this.name); // zhangsan
},
waitGet: function () {
setTimeout(
function () {
this.getName();
}.call(obj),
// }.apply(obj),
// }.bind(obj)(),
500
);
},
};
obj.waitGet();
call、apply、bind 的区别
都具有一个指定的 this
值,不同的是 call
接收若干个参数列表, apply
接收一个包含多个参数的数组。bind
接收若干个参数列表,但 不自动执行函数,需手动调用,其后加 ()
。而 call
和 apply
改变this指向后会 自动执行函数。
var obj = {
getName: function (a, b) {
console.log(a, b); // 1 2
},
};
var f1 = obj.getName;
f1.call(obj, 1, 2);
var f2 = obj.getName;
f2.apply(obj, [1, 2]);
var f3 = obj.getName;
f3.bind(obj, 1, 2)();
例:
var food = {
name: "张三",
price: "5元",
getPrice: function (type) {
console.log(type, this.price);
},
};
food.getPrice("面包"); // 面包 5元
// 改变 getPrice的this指向,指向传入的{ sex: "女", price: "10元" },所以this.price="10元"
var getPrice2 = food.getPrice.bind({ sex: "女", price: "10元" }, "烤肠");
getPrice2(); // 烤肠 10元