JavaScript中this之老生常谈

本文主要介绍 JavaScriptthis 的问题,这是每个学习 JavaScript 都绕不开研究的问题 ,而且有时候会越学越迷糊,本文从实用的角度出发来理解 this,因为要彻底的弄懂this并不是一件容易的事,这涉及到 ECMAScript标准规范,浏览器就是一局这套规范来解析 this 的指向的,学完本篇文章可以应付实际开发中80%的场景,要想彻底弄懂 this 可以参考此篇文章JavaScript深入之从ECMAScript规范解读this

作用域

在学习 this之前先来学习下 JavaScript 中的作用域,因为 this的理解机制与作用域刚好相反,理解了作用域的机制能更好的理解this的精髓。几乎每个编程语言都有作用域的概念,作用域又分为 静态作用域动态作用域,在 JavaScript 中作用域是静态作用域。在 ES6 以前,唯一产生作用域的方法就是 function,每一个 function 都有自己的作用域,在作用域外面你就存取不到这个 function 內部所定义的变量。然而 ES6的时候引入了 letconst 关键字,多了 block 的作用域。

var a = 100
function echo() {
  console.log(a) // 100 or 200?
}
  
function test() {
  var a = 200
  echo()
}
  
test()

上面的例子输出什么?只要迟疑了,说明还没有理解透彻。所谓 静态作用域 是指函数在定义的时候就决定了函数的作用域,与之后函数如何调用没有关系了,总结为一句话就是:函数在定义的时候就已经确定好了作用域!所以不管 echo 是在哪里调用的,其外层作用域就是全局作用域而不是 test 函数的作用域,由于 echo 函数内部没有 a 这个变量,所以会向外层作用域进行寻找,此时就找到了全局作用域下的 a = 100 这个变量,打印出来就是 100

经典的例子

再来看一个经典的例子,上面我们说过,在 ES6 以前,唯一产生作用域的方法就是 function,每一个 function 都有自己的作用域,其他方式是无法产生作用域的。

var btn = document.querySelectorAll('button')
for(var i=0; i<=4; i++) {
  btn[i].addEventListener('click', function() {
    alert(i)
  })
}

假设有五个按钮,上面的代码当我们点击每个按钮时会显示对应按钮的序号吗? 答案是全部显示 5,是不是感觉狠惊讶?下面我们来分析。

分析

由于只有函数可以产生作用域,而由于传入的函数内没有定义 i 这个变量,所以当点击按钮的时候会去其外层作用域寻找,而此时由于 for 循环已经执行完毕,此时的i的值已经是 5 了,所以无论点击哪个按钮都会显示 5

改如何才能显示对应的序号呢?

其实解决办法有二个,一个是让传入的函数自己的作用域内能有对应的 i,另外一个是假如传入的函数的作用域内没有对应的 i,在它向外层作用域寻找时能找到对应的 i

解决方案一

要让函数内部的作用域内能有对应变量,首先想到的一种方式就是把变量当做参数传入即可。

function getAlert(num) {
  return function() {
    alert(num)
  }
}
for(var i=0; i<=4; i++) {
  btn[i].addEventListener('click', getAlert(i))
}

或者:

function getAlert(num) {
  return function() {
    alert(num)
  }
}
for(var i=0; i<=4; i++) {
  btn[i].addEventListener('click', getAlert(i))
}

解决方案二

要想去外层作用域内找到不同的变量,那就得每个函数的外层作用域都不能一样,而不是都去全局的作用域或者同一个作用域下进行寻找。

for(let i=0; i<=4; i++) {
  btn[i].addEventListener('click', function() {
    alert(i)
  })
}

发现了吗,只是将关键字 var 改成了 let,因为 ES6let关键字会生成块作用域也就是代码块,所以建议开发中全部采用let 关键字,上面的代码可以看成这样的组织形式:

 { // 块级作用域
  let i=0
  btn[i].addEventListener('click', function() {
    alert(i)
  })
}
{ // 块级作用域
  let i=1
  btn[i].addEventListener('click', function() {
    alert(i)
  })
}
...

上面的我们简要的阐述了作用域的概念,我们始终要记住最核心的东西就是:

  • 函数在定义的时候就已经确定好了作用域!
  • letconst 关键字,多了 block 的作用域。

