JS之this指针面试题

再说this

在之前的文章中讲了this指针的绑定方式—JavaScript之this指针,这次通过几道题来巩固一下前面所学的内容

this指针绑定分为默认绑定、隐式绑定、显式绑定(call、apply、bind)、new绑定。并且其优先级为 new绑定 > 显式绑定(bind) > 隐式绑定 > 默认绑定

默认绑定

默认绑定就是函数调用时的this默认绑定window,箭头函数除外,函数可以访问window下的全局变量。

但是也有例外情况:

  1. 在非严格模式下,函数中的this是指向的是window
  2. 在严格模式下,函数中的this指向的是undefined,但是全局中的this还是指向window
  3. 使用let、const来定义变量并不会绑定到window中
//非严格模式下,函数中的this指向window
var a = 1;
function bar(){
    console.log(this);  //window
    console.log(this === window); //true
    console.log(this.a); //1
}
bar();
console.log(this); //window
console.log(window); //window
console.log(this.a); //1
console.log(window.a); //1
//严格模式下,函数中的this指向undefined
'use strict';
var a = 1;
function bar(){
    console.log(this);  //undefined
    console.log(this === window); //false
    console.log(this.a); //报错
}
//全局中的this还是指向window
console.log(this); //window
console.log(window); //window
console.log(this.a); //1
console.log(window.a); //1
bar();
//使用let或const来定义变量,window找到不到该变量,因此为undefined
let a = 1;
function bar(){
    console.log(this);  //window
    console.log(this === window); //true
    console.log(this.a); //undefined
}
bar();
console.log(this); //window
console.log(window); //window
console.log(this.a); //undefined
console.log(window.a); //undefined

函数不管套了几层,只要不是箭头函数,默认绑定的this始终是window

//由于函数中的this指向的是window,因此this.a还是全局作用域下的a,但在函数内部直接打印a的话,是使用内部定义的a,因为作用域会由里向外查找,当在内部作用域中找打a时,就停止查找了
var a = 1;
function bar(){
    var a = 11;
    console.log(a); //11
    console.log(this.a); //1
    //普通函数调用时默认指向始终指向window的,因此inner函数中的this还是window
    function inner(){
        //直接打印a的话,由于inner中没有d定义a,所以会在bar函数的作用域中查找,找到后就停止查找了,因此输出的还是11
        console.log(a); //11
        console.log(this.a); //1
    }
    inner();
}
bar();

隐式绑定

隐式绑定是指若有对象调用函数,函数中的this就会隐式的绑定到对象上。

隐式绑定的规则是:this始终指向函数被调用前的最近的对象。因为可能有嵌套子对象,这时哪个对象最后调用函数,那函数就指向谁。

function foo() {
    console.log(this.name); // obj1
}
var obj1 = {
    name: "obj1",
    foo: foo
};
var obj2 = {
    name: "obj2",
    obj1: obj1
};
obj2.obj1.foo();  //obj1,因此obj1对象是最后调用的

隐式绑定也会出现一些问题:

  1. 在函数赋值给变量时,会丢失函数原来绑定对象
//将函数赋值给另一个变量时就会丢失绑定对象
//通过赋值方式获取到的函数将会丢失原来的this指针
function foo () {
    console.log(this.a)
}
var obj = { a: 1, foo };
var a = 2;
var foo2 = obj.foo;
obj.foo(); //1
foo2(); //2  foo绑定的obj对象已丢失,因此foo2中的this指向window
  1. 将函数作为参数传递时也会丢失绑定对象
function foo(){
    console.log(this); 
    console.log(this.a); 
}
function callBack(fun){
    //var fun = fun; //其实已经隐式的做了一次变量赋值
    console.log(this);
    fun();
}

var obj = {a:1, foo: foo}
var a = 11;
//回调函数obj.foo的this指针指向window,不是因为callBack指向window,而是其本身指向window
callBack(obj.foo); //依次打印window, window,11

var obj2 = {a: 111, callBack: callBack}
//回调函数内的参数函数中的this与回调函数没有关系,两者的this互不影响,参数函数中的this指向方式与默认绑定规则一致
obj2.callBack(obj.foo); //依次打印obj2对象、window、11

