搜尋
首頁web前端js教程如何使用Vue 2.5中Diff演算法

如何使用Vue 2.5中Diff演算法

Jun 23, 2018 pm 05:00 PM
diffvue演算法

本文將針對Vue 2.5.3版本中所使用的Virtual Dom進行分析。 updataChildren是Diff演算法的核心,所以本文對updataChildren進行了圖文的分析。以下透過本文給大家分享Vue 2.5的Diff演算法,需要的朋友參考下吧

DOM“天生就慢”,所以前端各大框架都提供了對DOM操作進行優化的辦法,Angular中的是髒值檢查,React首先提出了Virtual Dom,Vue2.0也加入了Virtual Dom,與React類似。

本文將針對Vue 2.5.3版本中所使用的Virtual Dom進行分析。

updataChildren是Diff演算法的核心,所以本文對updataChildren進行了圖文的分析。

1.VNode物件

一個VNode的實例包含了以下屬性,這部分程式碼在src/core/vdom/vnode.js裡

export default class VNode {
 tag: string | void;
 data: VNodeData | void;
 children: ?Array<VNode>;
 text: string | void;
 elm: Node | void;
 ns: string | void;
 context: Component | void; // rendered in this component&#39;s scope
 key: string | number | void;
 componentOptions: VNodeComponentOptions | void;
 componentInstance: Component | void; // component instance
 parent: VNode | void; // component placeholder node

 // strictly internal
 raw: boolean; // contains raw HTML? (server only)
 isStatic: boolean; // hoisted static node
 isRootInsert: boolean; // necessary for enter transition check
 isComment: boolean; // empty comment placeholder?
 isCloned: boolean; // is a cloned node?
 isOnce: boolean; // is a v-once node?
 asyncFactory: Function | void; // async component factory function
 asyncMeta: Object | void;
 isAsyncPlaceholder: boolean;
 ssrContext: Object | void;
 functionalContext: Component | void; // real context vm for functional nodes
 functionalOptions: ?ComponentOptions; // for SSR caching
 functionalScopeId: ?string; // functioanl scope id support
  • tag: 目前節點的標籤名稱

  • data: 目前節點的資料對象,具體包含哪些欄位可以參考vue來源碼types /vnode.d.ts中VNodeData的定義

  • children: 陣列類型,包含了目前節點的子節點

  • text: 目前節點的文本,一般文字節點或註解節點會有該屬性

  • elm: 目前虛擬節點對應的真實的dom節點

  • ##ns : 節點的namespace

  • context: 編譯作用域

  • functionalContext: 函數化元件的作用域

  • #key: 節點的key屬性,用於作為節點的標識,有利於patch的優化

  • #componentOptions: 建立元件實例時會用到的選項資訊

  • child: 目前節點對應的元件實例

  • parent: 元件的佔位節點

  • raw: raw html

  • isStatic: 靜態節點的標識

  • #isRootInsert: 是否以根節點插入,被

  • #isComment: 目前節點是否為註解節點

  • ##isCloned: 目前節點是否為複製節點
  • isOnce: 目前節點是否有v- once指令

2.VNode的分類VNode可以理解為VueVirtual Dom的一個基類,透過VNode建構函式產生的VNnode實例可為下列幾類:

    EmptyVNode: 沒有內容的註解節點
  • TextVNode: 文字節點
  • ElementVNode: 普通元素節點
  • ComponentVNode: 元件節點
  • CloneVNode: 複製節點,可以是以上任意型別的節點,唯一的差別在於isCloned屬性為true

#3.Create-Element原始碼解析

  • ##這部分程式碼在src/core/vdom/create-element.js裡,我就直接黏程式碼加上我的註解了
  • export function createElement (
     context: Component,
     tag: any,
     data: any,
     children: any,
     normalizationType: any,
     alwaysNormalize: boolean
    ): VNode {
     // 兼容不传data的情况
     if (Array.isArray(data) || isPrimitive(data)) {
     normalizationType = children
     children = data
     data = undefined
     }
     // 如果alwaysNormalize是true
     // 那么normalizationType应该设置为常量ALWAYS_NORMALIZE的值
     if (isTrue(alwaysNormalize)) {
     normalizationType = ALWAYS_NORMALIZE
     }
     // 调用_createElement创建虚拟节点
     return _createElement(context, tag, data, children, normalizationType)
    }
    
