首頁 web前端 js教程 JavaScript中this詳解_javascript技巧

JavaScript中this詳解_javascript技巧

May 16, 2016 pm 03:41 PM
javascript this

都說 JavaScript 是一種很靈活的語言,這其實也可以說它是一個混亂的語言。它把函數式程式設計和物件導向程式設計糅合一起,再加上動態語言特性,簡直強大無比(其實是不能和C 比的,^_^ )。

這裡的主題是 this ,不扯遠了。 this 本身原本很簡單,總是指向類別的目前實例,this 不能賦值。這前提是說 this 不能脫離 類別/物件 來說,也就是說 this 是物件導向語言裡常見的一個關鍵字。說的極端點,如果你寫的 JS 採用函數式寫法,而不是物件導向式,你所有的程式碼裡 this 會少很多,甚至沒有。記住這一點,當你使用 this 時,你應該是在使用物件/類別 方式開發,否則 this 只是函數呼叫時的副作用。

JS 裡的 this

在 function 內部被建立
指向呼叫時所在函數所綁定的物件(拗口)
this 不能被賦值,但可以被 call/apply  改變

以前用 this 時常擔心,不踏實,你不知道它到底指向誰? 這裡把它所有用到的地方列出

this 和構造器
this 和物件
this 和函數
全域環境的 this
this 和 DOM/事件
this 可以被 call/apply 改變
ES5 新增的 bind 和 this
ES6 箭頭函數(arrow function) 和 this

1. this 與建構子

this 本身就是類別定義時構造器裡需要用到的,和構造器在一起再自然不過。

/**
 * 页签
 *
 * @class Tab
 * @param nav {string} 页签标题的class
 * @param content {string} 页面内容的class
 *
 */
function Tab(nav, content) {
  this.nav = nav
  this.content = content
}
 
Tab.prototype.getNav = function() {
  return this.nav;
};
Tab.prototype.setNav = function(nav) {
  this.nav = nav;
};
Tab.prototype.add = function() {
};
登入後複製

按照 JavaScript 的習慣, this 應該掛屬性/字段,方法都應該放在原型上。

2. this 和物件

JS 中的物件不用類別也可以創建,有人可能奇怪,類別是物件的模板,物件都是從模板裡 copy 出來的,沒有類別怎麼創建物件? JS 的確可以,而且你完全可以寫上萬行功能程式碼而不用寫一個類別。話說 OOP 裡說的是物件導向編程,也沒說面向類編程,是吧 ^_^ 。

var tab = {
  nav: '',
  content: '',
  getNav: function() {
    return this.nav;
  },
  setNav: function(n) {
    this.nav = n;
  }
}

登入後複製

3. this 和函數

首先,this 和獨立的函數放在一起是沒有意義的,前面也提到 this 應該是和物件導向相關的。純粹的函數只是一個低階的抽象,封裝和復用。如下

function showMsg() {
  alert(this.message)
}
showMsg() // undefined
登入後複製

定義 showMsg,然後以函數方式調用,this.message 是 undefined。因此堅決杜絕在 純函數內使用 this,但有時候會這麼寫,呼叫方式使用 call/apply

function showMsg() {
  alert(this.message)
}
 
var m1 = {
  message: '输入的电话号码不正确'
}
var m2 = {
  message: '输入的身份证号不正确'
}
 
showMsg.call(m1) // '输入的电话号码不正确'
showMsg.call(m2) // '输入的身份证号不正确'
登入後複製

用這種方式可以節省一些程式碼量,例如當兩個 類別/物件 有一共相似的方法時,不必寫兩份,只要定義一個,然後將其綁定在各自的原型和物件上。這時候其實你還是在使用物件或類別(方式1/2),只是間接使用罷了。

4. 全域環境的 this

前面提到 this 是 “指向調用時所在函數所綁定的對象”, 這句話拗口但絕對正確,沒有一個多餘的字。全域環境中有不同的宿主對象,瀏覽器環境中是 window, node 環境中是 global。這裡重點是說下瀏覽器環境中的 this。

瀏覽器環境中非函數內 this 指向 window

alert(window=== this) // true
因此你會看很多開源 JS lib 這麼寫

(function() {
    // ...
    
})(this);
或這樣寫

(function() {
    // ...
  
}).call(this);
例如 underscore 和 requirejs,大意是把全域變數 window 傳入匿名函數內部快取起來,避免直接存取。至於為啥要緩存,這跟 JS 作用域鏈有關係,讀取越外層的標識符效能會越差。請自行查閱相關知識,再說就扯遠了。

