this 到底指向哪里?

发布于 2022-11-10 12:42:14 字数 5478 浏览 151 评论 0

为什么要用 this

回答这个问题我们就先看看如果不使用 this 会出现什么问题。试想下面代码如果不使用 this 应该怎么写:

function speak(){
    var name = this.name
    console.log(`Hello I am ${name}`)
}
var me = {
    name: 'a',
    speak: speak
}
var you = {
    name: 'b',
    speak: speak
}
me.speak()  //Hello I am a
you.speak()  //Hello I am b

this 可以在同一个执行环境中使用不同的上下文对象。它其实提供了一种更加优雅的方式来隐式“传递”一个对象引用,因此可以使API设计的更加简洁且易于复用。

this 到底是谁?

this 既不是自身也不是当前函数的作用域。我们可以通过代码来测试。

判断是不是自身

function fn(){
    console.log(this.name)
}
fn.name = 'xxx'
fn()  //undefined

判断是不是作用域

function foo() { 
    var a = 2;
    this.bar(); 
}
function bar() { 
    console.log( this.a );
}
foo(); // ReferenceError: a is not defined

那么 this 到底是谁呢?其实不一定, this 是运行时绑定的,所以取决于函数的执行上下文。

当一个函数被调用时,会创建一个活动记录(执行上下文)。这个记录会包含函数在哪里被调用(调用栈)、函数的调用方法、传入的参数等信息,this也是这里的一个属性。

如何判断 this 指向?

确定 this 指向就是确定函数的执行上下文,也就是“谁调用的它”,有以下几种判断方式:

独立函数调用

function foo(){
    console.log(this.a)
}
var a = 2
foo()  // 2

这种直接调用的方式 this 指向全局对象,如果是在浏览器就指向 window

对象上下文(隐式绑定)

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

foo 虽然被定义在全局作用域,但是调用的时候是通过 obj 上下文引用的,可以理解为在 foo 调用的那一刻它被 obj 对象拥有。所以 this 指向 obj
这里有两个问题:

链式调用

链式调用的情况下只有最后一层才会影响调用位置,例如:

obj1.obj2.obj3.fn() //这里的fn中的this指向obj3

引式丢失

function foo() { 
    console.log( this.a );
}
var obj = { 
    a: 2,
    foo: foo 
};
var bar = obj.foo; // 函数别名!
var a = "xxxxx"
bar(); // xxxxx

这里的 bar 其实是引用了 obj.foo 的地址,这个地址指向的是一个函数,也就是说 bar 的调用其实符合“独立函数调用”规则。所以它的 this 不是 obj

回调函数其实就是隐式丢失。

稍微改一下上面的代码:

function foo() { 
    console.log( this.a );
}
var obj = { 
    a: 2,
    foo: foo 
};
var a = "xxxxx"
setTimeout( obj.foo ,100); // xxxxx

我们看到,回调函数虽然是通过 obj 引用的,但是 this 也不是 obj 了。其实内置的setTimeout()函数实现和下面的伪代码类似:

function setTimeout(fn, delay){
    //等待delay毫秒
    fn()
}

其实这段代码隐藏这一个操作就是 fn=obj.foo ,这和上面例子中的 bar=obj.foo 异曲同工。

显式绑定

显式绑定的说法是和隐式绑定相对的,指的是通过 callapplybind 显式地更改 this 指向。

这三个方法第一个参数是 this 要指向的对象。

注意,如果你给第一个参数传递一个值(字符串、布尔、数字)类型的话,这个值会被转换成对象形式(调用new String(..)、new Boolean(..)、new Number(..))。

这三个方法中的 bind 方法比较特殊,它可以延迟方法的执行,这可以让我们写出更加灵活的代码。它的原理也很容易模拟:

function foo(something) { 
    console.log( this.a, something ); 
    return this.a + something;
}
function bind(fn, obj) {
    return function() {
        return fn.apply( obj, arguments );
    }; 
}
var obj = { 
    a:2
};
var bar = bind( foo, obj );
var b = bar( 3 ); // 2 3 
console.log( b ); // 5

注意:如果第一个参数传入 null 或者 undefined,这个值会被忽略,相当于符合独立函数调用规则

new 绑定

Js中 new 与传统的面向类的语言机制不同,Js中的“构造函数”其实和普通函数没有任何区别。其实当我们使用 new 来调用函数的时候,发生了下列事情:

  • 创建一个全新的对象
  • 这个新对象会被执行“原型”链接
  • 这个新对象会被绑定到调用的this
  • 如果函数没有对象类型的返回值,这个对象会被返回

其中,第三步绑定了 this ,所以“构造函数”和原型中的 this 永远指向 new 出来的实例。

优先级

以上四条判断规则的权重是递增的。判断的顺序为:

  • 函数是 new 出来的, this 指向实例
  • 函数通过call、apply、bind绑定过, this 指向绑定的第一个参数
  • 函数在某个上下文对象中调用(隐式绑定), this 指向上下文对象
  • 以上都不是, this 指向全局对象

严格模式下的差异

以上所说的都是在非严格模式下成立,严格模式下的 this 指向是有差异的。

  • 独立函数调用: this 指向 undefined
  • 对象上的方法: this 永远指向该对象
  • 其他没有区别

箭头函数中的 this

箭头函数不是通过 function 关键字定义的,也不使用上面的 this 规则,而是 继承 外层作用域中的 this 指向。其实一起虽然没有箭头函数,我们也经常做和箭头函数一样效果的事情,比如说:

function foo() {
    var self = this; 
    setTimeout( function(){
        console.log( self );
    }, 100 );
}

getter 与 setter 中的 this

es6 中的 gettersetter 函数都会把 this 绑定到设置或获取属性的对象上。

function sum() {
  return this.a + this.b + this.c;
}
var o = {
  a: 1,
  b: 2,
  c: 3,
  get average() {
    return (this.a + this.b + this.c) / 3;
  }
};
Object.defineProperty(o, 'sum', { get: sum, enumerable: true, configurable: true} );
console.log(o.average, o.sum); // logs 2, 6

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据

关于作者

物价感观

暂无简介

文章
评论
27 人气
更多

推荐作者

櫻之舞

文章 0 评论 0

弥枳

文章 0 评论 0

m2429

文章 0 评论 0

野却迷人

文章 0 评论 0

我怀念的。

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文