昨天面试,本来一切准备的稳稳当当,但却被面试官的几道this指向题目和箭头函数的this指向题目问到懵。 原本一般函数的this指向我已经学会按照四种绑定方式套入来解题了,但是面试官把箭头函数一加进去,给我整紧张了,开始连蒙带猜地回答(也是怪自己掌握的不够透彻)。 于是,我决定好好地总结一波,起码下次不需要重新把已经看过的东西回头再看一遍,节省本就不多的时间,多去学习没遇见过的新知识。
对于一般的函数,在判断this之前,可以先判断其绑定方式,总的来说有四种,分别进行介绍。
- 默认绑定(一般是函数自调用) 最简单的情况
function sayName(){
console.log('Hello,', this.name);
}
var name = 'Puff';
sayName();
这里很明显是函数的自调用,this是window,那么window.name就是Puff啦
复杂一丢丢:函数内部的自调用
var obj = {
foo: function () {
function test() {
console.log(this);
}
test();
}
}
obj.foo()
这里看到obj.foo()差点以为是隐式绑定,但是仔细观察一下,发现打印的this是test函数当中的,而且test是在foo被执行时进行自调用执行的,因此,这里的this也是window啦
- 隐式绑定 (需要考虑一下有没有隐式丢失情况) 隐式绑定通常指函数作为对象的一个属性被调用,常见的形式是XXX.fun() 简单的情况:
function sayName(){
console.log('Hello,', this.name);
}
var person = {
name: 'Puff',
sayHi: sayHi
}
var name = 'Window';
person.sayName();
这里sayName函数是person对象的属性,并且通过对象.属性的方式进行调用执行,那么毫无疑问,sayName中的this就是person对象,输出就是Puff啦
这里需要注意,如果将person.sayName赋给一个变量,让该变量作为函数名来自调用,此时就是隐式丢失,那么sayName函数里面的this就和person毫无关系了,仅相当于这个sayName函数的自调用,就与默认绑定规则类似。
function sayName(){
console.log('Hello,', this.name);
}
var person = {
name: 'Puff',
sayName: sayName
}
var name = 'Window';
var other = person.sayName;
other();
呐,这个时候打印的this.name呢,就是Window啦
接着变形一下,与上面的例子类似,都是函数赋给变量后再自执行,下面例子是函数作为参数被赋值的情况
var a = 0
function foo() {
console.log(this)
}
function bar(fn) {
fn()
}
var obj = {
a: 2,
foo: foo
}
bar(obj.foo)
这里面obj.foo函数作为参数传给了形参,然后自调用,所以打印的就是window啦 题外话:那么这种情况下,有没有办法把this拉回obj呢,可以滴:
var a = 0
function foo() {
console.log(this)
}
function bar(fn) {
// fn()
fn.bind(obj)()
}
var obj = {
a: 2,
foo: foo
}
bar(obj.foo)
这样在bar函数当中可以给fn一个新的调用对象,打印的就是obj对象啦
- 显示绑定/硬绑定(call、apply、bind) 最简单的情况
function sayName(){
console.log('Hello,', this.name);
}
var person = {
name: 'Puff',
sayName: sayName
}
var name = 'Window';
var Hi = person.sayName;
Hi.call(person); //Hi.apply(person)
这是最简单的情况哈,this就等于call传入的参数对象。 我们都知道call的参数列表中第一个是对象,后面是参数,但是如果第一个参数不是对象呢,会发生什么?
- 是number/string/bolean则会转为其包装类对象
- 是null/undefined,由于这两没有包装类,所以会绑定失败
-
new 绑定
function sayName(name){
this.name = name;
}
var Hi = new sayName('Puff');
console.log('Hello,', Hi.name);
输出结果为 Hello, Puff, 原因是因为在var Hi = new sayName('Puff');这一步,会将sayName中的this绑定到Hi对象上。
以上四种绑定方式的优先级是依次递增的。
另外:你知道forEach、sort、setInterval函数参数中的回调函数里的this是指向哪个对象的吗? 通过MDN中的api查询,可以发现forEach可以传入一个对象,来决定里面的函数的this指向的,否则就是全局对象对象。
箭头函数本身是没有this的,看其父级作用域的this是什么,那它的this就是什么
var name = 'window'
var obj1 = {
name: '1',
fn1: function () {
console.log(this.name)
},
fn2: () => console.log(this.name),
fn3: function () {
return function () {
console.log(this.name)
}
},
fn4: function () {
return () => console.log(this.name)
}
}
var obj2 = {
name: '2'
}
obj1.fn1();
obj1.fn1.call(obj2);
obj1.fn2();
obj1.fn2.call(obj2);
obj1.fn3()();
obj1.fn3().call(obj2);
obj1.fn3.call(obj2)();
obj1.fn4()();
obj1.fn4().call(obj2);
obj1.fn4.call(obj2)();
这套题包含了上面的大部分知识点,看看准确率怎么样 答案:
var name = 'window'
var obj1 = {
name: '1',
fn1: function () {
console.log(this.name)
},
fn2: () => console.log(this.name),
fn3: function () {
return function () {
console.log(this.name)
}
},
fn4: function () {
return () => console.log(this.name)
}
}
var obj2 = {
name: '2'
}
obj1.fn1(); //1
obj1.fn1.call(obj2); //2
obj1.fn2(); //window
obj1.fn2.call(obj2); //window
obj1.fn3()(); //window
obj1.fn3().call(obj2); //2
obj1.fn3.call(obj2)(); //window
obj1.fn4()(); //1
obj1.fn4().call(obj2); //1
obj1.fn4.call(obj2)(); //2