瀏覽器中比較坑人,非函數內直接使用 var 宣告的變數預設為全域變量,且預設掛在 window 上作為屬性。

var andy = '刘德华'
alert(andy === window.andy) // true
alert(andy === this.andy) // true
alert(window.andy === this.andy) // true

登入後複製

因為這個特性,有些筆試題如

var x = 10;
function func() {
  alert(this.x)
}
var obj = {
  x: 20,
  fn: function() {
    alert(this.x)
  }
}
var fn = obj.fn
func() // 10
fn() // 10
登入後複製

没错,最终输出的都是全局的 10。永远记住这一点:判断 this 指向谁,看执行时而非定义时,只要函数(function)没有绑定在对象上调用,它的 this 就是 window。

5. this 和 DOM/事件

W3C 把 DOM 实现成了各种节点,节点嵌套一起形成 DOM tree。节点有不同类型,如文本节点,元素节点等10多种。元素节点又分成了很多,对写HTML的人来说便是很熟悉的标签(Tag),如 div,ul,label 等。 看 W3C 的 API 文档,会发现它完全是按照面向对象方式实现的各种 API,有 interface,extends 等。如

看到了吧,这是用 Java 写的,既然是用面向对象方式实现的API,一定有类/对象(废话^_^),有 类/对象,则一定有 this (别忘了这篇文章的中心主题)。所有的 HTML tag 类命名如 HTMLXXXElement,如

HTMLDivElement
HTMLLabelElement
HTMLInputElement
...
前面说过 this 是指向当前类的实例对象,对于这些 tag 类来说,不看其源码也知它们的很多方法内部用到的 this 是指向自己的。 有了这个结论,写HTML和JS时, this 就清晰了很多。

示例A

<!-- this 指向 div -->
<div onclick="alert(this)"></div>

登入後複製

示例B

<div id="nav"></div>
<script>
  nav.onclick = function() {
    alert(this) // 指向div#nav
  }
</script>

登入後複製

示例C

$('#nav').on('click', function() {
  alert(this) // 指向 nav
})

登入後複製

以上三个示例可以看到,在给元素节点添加事件的时候,其响应函数(handler)执行时的 this 都指向 Element 节点自身。jQuery 也保持了和标准一致,但却让人迷惑,按 “this 指向调用时所在函数所绑定的对象” 这个定义,jQuery 事件 handler 里的 this,应该指向 jQuery 对象,而非 DOM 节点。因此你会发现在用 jQuery 时,经常需要把事件 handler 里的 element 在用 $ 包裹下变成 jQuery 对象后再去操作。比如

$('#nav').on('click', function() {
  var $el = $(this) // 再次转为 jQuery 对象,如果 this 直接为 jQuery 对象更好
  $el.attr('data-x', x)
  $el.attr('data-x', x)
})

登入後複製

有人可能有如下的疑问

<div id="nav" onclick="getId()">ddd</div>
<script>
  function getId() {
    alert(this.id)
  }
</script>
登入後複製

点击 div 后,为什么 id 是 undefined,不说是指向的 当前元素 div 吗? 如果记住了前面提到的一句话,就很清楚为啥是 undefined,把这句话再贴出来。

判断 this 指向谁,看执行时而非定义时,只要函数(function)没有绑定在对象上调用,它的 this 就是 window

这里函数 getId 调用时没有绑定在任何对象上,可以理解成这种结构

div.onclick = function() {
  getId()
}
登入後複製

getId 所处匿名函数里的 this 是 div,但 getId 自身内的 this 则不是了。 当然 ES5 严格模式下还是有个坑。

6. this 可以被 call/apply 改变

call/apply 是函数调用的另外两种方式,两者的第一个参数都可以改变函数的上下文 this。call/apply 是 JS 里动态语言特性的表征。动态语言通俗的定义

程序在运行时可以改变其结构,新的函数可以被引进,已有的函数可以被删除,即程序在运行时可以发生结构上的变化

通常有以下几点特征表示它为动态语言

动态的数据类型
动态的函数执行
动态的方法重写
动态语言多从世界第二门语言 LISP 发展而来,如死去的 SmallTalk/VB,目前还活着的 Perl/Python, 以及还流行的 Ruby/JavaScript。JS 里动态数据类型的体现便是弱类型,执行的时候才去分析标识符的类型。函数动态执行体现为 eval,call/aply。方法重写则体现在原型重写。不扯远,这里重点说下 call/apply 对 this 的影响。

