Alter's Blog

  • 首页

  • 归档

  • 关于

  • 友链

  • 搜索

JavaScript异步总结

发表于 2019-07-14 | 更新于 2019-08-02 | 分类于 JavaScript | 评论数: | 阅读次数:

前言

JavaScript与其他语言最大的不同点就在于其异步操作。在JavaScript中,有3种异步的调用方法:callbacks、Promises和async/await。其中callbacks是最为传统最为老派的方式,没有那么高效灵活,一般仅在必要时使用。Promises是现代JavaScript异步的支柱,允许在异步操作结束后再根据结果进行后续操作。async/await则是基于Promises的、同步风格的方式,具有更高的可读性。

此外,由于JavaScript的语言特性较为灵活,再加上异步操作本来就很复杂,保持一个良好的编程风格就显得格外重要。在本文的最后部分将对编码风格进行讨论。

但是在讨论这些之前,先让我们了解下JavaScript的异步操作是如何工作的,以及一些常用的异步函数之间有什么不同。

JavaScript异步

异步的概念

在编写一般的同步代码时,如果遇到比较费时的任务,比如I/O操作、网络操作或者计算量较大的函数时,程序会卡死在此处,直到相应的操作完成。这将极大地降低用户的体验。

异步正是为了解决这一问题而生的。当遇到比较费时当任务时,可以运用异步来避免原地等待,而是设置当任务完成后需要调用的函数。callbacks方法最直观地体现了这一点。这样一来,程序可以非阻塞地继续运行下去,而如果刚才的任务完成了,触发相应的事件调用对应的回调函数。

异步的概念非常直观,但是实际上手又是另外一回事了。首先我们需要了解的就是JavaScript中的异步到底是怎么运行的。

JavaScript异步的原理

第一个需要记住的概念是JavaScript在一般情况下都是单线程的(后来我了解到其实JavaScript是可以多线程的,但是必须是在完全不同的context中才行)。即使你的CPU是多核的,JavaScript也只能在其中一个核上运行。单线程保证了JavaScript的异步不会过于复杂,由于同一时间只有一个执行点,JavaScript也不需要锁的机制。但是也正因为如此,JavaScript的异步往往和我们一开始想象的并不一样。

1
2
3
for (var i=0; i<3; i++) {
setTimeout(function(){ console.log(i); }, 0);
}

运行上述代码后,将输出3个3。原因在于,虽然设置了回调函数在0ms后启动,但是只有在线程空闲时,JavaScript的事件处理器才会运行。(另一个原因是这里使用了var来声明i,如果使用let则不会有问题)

基于上面的例子,我们也同样可以得出另一个容易造成困扰的结论:setTimeout中的第二个参数设置的时间并不能保证回调函数在这么长时间之后运行,而只是设置了一个下限。这个结论对于setInterval函数来说也同样适用。

常用异步函数

setTimeout()

setTimeout的一般使用方法为

1
setTimeout(func[, delay, [, param1, param2, ...]])

代表(至少)delay毫秒之后运行func。param则是func的参数。

特别的,当delay设置为0时,代表在主线程完成之后立即调用func。

另一种常用的调用方式是递归调用,从而达到和setInterval函数类似的效果。

1
2
3
4
setTimeout(function run () {
// Do something ...
setTimeout(run, delay);
}, delay);

与setInterval不同的是,使用这种方式的delay不包含回调函数执行的时间,而setInterval中则相反。

还有一个相关的函数是clearTimeout,其调用方式为

1
clearTimeout(timeoutID);

其中timeoutID是调用setTimeout后得到的返回值。

setInterval()

setInterval的常用方式为

1
setInterval(func, delay[, param1, param2, ...]);

代表每隔(至少)delay毫秒后运行func。param是func的参数。

取消setInterval的方法和取消setTimeout类似,可以采用如下方式

1
clearInterval(intervalID);

其中timeoutID是调用setTimeout后得到的返回值。

值得注意的是clearTimeout和clearInterval是在同一个列表里寻找要清除的entries的,所以实际上二者是可以混用的。但是为了程序的可读性,还是应该使用对应的函数。

requestAnimationFrame()

