日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 前端技术 > vue >内容正文

vue

为什么 Vue2 this 能够直接获取到 data 和 methods ? 源码揭秘!

發布時間:2023/12/9 vue 31 豆豆
生活随笔 收集整理的這篇文章主要介紹了 为什么 Vue2 this 能够直接获取到 data 和 methods ? 源码揭秘! 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

1. 前言

大家好,我是若川。最近組織了源碼共讀活動《1個月,200+人,一起讀了4周源碼》,已經有超50+人提交了筆記,群里已經有超1200人,感興趣的可以點此鏈接掃碼加我微信?ruochuan12

之前寫的《學習源碼整體架構系列》jQuery、underscore、lodash、vuex、sentry、axios、redux、koa、vue-devtools、vuex4十余篇源碼文章。其中最新的三篇是:

50行代碼串行Promise,koa洋蔥模型原來是這么實現?

Vue 3.2 發布了,那尤雨溪是怎么發布 Vue.js 的?

初學者也能看懂的 Vue3 源碼中那些實用的基礎工具函數

寫相對很難的源碼,耗費了自己的時間和精力,也沒收獲多少閱讀點贊,其實是一件挺受打擊的事情。從閱讀量和讀者受益方面來看,不能促進作者持續輸出文章。所以轉變思路,寫一些相對通俗易懂的文章。其實源碼也不是想象的那么難,至少有很多看得懂。歌德曾說:讀一本好書,就是在和高尚的人談話。同理可得:讀源碼,也算是和作者的一種學習交流的方式。

本文源于一次源碼共讀群里群友的提問,請問@若川,“為什么 data 中的數據可以用 this 直接獲取到啊”,當時我翻閱源碼做出了解答。想著如果下次有人再次問到,我還需要回答一次。當時打算有空寫篇文章告訴讀者自己探究原理,于是就有了這篇文章。

閱讀本文,你將學到:

1.?如何學習調試?vue2?源碼 2.?data?中的數據為什么可以用?this?直接獲取到 3.?methods?中的方法為什么可以用?this?直接獲取到 4.?學習源碼中優秀代碼和思想,投入到自己的項目中

本文不難,用過 Vue 的都看得懂,希望大家動手調試和學會看源碼。

看源碼可以大膽猜測,最后小心求證。

2. 示例:this 能夠直接獲取到 data 和 methods

眾所周知,這樣是可以輸出我是若川的。好奇的人就會思考為啥 this 就能直接訪問到呢。

const?vm?=?new?Vue({data:?{name:?'我是若川',},methods:?{sayName(){console.log(this.name);}}, }); console.log(vm.name);?//?我是若川 console.log(vm.sayName());?//?我是若川

那么為什么 this.xxx 能獲取到data里的數據,能獲取到 methods 方法。

我們自己構造寫的函數,如何做到類似Vue的效果呢。

function?Person(options){}const?p?=?new?Person({data:?{name:?'若川'},methods:?{sayName(){console.log(this.name);}} });console.log(p.name); //?undefined console.log(p.sayName()); //?Uncaught?TypeError:?p.sayName?is?not?a?function

如果是你,你會怎么去實現呢。帶著問題,我們來調試 Vue2源碼學習。

3. 準備環境調試源碼一探究竟

可以在本地新建一個文件夾examples,新建文件index.html文件。在<body></body>中加上如下js。

<script?src="https://unpkg.com/vue@2.6.14/dist/vue.js"></script> <script>const?vm?=?new?Vue({data:?{name:?'我是若川',},methods:?{sayName(){console.log(this.name);}},});console.log(vm.name);console.log(vm.sayName()); </script>

再全局安裝npm i -g http-server啟動服務。

npm?i?-g?http-server cd?examples http-server?. //?如果碰到端口被占用,也可以指定端口 http-server?-p?8081?.

這樣就能在http://localhost:8080/打開剛寫的index.html頁面了。

對于調試還不是很熟悉的讀者,可以看這篇文章《前端容易忽略的 debugger 調試技巧》

