vm._render是怎么实现的

上述updateComponent方法调用是运行了一个函数:

  1. // src\core\instance\lifecycle.js
  2. updateComponent = () => {
  3. vm._update(vm._render(), hydrating)
  4. }

其中会先运行vm._render函数,那么vm._render函数又是从哪里定义的呢?我们回到src\core\instance\index.js,这里一开始会运行renderMixin方法,而renderMixin函数具体定义在src\core\instance\render.js中,去到函数中可以清晰看到,我们心心念念的vm._render就定义在此处了。

  1. // src\core\instance\render.js
  2. export function renderMixin (Vue: Class<Component>) {
  3. ...
  4. Vue.prototype._render = function (): VNode {
  5. const vm: Component = this
  6. const { render, _parentVnode } = vm.$options
  7. ...
  8. let vnode
  9. try {
  10. // 暂时只关注这部分代码
  11. vnode = render.call(vm._renderProxy, vm.$createElement)
  12. } catch (e) {
  13. ...
  14. }
  15. ...
  16. return vnode
  17. }
  18. }

render函数中其他的先不做详细解读,先把目光聚焦在render.call(vm._renderProxy, vm.$createElement)。

render函数可以在配置中自己写,也可以是生成的,在上节Vue实例挂载中有具体分析过$mount是如何生成render函数的大概流程,忘了的可以回顾一下,往下会以自写的render去跑一遍代码流程。

剖析点①:

vm._renderProxy:在_init函数中定义。

  1. Vue.prototype._init = function (options?: Object) {
  2. ...
  3. if (process.env.NODE_ENV !== 'production') { //非生产环境
  4. initProxy(vm)
  5. } else {
  6. vm._renderProxy = vm
  7. }
  8. ...
  9. }

代码中可知生产环境其实就是vm或者说this本身,而非生产环境时则是在initProxy函数中定义vm._renderProxy。我们往下看看定义在src\core\instance\proxy.js里的initProxy方法。

  1. // src\core\instance\proxy.js
  2. let initProxy
  3. ...
  4. const hasProxy = typeof Proxy !== 'undefined' && isNative(Proxy)
  5. // 浏览器是否支持proxy
  6. if (hasProxy) {
  7. const isBuiltInModifier = makeMap('stop,prevent,self,ctrl,shift,alt,meta,exact')
  8. config.keyCodes = new Proxy(config.keyCodes, {
  9. set (target, key, value) {
  10. if (isBuiltInModifier(key)) {
  11. warn(`Avoid overwriting built-in modifier in config.keyCodes: .${key}`)
  12. return false
  13. } else {
  14. target[key] = value
  15. return true
  16. }
  17. }
  18. })
  19. }
  20. const hasHandler = {
  21. has (target, key) {
  22. const has = key in target
  23. const isAllowed = allowedGlobals(key) ||
  24. (typeof key === 'string' && key.charAt(0) === '_' && !(key in target.$data))
  25. if (!has && !isAllowed) {
  26. if (key in target.$data) warnReservedPrefix(target, key)
  27. else warnNonPresent(target, key)
  28. }
  29. return has || !isAllowed
  30. }
  31. }
  32. const getHandler = {
  33. get (target, key) {
  34. if (typeof key === 'string' && !(key in target)) {
  35. if (key in target.$data) warnReservedPrefix(target, key)
  36. else warnNonPresent(target, key)
  37. }
  38. return target[key]
  39. }
  40. }
  41. initProxy = function initProxy (vm) {
  42. if (hasProxy) {
  43. // determine which proxy handler to use
  44. const options = vm.$options
  45. const handlers = options.render && options.render._withStripped
  46. ? getHandler
  47. : hasHandler
  48. vm._renderProxy = new Proxy(vm, handlers)
  49. } else {
  50. vm._renderProxy = vm
  51. }
  52. }
  53. export { initProxy }

initProxy方法首先用hasProxy判断浏览器是否支持proxy,不支持情况下vm._renderProxy就是vm,支持的情况则是用Proxy对vm做一个数据劫持。

vm._renderProxy就告一段落了,下面看vm.$createElement

