this关键字与ES6中的箭头函数

this 关键字是JavaScript中最灵活也最令人困惑的知识点。

this指代的是什么

首先看如下代码:

var a = 20; //(1)
function fun() {
    var a = 10;
    console.log(this.a); //(2) 思考下此处this指代的是什么
}

fun(); //(3)

在浏览器中运行此段代码会得到

20

下面详细讲解这段代码:

  1. var a = 20这行代码看似平常。先不进一步解释这行代码。在上述代码片段的最后一行加入:

    console.log(window.a);
    

    会得到如下输出

    20

    是不是和a变量本身的值一致呢?
    事实上在浏览器环境中运行相当于window.a = 20。浏览器环境中会默认存在全局变量window

window是浏览器对象模型。所有的现代浏览器都支持该对象。并且JavaScript在浏览器运行环境中运行时,window均为全局对象。任何定义的变量或者是函数都是window这个对象的成员变量。

  1. 对于JavaScript中的this,完全不能按照Java中的this进行类比理解。在Java中,this指代的是当前对象的实例,且this所指代的对象永远是不变化的值。而在JavaScript中,this指代的是调用该方法的对象。如果当前方法是直接调用的话,this指代的是全局对象。在浏览器环境中指的是window对象。
    解释console.log(this.a)之前,需要先明白(3)处的方法调用。
    fun()方法调用没有明确指明调用该方法的对象。这么来说,fun()方法调用实际上相当于window.fun()
    到现在为止(2)这行代码中this的指向问题应该很清楚了。该处的this指向的正是window对象。

再看看这一段代码,试着确定下该代码的输出内容,检测下自己对上述内容的理解程度:

var a = 10;
function fun() {
    this.a = 20;
    console.log(this.a);
}
fun(); //输出什么?
console.log(a); //这里呢?
console.log(this.a); //这里又该是什么呢?

下面给出这段代码的解释:

var a = 10; //相当于window.a = 10
function fun() {
    this.a = 20;
    console.log(this.a);
}
fun(); //相当于window.fun();此时的方法调用this的指向为window对象,于是变量a的值会被覆盖成20
console.log(a); //相当于window.a。a=20
console.log(this.a); //相当于window.a。a=20

进阶内容

请看下面这段代码:

var person1 = {
    name: "Peter",
    sayName: function() {
        console.log(this.name);
    }
}
var person2 = {
    name: "Mary",
    sayName: person1.sayName
}

// 尝试推断以下输出
person1.sayName(); //(1)
person2.sayName(); //(2)

var fun = person1.sayName;
fun(); //(3)

解析

  • (1)处的输出为Peter。调用该方法的对象为person1,故this指向person1。所以输出为Peter。
  • (2)处的输出为Mary。虽然此处调用的方法为person1中的方法sayName,但是this却是在被调用的时候才会绑定。此处调用该方法的对象为person2,所以输出为Mary。
  • (3)处的输出为undefined。该处的方法调用相当于window.fun()。this指向的是window对象,而window中并没有name这个变量,所以输出为undefined。

绑定this之call, apply与bind

call与apply

看下面这段代码:

function sayName(greetings, otherMessages) {
    console.log(greetings + " " + this.name);
    console.log(otherMessages);
}

var obj = {
    name: "Paul"
};

sayName.call(obj, "Hello", "I am 20"); //(1)
sayName.apply(obj, ["Hello", "I am 25"]); //(2)

(1)和(2)处的代码均相当于把sayName这个函数的this绑定到obj这个对象上再执行。
总结一下来说call和apply方法能够绑定函数的this。不同之处在于call方法调用函数,函数的参数用逗号分隔的参数列表传入。而apply方法调用的话函数的参数用数组的形式传入。

bind方法

bind方法同样能够绑定function中的this变量,但是又有所不同。函数的this一旦被bind,即便再使用call或者是apply再次尝试重新绑定this,仍然不会生效。
示例代码如下:

function sayName(greetings) {
    console.log(greetings + " " + this.name);
}

var obj = {
    name: "Paul"
};

var obj2 = {
    name: "Kate"
};

var fun = sayName.bind(obj); //此处fun这个函数的this被绑定到了obj这个对象上
fun("Hello", "I am 20"); //输出为Hello Paul。fun函数的this已经被绑定到了obj对象
fun.call(obj2, "Hello", "I am 20"); //此处尝试使用obj2来绑定this,但是fun的this已经被绑定至obj,因此输出仍然是Hello Paul

