D14 - 服务生!我要 this this this

前言

想像一下,今天你是负责帮忙点菜的服务生,来了一桌客人,拿着菜单跟你说 「 我要 this this this ! 」,请问你知道他点三小什麽吗,相信除非你会通灵不然光听绝对猜不出来,那怎麽判断呢?
看客人的手指向哪道菜判断那个 this 对应谁

在 JavaScript 中也会遇到这种不直接告诉你 this 是谁的情况,虽然这里没有手指头做参考,但别怕,教你另一招绝对精准的判断方式,包你以後一秒指认 this !


Mighty Plants

怎麽知道 this 是谁?

怎麽看出 this 是谁? 从呼叫方式判断
不同的呼叫方法下的 this 会不相同,呼叫方式可分为以下四种:

  1. 一般函式呼叫
  2. 函式作为 method 呼叫
  3. new constructor 呼叫
  4. call、apply 呼叫

接下来就一一从每个呼叫方法来认识 this 是谁吧!

一般函式呼叫

最直接的函式呼叫方式,就是在函式名称後加上一个 ()
不论是 function declarationfunction expression立即函式 都算在一般的函式呼叫内。

这时的 this 是谁:在一般函式呼叫下的 this 代表的是全域,在严格模式下则是 undefined

//函式宣告
function func(){
    console.log(this)
}
func()  // window

//函式表达式
let func = function(){
    console.log(this)
}
func()  // window

//立即函式
(function (){ console.log (this)})()

作为 Method 呼叫

当函式成为一个物件内的属性时,就可以称该函式为一个 方法method,呼叫的语法像是 物件名称.函式名称()

这时的 this 是谁:作为 method 呼叫下的 this 会指向 method 所属的物件。

var dish = '黄金开口笑'
function order() {
    console.log (this.dish)
}

let customer1 = {
    dish: '烈冰鲜鲷山',
    orderMore: order
}

let customer2 = {
    dish: '奇蹟彗星炒饭',
    orderMore: order
}

// 以下请自行加上 console.log
customer1.orderMore()   // '烈冰鲜鲷山' ,this 指向 customer1
customer2.orderMore()   // '奇蹟彗星炒饭',this 指向 customer2
order()   // '黄金开口笑', 一般函式呼叫 this 指向全域

变化题,若是将 customer1.orderMore 这个 method 存入一个变数中再进行呼叫呢?

let secondDish = customer1.orderMore;  // 将 customer1 的 orderMore 存入另一个变数再呼叫
secondDish()     // 黄金开口笑,此时的呼叫属於一般函式呼叫,this 指向全域

// 除非是将使用 method 呼叫後的结果存入变数,该变数才会是 this 指向物件的结果
function order() {
    return (this.dish)
}
let secondDish = customer1.orderMore();
console.log( secondDish )   //  烈冰鲜鲷山

new constructor 呼叫

上一篇讲到使用关键字 new 和建构式 constructor 创物件,这时的 this 会指向这个新创的物件,详见 D13 - 做出鸡蛋糕 new + Constructor

这时的 this 是谁:使用 new + constructor 下的 this 是新创的那个物件。

function Order(dish) {
    this.dish = dish
}

let customer1 = new Order('烈冰鲜鲷山')
let cusomter2 = new Order('奇蹟彗星炒饭')

console.log( customer1 ) // Order {dish: "烈冰鲜鲷山"}
console.log( customer2 ) // Order {dish: "奇蹟彗星炒饭"}

call、apply

点菜时除了依照客户的喜好,服务生也可以推荐菜色,让客人更改 「this」的选项为餐厅主打的招牌菜。

JavaScript 内也可以指定我们要的 this,透过所有函式都具备的两种方法:applycall,在呼叫函式的同时,在参数内放入希望 this 指向的物件。

这时的 this 是谁:使用 call 和 apply 小括号内放的第一个引数。

var dish = '黄金开口笑'
function order() {
    console.log (this.dish)
}

let customer1 = {
    dish: '烈冰鲜鲷山',
    orderMore: order
}

let customer2 = {
    dish: '奇蹟彗星炒饭',
    orderMore: order
}

customer1.orderMore.call(customer2) // 奇蹟彗星炒饭,强制 this 指向 customer2
customer2.orderMore.apply(window)   // 黄金开口笑,强制 this 指向全域 window

order()                // 黄金开口笑, 一般函式呼叫 order, this 指向全域
order.call(customer1)  // 烈冰鲜鲷山, 透过 call 绑定 this 指向 customer1

call 和 apply 的差异?

两者唯一的差异是引数的格式,第一个都是放 this 指定的物件,剩下的放欲带入函式的引数

function.call    ( 指定 this 的物件, 引数1, 引数2, 引数3)
function.apply   ( 指定 this 的物件, [引数1, 引数2, 引数3])