vm.$createElement:把render函数转换成Vnode,它的定义需要回到我们开始说的vue._init方法,其中运行了initRender方法,而vm.$createElement就定义在initRender函数中,下面我们看看initRender函数。

  1. // src\core\instance\render.js
  2. export function initRender (vm: Component) {
  3. ...
  4. // bind the createElement fn to this instance
  5. // (将createElement fn绑定到此实例)
  6. // so that we get proper render context inside it.
  7. // (以便在其中获得适当的渲染上下文)
  8. // args order: tag, data, children, normalizationType, alwaysNormalize(参数标注)
  9. // internal version is used by render functions compiled from templates
  10. vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)
  11. // normalization is always applied for the public version, used in
  12. // user-written render functions.
  13. vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)
  14. ...
  15. }

其中vm._c是生产render所调用的,而vm.$createElement是自写render调用的,下面我们自写个render来做调试实验。

  1. new Vue({
  2. el:'#app',
  3. render(createElement){
  4. return createElement('div',{
  5. attrs:{
  6. id:"app1"
  7. }
  8. },this.msg)
  9. },
  10. data(){
  11. return{
  12. msg:"niccc"
  13. }
  14. }
  15. })

还记得前面的Vue.prototype._render方法不,里面有段代码render.call(vm._renderProxy, vm.$createElement),上面代码块中的render函数中的createElement参数,其实就是vm.$createElement方法。

注:官方render函数文档 https://cn.vuejs.org/v2/api/#render

createElement函数的实现

不知道各位是否还记得之前分析的render函数,其中的vm._c和vm.$createElement都是调用了createElement函数。下面我们来看看该函数

  1. //src\core\vdom\create-element.js
  2. const SIMPLE_NORMALIZE = 1
  3. const ALWAYS_NORMALIZE = 2
  4. export function createElement (
  5. context: Component,
  6. tag: any,
  7. data: any,
  8. children: any,
  9. normalizationType: any,
  10. alwaysNormalize: boolean
  11. ): VNode | Array<VNode> {
  12. //判断data参数是否为空,空时自动补全
  13. if (Array.isArray(data) || isPrimitive(data)) {
  14. normalizationType = children
  15. children = data
  16. data = undefined
  17. }
  18. if (isTrue(alwaysNormalize)) {
  19. normalizationType = ALWAYS_NORMALIZE
  20. }
  21. return _createElement(context, tag, data, children, normalizationType)
  22. }

createElement 方法实际上是对 _createElement 方法的封装,它允许传入的参数更加灵活,在处理这些参数后,调用真正创建 VNode 的函数 _createElement,由于函数体较大,我们进行分段解读。

  1. //src\core\vdom\create-element.js
  2. // part 1
  3. export function _createElement (
  4. context: Component, //上下文环境,一般就是vm
  5. tag?: string | Class<Component> | Function | Object, //标签(element)
  6. data?: VNodeData, //VNode数据,VnodeData类型,详见flow\vnode.js
  7. children?: any, //Vnode子节点
  8. normalizationType?: number //子节点规范类型
  9. ): VNode | Array<VNode> {
  10. if (isDef(data) && isDef((data: any).__ob__)) {
  11. process.env.NODE_ENV !== 'production' && warn(
  12. `Avoid using observed data object as vnode data: ${JSON.stringify(data)}\n` +
  13. 'Always create fresh vnode data objects in each render!',
  14. context
  15. )
  16. return createEmptyVNode()
  17. }
  18. // object syntax in v-bind
  19. if (isDef(data) && isDef(data.is)) {
  20. tag = data.is
  21. }
  22. if (!tag) {
  23. // in case of component :is set to falsy value
  24. return createEmptyVNode()
  25. }
  26. // warn against non-primitive key
  27. if (process.env.NODE_ENV !== 'production' &&
  28. isDef(data) && isDef(data.key) && !isPrimitive(data.key)
  29. ) {
  30. if (!__WEEX__ || !('@binding' in data.key)) {
  31. warn(
  32. 'Avoid using non-primitive value as key, ' +
  33. 'use string/number value instead.',
  34. context
  35. )
  36. }
  37. }
  38. ...
  39. }