    export function _createElement (
     context: Component,
     tag?: string | Class<Component> | Function | Object,
     data?: VNodeData,
     children?: any,
     normalizationType?: number
    ): VNode {
    
     /**
     * 如果存在data.__ob__,说明data是被Observer观察的数据
     * 不能用作虚拟节点的data
     * 需要抛出警告,并返回一个空节点
     *
     * 被监控的data不能被用作vnode渲染的数据的原因是:
     * data在vnode渲染过程中可能会被改变,这样会触发监控,导致不符合预期的操作
     */
     if (isDef(data) && isDef((data: any).__ob__)) {
     process.env.NODE_ENV !== &#39;production&#39; && warn(
      `Avoid using observed data object as vnode data: ${JSON.stringify(data)}\n` +
      &#39;Always create fresh vnode data objects in each render!&#39;,
      context
     )
     return createEmptyVNode()
     }
     // object syntax in v-bind
     if (isDef(data) && isDef(data.is)) {
     tag = data.is
     }
     if (!tag) {
     // 当组件的is属性被设置为一个falsy的值
     // Vue将不会知道要把这个组件渲染成什么
     // 所以渲染一个空节点
     // in case of component :is set to falsy value
     return createEmptyVNode()
     }
     // key为非原始值警告
     // warn against non-primitive key
     if (process.env.NODE_ENV !== &#39;production&#39; &&
     isDef(data) && isDef(data.key) && !isPrimitive(data.key)
     ) {
     warn(
      &#39;Avoid using non-primitive value as key, &#39; +
      &#39;use string/number value instead.&#39;,
      context
     )
     }
     // 作用域插槽
     // support single function children as default scoped slot
     if (Array.isArray(children) &&
     typeof children[0] === &#39;function&#39;
     ) {
     data = data || {}
     data.scopedSlots = { default: children[0] }
     children.length = 0
     }
     // 根据normalizationType的值,选择不同的处理方法
     if (normalizationType === ALWAYS_NORMALIZE) {
     children = normalizeChildren(children)
     } else if (normalizationType === SIMPLE_NORMALIZE) {
     children = simpleNormalizeChildren(children)
     }
     let vnode, ns
     // 如果标签名是字符串类型
     if (typeof tag === &#39;string&#39;) {
     let Ctor
     // 获取标签的命名空间
     ns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag)
     // 如果是保留标签
     if (config.isReservedTag(tag)) {
      // platform built-in elements
      // 就创建这样一个vnode
      vnode = new VNode(
      config.parsePlatformTagName(tag), data, children,
      undefined, undefined, context
      )
      // 如果不是保留字标签,尝试从vm的components上查找是否有这个标签的定义
     } else if (isDef(Ctor = resolveAsset(context.$options, &#39;components&#39;, tag))) {
      // component
      // 如果找到,就创建虚拟组件节点
      vnode = createComponent(Ctor, data, context, children, tag)
     } else {
      // unknown or unlisted namespaced elements
      // check at runtime because it may get assigned a namespace when its
      // parent normalizes children
      // 兜底方案,创建一个正常的vnode
      vnode = new VNode(
      tag, data, children,
      undefined, undefined, context
      )
     }
     } else {
     // 当tag不是字符串的时候,我们认为tag是组件的构造类
     // 所以直接创建
     // direct component options / constructor
     vnode = createComponent(tag, data, context, children)
     }
     if (isDef(vnode)) {
     // 应用命名空间
     if (ns) applyNS(vnode, ns)
     return vnode
     } else {
     // 返回一个空节点
     return createEmptyVNode()
     }
    }
    function applyNS (vnode, ns, force) {
     vnode.ns = ns
     if (vnode.tag === &#39;foreignObject&#39;) {
     // use default namespace inside foreignObject
     ns = undefined
     force = true
     }
     if (isDef(vnode.children)) {
     for (let i = 0, l = vnode.children.length; i < l; i++) {
      const child = vnode.children[i]
      if (isDef(child.tag) && (isUndef(child.ns) || isTrue(force))) {
      applyNS(child, ns, force)
      }
     }
     }
    }
  • ##4.Patch原理