var m1 = {
  message: 'This is A'
} 
var m2 = {
  message: 'This is B'
} 
 
function showMsg() {
  alert(this.message)
}
 
showMsg() // undefined
showMsg.call(m1) // 'This is A'
showMsg.call(m2) // 'This is B'
登入後複製

可以看到单独调用 showMsg 返回的是 undefined,只有将它绑定到具有 message 属性的对象上执行时才有意义。发挥想象力延伸下,如果把一些通用函数写好,可以任意绑定在多个类的原型上,这样动态的给类添加了一些方法,还节省了代码。这是一种强大的功能,也是动态语言的强表现力的体现。

经常会听到转向 Ruby 或 Python 的人提到“编程的乐趣”,这种乐趣是源自动态语言更接近人的思维(而不是机器思维),更符合业务流程而不是项目实现流程。同样一个功能,动态语言可以用更小的代码量来实现。动态语言对程序员生产力的提高,是其大行其道的主要原因。

性能方面,动态语言没有太大的优势,但动态语言的理念是:优化人的时间而不是机器的时间。提高开发者的生产力,宁肯牺牲部分的程序性能或者购买更高配置的硬件。随着IT业的不断发展和摩尔定律的作用,硬件相对于人件一直在贬值,这个理念便有了合理的现实基础。

JS 里的 call/apply 在任何一个流行的 lib 里都会用到,但几乎就是两个作用

配合写类工具实现OOP,如 mootools, ClassJS, class.js,
修复DOM事件里的 this,如 jQuery, events.js

关于 call 和 apply 复用:利用apply和arguments复用方法

关于 call 和 apply 的性能问题参考: 冗余换性能-从Backbone的triggerEvents说开了去

7. ES5 中新增的 bind 和 this

上面 6 里提到 call/apply 在 JS 里体现动态语言特性及动态语言的流行原因,其在 JS 用途如此广泛。ES5发布时将其采纳,提了一个更高级的方法 bind。

var modal = {
  message: 'This is A'
}
 
function showMsg() {
  alert(this.message)
}
 
var otherShowMsg = showMsg.bind(modal)
otherShowMsg() // 'This is A'
登入後複製

因为是ES5才加的,低版本的IE不支持,可以修复下Function.prototype。bind 只是 call/apply 的高级版,其它没什么特殊的。

8. ES6 箭头函数(arrow function) 和 this

ES6 在今年的 6月18日 正式发布(恰京东店庆日同一天,^_^),它带来的另一种类型的函数 - 箭头函数。箭头函数的一个重要特征就是颠覆了上面的一句话,再贴一次

判断 this 指向谁,看执行时而非定义时,只要函数(function)没有绑定在对象上调用,它的 this 就是 window

是的,前面一直用这句话来判断 this 的指向,在箭头函数里前面半句就失效了。箭头函数的特征就是,定义在哪,this 就指向那。即箭头函数定义在一个对象里,那箭头函数里的 this 就指向该对象。如下

var book = {
  author: 'John Resig',
  init: function() {
    document.onclick = ev => {
      alert(this.author) ; // 这里的 this 不是 document 了
    }
  }
};
book.init()
登入後複製

对象 book 里有一个属性 author, 有一个 init 方法, 给 document 添加了一个点击事件,如果是传统的函数,我们知道 this 指向应该是 document,但箭头函数会指向当前对象 book。

箭头函数让 JS 回归自然和简单,函数定义在哪它 this 就指向哪,定义在对象里它指向该对象,定义在类的原型上,指向该类的实例,这样更容易理解。

总结:

函数的上下文 this 是 JS 里不太好理解的,在于 JS 函数自身有多种用途。目的是实现各种语言范型(面向对象,函数式,动态)。this 本质是和面向对象联系的,和写类,对象关联一起的, 和“函数式”没有关系的。如果你采用过程式函数式开发,完全不会用到一个 this。 但在浏览器端开发时却无可避免的会用到 this,这是因为浏览器对象模型(DOM)本身采用面向对象方式开发,Tag 实现为一个个的类,类的方法自然会引用类的其它方法,引用方式必然是用 this。当你给DOM对象添加事件时,回调函数里引用该对象就只能用 this 了。

明白了么?

相信看完全文以后,this不再是坑~

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

如何使用WebSocket和JavaScript實現線上語音辨識系統 如何使用WebSocket和JavaScript實現線上語音辨識系統 Dec 17, 2023 pm 02:54 PM

