this的指向

整理自:

  1. https://zhuanlan.zhihu.com/p/23804247
  2. https://zhuanlan.zhihu.com/p/25991271
  3. https://juejin.im/post/5aa1eb056fb9a028b77a66fd#comment

若要理解this的指向,首先搞清楚JS中的函数。JS中的函数有两种形式:传统函数与ES6中新增的箭头函数。

一、JS中函数的写法

1. 常规函数的写法

ES6之前,JS中的函数由function关键字、params参数和{函数体}组成。为与箭头函数区别,将其称为常规函数,函数有两种写法:

<!--声明式-->
function f(name){
    console.log(name)
}
f('haha');

<!--表达式-->
let f2 = function (name){
    console.log(name)
}
f2('hehe');

2. 箭头函数写法

ES6箭头函数的引入,使函数的写法变的更加简洁,但在书写上要遵循一定的规则。

规则一:箭头函数只能用赋值式写法,不能用声明式写法

let f = (name)=>{
    console.log(name)
}
f('haha');

规则二:如果参数只有一个,可以不加括号,如果没有参数或者参数多于一个就需要加括号

<!--只有一个参数,省略参数括号-->
let f = name => {
    console.log(name)
}
f('haha');

<!--参数多于一个,必须加上括号-->
let f = (name,age)=>{
    console.log(name+":"+age)
}
f('haha',10);

规则三:如果函数体只有一句话,可以不加花括号

let f = name => console.log(name)
f('haha');

规则四:return和{}同在

const add = (p1, p2) => p1 + p2
add(10, 25)

<!--下面的情况会报错-->
const add = (p1, p2) => return p1 + p2

二、初步了解this

  1. this 就是你call一个函数时,传入的第一个参数。
  2. 如果你的函数调用形式不是call形式,请按照「转换代码」将其转换为 call 形式。

三、理解常规函数中的this

this是使用call方法调用函数时传递的第一个参数,它可以在函数调用时修改,在函数没有调用的时候,this的值是无法确定。

JS(ES5)里有三种函数调用形式:

func(p1, p2) 
obj.child.method(p1, p2)
func.call(context, p1, p2) // 先不讲 apply

一般,初学者都知道前两种形式,而且认为前两种形式「优于」第三种形式。但是第三种调用方式,才是正常调用方式,其余两种都是语法糖,可以等价地变为call形式。

func(p1, p2) 等价于
func.call(undefined, p1, p2)

obj.child.method(p1, p2) 等价于
obj.child.method.call(obj.child, p1, p2)

即,函数调用只有一种形式

func.call(context, p1, p2)

==this就是上面代码中的context。==

==this就是call一个函数时传入的context。==

1. 纯粹的函数调用

下面看一个例子:

function f(name){
    console.log(name)
    console.log(this)
}

f('haha')//调用函数的简写形式

f.call(undefined,'haha')//完整形式

call方法接受的第一个参数就是this,此处默认传入undefined。 按理说,函数执行之后log出来的this会是undefined吗?

实际输出结果为:

haha
Window对象

这是由于浏览器里有一条规则:
如果你传的 context 就 null 或者 undefined,那么 window 对象就是默认的 context(严格模式下默认 context 是 undefined)

如果希望这里的this不是window

f.call(obj,'haha')//里面的this就是obj对象了

2. 对象中函数的调用

下面看一个例子:

const obj = {
    name: 'Jerry',
    greet: function() {
        console.log(this.name)
    }
}
obj.greet()  //第一种调用方法,语法糖
obj.greet.call(obj) //第二种调用方法,完整的调用方式

第二种方法厉害的地方在于它可以手动指定this。

手动指定this

const obj = {
    name: 'Jerry',
    greet: function() {
        console.log(this.name)
    }
}
obj.greet.call({name: 'Spike'})  //打出来的是 Spike

[]语法

function fn (){ console.log(this) }
var arr = [fn, fn2]
arr[0]() // 这里面的 this 又是什么呢?

可以把 arr0想象为arr.0(),虽然后者的语法错了,但是形式与转换代码里的obj.child.method(p1,p2)对应上了,于是就可以愉快的转换了:

        arr[0]() 
假想为    arr.0()
然后转换为 arr.0.call(arr)
那么里面的 this 就是 arr 了 :)

3. 构造函数中的this

构造函数里的this稍微有点特殊,每个构造函数在new之后都会返回一个对象,这个对象就是this,也就是context上下文。

function Test() {
    this.name = 'Tom'
}
let p = new Test()
console.log(typeof p)  //object
console.log(p.name)    // Tom

4. window.setTimeout()和window.setInterval()中函数的调用

window.setTimeout()和window.setInterval()的函数中的this有些特殊,里面的this默认是window对象。