上面两个函数的好处在于可以灵活控制delay的时间。但是在处理动画时,由于JavaScript的事件处理机制,无法保持最佳的帧数,有时甚至会掉帧。另外,由于没有进行相关的优化,当页面切换出去或者动画部分已经被滚轮移出屏幕时,相应的程序仍然会运行。

requestAnimationFrame就是为了解决这些问题而生的。其常见的调用方法为

1
requestAnimationFrame(func);

代表在下一次重新绘制之前调用func函数。通常func的执行频率为每秒60次,但在大多数遵循W3C建议的浏览器中,func执行的频率与屏幕的刷新率相匹配。并且,为了提高性能,当requestAnimationFrame所在的窗口或者iframe运行在后台标签页或者是被隐藏时,requestAnimationFrame会被暂停调用。

异步调用的不同方式

callbacks

之前写的部分使用的基本都是callbacks的方法。简单来说就是定义某个事件发生后需要调用的函数。除了上面所说的与时间有关的三个函数外,通常的使用方法为

1
el.addEventListener(event, func);

代表当网页上的el元素发生event事件时,调用func函数。

该方法的优点是兼容性好,所有浏览器都支持该方法。

缺点在于:

  • 太多回调会使得代码复杂且难以阅读(callback hell)。
  • 如果要处理回调链中的错误,必须在回调链中的每个回调函数内分别处理。
  • 无法保证按照定义的顺序进行回调。

Promises

Promises定义了一个异步方法及其状态。其状态包括:

  • 待定(pending),代表其刚被创建出来的状态,既不是成功也不是失败。
  • 解决(resolved),代表异步方法已经返回。
    • 一个成功resolved的Promise称为fullfilled,其返回一个值作为then()中回调函数的参数
    • 一个不成功的resolved被称为rejected,其返回一个错误信息作为catch()中捕获到的错误

pending状态只存在于Promise被创建出来时,而fullfilled
一个令人迷惑的点在于,这些状态和改变状态的函数的命名方式并不是一致的。到达fullfilled的状态需要使用resolve()函数,到达rejected状态则需要使用reject()函数。虽然其实resolve和reject只是Promise构造函数的两个参数(见下文),可以随意命名,但是习惯上还是使用这两个令人迷惑的名称。

得到一个Promise有两种方法,一是直接使用构造函数new出来,另一种是使用一般方法调用异步函数(函数定义前带有async的那种)。在此处重点说明第一种方法,第二种方法将在下一部分中涉及。

1
2
3
4
5
let timeoutPromise = new Promise((resolve, reject) => {
setTimeout(function(){
resolve('Success!');
}, 2000);
});

执行完上述语句后得到一个timeoutPromise,该Promise在创建时为pending状态。当2000毫秒之后,setTimeout的回调函数启动,将该Promise的状态变为fullfilled。这个状态的改变则会影响其后的then和catch的执行。

1
2
3
timeoutPromise.then(function(msg){
console.log(msg);
})

上述语句定义了当timeoutPromise的状态变为fullfilled之后要执行的函数:打印fullfilled传递的信息。

值得注意的是,如果在then()中的函数也返回一个Promise,那么多个then语句可以串联起来。而catch语句则一般放在所有的then语句后面。和同步语句类似,finally函数在这里也是可用的。

1
2
3
4
5
6
7
8
9
timeoutPromise
.then(msg => { /* do something async */ })
.then(ret => { /* do something */ })
.catch(e => {
console.log('Error: ' + e.message);
})
.finally(() => {
console.log('Finished.');
});

有时候,需要等待多个Promise变为fullfilled之后再进行下一步操作,这就需要用到Promise.all()函数。

1
2
3
Promise.all([a, b, c]).then(values => {
...
});

在上述代码中,只有当a、b、c全部fullfilled之后才会执行then语句,并且在then语句中的函数的参数将为一个数组,分别对应的a、b、c中resolve函数的输入。而如果有任何一个Promise失败,则整个块都将变为rejected,然后进入catch部分。

async/await