如何使用WebSocket和JavaScript實現線上語音辨識系統引言:隨著科技的不斷發展,語音辨識技術已成為了人工智慧領域的重要組成部分。而基於WebSocket和JavaScript實現的線上語音辨識系統,具備了低延遲、即時性和跨平台的特點,成為了廣泛應用的解決方案。本文將介紹如何使用WebSocket和JavaScript來實現線上語音辨識系

WebSocket與JavaScript:實現即時監控系統的關鍵技術 WebSocket與JavaScript:實現即時監控系統的關鍵技術 Dec 17, 2023 pm 05:30 PM

WebSocket與JavaScript:實現即時監控系統的關鍵技術引言:隨著互聯網技術的快速發展,即時監控系統在各個領域中得到了廣泛的應用。而實現即時監控的關鍵技術之一就是WebSocket與JavaScript的結合使用。本文將介紹WebSocket與JavaScript在即時監控系統中的應用,並給出程式碼範例,詳細解釋其實作原理。一、WebSocket技

如何利用JavaScript和WebSocket實現即時線上點餐系統 如何利用JavaScript和WebSocket實現即時線上點餐系統 Dec 17, 2023 pm 12:09 PM

如何利用JavaScript和WebSocket實現即時線上點餐系統介紹:隨著網路的普及和技術的進步,越來越多的餐廳開始提供線上點餐服務。為了實現即時線上點餐系統,我們可以利用JavaScript和WebSocket技術。 WebSocket是一種基於TCP協定的全雙工通訊協議,可實現客戶端與伺服器的即時雙向通訊。在即時線上點餐系統中,當使用者選擇菜餚並下訂單

如何使用WebSocket和JavaScript實現線上預約系統 如何使用WebSocket和JavaScript實現線上預約系統 Dec 17, 2023 am 09:39 AM

如何使用WebSocket和JavaScript實現線上預約系統在當今數位化的時代,越來越多的業務和服務都需要提供線上預約功能。而實現一個高效、即時的線上預約系統是至關重要的。本文將介紹如何使用WebSocket和JavaScript來實作一個線上預約系統,並提供具體的程式碼範例。一、什麼是WebSocketWebSocket是一種在單一TCP連線上進行全雙工

JavaScript與WebSocket:打造高效率的即時天氣預報系統 JavaScript與WebSocket:打造高效率的即時天氣預報系統 Dec 17, 2023 pm 05:13 PM

JavaScript和WebSocket:打造高效的即時天氣預報系統引言:如今,天氣預報的準確性對於日常生活以及決策制定具有重要意義。隨著技術的發展,我們可以透過即時獲取天氣數據來提供更準確可靠的天氣預報。在本文中,我們將學習如何使用JavaScript和WebSocket技術,來建立一個高效的即時天氣預報系統。本文將透過具體的程式碼範例來展示實現的過程。 We

簡易JavaScript教學:取得HTTP狀態碼的方法 簡易JavaScript教學:取得HTTP狀態碼的方法 Jan 05, 2024 pm 06:08 PM

JavaScript教學:如何取得HTTP狀態碼,需要具體程式碼範例前言:在Web開發中,經常會涉及到與伺服器進行資料互動的場景。在與伺服器進行通訊時,我們經常需要取得傳回的HTTP狀態碼來判斷操作是否成功,並根據不同的狀態碼來進行對應的處理。本篇文章將教你如何使用JavaScript來取得HTTP狀態碼,並提供一些實用的程式碼範例。使用XMLHttpRequest

javascript如何使用insertBefore javascript如何使用insertBefore Nov 24, 2023 am 11:56 AM

用法:在JavaScript中,insertBefore()方法用於在DOM樹中插入一個新的節點。這個方法需要兩個參數:要插入的新節點和參考節點(即新節點將要插入的位置的節點)。

JavaScript與WebSocket:打造高效率的即時影像處理系統 JavaScript與WebSocket:打造高效率的即時影像處理系統 Dec 17, 2023 am 08:41 AM

JavaScript是一種廣泛應用於Web開發的程式語言,而WebSocket則是一種用於即時通訊的網路協定。結合二者的強大功能,我們可以打造一個高效率的即時影像處理系統。本文將介紹如何利用JavaScript和WebSocket來實作這個系統,並提供具體的程式碼範例。首先,我們需要明確指出即時影像處理系統的需求和目標。假設我們有一個攝影機設備,可以擷取即時的影像數

See all articles