this指向问题 题目&题解

88 阅读8分钟

写在前面:本帖用于this指向问题的编程题目,验证阶段性的学习成果,欢迎大家批评指正!

知识点:

  • this指向问题要与原型/方法等知识点进行联系,需要针对不同情况进行学习。
  • 箭头函数中的this会将上层的this继承下来,指向其最近的外层函数作用域的 this 所指对象
  • 对象不构成单独的作用域。
  • 最外层的执行对象是全局作用域window
  • 立即执行函数的执行对象是window
  • 调用其他函数时,若没有特殊指定,默认执行对象是window
  • 严格模式下与非严格模式下有区别,注意区分

apply方法

  • 作用,改变this指向,立即执行
  • 参数会放在数组中,调用方式为 fun.apply(obj,[param1,param2,...])

call方法

  • 作用,改变this指向,立即执行
  • 参数以逗号分隔,调用方式为 fun.apply(obj,param1,param2,...)

bind方法

  • 作用,改变this指向,需要再次调用
  • fun.bind(obj,param1,param2,...);
  • 多次bind只会使用第一次bind的结果。

new方法

  • 作用,使用一个构造函数,创建一个新的实例对象并返回
  • 工作流程,新建一个实例,将构造函数的this指向新的实例并执行,返回这个实例。

第1题:

知识点:函数嵌套调用

function foo() {
  console.log( this.a );
}

function doFoo() {
  foo();
}

var obj = {
  a: 1,
  doFoo: doFoo
};

var a = 2; 
obj.doFoo()

2

在执行doFoo函数时会调用foo方法,在调用其他方法时且未特殊指定,this指向会全局,故打印this.a时会打印全局的a的值,即14行的2.

第2题

知识点:箭头函数

var a = 10
var obj = {
  a: 20,
  say: () => {
    console.log(this.a)
  }
}
obj.say() 

var anotherObj = { a: 30 } 
obj.say.apply(anotherObj) 

10
10

此题涉及到箭头函数,执行上下文是window->obj.say,say箭头函数的this绑定到了父级所处的上下文,即window对象。

当执行8时,打印window.a 10

当执行11时,window->obj.say,this指向父级window,无法被bind改变,打印window.a 10,this指向不会改变。

变形1,箭头函数变为普通函数

var a = 10
var obj = {
  a: 20,
  say(){
    console.log(this.a)
  }
}
obj.say() 

var anotherObj = { a: 30 } 
obj.say.apply(anotherObj) 

20
30

普通函数的this指向会发生改变

8:window->obj.say,this指向obj,打印obj.a 20

11:window->obj.say,apply改变了this指向,其上下文改变成了window->anotherObj.say,打印anotherObj.a 30

变形2,对象中调用外层函数

var a = 10
foo(){
    console.log(this.a)
}
var obj = {
  a: 20,
  say: foo
}
obj.say() 

var anotherObj = { a: 30 } 
obj.say.apply(anotherObj) 

20
30

正常输出,结果不变,

9:执行上下文是window->obj.say,this指向obj,打印obj.a

12:window->obj.say,apply改变this,window->anotherObj.say,打印anotherObj.a

但是,如果改为函数嵌套的形式,就变为了对象的方法中调用外层的方法,执行上下文就是window对象

var a = 10
function foo(){
    console.log(this.a)
}
var obj = {
  a: 20,
  say(){
      foo();
  }
}
obj.say() 

var anotherObj = { a: 30 } 
obj.say.apply(anotherObj)  

10
10

11:window->obj.say,函数中调用了foo方法,this指向变为了window,最终打印window.a 10

14: window->obj.say通过apply改变了上下文,window->anotherObj.say,但最后函数中调用了foo方法,this指向变为了window,最终打印window.a 10

如果此时的foo方法是箭头函数呢?

var a = 10
let foo = ()=>{
    console.log(this.a)
}
var obj = {
  a: 20,
  say(){
      foo();
  }
}
obj.say() 

var anotherObj = { a: 30 } 
obj.say.apply(anotherObj)  

10
10

第3题

知识点:new调用

function foo(){
    console.log(this.name); 
}

var obj = { 
  name: 'cuggz', 
  fun: foo
} 
obj.fun()     // cuggz
new obj.fun() // undefined

第九行的打印比较好理解,关键在于new关键字,会将fun作为构造函数来使用,会将foo的this绑定在新的object实例对象中,由于新建的实例并没有name属性,故打印undefined

第4题

知识点:对象嵌套调用