我们先分析一下入参:

  context:上下文环境,一般就是vm;

  tag:标签;

  data:VNode数据,VnodeData类型,详见flow\vnode.js;

  children:Vnode子节点;

  normalizationType:子节点规范类型;

往下看其实就是对data数据的判断,看是否需要跑createEmptyVNode函数,即创建注释函数。下面我们继续看part 2。

  1. //src\core\vdom\create-element.js
  2. // part 2
  3. export function _createElement (
  4. context: Component, //上下文环境,一般就是vm
  5. tag?: string | Class<Component> | Function | Object, //标签(element)
  6. data?: VNodeData, //VNode数据,VnodeData类型,详见flow\vnode.js
  7. children?: any, //Vnode子节点
  8. normalizationType?: number //子节点规范类型
  9. ): VNode | Array<VNode> {
  10. ...
  11. if (normalizationType === ALWAYS_NORMALIZE) {
  12. children = normalizeChildren(children)
  13. } else if (normalizationType === SIMPLE_NORMALIZE) {
  14. children = simpleNormalizeChildren(children)
  15. }
  16. ...
  17. }

children最终形态是Vnode的节点,但是入参中的children却是any类型,所以我们需要对它进行转换,normalizeChildren和simpleNormalizeChildren就是做了这项任务。下面我们看看这两个函数

  1. // src\core\vdom\helpers\normalize-children.js
  2. // 其中simpleNormalizeChildren是拍扁数组,成为一维素组
  3. export function simpleNormalizeChildren (children: any) {
  4. for (let i = 0; i < children.length; i++) {
  5. if (Array.isArray(children[i])) {
  6. return Array.prototype.concat.apply([], children)
  7. }
  8. }
  9. return children
  10. }

simpleNormalizeChildren方法其实就是为了拍扁为一维数组,具体是什么场景下进行的,后续再研究过来填坑。

  1. // src\core\vdom\helpers\normalize-children.js
  2. export function normalizeChildren (children: any): ?Array<VNode> {
  3. return isPrimitive(children) //判断是否为基础类型
  4. ? [createTextVNode(children)] //是,创建一个vnode节点
  5. : Array.isArray(children) //否,判断是否为数组
  6. ? normalizeArrayChildren(children) //详见下文
  7. : undefined
  8. }

normalizeChildren其实最终也是返回一个一维数组,它分了三个情况:

①:isPrimitive判断是基础类型,返回一个用一维数组包裹着的文本Vnode;

②:Array.isArray判断是数组类型,调用normalizeArrayChildren函数;

③:啥都都不是,返回undefined;

然后我们看看第②个情况中,normalizeArrayChildren做了什么。

  1. // src\core\vdom\helpers\normalize-children.js
  2. function normalizeArrayChildren (children: any, nestedIndex?: string): Array<VNode> {
  3. const res = []
  4. let i, c, lastIndex, last
  5. for (i = 0; i < children.length; i++) {
  6. c = children[i]
  7. if (isUndef(c) || typeof c === 'boolean') continue
  8. lastIndex = res.length - 1
  9. last = res[lastIndex]
  10. // 注①,如果是数组类型,则继续调用normalizeArrayChildren递归,递归后于父数组apply为一个一维数组。
  11. if (Array.isArray(c)) {
  12. if (c.length > 0) {
  13. // 递归chilrend
  14. c = normalizeArrayChildren(c, `${nestedIndex || ''}_${i}`)
  15. // merge adjacent text nodes(合并相邻的文本节点)
  16. if (isTextNode(c[0]) && isTextNode(last)) {
  17. res[lastIndex] = createTextVNode(last.text + (c[0]: any).text)
  18. c.shift()
  19. }
  20. res.push.apply(res, c)
  21. }
  22. // 注②,如果是基础类型,则创建文本Vnode然后push到res数组中
  23. } else if (isPrimitive(c)) {
  24. if (isTextNode(last)) {
  25. // merge adjacent text nodes
  26. // this is necessary for SSR hydration because text nodes are
  27. // essentially merged when rendered to HTML strings
  28. res[lastIndex] = createTextVNode(last.text + c)
  29. } else if (c !== '') {
  30. // convert primitive to vnode
  31. res.push(createTextVNode(c))
  32. }
  33. // 注③,再否则其实就是一个正常的Vnode,然后回对一些v-for做一些处理,然后也是push到res数组中
  34. } else {
  35. if (isTextNode(c) && isTextNode(last)) {
  36. // merge adjacent text nodes
  37. res[lastIndex] = createTextVNode(last.text + c.text)
  38. } else {
  39. // default key for nested array children (likely generated by v-for)
  40. if (isTrue(children._isVList) &&
  41. isDef(c.tag) &&
  42. isUndef(c.key) &&
  43. isDef(nestedIndex)) {
  44. c.key = `__vlist${nestedIndex}_${i}__`
  45. }
  46. res.push(c)
  47. }
  48. }
  49. }
  50. return res
  51. }

