JavaScript Day 30. 关於 JavaScript 中的 This

第 30 天,本来想说或许最後一天可以来一篇心得文,让自己好好休息一下,因为这 30 天花了大量的精神在一边找资料一边吸收知识顺便应付职场,整体觉得需要出去吃喝玩乐放烂一天╰(°▽°)╯

但又突然发现,我是跟着这 30 天结束了就会停止学习了吗?又或者是这 30 天之後我就不会再碰任何程序语言了?好像也不是,那假如不是的话,30 天之後再休息好像也是可以的 XD;所以,我们就继续来讨论个什麽吧 ˊ_>ˋ

this 到底指向谁?

想要理解 this,我们可以记住两个关键,第一 this 永远指向一个对象,第二 this 的指向取决於函式的调用以及呼叫方式。this 预设指向为全域性物件 window,并且 this 只能指向物件,哪个物件呼叫函式,函式里的 this 就预设指向哪个物件。

this 的调用分成几种:

  • 纯粹的调用 ( Simple call )
  • 物件方法调用 ( As an object method )
  • DOM 物件调用
  • 建构式调用
  • 透过 call、apply、bind 调用 this
  • 重新指向 this

纯粹的调用 ( Simple call )

直接调用函式的 this 会指向 window

window.doraemon = '哆啦A梦';
function callDoraemon() {
  console.log('call:', this.doraemon);
  // this == window
  // 所以此时的 this.doraemon 一样可以取得 window 下的 doraemon
}

callDoraemon(); // call: 哆啦A梦

另外这个例子是 function 包覆 function,不过这里的 this 依然是会指向全域,因为我们是直接呼叫,使用直接调用的方式不论在哪一层的 this 都会指向全域。

window.sonOfOdin = '索尔';
function callSonOfOdin () {
  console.log('call:', this.sonOfOdin); // call: 索尔

  // function 内的 function
  function callAgainSonOfOdin () {
    console.log('call again:', this.sonOfOdin);
  }
  callAgainSonOfOdin(); // call again: 索尔
}

callSonOfOdin(); 

物件方法调用 ( As an object method )

物件方法调用的重点在於,是在哪一个物件下被呼叫,以下范例会先给一个纯粹调用的例子,然後才会是物件方法调用的范例。

// 物件方法调用
function callAvengers() {
  console.log(this.avengers);
}

var avengers = 'iron Man';
var hulk = {
  avengers: 'hulk',
  callAvengers: callAvengers 
}

callAvengers()        // iron Man
hulk.callAvengers() // hulk,this 在 hulk 物件下被调用,因此指向 hulk

宣告的方法不重要,重要的是我们怎麽呼叫,如果我们将物件内的函式赋予在一个变数上并调用它。这个 this 也会指向全域。

var avengers = 'iron Man';
var hulk = {
  avengers: 'hulk',
  callAvengers: function () {
    console.log(this.avengers);
  }
}

callTheAvengers = hulk.callAvengers; 
callTheAvengers() // iron Man

DOM 物件调用

DOM 搭配 addEventListener 时,this 的指向则是该 DOM。这里卡斯伯老师说:接下来点击画面任何一区域,该区域则会加上红线。

var e = document.getElementsByTagName('div');
function changeDOM() {
  console.log(this); // 指向当前的 DOM
  this.style.border = '1px solid red'
}

for (var i = 0; i < e.length; i++) {
  e[i].addEventListener('click', changeDOM, false);
}

这边我们另外再参考一个简单的范例,这里不论点到哪里都会显示我们所点到的资讯,表示此时的 this 会指向我们点击到的 DOM。

let e = document.querySelector('body');

function changeDOM() {
  console.log(this);  // <body>...</body>
}

e.addEventListener('click', changeDOM);

建构式调用

建构式下会 new 出一个新物件,此时的 this 会指向新的物件。

function mickeyMouse () {
  this.mouse = '米奇'
}

var myFavourite = new mickeyMouse();
console.log(myFavourite.mouse); // 米奇

这里的 this 不是全域,而且可以在新生成的物件上重新定义,所以他会指向新生成的物件。

var disney = 'Mickey Mouse';
function disneySeries(role) {
  this.disney = role || 'Donald Duck';
}

