this的指向
4种情况:
- 对象内方法中的this;
- 普通函数中的this;
- 类中的this,分为构造函数内和构造函数外;
- 箭头函数中的this;
作为对象的方法被调用
当函数作为对象的方法被调用时,this指向该对象
let obj = {
name: "东北F4",
movie: function () {
console.log(this.name); // 东北F4
console.log(this === obj); // true
}
}
obj.movie();
console.log(this); // window
注:对象外部的this指向全局对象window,也属此种情况。
普通函数中的this
当普通函数方法被调用时,this指向全局对象window
let obj = {
name: "东北F4",
movie: function () {
console.log(this.name); // 东北F4
console.log(this === obj); // true
}
}
window.name = '赵家班';
function fun1(){
console.log(this.name); //赵家班
console.log(this.obj); //undefined
}
fun1()
注:用let和const定义的全局变量并不属于对象window,只存在与块级作用域中。ES6规定用var和function定义的全局变量仍旧会存在对象window中。
类中的this,分为构造函数内和构造函数外
构造函数中的this
当new运算符调用函数时,总是返回一个对象,this通常也指向这个对象
ES6前
作为构造函数this指向和作为普通函数不同
var MyClass = function () {
this.name = '小沈阳';
console.log(this);
}
MyClass(); // window
var obj = new MyClass(); // obj = MyClass {name: '小沈阳'}
console.log(obj.name); //小沈阳
如果显式的返回了一个object对象,那么此次运算结果最终会返回这个对象。
var MyClass = function () {
this.age = 1;
return {
name: '小沈阳'
}
}
var myClass = new MyClass();
console.log('myClass:', myClass); // myClass: {name: '小沈阳'}
console.log('age:', myClass.age); // age: undefined
console.log('name:', myClass.name); // name: 小沈阳
构造函数不显示的返回任何数据,或者返回非对象类型的数据,就不会造成上述问题。
ES6后
构造函数中的this指向new出来的实例
class Person{
constructor(){
this.name = '东北F4';
}
}
var F4 = new Person();
console.log(F4); // Person {name: '东北F4'}
console.log(F4.name); //东北F4
构造函数外的this指向调用者
class Person{
constructor(){
this.name = '东北F4';
}
fun1 = function () {
console.log("this:",this);
}
}
let F4 = new Person();
F4.fun1(); // Person {name: '东北F4', fun1: ƒ}
构造函数外的this调用时,向下面的调用会导致this为undefined,原因之后详说。 当定义时的方法为箭头函数时,this的指向不会变,原因在下面。
let changThis = F4.fun1;
changThis(); // undefined
箭头函数
箭头函数不会创建自己的this,它只会从自己的作用域链的上一层继承this。
因此,在下面的代码中,传递给getVal函数内的this并不是调用者自身,而是外部的this~
this.val = 2;
var obj = {
val: 1,
getVal: () => {
console.log(this.val);
}
}
obj.getVal(); // 2
改变this的指向
在JavaScript 函数方法中,call、apply 和 bind 是 Function 对象自带的三个方法,这三个方法的主要作用是绑定且改便this 的指向,以适应需求。
call的使用
主要功能:允许你在一个对象上调用该对象没有定义的方法(另一个指向的对象上的),并且这个方法可以访问该对象中的属性。
基本使用:
var group = {
name: "东北F4",
fun: function(){
console.log(this);
}
}
var newGroup = { name: "江南四大才子" };
var groupShow = function(name, No){
console.log("我们的组合是:", this.name); //"东北F4"
console.log("我们的团队是:", name); // "赵家班"
console.log("我们的团队是:", No); // "小沈阳"
};
// groupShow为要调用的方法
// group为this绑定的指向
// "赵家班" 为向调用方法中穿的参数,传多个参数依次向后加
groupShow.call(group, "赵家班", "小沈阳")
复制代码
指向:
var group = {
name: "东北F4",
fun: function(){
console.log(this);
}
}
var newGroup = { name: "江南四大才子" };
var groupShow = function(){ };
group.fun(); //group对象
group.fun.call(); // window对象
group.fun.call(newGroup); // newGroup对象
group.fun.call(groupShow); // groupShow方法
复制代码
注:如果输入基本类型或实例,那么this会指向其创造的对象或方法。null和underfind除外,其指向window。
apply
apply 和 call的基本功能大致相同,使用方法也差不多,唯一的不同是传参。call是多个参数时列表依次传递,apply则以数组的形式传递。
function b(x,y,z){
console.log(x,y,z);
}
b.apply(null,[1,2,3]); // 1 2 3
复制代码
bind
bind是区别与call和apply的,这不是从功能或者使用上来说的,而是从实现上来说的。
基本使用:传参的方式和call相同
var group = {
name: "东北F4",
fun: function(){
console.log(this);
}
}
var newGroup = { name: "江南四大才子" };
let a = group.fun.bind(newGroup); // 不会执行,只会绑定
let b = group.fun.call(newGroup);
let c = group.fun.apply(newGroup)
a() // {name: '江南四大才子'}
b() // TypeError: b is not a function
c() // TypeError: b is not a function
复制代码
模拟实现bind:
Function.prototype.bind = function (obj) {
var _this = this; // 保存调用bind的函数
var obj = obj || window; // 确定被指向的this,如果obj为空,执行作用域的this就需要顶上喽
return function(){
return _this.apply(obj, arguments); // 修正this的指向
}
};
var obj = {
name: "孙悟空",
getName: function(){
console.log(this.name)
}
};
var func = function(){
console.log(this.name);
}.bind(obj);
func(); // 孙悟空
bind 和 apply、call的区别
bind: 不会立刻执行,this的改变是长久的,属于copy了一个方法,然后把this替换成想要指向的目标。只要用bind绑定了,用apply和call也不会改变this的指向。 当对一个函数调用多次bind的时候,最终起作用的是第一个bind。
apply 和 call: 立刻执行,this的改变不是长久的,只是通过这两个方法调用时this才指向目标,否则this指向不变。
ES5引入 bind 的真正目的是为了弥补 call/apply 的不足,由于 call/apply 会对目标函数自动执行,从而导致它无法在事件绑定函数中使用,因为事件绑定函数不需要我们手动执行,它是在事件被触发时由JS 内部自动执行的。而 bind 在实现改变函数 this 的同时又不会自动执行目标函数,因此可以完美的解决上述问题。
陷阱
this 永远指向最后调用它的那个对象
举个例子,当我们希望自己封装Dom方法,来精简代码时:
var getDomById = document.getElementById
getDomById('div1') // Uncaught TypeError: Illegal invocation(非法调用)
复制代码
我们期望的结果是能正常获取到DOM,但是结果却是非法调用。
这是因为当 document.getElementById 赋值给 getDomById时,其中的this已经不是document了,而是全局对象window。这就是this 永远指向最后调用它的那个对象。
注:document是window的子对象
修正:
document.getElementById = (function (func) {
return function(){
return func.call(document, ...arguments)
}
})(document.getElementById)
// 利用立即执行函数将document保存在作用域中。
复制代码
类:构造函数类的this可能会是underfind
书接上回:changThis()的执行结果为undefined
class Person{
constructor(){
this.name = '东北F4';
}
fun1 = function(){
console.log("this:",this);
}
}
let F4 = new Person();
let changThis = F4.fun1;
changThis(); // undefined
复制代码
原因是由陷阱1导致F4.fun1赋值给changThis时this的执行改变成了window。但是根据 JavaScript 的语法规则,所有在类中定义的方法都默认开启局部严格模式。在严格模式下,所有指向 window 对象的 this,都全部变更为 undefined。
总结
陷阱1的这个例子是从网上找到的,但我认为再经典不过了。其实我类似这样的陷阱还有很多,如果之后遇到,我也会将其更新到其中。
最后,希望大家学有所成!