來直接上代碼javascript
function foo(){ console.log(`我自己屬性a是 ${this.a}`) } var bar ={ a:2, foo:foo } var baz={ a:4, foo:foo } bar.foo();//我自己屬性a是 2 baz.foo()://我自己屬性a是 4 複製代碼
小夥伴們是否是已經在這個簡單的代碼中發現了 剛發foo只定義了一次,去能夠被不一樣的對象引用,實現了代碼共享java
接下來我們看看代碼中的是調試es6
接下來說用到函數的是兩個身份普通函數、普通對象, 看代碼()數組
function foo(){ this.count++ } var count=0; foo.count=0; for(var i=0;i<5;i++){ foo() } console.log(foo.count)//0 console.log(count)//5 複製代碼
從打印的結果上來看顯然,this指向的不是自己函數,固然我們通常看到這類的問題我們就會繞道而行,看代碼安全
function foo(){ this.count++ } var bar={ count:0 } foo.count=0; for(var i=0;i<5;i++){ foo.call(bar) } console.log(bar.count)//5 console.log(count)//0 複製代碼
雖然這種解決方案很好,也會有其餘的解決方案,可是咱們仍是不理解this的問題,內心仍是有種不安之感
bash
function foo(){ var num=2; console.log(this.num) } var num=0; foo()//0 複製代碼
我們看到代碼的執行結果後,發現this指向的並非該函數的做用域。markdown
this是在函數調用的時候綁定,不是在函數定義的時候綁定。它的上下文取決於函數調用時的各類條件,函數執行的時候會建立一個活動記錄,這個記錄裏面包含了該函數中定義的參數和參數,包含函數在哪裏被調用(調用棧)...,this就是其中的一個屬性。 來看圖 閉包
圖中我們看到this是在函數執行的時候建立的。app
前面幾步我們已經肯定的this的建立和this的指向的誤區,接下啦我們要看看this的綁定的規則,分爲4個規則。函數
function foo(){ var num=2; this.num++ console.log(this.num) } var num=0; foo()//1 複製代碼
上面代碼中就實現了默認綁定,在foo方法的代碼塊中操做的是window.num++。
function foo(){ console.log(this.name) } var bar={ name:'shiny', foo:foo } bar.foo()//shiny 複製代碼
要須要補充一點,無論你的對象嵌套多深,this只會綁定爲直接引用該函數的地址屬性的對象,看代碼
function foo(){ console.log(this.name) } var shiny={ name:'shiny', foo:foo } var red={ name:'red', obj:shiny } red.obj.foo()//shiny 複製代碼
function foo(){ console.log(this.name) } var shiny={ name:'shiny', foo:foo } function doFoo(fn){ fn() } doFoo(shiny.foo)//undefind 複製代碼
你們知道函數參數在函數執行的時候,其實有一個賦值的操做,我來解釋一下上面的,當函數doFoo執行的時候會開闢一個新的棧並被推入到全局棧中執行,在執行的過程當中會建立一個活動對象,這個活動對象會被賦值傳入的參數以及在函數中定義的變量函數,在函數執行時用到的變量和函數直接從該活動對象上面取值使用。 看圖 doFoo的執行棧
fn的執行棧
看下面原理和上面同樣經過賦值,致使隱式綁定的丟失,看代碼
function foo(){ console.log(this.name) } var shiny={ name:'shiny', foo:foo } var bar = shiny.foo bar()//undefined 複製代碼
你們是否是已經明白了爲何是undefined,來解釋一波,其實shiny的foo屬性是引用了foo函數的引用內存地址,那麼有把foo的引用地址賦值給了 bar 那麼如今的bar的引用地址個shiny.foo的引用地址是一個,那麼執行bar的時候也會觸發默認綁定規則由於沒有其餘規則能夠匹配,bar函數執行時,函數內部的this綁定的是全局變量。
看下滿的引用地址賦值是出現的,奇葩 隱式綁定丟失,看代碼
function foo(){ console.log(this.name) } var shiny={ name:'shiny', foo:foo } var red={ name:'red' } (red.foo=shiny.foo)()//undefined 複製代碼
賦值表達式 p.foo = o.foo 的返回值是目標函數的引用,所以調用位置是 foo() 而不是 p.foo() 或者 o.foo()。根據咱們以前說過的,這裏會應用默認綁定。
看代碼
function foo(){ console.log(this.age) } var shiny={ age:20 } foo.call(shiny)//20 function bar(){ console.log(this.age) } var red={ age:18 } bar.apply(red)//18 複製代碼
這兩個方法都是顯式的綁定了tihs
function foo(b){ return this.a+b } var obj={ a:2 } function bind(fn,obj){ return function(){ return fn.apply(obj,arguments) } } bind(foo,obj)(3)//5 複製代碼
語言解釋: 經過apply + 閉包機制 實現bind方法,實現強行綁定規則
API調用的「上下文」 第三方庫或者寄生在環境,以及js內置的一些方法都提供了一下 content 上下文參數,他的做用和 bind同樣,就是確保回調函數的this被綁定
function foo (el){ console.log(el,this.id) } var obj ={ id:'some one' }; [1,2,4].forEach(foo,obj) // 1 some one 2 some one 4 some one 複製代碼
傳統面向類的語言中的構函數,是在使用new操做符實例化類的時候,會調用類中的一些特殊方法(構造函數)
不少人認爲js中的new操做符和傳統面向類語言的構造函數是同樣的,其實有很大的差異
重新認識一下js中的構造函數,js中的構造函數 在被new操做符調用時,這個構造函數不屬於每一個類,也不會創造一個類,它就是一個函數,只是被new操做符調用。
使用new操做符調用 構造函數時會執行4步
我們瞭解了js new 操做符調用構造函數時都作了些什麼,哪麼我們就知道構造函數裏面的this是誰了
代碼實現
function Foo(a){ this.a=a } var F = new Foo(2) console.log(F.a)//2 複製代碼
看代碼
function foo(){ console.log(this.name) } var shiny={ name:'shiny', foo:foo } var red={ name:'red' } shiny.foo()//shiny shiny.foo.call(red)// red shiny.foo.apply(red)// red shiny.foo.bind(red)()//red 複製代碼
顯然在這場綁定this比賽中,顯式綁定贏了隱式綁定
function foo(name){ this.name=name } var shiny={ foo:foo } shiny.foo('shiny') console.log(shiny.name)//shiny var red = new shiny.foo('red') console.log(red.name)//red 複製代碼
顯然在這場綁定this比賽中new 操做符綁定贏了隱式綁定
使用call、apply方法不能結合new操做符會報錯誤
可是我們能夠是bind綁定this來比較 顯式綁定和new操做符的綁定this優先級。 看代碼function foo(){ console.log(this.name) } var shiny={ name:'shiny' } var bar = foo.bind(shiny) var obj = new bar(); console.log(obj.name)// undefind 複製代碼
顯然 new操做符綁定 打敗了 顯式綁定
function foo(){ console.log(name) } var name ='shiny' foo.call(null)//shiny foo.call(undefined)//shiny var bar = foo.bind(null) var baz = foo.bind(undefined) bar()//siny baz()//siny 複製代碼
把 null、undefined經過 apply、call、bind 顯式綁定,雖然實現可默認綁定,可是建議這麼作由於在非嚴格的模式下會給全局對象添加屬性,有時候會形成不可必要的bug。
function foo(a,b) { console.log( "a:" + a + ", b:" + b ); } // 咱們的空對象 var ø = Object.create( null ); // 把數組展開成參數 foo.apply( ø, [2, 3] ); // a:2, b:3 // 使用 bind(..) 進行柯里化 var bar = foo.bind( ø, 2 ); bar( 3 ); // a:2, b:3 複製代碼
function foo(){ return ()=>{ console.log(this.name) } } var obj ={ name:'obj' } var shiny ={ name:'shiny' } var bar = foo.call(obj); bar.call(shiny)// foo 複製代碼
咱們看到箭頭函數的this被綁定到該函數執行的做用域上。
我們在看看 js內部提供內置函數使用箭頭函數
function foo() { setTimeout(() => { // 這裏的 this 在此法上繼承自 foo() console.log( this.a ); },100); } var obj = { a:2 }; foo.call( obj ); // 2 複製代碼箭頭函數能夠像 bind(..) 同樣確保函數的 this 被綁定到指定對象,此外,其重要性還體 如今它用更常見的詞法做用域取代了傳統的 this 機制。實際上,在 ES6 以前咱們就已經 在使用一種幾乎和箭頭函數徹底同樣的模式。
function foo() { var self = this; // lexical capture of this setTimeout( function(){ console.log( self.a ); }, 100 ); } var obj = { a: 2 }; foo.call( obj ); // 2 複製代碼
雖然 self = this 和箭頭函數看起來均可以取代 bind(..),可是從本質上來講,它們想替 代的是 this 機制。 若是你常常編寫 this 風格的代碼,可是絕大部分時候都會使用 self = this 或者箭頭函數。 若是徹底採用 this 風格,在必要時使用 bind(..),儘可能避免使用 self = this 和箭頭函數。
若有不足,在評論中提出,我們一塊兒學習