var myFavourite = new disneySeries('Mickey');
var ordinary = new disneySeries();
console.log('我最喜欢', myFavourite.disney);    // 我最喜欢 Mickey
console.log('觉得还好', ordinary.disney);  // 觉得还好 Donald Duck

透过 call、apply、bind 调用 this

call()apply() 呼叫函式,然後我们设定 this 接着传入其他参数。

// 范例 1
let obj = {};

function foo() {
  console.log(this);
}

foo(); // "Window{}"
foo.call(obj); // Object{}
foo.apply(obj); // Object{}

两者第一个参数都是 this 值,也就是要绑定的物件,差异在於後面的参数。

// 范例 2
let obj = {};

function foo(a, b) {
  console.log(this, a, b);
}

foo.call(obj, 1, 2); // Object{} 1 2
foo.apply(obj, [1, 2]); // Object{} 1 2

call() 呼叫与平常函式呼叫方法相同,apply() 则需要使用阵列包起来。

bind() 会回传一个新的函式,当被呼叫时,会将提供的值设为 this 值。

// 范例 1
function foo() {
  console.log(this.name);
}

let obj = {
  name: 2,
};

let boo = foo.bind(obj);

boo(); // 2
setTimeout(boo, 100); // 2
boo.call(window); // 2

bind() 方法第一个也是 this 後面的参数则跟函式一样。

// 范例 2
let obj = {};

function foo(a, b) {
  console.log(this, a, b);
}

const myFoo = foo.bind(obj, 1, 2);

myFoo(); // Object{} 1 2

如果使用 name 属性查看 bind() 所创建的函式,将会在函式的名称前加上 "bound"

// 范例 3
let obj = {};

function foo() {
  console.log(this);
}

const myFoo = foo.bind(obj);

console.log(myFoo.name);  // "bound foo"

重新指向 this

立即函式 ( IIFE ) 或是非同步事件 ( setTimeout ) 大多会指向全域,如果需要调用的是物件本身,可以用一个变数指向 this,等到调用後再重新使用它。

function callName() {
  console.log('区域', this.name); // 区域 东尼史塔克
  var that = this;
  setTimeout(function () {
    console.log('全域', this.name); // 全域 索尔
    console.log('区域', that.name); // 区域 东尼史塔克
  }, 10);
}

var name = '索尔';
var ironMan = {
  name: '东尼史塔克',
  callName: callName
}

ironMan.callName();

上面我们看到一个 that,实际上这个变数名称我们可以自己定义,常用的有 thatvmself,办实际上还是可以照自己的习惯命名。


虽然我成功写完了这 30 篇文章,但对我来说这其实不是结束,反而只是开始,所以自己还是希望如果之後时间可以,或许也还会以发文的方式继续和大家一起讨论 JavaScript,也希望看过文章发现错误的朋友能够告知错误,也欢迎有新想法的朋友能一起讨论,相信在讨论的过程中,JavaScript 会慢慢变的越来越有趣 (⊙ꇴ⊙)

参考资料:

JavaScript 的 this 到底是谁?
JavaScript 的 this
JS 原力觉醒 Day17 - this 的四种系结


<<:  [25] 用 python 刷 Leetcode: 155 min-stack

>>:  Day24 - 遇到 404 或 500 怎麽办,客制化错误页面

[Day15] Andoroid - Kotlin笔记: MVVM简介

MVVM由三项组成。 分别为(Model、View、ViewModel) 先来上MVVM架构图,方便...

win破解後可以接着使用商业的大量授权吗

各位大大们 你好 小弟想询问一下,因为使用大量授权好像前面都需要先安装过win授权,像是随机版之类的...

Day 23: 174. Dungeon Game

Day 23: 174. Dungeon Game Tag:每月挑战(2021.10.02) Sou...

Day 19 - Spring Boot & Cookie

Cookie 介绍 Cookie 指得是储存在Client (用户)端上的资料,是一种在服务器与浏览...

强型闯入DenoLand[29] - 去标签密技

强型闯入DenoLand[29] - 去标签密技 笔者在昨天非常粗略地介绍了 Oak 的概念。此外...