一、 函数的this理解
this的指向只有在代码运行时才确定的,而不是定义时确定的
- 谁调用这个函数或方法,this关键字就指向谁,才决定了this的作用域
- js中的this指向谁,是由调用模式决定的
二、js中的调用模式
- 普通函数调用
// 普通函数调用 this指向 window
function sum() {
console.log(this); // window
}
sum();
- 作为方法来调用
- 当函数被保存为一个对象的属性时,它就可称为这个对象的方法。当一个方法被调用时,this被绑定到这个对象上。
- 如果调用表达式包含一个提取属性的动作(. 或 []),则它被称为方法调用。
// 作为方法来调用 this 指向当前对象
var peroson = {
name: 'yian',
age: 15,
say(){
console.log(this); //{name: "yian", age: 15, say: ƒ}
return this.name + this.age;
}
}
peroson.say();
var o = {
a:10,
b:{
a:12,
fn:function(){
console.log(this.a); //12
}
}
}
o.b.fn();
- 这里同样也是对象o点出来的,但是同样this并没有执行它.
情况1:如果一个函数中有this,但是它没有被上一级的对象所调用,那么this指向的就是window,这里需要说明的是在js的严格版中this指向的不是window,但是我们这里不探讨严格版的问题,你想了解可以自行上网查找。
情况2:如果一个函数中有this,这个函数有被上一级的对象所调用,那么this指向的就是上一级的对象。
情况3:如果一个函数中有this,这个函数中包含多个对象,尽管这个函数是被最外层的对象所调用,this指向的也只是它上一级的对象,例子3可以证明,如果不相信,那么接下来我们继续看几个例子。
var o = {
a:10,
b:{
// a:12,
fn:function(){
console.log(this.a); //undefined
}
}
}
o.b.fn();
- 尽管对象b中没有属性a,这个this指向的也是对象b,因为this只会指向它的上一级对象,不管这个对象中有没有this要的东西。
- 直接执行了fn
例:特殊的情况
var o = {
a:10,
b:{
a:12,
fn:function(){
console.log(this.a); //undefined
console.log(this); //window
}
}
}
var j = o.b.fn;
j();
- 这里this指向的是window,因为this永远指向的是最后调用它的对象,也就是看它执行的时候是谁调用的,例中虽然函数fn是被对象b所引用,但是在将fn赋值给变量j的时候并没有执行所以最终指向的是window。
- 作为构造函数来调用
若在一个函数前加new关键字来调用,则就会创建一个连接到该函数的prototype成员的新对象,同时,this会被绑定到这个新对象上。这种情况下,这个函数就可成为此对象的构造函数。
//作为构造函数来调用 this 指向当前实例化的对象
function Animal(name, age) {
this.name = name;
this.age = age;
this.run = function() {
console.log(this); //Animal {name: "yian", age: 5, run: ƒ}
return this.name + ' ' + this.age;
};
}
let animal = new Animal('yian', 5);
console.log(animal.run()); //yian 5
- 使用apply/call方法调用
- 在JS中,函数也是对象,所有函数对象都有两个方法:apply和call,这两个方法可以让我们构建一个参数数组传递给调用函数,也允许我们改变this的值。
- 在apply()、call() 方法中,接收两个参数:第一个参数是this指向;第二个参数在apply()中是数组,在call()中是普通的形参,如字符串等。
例1:使用构造函数定义一个长方形
//使用apply/call方法来调用======this指向取决于两个方法的第一个参数
function Rectangle(width, height) {
this.width = width;
this.height = height;
this.getRectangle = function() {
console.log(this);
return `长度是:${width},高度是:${height}`;
};
}
// 实例化一个长方形
let a = new Rectangle(10, 20);
//当调用a.getRectangle()时,Rectangle()函数中的this 指向的是它的实例
a.getRectangle();
let model = {
width: 35,
height: 50
};
//当model对象在调用js的call()||apply()方法后,Rectangle()函数中的this就指向了model对象
a.getRectangle.apply(model);
例2:使用apply(),call()方法求最大值
//Math对象有个max()方法
//由于参数是个数组,使用apply()方法 ,当第一个参数是null || undefind时,this指向window
let arr = [1, 5, 25, 68, 59];
console.log(Math.max.apply(null, arr)); //68
console.log(Math.max.apply(undefined, arr)); //68
- Function.prototype.bind方法
例3:异步模拟,创建一个水果模型
function Fruit(name, color) {
this.name = name;
this.color = color;
this.change = function() {
console.log(this); //这个this指向了 Fruit {name: "苹果", color: "黑色", change: ƒ}的实例
//1秒后打印水果的颜色
setTimeout(function() {
console.log(this); //这个this指向了 Window
console.log(this.name + '的颜色是 ' + this.color); //苹果的颜色是 undefined
}, 1000);
};
}
let apple = new Fruit('苹果', '黑色');
apple.change();
- 在浏览器BOM中的方法,this是指向window的。因此在构造函数中Fruit()中,change()方法里的定时器里的this是指向window的,并不是当前构造函数的实例apple
- 解决方法:用bind()将this指向绑定到当前实例化的对象;使用es6箭头函数
解决例3(法一):用bind()将this指向绑定到当前实例化的对象
function Fruit(name, color) {
this.name = name;
this.color = color;
this.change = function() {
console.log(this); // 这个this 指向了 Fruit {name: "苹果", color: "黑色", change: ƒ}的实例
//1秒后打印水果的颜色
setTimeout(function() {
console.log(this); //此时this 指向了当前实例
console.log(this.name + '的颜色是' + this.color);
}.bind(this), 1000);
};
}
let apple = new Fruit('苹果', '黑色');
apple.change();
- es6箭头函数
解决例3(法二):使用es6箭头函数
/*es6的箭头函数 es6里面this指向固定化,始终指向父级的运行上下文环境
因箭头函数没有this,因此它自身不能进行new实例化,另外箭头函数是从父级继承来的,是父级通过 call方法实现的*/
function Fruit(name, color) {
this.name = name;
this.color = color;
this.change = function() {
console.log(this); // 这个this 指向了 Fruit {name: "苹果", color: "红色", change: ƒ}的实例
//1秒后打印水果的名字和颜色
setTimeout(() => {
console.log(this); // 此时this也指向了当前实例
console.log(this.name + '的颜色是' + this.color);
}, 1000);
};
}
let apple = new Fruit('苹果', '红色');
apple.change();