call 可以接受任一数量的引数,适用在有多个彼此不相关的变数或实质
apply 的引数须包在阵列内

bind、箭头函式

除了上面 call 和 apply 可以指定 this,另外两种绑定 this 的方式为: bind 和 箭头函式

为什麽要分开写呢? 因为这两种并不会进行函数呼叫,无法从呼叫判定,两者的 this 都是在定义时就绑定好

bind 的 this 绑定跟 apply 和 call 一样,在 () 中放入 指定的 this 物件

这时的 this 是谁:bind 依照小括号内放置的物件

var dish = '黄金开口笑'
function order() {
    console.log (this.dish)
}

let customer1 = {
    dish: '烈冰鲜鲷山',
    orderMore: order// 绑定 this 为 customer 2
}

let customer2 = {
    dish: '奇蹟彗星炒饭',
    orderMore: order.bind(customer1)
}

customer2.orderMore()                // 烈冰鲜鲷山
customer2.orderMore.call(customer2)  // 烈冰鲜鲷山,无法透过 call, apply 重新指定 this


let specialMenu = order.bind({dish: 'JS吃到饱'}) // 绑定 order function 内的 this
specialMenu()   // JS吃到饱

箭头函式为 ES6 加入的 function expression 新写法

  • 写法更简洁
  • this 的强制绑定

箭头函式下的 this 会指向创建时的物件,一般几乎指向全域,除非是使用建构式生成时,箭头函式的 this 指向新创的物件,因此在箭头函式内使用 this 要特别留意,一但定义了就无法重新绑定罗!

这时的 this 是谁:箭头函式下的 this 可能指向全域或是创建的物件。

var dish = '黄金开口笑'

let customer1 = {
    dish: '烈冰鲜鲷山',
    orderMore: ()=> this.dish,
    orderMoreMore: function(){
        return (this.dish)
    }
}

let customer2 = {
    dish: '奇蹟彗星炒饭',
}
 
// 虽然使用 method 方式呼叫,但因 orderMore 使用箭头函式, this 已绑定在全域
customer1.orderMore()                // 黄金开口笑
customer1.orderMore.call(customer2)  // 黄金开口笑,绑定在全域下无法透过 call、apply 更改 this

// 一般匿名函式的 this 不会先被绑定,随呼叫不同而改变
customer1.orderMoreMore()               // 烈冰鲜鲷山
customer1.orderMoreMore.call(customer2) // 奇蹟彗星炒饭

一般情况下的 this 都会指向全域,但在 constructor 内使用箭头函式的 this 可以绑定在新物件上


function Order(dish) {
    this.dish = dish;
    this.orderMore = ()=> this.dish
}

let customer1 = new Order('烈冰鲜鲷山')
let customer2 = new Order('奇蹟彗星炒饭')
customer1.orderMore()   // 烈冰鲜鲷山,箭头函式内的 this 指向新建立的物件 customer1
customer2.orderMore()   // 奇蹟彗星炒饭,箭头函式内的 this 指向新建立的物件 customer2

总结

以後遇到 this 想知道它到底指向谁,记住先看它是怎麽被呼叫的,再次整理如下

  • 一般函式呼叫: 非严格模式 - 全域、严格模式 - undefined
  • 作为 method 呼叫:this 指向 method 属於的该物件
  • new constructor 呼叫:this 指向 new 出来的新物件
  • apply、call:this 为 apply 及 call 放的第一个引数值
  • bind:this 为 bind 括号内指定的物件
  • 箭头函式:this 指向箭头函式建立时的物件,一般为全域,在 constructor 内建立的指向新物件

Reference

[008重新认识 JavaScipt]
忍者 JavaScript 开发技巧探秘第二版 by John Resig, Bear Bibeault, Josip Maras


<<:  【Day 16】深度学习(Deep Learning)--- Tip(一)

>>:  Day 15 -资料查询语言 INNER JOIN!

【Go】多维 slice / array

虽然之前有看过 slice / array 比较的文章, 但在写 leetcode 时还是碰到点小麻...

DAY 21 - 四足战车 (2)

大家好~ 我是五岁~ 今天来画四足战车的草图细节~ 首先来画上半部 依照昨天的外型草稿,进一步认真的...

javascript表单资料处理&验证(DAY22)

这篇文章会介绍如何使用DOM来处理表单的物件存取,以及利用条件判断式来处理表单的验证,像是在上一篇的...

数位化世界

人的科技文明发展始终来自於人性 在现今的科技与资讯发达的社会,人手一机已不再是奢望,连小小年纪的小朋...

什麽是 Github? 开发人员不能不知道的协同合作平台

本篇文章同步发布於个人部落格 (後续更新皆会以部落格为主): 什麽是 Github? 本系列文章会以...