JS中函数内部this的探究

本文参考链接 彻底理解js中this的指向知乎回答 以及 JavaScript中的对象查找

下面所说的全部为标准模式下的情况,至于严格模式下的this,请参阅另一篇文章最后一部分内容

首先必须要说的是,this的指向在函数定义的时候是确定不了的,只有函数执行的时候才能确定this到底指向谁,实际上this的最终指向的是那个调用它的对象。

既然this最终指向调用它的对象,那么我就首先看下,JS中函数的调用分那几种情况。

JS中函数调用共分为四种类型

一:Function Invocation Pattern

诸如foo()的调用形式被称为Function Invocation Pattern,是函数最直接的使用形式,注意这里的foo是作为单独的变量出现,而不是属性,前面并无调用对象。
在这种模式下,foo函数体中的this永远为Global对象,在浏览器中就是window对象。比如下面的代码:

function foo(){
    var user = "追梦子";
    console.log(this.user); //undefined
    console.log(this); //Window
}
foo();

即使在函数内部调用,由于前面并无调用对象,this还是指向window全局对象,如以下代码

function printThis() {
  console.log(this)
  let print = function () { console.log(this) };
  print();  
}
printThis()  // 第一行打印 window,第二行打印window
printThis.call([1]);  // 第一行打印[1],第二行打印window
printThis.call([2]);  // 第一行打印[2],第二行打印window

在上面的代码中,第一条将打印printThis的this,当通过call函数的时候,参数作为this传递到函数中,因此分别打印[1]和[2]。第二条因为没有对象调用print,因此一直打印window。
即使将print改为匿名函数,比如下面的代码,结果依然没有变化。因此此时匿名函数依然由window调用

function printThis() {
  console.log(this)
  (function () {
    console.log(this)
  })(); // 匿名函数和上面的print都由window调用
}
printThis()  // 第一行打印 window,第二行打印window
printThis.call([1]);  // 第一行打印[1],第二行打印window
printThis.call([2]);  // 第一行打印[2],第二行打印window

但是在ES6中,随着箭头函数的引入,这种情况有所变化,将上面的函数改为下面的格式:

function printThis() {
  console.log(this)
  let print = () => console.log(this);
  print();
}
printThis()  // 第一行打印 window,第二行打印window
printThis.call([1]);  // 第一行打印[1],第二行打印[1]
printThis.call([2]);  // 第一行打印[2],第二行打印[2]

此时的结果和上面就有所不同,这是因为在箭头函数中,抛弃了自己的this属性,而是直接使用封闭执行上下文的this值。所谓的封闭执行上下文,就是箭头函数出现的地方的代码域。此时第一条和第二条打印内容相同。
在箭头函数中,所有的this原则,无论是标准模式还是严格模式下,都不再生效。

二:Method Invocation Pattern

