摘要:前言最近在學(xué)習(xí)的源碼,剛開始看其源碼,著實(shí)找不到方向,因?yàn)槠湓诘膶?shí)現(xiàn)上還加入了很多本身的鉤子,加大了閱讀難度。
前言
最近在學(xué)習(xí)vue2.0的源碼,剛開始看其vdom源碼,著實(shí)找不到方向,因?yàn)槠湓趘dom的實(shí)現(xiàn)上還加
入了很多vue2.0本身的鉤子,加大了閱讀難度。于是看到第一行尤大說vue2.0的vdom是在snabbdom
的基礎(chǔ)上改過來的,而snabbdom只有不到300sloc,那不妨先從snabbdom入手,熟悉其中的原理,
再配合vue2.0的vdom看,效果可能更好。
virtual-dom可以看做一棵模擬了DOM樹的JavaScript樹,其主要是通過vnode,實(shí)現(xiàn)一個(gè)無
狀態(tài)的組件,當(dāng)組件狀態(tài)發(fā)生更新時(shí),然后觸發(fā)virtual-dom數(shù)據(jù)的變化,然后通過virtual-dom
和真實(shí)DOM的比對(duì),再對(duì)真實(shí)dom更新。
我們知道,當(dāng)我們希望實(shí)現(xiàn)一個(gè)具有復(fù)雜狀態(tài)的界面時(shí),如果我們?cè)诿總€(gè)可能發(fā)生變化的組件上都綁定
事件,綁定字段數(shù)據(jù),那么很快由于狀態(tài)太多,我們需要維護(hù)的事件和字段將會(huì)越來越多,代碼也會(huì)
越來越復(fù)雜,于是,我們想我們可不可以將視圖和狀態(tài)分開來,只要視圖發(fā)生變化,對(duì)應(yīng)狀態(tài)也發(fā)生
變化,然后狀態(tài)變化,我們?cè)僦乩L整個(gè)視圖就好了。這樣的想法雖好,但是代價(jià)太高了,于是我們又
想,能不能只更新狀態(tài)發(fā)生變化的視圖?于是virtual-dom應(yīng)運(yùn)而生,狀態(tài)變化先反饋到vdom上,
vdom在找到最小更新視圖,最后批量更新到真實(shí)DOM上,從而達(dá)到性能的提升。
除此之外,從移植性上看,virtual-dom還對(duì)真實(shí)dom做了一次抽象,這意味著virtual-dom對(duì)應(yīng)
的可以不是瀏覽器的dom,而是不同設(shè)備的組件,極大的方便了多平臺(tái)的使用。
好了,說了這么多,我們先來看看snabbdom吧,我看的是這個(gè)版本的snabbdom
(心塞,typescript學(xué)的不深,看最新版的有點(diǎn)吃力,所以選了ts版本前的一個(gè)版本)。好了我們先
看看snabbdom的主要目錄結(jié)構(gòu)。
名稱 | 類型 | 解釋 |
---|---|---|
dist | 文件夾 | 里面包含了snabddom打包后的文件 |
examples | 文件夾 | 里面包含了使用snabbdom的例子 |
helpers | 文件夾 | 包含svg操作需要的工具 |
modules | 文件夾 | 包含了對(duì)attribute,props,class,dataset,eventlistner,style,hero的操作 |
perf | 文件夾 | 性能測(cè)試 |
test | 文件夾 | 測(cè)試 |
h | 文件 | 把狀態(tài)轉(zhuǎn)化為vnode |
htmldomapi | 文件 | 原生dom操作的抽象 |
is | 文件 | 判斷類型 |
snabbdom.bundle | 文件 | snabbdom本身依賴打包 |
snabbdom | 文件 | snabbdom 核心,包含diff,patch等操作 |
thunk | 文件 | snabbdom下的thunk功能實(shí)現(xiàn) |
vnode | 文件 | 構(gòu)造vnode |
首先,我們從最簡(jiǎn)單的vnode開始入手,vnode實(shí)現(xiàn)的功能非常簡(jiǎn)單,就是講輸入的數(shù)據(jù)轉(zhuǎn)化為vnode
對(duì)象的形式
//VNode函數(shù),用于將輸入轉(zhuǎn)化成VNode /** * * @param sel 選擇器 * @param data 綁定的數(shù)據(jù) * @param children 子節(jié)點(diǎn)數(shù)組 * @param text 當(dāng)前text節(jié)點(diǎn)內(nèi)容 * @param elm 對(duì)真實(shí)dom element的引用 * @returns {{sel: *, data: *, children: *, text: *, elm: *, key: undefined}} */ module.exports = function ( sel, data, children, text, elm ) { var key = data === undefined ? undefined : data.key; return { sel: sel, data: data, children: children, text: text, elm: elm, key: key }; };
vnode主要有5大屬性:
sel 對(duì)應(yīng)的是選擇器,如"div","div#a","div#a.b.c"的形式
data 對(duì)應(yīng)的是vnode綁定的數(shù)據(jù),可以有以下類型:attribute、props、eventlistner、
class、dataset、hook
children 子元素?cái)?shù)組
text 文本,代表該節(jié)點(diǎn)中的文本內(nèi)容
elm 里面存儲(chǔ)著對(duì)對(duì)應(yīng)的真實(shí)dom element的引用
key 用于不同vnode之間的比對(duì)
第二站 h說完vnode,就到h了,h也是一個(gè)包裝函數(shù),主要是在vnode上再做一層包裝,實(shí)現(xiàn)功能如下
如果是svg,則為其添加命名空間
將children中的text包裝成vnode形式
var VNode = require ( "./vnode" ); var is = require ( "./is" ); //添加命名空間(svg才需要) function addNS ( data, children, sel ) { data.ns = "http://www.w3.org/2000/svg"; //如果選擇器 if ( sel !== "foreignObject" && children !== undefined ) { //遞歸為子節(jié)點(diǎn)添加命名空間 for (var i = 0; i < children.length; ++i) { addNS ( children[ i ].data, children[ i ].children, children[ i ].sel ); } } } //將VNode渲染為VDOM /** * * @param sel 選擇器 * @param b 數(shù)據(jù) * @param c 子節(jié)點(diǎn) * @returns {{sel, data, children, text, elm, key}} */ module.exports = function h ( sel, b, c ) { var data = {}, children, text, i; //如果存在子節(jié)點(diǎn) if ( c !== undefined ) { //那么h的第二項(xiàng)就是data data = b; //如果c是數(shù)組,那么存在子element節(jié)點(diǎn) if ( is.array ( c ) ) { children = c; } //否則為子text節(jié)點(diǎn) else if ( is.primitive ( c ) ) { text = c; } } //如果c不存在,只存在b,那么說明需要渲染的vdom不存在data部分,只存在子節(jié)點(diǎn)部分 else if ( b !== undefined ) { if ( is.array ( b ) ) { children = b; } else if ( is.primitive ( b ) ) { text = b; } else { data = b; } } if ( is.array ( children ) ) { for (i = 0; i < children.length; ++i) { //如果子節(jié)點(diǎn)數(shù)組中,存在節(jié)點(diǎn)是原始類型,說明該節(jié)點(diǎn)是text節(jié)點(diǎn),因此我們將它渲染為一個(gè)只包含text的VNode if ( is.primitive ( children[ i ] ) ) children[ i ] = VNode ( undefined, undefined, undefined, children[ i ] ); } } //如果是svg,需要為節(jié)點(diǎn)添加命名空間 if ( sel[ 0 ] === "s" && sel[ 1 ] === "v" && sel[ 2 ] === "g" ) { addNS ( data, children, sel ); } return VNode ( sel, data, children, text, undefined ); };第三站 htmldomapi
htmldomapi中提供了對(duì)原生dom操作的一層抽象,這里就不再闡述了
第四站 modulesmodules中主要包含attributes,class,props,dataset,eventlistener,hero,style
這些模塊,其中attributes,class,props,dataset,eventlistener,style這些模塊是我們
日常所需要的,也是snabbdom.bundle默認(rèn)注入的也是這幾個(gè),這里就詳細(xì)介紹這幾個(gè)模塊
主要功能如下:
從elm的屬性中刪除vnode中不存在的屬性(包括那些boolean類屬性,如果新vnode設(shè)置為false,同樣刪除)
如果oldvnode與vnode用同名屬性,則在elm上更新對(duì)應(yīng)屬性值
如果vnode有新屬性,則添加到elm中
如果存在命名空間,則用setAttributeNS設(shè)置
var NamespaceURIs = { "xlink": "http://www.w3.org/1999/xlink" }; var booleanAttrs = ["allowfullscreen", "async", "autofocus", "autoplay", "checked", "compact", "controls", "declare", "default", "defaultchecked", "defaultmuted", "defaultselected", "defer", "disabled", "draggable", "enabled", "formnovalidate", "hidden", "indeterminate", "inert", "ismap", "itemscope", "loop", "multiple", "muted", "nohref", "noresize", "noshade", "novalidate", "nowrap", "open", "pauseonexit", "readonly", "required", "reversed", "scoped", "seamless", "selected", "sortable", "spellcheck", "translate", "truespeed", "typemustmatch", "visible"]; var booleanAttrsDict = Object.create(null); //創(chuàng)建屬性字典,默認(rèn)為true for(var i=0, len = booleanAttrs.length; i < len; i++) { booleanAttrsDict[booleanAttrs[i]] = true; } function updateAttrs(oldVnode, vnode) { var key, cur, old, elm = vnode.elm, oldAttrs = oldVnode.data.attrs, attrs = vnode.data.attrs, namespaceSplit; //如果舊節(jié)點(diǎn)和新節(jié)點(diǎn)都不包含屬性,立刻返回 if (!oldAttrs && !attrs) return; oldAttrs = oldAttrs || {}; attrs = attrs || {}; // update modified attributes, add new attributes //更新改變了的屬性,添加新的屬性 for (key in attrs) { cur = attrs[key]; old = oldAttrs[key]; //如果舊的屬性和新的屬性不同 if (old !== cur) { //如果是boolean類屬性,當(dāng)vnode設(shè)置為falsy value時(shí),直接刪除,而不是更新值 if(!cur && booleanAttrsDict[key]) elm.removeAttribute(key); else { //否則更新屬性值或者添加屬性 //如果存在命名空間 namespaceSplit = key.split(":"); if(namespaceSplit.length > 1 && NamespaceURIs.hasOwnProperty(namespaceSplit[0])) elm.setAttributeNS(NamespaceURIs[namespaceSplit[0]], key, cur); else elm.setAttribute(key, cur); } } } //remove removed attributes // use `in` operator since the previous `for` iteration uses it (.i.e. add even attributes with undefined value) // the other option is to remove all attributes with value == undefined //刪除不在新節(jié)點(diǎn)屬性中的舊節(jié)點(diǎn)的屬性 for (key in oldAttrs) { if (!(key in attrs)) { elm.removeAttribute(key); } } } module.exports = {create: updateAttrs, update: updateAttrs};class
主要功能如下:
從elm中刪除vnode中不存在的或者值為false的類
將vnode中新的class添加到elm上去
function updateClass(oldVnode, vnode) { var cur, name, elm = vnode.elm, oldClass = oldVnode.data.class, klass = vnode.data.class; //如果舊節(jié)點(diǎn)和新節(jié)點(diǎn)都沒有class,直接返回 if (!oldClass && !klass) return; oldClass = oldClass || {}; klass = klass || {}; //從舊節(jié)點(diǎn)中刪除新節(jié)點(diǎn)不存在的類 for (name in oldClass) { if (!klass[name]) { elm.classList.remove(name); } } //如果新節(jié)點(diǎn)中對(duì)應(yīng)舊節(jié)點(diǎn)的類設(shè)置為false,則刪除該類,如果新設(shè)置為true,則添加該類 for (name in klass) { cur = klass[name]; if (cur !== oldClass[name]) { elm.classList[cur ? "add" : "remove"](name); } } } module.exports = {create: updateClass, update: updateClass};dataset
主要功能如下:
從elm中刪除vnode不存在的屬性集中的屬性
更新屬性集中的屬性值
function updateDataset(oldVnode, vnode) { var elm = vnode.elm, oldDataset = oldVnode.data.dataset, dataset = vnode.data.dataset, key //如果新舊節(jié)點(diǎn)都沒數(shù)據(jù)集,則直接返回 if (!oldDataset && !dataset) return; oldDataset = oldDataset || {}; dataset = dataset || {}; //刪除舊節(jié)點(diǎn)中在新節(jié)點(diǎn)不存在的數(shù)據(jù)集 for (key in oldDataset) { if (!dataset[key]) { delete elm.dataset[key]; } } //更新數(shù)據(jù)集 for (key in dataset) { if (oldDataset[key] !== dataset[key]) { elm.dataset[key] = dataset[key]; } } } module.exports = {create: updateDataset, update: updateDataset}eventlistener
snabbdom中對(duì)事件處理做了一層包裝,真實(shí)DOM的事件觸發(fā)的是對(duì)vnode的操作,主要途徑是:
createListner => 返回handler作事件監(jiān)聽生成器 =>handler上綁定vnode =>將handler作真實(shí)DOM的事件處理器
真實(shí)DOM事件觸發(fā)后 => handler獲得真實(shí)DOM的事件對(duì)象 => 將真實(shí)DOM事件對(duì)象傳入handleEvent => handleEvent找到
對(duì)應(yīng)的vnode事件處理器,然后調(diào)用這個(gè)處理器從而修改vnode
//snabbdom中對(duì)事件處理做了一層包裝,真實(shí)DOM的事件觸發(fā)的是對(duì)vnode的操作 //主要途徑是 // createListner => 返回handler作事件監(jiān)聽生成器 =>handler上綁定vnode =>將handler作真實(shí)DOM的事件處理器 //真實(shí)DOM事件觸發(fā)后 => handler獲得真實(shí)DOM的事件對(duì)象 => 將真實(shí)DOM事件對(duì)象傳入handleEvent => handleEvent找到 //對(duì)應(yīng)的vnode事件處理器,然后調(diào)用這個(gè)處理器從而修改vnode //對(duì)vnode進(jìn)行事件處理 function invokeHandler ( handler, vnode, event ) { if ( typeof handler === "function" ) { // call function handler //將事件處理器在vnode上調(diào)用 handler.call ( vnode, event, vnode ); } //存在事件綁定數(shù)據(jù)或者存在多事件處理器 else if ( typeof handler === "object" ) { //說明只有一個(gè)事件處理器 if ( typeof handler[ 0 ] === "function" ) { //如果綁定數(shù)據(jù)只有一個(gè),則直接將數(shù)據(jù)用call的方式調(diào)用,提高性能 //形如on:{click:[handler,1]} if ( handler.length === 2 ) { handler[ 0 ].call ( vnode, handler[ 1 ], event, vnode ); } //如果存在多個(gè)綁定數(shù)據(jù),則要轉(zhuǎn)化為數(shù)組,用apply的方式調(diào)用,而apply性能比call差 //形如:on:{click:[handler,1,2,3]} else { var args = handler.slice ( 1 ); args.push ( event ); args.push ( vnode ); handler[ 0 ].apply ( vnode, args ); } } else { //如果存在多個(gè)相同事件的不同處理器,則遞歸調(diào)用 //如on:{click:[[handeler1,1],[handler,2]]} for (var i = 0; i < handler.length; i++) { invokeHandler ( handler[ i ] ); } } } } /** * * @param event 真實(shí)dom的事件對(duì)象 * @param vnode */ function handleEvent ( event, vnode ) { var name = event.type, on = vnode.data.on; // 如果找到對(duì)應(yīng)的vnode事件處理器,則調(diào)用 if ( on && on[ name ] ) { invokeHandler ( on[ name ], vnode, event ); } } //事件監(jiān)聽器生成器,用于處理真實(shí)DOM事件 function createListener () { return function handler ( event ) { handleEvent ( event, handler.vnode ); } } //更新事件監(jiān)聽 function updateEventListeners ( oldVnode, vnode ) { var oldOn = oldVnode.data.on, oldListener = oldVnode.listener, oldElm = oldVnode.elm, on = vnode && vnode.data.on, elm = vnode && vnode.elm, name; // optimization for reused immutable handlers //如果新舊事件監(jiān)聽器一樣,則直接返回 if ( oldOn === on ) { return; } // remove existing listeners which no longer used //如果新節(jié)點(diǎn)上沒有事件監(jiān)聽,則將舊節(jié)點(diǎn)上的事件監(jiān)聽都刪除 if ( oldOn && oldListener ) { // if element changed or deleted we remove all existing listeners unconditionally if ( !on ) { for (name in oldOn) { // remove listener if element was changed or existing listeners removed oldElm.removeEventListener ( name, oldListener, false ); } } else { //刪除舊節(jié)點(diǎn)中新節(jié)點(diǎn)不存在的事件監(jiān)聽 for (name in oldOn) { // remove listener if existing listener removed if ( !on[ name ] ) { oldElm.removeEventListener ( name, oldListener, false ); } } } } // add new listeners which has not already attached if ( on ) { // reuse existing listener or create new //如果oldvnode上已經(jīng)有l(wèi)istener,則vnode直接復(fù)用,否則則新建事件處理器 var listener = vnode.listener = oldVnode.listener || createListener (); // update vnode for listener //在事件處理器上綁定vnode listener.vnode = vnode; // if element changed or added we add all needed listeners unconditionally‘ //如果oldvnode上沒有事件處理器 if ( !oldOn ) { for (name in on) { // add listener if element was changed or new listeners added //直接將vnode上的事件處理器添加到elm上 elm.addEventListener ( name, listener, false ); } } else { for (name in on) { // add listener if new listener added //否則添加oldvnode上沒有的事件處理器 if ( !oldOn[ name ] ) { elm.addEventListener ( name, listener, false ); } } } } } module.exports = { create: updateEventListeners, update: updateEventListeners, destroy: updateEventListeners };props
主要功能:
從elm上刪除vnode中不存在的屬性
更新elm上的屬性
function updateProps(oldVnode, vnode) { var key, cur, old, elm = vnode.elm, oldProps = oldVnode.data.props, props = vnode.data.props; //如果新舊節(jié)點(diǎn)都不存在屬性,則直接返回 if (!oldProps && !props) return; oldProps = oldProps || {}; props = props || {}; //刪除舊節(jié)點(diǎn)中新節(jié)點(diǎn)沒有的屬性 for (key in oldProps) { if (!props[key]) { delete elm[key]; } } //更新屬性 for (key in props) { cur = props[key]; old = oldProps[key]; //如果新舊節(jié)點(diǎn)屬性不同,且對(duì)比的屬性不是value或者elm上對(duì)應(yīng)屬性和新屬性也不同,那么就需要更新 if (old !== cur && (key !== "value" || elm[key] !== cur)) { elm[key] = cur; } } } module.exports = {create: updateProps, update: updateProps};style
主要功能如下:
將elm上存在于oldvnode中但不存在于vnode中不存在的style置空
如果vnode.style中的delayed與oldvnode的不同,則更新delayed的屬性值,并在下一幀將elm的style設(shè)置為該值,從而實(shí)現(xiàn)動(dòng)畫過渡效果
非delayed和remove的style直接更新
vnode被destroy時(shí),直接將對(duì)應(yīng)style更新為vnode.data.style.destory的值
vnode被reomve時(shí),如果style.remove不存在,直接調(diào)用全局remove鉤子進(jìn)入下一個(gè)remove過程
如果style.remove存在,那么我們就需要設(shè)置remove動(dòng)畫過渡效果,等到過渡效果結(jié)束之后,才調(diào)用
下一個(gè)remove過程
//如果存在requestAnimationFrame,則直接使用,以優(yōu)化性能,否則用setTimeout var raf = (typeof window !== "undefined" && window.requestAnimationFrame) || setTimeout; var nextFrame = function(fn) { raf(function() { raf(fn); }); }; //通過nextFrame來實(shí)現(xiàn)動(dòng)畫效果 function setNextFrame(obj, prop, val) { nextFrame(function() { obj[prop] = val; }); } function updateStyle(oldVnode, vnode) { var cur, name, elm = vnode.elm, oldStyle = oldVnode.data.style, style = vnode.data.style; //如果oldvnode和vnode都沒有style,直接返回 if (!oldStyle && !style) return; oldStyle = oldStyle || {}; style = style || {}; var oldHasDel = "delayed" in oldStyle; //遍歷oldvnode的style for (name in oldStyle) { //如果vnode中無該style,則置空 if (!style[name]) { elm.style[name] = ""; } } //如果vnode的style中有delayed且與oldvnode中的不同,則在下一幀設(shè)置delayed的參數(shù) for (name in style) { cur = style[name]; if (name === "delayed") { for (name in style.delayed) { cur = style.delayed[name]; if (!oldHasDel || cur !== oldStyle.delayed[name]) { setNextFrame(elm.style, name, cur); } } } //如果不是delayed和remove的style,且不同于oldvnode的值,則直接設(shè)置新值 else if (name !== "remove" && cur !== oldStyle[name]) { elm.style[name] = cur; } } } //設(shè)置節(jié)點(diǎn)被destory時(shí)的style function applyDestroyStyle(vnode) { var style, name, elm = vnode.elm, s = vnode.data.style; if (!s || !(style = s.destroy)) return; for (name in style) { elm.style[name] = style[name]; } } //刪除效果,當(dāng)我們刪除一個(gè)元素時(shí),先回調(diào)用刪除過度效果,過渡完才會(huì)將節(jié)點(diǎn)remove function applyRemoveStyle(vnode, rm) { var s = vnode.data.style; //如果沒有style或沒有style.remove if (!s || !s.remove) { //直接調(diào)用rm,即實(shí)際上是調(diào)用全局的remove鉤子 rm(); return; } var name, elm = vnode.elm, idx, i = 0, maxDur = 0, compStyle, style = s.remove, amount = 0, applied = []; //設(shè)置并記錄remove動(dòng)作后刪除節(jié)點(diǎn)前的樣式 for (name in style) { applied.push(name); elm.style[name] = style[name]; } compStyle = getComputedStyle(elm); //拿到所有需要過渡的屬性 var props = compStyle["transition-property"].split(", "); //對(duì)過渡屬性計(jì)數(shù),這里applied.length >=amount,因?yàn)橛行傩允遣恍枰^渡的 for (; i < props.length; ++i) { if(applied.indexOf(props[i]) !== -1) amount++; } //當(dāng)過渡效果的完成后,才remove節(jié)點(diǎn),調(diào)用下一個(gè)remove過程 elm.addEventListener("transitionend", function(ev) { if (ev.target === elm) --amount; if (amount === 0) rm(); }); } module.exports = {create: updateStyle, update: updateStyle, destroy: applyDestroyStyle, remove: applyRemoveStyle};第五站 is
啃完modules這些大部頭,總算有個(gè)比較好吃的甜品了,他主要功能就是判斷是否為array類型或者原始類型
//is工具庫,用于判斷是否為array或者原始類型 module.exports = { array: Array.isArray, primitive: function(s) { return typeof s === "string" || typeof s === "number"; }, };中途休息
看了這么多源碼,估計(jì)也累了吧,畢竟一下完全理解可能有點(diǎn)難,不妨先休息一下,消化一下,下一章將會(huì)見到最大的boss——snabbdom本身!
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.hztianpu.com/yun/88234.html
摘要:前言在上一章我們學(xué)習(xí)了,等模塊,在這一篇我們將會(huì)學(xué)習(xí)到的核心功能和功能。如果父節(jié)點(diǎn)沒變化,我們就比較所有同層的子節(jié)點(diǎn),對(duì)這些子節(jié)點(diǎn)進(jìn)行刪除創(chuàng)建移位操作。只需要對(duì)兩個(gè)進(jìn)行判斷是否相似,如果相似,則對(duì)他們進(jìn)行操作,否則直接用替換。 前言 在上一章我們學(xué)習(xí)了,modules,vnode,h,htmldomapi,is等模塊,在這一篇我們將會(huì)學(xué)習(xí)到snabbdom的核心功能——patchVno...
摘要:閑聊在學(xué)的過程中,虛擬應(yīng)該是聽的最多的概念之一,得知其是借鑒進(jìn)行開發(fā),故習(xí)之。以我的觀點(diǎn)來看,多個(gè)相同元素渲染時(shí),則需要為每個(gè)元素添加值。 閑聊:在學(xué)vue的過程中,虛擬dom應(yīng)該是聽的最多的概念之一,得知其是借鑒snabbdom.js進(jìn)行開發(fā),故習(xí)之。由于我工作處于IE8的環(huán)境,對(duì)ES6,TS這些知識(shí)的練習(xí)也只是淺嘗輒止,而snabbdom.js從v.0.5.4這個(gè)版本后開始使用TS...
摘要:司徒正美的一款了不起的化方案,支持到。行代碼內(nèi)實(shí)現(xiàn)一個(gè)胡子大哈實(shí)現(xiàn)的作品其實(shí)就是的了源碼學(xué)習(xí)個(gè)人文章源碼學(xué)習(xí)個(gè)人文章源碼學(xué)習(xí)個(gè)人文章源碼學(xué)習(xí)個(gè)人文章這幾片文章的作者都是司徒正美,全面的解析和官方的對(duì)比。 前言 在過去的一個(gè)多月中,為了能夠更深入的學(xué)習(xí),使用React,了解React內(nèi)部算法,數(shù)據(jù)結(jié)構(gòu),我自己,從零開始寫了一個(gè)玩具框架。 截止今日,終于可以發(fā)布第一個(gè)版本,因?yàn)榫驮谧蛱?,?..
摘要:閱讀源碼的時(shí)候,想了解虛擬結(jié)構(gòu)的實(shí)現(xiàn),發(fā)現(xiàn)在的地方。然而慢慢的人們發(fā)現(xiàn),在我們的代碼中布滿了一系列操作的代碼。源碼解析系列源碼解析一準(zhǔn)備工作源碼解析二函數(shù)源碼解析三對(duì)象源碼解析四方法源碼解析五鉤子源碼解析六模塊源碼解析七事件處理個(gè)人博客地址 前言 虛擬 DOM 結(jié)構(gòu)概念隨著 react 的誕生而火起來,之后 vue2.0 也加入了虛擬 DOM 的概念。 閱讀 vue 源碼的時(shí)候,想了解...
閱讀 3589·2023-04-25 22:45
閱讀 1352·2021-11-11 16:54
閱讀 2857·2019-08-30 15:44
閱讀 3249·2019-08-30 15:44
閱讀 1710·2019-08-30 13:55
閱讀 1012·2019-08-29 18:45
閱讀 1251·2019-08-29 17:25
閱讀 1071·2019-08-29 12:59