因此,当隐式绑定时,函数赋值给另一个变量会丢失原来绑定对象,或函数作为参数传入另一个函数中时也会丢失原来对象,并且this指向遵循默认绑定规则

显式绑定

显式绑定是通过call、apply、bind来强行改变函数的this指针

  1. call接收一个参数列表,apply接收一个参数数组
  2. 使用call和apply,函数会直接执行,而bind是先创建一个函数,调用之后才会执行
function foo () {
    console.log(this.a)
}
var obj = { a: 1 };
var a = 11;

foo(); //11
foo.call(obj);  //1
foo.apply(obj);  //1
foo.bind(obj)  //不会执行,只是创建了一个函数,没有调用
//foo.bind(obj)();  //这样才会执行 
var obj1 = {
    a: 1
};
var obj2 = {
    a: 2,
    foo1: function () {
        console.log(this.a);  //2
    },
    foo2: function () {
        function inner () {
            console.log(this);  //window
            console.log(this.a); //3
        }
        inner(); //inner函数依次打印window,3
        inner.call(obj1); //inner函数依次打印obj1对象,1
    }
};
var a = 3;
obj2.foo1();
obj2.foo2();  
//笔试题1
function foo () {
  console.log(this.a)
}
var obj = { a: 1, foo };
var a = 2;
var foo2 = obj.foo;
var obj2 = { a: 3, foo2: obj.foo }

obj.foo(); //1,隐式转换
foo2(); //2,发生了隐式丢失,这是this指向window
obj2.foo2(); //3,发生了隐式丢失,然后发生了隐式转化,this指向obj2

//笔试题2
function foo () {
  console.log(this.a)
}
var obj = { a: 1 };
var a = 2;
foo(); //2
foo.call(obj); //1
foo().call(obj); //2 报错   先执行foo()打印出2,然后call报错,因为foo()的返回值不是一个函数了,若foo()返回一个函数,将会绑定到obj

//笔试题3
function foo () {
  console.log(this.a);
  return function () {
    console.log(this.a);
  }
}
var obj = { a: 1 };
var a = 2;
//bind()函数是创建了一个新函数,但是并没有执行该函数
foo(); //2
foo.bind(obj);  //定义函数但未执行
foo().bind(obj); //2 ——foo()执行了,但是后面的绑定函数未执行
//笔试题4
function foo () {
  console.log(this.a)
  returnfunction () {
    console.log(this.a)
  }
}
var obj = { a: 1 };
var a = 2;
//先执行foo.call(obj)输出1,并返回一个匿名函数,然后再执行匿名函数,此时匿名函数是指向window的
foo.call(obj)(); //先输出1,然后是2
//题5
var obj = {
  a: 'obj',
  foo: function () {
    console.log('foo:', this.a)
    return function () {
      console.log('inner:', this.a)
    }
  }
};
var a = 'window';
var obj2 = { a: 'obj2' };
obj.foo()(); //foo:obj   foo:window
obj.foo.call(obj2)(); //foo:obj2   foo:window
obj.foo().call(obj2); //foo: obj  foo:obj2

//题6
function foo() {
  console.log(this.a);
}
var a = 1;
var obj1 = {
  a: 2,
};
var obj2 = {
  a:3,  
};
//call、apply、bind一旦使用,this就不再改变,但是这里好像不行,还有待研究
foo(); //1
foo.call(obj1); //2
foo.call(obj2); //3

new绑定

new绑定会构造一个新对象并把这个新对象绑定到调用函数中的this

//使用new来调用Person,构造了一个新对象person,并把person绑定到Person调用中的this
function Person (name) {
  this.name = name;
}
var name = 'window';
var persion = new Person('simon');
console.log(person1.name); //simon

//其实原理与上面的例子是一样的当函数返回值为一个函数时,这是返回函数this指向window
function Person (name) {
    this.name = name;
    this.foo1 = function () {
        console.log(this.name);
    };
    this.foo2 = function () {
        return function () {
            console.log(this.name);
        }
    }
}
var name = 'window';
var person1 = new Person('object');
person1.foo1(); //object
person1.foo2()(); //window

箭头函数