async可以被放在函数声明之前,将函数的返回值变为Promise,而函数原来的返回值(如果有的话)将通过自动传递到then()中。该Promise将在函数内部定义的代码全部执行完毕之后,进入fullfilled状态。

1
2
async function hello() { return "Hello" };
hello().then((value) => console.log(value));

await只能在使用async声明的函数(异步函数)中使用。其作用是等待一个异步函数的调用完成(或者说等待返回的这个Promise的状态变为fillfilled)。并且会使得异步函数直接返回原来的返回值(而不是一个Promise)。在该异步函数执行完成之前,将不会执行await语句后面的部分。

1
2
3
4
async function hello() {
return greeting = await Promise.resolve("Hello");
};
hello().then(alert);

在使用async和await的函数中,异常处理将和同步代码类似,可以直接使用try/catch的方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
async function myFetch() {
try {
let response = await fetch('coffee.jpg');
let myBlob = await response.blob();

let objectURL = URL.createObjectURL(myBlob);
let image = document.createElement('img');
image.src = objectURL;
document.body.appendChild(image);
} catch(e) {
console.log(e);
}
}
myFetch();

或者也可以使用Promises部分所描述的那样。因为catch函数不但能捕获Promise链中的错误,也能捕获try代码块中的错误。

async/await方法使得异步代码阅读起来和同步代码类似。但是这种方法也有一定的缺陷。在async函数中,程序会在await处等待相关函数执行完毕再运行下一行代码。所以程序可能因为大量的Promises相继await和执行而变慢。

1
2
3
4
5
6
7
8
9
10
11
12
13
function timeoutPromise(interval) {
return new Promise((resolve, reject) => {
setTimeout(function(){
resolve("done");
}, interval);
});
};

async function timeTest() {
await timeoutPromise(1000);
await timeoutPromise(1000);
await timeoutPromise(1000);
}

上述语句将花费大约3000毫秒来执行,因为这3个Promise必须依次被创建和等待。

1
2
3
4
5
6
7
8
9
async function timeTest() {
const timeoutPromise1 = timeoutPromise(1000);
const timeoutPromise2 = timeoutPromise(1000);
const timeoutPromise3 = timeoutPromise(1000);

await timeoutPromise1;
await timeoutPromise2;
await timeoutPromise3;
}

这种方法则只需要约1000毫秒就能执行完毕,因为3个Promise是同时被创建的。这种使用方法和Promise.all()函数类似。

未来计划

未来可能会参考《JavaScript异步编程》和《编写可维护的JavaScript》增加编码风格的部分,以及对现在所写的内容做一些补充修改。

参考资料

Mozilla JavaScript Documentation

SVN用法总结

发表于 2019-06-24 | 更新于 2019-06-26 | 分类于 版本控制 | 评论数: | 阅读次数:

这篇文章旨在较为系统地整理SVN的用法。如果读者希望快速上手,可以直接阅读SVN生命周期部分。

SVN简介

SVN全称为Apache Subversion,是一个开源的版本控制系统。

SVN中的一些概念

  • Repository(源代码库):源代码统一存放的地方。
  • Checkout(提取):当你手上没有源代码的时候,你需要从repository checkout一份。
  • Commit(提交):当你已经修改了代码,你就需要Commit到repository。
  • Update (更新):当你已经Checkout了一份源代码, Update一下你就可以和Repository上的源代码同步,你手上的代码就会有最新的变更。

日常开发的流程通常为:Update(获得最新的代码)=>作出自己的修改并调试成功=> Commit(大家就可以看到你的修改了) 。
值得注意的是,SVN管理源代码是以行为单位的。所以如果两个程序员同时修改了同一个文件,只要没有修改同一行,SVN就可以合并这两个改动。如果是同一行则会提示Conflict,需要手动解决冲突。

SVN的主要功能

  • 目录版本控制:SVN通过一个“虚拟”的版本管控文件系统增加了对目录变动的支持。
  • 真实的版本历史:和CVS相比,支持了对目录或文件的增加(add)、删除(delete)、复制(copy)和重命名(rename)。所有新增的文件都从一个新的、干净的版本开始。
  • 自动提交:commit是原子操作,不是全部更新就是完全不更新。
  • 纳入版本控管的元数据:每个文件与目录所附有的属性也接受管控,如同文件内容一样。
  • 选择不同的网络层:SVN可以作为一个扩展模块嵌入Apache HTTP服务器中;也可以使用轻量级的独立SVN服务器,并自定义通信协议。另外还支持:身份认证, 授权, 在线压缩, 以及文件库浏览等功能。
  • 一致的数据处理方式:SVN使用二进制差异算法,所以在对二进制文件的支持和对文本文件的支持一样好。
  • 有效分支(branch)与标签(tag):SVN通过复制项目来建立分支和标签,类似与硬连接(hard-link),所以只会花费固定的少量时间。
  • Hackability:SVN没有历史包袱、便于维护。

安装

  • Windows:https://sourceforge.net/projects/win32svn/
  • CentOS:yum install subversion
  • Ubuntu: apt-get install subversion
  • MacOS:brew install subversion(需要先安装homebrew)

其中CentOS和MacOS通常自带Subversion,可通过svn --version命令检查已安装版本。

SVN生命周期

创建版本库=>检出=>更新=>执行变更=>复查变化=>修复错误=>解决冲突=>提交更改

  • 创建版本库(create):大多情况下只会执行一次。
  • 检出(checkout):从版本库创建一个工作副本,作为开发者私人的工作空间。
  • 更新(update):讲工作副本与版本库进行同步。
  • 执行变更(commit):对项目的编辑会被添加到待变更列表中,直到执行commit才会成为版本库的一部分。
  • 复查变化(status):在commit之前复查是很好的习惯,status会列出工作副本中的改动,可以使用diff来查看详细变动。
  • 修复错误(revert):重置对工作副本的修改(部分或者全部),会销毁待变更列表并恢复工作副本到原始状态。
  • 解决冲突(mrege、resolve):merge会自动处理可以安全合并的部分,resolve用来帮助用户找出并处理冲突。
  • 提交更改(commit):提交前必须讲文件/目录添加(add)到待变更列表中。通常需要提供一个注释来说明为什么进行这些改动。

SVN启动模式

  • 手动新建版本库目录
    mkdir /opt/svn

  • 使用svn命令创建版本库
    svnadmin create /opt/svn/project_repo

  • 使用senserve启动服务
    svnserve -d -r 目录 --listen-port 端口号

    • --listen-port指定了svn监听端口(默认3690)

    • -r决定了版本库访问的方式:

      • 在project_repo下为单库svnserve方式svnserve -d -r /opt/svn/project_repo

        authz配置文件
        1
        2
        3
        4
        5
        6
        [groups]
        admin=user1
        dev=user2
        [/]
        @admin=rw
        user2=r

        使用类似这样的URL:svn://192.168.0.1/ 即可访问project_repo版本库

      • 在svn下则为多库svnserve方式svnserve -d -r /opt/svn

        authz配置文件
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        [groups]
        admin=user1
        dev=user2
        [project_repo:/]
        @admin=rw
        user2=r

        [project_repo01:/]
        @admin=rw
        user2=r

        如果此时你还用[/],则表示所有库的根目录,同理,[/src]表示所有库的根目录下的src目录。
        使用类似这样的URL:svn://192.168.0.1/project_repo 即可访问project_repo版本库。

配置SVN版本库

进入/opt/svn/project_repo/conf目录 修改默认配置文件。

svn服务配置文件svnserve.conf

该文件仅由一个[general]配置段组成。

conf/svnserve.conf
1
2
3
4
5
6
[general]
anon-access = none
auth-access = write
password-db = /home/svn/passwd
authz-db = /home/svn/authz
realm = tiku
  • anon-access: 非鉴权用户访问版本库的权限。”write”表示可读可写,”read”表示只读,”none”表示无访问权限。 缺省值:read。
  • auth-access: 鉴权用户(authentication user)访问版本库的权限。”write”表示可读可写,”read”表示只读,”none”表示无访问权限。 缺省值:write。
  • authz-db: 权限配置文件名,通过该文件可以实现以路径为基础的访问控制。 除非指定绝对路径,否则文件位置为相对conf目录的相对路径。 缺省值:authz。
  • password-db: 用户名口令文件名。 除非指定绝对路径,否则文件位置为相对conf目录的相对路径。缺省值为passwd。
  • realm: 版本库的认证域,即在登录时提示的认证域名称。若两个版本库的认证域相同,建议使用相同的用户名口令数据文件。缺省值为一个UUID(Universal Unique IDentifier,全局唯一标示)。

