文章里的每个案例都是我亲自编写并验证的,建议阅读文章时,可以在浏览器执行案例,会更有利于理解。
默认绑定
默认绑定,在不能应用其它绑定规则时使用的默认规则,通常是使用全局变量或者独立函数调用。
- 全局环境下,
this
始终指向全局对象window
,无论是否严格模式。 - 对于延时函数内部的回调函数的
this
始终指向全局对象window
,无论是否是箭头函数或者严格模式。 - 普通函数内部的
this
分两种情况,严格模式和非严格模式。非严格模式下,this
默认指向全局对象window
。严格模式下,this
指向undefined
。 - 箭头函数下,
this
始终指向全局对象window
,无论是否严格模式。
console.log("全局环境下的this: ", this); //window
setTimeout(function () {
console.log(this); //window
});
setTimeout(function () {
"use strict";
console.log(this); //window
});
setTimeout(() => {
console.log(this); //window
});
setTimeout(() => {
"use strict";
console.log(this); //window
});
function f1() {
console.log("方法下的this: ", this); //window
}
f1();
function f2() {
"use strict";
console.log("严格模式下方法下的this: ", this); // undefined
}
f2();
const f3 = () => {
console.log("箭头函数方法下的this: ", this); //window
};
f3();
const f4 = () => {
"use strict";
console.log("严格模式下箭头函数方法下的this: ", this); //window
};
f4();
隐式绑定
在隐式绑定中通常函数作为对象的方法被调用。
- 当函数作为对象里的方法被调用时,它们的
this
是调用该函数的对象。 - 多层嵌套的对象,内部方法的
this
指向离被调用函数最近的对象。
const obj1 = {
name: "randy1",
say() {
return this.name;
},
b: {
name: "randy2",
say() {
return this.name;
},
},
};
console.log(obj1.name); // randy1
console.log(obj1.b.name); // randy2
显示绑定
显式绑定比较好理解,就是通过call、apply、bind的方式,显式的修改this所指向的对象。
apply(this, [args])
、call(this, args)
、bind(this, args)()
这三者的区别是apply参数列表是数组,bind需要再次调用。- 如果
call
、apply
或者bind
传入的第一个参数值是undefined
或者null
,严格模式下this
的值为传入的值null /undefined
。非严格模式下,实际应用的默认绑定规则,this
指向全局对象(node环境为global,浏览器环境为window)。
var name = "global name";
function say(age) {
console.log(`${this.name}今年${age}啦!`);
}
const user = { name: "randy" };
const user2 = { name: "randy2" };
say(24); // global name今年24啦!
say.apply(user, [25]); // randy今年25啦!
say.apply(user2, [26]); // randy2今年26啦!
say.call(user, 25); // randy今年25啦!
say.call(user2, 26); // randy2今年26啦!
say.bind(user, 25)(); // randy今年25啦!
say.bind(user2, 26)(); // randy2今年26啦!
say.apply(null, [25]); // global name今年25啦!
say.apply(undefined, [26]); // global name今年26啦!
new 绑定
- 当一个函数用作构造函数时(使用new关键字),它的
this
被绑定到正在构造的新对象。 - 构造器返回的默认值是
this
所指的那个对象,也可以手动返回其他的新对象。 - 如果构造函数返回了一个新的非空对象,则
this
指向新对象,否则指向我们创建的对象。
function People() {
this.name = "randy";
}
const p1 = new People();
console.log(p1.name); // randy
function People2() {
this.name = "randy";
return { name: "demi" }; //手动设置返回{ name: 'demi' }新对象
}
const p2 = new People2();
console.log(p2.name); // demi
扩展
vue中延迟函数的this
在vue
中,延迟函数如果是箭头函数,它的this
指向当前vue
实例。
created() {
console.log(this); // 当前vue实例
setTimeout(() => {
console.log(this); // 当前vue实例
});
setTimeout(function () {
console.log(this); // window对象
});
},
事件中的this
在事件中e.target
始终指向触发事件的元素,e.currentTarget
始终指向绑定事件的元素。
事件中的this
指向又有所区别
- 不管以何种方式绑定的事件,如果事件函数是箭头函数,
this
始终指向window
。这也就印证了箭头函数的this只取决于裹箭头函数的第一个普通函数的this
。 - 如果事件函数是普通函数就分为两种情况,如果采用监听或者给
dom
元素间接绑定事件的方式this
指向绑定事件的元素,如果是直接绑定事件this
指向window
。
下面我用例子说明
<div id="div1">
<span>点我吧1</span>
</div>
<div id="div2">
<span>点我吧2</span>
</div>
<div id="div3" onclick="click3(event)">
<span>点我吧3</span>
</div>
const div1 = document.getElementById("div1");
div1.addEventListener("click", function (e) {
console.log(this); // <div id="div1"><span>点我吧1</span></div>
console.log("currentTarget", e.currentTarget); // <div id="div1"><span>点我吧1</span></div>
console.log("target", e.target); // <span>点我吧1</span>
});
div1.addEventListener("click", (e) => {
console.log(this); // window
console.log("currentTarget", e.currentTarget); // <div id="div1"><span>点我吧1</span></div>
console.log("target", e.target); // <span>点我吧1</span>
});
const div2 = document.getElementById("div2");
div2.onclick = function (e) {
console.log(this); // <div id="div2"><span>点我吧2</span></div>
console.log("currentTarget", e.currentTarget); // <div id="div2"><span>点我吧2</span></div>
console.log("target", e.target); // <span>点我吧2</span>
};
// div2.onclick = (e) => {
// console.log(this); // window
// console.log("currentTarget", e.currentTarget); // <div id="div2"><span>点我吧2</span></div>
// console.log("target", e.target); // <span>点我吧2</span>
// };
function click3(e) {
console.log(this); // window
console.log("currentTarget", e.currentTarget); // <div id="div3"><span>点我吧3</span></div>
console.log("target", e.target); // <span>点我吧3</span>
}
// const click3 = (e) => {
// console.log(this); // window
// console.log("currentTarget", e.currentTarget); // <div id="div3"><span>点我吧3</span></div>
// console.log("target", e.target); // <span>点我吧3</span>
// };
箭头函数中的this
- 箭头函数的
this
是在定义函数时绑定的,不是在执行过程中绑定的。简单的说,函数在定义时,this
就继承了定义函数的对象。 - 箭头函数中的
this
只取决包裹箭头函数的第一个普通函数的this
,否则应用的是默认绑定规则。 - 箭头函数不能通过
apply call bind
改变this
。 - 箭头函数不能使用
arguments
,得使用reset
参数 - 箭头函数不能用于构造函数。
- 不可以使用
yield
命令,因此箭头函数不能用作Generator
函数。
下面我将举例重点说明下箭头函数的绑定问题,其他特性就不详细举例了,小伙伴们可以自行测试。
const obj = {
hi: function () {
console.log(this);
return () => {
console.log(this);
};
},
sayHi: function () {
return function () {
console.log(this);
return () => {
console.log(this);
};
};
},
say: () => {
console.log(this);
},
};
let hi = obj.hi(); //输出obj对象
hi(); //输出obj对象
let sayHi = obj.sayHi();
let fun1 = sayHi(); //输出window
fun1(); //输出window
obj.say(); //输出window
下面我们来分析下。
obj.hi()
应用隐式绑定规则,this就是obj对象,所以输出obj。hi()
箭头函数里再输出this,应用我们上面说的特性,箭头函数中的 this 只取决包裹箭头函数的第一个普通函数的 this,所以也输出obj对象。obj.sayHi()
返回一个普通的函数。sayHi()
就是调用刚返回的普通函数,应用默认绑定规则,返回window对象。fun1()
箭头函数里再输出this,应用我们上面说的特性,箭头函数中的 this 只取决包裹箭头函数的第一个普通函数的 this,所以也输出window对象。obj.say()
箭头函数里再输出this,应用我们上面说的特性,箭头函数中的 this 只取决包裹箭头函数的第一个普通函数的 this,如果没有被普通函数包裹,就会应用默认绑定规则所以输出window对象。
如何准确判断this
说了这么多,那我们到底如何来判断this指向问题呢?
- 函数是否在
new
中调用(new 绑定),如果是,那么this
绑定的是新创建的对象。 - 函数是否通过
call,apply,bind
调用,如果是,那么this
绑定的就是指定的对象。 - 函数是否在某个上下文对象中调用(隐式绑定),如果是的话,
this
绑定的是那个上下文对象。 - 如果以上都不是,那么使用默认绑定。
- 如果把
null
或者undefined
作为this
的绑定对象传入call、apply、bind
,非严格模式下这些值在调用时会被忽略,实际应用的是默认绑定规则。 - 如果是箭头函数,箭头函数的
this
只取决包裹箭头函数的第一个普通函数的this
。如果没有被普通函数包裹实际应用的是默认绑定规则。
总结就是new绑定 > 显示绑定 > 隐式绑定 > 默认绑定
。
系列文章
后记
感谢小伙伴们的耐心观看,本文为笔者个人学习笔记,如有谬误,还请告知,万分感谢!如果本文对你有所帮助,还请点个关注点个赞~,您的支持是笔者不断更新的动力!