IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> JavaScript知识库 -> Vue2 源码解读三:Vue实例 -> 正文阅读

[JavaScript知识库]Vue2 源码解读三:Vue实例

文章目录

Vue2 源码解读一:Global API
Vue2 源码解读二:mergeOptions
Vue2 源码解读三:Vue实例
??1、入口方法
??2、initMixin
??3、stateMixin
??4、eventsMixin
??5、lifecycleMixin
??6、renderMixin


Vue2 源码解读三:Vue实例

在创建Vue应用时,都会用到初始化Vue实例对象的方法new Vue(options)。接下来,就看看在创建Vue实例对象时,Vue2源码都做了些什么?

1、入口文件

先看一下创建Vue时的入口文件src/instance/index.js

import { initMixin } from './init'
import { stateMixin } from './state'
import { renderMixin } from './render'
import { eventsMixin } from './events'
import { lifecycleMixin } from './lifecycle'
import { warn } from '../util/index'

// 定义Vue
function Vue (options) {
  // 必须是Vue的实例
  if (process.env.NODE_ENV !== 'production' &&
    !(this instanceof Vue)
  ) {
    warn('Vue is a constructor and should be called with the `new` keyword')
  }
  // 调用Vue初始化方法(在init.js中定义的)
  this._init(options)
}

// 给Vue实例添加_init方法
initMixin(Vue)
// 给Vue实例添加 state相关的属性和方法
stateMixin(Vue)
// 给Vue实例添加$on、$off、$once、$emit
eventsMixin(Vue)
// 给Vue实例添加生命周期的主动触发方法
lifecycleMixin(Vue)
// 给Vue实例添加渲染函数和nextTick钩子函数
renderMixin(Vue)

export default Vue

从以上源码可以看出,先定义function Vue,然后给这个Vue的原型属性prototype上添加一些属性和方法,以$开头的是暴露给用户使用,以_开头的是Vue内部使用。new Vue时调用_init初始化方法。

  1. initMixin(Vue):该方法在Vue的原型属性prototype上添加_init初始化方法;
  2. stateMixin(Vue):该方法在Vue的原型属性prototype上添加$data$props数据属性,添加$set响应式设置、$del响应式删除、$watch添加建建ring器方法;
  3. eventsMixin(Vue):该方法在Vue的原型属性prototype上添加$on挂载监听方法、$once只监听一次的监听方法、$off卸载监听方法、$emit暴露监听方法;
  4. lifecycleMixin(Vue):该方法在Vue的原型属性prototype上添加_update$forceUpdate强制更新视图方法、$destroy销毁Vue实例函数;
  5. renderMixin(Vue):该方法在Vue的原型属性prototype上添加$nextTick等待dom更新完成的钩子函数、_render渲染函数。

大概可以分为4类:

  1. 实例 property:vm.$data、vm.$props、vm.$el、vm.$options、vm.$parent、vm.$root、vm.$children、vm.$slots、vm.$scopedSlots、vm.$refs、vm.$isServer、vm.$attrs、vm.$listeners 。 使用详情请看官方说明
  2. 实例方法 / 数据:vm.$watch、vm.$set、vm.$delete。 使用详情请看官方说明
  3. 实例方法 / 事件:vm.$on、vm.$once、vm.$off、vm.$emit。 使用详情请看官方说明
  4. 实例方法 / 生命周期:vm.$mount、vm.$forceUpdate、vm.$nextTick、vm.$destroy。 使用详情请看官方说明

2、initMixin

该方法在init.js里面,整个js文件所做的都是给Vue实例添加_init方法。

/* @flow */

import config from '../config'
import { initProxy } from './proxy'
import { initState } from './state'
import { initRender } from './render'
import { initEvents } from './events'
import { mark, measure } from '../util/perf'
import { initLifecycle, callHook } from './lifecycle'
import { initProvide, initInjections } from './inject'
import { extend, mergeOptions, formatComponentName } from '../util/index'

// 全局的自增ID
let uid = 0