this的理解

现在我们正式切入 this 这个老生常谈的问题,首先请记住:this 的具体指向要看这个函数是怎么调用的。这和作用域的机制是相反的,作用域是在函数定义的时候就确定了,与函数的调用是无关的,而 this 且是在函数的调用时才决定的,不同的调用会导致 this 的指向是不一样的,这就有点动态的概念。

var x = 10
var obj = {
  x: 20,
  fn: function() {
    var test = function() {
      console.log(this.x)
    }
    test()
  }
}
obj.fn()

上面这个应该输出多少?在讲解之前请容我再次强调一句:this 的具体指向要看这个函数是怎么调用的。最后的 this 是在 test 中显示的,test 最后是谁在调用,这里我们可以看成window 在调用(非严格模式下)也就是 window.test(),所以会找到全局作用域下的 x=10

关于call,apply和bind

这几个函数都可以改变覆盖 this 的指向从而更改 this 的指向,callapply 基本上相同,只是传参的方式不太一样,apply 接受的是一个数组。

class Car {
  hello() {
    console.log(this)
  }
}
const myCar = new Car()
myCar.hello() // myCar instance
myCar.hello.call('yaaaa') // yaaaa
myCar.hello.apply('yaaaa') // yaaaa

bind 是返回一个新的函数:

'use strict';
function hello() {
  console.log(this)
}
const myHello = hello.bind('my')
myHello() // my

可以发下 callapplybind 都可以改变 this 的指向,需要注意的是一旦用 bind 绑定 this 后,在使用 call 来想改变 this 的指向是不行的。

'use strict';
function hello() {
  console.log(this)
}
const myHello = hello.bind('my')
myHello.call('call') // my

用call来确定this

只要把函数的调用转成 call 的方式调用,就能狠容易的看出 this 指向的是什么。

const obj = {
  value: 1,
  hello: function() {
    console.log(this.value)
  },
  inner: {
    value: 2,
    hello: function() {
      console.log(this.value)
    }
  }
}

const obj2 = obj.inner
const hello = obj.inner.hello
obj.inner.hello()
obj2.hello()
hello()

上面的函数调用在转化成 call 的方式后如下:

obj.inner.hello() // obj.inner.hello.call(obj.inner) => 2
obj2.hello() // obj2.hello.call(obj2) => 2
hello() // hello.call() => undefined

箭头函数中的this

箭头函数中的 this 则是遵循"在定义它的地方的 this 是什么,它的 this 就是什么"。直白点就是箭头函数在哪个作用域定义的,那个作用域的 this 是什么,箭头函数中的 this 就是什么,当然作用域中的 this 也要结合上面的原则进行判断。

const obj = {
  x: 1,
  hello: function(){
    // 这里的this是什么,test的this就是什么
    // 就是我说的:
    // 在定义它的地方的this是什么,test的this就是什么
    console.log(this)     
    const test = () => {
      console.log(this.x)
    }
    test()
  }
}

obj.hello() // 1
const hello = obj.hello
hello() // undefined

上面的例子可以看出,不用方式调用 hello会导致 console.log(this) 这句代码的作用域中 this的指向不用,进而导致箭头函数中的 this 指向也不一样。

总结

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

推荐阅读更多精彩内容

  • JavaScript中的this实际上是在函数被调用时发生的绑定,它指向什么完全取决于函数在哪里被调用。 先来列举...
    __MYSTERY阅读 174评论 1 5
  • 写在最前:文章转自掘金 this在JavaScript中是非常重要的概念,因为我们用到它的频率非常之高,在享受到它...
    没名字的某某人阅读 245评论 0 1
  • this的指向 全局作用域 无论是否在严格模式下,在全局执行环境中(在任何函数体外部)this 都指向全局对象。 ...
    locky丶阅读 221评论 0 1
  • 1. this的作用 从某些角度来说, 开发中如果没有this, 很多的问题我们也是有解决方案 但是没有this,...
    未路过阅读 115评论 0 0
  • this是 JavaScript 语言的一个关键字。 它是函数运行时,在函数体内部自动生成的一个对象,只能在函数体...
    ERICOOLU阅读 454评论 0 0