var obj = {
   say: function() {
     var f1 = () =>  {
       console.log("1111", this);
     }
     f1();
   },
   pro: {
     getPro:() =>  {
        console.log(this);
     }
   }
}
var o = obj.say;
o();
obj.say();
obj.pro.getPro();

1111 window对象
1111 obj对象
window对象

15:window.say->f1,f1是箭头函数,this指向父级的上下文window

16:window->obj.say->f1,f1指向父级的obj,this指向obj

17:window->obj.pro.getPro,getPro箭头函数this指向外层的上下文,外层是pro对象,但是对象不构成上下文,所以this绑定在全局上下文window中。

如果f1是普通函数呢?

var obj = {
   say: function() {
     var f1 = function (){
       console.log("1111", this);
     }
     f1();
   },
   pro: {
     getPro:() =>  {
        console.log(this);
     }
   }
}
var o = obj.say;
o();
obj.say();
obj.pro.getPro();

1111 window对象
1111 window对象
window对象

可以看到,由于在函数中调用函数,this指向全局对象window

变形1,在函数内部调用新的函数

var obj = {
   say: function() {
     var f1 = () =>  {
       console.log("1111", this);
     }
     f1();
   },
   pro: function(){
     let getPro = function(){
        console.log(this);
     }
     getPro();
   }
}
var o = obj.say;
o();
obj.say();
obj.pro();

1111 window对象
1111 obj对象
window对象

pro方法中调用getPro,上下文默认是window

变形2:在函数内部调用箭头函数

var obj = {
   say: function() {
     var f1 = () =>  {
       console.log("1111", this);
     }
     f1();
   },
   pro: function(){
     let getPro = ()=>{
        console.log(this);
     }
     getPro();
   }
}
var o = obj.say;
o();
obj.say();
obj.pro();

1111 window对象
1111 obj对象
obj对象

window->obj.pro.getPro,getPro的this指向在调用时确定,指向父级window

第5题

知识点:立即执行函数

var myObject = {
    foo: "bar",
    func: function() {
        var self = this;
        console.log(this.foo);  
        console.log(self.foo);  
        (function() {
            console.log(this.foo);  
            console.log(self.foo);  
        }());
    }
};
myObject.func();

bar bar undefined bar

13:window->myObject.func,func是普通函数,this指向myObject,此时的this和self指向myObject,打印bar

当执行立即执行函数时,this指向window,打印undefined,而self依旧指向myObject打印bar

当立即执行函数是一个箭头函数呢?

var myObject = {
    foo: "bar",
    func: function() {
        var self = this;
        console.log(this.foo);  
        console.log(self.foo);  
        (() => {
            console.log(this.foo);  
            console.log(self.foo);  
        })();
    }
};
myObject.func();

bar bar bar bar

看来箭头函数还是那么强大,此时的this并不是默认指向window全局对象。Window->myObject.func->立即执行,立即执行函数构成了单独的作用域,使用箭头函数则跳过了单独的作用域,指向父级的myObject,最后this和self都指向myObject.

第6题

知识点:立即执行函数

window.number = 2;
var obj = {
 number: 3,
 db1: (function(){
   console.log(this);
   this.number *= 4;
   return function(){
     console.log(this);
     this.number *= 5;
   }
 })()
}
var db1 = obj.db1;
db1();
obj.db1();
console.log(obj.number);     // 15
console.log(window.number);  // 40

13:db1指向立即执行函数返回的函数.立即执行函数中的this指向全局作用域window,window.number*=4,变为8

14:执行db1,相当于在全局作用域下执行window.db1,this指向window,则window.number*=5,变为40

15:window->obj.db1,this指向obj,obj.number*=5,变为15

16:打印obj.number 15

17:打印window.number 40

变形1,立即执行函数返回的是箭头函数

window.number = 2;
var obj = {
 number: 3,
 db1: (function(){
   // console.log(this);
   this.number *= 4;
   return ()=>{
     // console.log(this);
     this.number *= 5;
   }
 })()
}
var db1 = obj.db1;
db1();
obj.db1();
console.log(obj.number);     // 3
console.log(window.number);  // 200

13:跟之前分析一致,立即执行函数中this指向全局作用域,window,window.number*=4,变为8

14:跟之前分析一致,相当于在全局作用域下执行window.db1,this指向window,则window.number*=5,变为40

15:window->obj.db1,由于是箭头函数,this指向父级的Window,window.number*=5,变为200

16:打印obj.number 3

