JavaScript this

this 是 JavaScript 中一个比较特殊的关键字,它的机制比较复杂,会被自动定义在函数的作用域中,表示函数执行时的上下文对象。

如果不存在 this 的话,在函数执行的时候,就需要显示传入一个上下文对象,程序就会变得很复杂。然而,this 提供了一种更优雅的方式来隐式“传递”一个对象引用,因此可以将 API 设计得更加简洁并且易于复用。

1. this 机制

this 是在运行时进行绑定的,并不是在编写时绑定,它的上下文取决于函数调用时的各种条件。this 的绑定和函数声明的位置没有任何关系,只取决于函数的调用方式/位置。

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

2. 调用位置

每个函数的 this 是在调用时被绑定的,完全取决于函数的调用位置(函数的调用方式)。

调用位置:调用位置就是函数在代码中被调用的位置,而不是声明的位置。分析出函数的真正调用位置,才能正确找到其 this 的绑定对象。

通过分析函数的调用栈(到达当前执行位置所调用的所有函数),就可以找到函数的调用位置,调用位置就在当前正在执行的函数的前一个调用中。

function baz() {
  // 当前调用栈是:baz
  // 因此,当前调用位置是全局作用域
  console.log("baz");
  bar(); // <-- bar 的调用位置
}
function bar() {
  // 当前调用栈是 baz -> bar
  // 因此,当前调用位置在 baz 中
  console.log("bar");
  foo(); // <-- foo 的调用位置
}
function foo() {
  // 当前调用栈是 baz -> bar -> foo // 因此,当前调用位置在 bar 中
  console.log("foo");
}
baz(); // <-- baz 的调用位置

3. 绑定规则

找到函数的调用位置,然后结合不同类型的绑定规则,才可找到 this 的绑定对象。

3.1 默认绑定

默认绑定:直接使用不带任何修饰的函数引用进行调用的,此时使用的即默认绑定

如下代码,是一个独立函数的调用。函数调用应用了 this 的默认绑定,默认绑定绑定的是全局对象(非严格模式)。

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

默认绑定的对象,需要区分严格模式与非严格模式。即在非严格模式(no-strict mode)下,默认绑定的是全局对象。而严格模式下(strict mode)全局对象无法使用默认绑定,此时的 this 会绑定到 undefined。如下代码:

function foo() {
  "use strict";
  console.log(this.a);
}
var a = 2;
foo(); // TypeError: this is undefined

3.2 隐式绑定

隐式绑定:当函数引用有上下文对象时,隐式绑定规则会把函数调用中的 this 绑定到这个上下文对象。

如下代码,因为调用 foo 时 this 被绑定到 obj,因此 this.aobj.a 是一样的。

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

隐式绑定丢失,如下代码 foo 会丢失绑定对象,即 foo 最终调用位置是 bar,然后会被应用默认绑定,从而会把 this 绑定到全局对象或者 undefined, 取决于是否开启严格模式。

function foo() {
  console.log(this.a);
}
var obj = { a: 2, foo: foo };
var bar = obj.foo; // 函数别名
var a = "oops, global"; // a 是全局对象的属性
bar(); // "oops, global"

3.3 显式绑定

显式绑定:使用 bind(..)call(..)apply(..) 方法,把对象绑定到 this,接着在调用函数时指定这个 this。
这种直接执行 this 绑定对象的方式,称为显式绑定

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

另外一种常见地显式绑定方式就是 API 调用的上下文。

第三方库的许多函数,以及 JavaScript 语言和宿主环境中许多新的内置函数,都提供了一 个可选的参数,通常被称为“上下文”,作用和 bind(..) 一样,确保的回调函数使用指定的 this。

function foo(el) {
  console.log(el, this.id);
}
var obj = {
  id: "awesome",
};
// 调用 foo(..) 时把 this 绑定到 obj
[1, 2, 3].forEach(foo, obj); // 1 awesome 2 awesome 3 awesome

3.4 new 绑定

当使用 new 来调用函数,或者说发生构造函数调用时,会自动执行下面的操作。

  1. 创建(或者说构造)一个全新的对象。
  2. 这个新对象会被执行[[原型]]连接。
  3. 这个新对象会绑定到函数调用的 this。
  4. 如果函数没有返回其他对象,那么 new 表达式中的函数调用会自动返回这个新对象。
function foo(a) {
  this.a = a;
}
var bar = new foo(2);
console.log(bar.a); // 2
  1. 但是如果构造函数返回了一个对象,那么 this 就会指向该对象,如下:
var MyClass = function(){
  this.name = 'sven';
  return { // 显式的返回一个对象
    name: 'anne' 
  }
};
var obj = new MyClass();
console.log( obj.name ); // 输出:anne

4. 优先级

new 绑定 = 显示绑定 > 隐式绑定 > 默认绑定

通过优先级来判断函数在某个调用位置应用的是哪条规则。可以按照下面的顺序来进行判断:

  1. 函数是否在 new 中调用(new 绑定)?如果是的话 this 绑定的是新创建的对象。
    var bar = new foo()
  2. 函数是否通过 call、apply(显式绑定)或者硬绑定调用?如果是的话,this 绑定的是指定的对象。
    var bar = foo.call(obj2)
  3. 函数是否在某个上下文对象中调用(隐式绑定)?如果是的话,this 绑定的是那个上下文对象。
    var bar = obj1.foo()
  4. 如果都不是的话,使用默认绑定。如果在严格模式下,就绑定到 undefined,否则绑定到全局对象。
    var bar = foo()

5. 例外的绑定

5.1 被忽略的 this

如果你把 null 或者 undefined 作为 this 的绑定对象传入 callapply 或者 bind,这些值在调用时会被忽略,实际应用的是默认绑定规则,即可能会绑定到全局对象上。

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

5.2 间接引用

如果创建了一个函数的“间接引用”,在这种情况下,调用这个函数会应用默认绑定规则。

如下代码,赋值表达式 p.foo = o.foo 的返回值是目标函数的引用,因此调用位置是 foo() 而不是 p.foo() 或者 o.foo()。所以这里会被应用默认绑定。

function foo() {
  console.log(this.a);
}
var a = 2;
var o = { a: 3, foo: foo };
var p = { a: 4 };
o.foo(); // 3
(p.foo = o.foo)(); // 2

6. 箭头函数中的 this

箭头函数不适用于普通函数的以上规则,其不是通过使用 function 关键字来定义,而是通过使用操作符 => 定义的。

箭头函数根据外层(函数或者全局)作用域来决定 this。

如下代码,foo() 内部创建的箭头函数会捕获调用时 foo()this。由于 foo() 的 this 绑定到 obj1bar(引用箭头函数)的 this 也会绑定到 obj1,箭头函数的绑定无法被修改。(new 也不行)

function foo() {
  // 返回一个箭头函数
  return (a) => {
    //this 继承自 foo()
    console.log(this.a);
  };
}
var obj1 = { a: 2 };
var obj2 = { a: 3 };
var bar = foo.call(obj1);
bar.call(obj2); // 2, 不是 3 !
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,874评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,102评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,676评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,911评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,937评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,935评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,860评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,660评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,113评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,363评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,506评论 1 346
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,238评论 5 341
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,861评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,486评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,674评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,513评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,426评论 2 352

推荐阅读更多精彩内容