我終於能夠寫這章東西了。拖這麼久的緣由是由於我在寫業務代碼,加上畢設的代碼結構設計搞得我有點懵逼,加上以前確實有點懈怠,而後去寫了個簡易版的 MVVM 雙向綁定和指令渲染的東西,通過那個小項目我發現我對 內存、變量、閉包、this 有了更近一步的理解,而後加上看了點書有了點本身的理解,因而今天記錄一下。git
我所寫的這些東西只是我本身的理解,不表明就是絕對正確的,是我對 JavaScript 的見解和理解。github
我要講的很雜很亂,由於這節原本就很龐大,涉及到了好多東西,我準備從:變量與內存、閉包與 this、綁定 this 這幾個方面講如下我本身的見解。緩存
這個其實很簡單,咱們在代碼之中寫下了一堆又一堆的變量:bash
let foo = 'test'
const fn = function () {
let name = 'Nicholas'
console.log(name)
}
let handler = {
addHandler: function (name, handler) {},
fire: function () {}
}
複製代碼
以上咱們定義了: 普通變量、函數、對象變量。這三種大概就是咱們平時打代碼過程當中定義的最多的東西了。首先要肯定的是,基本數據類型是直接引用,而對象(引用變量)則被存儲在了內存中,咱們沒法直接訪問到內存,只能取得對象的引用。 這句話意思是:變量或者說對象它就安靜地在內存裏(若是沒被垃圾收集機制清除),而咱們定義地變量名只是對其引用。因而:閉包
let fns = {
sayHi: function () {
console.log('Hi')
},
fight: function () {
console.log(`I'll fight with you`) } } let ffight = fns.fight 複製代碼
上述代碼中,我本身認爲:首先確定是有兩個函數在內存中躺着,而後
fns.sayHi
指向了其中一個函數,fns.fight
指向了另外一個函數,而ffight
指向的函數和fns.sayHi
指向的是同一個函數,它們都只是對內存中存在的對象進行了引用 因此,我認爲:內存和變量實際上較爲獨立又緊密聯繫:變量自己存儲在這裏,它只是指向了內存中的某一個對象而已,並沒有它用。app
在我自認爲理清楚了變量與內存的關係以後,我彷佛對閉包與this也有了一點認識。首先上一節閉包中我其實對閉包是什麼已經有了認知,可是那時候對this這個東西有點模糊,只是依稀知道 在理解this時不可用閉包的詞法做用域來認知。 首先看一段代碼:函數
let fn = function () {
let name = 'Nicholas'
return function () {
console.log(name)
}
}
function getTimeout () {
setTimeout(fn(), 1000)
}
getTimeOut() // 1秒後輸出 Nicholas
複製代碼
上面這段代碼的結果應該十分簡單,由於 fn() 獲得的函數保留了對 fn 做用域中的 name 的引用,因此依然能夠訪問到 name。 我本身的理解:最開始我認爲 this 就是對當前的引用,至於當前是什麼,我相信不少人都和我一開始同樣認爲 所謂當前就是這行代碼執行時它能訪問到的變量環境,因而就有了下面這段代碼:學習
var name = 'Bob'
function getTimeout () {
setTimeout(function () {
console.log(this.name)
}, 1000)
}
var obj = {
name: 'Nicholas',
getTimeout: getTimeout
}
obj.getTimeout() // 1秒後輸出 Bob
複製代碼
這裏我以爲應該先上結論:this指向調用它的執行環境,毫不能用詞法做用域的方法去找this的指向ui
若是照着咱們最開始的理解,當執行這個函數的時候要去訪問
name
,咱們天然而然會順着代碼書寫位置去看,咱們在obj
函數中找到了name
,因而咱們覺得會this就是obj,應該輸出 Nicholas。 可是這裏咱們在看看上面這段代碼之中的變量和內存:首先咱們定義了一個函數getTimeout
,它在內存中,在setTimeout
的參數中咱們其實又定義了一個匿名函數,它也在內存中,並且obj.getTimeout就是那個匿名函數。而後咱們調用了getTimeout
, 這個函數中咱們使用定時器去調用那個匿名函數,因而1秒後,至關於執行了這個函數,這個函數就是普普統統的執行了:this
// 像這樣執行了:
// 這裏咱們先取得這個匿名函數的引用,雖然咱們是從新定義了一個新的函數,爲了演示做用,這裏咱們假設 noName 就是那個匿名函數
function noName () {
console.log(this.name)
}
// 1秒後咱們調用這個匿名函數
noName()
複製代碼
這樣看,結果就比較明顯了,這裏咱們天然能夠去看 name 就是全局的 Bob。 而咱們最多見的作法是緩存this:
var name = 'Bob'
function getTimeout () {
var that = this
setTimeout(function () {
console.log(that.name)
}, 1000)
}
var obj = {
name: 'Nicholas',
getTimeout: getTimeout
}
obj.getTimeout() // 1秒後輸出 Nicholas
複製代碼
咱們平時就是用這種綁定的方式獲得了咱們想要的結果,咱們理一下思路:
咱們知道this的綁定是動態的即它在執行前才能肯定指向,就是由於多個變量能夠指向同一個變量纔有這種說法,
obj.getTimeout()
這行代碼執行了內存中的代碼,可是執行這串代碼的環境確實obj
,因而這時候getTimeout
這個函數中的this
天然就是obj
緊接着咱們緩存了
this
到that
變量中,而後1秒以後咱們調用了匿名函數,該函數是個閉包,它擁有了getTimeout
函數的變量對象的訪問權限,天然就能夠訪問到that
變量,因而這個that
就指向了obj
,obj.name
天然就是 Nicholas了。
因而咱們獲得了那個結論:函數中的this,實際上是在運行時決定的,雖然咱們運行的是同一段代碼,可是調用這段代碼的方式是不同的,就爲這段代碼中的this綁定了不一樣的值。
咱們須要綁定this,才能獲得咱們想要的結果。
- 隱式綁定:
obj.fn()
,這個時候咱們經過obj
調用fn
,其中this天然被obj
綁定了- 硬綁定:
fn.call(obj, args)
或者fn.apply(obj, args)
,這樣咱們其實至關於傳入一個對象使它做爲fn
函數運行時的this。- bind函數:
bind
是每一個Function
對象都有的方法,其返回結果是一個函數,使用方法:let foo = bar.bind(obj)
,這個時候只是將this的指向綁定成obj。- new操做符:
let bar = new Fn()
,咱們在第六章講過new
發生過什麼,因此這邊的this指向就是返回的對象。
既然有多種方式,天然分出優先級:
- new
- bind
- 硬綁定
- 隱式綁定
證實:
// 咱們定義一個函數
var name = 'Bob'
var obj1 = {
name: 'Nicholas',
test: test
}
var obj2 = {
name: 'Peter',
}
var obj3 = {
name: 'Shally'
}
function test () {
console.log(this.name)
}
// 隱式綁定和硬綁定:
obj1.test() // Nicholas
obj1.test.call(obj2) // Peter
// bind和硬綁定
let fn = test.bind(obj3)
fn() // Shally
fn.call(obj2) // Shally
// new和bind 我以爲這兩個其實沒什麼比較,由於new出來是個對象,這個對象就是this,具體理解仍是看代碼吧
function Person (name) {
this.name = name
}
// 首先咱們試圖建立另外一個構造函數,讓這個構造函數的this經過bind綁定一個對象
// 所以咱們須要一個空對象
var testObj = {}
// 接着咱們建立另外一個構造函數
var AnotherFn = Person.bind(testObj)
// 緊接着咱們調用這個AnotherFn
AnotherFn('Nicholas')
console.log(testObj.name) // Nicholas
// 接着咱們經過另外一個構造函數 new 一個對象出來
var person = new AnotherFn('Bob')
// 這個結果其實說明,new 這個操做符其實餅不是修改一個對象,而是先建立這個對象,而後this就是這個對象,而後返回這個對象
// 若是bind優先級更高,則這時候 testObj.name 應該是 Bob,而實際狀況像咱們說明並不是如此。
console.log(testObj.name) // Nicholas
console.log(person.name) // Bob
複製代碼
以上就是幾種綁定this的狀況。
寫這一節真的是要死要活。由於其實到寫以前我都不是很清楚this的語法,只是模棱兩可,如今有些理解,就趕忙記錄一下。可是我以爲我如今的理解應該也是瑕疵挺多的吧。一步一步來,慢慢學習。
應該會挑個時間,寫一下前兩天寫的 MVVM 的那個思路,感受仍是有收穫的,雖然別人以爲很簡單,可是本身寫起來仍是印象深入些。
持續更新在github