js的this的原理以及用法

​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。

解析:

  1. 在这道题中,虽然 fn 作为 method 的参数传了进来,但它的调用者并不受影响,仍然是 window,所以输出了 10。

  2. arguments[0]();这条语句并不常见,可能大家有疑惑的点在这里。其实,arguments 是一种特殊的对象。在函数中,我们无需指出参数名,就能访问。可以认为它是一种,隐式的传参形式。

  3. 当执行 arguments[0](); 时,其实调用了 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.aobj.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显示绑定,绑定到执行的对象。

解释:

可以使用callapply方法显式的指定this的绑定对象,如:

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

foo.call(obj); // 2
foo.apply(obj); // 2

通过foo.call(...),我们可以在调用foo时强制把它的this绑定到obj上。

硬绑定

通过callapply仍然无法解决绑定丢失的问题,但是通过显示绑定的一个变种可以解决这个问题,如:

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),因此强制把foothis绑定到了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来调用函数,或者说发生构造函数调用时,会自动执行下面的操作:

  1. 创建(构造)一个全新的对象。

  2. 这个新对象会被执行[[Prototype]]连接。

  1. 这个新对象会绑定到函数调用的this。

  2. 如果函数没有返回其他对象,那么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。

解析:

  1. 执行 obj.dbl(); 时, this.val 的 this 指向 obj,而下一行的 val 指向 window。所以,由 window.val 输出 2,obj.val 输出 4 。

  2. 最后一行 func(); 的调用者是 window。所以,现在的 this.val 的 this 指向 window。

  1. 别忘了刚才的运算结果,window.val 已经是 2 了,所以现在 this.val *= 2; 的执行结果就是 4。

  2. val *= 2; 的执行结果,就是 8 了。所以,最终的结果就是输出 8 和 8 。

最后听一首悦耳的歌放松放松,回忆学到的东西。

点击下面播放音乐

徐心愉 - 热爱105°C的你 (完整女声版).mp300:0003:15#让生活多一点生机

长按二维码关注,一起努力。

 

助力寻人启事

 

微信公众号回复 加群 一起学习。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值