如上圖所示,a函數定義在全域性,其作用域鏈,只有GO物件,當其執行的時候會臨時產生一個aAO物件,所以b函數的作用域鏈就是 aAO -> GO
函數每次執行都會產生一個新的AO物件掛在作用域鏈頭部,函數被解釋執行的時候,其內部識別符號的檢索都是在作用域鏈上檢索的。
根據以上理論,我們來執行b函數。
控制檯輸出如下:
為什麼b函數中看到的this是window呢?是因為其順著作用域鏈找,bBO -> aAO -> GO,只有GO上有this,就是window。
當然對於普通函數,我們可以改變其this指向:
控制檯輸出如下:
於是我們得到一個結論:
函數執行生成的臨時AO物件中,包含了arguments隱式變數來儲存實參列表。
函數執行看到的this變數,可以修改,通過物件呼叫,call,apply來修改。
但是,但是,但是。。。。
箭頭函數,它就不是這樣的。。。
控制檯輸出:
箭頭函數,沒有arguments隱式變數了。而且,this它居然修改不了。。
那麼說白了,this只能在其作用域鏈上找了,生成的臨時AO物件上沒有this,沒有this。
是否有arguments隱式變數 | 是否能改變this指向 | |
普通函數 | 是 | 是 |
箭頭函數 | 否 | 否 |
由於plus,minus,showCount三個函數的作用域鏈中有aaa的AO物件,所以當他們被返回後,形成了閉包。
但是其實除了這樣給物件加屬性外,我們也可以通過defineProperty來給物件加屬性。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>demo01-defineProperty的使用</title> </head> <body> <script type="application/javascript"> // defineProperty() let obj = { name: 'zhangsan', age: 33, showInfo() { console.log(this.name + "--" + this.age) } } obj.showInfo(); Object.defineProperty(obj, 'ccc', { value: 10, enumerable: true, // 是否能列舉 configurable: true, // 是否能刪除 writable: true // 是否能寫入 }) // 列舉 var keys = Object.keys(obj); console.log(keys); // 寫入 obj.ccc = 100; console.log(obj); // 刪除 delete obj.ccc; console.log(obj); </script> </body> </html>
上述程式碼控制檯輸入如下:
其實這樣的話,定義屬性和我們直接寫屬性沒什麼太大區別,關鍵是下面這樣的寫法:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>demo01-defineProperty的使用</title> </head> <body> <script type="application/javascript"> // defineProperty() let obj = { name: 'zhangsan', age: 33, showInfo() { console.log(this.name + "--" + this.age) } } let ccc = 10; Object.defineProperty(obj, 'ccc', { // value: 10, enumerable: true, // 是否能列舉 configurable: true, // 是否能刪除 // writable: true, // 是否能寫入 get: function proxyGet() { return ccc; }, set: function proxySet(value) { ccc = value; } }) console.log(obj.ccc); obj.ccc = 100; console.log(obj.ccc); console.log(obj); </script> </body> </html>
注意,如果我們要定義屬性的get/set,那麼就不能定義value和writable了,否則會報錯。
此時我們對屬性ccc的寫入和讀取將走get/set方法了。
控制檯輸出如下:
這個ccc屬性的三個點,是不是特別想我們使用vue的時候點開的元件物件裡面的一些屬性。
在這裡我插一句,我點開set/get給大家看看,其實能看見[[scopes]]作用域鏈了。
如下圖:
當然,這裡我們看見了,set/get函數定義的時候的作用域鏈[[scopes]],其實是SO -> GO,這個SO其實就是外層包裹的script標籤。
可以理解成,script標籤執行流程就像一個函數執行一樣,也會產生作用域物件掛在[[scopes]]上。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>demo01-vue簡單使用</title> <!--引入vue--> <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.js"></script> </head> <body> <div id="app"> <h3>姓名:{{name}}</h3> <h3>年齡:{{age}}</h3> <button @click="agePlusOne">年齡+1</button> </div> <script type="application/javascript"> let vm = new Vue({ el: '#app', data(){ return { name: '張三', age: 33 } }, methods: { agePlusOne(){ this.age ++; console.log(this) } } }); </script> </body> </html>
點選按鈕,我把Vue物件列印出來:
可以清晰的看到,我們設定的data物件中的屬性都被定義在了Vue元件物件中。
起碼,這裡看到了,vue做了資料代理,我們在元件物件中對data中同名屬性的set和get都走了其對應的代理方法。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>demo01-資料劫持</title> </head> <body> <div id="app"> </div> <script type="application/javascript"> function setAppInnerText (value) { document.querySelector("#app").innerText = value; } let obj = { name: '張三', age: 100, showInfo() { return this.name + "---" + this.age; } }; setAppInnerText(obj.showInfo()) // 資料劫持 Object.keys(obj).forEach(key => { let value = obj[key]; Object.defineProperty(obj, key, { enumerable: true, configurable: true, set(newValue) { if(newValue === value) { return ; } else { value = newValue; setAppInnerText(obj.showInfo()); } }, get() { return value; } }) }) obj.age = 1000; </script> </body> </html>
上面的程式碼,就是資料劫持,每次屬性設定的時候,都觸發了setAppInnerText函數的呼叫。
上述程式碼執行完之後,只要我們對obj物件的屬性進行修改,都會觸發頁面的變化。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>data_observer</title> </head> <body> <div id="root"> a = {{a}} <br> b = {{b}} </div> <script> function Vue(config) { this._data = config.data; // 資料代理 方便程式設計師操作 for (let key in config.data) { Object.defineProperty(this, key, { enumerable: true, get: function proxyGet() { return this._data[key]; }, set: function proxySet(value) { this._data[key] = value; } }) } this.mounted = false; if (config.el) { this.$mount(config.el); } } Vue.prototype.$mount = function (id) { if (!this.mounted) { this.originInnerHtml = document.getElementById(id).innerHTML; // 編譯模板生成render let _self = this; function render() { let innerHtml = _self.originInnerHtml; for (let key in _self._data) { innerHtml = innerHtml.replaceAll('{{' + key + '}}', _self._data[key]); } document.getElementById(id).innerHTML = innerHtml; } // 資料劫持 for (let key in this._data) { let value = this._data[key]; Object.defineProperty(this._data, key, { enumerable: true, configurable: true, get: function getObserver() { return value; }, set: function setObserver(newValue) { if (value !== newValue) { value = newValue; render(); } } }) } // 執行render render(); this.mounted = true; } } let config = { el: 'root', data: { a: '牛逼的訊息', b: '學習vue2底層實現' } }; let vm = new Vue(config); </script> </body> </html>
控制檯列印如下: