從零手寫 Vue 之響應式系統

大家好,我是若川。持續組織了8個月源碼共讀活動,感興趣的可以?點此加我微信ruochuan12?參與,每周大家一起學習200行左右的源碼,共同進步。同時極力推薦訂閱我寫的《學習源碼整體架構系列》?包含20余篇源碼文章。歷史面試系列。另外:目前建有江西|湖南|湖北籍前端群,可加我微信進群。

之前的文章把響應式系統基本講完了,沒看過的同學可以看一下 vue.windliang.wang/。這篇文章主要是按照 Vue2 源碼的目錄格式和調用過程,把我們之前寫的響應式系統移動進去。

html 中我們提供一個 idroot 的根 dom

<!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>Document</title></head><body><div?id="root"></div><script?src="bundle.js"></script></body>
</html>

其中 bundle.js 就是我們打包好的測試代碼,對應 ./VueLiang0/vueliang0.js ,代碼如下:

import?Vue?from?"./src/core/index";new?Vue({el:?"#root",data()?{return?{test:?1,name:?"data:liang",};},watch:?{test(newVal,?oldVal)?{console.log(newVal,?oldVal);},},computed:?{text()?{return?"computed:hello:"?+?this.name;},},methods:?{hello()?{return?"調用methods:hello";},click()?{this.test?=?3;this.name?=?"wind";},},render()?{const?node?=?document.createElement("div");const?dataNode?=?document.createElement("div");dataNode.innerText?=?this.test;node.append(dataNode);const?computedNode?=?document.createElement("div");computedNode.innerText?=?this.text;node.append(computedNode);const?methodsNode?=?document.createElement("div");methodsNode.innerText?=?this.hello();node.append(methodsNode);node.addEventListener("click",?this.click);return?node;},
});

提供了 datawatchcomputedmethods ,在 render 方法中正常情況的話應該是返回虛擬 dom ,這里我們直接生成一個真的 dom 返回。

代理

我們使用 datamethods 或者 computed 的時候,都是通過 this.xxx ,而不是 this.data.xxx 或者 this.methods.xxx ,是因為 Vue 幫我們把這些屬性、方法都掛載到了 Vue 實例上。

掛載 methods

//?VueLiang0/src/core/instance/state.js
function?initMethods(vm,?methods)?{for?(const?key?in?methods)?{vm[key]?=typeof?methods[key]?!==?"function"???noop?:?bind(methods[key],?vm);}
}

掛載 computed

export?function?defineComputed(target,?key,?userDef)?{...Object.defineProperty(target,?key,?sharedPropertyDefinition);
}

掛載 data

function?initData(vm)?{let?data?=?vm.$options.data;data?=?vm._data?=typeof?data?===?"function"???getData(data,?vm)?:?data?||?{};if?(!isPlainObject(data))?{data?=?{};}//?proxy?data?on?instanceconst?keys?=?Object.keys(data);const?props?=?vm.$options.props;const?methods?=?vm.$options.methods;let?i?=?keys.length;while?(i--)?{const?key?=?keys[i];//?檢查?methods?是否有同名屬性if?(process.env.NODE_ENV?!==?"production")?{if?(methods?&&?hasOwn(methods,?key))?{console.warn(`Method?"${key}"?has?already?been?defined?as?a?data?property.`,vm);}}//?檢查?props?是否有同名屬性if?(props?&&?hasOwn(props,?key))?{process.env.NODE_ENV?!==?"production"?&&console.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(data);?//?變為響應式數據
}

為了保證 data 的對象值的穩定,我們的 data 屬性其實是一個函數,返回一個對象,所以上邊我們用 getData 方法先拿到對象。

export?function?getData(data,?vm)?{try?{return?data.call(vm,?vm);}?catch?(e)?{return?{};}
}

之后依次判斷 data 屬性是否和 methodscomputed 屬性重名,非線上環境會打印警告,然后調用 isReserved 判斷是否是內置屬性。

/***?Check?if?a?string?starts?with?$?or?_*/
export?function?isReserved(str)?{const?c?=?(str?+?"").charCodeAt(0);return?c?===?0x24?||?c?===?0x5f;
}

最后調用 proxy 方法,將 data 屬性掛在到 ?vm 對象中,相當于將 methodscomputed 的同名屬性進行了覆蓋。

export?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);
}

響應式

把各個屬性初始化完成后,調用 mounted 方法,把我們的 dom 掛載到根節點中。

Vue.prototype._init?=?function?(options)?{const?vm?=?this;vm.$options?=?options;vm._renderProxy?=?vm;initState(vm);if?(vm.$options.el)?{vm.$mount(vm.$options.el);}
};

