git
Git
是什么
-
Git
版本控制系统是
一个分布式的系统,是
用来保存工程源代码历史状态(游戏存档)的命令行
工具 -
Git是一个命令行(小黑窗)工具,用于版本控制(存档器)
Git的作用是什么?
-
-
版本管理
工具:说人话就是可以记录你敲代码的每一个环节(类似于玩游戏存档)
-
Git应用场景介绍
-
多人开发
代码管理
目前我们多人代码的时候,想把代码合并一起是利用最原始的复制粘贴操作。有了git之后,可以一键搞
-
异地开发
代码管理
实际开发中,我们上班可能会用公司电脑敲代码。有时候回到家里用自己电脑偷偷加个班,以前的做法是备一个U盘,复制粘贴。有了git之后,直接一键搞定。
-
版本管理
比如我现在公司网站已经做出来了1.0版本在使用,现在计划增加一些新功能,但是这个功能不稳定需要经过开发和测试环节,为了不影响现有的稳定版本。以前的做法是把稳定版本单独拷贝一份增加功能,等做好之后再把以前版本给替换掉。有了git之后可以一键搞定。 -
版本回滚
: 比如这周产品经理提出一个功能,我辛辛苦苦写了一周代码。 到了下周,产品经理说这个功能不做了,叫我删掉改成其他功能。 我们把代码删掉之后按照产品经理要求又辛辛苦苦写了一周代码。到了下下周,产品经理说这个功能还是没有上次那个好,还是改成上周的吧…………别着急,有了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,在数据变动时发布消息给订阅者,触发相应的监听回调。主要分为以下几个步骤:
-
需要 observe 的数据对象进行递归遍历,包括子属性对象的属性,都加上 setter 和 getter 这样的话,给这个对象的某个值赋值,就会触发 setter,那么就能监听到了数据变化
-
compile 解析模板指令,将模板中的变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,更新视图
-
Watcher 订阅者是 Observer 和 Compile 之间通信的桥梁,主要做的事情是: ① 在自身实例化时往属性订阅器(dep)里面添加自己 ② 自身必须有一个 update()方法 ③ 待属性变动 dep.notice()通知时,能调用自身的 update()方法,并触发 Compile 中绑定的回调,则功成身退。
-
MVVM 作为数据绑定的入口,整合 Observer、Compile 和 Watcher 三者,通过 Observer 来监听自己的 model 数据变化,通过 Compile 来解析编译模板指令,最终利用 Watcher 搭起 Observer 和 Compile 之间的通信桥梁,达到数据变化 -> 视图更新;视图交互变化(input) -> 数据 model 变更的双向绑定效果。
-
vue3 响应式原理
Vue3 使用 Proxy 来监控数据的变化。Proxy 是 ES6 中提供的功能,其作用为:用于定义基本操作的自定义行为(如属性查找,赋值,枚举,函数调用等).相对于 Object.defineProperty() 其有以下特点:Proxy 直接代理整个对象而非对象属性,这样只需做一层代理就可以监听同级结构下的所有属性变化,包括新增属性和删除属性。
Proxy 可以监听数组的变化。
理解响应式
总结一下:
-
任何一个 Vue Component 都有一个与之对应的 Watcher 实例。
-
Vue 的
data
上的属性会被添加 getter 和 setter 属性。 -
当 Vue Component
render
函数被执行的时候,data
上会被触碰(touch)
, 即被读
,getter
方法会被调用, 此时 Vue 会去记录此 Vue component 所依赖的所有data
。(这一过程被称为依赖收集) -
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()
函数会产生一个副作用,或者就简称为作用,因为它会更改程序里的状态。 -
A0
和A1
被视为这个作用的依赖,因为它们的值被用来执行这个作用。因此这次作用也可以说是一个它依赖的订阅者。
我们无法直接追踪对上述示例中局部变量的读写过程,在原生 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 的问题
-
深度监听需要一次性递归;
-
无法监听对象新增|删除的属性
-
无法监听数组的元素操作
#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
)这个概念相信大家都不陌生,从 React
到 Vue
,虚拟 DOM
为这两个框架都带来了跨平台的能力(React-Native
和 Weex
)
实际上它只是一层对真实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
通过VNode
,vue
可以对这颗抽象树进行创建节点,删除节点以及修改节点的操作, 经过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
首先可以看看vue
中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'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
函数是用户手写的 -
编译
slot
、v-for
的时候会产生嵌套数组
无论是simpleNormalizeChildren
还是normalizeChildren
都是对children
进行规范(使children
变成了一个类型为 VNode
的 Array
),这里就不展开说了
规范化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
###