調試:在 F12 打開調試,source 面板,在例子中const vm = new Vue({打上斷點。

debugger

刷新頁面后按F11進入函數,這時斷點就走進了 Vue 構造函數。

3.1 Vue 構造函數

function?Vue?(options)?{if?(!(this?instanceof?Vue))?{warn('Vue?is?a?constructor?and?should?be?called?with?the?`new`?keyword');}this._init(options); } //?初始化 initMixin(Vue); stateMixin(Vue); eventsMixin(Vue); lifecycleMixin(Vue); renderMixin(Vue);

值得一提的是:if (!(this instanceof Vue)){} 判斷是不是用了 new 關鍵詞調用構造函數。一般而言,我們平時應該不會考慮寫這個。

當然看源碼庫也可以自己函數內部調用 new 。但 vue 一般一個項目只需要 new Vue() 一次,所以沒必要。

而 jQuery 源碼的就是內部 new ,對于使用者來說就是無new構造。

jQuery?=?function(?selector,?context?)?{//?返回new之后的對象return?new?jQuery.fn.init(?selector,?context?); };

因為使用 jQuery 經常要調用。其實 jQuery 也是可以 new 的。和不用 new 是一個效果。

如果不明白 new 操作符的用處,可以看我之前的文章。面試官問:能否模擬實現JS的new操作符

調試:繼續在this._init(options);處打上斷點,按F11進入函數。

3.2 _init 初始化函數

進入 _init 函數后,這個函數比較長,做了挺多事情,我們猜測跟data和methods相關的實現在initState(vm)函數里。

//?代碼有刪減 function?initMixin?(Vue)?{Vue.prototype._init?=?function?(options)?{var?vm?=?this;//?a?uidvm._uid?=?uid$3++;//?a?flag?to?avoid?this?being?observedvm._isVue?=?true;//?merge?optionsif?(options?&&?options._isComponent)?{//?optimize?internal?component?instantiation//?since?dynamic?options?merging?is?pretty?slow,?and?none?of?the//?internal?component?options?needs?special?treatment.initInternalComponent(vm,?options);}?else?{vm.$options?=?mergeOptions(resolveConstructorOptions(vm.constructor),options?||?{},vm);}//?expose?real?selfvm._self?=?vm;initLifecycle(vm);initEvents(vm);initRender(vm);callHook(vm,?'beforeCreate');initInjections(vm);?//?resolve?injections?before?data/props//??初始化狀態initState(vm);initProvide(vm);?//?resolve?provide?after?data/propscallHook(vm,?'created');}; }

調試:接著我們在initState(vm)函數這里打算斷點,按F8可以直接跳轉到這個斷點,然后按F11接著進入initState函數。

3.3 initState 初始化狀態

從函數名來看,這個函數主要實現功能是:

初始化?props 初始化?methods 監測數據 初始化?computed 初始化?watchfunction?initState?(vm)?{vm._watchers?=?[];var?opts?=?vm.$options;if?(opts.props)?{?initProps(vm,?opts.props);?}//?有傳入?methods,初始化方法if?(opts.methods)?{?initMethods(vm,?opts.methods);?}//?有傳入?data,初始化?dataif?(opts.data)?{initData(vm);}?else?{observe(vm._data?=?{},?true?/*?asRootData?*/);}if?(opts.computed)?{?initComputed(vm,?opts.computed);?}if?(opts.watch?&&?opts.watch?!==?nativeWatch)?{initWatch(vm,?opts.watch);} }

我們重點來看初始化 methods,之后再看初始化 data。

調試:在 initMethods 這句打上斷點,同時在initData(vm)處打上斷點,看完initMethods函數后,可以直接按F8回到initData(vm)函數。繼續按F11,先進入initMethods函數。

3.4 initMethods 初始化方法

function?initMethods?(vm,?methods)?{var?props?=?vm.$options.props;for?(var?key?in?methods)?{{if?(typeof?methods[key]?!==?'function')?{warn("Method?\""?+?key?+?"\"?has?type?\""?+?(typeof?methods[key])?+?"\"?in?the?component?definition.?"?+"Did?you?reference?the?function?correctly?",vm);}if?(props?&&?hasOwn(props,?key))?{warn(("Method?\""?+?key?+?"\"?has?already?been?defined?as?a?prop."),vm);}if?((key?in?vm)?&&?isReserved(key))?{warn("Method?\""?+?key?+?"\"?conflicts?with?an?existing?Vue?instance?method.?"?+"Avoid?defining?component?methods?that?start?with?_?or?$.");}}vm[key]?=?typeof?methods[key]?!==?'function'???noop?:?bind(methods[key],?vm);} }

initMethods函數,主要有一些判斷。

判斷 methods 中的每一項是不是函數,如果不是警告。 判斷 methods 中的每一項是不是和 props 沖突了,如果是,警告。 判斷?methods?中的每一項是不是已經在?new?Vue實例 vm 上存在,而且是方法名是保留的?_?$?(在JS中一般指內部變量標識)開頭,如果是警告。

除去這些判斷,我們可以看出initMethods函數其實就是遍歷傳入的methods對象,并且使用bind綁定函數的this指向為vm,也就是new Vue的實例對象。

這就是為什么我們可以通過this直接訪問到methods里面的函數的原因

我們可以把鼠標移上 bind 變量,按alt鍵,可以看到函數定義的地方,這里是218行,點擊跳轉到這里看 bind 的實現。

3.4.1 bind 返回一個函數,修改 this 指向

function?polyfillBind?(fn,?ctx)?{function?boundFn?(a)?{var?l?=?arguments.length;return?l??l?>?1??fn.apply(ctx,?arguments):?fn.call(ctx,?a):?fn.call(ctx)}boundFn._length?=?fn.length;return?boundFn }function?nativeBind?(fn,?ctx)?{return?fn.bind(ctx) }var?bind?=?Function.prototype.bind??nativeBind:?polyfillBind;

簡單來說就是兼容了老版本不支持 原生的bind函數。同時兼容寫法,對參數多少做出了判斷,使用call和apply實現,據說是因為性能問題。

如果對于call、apply、bind的用法和實現不熟悉,可以查看我在面試官問系列面試官問:能否模擬實現JS的call和apply方法面試官問:能否模擬實現JS的bind方法

調試:看完了initMethods函數,按F8回到上文提到的initData(vm)函數斷點處。

3.5 initData 初始化 data

initData 函數也是一些判斷。主要做了如下事情:

先給?_data 賦值,以備后用。 最終獲取到的 data 不是對象給出警告。 遍歷 data ,其中每一項: 如果和 methods 沖突了,報警告。 如果和 props 沖突了,報警告。 不是內部私有的保留屬性,做一層代理,代理到?_data 上。 最后監測 data,使之成為響應式的數據。function?initData?(vm)?{var?data?=?vm.$options.data;data?=?vm._data?=?typeof?data?===?'function'??getData(data,?vm):?data?||?{};if?(!isPlainObject(data))?{data?=?{};warn('data?functions?should?return?an?object:\n'?+'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',vm);}//?proxy?data?on?instancevar?keys?=?Object.keys(data);var?props?=?vm.$options.props;var?methods?=?vm.$options.methods;var?i?=?keys.length;while?(i--)?{var?key?=?keys[i];{if?(methods?&&?hasOwn(methods,?key))?{warn(("Method?\""?+?key?+?"\"?has?already?been?defined?as?a?data?property."),vm);}}if?(props?&&?hasOwn(props,?key))?{warn("The?data?property?\""?+?key?+?"\"?is?already?declared?as?a?prop.?"?+"Use?prop?default?value?instead.",vm);}?else?if?(!isReserved(key))?{proxy(vm,?"_data",?key);}}//?observe?dataobserve(data,?true?/*?asRootData?*/); }

3.5.1 getData 獲取數據

是函數時調用函數,執行獲取到對象。

function?getData?(data,?vm)?{//?#7573?disable?dep?collection?when?invoking?data?getterspushTarget();try?{return?data.call(vm,?vm)}?catch?(e)?{handleError(e,?vm,?"data()");return?{}}?finally?{popTarget();} }

3.5.2 proxy 代理

其實就是用 Object.defineProperty 定義對象

這里用處是:this.xxx 則是訪問的 this._data.xxx。

/***?Perform?no?operation.*?Stubbing?args?to?make?Flow?happy?without?leaving?useless?transpiled?code*?with?...rest?(https://flow.org/blog/2017/05/07/Strict-Function-Call-Arity/).*/ function?noop?(a,?b,?c)?{} var?sharedPropertyDefinition?=?{enumerable:?true,configurable:?true,get:?noop,set:?noop };function?proxy?(target,?sourceKey,?key)?{sharedPropertyDefinition.get?=?function?proxyGetter?()?{return?this[sourceKey][key]};sharedPropertyDefinition.set?=?function?proxySetter?(val)?{this[sourceKey][key]?=?val;};Object.defineProperty(target,?key,?sharedPropertyDefinition); }

3.5.3 Object.defineProperty 定義對象屬性

Object.defineProperty 算是一個非常重要的API。還有一個定義多個屬性的API:Object.defineProperties(obj, props) (ES5)

Object.defineProperty 涉及到比較重要的知識點,面試也常考。

value——當試圖獲取屬性時所返回的值。 writable——該屬性是否可寫。 enumerable——該屬性在for?in循環中是否會被枚舉。 configurable——該屬性是否可被刪除。 set()——該屬性的更新操作所調用的函數。 get()——獲取屬性值時所調用的函數。

詳細舉例見此鏈接

3.6 文中出現的一些函數,最后統一解釋下

3.6.1 hasOwn 是否是對象本身擁有的屬性

調試模式下,按alt鍵,把鼠標移到方法名上,可以看到函數定義的地方。點擊可以跳轉。

/***?Check?whether?an?object?has?the?property.*/ var?hasOwnProperty?=?Object.prototype.hasOwnProperty; function?hasOwn?(obj,?key)?{return?hasOwnProperty.call(obj,?key) }hasOwn({?a:?undefined?},?'a')?//?true hasOwn({},?'a')?//?false hasOwn({},?'hasOwnProperty')?//?false hasOwn({},?'toString')?//?false //?是自己的本身擁有的屬性,不是通過原型鏈向上查找的。

3.6.2 isReserved 是否是內部私有保留的字符串$ ?和 _ 開頭

/***?Check?if?a?string?starts?with?$?or?_*/ function?isReserved?(str)?{var?c?=?(str?+?'').charCodeAt(0);return?c?===?0x24?||?c?===?0x5F } isReserved('_data');?//?true isReserved('$options');?//?true isReserved('data');?//?false isReserved('options');?//?false

4. 最后用60余行代碼實現簡化版

function?noop?(a,?b,?c)?{} var?sharedPropertyDefinition?=?{enumerable:?true,configurable:?true,get:?noop,set:?noop }; function?proxy?(target,?sourceKey,?key)?{sharedPropertyDefinition.get?=?function?proxyGetter?()?{return?this[sourceKey][key]};sharedPropertyDefinition.set?=?function?proxySetter?(val)?{this[sourceKey][key]?=?val;};Object.defineProperty(target,?key,?sharedPropertyDefinition); } function?initData(vm){const?data?=?vm._data?=?vm.$options.data;const?keys?=?Object.keys(data);var?i?=?keys.length;while?(i--)?{var?key?=?keys[i];proxy(vm,?'_data',?key);} } function?initMethods(vm,?methods){for?(var?key?in?methods)?{vm[key]?=?typeof?methods[key]?!==?'function'???noop?:?methods[key].bind(vm);}? }function?Person(options){let?vm?=?this;vm.$options?=?options;var?opts?=?vm.$options;if(opts.data){initData(vm);}if(opts.methods){initMethods(vm,?opts.methods)} }const?p?=?new?Person({data:?{name:?'若川'},methods:?{sayName(){console.log(this.name);}} });console.log(p.name); //?未實現前:undefined //?'若川' console.log(p.sayName()); //?未實現前:Uncaught TypeError: p.sayName is not a function //?'若川'

5. 總結

本文涉及到的基礎知識主要有如下:

構造函數 this?指向 call、bind、apply Object.defineProperty 等等基礎知識。

本文源于解答源碼共讀群友的疑惑,通過詳細的描述了如何調試 Vue 源碼,來探尋答案。

解答文章開頭提問:

通過this直接訪問到methods里面的函數的原因是:因為methods里的方法通過 bind 指定了this為 new Vue的實例(vm)。

通過 this 直接訪問到 data 里面的數據的原因是:data里的屬性最終會存儲到new Vue的實例(vm)上的 _data對象中,訪問 this.xxx,是訪問Object.defineProperty代理后的 this._data.xxx。

Vue的這種設計,好處在于便于獲取。也有不方便的地方,就是props、methods 和 data三者容易產生沖突。

文章整體難度不大,但非常建議讀者朋友們自己動手調試下。調試后,你可能會發現:原來 Vue 源碼,也沒有想象中的那么難,也能看懂一部分。

啟發:我們工作使用常用的技術和框架或庫時,保持好奇心,多思考內部原理。能夠做到知其然,知其所以然。就能遠超很多人。

你可能會思考,為什么模板語法中,可以省略this關鍵詞寫法呢,內部模板編譯時其實是用了with。有余力的讀者可以探究這一原理。

最后歡迎加我微信 ruochuan12源碼共讀 活動,大家一起學習源碼,共同進步。

最近組建了一個湖南人的前端交流群,如果你是湖南人可以加我微信?ruochuan12?私信?湖南?拉你進群。


推薦閱讀

1個月,200+人,一起讀了4周源碼
我讀源碼的經歷

老姚淺談:怎么學JavaScript?

我在阿里招前端,該怎么幫你(可進面試群)

·················?若川簡介?·················

你好,我是若川,畢業于江西高校。現在是一名前端開發“工程師”。寫有《學習源碼整體架構系列
從2014年起,每年都會寫一篇年度總結,已經寫了7篇,點擊查看年度總結。
同時,最近組織了源碼共讀活動

識別方二維碼加我微信、拉你進源碼共讀

今日話題

略。歡迎分享、收藏、點贊、在看我的公眾號文章~

總結

以上是生活随笔為你收集整理的为什么 Vue2 this 能够直接获取到 data 和 methods ? 源码揭秘!的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。