用户名口令文件passwd

用户名口令文件由svnserve.conf的配置项password-db指定,缺省为conf目录中的passwd。该文件仅由一个[users]配置段组成。

conf/passwd
1
2
3
[users]
admin = admin
thinker = 123456

[users]配置段的配置行格式如下:
<用户名> = <口令>

权限配置文件

权限配置文件由svnserve.conf的配置项authz-db指定,缺省为conf目录中的authz。该配置文件由一个[groups]配置段和若干个版本库路径权限段组成。

conf/authz
1
2
3
4
5
6
7
8
9
10
[groups]
g_admin = admin,thinker

[admintools:/]
@g_admin = rw
* =

[test:/home/thinker]
thinker = rw
* = r

[groups]配置段的配置行格式如下:
<用户名> = <口令>

版本库路径权限段的段名格式如下:
[<版本库名>:<路径>]

本例是使用svnserve -d -r /opt/svn以多库svnserve方式启动SVN,所以URL为svn://192.168.0.1/project_repo01。

SVN检出操作

1
svn checkout svn://svn.server.com/svn/project_repo --username=user01

检出成功后在当前目录下生成project_repo副本目录。

SVN解决冲突

  • 使用以下命令查看更改
    svn diff

    1
    2
    3
    4
    5
    6
    7
    Index: HelloWorld.html
    ===================================================================
    --- HelloWorld.html (revision 5)
    +++ HelloWorld.html (working copy)
    @@ -1,2 +1 @@
    -HelloWorld! http://www.svn.com/
    +HelloWorld! http://www.svn.com/!
  • 尝试使用下面的命令来提交他的更改
    svn commit -m "change HelloWorld.html first"

    1
    2
    3
    4
    root@hostname:~/svn/project_repo01/trunk# svn commit -m "change HelloWorld.html first"
    Sending HelloWorld.html
    Transmitting file data .svn: E160028: Commit failed (details follow):
    svn: E160028: File '/trunk/HelloWorld.html' is out of date

    此时,HelloWorld.html 已经被 user02 修改并提交到了仓库,所以提交失败了。
    为了避免两人的代码被互相覆盖在提交更改之前必须先更新工作副本。

  • 更新工作副本
    svn update

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    root@hostname:~/svn/project_repo01/trunk# svn update
    Updating '.':
    C HelloWorld.html
    Updated to revision 6.
    Conflict discovered in file 'HelloWorld.html'.
    Select: (p) postpone, (df) show diff, (e) edit file, (m) merge,
    (mc) my side of conflict, (tc) their side of conflict,
    (s) show all options: mc
    Resolved conflicted state of 'HelloWorld.html'
    Summary of conflicts:
    Text conflicts: 0 remaining (and 1 already resolved)

    在上面的例子中选择了”mc”,代表以本地的文件为主。你也可以使用其选项对冲突的文件进行不同的操作。

    默认是更新到最新的版本,我们也可以指定更新到哪个版本
    svn update -r6

  • 工作副本和仓库同步完毕后即可安全地提交
    svn commit -m "change HelloWorld.html second"

    1
    2
    3
    4
    root@hostname:~/svn/project_repo01/trunk# svn commit -m "change HelloWorld.html second"
    Sending HelloWorld.html
    Transmitting file data .
    Committed revision 7.

SVN提交操作

查看工作副本中的状态

1
2
root@hostname:~/svn/project_repo01/trunk# svn status
? readme

此时 readme的状态为?,说明它还未加到版本控制中。
将文件readme加到版本控制,等待提交到版本库。

1
2
root@hostname:~/svn/project_repo01/trunk# svn add readme
A readme

查看工作副本中的状态

1
2
root@hostname:~/svn/project_repo01/trunk# svn status
A readme

此时 readme的状态为A,它意味着这个文件已经被成功地添加到了版本控制中。
为了把 readme 存储到版本库中,使用 commit -m 加上注释信息来提交。
如果你忽略了 -m 选项, SVN会打开一个可以输入多行的文本编辑器来让你输入提交信息。

1
2
3
4
5
root@hostname:~/svn/project_repo01/trunk# svn commit -m "SVN readme."
Adding readme
Transmitting file data.
Committed revision 8.
svn commit -m "SVN readme."

现在 readme 被成功地添加到了版本库中,并且修订版本号自动增加了1。

SVN版本回退

回退未提交的修改

1
2
root@hostname:~/svn/project_repo01/trunk# svn status
M readme

使用一下命令回退

1
2
root@hostname:~/svn/project_repo01/trunk# svn revert readme
Reverted 'readme'

回退后的状态为

1
2
root@hostname:~/svn/project_repo01/trunk# svn status
root@hostname:~/svn/project_repo01/trunk#

进行 revert 操作之后,readme 文件恢复了原始的状态。 revert 操作不单单可以使单个文件恢复原状, 而且可以使整个目录恢复原状。恢复目录用 -R 命令,如下

1
svn revert -R trunk

回退已提交的修改

为了消除一个旧版本,我们必须撤销旧版本里的所有更改然后提交一个新版本。这种操作叫做 reverse merge。
首先,找到仓库的当前版本,现在是版本 22,我们要撤销回之前的版本,比如版本 21。

1
svn merge -r 22:21 readme

SVN查看历史信息

通过svn命令可以根据时间或修订号去除过去的版本,或者某一版本所做的具体的修改。以下四个命令可以用来查看svn 的历史:

  • svn log: 用来展示svn 的版本作者、日期、路径等等。
  • svn diff: 用来显示特定修改的行级详细信息。
  • svn cat: 取得在特定版本的某文件显示在当前屏幕。
  • svn list: 显示一个目录或某一版本存在的文件。

svn log

  • 基本使用方法

    1
    svn log

    可以显示所有的信息。

  • 如果只想查看某一个文件的版本修改信息,可以使用

    1
    svn log filename
  • 如果只希望查看特定的某两个版本之间的信息,可以加上-r 6:8。 其中6和8分别代表开始的版本和结束的版本

  • 如果希望得到目录的信息,加上-v。

  • 如果希望显示限定N条记录的目录信息,加上-l N。

svn diff

用来检查历史修改的详情。

  • 检查本地修改
    如果不带任何参数,它将会比较你的工作文件与缓存在 .svn 的”原始”拷贝。

    1
    svn diff
  • 比较工作拷贝与版本库
    比较你的工作拷贝和版本库中版本号为 3 的文件 rule.txt。

    1
    svn diff -r 3 rule.txt
  • 比较版本库与版本库
    通过-r(revision)传递两个通过冒号分开的版本号,这两个版本会进行比较。
    比较 svn 工作版本中版本号2和3的这个文件的变化。

    1
    svn diff -r 2:3 rule.txt

svn cat

如果只是希望检查一个过去版本,不希望查看他们的区别,可使用svn cat。

1
svn cat -r 版本号 rule.txt

这个命令会显示在该版本号下的该文件内容。

svn list

svn list可以在不下载文件到本地目录的情况下来察看目录中的文件。

1
svn list svn://192.168.0.1/project_repo

SVN分支

Branch 选项会给开发者创建出另外一条线路。当有人希望开发进程分开成两条不同的线路时,这个选项会非常有用。

比如项目 demo 下有两个小组,svn 下有一个 trunk 版。

由于客户需求突然变化,导致项目需要做较大改动,此时项目组决定由小组 1 继续完成原来正进行到一半的工作(某个模块),小组 2 进行新需求的开发。