new关键字的背后

和在Java中单纯的创建新实例不同,JavaScript中的new关键字所做的事情要复杂多了。
使用new关键字会执行如下步骤:

  1. 生成一个空的object,即 {}
  2. 设置新对象的prototype为构造函数的prototype
  3. 将构造函数中的this绑定至新创建的object
  4. 执行构造函数
  5. 返回创建的新object

示例代码如下:

function Person() {
    this.name = "Paul";
    this.age = "20";
    this.sayName = function() {
        console.log(this.name);
    }
}

Person.prototype.sayAge = function() {
    console.log(this.age);
}

var person = new Person();
person.sayName();
person.sayAge();

在new这个对象的时候发生了如下事情,使用代码来描述如下:

var person = {}; //创建空的object
person.prototype = Person.prototype; //新创建object的prototype指向构造函数的prototype
Person.call(person); //执行构造函数,同时函数中的this指向新创建的object

问题来了,如果构造函数有返回值会发生什么事情呢?例如这段代码:

function Obj1() {
    this.a = 10;
    return "modified";
}

var obj1 = new Obj1(); //此处obj1为{a: 10},并没有受到返回值的影响

function Obj2() {
    this.a = 10;
    return {
        b: 20
    };
}

var obj2 = new Obj2(); //此处的obj2为{b: 20},构造函数的返回值影响了新创建的object

总结如下:

  • 如果构造函数返回值不为object类型,则构造函数的返回值会被忽略
  • 如果构造函数返回值是object类型,则新创建的object会被替换为构造函数返回的object

箭头函数

想之前讨论的那样,传统的this绑定问题在实际应用时特别棘手。有个例子可以说明:

var obj = {
    a: 10,
    b: [1, 2, 3],
    c: function() {
        return this.b.map(function(item) {
            item + this.a; //(1)
        });
    }
}

console.log(obj.c()); //输出是[11, 12, 13]吗?实际上该处的输出为[undefined, undefined, undefined]
// 因为(1)处的代码this指向的是window,所以说该处this.a的值为undefined

ES6中引入了新的箭头函数,能够解决this绑定这一大难题。

var obj = {
    a: 10,
    b: [1, 2, 3],
    c: function() {
        return this.b.map((item) => this.a + item);
    }
}

console.log(obj.c()); //输出为[11, 12, 13]

事实上箭头函数执行时候不绑定this,而是在自动寻找上层的this(如果没有继续向上层寻找)并且绑定,箭头函数中的this会向上寻找到obj.c这个函数,在该函数中this.a=10,所以说该处代码能够返回我们想要的结果。
为了验证这段话,请看下面的代码:

function test() {
    this.a = 10;
    var obj1 = {
        a: 20,
        b: function() {
            return () => console.log(this.a);
        },
        c: function() {
            console.log(this.a);
        }
    };

    var obj2 = {
        a: 30,
        b: obj1.b(),
        c: obj1.c
    };
    
    obj1.b()(); //返回20。obj1的b函数执行时this为obj1对象。其返回的箭头函数中的this也指向obj1。
    obj1.c(); //返回20。obj1的c函数中的this指向obj1。
    var f = obj1.b;
    f()(); //返回10。相当于var fun = window.f(); fun(); f函数执行时this绑定到window,其返回的箭头函数中的this依照原则指向的同样是window对象。
    f = obj1.c;
    f(); //返回10。相当于window.f()
    obj2.b(); //返回20。obj1.b在obj2对象定义的时候执行,obj1.b执行的时候this绑定至obj1对象,其中箭头函数的this同样指向的是obj1对象。
    obj2.c(); //返回30。obj1的c函数在执行时候绑定,因此this指向obj2。
}
test();

本文总结

  • 普通函数中的this变量在定义的时候不能确定,而是在函数被执行的时候才绑定。
  • 普通函数中的this在执行时绑定至调用该函数的object。
  • call、apply和bind可以改变函数的this绑定。bind绑定的this无法再显式绑定(new除外)。
  • 箭头函数执行时不绑定this,而是自动向上寻找this,直到找到为止。应用场景多为回调函数,例如map,forEach和reduce。

本文为原创内容,欢迎大家讨论、批评指正与转载。转载时请注明出处。

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

推荐阅读更多精彩内容