$mount 方法中把 el 對應的 dom 拿到,然后調用 mountComponent 方法進行掛載 dom

Vue.prototype.$mount?=?function?(el)?{el?=?el?&&?document.querySelector(el);return?mountComponent(this,?el);
};

mountComponent 方法中定義 ?updateComponent 方法和 Watcher 對象,這樣當 updateComponent 中依賴的屬性變化的時候,updateComponent 就會被自動調用。

export?function?mountComponent(vm,?el)?{vm.$el?=?el;let?updateComponent;updateComponent?=?()?=>?{vm._update(vm._render());};//?we?set?this?to?vm._watcher?inside?the?watcher's?constructor//?since?the?watcher's?initial?patch?may?call?$forceUpdate?(e.g.?inside?child//?component's?mounted?hook),?which?relies?on?vm._watcher?being?already?definednew?Watcher(vm,?updateComponent,?noop?/*?isRenderWatcher?*/);return?vm;
}

_update 方法原本是進行虛擬 dom 的掛載,這里的話我們直接將 render 返回的 dom 進行掛載。

Vue.prototype._update?=?function?(dom)?{const?vm?=?this;/*****這里僅僅是把?dom?更新,vue2?源碼中這里會進行虛擬?dom?的處理?*/if?(vm.$el.children[0])?{vm.$el.removeChild(vm.$el.children[0]);}vm.$el.appendChild(dom);/*******************************/
};

整體流程

入口文件代碼如下:

import?Vue?from?"./src/core/index";new?Vue({el:?"#root",...
});

第一行代碼 import Vue from "./src/core/index"; 的時候會進行一些初始化,src/core/index 代碼如下:

//?src/core/index
import?Vue?from?'./instance/index'
import?{?initGlobalAPI?}?from?'./global-api/index'initGlobalAPI(Vue)?//?Vue?上掛載一些靜態全局的方法export?default?Vue

第一行 import Vue from './instance/index' 繼續進行一些初始化,instance/index 代碼如下:

//?src/core/instance/index.js
import?{?initMixin?}?from?"./init";
import?{?stateMixin?}?from?"./state";
import?{?lifecycleMixin?}?from?"./lifecycle";
import?{?renderMixin?}?from?"./render";function?Vue(options)?{this._init(options);
}initMixin(Vue);
stateMixin(Vue);
lifecycleMixin(Vue);
renderMixin(Vue);export?default?Vue;

initMixin 是在 Vue 掛載一個 _init 方法,也就是在 new Vue 的時候執行。

import?{?initState?}?from?"./state";export?function?initMixin(Vue)?{Vue.prototype._init?=?function?(options)?{const?vm?=?this;vm.$options?=?options;vm._renderProxy?=?vm;initState(vm);if?(vm.$options.el)?{vm.$mount(vm.$options.el);}};
}

_init 方法調用 initState 方法初始化 datawatchcomputedmethods ,并且把他們變為響應式數據,還有上邊講到的把屬性掛載到 Vue 實例上。

$mount 方法就是前邊講到的,把 render 返回的 dom 掛載到 el 節點上。

剩下的 stateMixinlifecycleMixinrenderMixin 是在 ?Vue.prototype ?原型對象中掛載各種方法,這里不細說了。

所以整體過程就是下邊的樣子:

180421a30e5e544d4af420fe1f0a44f4.png
image-20220529125250794

最開始的各種 Mixin 是在 Vue.prototype ?原型對象上掛載需要的方法,initGlobalAPI 是直接在 Vue 上掛載方法,new Vue 就是傳入 options 屬性,接著調用 this.init 方法將 datawatchcomputedmethods ?這些進行初始化,最后調用 $mount 方法掛載 dom

最終效果

我們運行下程序,修改 webpack.config.jsentry 為我們寫好的測試文件。

const?path?=?require("path");
module.exports?=?{entry:?"./VueLiang0/vueliang0.js",output:?{path:?path.resolve(__dirname,?"./dist"),filename:?"bundle.js",},devServer:?{static:?path.resolve(__dirname,?"./dist"),},
};

然后執行 npm run dev

319abbe8977e157366cdeaaf21fb0955.png
image-20220529125906737

可以看到 datacomputedmethods ?都調用正常,接下來測試一下響應式,我們測試文件中添加了 click 事件。

