this到底指向谁
- 在函数体中,非显式或隐式地简单调用函数时,在严格模式下,函数内的this会被绑定到undefined上,在非严格模式下则会被绑定到全局对象window/global上。
- 一般使用new方法调用构造函数时,构造函数内的this会被绑定到新创建的对象上。
- 一般通过call/apply/bind方法调用函数时,函数体内的this会被绑定到指定参数的对象上。
- 一般通过上下文对象调用函数时,函数体内的this会被绑定到该对象上。
- 在箭头函数中,this的指向是由外层(函数或全局)作用域来决定的。
实战例题
例题组合1:全局环境中的this
function f1(){
console.log(this)
}
function f2(){
'use strict'
console.log(this)
}
f1()//window
f2()//undefined
函数在浏览器全局环境中被简单调用,在非严格模式下this指向window,在通过use strict指明严格模式的情况下指向undefined。
const foo = {
bar:10,
fn:function(){
console.log(this);
console.log(this.bar);
}
}
var fn1 = foo.fn;
fn1();//window,undefined
这里的this仍然指向window。虽然fn函数在foo对象中用来作为对象的方法,但是在赋值给fn1之后,fn1仍然在window的全局环境中执行的。因此,以上代码会输出window和undefined,其输出结果与以下语句等价
console.log(window);//window
console.log(window.bar);//undefined
const foo = {
bar:10,
fn:function(){
console.log(this);
console.log(this.bar);
}
}
foo.fn()//{bar:10,fn:f},10
这时,this指向的是最后调用它的对象,在foo.fn()语句中,this指向foo对象。
例题组合2:上下文对象调用中的this
const student = {
name:'jieye',
fn:function(){
return this;
}
}
console.log(student.fn() === student);//true
当存在更复杂的调用关系时,如以下代码中的嵌套关系,this会指向最后调用它的对象,因此输出将会是Mike。
const person = {
name:'jieye',
brother:{
name:'Mike',
fn:function(){
return this.name;
}
}
}
console.log(person.brother.fn());//Mike
const o1 = {
text:'o1',
fn:function(){
return this.text;
}
}
const o2 = {
text:'o2',
fn:function(){
return o1.fn();
}
}
const o3 = {
text:'o3',
fn:function(){
var fn = o1.fn;
return fn();
}
}
console.log(o1.fn());//o1
console.log(o2.fn());//o1
console.log(o3.fn());//undefined
如果我们需要让console.log(o2.fn())语句输出o2,该怎么做?(不使用bind、call、apply)
const o1 = {
text:'o1',
fn:function(){
return this.text;
}
}
const o2 = {
text:'o2',
fn:o1.fn
}
console.log(o2.fn())
例题组合3:通过bind、call、apply改变this指向
bind、call、apply它们都是用来改变相关函数this指向的,但是call、apply是直接进行相关函数调用的;bind不会执行相关函数,而是返回一个新的函数,这个新的函数已经自动绑定了新的this指向,开发者可以手动调用它。而call和apply的区别主要体现在参数的设定上。
以下3段代码是等价的:
//1
const target = {};
fn.call(target,'arg1','arg2');
//2
const target = {};
fn.apply(target,['arg1','arg2']);
//3
const target = {};
fn.bind(target,'arg1','arg2'){}
const foo = {
name:'jieye',
logName:function(){
console.log(this.name);
}
}
const bar = {
name:'Mike'
}
console.log(foo.logName.call(bar));//Mike
例题组合4:构造函数和this
先看一道最直接的例题:
function Foo(){
this.bar = 'jieye',
}
const instance = new Foo();
console.log(instance.bar);//jieye
new操作符调用构造函数时具体做了什么?
- 创建一个新的对象;
- 将构造函数的this指向这个新的对象;
- 为这个对象添加属性、方法等;
- 最终返回新的对象。
上述过程也可以用如下代码表述
var obj = {};
obj._proto_ = Foo.prototype;
Foo.call(obj);
如果在构造函数中出现了显式return的情况,那么需要注意,其可以细分为两种场景:
场景1:执行以下代码将输出undefined,此时instance返回的是空对象o。
function Foo(){
this.user = 'jieye';
const o = {};
return o;
}
const instance = new Foo();
console.log(instance.user);//undefined
场景2:执行以下代码将输出jieye,也就是说,instance此时返回的是目标对象实例this。
function Foo(){
this.user = 'jieye';
return 1;
}
const instance = new Foo();
console.log(instance.user)
所以,如果构造函数中显式返回一个值,且返回的是一个对象(返回复杂类型),那么this就指向这个返回的对象;如果返回的不是一个对象(返回基本类型),那么this仍然指向实例。
例题组合5:箭头函数中的this
下面来看一段示例代码。在这段代码中,this出现在setTimeout()的匿名函数中,因此this指向window对象。
const foo = {
fn:function(){
setTimeout(function(){
console.log(this)
})
}
}
console.log(foo.fn())
如果需要让this指向foo这个对象,则可以巧用箭头函数来解决,代码如下:
const foo = {
fn:function(){
setTimeout(()=>{
console.log(this)
})
}
}
console.log(foo.fn())//{fn:f}
例题组合6:this优先级
我们常常把通过call、apply、bind、new对this进行绑定的情况称为显示绑定,而把根据调用关系确定this指向的情况称为隐式绑定。
function foo(){
console.log(this.a);
}
const obj1 = {
a:1,
foo:foo
}
const obj2 = {
a:2,
foo:foo
}
obj1.foo.call(obj2);//2
obj2.foo.call(obj1);//1
输出结果为2、1,也就是说,call、apply的显示绑定一般来说优先级更高。
function(){
this.a = a;
}
const obj1 = {};
var bar = foo.bind(obj1);
bar(2);
console.log(obj1.a);
上述代码通过bind将bar函数中的this绑定为obj1对象。执行bar(2)后,obj1.a的值为2,即执行bar(2)后,obj1对象为{a:2};
当再使用bar作为构造函数时,例如执行以下代码,则会输出3
var baz = new bar(3);
console.log(baz.a);
bar函数本身是通过bind方法构造的函数,其内部已经将this绑定为obj1,当它再次作为构造函数通过new被调用时,返回的实例就已经与obj1解绑了。也就是说,new绑定了bind绑定中的this指向。因此new绑定的优先级比显示bind绑定的更高。
function foo(){
return a =>{
console.log(this.a)
}
}
const obj1 = {
a:2
}
const obj2 = {
a:3
}
const bar = foo.call(obj1);
console.log(bar.call(obj2));
以上代码的输出结果为2,由于foo中的this绑定到了obj上,所以bar(引用箭头函数)中的this也会绑定到obj1上,箭头函数的绑定无法被修改。
如果将foo完全写成如下所示的箭头函数的形式,则会输出123
var a = 123;
const foo = ()=> a=> {
console.log(this.a);
}
const obj1 = {
a:2
}
const obj2 = {
a:3
}
var bar = foo.call(obj1);
console.log(bar.call(obj2));
如果在把上述代码中的第一处变量a的声明修改一下:
const a = 123;
const foo = ()=> a=> {
console.log(this.a);
}
const obj1 = {
a:2
}
const obj2 = {
a:3
}
var bar = foo.call(obj1);
console.log(bar.call(obj2));
答案为undefined,原因是使用const声明的变量不会挂载到window全局对象上。因此,this指向window时,自然也找不到a变量了。
开放例题分析
实现一个bind函数
Function.prototypr.bind = Function.prototype.bind || function(context){
var me = this;
var args = Array.prototype.slice.call(arguments,1);
return function bound(){
var innerArgs = Array.prototype.slice.call(arguments);
var finalArgs = args.concat(innerArgs);
return me.apply(context,finalArgs);
}
}