如果 new 关键词出现在被调用函数的前面,那么JavaScript引擎会创建一个新的对象,被调用函数中的this指向的就是这个新创建的对象。
如果通过apply、call或者bind的方式触发函数,那么函数中的this指向传入函数的第一个参数。
如果一个函数是某个对象的方法,并且对象使用句点符号触发函数,那么this指向的就是该函数作为那个对象的属性的对象,也就是,this指向句点左边的对象。
如果一个函数作为FFI被调用,意味着这个函数不符合以上任意一种调用方式,this指向全局对象,在浏览器中,即是window。
如果出现上面对条规则的累加情况,则优先级自1至4递减,this的指向按照优先级最高的规则判断。
相关实例:
1、全局环境下的 this
function f1() {
console.log(this);
}
function f2() {
;
console.log(this);
}
f1(); // window
f2(); // undefine
const foo = {
bar: 10,
fn: function () {
console.log(this);
console.log(this.bar);
},
};
var fn1 = foo.fn;
fn1(); // window undefine
这里 this 仍然指向的是 window。虽然 fn 函数在 foo 对象中作为方法被引用,但是在赋值给 fn1 之后,fn1 的执行仍然是在 window 的全局环境中。因此输出 window 和 undefined,它们相当于:
console.log(this)
console.log(window.bar)
还是上面这道题目,如果调用改变为:
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 对象。请记住:在执行函数时,如果函数中的 this 是被上一级的对象所调用,那么 this 指向的就是上一级的对象;否则指向全局环境
2、上下文对象调用中的this
如上结论,面对下题时我们便不再困惑:
const student = {
name: 'Lucas',
fn: function () {
return this;
},
};
console.log(student.fn() === student); // true
最终结果将会返回 true。
当存在更复杂的调用关系时,请看例题:
const person = {
name: 'Lucas',
brother: {
name: 'Mike',
fn: function () {
return this.name;
},
},
};
console.log(person.brother.fn()); // Mike
到此,this 的上下文对象调用已经理解得比较清楚了。让我再看一道更高阶的题目:
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()); // undefine
答案是:o1、o1、undefined,你答对了吗?
我们来一一分析。
第一个 console 最简单,o1 没有问题。难点在第二个和第三个上面,关键还是看调用 this 的那个函数。
第二个 console 的 o2.fn(),最终还是调用 o1.fn(),因此答案仍然是 o1
最后一个,在进行 var fn = o1.fn 赋值之后,是「裸奔」调用,因此这里的 this 指向 window,答案当然是 undefined。
如果面试者回答顺利,可以紧接着追问,如果我们需要让:
console.log(o2.fn())
输出 o2,该怎么做?
一般开发者可能会想到使用 bind/call/apply 来对 this 的指向进行干预,这确实是一种思路。但是我接着问,如果不能使用 bind/call/apply,有别的方法吗?
这样可以考察候选人基础掌握的深度以及随机应变的思维能力。答案为:
const o1 = {
text: 'o1',
fn: function () {
return this.text;
},
};
const o2 = {
text: 'o2',
fn: o1.fn,
};
console.log(o2.fn()); // o2
还是应用那个重要的结论:this 指向最后调用它的对象,在 fn 执行时,挂到 o2 对象上即可,我们提前进行了赋值操作。
3、bind/call/apply 改变this指向
const foo = {
name: 'lucas',
logName: function () {
console.log(this.name);
},
};
const bar = {
name: 'mike',
};
foo.logName.call(bar); // mike
将会输出 mike,这不难理解。但是对 call/apply/bind 的高级考察往往会结合构造函数以及组合式实现继承。实现继承的话题,我们会单独讲到。构造函数的使用案例,我们结合接下来的例题组合进行分析。
4、构造函数和this
function Foo() {
this.bar = 'Lucas';
}
const instance = new Foo();
console.log(instance.bar); // Lucas
答案将会输出 Lucas。但是这样的场景往往伴随着下一个问题:new 操作符调用构造函数,具体做了什么? 以下供参考:
创建一个新的对象;
将构造函数的 this 指向这个新对象;
为这个对象添加属性、方法等;
最终返回新对象。
5、箭头函数和this
首先我们再来温习一下相关结论。
结论:箭头函数使用 this 不适用以上标准规则,而是根据外层(函数或者全局)上下文来决定。
来看题目:
const foo = {
fn: function () {
setTimeout(function () {
console.log(this);
});
},
};
foo.fn(); // window
这道题中,this 出现在 setTimeout() 中的匿名函数里,因此 this 指向 window 对象。如果需要 this 指向 foo这个 object 对象,可以巧用箭头函数解决:
const foo = {
fn: function () {
setTimeout(() => {
console.log(this);
});
},
};
foo.fn(); // {fn:f}