简单易懂的JavaScript中的this指针
文章目录
- 默认绑定 / 隐式绑定
- 如何调整this
- 1.用变量固定this
- 2.箭头函数
- 2.bind
- 3.call/apply(一次性)
默认绑定 / 隐式绑定
要找this
指针指向谁,我们首先要做的是:找到一个明确的对象,这个对象调用了含有this
指针的函数,则:
谁调用函数,this指向谁。
而如果找不到,this
默认指向全局对象(浏览器中是 window
,Node.js
中是 global
)。
以下面几个例子说明一下:
const obj = {name: 'Alice',Hello: function() {console.log(this.name);}
};obj.Hello(); // 输出 "Alice" - this 指向 obj
这个例子中的this
指针所属函数为Hello
,谁调用了Hello()
?是obj
,所以this.name
就是要在obj
中找到name
。
const obj = {name: 'Alice',Hello: function() {console.log(name);}
};const obj1 = {};
obj1.name = 'Joan';
obj1.Hello = obj.Hello;
obj1.Hello();// 输出 "Joan" - this 指向 obj1
这里调用Hello
的对象成了obj1
,自然地,name就需要找obj1中的了。
function test() {console.log(this.x);
}var obj = {};
obj.x = 1;
obj.m = test;obj.m(); // 1
在这个例子中,this
指针所属函数为test
, test
被赋值给m
,此时m
和test
指向同一个函数,那么obj
调用m()
就等同于调用test()
,this.x
就是要在obj
中找到那个x
。其实和上一个例子本质上大差不差。
要是我们换一种方法呢?
function test() {console.log(this.x);
}
test();// 非严格模式:可能输出 undefined// 严格模式:报错(Cannot read property 'x' of undefined)
这个时候我们会发现找不到谁调用了test,这就遵循默认绑定规则,this 会默认绑定到全局对象(非严格模式)或 undefined(严格模式)。
当然,这是因为我们把test绑定到了全局,并且非要在全局没有x的情况下找出一个x。
那么如果我们不找那个x呢?
function test() {console.log(this);
}
test(); // 输出 window 对象
此时,在非严格模式下会输出window
,在严格模式下,this 还是undefined
。
这是删掉.x
的情况,但这样改显然不符合这个函数原先的想法。那么如果我们依然把this指向全局,并且在全局中给他一个x呢?
let x = 1;
function test() {console.log(this.x);
}
test(); // 1
这时候就会输出全局中的x。
let x = 1;
function test() {return this.x;
}
console.log(test()); //1
当然换一种方式也是一样的。
function test() {return this;
}
function test1(func) {func();
}test1(test()) //window
在这个例子中,test函数以参数的形式被调用,有没有那么一个明确的对象调用了test呢?我们找不到这个调用者,所以this指针指向window
其他情况:
除了上面的情况,还有一些情况可以拎出来单独说一下,我们回到第一个例子,给它额外加个定时器:
const obj = {name: 'Alice',Hello: function() {setTimeout(function(){ console.log(this.name);})}
};obj.Hello(); // undefined(非严格模式)或 报错(严格模式)
现在我们的this
所属函数是setTimeout
,但是是谁调用了它呢?我们找不到了。也就是说这种情况下,this
会丢失原来的绑定,默认指向全局对象。而全局中找不到name
。
定时器中的回调函数的this指针总是指向window对象
如何调整this
出现上面的this
丢失原来的绑定的情况不是我们希望看到的,那么有没有什么办法能按照原本的想法输出呢?
1.用变量固定this
const obj = {name: 'Alice',Hello: function() {let that = this;setTimeout(function(){ console.log(that.name);})}
};obj.Hello(); // "Alice"
我们用一个额外的变量先把this
固定下来,下面再利用这个变量进行操作就可以了。
2.箭头函数
箭头函数没有自己的 this
,它会捕获所在上下文的 this
值。所以我们可以这么写:
const obj = {name: 'Alice',Hello: function() {setTimeout(() => { console.log(this.name); // this 继承 Hello 的 this(即 obj)}, 0);}
};
obj.Hello(); // 输出 "Alice"
可以想象箭头函数就是把它所在的那个函数趟平了,setTimeout
现在不是一个拦路石了,它变成了单纯的“一条路”,那我们直接往上走就好,现在this
通往的函数就是Hello
了
2.bind
如果不方便用变量固定this
,箭头函数也不太合适呢?
比如这种情况:
class Person {constructor(){this.name = "Alice";}getName(){return this.name;}
}let change = {name: "Joan"
}let a = new Person()
change.getName = a.getName
console.log(change.getName()) //"Joan"
这种情况下,如果我们想要this.name始终为"Alice",就可以使用bind了,bind的意义指定一个this的指定对象,可以在于返回一个始终指向该对象的值。
class Person {constructor(){this.name = "Alice";this.getName = this.getName.bind(this);}getName(){return this.name;}
}let change = {name: "Joan"
}let a = new Person()
change.getName = a.getName
console.log(change.getName()) //"Alice"
我们添加了一句代码:
this.getName = this.getName.bind(this);
为了便于理解,我们可以把它看成
function1 = function.bind(this)
意思就是,我给你一个function1 ,这个方法是我把function和一个this指向绑定到了一起,从今往后你在任何地方调用function1 ,它的指向永远固定,而回到情境中,function1是this.getName,function也是this.getName,也就是说,我们给this.getName重新赋值了一下。现在它永远指向Alice了。
为了加深理解,我们可以多看一个例子:
function Hello() {console.log(this.name);
}const person = { name: 'Bob' };const BobHello = Hello.bind(person);
BobHello(); // 输出"Bob"
这个会容易理解一点是不是?
3.call/apply(一次性)
既然提到了bind,就不得不提一下call/apply了。他们的作用相似,但有一些微妙的差别,比方说bind并不会立即执行,但这两个会,并且他们是一次性的,不能接收,如下:
function Hello() {console.log(this.name);
}const person = { name: 'Bob' };Hello.call(preson); // 输出"Bob"
Hello.apply(preson); // 输出"Bob"
const BobHello = greet.bind(person);
BobHello(); // 输出"Bob"
而call和apply的区别如下:
- apply接受两个参数,第一个参数是this的指向,第二个参数是函数接受的参数,以数组的形式传入
当参数是动态数组时,apply 比 call 更方便。
改变this指向后原函数会立即执行,且此方法只是临时改变this指向一次
function fn(...args){console.log(this,args);
}
let obj = {myname:"张三"
}fn.apply(obj,[1,2]); // this会变成传入的obj,传入的参数必须是一个数组;
fn(1,2) // this指向window
当第一个参数为null、undefined的时候,默认指向window(在浏览器中)
fn.apply(null,[1,2]); // this指向window
fn.apply(undefined,[1,2]); // this指向window
- call方法的第一个参数也是this的指向,后面传入的是一个参数列表(逐个传入)
call 通常更快,因为 apply 需要处理数组。
跟apply一样,改变this指向后原函数会立即执行,且此方法只是临时改变this指向一次
function fn(...args){console.log(this,args);
}
let obj = {myname:"张三"
}fn.call(obj,1,2); // this会变成传入的obj,传入的参数必须是一个数组;
fn(1,2) // this指向window
同样的,当第一个参数为null、undefined的时候,默认指向window(在浏览器中)
fn.call(null,[1,2]); // this指向window
fn.call(undefined,[1,2]); // this指向window