诸如foo.bar()的调用形式被称为Method Invocation Pattern,注意其特点是被调用的函数作为一个对象的属性出现,必然会有“.”或者“[]”这样的关键符号。
在这种模式下,bar函数体中的this永远为“.”或“[”前的那个对象,如上例中就一定是foo对象。比如下面代码:

var o = {
  a:10,
  b:{
    a:12,
    fn:function(){
      console.log(this.a); //12
    }
  }
}
o.b.fn(); 

虽然首先调用的对象为o,但最终的函数fn是对象b的一个属性,因此this.a为12。
然后我们再看下下面的代码

var o = {
  a:10,
  b:{
    a:12,
    fn:function(){
      console.log(this.a); //12
    }
  }
}
var j = o.b.fn;  // 只是定义,并未调用执行,真正的调用执行在下面的j()
j(); 

此时的this.a应该是多少呢?此时的this其实为window,this.a为undefined。为什么如此呢?回顾一下,在文章的最开始,我们就提到了this的最终指向的是那个调用它的对象。而我们在使用o.b.fn的时候,并没有调用执行,真正的调用执行是在下面的j(),此时的情况和第一种方式一样。

三:Constructor Pattern

new foo()这种形式的调用被称为Constructor Pattern,其关键字new就很能说明问题,非常容易识别。
在这种模式下,foo函数内部的this永远是new foo()返回的对象。比如下面的代码:

function Foo () {
  this.x = 1;
}
Foo.prototype.print = function () {
  console.log(this.x);
}

let foo = new Foo();
foo.print(); 
foo.print.call({x: 2});

此时,第一条打印为1,表示this为foo实例对象。this.x为constructor函数中赋值的1。第二条打印为2,因此为使用了{x: 2}作为对象替换了foo实例对象中的this
在这里需要注意Function创建对象的一种特殊情况

function Foo () {
  this.x = 1;
  return {x: 2};
}
Foo.prototype.print = function () {
  console.log(this.x);
}
let foo = new Foo();
console.log(foo.x);  // 2
foo.print();  // funciton print undefined

如果构建函数Foo返回的是一个对象,那么foo就会被这个对象所替换掉,此时foo为{x: 2},其中只有一个x属性等于2,并无print这个方法属性。但是如果我们修改下代码,改成下面的格式:

function Foo () {
  this.x = 1;
  return 1;
}
Foo.prototype.print = function () {
  console.log(this.x);
}
let foo = new Foo();
console.log(foo.x);  // 1
foo.print();  // 1

因为构建函数Foo返回的是并不是一个对象,那么foo就不会被替换掉,依然是Foo的一个实例对象,此时的foo.xthis.x全部为1。

四:Apply Pattern

foo.call(thisObject)foo.apply(thisObject)的形式被称为Apply Pattern,使用了内置的callapply函数。
在这种模式下,callapply的第一个参数就是foo函数体内的this,如果thisObject是nullundefined,那么会变成window对象。具体代码,我们在上面的三种模式中已经顺带阐述,因此不再赘述。


练习部分

查看下下面的代码,分析下打印的结果,然后实际运行下,看下是否和结果一致

var x = 0;
function Foo () {
  this.x = 1;
}
Foo.prototype.print = function () {
  console.log(this);
  console.log(this.x);
  (function () {
    console.log(this);
    console.log(this.x)
  })()
}

let foo = new Foo();
foo.print.call({x: 2});

查看代码,首先注意到Foo对象的print中,第三条和第四条打印是在一个匿名函数中,此时该匿名函数的调用者为window全局变量,因此第四条打印中的this.xwindow.x = 0。再往下看,发现使用{x:2}代替了print函数中的this,因此第一条打印为{x: 2},第二条打印为2。

如果修改下代码,将print的定义改为箭头函数呢?结果又如何?代码如下:

var x = 0;
function Foo () {
  this.x = 1;
}
Foo.prototype.print = () => {
  console.log(this);
  console.log(this.x);
  (function () {
    console.log(this);
    console.log(this.x)
  })()
}

let foo = new Foo();
foo.print.call({x: 2});

首先,我们知道箭头函数不包含this,它的this为执行上下文中的this。而且箭头函数的执行上下文的判定,就在其定义的时刻决定,我们发现,它是在脚本中定义的,此时的作用域为全局,因此此时,前两条打印和后两条打印一样,都为0和window。

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

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,580评论 18 139
  • 函数参数的默认值 基本用法 在ES6之前,不能直接为函数的参数指定默认值,只能采用变通的方法。 上面代码检查函数l...
    呼呼哥阅读 3,349评论 0 1
  • 1.概念 在JavaScript中,this 是指当前函数中正在执行的上下文环境,因为这门语言拥有四种不同的函数调...
    BluesCurry阅读 1,119评论 0 2
  • 函数和对象 1、函数 1.1 函数概述 函数对于任何一门语言来说都是核心的概念。通过函数可以封装任意多条语句,而且...
    道无虚阅读 4,521评论 0 5
  • 大多数人都是需要口语和听力,单词量又是基础,如何兼顾? 每天一篇听力,和跟读。 单词不用刻意背,坚持读书。 有病就...
    Nick_k哥阅读 237评论 0 0