js的this指向是工作中最常碰到的,同时也是笔试 or 面试中会被问到的问题,故在本文整理了js的this指向,以供参考。
此篇以面试题开题,而后再讲JavaScript的this指向,最后以面试题结束。
面试题
var length = 10;
function fn () {
console.log(this.length);
}
var obj = {
length: 5,
method: function (fn) {
fn();
arguments[0]();
}
};
obj.method(fn, 1);
问:浏览器的输出结果是什么?
它的答案是:先输出一个 10,然后输出一个 2。
解析:
- 在这道题中,虽然 fn 作为 method 的参数传了进来,但它的调用者并不受影响,仍然是 window,所以输出了 10。
- arguments0;这条语句并不常见,可能大家有疑惑的点在这里。其实,arguments 是一种特殊的对象。在函数中,我们无需指出参数名,就能访问。可以认为它是一种,隐式的传参形式。
- 当执行 arguments0; 时,其实调用了 fn()。而这时,fn 函数中 this 就指向了 arguments,这个特殊的对象。obj.method 方法接收了 2 个参数,所以 arguments 的 length,很显然就是 2 了。
this 是什么
this 是 JavaScript 中的一个关键字。它通常被运用于函数体内,依赖于函数调用的上下文条件,与函数被调用的方式有关。它指向谁,则完全是由函数被调用的调用点来决定的。
所以,this,是在运行时绑定的,而与编写时的绑定无关。随着函数使用场合的不同,this 的值也会发生变化。但是有一个总的原则:那就是this 总会指向,调用函数的那个对象。
js中的this指向
如何判断this指向?可以按照下面的顺序来进行判断:
// 1. 函数是否在new中调用(new绑定) ?如果是的话,this绑定的是新创建的对象。
var bar = new foo();
// 2. 函数是否通过call、apply(显示绑定)或者硬绑定(bind)调用?如果是的话,this绑定的是指定的对象。
var bar = foo.call(obj2);
// 3. 函数是否在某个上下文对象中调用(隐式绑定)?如果是的话,this绑定的是那个上下文对象。
var bar = obj1.foo();
// 4. 如果都不是的话,使用默认绑定。如果在严格模式下,就绑定到undefined,否则绑定到全局对象
var bar = foo();
不理解什么是new绑定
、显示绑定
、硬绑定
、隐式绑定
、默认绑定
?没关系,看下面 ↓:
1. 默认绑定。在严格模式下绑定到undefined,否则绑定到全局对象。
解释:
最常用的函数调用类型是:独立函数调用。可以把这条规则看做是无法应用其他规则时的默认规则。如:
function foo () {
console.log(this.a)
}
var a = 2;
foo(); // 2
在本例中,foo函数调用时应用了默认绑定,因此this指向全局对象,而声明在全局作用域中的变量(比如 var a = 2)就是全局对象的一个同名属性,所以在调用foo()时,this.a被解析成了全局变量a。
那么,我们是怎么知道这里应用了默认绑定呢?
在代码中,foo()函数时直接使用不带任何修饰的函数引用进行调用的,因此只能使用默认绑定,无法应用其他规则。
如果使用严格模式(strict mode),则不能将全局对象用于默认绑定,因此this会绑定到undefined:
function foo() {
"use strict";
console.log(this.a);
}
var a = 2;
foo(); // 会报错。TypeRrror: this is undefined。
这里还有一个非常重要的细节,虽然this的绑定规则完全取决于调用位置,但是只有foo()运行在非strict mode下时,默认绑定才能绑定到全局,在严格模式下调用foo()则不影响默认绑定:
function foo() {
console.log(this.a);
}
var a = 2;
(function () {
"use strict";
foo(); // 2
})()
一般来讲,不应该在代码中混合使用strict模式和非strict模式。整个程序要么严格要么非严格。然而,有时候可能会用到第三方库,其严格程度和自己的代码有所不同,因此一定要注意这类兼容性细节。
2. 隐式绑定。由上下文对象调用,绑定到那个上下文对象。
解释:
确定调用位置是否有上下文对象,或者说是否被某个对象拥有或包含,如:
function foo () {
console.log(this.a)
}
var obj = {
a: 2,
foo: foo
}
obj.foo(); // 2
调用位置会使用obj上下文来引用函数,因此,可以说函数被调用时obj对象“拥有”或“包含”它。
当函数引用有上下文对象时,隐式绑定规则会把函数调用中的this绑定到这个上下文对象。因此this.a
和obj.a
是一样的。
对象属性引用链中只有上一层或者说最后一层在调用位置中起作用,如:
function foo() {
console.log( this.a );
}
var obj2 = {
a: 42,
foo: foo,
}
var obj1 = {
a: 2,
obj2: obj2,
}
obj1.obj2.foo(); // 42
隐式丢失
被隐式绑定的函数会丢失绑定对象,也就是说它会应用默认绑定绑定,从而把this绑定到全局对象或者undefined上。如下:
function foo() {
console.log(this.a);
}
var obj = {
a: 2,
foo: foo
}
var bar = obj.foo; // 函数别名
var a = 'global'; // a 是全局对象的额属性
bar(); // 'global'
虽然bar是obj.foo的一个引用,但是实际上,它引用的是foo函数本身,因此此时的bar()其实是一个不带任何修饰的函数调用,因此应用了默认绑定。
在传入回调函数时,也会发生隐式丢失:
function foo () {
console.log(this.a);
}
function doFoo (fn) {
// fn 其实引用的是foo
fn();
}
var obj = {
a: 2,
foo: foo
}
var a = 'global';
doFoo( obj.foo ); // 'global'
和上一个例子一样,fn引用的是foo函数本身,因此此时的fn()其实是一个不带任何修饰的函数调用,因此应用了默认绑定。
在js环境中内置的setTimeout()函数实现和下面的伪代码类似:
function setTimeout(fn, delay) {
// 等待delay毫秒
fn();
}
所以传递给定时器的函数,也会应用默认绑定。
3. 显示绑定。由call/apply/bind显示绑定,绑定到执行的对象。
解释:
可以使用call
、apply
方法显式的指定this的绑定对象,如:
function foo () {
console.log(this.a);
}
var obj = {
a: 2
}
foo.call(obj); // 2
foo.apply(obj); // 2
通过foo.call(...),我们可以在调用foo时强制把它的this绑定到obj上。
硬绑定
通过call
、apply
仍然无法解决绑定丢失的问题,但是通过显示绑定的一个变种可以解决这个问题,如:
function foo() {
console.log(this.a);
}
var obj = {
a: 2
}
var bar = function () {
foo.call(obj);
}
bar(); // 2
setTimeout(bar, 100); // 2
bar.call( window ); // 2。强制绑定的bar不可能再修改它的this。
为什么会出现这样的情况呢?
是由于我们创建了函数bar()
,并在它的内部手动调用了foo.call(obj)
,因此强制把foo
的this
绑定到了obj
。无论之后如何调用函数bar
,它总会手动在obj
上调用foo
。这种绑定是一种显式的强制绑定,因此我们称之为硬绑定。
由于硬绑定是一种非常常用的模式,所以ES5提供了方法bind,使用方法:
function foo(something) {
console.log(this.a, something);
return this.a + something;
}
var obj = {
a: 2,
}
var bar = foo.bind(obj);
var b = bar(3);// 2 3
console.log(b); // 5
bind 会返回一个新函数,它会把指定的参数设置为this的上下文并调用原始函数。
4. new绑定。由new调用,绑定到新创建的对象。
解释:
使用new来调用函数,或者说发生构造函数调用时,会自动执行下面的操作:
- 创建(构造)一个全新的对象。
- 这个新对象会被执行[[Prototype]]连接。
- 这个新对象会绑定到函数调用的this。
- 如果函数没有返回其他对象,那么new表达式中的函数调用会自动返回这个新对象。
function foo(a) {
this.a = a;
}
var bar = new foo(2);
console.log(bar.a); // 2
使用new来调用foo(...)时,我们会构造一个新对象并把它绑定到foo(...)调用中的this上。
注意:ES6中的箭头函数不会使用四条标准的绑定规则,而是根据当前的词法作用域来决定this,具体来说,箭头函数会继承外层函数调用的this绑定(无论this绑定到什么)。这其实和ES6之前的代码中的self=this机制一样。
另一道面试题
最后,让我们来巩固一下 this 的概念和用法。来看一道面试题:
window.val = 1;
var obj = {
val: 2,
dbl: function () {
this.val *= 2;
val *= 2;
console.log('val:', val);
console.log('this.val:', this.val);
}
};
// 说出下面的输出结果
obj.dbl();
var func = obj.dbl;
func();
答案是输出:2 、 4 、 8 、 8。
解析:
- 执行 obj.dbl(); 时, this.val 的 this 指向 obj,而下一行的 val 指向 window。所以,由 window.val 输出 2,obj.val 输出 4 。
- 最后一行 func(); 的调用者是 window。所以,现在的 this.val 的 this 指向 window。
- 别忘了刚才的运算结果,window.val 已经是 2 了,所以现在 this.val *= 2; 的执行结果就是 4。
- val *= 2; 的执行结果,就是 8 了。所以,最终的结果就是输出 8 和 8 。
最后听一首悦耳的歌放松放松,回忆学到的东西。
点击下面播放音乐
徐心愉 - 热爱105°C的你 (完整女声版).mp3 00:00 03:15#让生活多一点生机
长按二维码关注,一起努力。
助力寻人启事
微信公众号回复 加群 一起学习。
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!