箭头函数是ES6的新函数类型,与普通函数不同的是,箭头函数本身没有this指针的,必须通过查找作用域来决定,如果箭头函数被非箭头函数包含,则this绑定的是最近一层非箭头函数的this,否则,this为undefined。并且,是指向函数定义时的this而不是执行时的

var obj = {
  name: 'obj',
  foo1: () => {
    console.log(this.name)
  },
  foo2: function () {
    console.log(this.name)
    return ()=> {
      console.log(this.name)
    }
  }
};
var name = 'window';
obj.foo1(); //window
obj.foo2()(); //obj obj

//this由外层作用域决定,且指向函数定义时的this而非执行时
var name = 'window';
function Person (name) {
   //字面量写法
  this.name = name
  this.foo1 = function () {
    console.log(this.name);
  }
  this.foo2 = () => {
    console.log(this.name);
  }
}
var person2 = {
  name: 'person2',
  foo2: () => {
    console.log(this.name)
  }
}
var person1 = new Person('person1');
person1.foo1(); //person1
person1.foo2(); //person1,因为new Person后,person1的this指向了Person了
person2.foo2(); //window,没有使用new操作符,因为箭头函数this向外查找,找到window
//使用call、apply、bind无法改变箭头函数的this指向,但可以改变作用域中的this来间接修改
var name = 'window';
var obj1 = {
  name: 'obj1',
  foo1: function () {
    console.log(this.name);
    return() => {
      console.log(this.name);
    }
  },
  foo2: () => {
    console.log(this.name);
    return function () {
      console.log(this.name);
    }
  }
}
var obj2 = {
  name: 'obj2',
}
obj1.foo1.call(obj2)(); //obj2 obj2-call改变了foo1的this指向,内部的箭头函数也会跟着改
obj1.foo1().call(obj2); //obj1 obj1-call无法改变箭头函数的this指向
obj1.foo2.call(obj2)(); //window window-call函数对箭头函数无效
obj1.foo2().call(obj2); //window obj2-foo2箭头函数内部是一个普通函数,因此能够改变this指向

总结:

  1. 它里面的this是由外层作用域来决定的,且指向函数定义时的this而非执行时
  2. 字面量创建的对象,作用域是window,如果里面有箭头函数属性的话,this指向的是window
  3. 构造函数创建的对象,作用域是可以理解为是这个构造函数,且这个构造函数的this是指向新建的对象的,因此this指向这个对象
  4. 箭头函数的this是无法通过bind、call、apply直接修改,但是可以通过改变作用域中this的指向来间接修改

综合题

var name = 'window';
var person1 = {
  name: 'person1',
  foo1: function () {
    console.log(this.name);
  },
  foo2: () =>console.log(this.name),
  foo3: function () {
    return function () {
      console.log(this.name);
    }
  },
  foo4: function () {
    return() => {
      console.log(this.name);
    }
  }
}
var person2 = { name: 'person2' };

person1.foo1(); // 'person1'
person1.foo1.call(person2); // 'person2'

person1.foo2(); // 'window'
person1.foo2.call(person2); // 'window'-无法改变箭头函数this指向

person1.foo3()(); // 'window'
person1.foo3.call(person2)(); // 'window'
person1.foo3().call(person2); // 'person2'

person1.foo4()(); // 'person1'
person1.foo4.call(person2)(); // 'person2'
person1.foo4().call(person2); // 'person1'
var name = 'window'
function Person (name) {
  this.name = name
  this.obj = {
    name: 'obj',
    foo1: function () {
      return function () {
        console.log(this.name)
      }
    },
    foo2: function () {
      return() => {
        console.log(this.name)
      }
    }
  }
}
var person1 = new Person('person1')
var person2 = new Person('person2')

person1.obj.foo1()() // 'window'-普通函数this始终指向window
person1.obj.foo1.call(person2)() // 'window'-最后也是一个普通函数
person1.obj.foo1().call(person2) // 'person2'-改变了this的指向

person1.obj.foo2()() // 'obj'-根据外部作用域的this指向来决定箭头函数的this
person1.obj.foo2.call(person2)() // 'person2'
person1.obj.foo2().call(person2) // 'obj'

参考链接: https://mp.weixin.qq.com/s/YhV6A8dsU_sAyjfZTJevIQ

猜你喜欢

转载自blog.csdn.net/qq_31947477/article/details/106156073