normalizeArrayChildren做的其实就是利用递归把多维数组apply合并成一维数组。

注①:如果是数组类型,则继续调用normalizeArrayChildren递归,递归后于父数组apply为一个一维数组。

注②:如果是基础类型,则创建文本Vnode然后push到res数组中;

注③:再否则其实就是一个正常的Vnode,然后回对一些v-for做一些处理,然后也是push到res数组中;

  1. //src\core\vdom\create-element.js
  2. // part 3
  3. export function _createElement (
  4. context: Component, //上下文环境,一般就是vm
  5. tag?: string | Class<Component> | Function | Object, //标签(element)
  6. data?: VNodeData, //VNode数据,VnodeData类型,详见flow\vnode.js
  7. children?: any, //Vnode子节点
  8. normalizationType?: number //子节点规范类型
  9. ): VNode | Array<VNode> {
  10. ...
  11. let vnode, ns
  12. //这个tag就是'string'
  13. if (typeof tag === 'string') {
  14. let Ctor
  15. ns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag)
  16. //是否HTML原生标签
  17. if (config.isReservedTag(tag)) {
  18. // platform built-in elements
  19. vnode = new VNode(
  20. //config.parsePlatformTagName方法其实就是传什么返回什么
  21. config.parsePlatformTagName(tag), data, children,
  22. undefined, undefined, context
  23. )
  24. } else if ((!data || !data.pre) && isDef(Ctor = resolveAsset(context.$options, 'components', tag))) {
  25. // component
  26. vnode = createComponent(Ctor, data, context, children, tag)
  27. } else {
  28. // unknown or unlisted namespaced elements
  29. // check at runtime because it may get assigned a namespace when its
  30. // parent normalizes children
  31. vnode = new VNode(
  32. tag, data, children,
  33. undefined, undefined, context
  34. )
  35. }
  36. } else {
  37. //组件形式后面再说
  38. // direct component options / constructor
  39. vnode = createComponent(tag, data, context, children)
  40. }
  41. if (Array.isArray(vnode)) {
  42. return vnode
  43. } else if (isDef(vnode)) {
  44. if (isDef(ns)) applyNS(vnode, ns)
  45. if (isDef(data)) registerDeepBindings(data)
  46. return vnode
  47. } else {
  48. return createEmptyVNode()
  49. }
  50. }

首先会判断tag的类型,tag可以是字符串,也可以是组件,组件我们往后再细说,这里先关注String类型。config.isReservedTag判断是否HTML原生标签,(该方法定义在src\platforms\web\util\element.js),config.parsePlatformTagName方法其实就是传什么返回什么,然后new Vnode创建一个Vnode实例。

这个最终生成好的vnode,会经过几个方法return到_render函数。

①:return到createElement函数;

②:return到vm.$createElement;

③:return到vue._render中的vnode = render.call(vm._renderProxy, vm.$createElement)。

自此_render函数就告一段落了,我们接下来开始分析vm._update(vm._render(), hydrating)的vm._update函数。

_update函数的实现

_update是实例的一个私有方法,主要是吧Vnode渲染成真实的dom。调用的情况有两种,一是首次渲染,二是数据更新的时候。_update方法定义在src\core\instance\lifecycle.js,接下来我们看看具体的方法。

  1. // src\core\instance\lifecycle.js
  2. export function lifecycleMixin (Vue: Class<Component>) {
  3. Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
  4. ...
  5. // 主要方法patch
  6. vm.$el = vm.__patch__(prevVnode, vnode)
  7. ...
  8. }
  9. }

其中核心其实就是vm.__patch__函数,其定义在 src\platforms\web\runtime\index.js

  1. // src\platforms\web\runtime\index.js
  2. import { patch } from './patch'
  3. // 判断是否浏览器环境
  4. Vue.prototype.__patch__ = inBrowser ? patch : noop

__patch__有个三元表达式,inBrowser,用于判断是否为浏览器环境,是则为patch方法,否则就是noop空函数(服务器渲染时用到的)。下面我们看看patch方法,patch方法定义在src\platforms\web\runtime\patch.js

  1. // src\platforms\web\runtime\patch.js
  2. import * as nodeOps from 'web/runtime/node-ops'
  3. import { createPatchFunction } from 'core/vdom/patch'
  4. import baseModules from 'core/vdom/modules/index'
  5. import platformModules from 'web/runtime/modules/index'
  6. // 合并两个集合
  7. const modules = platformModules.concat(baseModules)
  8. export const patch: Function = createPatchFunction({ nodeOps, modules })
  9. // createPatchFunction方法创建了许多辅助函数,最后返回patch函数(函数柯里化)

可以看出,patch方法其实就是createPatchFunction方法调用后的return值,createPatchFunction入参分别是nodeOps和modules。

nodeOps其实就是封装一些dom原生操作的方法,有兴趣的可以到src\platforms\web\runtime\node-ops.js看看;

modules是baseModules和platformModules的合集,主要是一些模块的钩子函数,主要用于生成dom,这里暂时不多赘述。

我们把关注点放到createPatchFunction函数上,由于createPatchFunction函数比较复杂,下面会分段分析。

  1. // src\core\vdom\patch.js
  2. const hooks = ['create', 'activate', 'update', 'remove', 'destroy']
  3. export function createPatchFunction (backend) {
  4. ...
  5. return function patch (oldVnode, vnode, hydrating, removeOnly) {
  6. ...
  7. }
  8. }

createPatchFunction很复杂,看源码是可以看出它其中定义了很多的辅助函数,然后最终返回了一个patch函数,这个也就是patch的最终方法,为什么要这么处理呢?其实这里是运用了函数柯里化的技巧,主要目的是让其有更高的灵活度和可复用性。接下来我们来详细看patch方法。

  1. // src\core\vdom\patch.js
  2. return function patch (oldVnode, vnode, hydrating, removeOnly) {
  3. ...
  4. // oldVnode有值所以进入 else 阶段
  5. if (isUndef(oldVnode)) {
  6. ...
  7. } else {
  8. const isRealElement = isDef(oldVnode.nodeType)
  9. // isRealElement为true,进入else阶段
  10. if (!isRealElement && sameVnode(oldVnode, vnode)) {
  11. ...
  12. } else {
  13. // isRealElement为true
  14. if (isRealElement) {
  15. ...
  16. // either not server-rendered, or hydration failed.
  17. // create an empty node and replace it
  18. // emptyNodeAt函数作用是把真实dom转换成虚拟dom
  19. oldVnode = emptyNodeAt(oldVnode)
  20. }
  21. // replacing existing element
  22. const oldElm = oldVnode.elm //提取真实dom对象(div#app)
  23. const parentElm = nodeOps.parentNode(oldElm) //真实dom的父级(body)
  24. // create new node
  25. // 下面会单独分析
  26. createElm(
  27. vnode,
  28. insertedVnodeQueue,
  29. // extremely rare edge case: do not insert if old element is in a
  30. // leaving transition. Only happens when combining transition +
  31. // keep-alive + HOCs. (#4590)
  32. oldElm._leaveCb ? null : parent Elm,
  33. nodeOps.nextSibling(oldElm)
  34. )
  35. ...
  36. }
  37. }
  38. //插入钩子函数,后续再细看
  39. invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch)
  40. return vnode.elm
  41. }

因为patch的高复用性,因此patch函数有多个条件判断语句,建议调试看着代码去理解patch。

现在我们以vue-cli默认模板来去分析,其中省略了部分没运行到的代码,上述代码注释中有注释解析,这边就不多赘述了,就简单说一下入参。oldVnode其实就是真实dom对象;vnode是虚拟dom;hydrating和removeOnly其实就是false。

