javascript
深入理解JS中this关键字
為什么要使用this關鍵字
???看個例子
function indetify() {retun this.name.toUpperCase()}var obj = {name: 'zz'}indetify.call(obj) // 'ZZ' 復制代碼這里函數identify中的this指向變量obj,如果不使用this的話,想實現這種效果,就需要顯示的傳入上下文對象,例如
function indetify(context) {retun context.name.toUpperCase()}var obj = {name: 'zz'}indetify(obj) // 'ZZ' 復制代碼當模式越來越復雜的時候,顯示的傳遞上下文對象就會變得不太好管理,而this提供了一種更優雅的方式,隱式的"傳遞"一個對象的引用,因此可以將API設計的更加簡潔并且易于復用。
對于this的誤解
this到底是什么
this是在運行的時候被綁定的,并不是編寫的時候,它的上下文取決于函數調用的各種條件。當一個函數被調用時,會創建一個活動記錄(即執行上下文),這個記錄包含函數的調用棧(call Stack),調用方式,傳入參數等信息,this就是這個記錄的一個屬性,在函數執行的過程中找到。
要想找到this的綁定對象,首先得找到函數的調用位置,調用位置就在當前執行函數的前一個調用中。
function baz () {// 當前調用棧是baz// 當前調用位置是全局作用域console.log('baz')bar() // bar的調用位置}function bar () {// 當前的調用棧是baz -> bar// 所以當前的調用位置在baz中console.log('bar')foo() // foo的調用位置}function foo () {// 當前的調用棧是baz -> bar -> foo// 所以當前的調用位置在bar中console.log('foo')}baz() // baz的調用位置 復制代碼找到調用位置,接下來就要分析this的綁定規則了。
this綁定規則
因為foo()是直接不帶任何修飾的函數引用進行調用的,所以只能使用默認綁定。非嚴格模式下指向window,嚴格模式下綁定到undefined
分析隱式綁定時,必須在一個對象內部包含一個指向函數的屬性,通過這個屬性間接引用函數
function foo () {console.log(this.a) }var obj = {a: 2,foo: foo }obj.foo() // 2 復制代碼首先要聲明的是,無論foo函數是先聲明還是直接在obj對象中定義,這個函數嚴格來說,都不屬于obj對象。
隱式綁定會遇到一個問題,就是會丟失綁定對象,也就是會應用默認綁定。比如
function foo () {console.log(this.a) }var obj = {a: 2,foo: foo }var another = obj.foo // 函數別名var a = '隱式丟失'obj.foo() // 2 another() // '隱式丟失' 復制代碼這里雖然bar是obj.foo的一個引用,但實際上引用的是foo函數本身,因此bar()是不帶任何修飾的函數調用,所以也是默認綁定。
還有一種更微妙的情況,發生在傳入回調函數的時候
function foo () {console.log(this.a) }var obj = {a: 2,foo: foo }function doFun (fn) {// fn 其實是引用foo// 相當于var fn = foofn() }var a = '又隱式丟失'obj.foo() // 2 doFun(obj.foo) // '又隱式丟失' 復制代碼實際上傳遞參數,實際上就是一種賦值操作,所以結果和上面一樣
通常情況,我們使用js提供的call和apply方法實現顯示綁定
這倆個方法的第一個參數是一個對象,是給this準備的,在調用函數是將函數綁定到this,因此稱為顯示綁定。第二個參數就是一個參數列表或參數對象,就是傳遞函數的調用的參數。
function foo (name) {console.log(this.a+name) }var obj = {a: 1 }foo.call(obj,'顯示綁定') // 1'顯示綁定' 復制代碼但是,顯示綁定還是會出現綁定丟失的情況,能有辦法解決嗎?當然有
- 硬綁定
我們創建了函數bar(),并且在內部手動調用foo.call(obj),強制將foo的this綁定到了obj,無論后面如何調用函數bar,都會手動在obj上調用foo
- API調用的“上下文”
js語言和宿主環境中許多新的內置函數,都提供了一個可選參數,通常稱為“上下文”(context),作用和bind(..)一樣,確保回調函數使用指定的this
function foo (el) {console.log(el, this.id)}var obj = {id: 'awesome'}// 調用foo的同時把this綁定到obj上[1,2,3].forEach(foo, obj)// 1'awesome' 2'awesome' 3'awesome' 復制代碼js中的new的機制和面向類的語言完全不同
我們習慣把new的函數叫做構造函數,實際上,只是使用new操作符時被調用的函數,不會實例化一個類,因為實例化的類與類之間是復制,是兩位完全沒關系的,但js不是。只能說這些函數是被new操作符調用的普通函數而已。
首先,我們看看new操作符會做些什么
創建(或者說構造)一個全新的對象
這個對象會被執行[[prototypt]]鏈(原型鏈)
新的對象會綁定到函數調用的this上
如果函數沒有返回對象,那new表達式中的函數會自動返回這個新對象
優先級
既然this有這么多種綁定方式,肯定會存在綁定的優先級
首先,毫無疑問,默認綁定的優先級是最低的
很顯然,顯示綁定的優先級比隱式綁定的高。
看來new綁定的優先級是比隱式綁定高的,最后我們看一下new和顯示綁定誰的優先級高,因為new和call/apply無法一起使用,所以沒法通過new foo.call(obj1)來直接測試,我們選擇用硬綁定來測試。
回憶一下硬綁定是如何工作的,Function.prototype.bind(..)會創建一個新的包裝函數,這個函數會忽略它當前的this綁定,并把提供的對象綁定到this上。
bar被硬綁定到obj1上,但是new bar(2),并沒有將obj1.a修改成2,相反,new修改了硬綁定(到obj1的)調用bar(..)中的this。這樣看來new調用的函數,新創建的this替換了硬綁定的參數,所以new的優先級是最高的。
那我們判定優先級的方法就是從優先級由高往下去判定。
綁定例外
凡事都有例外,不是所有的綁定都遵循這個規則的。
如果是call(null)或者apply(undefined),這里對應的其實是默認綁定,因為其實,null和undefined只是基礎的數據類型,并不是對象。
軟綁定
硬綁定可以強制綁定到指定對象,但是這大大降低了函數的靈活性,之后無法使用隱式或顯示綁定修改this的指向,所以我們來實現一下軟綁定
// 實現軟綁定if (!Function.prototype.softBind) {Function.prototype.softBind = function (obj) {var fn = this// 捕獲所有curried參數var curried = [].slice.call(arguments, 1)var bound = function () {return fn.apply((!this || this === (window || global)) ?obj : this,curried.concat.apply(curried, arguments))}bound.prototype = Object.create(fn.prototype)return bound}}// 驗證function foo () {console.log("name: " + this.name)}var obj1 = { name: "obj1"}var obj2 = { name: "obj2"}var obj3 = { name: "obj3"}var fooOBJ = foo.softBind(obj)fooOBJ() // "obj"obj2.foo = foo.softBind(obj)obj2.foo() // "obj2"fooOBJ.call(obj3) // "obj3"setTimeOut(obj2.foo, 10) // "obj" 復制代碼可以看到,軟綁定的foo()可以手動的將this綁定到obj2或者obj3,但如果應用默認綁定則將this綁定到obj1上。
此前的4條規則使用與大部分函數,但在ES6中的箭頭函數卻不適用,因為箭頭函數不是function關鍵字定義的,而是使用被稱為“胖箭頭”的操作符 => 定義的。箭頭函數不使用this的四種標準規則,而是根據外層的作用域決定this的。所以叫做詞法
function foo () {// 返回一個箭頭函數return (a) => {// this繼承自foo()console.log(this.a)}}var obj1 = {a: 2}var obj2 = {a: 3}var bar = foo.call(obj1)bar.call(obj2) // 2 復制代碼foo內部創建的箭頭函數會捕獲調用時foo()的this,由于foo()的this是綁定到obj1上的,bar(只是引用箭頭函數)的this也會綁定到obj1上,箭頭函數的綁定無法修改。類似于
function foo () {var self = thissetTimeout(function () {console.log(self.a)}, 100)} 復制代碼關于this的理解就說這么多,歡迎指正和交流。
轉載于:https://juejin.im/post/5cf4e610f265da1b897aba93
總結
以上是生活随笔為你收集整理的深入理解JS中this关键字的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 机器学习中的度量—— 向量距离
- 下一篇: 如何用socket构建一个简单的Web