export function initMixin (Vue: Class<Component>) {
  // Vue初始化时的方法
  Vue.prototype._init = function (options?: Object) {
    const vm: Component = this
    // a uid 自增ID
    vm._uid = uid++

    let startTag, endTag
    /* istanbul ignore if */
    if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
      startTag = `vue-perf-start:${vm._uid}`
      endTag = `vue-perf-end:${vm._uid}`
      mark(startTag)
    }

    // a flag to avoid this being observed
    // _isVue   不会被监听
    vm._isVue = true
    // merge options
    if (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 {
      // 合并options
      vm.$options = mergeOptions(
        //  解析vm的构造函数
        resolveConstructorOptions(vm.constructor),
        options || {},
        vm
      )
    }
    /* istanbul ignore else */
    if (process.env.NODE_ENV !== 'production') {
      // 使用Proxy代理_renderProxy
      initProxy(vm)
    } else {
      vm._renderProxy = vm
    }
    // expose real self
    vm._self = vm
    // 初始化声明周期
    initLifecycle(vm)
    // 初始化事件
    initEvents(vm)
    // 初始化渲染函数
    initRender(vm)
    // 调用beforeCreate钩子
    callHook(vm, 'beforeCreate')
    // 初始化Injections
    initInjections(vm) // resolve injections before data/props
    // 初始化state
    initState(vm)
    // 初始化Provide
    initProvide(vm) // resolve provide after data/props
    // 调用created钩子
    callHook(vm, 'created')

    /* istanbul ignore if */
    if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
      vm._name = formatComponentName(vm, false)
      mark(endTag)
      measure(`vue ${vm._name} init`, startTag, endTag)
    }

    // 挂载el
    if (vm.$options.el) {
      vm.$mount(vm.$options.el)
    }
  }
}

export function initInternalComponent (vm: Component, options: InternalComponentOptions) {
  const opts = vm.$options = Object.create(vm.constructor.options)
  // doing this because it's faster than dynamic enumeration.
  // 手动赋值这样做,比动态枚举更快
  // 父节点处理
  const parentVnode = options._parentVnode
  opts.parent = options.parent
  opts._parentVnode = parentVnode

  // 组件属性处理
  const vnodeComponentOptions = parentVnode.componentOptions
  opts.propsData = vnodeComponentOptions.propsData
  opts._parentListeners = vnodeComponentOptions.listeners
  opts._renderChildren = vnodeComponentOptions.children
  opts._componentTag = vnodeComponentOptions.tag

  // render函数处理
  if (options.render) {
    opts.render = options.render
    opts.staticRenderFns = options.staticRenderFns
  }
}

export function resolveConstructorOptions (Ctor: Class<Component>) {
  let options = Ctor.options
  // 处理super,来自于extend
  if (Ctor.super) {
    // 递归处理super
    const superOptions = resolveConstructorOptions(Ctor.super)
    const cachedSuperOptions = Ctor.superOptions
    if (superOptions !== cachedSuperOptions) {
      // super option changed,
      // need to resolve new options.
      // super发生变化,采用新的
      Ctor.superOptions = superOptions
      // check if there are any late-modified/attached options (#4976)
      // 检查更新的options
      const modifiedOptions = resolveModifiedOptions(Ctor)
      // update base extend options
      if (modifiedOptions) {
        extend(Ctor.extendOptions, modifiedOptions)
      }
      // 合并options
      options = Ctor.options = mergeOptions(superOptions, Ctor.extendOptions)
      if (options.name) {
        options.components[options.name] = Ctor
      }
    }
  }
  return options
}

function resolveModifiedOptions (Ctor: Class<Component>): ?Object {
  let modified
  const latest = Ctor.options
  // 来自于extend
  const sealed = Ctor.sealedOptions
  // 对比Ctor.options和Ctor.sealedOptions,统计更新
  for (const key in latest) {
    if (latest[key] !== sealed[key]) {
      if (!modified) modified = {}
      modified[key] = latest[key]
    }
  }
  return modified
}

该方法大致的流程如下:

  1. 添加_uid属性,标志Vue实例的唯一性;添加_isVue属性,标记属性不被监听。
  2. 如果是组件,则优化内部组件initInternalComponent(),否则合并options给Vue的$options属性。
  3. 添加_renderProxy属性,优先使用Proxy数据劫持,然后对get、set、has方法做一些逻辑判断提示。
  4. initLifecycle(vm):初始化生命周期,对生命周期的相关属性设置默认值,比如:_isMounted、_watcher、_inactive、_isDestroyed等等。
  5. initEvents(vm):初始化事件,对事件的相关属性设置默认值,比如:_events、_hasHookEvent。
  6. initRender(vm):初始化渲染函数,对渲染函数的相关属性设置默认值。比如_vnode,$slots、_c(内部调用的模板编译渲染函数)、$createElement(用户调用的渲染函数)等等
  7. 触发beforeCreate钩子函数callHook(vm, 'beforeCreate')
  8. initInjections(vm):初始化inject,给inject所有属性添加监听,变成响应式。
  9. initState(vm):初始化props、methods、data、computed、watch等。给他们添加监听,变成响应式。
  10. initProvide(vm):初始化provide。
  11. 触发created钩子函数callHook(vm, 'created')。从beforeCreate到created,主要是给Vue实例中的数据添加监听,变成响应式。
  12. 挂载$el vm.$mount(vm.$options.el)

以上的初始化方法、callHook以及挂载,后续会继续详解。

3、stateMixin

给Vue实例添加 state相关的属性和方法

export function stateMixin (Vue: Class<Component>) {
  // flow somehow has problems with directly declared definition object
  // when using Object.defineProperty, so we have to procedurally build up
  // the object here.
  // 定义空的data
  const dataDef = {}
  // 指向到_data,在initState时定义的
  dataDef.get = function () { return this._data }
  // 定义空的prop
  const propsDef = {}
  // 指向到_props,在initProps时定义的
  propsDef.get = function () { return this._props }
  // set禁用提示
  if (process.env.NODE_ENV !== 'production') {
    dataDef.set = function () {
      warn(
        'Avoid replacing instance root $data. ' +
        'Use nested data properties instead.',
        this
      )
    }
    propsDef.set = function () {
      warn(`$props is readonly.`, this)
    }
  }
  // 把dataDef给$data,并添加劫持
  Object.defineProperty(Vue.prototype, '$data', dataDef)
  // 把propsDef给$props,并添加劫持
  Object.defineProperty(Vue.prototype, '$props', propsDef)

  // $set等同于全局的Vue.set
  Vue.prototype.$set = set
  // $delete等同于全局的Vue.delete
  Vue.prototype.$delete = del

  // 定义$watch方法
  Vue.prototype.$watch = function (
    expOrFn: string | Function,
    cb: any,
    options?: Object
  ): Function {
    const vm: Component = this
    // 如果cb是一个对象,则把对象使用$watch解析
    if (isPlainObject(cb)) {
      return createWatcher(vm, expOrFn, cb, options)
    }
    options = options || {}
    options.user = true
    // 创建监听器Watcher对象
    const watcher = new Watcher(vm, expOrFn, cb, options)
    // 如果指定了immediate,立即触发cb回调
    if (options.immediate) {
      const info = `callback for immediate watcher "${watcher.expression}"`
      pushTarget()
      invokeWithErrorHandling(cb, vm, [watcher.value], vm, info)
      popTarget()
    }
    return function unwatchFn () {
      // 从所有依赖项的dep列表中删除自身watcher
      watcher.teardown()
    }
  }
}

4、eventsMixin

给Vue实例添加$on、$off、$once、$emit事件相关方法

export function eventsMixin (Vue: Class<Component>) {
  const hookRE = /^hook:/
  /** 监听当前实例上的自定义事件 */
  Vue.prototype.$on = function (event: string | Array<string>, fn: Function): Component {
    const vm: Component = this
    // 数组处理,其实就是循环调用$on,添加监听
    if (Array.isArray(event)) {
      for (let i = 0, l = event.length; i < l; i++) {
        vm.$on(event[i], fn)
      }
    } else {
      // 添加到事件列表_event
      (vm._events[event] || (vm._events[event] = [])).push(fn)
      // optimize hook:event cost by using a boolean flag marked at registration
      // instead of a hash lookup
      // 优化 hook:event。监听生命周期的钩子触发
      if (hookRE.test(event)) {
        vm._hasHookEvent = true
      }
    }
    return vm
  }
  /** 监听一个自定义事件,但是只触发一次 */
  Vue.prototype.$once = function (event: string, fn: Function): Component {
    const vm: Component = this
    function on () {
      // 移除事件
      vm.$off(event, on)
      // 调用回调函数
      fn.apply(vm, arguments)
    }
    on.fn = fn
    // 添加监听
    vm.$on(event, on)
    return vm
  }
  /** 移除自定义事件监听器 */
  Vue.prototype.$off = function (event?: string | Array<string>, fn?: Function): Component {
    const vm: Component = this
    // all
    // 如果没有传递参数,则移除所有的事件监听器
    if (!arguments.length) {
      vm._events = Object.create(null)
      return vm
    }
    // array of events
    // 数组处理,其实就是循环调用$off,移除监听
    if (Array.isArray(event)) {
      for (let i = 0, l = event.length; i < l; i++) {
        vm.$off(event[i], fn)
      }
      return vm
    }
    // specific event
    const cbs = vm._events[event]
    // 如果事件不存在,直接返回
    if (!cbs) {
      return vm
    }
    // 如果没有提供回调函数,则移除该事件所有的监听器
    if (!fn) {
      vm._events[event] = null
      return vm
    }
    // specific handler
    let cb
    let i = cbs.length
    while (i--) {
      cb = cbs[i]
      // 只移除该事件的该回调函数的监听器
      if (cb === fn || cb.fn === fn) {
        cbs.splice(i, 1)
        break
      }
    }
    return vm
  }
  /** 触发当前实例上的事件 */
  Vue.prototype.$emit = function (event: string): Component {
    const vm: Component = this
    if (process.env.NODE_ENV !== 'production') {
      // 事件名称应该小写字母+连字符
      const lowerCaseEvent = event.toLowerCase()
      if (lowerCaseEvent !== event && vm._events[lowerCaseEvent]) {
        tip(
          `Event "${lowerCaseEvent}" is emitted in component ` +
          `${formatComponentName(vm)} but the handler is registered for "${event}". ` +
          `Note that HTML attributes are case-insensitive and you cannot use ` +
          `v-on to listen to camelCase events when using in-DOM templates. ` +
          `You should probably use "${hyphenate(event)}" instead of "${event}".`
        )
      }
    }
    let cbs = vm._events[event]
    // 判断是否存在该事件的监听器
    if (cbs) {
      // 用数组装回调函数
      cbs = cbs.length > 1 ? toArray(cbs) : cbs
      // 把参数转成数组,并移除事件名称参数
      const args = toArray(arguments, 1)
      const info = `event handler for "${event}"`
      // 遍历调用该事件下的所有回调函数
      for (let i = 0, l = cbs.length; i < l; i++) {
        invokeWithErrorHandling(cbs[i], vm, args, vm, info)
      }
    }
    return vm
  }
}

5、lifecycleMixin

给Vue实例添加生命周期的主动触发方法

export function lifecycleMixin (Vue: Class<Component>) {
  Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
    const vm: Component = this
    const prevEl = vm.$el
    const prevVnode = vm._vnode
    const restoreActiveInstance = setActiveInstance(vm)
    vm._vnode = vnode
    // Vue.prototype.__patch__ is injected in entry points
    // based on the rendering backend used.
    if (!prevVnode) {
      // initial render
      // 第一次渲染
      vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)
    } else {
      // updates
      // 更新
      vm.$el = vm.__patch__(prevVnode, vnode)
    }
    restoreActiveInstance()
    // update __vue__ reference
    if (prevEl) {
      prevEl.__vue__ = null
    }
    if (vm.$el) {
      vm.$el.__vue__ = vm
    }
    // if parent is an HOC, update its $el as well
    if (vm.$vnode && vm.$parent && vm.$vnode === vm.$parent._vnode) {
      vm.$parent.$el = vm.$el
    }
    // updated hook is called by the scheduler to ensure that children are
    // updated in a parent's updated hook.
  }

  Vue.prototype.$forceUpdate = function () {
    const vm: Component = this
    // 调用_watcher的update函数,通知页面更新
    if (vm._watcher) {
      vm._watcher.update()
    }
  }

  Vue.prototype.$destroy = function () {
    const vm: Component = this
    // 正在销毁中
    if (vm._isBeingDestroyed) {
      return
    }
    // 触发beforeDestroy钩子
    callHook(vm, 'beforeDestroy')
    // 标记销毁中
    vm._isBeingDestroyed = true
    // remove self from parent
    // 从parent中移除自己
    const parent = vm.$parent
    if (parent && !parent._isBeingDestroyed && !vm.$options.abstract) {
      remove(parent.$children, vm)
    }
    // teardown watchers
    // 卸载watcher
    if (vm._watcher) {
      vm._watcher.teardown()
    }
    // 卸载所有的watcher
    let i = vm._watchers.length
    while (i--) {
      vm._watchers[i].teardown()
    }
    // remove reference from data ob
    // frozen object may not have observer.
    if (vm._data.__ob__) {
      vm._data.__ob__.vmCount--
    }
    // call the last hook...
    // 标记已销毁
    vm._isDestroyed = true
    // invoke destroy hooks on current rendered tree
    // 从当前渲染中树销毁
    vm.__patch__(vm._vnode, null)
    // fire destroyed hook
    // 触发destroyed钩子
    callHook(vm, 'destroyed')
    // turn off all instance listeners.
    // 移除所有监听器
    vm.$off()
    // remove __vue__ reference
    // 移除—__vue__关联
    if (vm.$el) {
      vm.$el.__vue__ = null
    }
    // release circular reference (#6759)
    if (vm.$vnode) {
      vm.$vnode.parent = null
    }
  }
}

6、renderMixin

给Vue实例添加渲染函数和nextTick钩子函数

/* @flow */

import {
  warn,
  nextTick,
  emptyObject,
  handleError,
  defineReactive
} from '../util/index'

import { createElement } from '../vdom/create-element'
import { installRenderHelpers } from './render-helpers/index'
import { resolveSlots } from './render-helpers/resolve-slots'
import { normalizeScopedSlots } from '../vdom/helpers/normalize-scoped-slots'
import VNode, { createEmptyVNode } from '../vdom/vnode'

import { isUpdatingChildComponent } from './lifecycle'

/** 初始化reader函数 */
export function initRender (vm: Component) {
  vm._vnode = null // the root of the child tree
  vm._staticTrees = null // v-once cached trees
  const options = vm.$options
  const parentVnode = vm.$vnode = options._parentVnode // the placeholder node in parent tree
  const renderContext = parentVnode && parentVnode.context
  vm.$slots = resolveSlots(options._renderChildren, renderContext)
  vm.$scopedSlots = emptyObject
  // bind the createElement fn to this instance
  // so that we get proper render context inside it.
  // args order: tag, data, children, normalizationType, alwaysNormalize
  // internal version is used by render functions compiled from templates
  // 内部调用的模板编译渲染函数
  vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)
  // normalization is always applied for the public version, used in
  // user-written render functions.
  // 用户调用的渲染函数
  vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)

  // $attrs & $listeners are exposed for easier HOC creation.
  // they need to be reactive so that HOCs using them are always updated
  const parentData = parentVnode && parentVnode.data

  /* istanbul ignore else */
  if (process.env.NODE_ENV !== 'production') {
    // 只读的$attrs
    defineReactive(vm, '$attrs', parentData && parentData.attrs || emptyObject, () => {
      !isUpdatingChildComponent && warn(`$attrs is readonly.`, vm)
    }, true)
    // 只读的$listeners
    defineReactive(vm, '$listeners', options._parentListeners || emptyObject, () => {
      !isUpdatingChildComponent && warn(`$listeners is readonly.`, vm)
    }, true)
  } else {
    // 只读的$attrs
    defineReactive(vm, '$attrs', parentData && parentData.attrs || emptyObject, null, true)
    // 只读的$listeners
    defineReactive(vm, '$listeners', options._parentListeners || emptyObject, null, true)
  }
}

export let currentRenderingInstance: Component | null = null

// for testing only
export function setCurrentRenderingInstance (vm: Component) {
  currentRenderingInstance = vm
}

export function renderMixin (Vue: Class<Component>) {
  // install runtime convenience helpers
  // 安装运行时的helper
  installRenderHelpers(Vue.prototype)

  // $nextTick调用全局的nextTick函数
  Vue.prototype.$nextTick = function (fn: Function) {
    return nextTick(fn, this)
  }

  Vue.prototype._render = function (): VNode {
    const vm: Component = this
    const { render, _parentVnode } = vm.$options

    if (_parentVnode) {
      // 规范化ScopedSlots
      vm.$scopedSlots = normalizeScopedSlots(
        _parentVnode.data.scopedSlots,
        vm.$slots,
        vm.$scopedSlots
      )
    }

    // set parent vnode. this allows render functions to have access
    // to the data on the placeholder node.
    // 设置父节点vnode,允许通过$vnode访问数据
    vm.$vnode = _parentVnode
    // render self
    let vnode
    try {
      // There's no need to maintain a stack because all render fns are called
      // separately from one another. Nested component's render fns are called
      // when parent component is patched.
      // 不需要使用堆栈,因为所有render方法都是单独调用的。当父组件被patch的时候,会调用嵌套组件的render函数。
      // 当前正在渲染的实例
      currentRenderingInstance = vm
      // 调用渲染函数
      vnode = render.call(vm._renderProxy, vm.$createElement)
    } catch (e) {
      // 抛出异常
      handleError(e, vm, `render`)
      // return error render result,
      // or previous vnode to prevent render error causing blank component
      /* istanbul ignore else */
      // _vnode来自于实例挂载、或者更新的时候
      if (process.env.NODE_ENV !== 'production' && vm.$options.renderError) {
        try {
          vnode = vm.$options.renderError.call(vm._renderProxy, vm.$createElement, e)
        } catch (e) {
          handleError(e, vm, `renderError`)
          vnode = vm._vnode
        }
      } else {
        vnode = vm._vnode
      }
    } finally {
      // 渲染结束,当前渲染实例置空
      currentRenderingInstance = null
    }
    // if the returned array contains only a single node, allow it
    // 单个元素
    if (Array.isArray(vnode) && vnode.length === 1) {
      vnode = vnode[0]
    }
    // return empty vnode in case the render function errored out
    // 非VNode
    if (!(vnode instanceof VNode)) {
      if (process.env.NODE_ENV !== 'production' && Array.isArray(vnode)) {
        warn(
          'Multiple root nodes returned from render function. Render function ' +
          'should return a single root node.',
          vm
        )
      }
      // 创建空的VNode
      vnode = createEmptyVNode()
    }
    // set parent
    // 设置parentVNode
    vnode.parent = _parentVnode
    return vnode
  }
}
  JavaScript知识库 最新文章
ES6的相关知识点
react 函数式组件 & react其他一些总结
Vue基础超详细
前端JS也可以连点成线(Vue中运用 AntVG6)
Vue事件处理的基本使用
Vue后台项目的记录 (一)
前后端分离vue跨域,devServer配置proxy代理
TypeScript
初识vuex
vue项目安装包指令收集
上一篇文章      下一篇文章      查看所有文章
加:2021-08-19 11:57:40  更:2021-08-19 12:00:48 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 9:45:52-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码