一套流程下来去到了createElm函数,它是定义在createPatchFunction中的,下面我们来具体分析一下这个函数。

  1. // src\core\vdom\patch.js
  2. function createElm (
  3. vnode,
  4. insertedVnodeQueue,
  5. parentElm,
  6. refElm,
  7. nested,
  8. ownerArray,
  9. index
  10. ) {
  11. ...
  12. // 标注①
  13. if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {
  14. return
  15. }
  16. const data = vnode.data
  17. const children = vnode.children
  18. const tag = vnode.tag
  19. if (isDef(tag)) {
  20. ...
  21. // 标注②
  22. vnode.elm = vnode.ns
  23. ? nodeOps.createElementNS(vnode.ns, tag)
  24. : nodeOps.createElement(tag, vnode)
  25. setScope(vnode)
  26. /* istanbul ignore if */
  27. if (__WEEX__) {
  28. ...
  29. } else {
  30. // 标注③
  31. createChildren(vnode, children, insertedVnodeQueue)
  32. if (isDef(data)) {
  33. // 创建一些钩子,后续再研究
  34. invokeCreateHooks(vnode, insertedVnodeQueue)
  35. }
  36. // 标注④
  37. insert(parentElm, vnode.elm, refElm)
  38. }
  39. if (process.env.NODE_ENV !== 'production' && data && data.pre) {
  40. creatingElmInVPre--
  41. }
  42. } else if (isTrue(vnode.isComment)) {
  43. vnode.elm = nodeOps.createComment(vnode.text)
  44. insert(parentElm, vnode.elm, refElm)
  45. } else {
  46. vnode.elm = nodeOps.createTextNode(vnode.text)
  47. insert(parentElm, vnode.elm, refElm)
  48. }
  49. }

createElm的作用其实就是通过vnode创建真实dom并插入到父节点中。

下面我们来分析上述代码块中标注的代码:

①:这里会尝试创建一个组件,当然我们现在创建的是页面,返回的是undefined,后续讲解组件化的时候会再详细聊createComponent函数;

②:vnode.ns为undefined,因此运行的是nodeOps.createElement(tag, vnode),createElement其实就是就是调用了原生的document.createElement方法去创建一个dom,源码在src\platforms\web\runtime\node-ops.js中;

③:children是否有子节点,有的话创建子节点;createChildren函数其实其实就是递归createElm方法,把子节点插入进来;

④:insert其作用就是根据判断插入dom(insertBefore/appendChild),用到的地方很多,是插入真实dom的主要方法;

总结

自此,数据和模板是如何渲染到dom的过程我们已经分析完毕,当然中间还有很多细节的东西我们没有去探讨,我们首先把思维流程架构起来,然后再慢慢发散分支,这样会更有助我们去把源码读懂,下面再附上一张数据驱动流程图。

