「这是我参与2022首次更文挑战的第4天,活动详情查看:2022首次更文挑战」。
大家好,我是刘十一,今天我们一起看看什么this、如何判断this的指向以及this的实际应用。
一、什么是this
this 是 执行上下文中的 一个属性,它 指向 最后一次调用 这个方法 的 对象。
二、重点结论
1、this的指向,是在 函数 被调用的时候 确定的,也就是 执行上下文被创建时 确定的。
因此,一个函数中的this指向,可以非常灵活。如下面的例子中,同一个函数由于调用方式的不同,this指向了不一样的对象。
var a = 10;
var obj = {
a: 20
}
function fn() {
console.log(this.a);
}
fn(); // 10
fn.call(obj); // 20
2、除此之外,在函数执行过程中,this一旦被确定,就不可更改了。
var a = 10;
var obj = {
a: 20
}
function fn() {
this = obj; // 这句话试图修改this,运行后会报错
console.log(this.a);
}
fn();
三、this指向判断
1、函数调用模式
(1)当 函数 不是一个对象的 属性 时,直接(在全局对象中)作为 函数来调用 ,this 指向全局对象。
// 1)通过this绑定到 全局对象
this.a2 = 20;
// 2)通过声明绑定到 变量对象,但在全局环境中,变量对象就是它自身
var a1 = 10;
// 3)仅仅只有赋值操作,标识符会 隐式绑定 到 全局对象
a3 = 30;
// 输出结果会全部符合预期
console.log(a1);
console.log(a2);
console.log(a3);
2、方法调用模式
(1)如果函数 作为一个对象的 方法 调用时,this 指向这个对象。
var a = 20;
var obj = {
a: 10,
c: this.a + 20,
fn: function () {
return this.a;
}
}
console.log(obj.c); // 40
console.log(obj.fn()); // 10
(2)如果函数独立调用,那么该函数内部的this,则指向undefined。但是在非严格模式中,当this指向undefined时,它会被自动指向全局对象。
var a = 20;
var foo = {
a: 10,
getA: function () {
return this.a;
}
}
console.log(foo.getA()); // 10
var test = foo.getA;
console.log(test()); // 20 独立调用
var a = 20;
function getA() {
return this.a;
}
var foo = {
a: 10,
getA: getA
}
console.log(foo.getA()); // 10
console.log(getA()); // 20 独立调用
var a = 20;
function foo() {
var a = 1;
var obj = {
a: 10,
c: this.a + 20,
fn: function () {
return this.a;
}
}
return obj.c;
}
console.log(foo()); //40 独立使用
console.log(window.foo()); // 40
3、构造器调用模式
使用 new 来构建函数,会执行如下四步操作:
- 创建一个空的简单 JavaScript 对象(即 {} );
- 为步骤1新创建的对象添加属性 proto ,将该属性链接至构造函数的原型对象 ;
- 将步骤1新创建的对象作为this的上下文;
- 如果该函数没有返回对象,则返回 this 。
(1)如果一个函数 用 new 调用 时,函数执行前会 新创建一个对象,this 指向这个新创建的对象。
function Person(name, age) {
// 这里的this指向了谁?
this.name = name;
this.age = age;
}
Person.prototype.getName = function() {
// 这里的this又指向了谁?
return this.name;
}
// 上面的2个this,是同一个吗?
var p1 = new Person('Nick', 20);
p1.getName();
解析: 函数没有返回对象,则返回 this,并且新建的对象 p1是this的上下文,即,构造函数的this,指向了新的实例对象 p1。
根据上边对函数模式中this的定义,如果函数 作为一个对象的 方法 调用时,this 指向这个对象,p1.getName()中getName中的this,也是指向了p1。
4、原型链中的调用模式
(1)指向生成的新对象
function Student(name){
this.name = name;
}
var s1 = new Student('牛小六');
Student.prototype.doSth = function(){
console.log(this.name);
}
s1.doSth(); // '牛小六'
5、apply、call、bind调用模式
apply、call、bind三者的作用与区别:
- apply 、 call 、bind 这三个方法都可以 显示的 指定 调用函数的 this 指向;
- apply 、 call 、bind 三者 第一个参数 都是this 要指向的对象,也就是想指定的上下文;
- apply 、 call 、bind 三者都可以利用 后续参数 传参;
- bind 是 返回 对应函数,便于 稍后调用;
- apply 、call 则是 立即调用 。
(1)apply 方法接收两个参数:一个是 this 绑定的对象,一个是参数数组(或类数组)。
fun.apply(thisArg,[arg1, arg2, ...])
(2)call 方法接收的参数,第一个是 this 绑定的对象,后面的其余参数是传入函数执行的参数。
即,在使用 call() 方法时,传递给函数的参数必须逐个列举出来。
fun.call(thisArg, arg1, arg2, ...)
(3)bind 方法通过传入一个对象,返回一个 this 绑定了传入对象的新函数。
这个函数的 this 指向 除了使用 new 时会被改变,其他情况下都不会改变。
fun.bind(thisArg[, arg1[, arg2[, ...]]])
代码示例
如果我们有一个对象 banana= {color : "yellow"} ,但我们不想对它重新定义 say 方法,那么我们就可以通过 call 或 apply 使用 apple 中的 say 方法。
function fruits() {}
fruits.prototype = {
color: "red",
say: function() {
console.log("My color is " + this.color);
}
}
var apple = new fruits;
apple.say(); //My color is red
banana = {
color: "yellow"
}
apple.say.call(banana); //My color is yellow
apple.say.apply(banana); //My color is yellow
bind 最简单的用法是创建一个函数,使这个函数不论怎么调用都有同样的this值。 常见的错误就像下面的例子一样,将方法从对象中拿出来,然后调用,并且希望this指向原来的对象。
this.num = 80;
var mymodule = {
num: 100,
getNum: function() {
console.log(this.num);
}
};
mymodule.getNum(); // 100
var getNum = mymodule.getNum;
getNum(); // 80, 因为在这个例子中,"this"指向全局对象
var boundGetNum = getNum.bind(mymodule);
boundGetNum(); // 100
6、箭头函数调用模式
箭头函数和普通函数的主要区别:
- 没有自己的 this、super、arguments和new.target绑定。
- 不能使用new来调用。
- 没有原型对象。
- 不可以改变this的绑定。
- 形参名称不能重复。
- 没有this绑定,必须通过查找作用域链来决定其值。
(1) 如果箭头函数被非箭头函数包含,则this绑定的是最近一层非箭头函数的this,否则this的值则被设置为全局对象
var name = 'window';
var student = {
name: '牛小六',
doSth: function(){
// var self = this;
var arrowDoSth = () => {
// console.log(self.name);
console.log(this.name);
}
arrowDoSth();
},
arrowDoSth2: () => {
console.log(this.name);
}
}
student.doSth(); // '牛小六'
student.arrowDoSth2(); // 'window'
也就是说无法通过call、apply、bind绑定箭头函数的this(它自身没有this)。
而call、apply、bind可以绑定缓存箭头函数上层的普通函数的this。
var student = {
name: '牛小六',
doSth: function(){
console.log(this.name);
return () => {
console.log('arrowFn:', this.name);
}
}
}
var person = {
name: 'person',
}
student.doSth().call(person); // '牛小六' 'arrowFn:' '牛小六'
student.doSth.call(person)(); // 'person' 'arrowFn:' 'person'
四、实际应用
题目1
var x = 0;
var obj = {
x: 3,
p: () => {
console.log(this.x, "b");
},
};
function Fn() {
//函数名大写开头,默认为 构造函数
this.a = 1;
this.b = {
x: 1,
p: function (callback) {
//callback 默认为 回调函数
this.c = this.x + 1; //添加了一个c:2
console.log(this.x, "a");
obj.p.apply(this);
callback();
// window.callback();
},
};
}
var fn = new Fn()
fn.b.p(function () {
console.log(this.x, "c");
})
//答案1:1 'a' 0 'b' 0 'c'
解析1:
- 调用 Fn构造函数的 实例对象fn 里的b对象变量 里的 名为 p的函数
- 此时p函数 的this指向它的直接调用者 fn.b,即 this.x =1 (如果函数作为对象的一个属性被调用时,函数中的this指向该对象)
- 欲将obj对象下的p函数的this指向改变到 obj.p函数,但obj.p是箭头函数, 箭头函数特点是:箭头函数不绑定this,会捕获其所在的上下文的this值,作为自己的this值。 此处this.x=0
- callback()回调函数的调用者为window,则this.x=0
题目2
var x = 0;
var obj = {
x: 3,
p: () => {
console.log(this.x, "b");
},
};
function Fn() {
//函数名大写开头,默认为构造函数
this.a = 1;
this.b = {
x: 1,
p: function (callback) {
//callback 默认为回调函数
this.c = this.x + 1; //添加了一个c:2
console.log(this.x, "a");
obj.p.apply(this);
callback();
// window.callback();
},
};
}
var fn = new Fn()
function myFunction() {
fn.b.p(() => {
console.log(this.x, "d");
});
}
obj.fn = myFunction;
obj.fn()
//答案2:1 'a' 0 'b' 3 'd'
解析2:
- obj对象中新增fn:myFunction(){...}元素
- 此时p函数 的this指向它的直接调用者 fn.b,即 this.x =1
- 欲将obj对象下的p函数的this指向改变到 obj.p函数,但obj.p是箭头函数,此处this.x=0
- callback()回调函数的调用者为obj.fn ,则this.x=3
五、知识点总结
1、全局环境中(即在浏览器环境严格模式中(区别于node)): this、setTimeout、立即执行函数 等this默认指向window,但是,在箭头函数中,this的作用域环境在上层作用域内。
2、对象函数中: 如果函数作为对象的一个属性被调用时,函数中的this指向该对象。
3、构造函数中: 构造函数创建的实例的属性指向构造函数的prototype。
4、箭头函数中 箭头函数的this是在定义函数时绑定的,不是在执行过程中绑定的,箭头函数中的this取决于该函数被创建时的作用域环境。
5、apply、call、bind方法: 这三个方法都可以 显示的 指定 调用函数的 this 指向, 第一个参数 都是this 要指向的对象,也就是想指定的上下文。