那么此时,我们就可以为小组2建立一个分支,分支其实就是 trunk 版(主干线)的一个copy版,不过分支也是具有版本控制功能的,而且是和主干线相互独立的,当然,到最后我们可以通过(合并)功能,将分支合并到 trunk 上来,从而最后合并为一个项目。

  • 创建分支

    1
    root@hostname:~/svn/project_repo01#svn copy trunk/ branches/my_branch
  • 提交分支

    1
    root@hostname:~/svn/project_repo01#svn commit -m "add my_branch"
  • 切换分支

    1
    root@hostname:~/svn/project_repo01# cd branches/my_branch/
  • 提交分支中的修改

    1
    root@hostname:~/svn/project_repo01/branches/my_branch# svn add newfile
    1
    root@hostname:~/svn/project_repo01/branches/my_branch# svn commit -m "add newfile"
  • 合并分支
    切换到 trunk,执行 svn update,然后将 my_branch 分支合并到 trunk 中。

    1
    root@hostname:~/svn/project_repo01/trunk# svn merge ../branches/my_branch/
  • 提交主干
    将合并好的 trunk 提交到版本库中。

    1
    root@hostname:~/svn/project_repo01/trunk# svn commit -m "add newfile"

SVN标签

版本管理系统支持 tag 选项,通过使用 tag 的概念,我们可以给某一个具体版本的代码一个更加有意义的名字。

Tags 即标签主要用于项目开发中的里程碑,比如开发到一定阶段可以单独一个版本作为发布等,它往往代表一个可以固定的完整的版本。

使用一下命令在本地工作副本创建一个 tag

1
root@hostname:~/svn/project_repo01# svn copy trunk/ tags/v1.0

TortoiseSVN的使用

具有图形界面的SVN。
由于我目前主要在Mac上工作,而且GUI的工具也比较好上手,故在此略过了。
如有需要可以参考SVN教程

参考资料

SVN教程:https://www.runoob.com/svn/svn-tutorial.html

写在前面

发表于 2019-06-15 | 更新于 2019-07-10 | 分类于 随笔 | 评论数: | 阅读次数:

托暑期实习的福,我终于真的开始做web开发了。这段时间在学习Vue.js框架,也还没有正式做项目,所以还比较轻松,也让我有时间去思考了很多。

这个博客是我忙里偷闲搞出来的。想过很多方案,最终还是采用了Hexo这个框架,配合git私有库自动部署到远程Nginx服务器上。其实也不复杂,只是中间踩了不少坑。不过这也让我对其中的很多方面有了更深的了解。从域名和vps提供商到git的使用,web服务器的选择以及静态网站这个趋势,太多东西要考虑,也让我意识到了还有太多东西要学习。

之所以下决心要自己搭一个博客,起因是看到了以前学长的博客。他的第一篇日志是在他刚踏入大学的时候写的,言语之间还透露出些许的稚嫩。然而现在他已经是一个黑客大牛。时间真的像有魔力一般,改变着我们。回想过去,我也还算好学,但总是太过贪心,觉得人工智能、网络安全、电子技术甚至是量子力学都很有趣,有了什么新的进展也都会去了解一番。说好听点这叫兴趣广泛,但是本质上还是这个时代的通病:焦虑。

所以真的需要一个载体,让我在这学生时代的最后一个暑假好好地沉淀一番。忘掉那些贩卖焦虑的自媒体上的快餐知识,沉静在一个领域里。

在未来比较长的一段时间里,我应该都会投身在大前端这个领域里。暑假是Vue,下学期又有Angular的课程,另外还有一些看了一半的React资料。这么多东西,这下可以在知识多海洋里尽情遨游了^_^。

Talk is cheap, show me the code.

待办事项

  • Gzip压缩
  • HTTPS
  • 重定向优化
  • SEO优化
  • 友情链接

Al73r

我感觉良好,但是,在我内心总有一种厌倦、孤独,有时是种激奋的情绪,这种情绪犹如一头活生生、热烘烘的野兽在我体内骚动。我常忘却了世间,忘却了生命的短暂,忘却了世间美好的感情。我考虑着,要过一种卑鄙无耻的生活,这是我的理想。                 —— 弗朗索瓦丝・萨冈
3 日志
3 分类
RSS
GitHub 简书 E-Mail
© 2020 Al73r | 累计访客:
由 Hexo 强力驱动 v3.9.0
|
主题 – NexT.Mist v7.1.2
0%