  • #patch函數的定義在src/core/vdom/patch.js中,patch邏輯比較簡單,就不黏程式碼了

  • ##patch函數接收6個參數:
  • oldVnode: 舊的虛擬節點或舊的真實dom節點

  • ##vnode: 新的虛擬節點
  • #hydrating: 是否要跟真是dom混合

removeOnly: 特殊flag,用於

parentElm: 父節點

refElm: 新節點會插入到refElm之前

patch的邏輯是:

##if vnode不存在但是oldVnode存在,說明意圖是要銷毀舊節點,那麼就調用invokeDestroyHook(oldVnode)來進行銷

if oldVnode不存在但是vnode存在,說明意圖是要創建新節點,那麼就調用createElm來建立新節點

else 當vnode和oldVnode都存在時

if oldVnode和vnode是同一個節點,就呼叫patchVnode來進行patch
  • 當vnode和oldVnode不是同一個節點時,如果oldVnode是真實dom節點或hydrating設定為true,需要用hydrate函數將虛擬dom和真是dom進行映射,然後將oldVnode設定為對應的虛擬dom,找到oldVnode.elm的父節點,根據vnode建立一個真實dom節點並插入到該父節點中oldVnode.elm的位置

  • patchVnode的邏輯是:
  • 1.如果oldVnode跟vnode完全一致,那麼不需要做任何事情
  • 2.如果oldVnode跟vnode都是靜態節點,且具有相同的key,當vnode是克隆節點或是v-once指令控制的節點時,只需要把oldVnode. elm和oldVnode.child都複製到vnode上,也不用再有其他操作

    3.否則,如果vnode不是文字節點或註解節點
  • ##如果oldVnode和vnode都有子節點,且2方的子節點不完全一致,就執行updateChildren

    ##########如果只有oldVnode有子節點,那就把這些節點都刪除###### #######如果只有vnode有子節點,那就建立這些子節點############如果oldVnode和vnode都沒有子節點,但oldVnode是文字節點或註解節點,就把vnode.elm的文字設定為空字串###

4.如果vnode是文本节点或注释节点,但是vnode.text != oldVnode.text时,只需要更新vnode.elm的文本内容就可以

代码如下:

 function patchVnode (oldVnode, vnode, insertedVnodeQueue, removeOnly) {
 // 如果新旧节点一致,什么都不做
 if (oldVnode === vnode) {
  return
 }
 // 让vnode.el引用到现在的真实dom,当el修改时,vnode.el会同步变化
 const elm = vnode.elm = oldVnode.elm
 // 异步占位符
 if (isTrue(oldVnode.isAsyncPlaceholder)) {
  if (isDef(vnode.asyncFactory.resolved)) {
  hydrate(oldVnode.elm, vnode, insertedVnodeQueue)
  } else {
  vnode.isAsyncPlaceholder = true
  }
  return
 }
 // reuse element for static trees.
 // note we only do this if the vnode is cloned -
 // if the new node is not cloned it means the render functions have been
 // reset by the hot-reload-api and we need to do a proper re-render.
 // 如果新旧都是静态节点,并且具有相同的key
 // 当vnode是克隆节点或是v-once指令控制的节点时,只需要把oldVnode.elm和oldVnode.child都复制到vnode上
 // 也不用再有其他操作
 if (isTrue(vnode.isStatic) &&
  isTrue(oldVnode.isStatic) &&
  vnode.key === oldVnode.key &&
  (isTrue(vnode.isCloned) || isTrue(vnode.isOnce))
 ) {
  vnode.componentInstance = oldVnode.componentInstance
  return
 }
 let i
 const data = vnode.data
 if (isDef(data) && isDef(i = data.hook) && isDef(i = i.prepatch)) {
  i(oldVnode, vnode)
 }
 const oldCh = oldVnode.children
 const ch = vnode.children
 if (isDef(data) && isPatchable(vnode)) {
  for (i = 0; i < cbs.update.length; ++i) cbs.update[i](oldVnode, vnode)
  if (isDef(i = data.hook) && isDef(i = i.update)) i(oldVnode, vnode)
 }
 // 如果vnode不是文本节点或者注释节点
 if (isUndef(vnode.text)) {
  // 并且都有子节点
  if (isDef(oldCh) && isDef(ch)) {
  // 并且子节点不完全一致,则调用updateChildren
  if (oldCh !== ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly)
  // 如果只有新的vnode有子节点
  } else if (isDef(ch)) {
  if (isDef(oldVnode.text)) nodeOps.setTextContent(elm, &#39;&#39;)
  // elm已经引用了老的dom节点,在老的dom节点上添加子节点
  addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue)
  // 如果新vnode没有子节点,而vnode有子节点,直接删除老的oldCh
  } else if (isDef(oldCh)) {
  removeVnodes(elm, oldCh, 0, oldCh.length - 1)
  // 如果老节点是文本节点
  } else if (isDef(oldVnode.text)) {
  nodeOps.setTextContent(elm, &#39;&#39;)
  }
  // 如果新vnode和老vnode是文本节点或注释节点
  // 但是vnode.text != oldVnode.text时,只需要更新vnode.elm的文本内容就可以
 } else if (oldVnode.text !== vnode.text) {
  nodeOps.setTextContent(elm, vnode.text)
 }
 if (isDef(data)) {
  if (isDef(i = data.hook) && isDef(i = i.postpatch)) i(oldVnode, vnode)
 }
 }

5.updataChildren原理

updateChildren的逻辑是:

分别获取oldVnode和vnode的firstChild、lastChild,赋值给oldStartVnode、oldEndVnode、newStartVnode、newEndVnode

如果oldStartVnode和newStartVnode是同一节点,调用patchVnode进行patch,然后将oldStartVnode和newStartVnode都设置为下一个子节点,

如果oldEndVnode和newEndVnode是同一节点,调用patchVnode进行patch,然后将oldEndVnode和newEndVnode都设置为上一个子节点,重复上述流程

如果oldStartVnode和newEndVnode是同一节点,调用patchVnode进行patch,如果removeOnly是false,那么可以把oldStartVnode.elm移动到oldEndVnode.elm之后,然后把oldStartVnode设置为下一个节点,newEndVnode设置为上一个节点,重复上述流程

如果newStartVnode和oldEndVnode是同一节点,调用patchVnode进行patch,如果removeOnly是false,那么可以把oldEndVnode.elm移动到oldStartVnode.elm之前,然后把newStartVnode设置为下一个节点,oldEndVnode设置为上一个节点,重复上述流程

如果以上都不匹配,就尝试在oldChildren中寻找跟newStartVnode具有相同key的节点,如果找不到相同key的节点,说明newStartVnode是一个新节点,就创建一个,然后把newStartVnode设置为下一个节点

如果上一步找到了跟newStartVnode相同key的节点,那么通过其他属性的比较来判断这2个节点是否是同一个节点,如果是,就调用patchVnode进行patch,如果removeOnly是false,就把newStartVnode.elm插入到oldStartVnode.elm之前,把newStartVnode设置为下一个节点,重复上述流程

如果在oldChildren中没有寻找到newStartVnode的同一节点,那就创建一个新节点,把newStartVnode设置为下一个节点,重复上述流程

如果oldStartVnode跟oldEndVnode重合了,并且newStartVnode跟newEndVnode也重合了,这个循环就结束了

具体代码如下:

function updateChildren (parentElm, oldCh, newCh, insertedVnodeQueue, removeOnly) {
 let oldStartIdx = 0 // 旧头索引
 let newStartIdx = 0 // 新头索引
 let oldEndIdx = oldCh.length - 1 // 旧尾索引
 let newEndIdx = newCh.length - 1 // 新尾索引
 let oldStartVnode = oldCh[0] // oldVnode的第一个child
 let oldEndVnode = oldCh[oldEndIdx] // oldVnode的最后一个child
 let newStartVnode = newCh[0] // newVnode的第一个child
 let newEndVnode = newCh[newEndIdx] // newVnode的最后一个child
 let oldKeyToIdx, idxInOld, vnodeToMove, refElm
 // removeOnly is a special flag used only by <transition-group>
 // to ensure removed elements stay in correct relative positions
 // during leaving transitions
 const canMove = !removeOnly
 // 如果oldStartVnode和oldEndVnode重合,并且新的也都重合了,证明diff完了,循环结束
 while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
  // 如果oldVnode的第一个child不存在
  if (isUndef(oldStartVnode)) {
  // oldStart索引右移
  oldStartVnode = oldCh[++oldStartIdx] // Vnode has been moved left
  // 如果oldVnode的最后一个child不存在
  } else if (isUndef(oldEndVnode)) {
  // oldEnd索引左移
  oldEndVnode = oldCh[--oldEndIdx]
  // oldStartVnode和newStartVnode是同一个节点
  } else if (sameVnode(oldStartVnode, newStartVnode)) {
  // patch oldStartVnode和newStartVnode, 索引左移,继续循环
  patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue)
  oldStartVnode = oldCh[++oldStartIdx]
  newStartVnode = newCh[++newStartIdx]
  // oldEndVnode和newEndVnode是同一个节点
  } else if (sameVnode(oldEndVnode, newEndVnode)) {
  // patch oldEndVnode和newEndVnode,索引右移,继续循环
  patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue)
  oldEndVnode = oldCh[--oldEndIdx]
  newEndVnode = newCh[--newEndIdx]
  // oldStartVnode和newEndVnode是同一个节点
  } else if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right
  // patch oldStartVnode和newEndVnode
  patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue)
  // 如果removeOnly是false,则将oldStartVnode.eml移动到oldEndVnode.elm之后
  canMove && nodeOps.insertBefore(parentElm, oldStartVnode.elm, nodeOps.nextSibling(oldEndVnode.elm))
  // oldStart索引右移,newEnd索引左移
  oldStartVnode = oldCh[++oldStartIdx]
  newEndVnode = newCh[--newEndIdx]
  // 如果oldEndVnode和newStartVnode是同一个节点
  } else if (sameVnode(oldEndVnode, newStartVnode)) { // Vnode moved left
  // patch oldEndVnode和newStartVnode
  patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue)
  // 如果removeOnly是false,则将oldEndVnode.elm移动到oldStartVnode.elm之前
  canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm)
  // oldEnd索引左移,newStart索引右移
  oldEndVnode = oldCh[--oldEndIdx]
  newStartVnode = newCh[++newStartIdx]
  // 如果都不匹配
  } else {
  if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx)
  // 尝试在oldChildren中寻找和newStartVnode的具有相同的key的Vnode
  idxInOld = isDef(newStartVnode.key)
   ? oldKeyToIdx[newStartVnode.key]
   : findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx)
  // 如果未找到,说明newStartVnode是一个新的节点
  if (isUndef(idxInOld)) { // New element
   // 创建一个新Vnode
   createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm)
  // 如果找到了和newStartVnodej具有相同的key的Vnode,叫vnodeToMove
  } else {
   vnodeToMove = oldCh[idxInOld]
   /* istanbul ignore if */
   if (process.env.NODE_ENV !== &#39;production&#39; && !vnodeToMove) {
   warn(
    &#39;It seems there are duplicate keys that is causing an update error. &#39; +
    &#39;Make sure each v-for item has a unique key.&#39;
   )
   }
   // 比较两个具有相同的key的新节点是否是同一个节点
   //不设key,newCh和oldCh只会进行头尾两端的相互比较,设key后,除了头尾两端的比较外,还会从用key生成的对象oldKeyToIdx中查找匹配的节点,所以为节点设置key可以更高效的利用dom。
   if (sameVnode(vnodeToMove, newStartVnode)) {
   // patch vnodeToMove和newStartVnode
   patchVnode(vnodeToMove, newStartVnode, insertedVnodeQueue)
   // 清除
   oldCh[idxInOld] = undefined
   // 如果removeOnly是false,则将找到的和newStartVnodej具有相同的key的Vnode,叫vnodeToMove.elm
   // 移动到oldStartVnode.elm之前
   canMove && nodeOps.insertBefore(parentElm, vnodeToMove.elm, oldStartVnode.elm)
   // 如果key相同,但是节点不相同,则创建一个新的节点
   } else {
   // same key but different element. treat as new element
   createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm)
   }
  }
  // 右移
  newStartVnode = newCh[++newStartIdx]
  }
 }

6.具体的Diff分析

不设key,newCh和oldCh只会进行头尾两端的相互比较,设key后,除了头尾两端的比较外,还会从用key生成的对象oldKeyToIdx中查找匹配的节点,所以为节点设置key可以更高效的利用dom。

diff的遍历过程中,只要是对dom进行的操作都调用api.insertBefore,api.insertBefore只是原生insertBefore的简单封装。

比较分为两种,一种是有vnode.key的,一种是没有的。但这两种比较对真实dom的操作是一致的。

对于与sameVnode(oldStartVnode, newStartVnode)和sameVnode(oldEndVnode,newEndVnode)为true的情况,不需要对dom进行移动。

总结遍历过程,有3种dom操作:上述图中都有

1.当oldStartVnode,newEndVnode值得比较,说明oldStartVnode.el跑到oldEndVnode.el的后边了。

2.当oldEndVnode,newStartVnode值得比较,oldEndVnode.el跑到了oldStartVnode.el的前边,准确的说应该是oldEndVnode.el需要移动到oldStartVnode.el的前边”。

3.newCh中的节点oldCh里没有, 将新节点插入到oldStartVnode.el的前边

在结束时,分为两种情况:

1.oldStartIdx > oldEndIdx,可以认为oldCh先遍历完。当然也有可能newCh此时也正好完成了遍历,统一都归为此类。此时newStartIdx和newEndIdx之间的vnode是新增的,调用addVnodes,把他们全部插进before的后边,before很多时候是为null的。addVnodes调用的是insertBefore操作dom节点,我们看看insertBefore的文档:parentElement.insertBefore(newElement, referenceElement)

如果referenceElement为null则newElement将被插入到子节点的末尾。如果newElement已经在DOM树中,newElement首先会从DOM树中移除。所以before为null,newElement将被插入到子节点的末尾。

2.newStartIdx > newEndIdx,可以認為newCh先遍歷完。此時oldStartIdx和oldEndIdx之間的vnode在新的子節點裡已經不存在了,調用removeVnodes將它們從dom裡刪除

上面是我整理給大家的,希望今後會對大家有幫助。

相關文章:

Chrome Firefox 自帶調試工具調試(詳細教程)

有關Vue.js如何實現無限滾動加載

使用Angular如何實作表格過濾

#使用JavaScript如何實作計算器

以上是如何使用Vue 2.5中Diff演算法的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
了解JavaScript引擎:實施詳細信息了解JavaScript引擎:實施詳細信息Apr 17, 2025 am 12:05 AM

理解JavaScript引擎內部工作原理對開發者重要,因為它能幫助編寫更高效的代碼並理解性能瓶頸和優化策略。 1)引擎的工作流程包括解析、編譯和執行三個階段;2)執行過程中,引擎會進行動態優化,如內聯緩存和隱藏類;3)最佳實踐包括避免全局變量、優化循環、使用const和let,以及避免過度使用閉包。

Python vs. JavaScript:學習曲線和易用性Python vs. JavaScript:學習曲線和易用性Apr 16, 2025 am 12:12 AM

Python更適合初學者,學習曲線平緩,語法簡潔;JavaScript適合前端開發,學習曲線較陡,語法靈活。 1.Python語法直觀,適用於數據科學和後端開發。 2.JavaScript靈活,廣泛用於前端和服務器端編程。

