简要分析git作用及应公司业务要求分析,什么是响应式和虚拟dom面试题

git

Git是什么

  • Git版本控制系统一个分布式的系统,用来保存工程源代码历史状态(游戏存档)的命令行工具

  • Git是一个命令行(小黑窗)工具,用于版本控制(存档器)

Git的作用是什么?

    • 版本管理工具:说人话就是可以记录你敲代码的每一个环节(类似于玩游戏存档)

Git应用场景介绍

  1. 多人开发代码管理

目前我们多人代码的时候,想把代码合并一起是利用最原始的复制粘贴操作。有了git之后,可以一键搞

  1. 异地开发代码管理

实际开发中,我们上班可能会用公司电脑敲代码。有时候回到家里用自己电脑偷偷加个班,以前的做法是备一个U盘,复制粘贴。有了git之后,直接一键搞定。

  1. 版本管理 比如我现在公司网站已经做出来了1.0版本在使用,现在计划增加一些新功能,但是这个功能不稳定需要经过开发和测试环节,为了不影响现有的稳定版本。以前的做法是把稳定版本单独拷贝一份增加功能,等做好之后再把以前版本给替换掉。有了git之后可以一键搞定。

  2. 版本回滚: 比如这周产品经理提出一个功能,我辛辛苦苦写了一周代码。 到了下周,产品经理说这个功能不做了,叫我删掉改成其他功能。 我们把代码删掉之后按照产品经理要求又辛辛苦苦写了一周代码。到了下下周,产品经理说这个功能还是没有上次那个好,还是改成上周的吧…………

    别着急,有了git之后,一键搞定。

版本管理工具还有其他的么?

  • git:目前使用最多的版本管理工具

  • svn:也有公司在用,用法与git类似(相当于腾讯视频和优酷视频)

使用git的基本工作过程

9个常见操作

程序员比较高频的操作有9个。具体如下。

  • 1.新建项目文件夹(只做一次)

  • 2.进入文件夹 (重要)

  • 3.初始化仓库:git init(只做一次)

  • 4.编码

  • 5.添加文件信息: git add .

  • 6.确认添加信息:git commit -m"描述信息"

  • 7.查看详细日志信息:git log

  • 8.查看简略日志信息:git log --oneline

  • 9.版本回滚:git reset --hard 版本号

第123步一个项目只需要一次,456步频繁使用,789步偶尔会用