其实回调函数是在全局范围内调用的,形式同纯粹函数调用形式,其context为undefined,所以默认是window对象,

5. Event handler 中的this

btn.addEventListener('click' ,function handler(){
  console.log(this) // 请问这里的 this 是什么
})

this都是由call或apply指定的,只要找到handler被调用时的代码就可以找到this了。但是addEventListener是浏览器内置的方法,无法看到源码,不过MDN中进行了说明:

通常来说this的值是触发事件的元素的引用,这种特性在多个相似的元素使用同一个通用事件监听器时非常让人满意。 

当使用 addEventListener() 为一个元素注册事件的时候,句柄里的 this 值是该元素的引用。其与传递给句柄的 event 参数的 currentTarget 属性的值一样。

所以,可以假想handler调用时:

// 当事件被触发时
handler.call(event.currentTarget, event) 
// 那么 this 是什么不言而喻

currentTarget指向监听事件注册到的元素,target指向实际出发事件的元素

四、箭头函数中的this

==所有的箭头函数都没有自己的this,都指向外层==

《ES6 标准入门》中指出:“箭头函数”的this,总是指向定义时所在的对象,而不是运行时所在的对象。

也可以说是,总是指向所在函数运行时的this

箭头函数没有自身的this绑定,例如

var foo=()=> {
  console.log(this);
}

使用babel编译成ES5形式

var foo = function foo() {
  console.log(undefined);
};

在函数执行前绑定this的时候,传入的thisArguments会被直接忽略,也就是说箭头函数本身没法修改this,所以对this的访问永远是它继承外部上下文的this。

例如:

var foo=function (){
  var f=()=>{console.log(this)}
  f.call({name:'test'})
}
foo.call({name:'outer'})

输出结果为:

{name:'outer'}

babel的编译结果:

var foo = function foo() {
  var _this = this;

  var f = function f() {
    console.log(_this);
  };
  f.call({ name: 'test' });
};

上面的结果说明了call没有改变this的指向,因为从babel的编译结果来看,箭头函数中的this就不是自己的this,而是引用的外部this

以babel的实现来说,在箭头函数内部没有this引用的时候,默认编译成:

var f = function(v) {
  return v;
};

如果箭头函数内部使用了this,就会被编译成

function test() {
  var _this = this;

  var f = function f(v) {
    return _this.a;
  };
}

所以说,全程没有绑定这回事。箭头函数的this指向的是定义时所在的对象。

function foo() {
  return () => {
    return () => {
      return () => {
        console.log("id:", this.id);
      };
    };
  };
}

var f = foo.call({id: 1});

var t1 = f.call({id: 2})()();//id:1
var t2 = f().call({id: 3})();//id:1
var t3 = f()().call({id: 4});//id:1

上面代码的输出结果都是id:1,为什么会出现这种情况?

使用babel将函数foo转换成ES5形式

"use strict";

function foo() {
  var _this = this;

  return function () {
    return function () {
      return function () {
        console.log("id:", _this.id);
      };
    };
  };
}

验证了箭头函数没有自身的this绑定,其this只是一个引用了外部this的_this变量

五、多层对象嵌套里函数的this

参考的文章中,提出了问题:箭头函数里的this是和外层保持一致的,但是如果这个外层有好多层,那它是和哪层保持一致呢?

function test(){
  const obj = {
    a: function() { console.log(this) },
    b: {
        c: () => {console.log(this)}
    }
  }
    
}

babel编译后的结果

function test() {
  var _this = this;

  var obj = {
    a: function a() {
      console.log(this);
    },
    b: {
      c: function c() {
        console.log(_this);
      }
    }
  };
}

结果显示,多层对象嵌套里箭头函数的this指向的是最外层对象所在的上下文中的this。

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

推荐阅读更多精彩内容

  • 葡萄藤PPT JS中this的指向 大家好,我是IT修真院郑州分院第6期的学员王栋,一枚正直、纯洁、善良的前端程序...
    17064阅读 620评论 0 2
  • ES6 允许使用 “ 箭头 ” (=>)定义函数。 箭头函数 填 坑。 this的指向是 向上查找 非箭头函数的...
    kismetajun阅读 803评论 0 12
  • 前言 看了3.0 this的前置了解,总算能知道如何判断this的指向了,那么下面的知识也不难理解了。 总结 作为...
    饥人谷_远方阅读 313评论 0 0
  • 大家好,我是IT修真院北京总院第29期的学员禚洪宇,一枚正直、纯洁、善良的前端程序员今天给大家分享一下,修真...
    晚_cc3a阅读 190评论 0 0
  • 每次回家路过那熟悉的没人居住的院子就会想到她,眼前就会浮现出她说朴实真诚的笑脸,她是小时候一起长大的村里朋友的母亲...
    海深深阅读 446评论 2 3