Python vs. JavaScript:社區,圖書館和資源Python vs. JavaScript:社區,圖書館和資源Apr 15, 2025 am 12:16 AM

Python和JavaScript在社區、庫和資源方面的對比各有優劣。 1)Python社區友好,適合初學者,但前端開發資源不如JavaScript豐富。 2)Python在數據科學和機器學習庫方面強大,JavaScript則在前端開發庫和框架上更勝一籌。 3)兩者的學習資源都豐富,但Python適合從官方文檔開始,JavaScript則以MDNWebDocs為佳。選擇應基於項目需求和個人興趣。

從C/C到JavaScript:所有工作方式從C/C到JavaScript:所有工作方式Apr 14, 2025 am 12:05 AM

從C/C 轉向JavaScript需要適應動態類型、垃圾回收和異步編程等特點。 1)C/C 是靜態類型語言,需手動管理內存,而JavaScript是動態類型,垃圾回收自動處理。 2)C/C 需編譯成機器碼,JavaScript則為解釋型語言。 3)JavaScript引入閉包、原型鍊和Promise等概念,增強了靈活性和異步編程能力。

JavaScript引擎:比較實施JavaScript引擎:比較實施Apr 13, 2025 am 12:05 AM

不同JavaScript引擎在解析和執行JavaScript代碼時,效果會有所不同,因為每個引擎的實現原理和優化策略各有差異。 1.詞法分析:將源碼轉換為詞法單元。 2.語法分析:生成抽象語法樹。 3.優化和編譯:通過JIT編譯器生成機器碼。 4.執行:運行機器碼。 V8引擎通過即時編譯和隱藏類優化,SpiderMonkey使用類型推斷系統,導致在相同代碼上的性能表現不同。

超越瀏覽器:現實世界中的JavaScript超越瀏覽器:現實世界中的JavaScriptApr 12, 2025 am 12:06 AM

JavaScript在現實世界中的應用包括服務器端編程、移動應用開發和物聯網控制:1.通過Node.js實現服務器端編程,適用於高並發請求處理。 2.通過ReactNative進行移動應用開發,支持跨平台部署。 3.通過Johnny-Five庫用於物聯網設備控制,適用於硬件交互。

使用Next.js(後端集成)構建多租戶SaaS應用程序使用Next.js(後端集成)構建多租戶SaaS應用程序Apr 11, 2025 am 08:23 AM

我使用您的日常技術工具構建了功能性的多租戶SaaS應用程序(一個Edtech應用程序),您可以做同樣的事情。 首先,什麼是多租戶SaaS應用程序? 多租戶SaaS應用程序可讓您從唱歌中為多個客戶提供服務

如何使用Next.js(前端集成)構建多租戶SaaS應用程序如何使用Next.js(前端集成)構建多租戶SaaS應用程序Apr 11, 2025 am 08:22 AM

本文展示了與許可證確保的後端的前端集成,並使用Next.js構建功能性Edtech SaaS應用程序。 前端獲取用戶權限以控制UI的可見性並確保API要求遵守角色庫

See all articles

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

AI Hentai Generator

AI Hentai Generator

免費產生 AI 無盡。

熱門文章

R.E.P.O.能量晶體解釋及其做什麼(黃色晶體)
1 個月前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳圖形設置
1 個月前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您聽不到任何人,如何修復音頻
1 個月前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.聊天命令以及如何使用它們
1 個月前By尊渡假赌尊渡假赌尊渡假赌

熱工具

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

SublimeText3 英文版

SublimeText3 英文版

推薦:為Win版本,支援程式碼提示!

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

MantisBT

MantisBT

Mantis是一個易於部署的基於Web的缺陷追蹤工具,用於幫助產品缺陷追蹤。它需要PHP、MySQL和一個Web伺服器。請查看我們的演示和託管服務。

VSCode Windows 64位元 下載

VSCode Windows 64位元 下載

微軟推出的免費、功能強大的一款IDE編輯器