Vue2.0源码学习(2) - 数据和模板的渲染(下)的更多相关文章

  1. Vue2.0源码学习(1) - 数据和模板的渲染(上)

    准备 一.首先去GitHub上把vue源码download下来,传送门:https://github.com/vuejs/vue 二.搭建一个vue-cli跑起来,用于代码调试,不看着代码动起来只看源 ...

  2. Vue2.0源码学习(4) - 合并配置

    合并配置 通过之前的源码学习,我们已经了解到了new Vue主要有两种场景,第一种就是在外部主动调用new Vue创建一个实例,第二个就是代码内部创建子组件的时候自行创建一个new Vue实例.但是无 ...

  3. Vue2.0源码学习(3) - 组件的创建和patch过程

    组件化 组件化是vue的另一个核心思想,所谓的组件化就,就是说把页面拆分成多个组件(component),每个组件依赖的css.js.图片等资源放在一起开发和维护.组件是资源独立的,在内部系统中是可以 ...

  4. Vue2.0源码学习(6) - 组件注册

    组件注册 前言 在 Vue.js 中,除了它内置的组件如 keep-alive.component.transition.transition-group 等,其它用户自定义组件在使用前必须注册.在开 ...

  5. Spring5.0源码学习系列之事务管理概述

    Spring5.0源码学习系列之事务管理概述(十一),在学习事务管理的源码之前,需要对事务的基本理论比较熟悉,所以本章节会对事务管理的基本理论进行描述 1.什么是事务? 事务就是一组原子性的SQL操作 ...

  6. 【Spark2.0源码学习】-1.概述

          Spark作为当前主流的分布式计算框架,其高效性.通用性.易用性使其得到广泛的关注,本系列博客不会介绍其原理.安装与使用相关知识,将会从源码角度进行深度分析,理解其背后的设计精髓,以便后续 ...

  7. [Android FrameWork 6.0源码学习] View的重绘过程之WindowManager的addView方法

    博客首页:http://www.cnblogs.com/kezhuang/p/关于Activity的contentView的构建过程,我在我的博客中已经分析过了,不了解的可以去看一下<[Andr ...

  8. spark2.0源码学习

    [Spark2.0源码学习]-1.概述 [Spark2.0源码学习]-2.一切从脚本说起 [Spark2.0源码学习]-3.Endpoint模型介绍 [Spark2.0源码学习]-4.Master启动 ...

  9. Spring5.0源码学习系列之浅谈BeanFactory创建

    Spring5.0源码学习系列之浅谈BeanFactory创建过程 系列文章目录 提示:Spring源码学习专栏链接 @ 目录 系列文章目录 博客前言介绍 一.获取BeanFactory主流程 二.r ...

随机推荐

  1. scrollTop、scrollHeight与clientHeight

    MDN上概念 scrollTop:获取或设置一个元素的内容垂直滚动的像素数. scrollHeight:一个元素内容高度的度量,包括由于溢出导致的视图中不可见内容. clientHeight:元素内部 ...

  2. 使用 Jenkins + Ansible 实现 Spring Boot 自动化部署101

    本文要点:设计一条 Spring Boot 最基本的流水线:包括构建.制品上传.部署.使用 Docker 容器运行构建逻辑.自动化整个实验环境:包括 Jenkins 的配置,Jenkins agent ...

  3. Jekyll + NexT + GitHub Pages 主题深度优化

    前言 笔者在用 Jekyll 搭建个人博客时踩了很多的坑,最后发现了一款不错的主题 jekyll-theme-next,但网上关于 Jekyll 版的 Next 主题优化教程少之又少,于是就决定自己写 ...

  4. R语言服务器程序 Rserve详解

    R语言服务器程序 Rserve详解 R的极客理想系列文章,涵盖了R的思想,使用,工具,创新等的一系列要点,以我个人的学习和体验去诠释R的强大. R语言作为统计学一门语言,一直在小众领域闪耀着光芒.直到 ...

  5. JavaScript DOM 基础操作

    JavaScript DOM 基础操作 一.获取元素的六方式 document.getElementById('id名称') //根据id名称获取 document.getElementsByclas ...

  6. NPOI处理Excel

    using NPOI; using NPOI.XSSF.UserModel; using NPOI.SS.UserModel; using NPOI.HSSF.UserModel; NPOI.SS.U ...

  7. 🏆【Alibaba中间件技术系列】「RocketMQ技术专题」Broker服务端自动创建topic的原理分析和问题要点指南

    前提背景 使用RocketMQ进行发消息时,一般我们是必须要指定topic,此外topic必须要提前建立,但是topic的创建(自动或者手动方式)的设置有一个开关autoCreateTopicEnab ...

  8. macos下命令行通过ndk编译android下可以执行的ELF程序(并验证opencl的调用)

    源码如下,实现把一个JPG保存成灰度图格式的BMP 1 //jpg2bmp.cpp 2 #include <stdio.h> 3 #include <inttypes.h> 4 ...

  9. Docker环境安装,基本命令集合

    一.docker安装 1).卸载旧的安装包 centos7默认安装的docker版本是1.13.1,卸载它,安装新的版本. root用户下,一次把这坨命令复制进去 yum remove docker ...

  10. 浅谈kali : aircrack-ng套件

    aircrack-ng Aircrack-ng是一个与802.11标准的无线网络分析有关的安全软件,主要功能有:网络侦测,数据包嗅探,WEP和WPA/WPA2-PSK破解.Aircrack-ng可以工 ...