import?Vue?from?"./src/core/index";new?Vue({el:?"#root",data()?{return?{test:?1,name:?"data:liang",};},watch:?{test(newVal,?oldVal)?{console.log(newVal,?oldVal);},},computed:?{text()?{return?"computed:hello:"?+?this.name;},},methods:?{hello()?{return?"調用methods:hello";},click()?{this.test?=?3;this.name?=?"wind";},},render()?{const?node?=?document.createElement("div");const?dataNode?=?document.createElement("div");dataNode.innerText?=?this.test;node.append(dataNode);const?computedNode?=?document.createElement("div");computedNode.innerText?=?this.text;node.append(computedNode);const?methodsNode?=?document.createElement("div");methodsNode.innerText?=?this.hello();node.append(methodsNode);//?click?事件node.addEventListener("click",?this.click);return?node;},
});

點擊的時候會更改 textname 的值,看一下效果:

e10fc3f19f389fc3b99ce3fd74de2275.gif
Kapture 2022-05-29 at 13.01.11

當我們點擊的時候視圖就自動進行了更新,簡化的響應式系統就被我們實現了。

更詳細代碼的大家可以在 github 進行查看和調試。

https://github.com/wind-liang/vue2

現在我們的 render 函數是直接返回 dom ,當某個屬性改變的時候整個 dom 樹會全部重新生成,但更好的方式肯定是采用虛擬 dom ,進行局部更新。

8003eb8f71e933a3350190926673a7c8.gif

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

你好,我是若川,畢業于江西高校。現在是一名前端開發“工程師”。寫有《學習源碼整體架構系列》20余篇,在知乎、掘金收獲超百萬閱讀。
從2014年起,每年都會寫一篇年度總結,已經堅持寫了8年,點擊查看年度總結。
同時,最近組織了源碼共讀活動,幫助4000+前端人學會看源碼。公眾號愿景:幫助5年內前端人走向前列。

12397f2579abe561742a890034b8f0a5.png

掃碼加我微信 ruochuan12、拉你進源碼共讀

今日話題

目前建有江西|湖南|湖北?籍 前端群,想進群的可以加我微信 ruochuan12?進群。分享、收藏、點贊、在看我的文章就是對我最大的支持~

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/274636.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/274636.shtml
英文地址,請注明出處:http://en.pswp.cn/news/274636.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

WPF 分頁控件應用

效果圖&#xff1a; 前臺代碼&#xff1a; <UserControl x:Class"Layout.UI.Comm.Pager"xmlns"http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x"http://schemas.microsoft.com/winfx/2006/xaml"xmlns:mc"http:/…

李寧品牌重塑_邁伊多品牌重塑的幕后

李寧品牌重塑This post was originally published on the Maido blog.這篇文章最初發表在 Maido博客上 。 You might notice that we’ve had a little facelift at Maido. Or you might not — and that’s totally fine. What we launched at the end of last year was not r…

搭建前端監控,如何采集異常數據?

大家好&#xff0c;我是若川。持續組織了近一年的源碼共讀活動&#xff0c;感興趣的可以 點此加我微信ruochuan12 參與&#xff0c;每周大家一起學習200行左右的源碼&#xff0c;共同進步。同時極力推薦訂閱我寫的《學習源碼整體架構系列》 包含20余篇源碼文章。歷史面試系列。…

產品經理如何提高創造力_如何提高產品設計師的創造力

產品經理如何提高創造力When David Kelley, Bill Moggridge, and Mike Nuttall founded IDEO, a consulting firm that would become one of the most innovative companies of the late 90s, they brought a new perspective in product development.當大衛凱利(David Kelley)…

Github上8個很棒的Vue項目

大家好&#xff0c;我是若川。持續組織了近一年的源碼共讀活動&#xff0c;感興趣的可以 點此加我微信ruochuan12 參與&#xff0c;每周大家一起學習200行左右的源碼&#xff0c;共同進步。同時極力推薦訂閱我寫的《學習源碼整體架構系列》 包含20余篇源碼文章。歷史面試系列。…

域名解析文件hosts文件是什么?如何修改hosts文件?

如何修改hosts文件&#xff1f; hosts文件的位置&#xff1a;xp,2000等系統在 C:\windows\system32\drivers\etc 文件夾中找到Hosts文件并用記事本打開(Windows 9x/Me系統在C:\Windows文件夾中找)按照 ip地址 域名 的格式添加單獨的一行記錄。例如72.14.219.190 www.hbcms.net…

python 投資組合_成功投資組合的提示

python 投資組合Lately, I’ve had some free time during my job transition and have been reviewing a few of my friends’ design portfolios. Gradually, I found some common themes around the feedback I’ve given. And it occurred to me that others might find so…

Github上8個很棒的React項目

大家好&#xff0c;我是若川。持續組織了近一年的源碼共讀活動&#xff0c;感興趣的可以 點此加我微信ruochuan12 參與&#xff0c;每周大家一起學習200行左右的源碼&#xff0c;共同進步。同時極力推薦訂閱我寫的《學習源碼整體架構系列》 包含20余篇源碼文章。歷史面試系列。…

騰訊的筆試題一道

搜羅了一些騰訊的筆試題目 題目是這樣的&#xff1a;在如下8*6的矩陣中&#xff0c;請計算從A移動到B一共有多少種走法&#xff1f;要求每次只能向上揮著向右移動一格&#xff0c;并且不能經過P&#xff1b; B P …

屏幕廣播系統_如何設計系統,而不是屏幕

屏幕廣播系統重點 (Top highlight)Over the past several decades, rapid advances in technology have dramatically enhanced the digital customer experience and their expectations. In the face of these heightened customer expectations, the role of the Interactio…

Umi 4 發布啦

大家好&#xff0c;我是若川。持續組織了近一年的源碼共讀活動&#xff0c;感興趣的可以 點此加我微信ruochuan12 參與&#xff0c;每周大家一起學習200行左右的源碼&#xff0c;共同進步。同時極力推薦訂閱我寫的《學習源碼整體架構系列》 包含20余篇源碼文章。歷史面試系列。…

Win32匯編--加載菜單資源

基本上的窗口都會有一個菜單,現在就來看看Win32匯編中是如何加載菜單的: 1>在工程中添加新的菜單資源 2>雙擊新添加的菜單資源進行編輯 3>菜單欄:Make->Compile RC來編譯資源文件 4>導出資源中的ID號并寫到數據段的.const中 5>下面是完整的源代碼供參考:(工程…

Futura:從納粹主義到月球-甚至更遠

Reading the title of this article, the first thing that will come to mind for some is the funny expression of Buzz Lightyear — the Disney character — when he stretches his arms outwards and utters the famous phrase “To infinity and beyond!” before jump…

如何碎片化時間高效學習前端~

前端技術日新月異&#xff0c;發展迅速&#xff0c;作為一個與時俱進的前端工程師&#xff0c;需要不斷的學習。這里強烈推薦幾個前端開發工程師必備的優質公眾號&#xff0c;希望對你有所幫助。大家可以像我一樣&#xff0c;利用碎片時間閱讀這些公眾號的文章。前端從進階到入…

爬取淘寶定價需要多久時間_如何對設計工作進行定價—停止收??取時間并專注于價值

爬取淘寶定價需要多久時間Pricing creative work is a new concept for most freelancers who are starting their business. We are used to being paid for our time, either by an hourly wage or an annual salary. It makes it simple to quantify how much value we thin…

OEA 框架中集成的 RDLC 報表介紹

之前 OEA 一直用著一個 Delphi 開發的報表&#xff0c;所以兩年來我一直就想在 OEA 中構建一個純 .NET 的報表模塊&#xff0c;但是一想到要開發復雜的報表引擎和設計器就覺得麻煩。所以這事一直拖著。最近開始研究一些成熟的報表引擎&#xff0c;經過對比&#xff0c;還是發現…

昆蟲繁殖_“專為昆蟲而生” –好奇!

昆蟲繁殖重點 (Top highlight)The industry is changing towards a more agile approach and jacks of one trade can go extinct sooner than we think.該 行業正在發生變化 朝著更加靈活的方法和一個貿易的插Kong可以去滅絕快于我們的想法。 I’ve read a quote in a book r…

ECMAScript 2022 正式發布,有哪些新特性?

大家好&#xff0c;我是若川。持續組織了近一年的源碼共讀活動&#xff0c;感興趣的可以 點此加我微信ruochuan12 參與&#xff0c;每周大家一起學習200行左右的源碼&#xff0c;共同進步。同時極力推薦訂閱我寫的《學習源碼整體架構系列》 包含20余篇源碼文章。歷史面試系列。…

字母框如何影響UI內容的理解

What is your earliest memory of reading? Mine’s reading comics. I preferred films over books, I still do, but I seemed to have a fascination for comics. The experience of reading a comic, to me, was somewhere between watching a film and reading a novel, …

Vue2.7 本周發布?支持組合式 API、setup、css v-bind

大家好&#xff0c;我是若川。持續組織了近一年的源碼共讀活動&#xff0c;感興趣的可以 點此加我微信ruochuan12 參與&#xff0c;每周大家一起學習200行左右的源碼&#xff0c;共同進步。同時極力推薦訂閱我寫的《學習源碼整體架構系列》 包含20余篇源碼文章。歷史面試系列。…