17:打印window.number 200

第7题

知识点:arguments参数列表

var length = 10;
function fn() {
    console.log(this.length);
}
 
var obj = {
  length: 5,
  method: function(fn) {
    fn();
    arguments[0]();
  }
};
 
obj.method(fn, 1);

14:window->obj.method,由于函数内部调用了fn(),且fn是普通函数,默认执行上下文是window,打印window.length 10

10:arguments的下标0是fn方法,下标1是参数1,总共有两个参数,调用方法时,this指向arguments,打印arguments.length 2

第8题

知识点:函数嵌套调用

var a = 1;
function printA(){
  console.log(this.a);
}
var obj={
  a:2,
  foo:printA,
  bar:function(){
    printA();
  }
}

obj.foo(); // 2
obj.bar(); // 1
var foo = obj.foo;
foo(); // 1

13:window->obj.printA,由于printA函数是普通函数,打印obj.a 2

14:window->obj.bar,在bar函数中调用了printA函数,默认执行上下文是window,this指向window,打印window.a 1

16:默认执行上下文是window,this指向window,打印window.a 1

第9题

知识点:立即执行函数

var x = 3;
var y = 4;
var obj = {
    x: 1,
    y: 6,
    getX: function() {
        var x = 5;
        return function() {
            return this.x;
        }();
    },
    getY: function() {
        var y = 7;
        return this.y;
    }
}
console.log(obj.getX()) // 3
console.log(obj.getY()) // 6

17:window->obj.getX,由于getX最后返回的是立即执行函数,所以需要先计算。在立即执行函数中,this默认指向window,返回window.x 3

18:window->obj.getY,getY是普通函数,this指向obj,最后返回obj.y 6

第10题

知识点:立即执行函数

 var a = 10; 
 var obt = { 
   a: 20, 
   fn: function(){ 
     var a = 30; 
     console.log(this.a)
   } 
 }
 obt.fn();  // 20
 obt.fn.call(); // 10
 (obt.fn)(); // 20

9:window->obt.fn,fn是普通函数,this指向obt,打印obt.a 20

10:call不传递值时默认是window,则打印window.a 10

11:加了括号,相当于是obt.fn();依旧打印obt.a 20

第11题

知识点:全局变量冲突

function a(xx){
  this.x = xx;
  return this
};
var x = a(5);
var y = a(6);

console.log(x.x)  // undefined
console.log(y.x)  // 6

8:执行5时,执行上下文window.a,this指向window,window.x=5,返回window。此时var x = a(5)相当于window.x=window,此处有些绕,大家仔细回味一下。window.x指向window,x.x==window.x.x

x===x.x===window.x 都指向window

9:执行6时,执行上下文window.a,this指向window,window.x=6,返回window,此时y指向window

最后打印结果时,x的值已经被改变为5,y指向window,所以x.x指向undefined,y.x为6

第12题

知识点:new函数

function foo(something){
    this.a = something
}

var obj1 = {
    foo: foo
}

var obj2 = {}

obj1.foo(2); 
console.log(obj1.a); // 2

obj1.foo.call(obj2, 3);
console.log(obj2.a); // 3

var bar = new obj1.foo(4)
console.log(obj1.a); // 2
console.log(bar.a); // 4

11:window->obj1.foo,this指向obj1,obj1.a 2

14:call改变了foo函数的指向,同理,obj2.a 3

17:new函数会暂时将foo方法绑定在新创建的实例上,为bar添加a属性,值为4。obj1本身的值不会改变

第13题

知识点:

function foo(something){
    this.a = something
}

var obj1 = {}

var bar = foo.bind(obj1);
bar(2);
console.log(obj1.a); // 2

var baz = new bar(3);
console.log(obj1.a); // 2
console.log(baz.a); // 3

7:bind函数会绑定函数到该对象上,返回新的函数,此时bar的this指向obj1

8:window->obj1.foo,obj1.a 2

11:new暂时将this指向新创建的baz实例上,为baz添加新的属性a 3

12:obj1.a不受影响 2

13:baz.a 3

此时的foo的this指向哪里呢?

function foo(so){
    this.a = so
}

var obj1 = {}

var bar = foo.bind(obj1);
bar(2);
console.log(obj1.a); // 2

bar(3);
console.log(obj1.a); // 3
var baz = new bar(3);
console.log(obj1.a); // 3
console.log(baz.a); // 3

bar(4);
console.log(obj1.a); // 4

可见,foo的this依旧指向obj1