Git命名作用详细描述
git init初始化git仓库(类似于玩游戏新建一个存档文件)在当前文件夹中新建一个 .git隐藏文件夹
git add .添加文件信息(相当于游戏开始存档)把要提交的文件的信息添加到暂存区中(常用
git commit -m"描述信息"确认添加信息 (确认存档)将暂存区中的文件提交到本地仓库中(常用
git config --global user.email "you@example.com"设置邮箱第一次使用git会让你输入邮箱
git config --global user.name "Your Name"设置用户名第一次使用git会让你输入用户名
git reset --hard 版本号版本回滚(游戏回档)可以让你的文件回退到历史某一个版本
git log查看详细日志(存档日志)会显示你的每一次存档信息
git log --oneline查看简略版日志快速查看版本号
git reflog查看所有日志(包含回滚日志)回档回错了会用到
git clone '远程仓库地址'克隆远程仓库把远程仓库所有文件下载到本地
git push推送代码到远程仓库把当前电脑已经commit过的代码上传到远程仓库
git push origin 分支名推送指定分支一般不用记,git push如果不行会提示这个命令
git pull拉取远程仓库到本地把远程仓库上别人push的代码同步到自己的本地电脑(只有多人开发才生效)
git pull origin 分支名拉取远程仓库指定分支到本地
git branch查看当前工作分支
git branch 分支名新建子分支新建一个小号存档,把当前存档备份到小号中
git checkout 分支名切换工作分支切换当前的账号(换小号刷装备)
git merge分支名合并分支把子分支代码合并到主分支(小号刷得装备移到大号上面)
git branch -d 分支名删除子分支(慎用)把某一个分支删除

  • 响应式原理

    #简版

    • vue2 响应式原理

    Vue.js 是采用数据劫持结合发布者-订阅者模式的方式,通过 Object.defineProperty()来劫持各个属性的 setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。主要分为以下几个步骤:

    1. 需要 observe 的数据对象进行递归遍历,包括子属性对象的属性,都加上 setter 和 getter 这样的话,给这个对象的某个值赋值,就会触发 setter,那么就能监听到了数据变化

    2. compile 解析模板指令,将模板中的变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,更新视图

    3. Watcher 订阅者是 Observer 和 Compile 之间通信的桥梁,主要做的事情是: ① 在自身实例化时往属性订阅器(dep)里面添加自己 ② 自身必须有一个 update()方法 ③ 待属性变动 dep.notice()通知时,能调用自身的 update()方法,并触发 Compile 中绑定的回调,则功成身退。

    4. MVVM 作为数据绑定的入口,整合 Observer、Compile 和 Watcher 三者,通过 Observer 来监听自己的 model 数据变化,通过 Compile 来解析编译模板指令,最终利用 Watcher 搭起 Observer 和 Compile 之间的通信桥梁,达到数据变化 -> 视图更新;视图交互变化(input) -> 数据 model 变更的双向绑定效果。

    • vue3 响应式原理

    Vue3 使用 Proxy 来监控数据的变化。Proxy 是 ES6 中提供的功能,其作用为:用于定义基本操作的自定义行为(如属性查找,赋值,枚举,函数调用等).相对于 Object.defineProperty() 其有以下特点:Proxy 直接代理整个对象而非对象属性,这样只需做一层代理就可以监听同级结构下的所有属性变化,包括新增属性和删除属性。

    Proxy 可以监听数组的变化。

    理解响应式

    总结一下:

    1. 任何一个 Vue Component 都有一个与之对应的 Watcher 实例。

    2. Vue 的 data 上的属性会被添加 getter 和 setter 属性。

    3. 当 Vue Component render 函数被执行的时候, data 上会被 触碰(touch), 即被, getter 方法会被调用, 此时 Vue 会去记录此 Vue component 所依赖的所有 data。(这一过程被称为依赖收集)

    4. data 被改动时(主要是用户操作), 即被, setter 方法会被调用, 此时 Vue 会去通知所有依赖于此 data 的组件去调用他们的 render 函数进行更新

    响应性是一种可以使我们声明式地处理变化的编程范式

    let A0 = 1
    let A1 = 2
    let A2 = A0 + A1
    ​
    console.log(A2) // 3
    ​
    A0 = 2
    console.log(A2) // 仍然是 3

    分析

    let A2
    ​
    function update() {
      A2 = A0 + A1
    }

    当 A0, A1 的值改变时,就去自动执行 update

    然后,我们需要定义几个术语:

    • 这个 update() 函数会产生一个副作用,或者就简称为作用,因为它会更改程序里的状态。

    • A0A1 被视为这个作用的依赖,因为它们的值被用来执行这个作用。因此这次作用也可以说是一个它依赖的订阅者

    我们无法直接追踪对上述示例中局部变量的读写过程,在原生 JavaScript 中没有提供这样一种机制。但是,我们是可以追踪一个对象的属性进行读和写的。

    #Object.defineProperty

    v2 的用法

    基本使用

    Object.defineProperty(obj, 'a', {
      set(val) {
        console.log('obj.a被赋值...')
      },
      get() {
        console.log('obj.a被访问...')
      },
    })

    封装函数

    function defineReactive(obj, key, value) {
      Object.defineProperty(obj, key, {
        set(val) {
          console.log('obj.a被赋值...')
          value = val
        },
        get() {
          console.log('obj.a被访问...')
          return value
        },
      })
    }
    let obj = { a: 1, b: 2 }
    defineReactive(obj, 'a', obj['a'])

    循环+递归

    function defineReactive(obj, key, value) {
      Object.defineProperty(obj, key, {
        set(val) {
          console.log(key, '被赋值...')
          value = val
        },
        get() {
          console.log(key, '被访问...')
          return value
        },
      })
    }
    var obj = { a: 1, b: 2, c: { c1: 0 } }
    function walk(obj) {
      for (let key in obj) {
        let value = obj[key]
        if (typeof value === 'object') {
          walk(value)
        } else {
          defineReactive(obj, key, value)
        }
      }
    }
    ​
    walk(obj)

    #v2 的问题

    1. 深度监听需要一次性递归;

    2. 无法监听对象新增|删除的属性

    3. 无法监听数组的元素操作

    #Proxy

    可以有效解决上面的三个问题

    let obj = { a: 1, b: 2, c: { c1: 0 } }
    ​
    function reactive(obj) {
      return new Proxy(obj, {
        get(target, key) {
          console.log(key, '被访问')
          let value = target[key]
          if (typeof value === 'object') {
            return reactive(value)
          } else {
            return target[key]
          }
        },
        set(target, key, newValue) {
          target[key] = newValue
          console.log(key, '被设置')
        },
      })
    }
    let newObj = reactive(obj)

虚拟 DOM

#简版

Virtual DOM 其实就是一棵以 JavaScript 对象(VNode 节点)作为基础的树,用对象属性来描述节点,相当于在 js 和真实 dom 中间加来一个缓存,利用 dom diff 算法避免没有必要的 dom 操作,从而提高性能。当然算法有时并不是最优解,因为它需要兼容很多实际中可能发生的情况,比如后续会讲到两个节点的 dom 树移动。

在 vue 中一般都是通过修改元素的 state,订阅者根据 state 的变化进行编译渲染,底层的实现可以简单理解为三个步骤:

1、用 JavaScript 对象结构表述 dom 树的结构,然后用这个树构建一个真正的 dom 树,插到浏览器的页面中。

2、当状态改变了,也就是我们的 state 做出修改,vue 便会重新构造一棵树的对象树,然后用这个新构建出来的树和旧树进行对比(只进行同层对比),记录两棵树之间的差异。

3、把记录的差异再重新应用到所构建的真正的 dom 树,视图就更新了。

它的表达方式就是把每一个标签都转为一个对象,这个对象可以有三个属性:tag、props、children

  • tag:必选。就是标签。也可以是组件,或者函数

  • props:非必选。就是这个标签上的属性和方法

  • children:非必选。就是这个标签的内容或者子节点,如果是文本节点就是字符串,如果有子节点就是数组。换句话说 如果判断 children 是字符串的话,就表示一定是文本节点,这个节点肯定没有子元素

(@王游游)

#什么是虚拟 DOM

虚拟 DOM (Virtual DOM )这个概念相信大家都不陌生,从 ReactVue ,虚拟 DOM 为这两个框架都带来了跨平台的能力(React-NativeWeex

实际上它只是一层对真实DOM的抽象,以JavaScript 对象 (VNode 节点) 作为基础的树,用对象的属性来描述节点,最终可以通过一系列操作使这棵树映射到真实环境上

Javascript对象中,虚拟DOM 表现为一个 Object对象。并且最少包含标签名 (tag)、属性 (attrs) 和子元素对象 (children) 三个属性,不同框架对这三个属性的名命可能会有差别

创建虚拟DOM就是为了更好将虚拟的节点渲染到页面视图中,所以虚拟DOM对象的节点与真实DOM的属性一一照应

vue中同样使用到了虚拟DOM技术

定义真实DOM

<div id="app">
  <p class="p">节点内容</p>
  <h3>{{ foo }}</h3>
</div>

1 2 3 4

实例化vue

const app = new Vue({
  el: '#app',
  data: {
    foo: 'foo',
  },
})

1 2 3 4 5 6

观察模板对应的render函数,我们能得到虚拟DOM

;(function anonymous() {
  with (this) {
    return _c('div', { attrs: { id: 'app' } }, [_c('p', { staticClass: 'p' }, [_v('节点内容')]), _v(' '), _c('h3', [_v(_s(foo))])])
  }
})

1 2 3 4 5

通过VNodevue可以对这颗抽象树进行创建节点,删除节点以及修改节点的操作, 经过diff算法得出一些需要修改的最小单位,再更新视图,减少了dom操作,提高了性能

#为什么需要虚拟 DOM

  • 性能提升

  • 跨平台

真实DOM是很慢的,其元素非常庞大,页面的性能问题,大部分都是由DOM操作引起的。真实的DOM节点,哪怕一个最简单的div也包含着很多属性,可以打印出来直观感受一下:

现代的前端开发

有了一定的复杂度,想减少计算次数比较难,

能不能把计算,更多的转移为 JS 计算? js 的执行速度很快

Vdom-用 js 模拟 dom 结构,计算出最小的变更,操作 dom

#用 js 模拟 dom 结构

没有严格的,统一的标准。一般会有: tag, props, children 三项。它们会组成一棵树。基本过关条件: 根据左图写出右图。

#snabbdom

简洁强大的 vdom 库,易学易用

vue 参考它实现的 vdom 和 diff。v3 重写了 vdom 的代码,但是基本理念不变

很多人认为虚拟 DOM 最大的优势是 diff 算法,减少 JavaScript 操作真实 DOM 的带来的性能消耗。虽然这一个虚拟 DOM 带来的一个优势,但并不是全部。虚拟 DOM 最大的优势在于抽象了原本的渲染过程,实现了跨平台的能力,而不仅仅局限于浏览器的 DOM,可以是安卓和 IOS 的原生组件,可以是近期很火热的小程序,也可以是各种 GUI

如何实现虚拟 DOM

首先可以看看vueVNode的结构

源码位置: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's scope
  functionalContext: Component | void // only for functional component root nodes
  key: string | number | void
  componentOptions: VNodeComponentOptions | void
  componentInstance: Component | void // component instance
  parent: VNode | void // component placeholder node
  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?

  constructor(tag?: string, data?: VNodeData, children?: ?Array<VNode>, text?: string, elm?: Node, context?: Component, componentOptions?: VNodeComponentOptions) {
    /*当前节点的标签名*/
    this.tag = tag
    /*当前节点对应的对象,包含了具体的一些数据信息,是一个VNodeData类型,可以参考VNodeData类型中的数据信息*/
    this.data = data
    /*当前节点的子节点,是一个数组*/
    this.children = children
    /*当前节点的文本*/
    this.text = text
    /*当前虚拟节点对应的真实dom节点*/
    this.elm = elm
    /*当前节点的名字空间*/
    this.ns = undefined
    /*编译作用域*/
    this.context = context
    /*函数化组件作用域*/
    this.functionalContext = undefined
    /*节点的key属性,被当作节点的标志,用以优化*/
    this.key = data && data.key
    /*组件的option选项*/
    this.componentOptions = componentOptions
    /*当前节点对应的组件的实例*/
    this.componentInstance = undefined
    /*当前节点的父节点*/
    this.parent = undefined
    /*简而言之就是是否为原生HTML或只是普通文本,innerHTML的时候为true,textContent的时候为false*/
    this.raw = false
    /*静态节点标志*/
    this.isStatic = false
    /*是否作为跟节点插入*/
    this.isRootInsert = true
    /*是否为注释节点*/
    this.isComment = false
    /*是否为克隆节点*/
    this.isCloned = false
    /*是否有v-once指令*/
    this.isOnce = false
  }

  // DEPRECATED: alias for componentInstance for backwards compat.
  /* istanbul ignore next https://github.com/answershuto/learnVue*/
  get child(): Component | void {
    return this.componentInstance
  }
}

这里对VNode进行稍微的说明:

  • 所有对象的 context 选项都指向了 Vue 实例

  • elm 属性则指向了其相对应的真实 DOM 节点

vue`是通过`createElement`生成`VNode

源码位置:src/core/vdom/create-element.js

export function createElement(context: Component, tag: any, data: any, children: any, normalizationType: any, alwaysNormalize: boolean): VNode | Array<VNode> {
  if (Array.isArray(data) || isPrimitive(data)) {
    normalizationType = children
    children = data
    data = undefined
  }
  if (isTrue(alwaysNormalize)) {
    normalizationType = ALWAYS_NORMALIZE
  }
  return _createElement(context, tag, data, children, normalizationType)
}

上面可以看到createElement 方法实际上是对 _createElement 方法的封装,对参数的传入进行了判断

export function _createElement(
    context: Component,
    tag?: string | Class<Component> | Function | Object,
    data?: VNodeData,
    children?: any,
    normalizationType?: number
): VNode | Array<VNode> {
    if (isDef(data) && isDef((data: any).__ob__)) {
        process.env.NODE_ENV !== 'production' && warn(
            `Avoid using observed data object as vnode data: ${JSON.stringify(data)}\n` +
            'Always create fresh vnode data objects in each render!',
            context`
        )
        return createEmptyVNode()
    }
    // object syntax in v-bind
    if (isDef(data) && isDef(data.is)) {
        tag = data.is
    }
    if (!tag) {
        // in case of component :is set to falsy value
        return createEmptyVNode()
    }
    ...
    // support single function children as default scoped slot
    if (Array.isArray(children) &&
        typeof children[0] === 'function'
    ) {
        data = data || {}
        data.scopedSlots = { default: children[0] }
        children.length = 0
    }
    if (normalizationType === ALWAYS_NORMALIZE) {
        children = normalizeChildren(children)
    } else if ( === SIMPLE_NORMALIZE) {
        children = simpleNormalizeChildren(children)
    }
	// 创建VNode
    ...
}

可以看到_createElement接收 5 个参数:

  • context 表示 VNode 的上下文环境,是 Component 类型

  • tag 表示标签,它可以是一个字符串,也可以是一个 Component

  • data 表示 VNode 的数据,它是一个 VNodeData 类型

  • children 表示当前 VNode的子节点,它是任意类型的

  • normalizationType 表示子节点规范的类型,类型不同规范的方法也就不一样,主要是参考 render 函数是编译生成的还是用户手写的

根据normalizationType 的类型,children会有不同的定义

if (normalizationType === ALWAYS_NORMALIZE) {
    children = normalizeChildren(children)
} else if ( === SIMPLE_NORMALIZE) {
    children = simpleNormalizeChildren(children)
}

simpleNormalizeChildren方法调用场景是 render 函数是编译生成的

normalizeChildren方法调用场景分为下面两种:

  • render 函数是用户手写的

  • 编译 slotv-for 的时候会产生嵌套数组

无论是simpleNormalizeChildren还是normalizeChildren都是对children进行规范(使children 变成了一个类型为 VNodeArray),这里就不展开说了

规范化children的源码位置在:src/core/vdom/helpers/normalzie-children.js

在规范化children后,就去创建VNode

let vnode, ns
// 对tag进行判断
if (typeof tag === 'string') {
  let Ctor
  ns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag)
  if (config.isReservedTag(tag)) {
    // 如果是内置的节点,则直接创建一个普通VNode
    vnode = new VNode(config.parsePlatformTagName(tag), data, children, undefined, undefined, context)
  } else if (isDef((Ctor = resolveAsset(context.$options, 'components', tag)))) {
    // component
    // 如果是component类型,则会通过createComponent创建VNode节点
    vnode = createComponent(Ctor, data, context, children, tag)
  } else {
    vnode = new VNode(tag, data, children, undefined, undefined, context)
  }
} else {
  // direct component options / constructor
  vnode = createComponent(tag, data, context, children)
}

createComponent`同样是创建`VNode

源码位置:src/core/vdom/create-component.js

export function createComponent(Ctor: Class<Component> | Function | Object | void, data: ?VNodeData, context: Component, children: ?Array<VNode>, tag?: string): VNode | Array<VNode> | void {
  if (isUndef(Ctor)) {
    return
  }
  // 构建子类构造函数
  const baseCtor = context.$options._base

  // plain options object: turn it into a constructor
  if (isObject(Ctor)) {
    Ctor = baseCtor.extend(Ctor)
  }

  // if at this stage it's not a constructor or an async component factory,
  // reject.
  if (typeof Ctor !== 'function') {
    if (process.env.NODE_ENV !== 'production') {
      warn(`Invalid Component definition: ${String(Ctor)}`, context)
    }
    return
  }

  // async component
  let asyncFactory
  if (isUndef(Ctor.cid)) {
    asyncFactory = Ctor
    Ctor = resolveAsyncComponent(asyncFactory, baseCtor, context)
    if (Ctor === undefined) {
      return createAsyncPlaceholder(asyncFactory, data, context, children, tag)
    }
  }

  data = data || {}

  // resolve constructor options in case global mixins are applied after
  // component constructor creation
  resolveConstructorOptions(Ctor)

  // transform component v-model data into props & events
  if (isDef(data.model)) {
    transformModel(Ctor.options, data)
  }

  // extract props
  const propsData = extractPropsFromVNodeData(data, Ctor, tag)

  // functional component
  if (isTrue(Ctor.options.functional)) {
    return createFunctionalComponent(Ctor, propsData, data, context, children)
  }

  // extract listeners, since these needs to be treated as
  // child component listeners instead of DOM listeners
  const listeners = data.on
  // replace with listeners with .native modifier
  // so it gets processed during parent component patch.
  data.on = data.nativeOn

  if (isTrue(Ctor.options.abstract)) {
    const slot = data.slot
    data = {}
    if (slot) {
      data.slot = slot
    }
  }

  // 安装组件钩子函数,把钩子函数合并到data.hook中
  installComponentHooks(data)

  //实例化一个VNode返回。组件的VNode是没有children的
  const name = Ctor.options.name || tag
  const vnode = new VNode(`vue-component-${Ctor.cid}${name ? `-${name}` : ''}`, data, undefined, undefined, undefined, context, { Ctor, propsData, listeners, tag, children }, asyncFactory)
  if (__WEEX__ && isRecyclableComponent(vnode)) {
    return renderRecyclableComponentTemplate(vnode)
  }

  return vnode
}

稍微提下createComponent生成VNode的三个关键流程:

  • 构造子类构造函数Ctor

  • installComponentHooks安装组件钩子函数

  • 实例化 vnode

###

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值