0%

https://cn.vuejs.org/tutorial

1
2
3
4
5
6
7
8
9
10
11
12
13
<script setup>
import { reactive, ref } from 'vue'

// 在 script 中声明对象
const counter = reactive({ count: 0 })
const message = ref('Hello World!')
</script>

// 在 template 中调用对象
<template>
<h1>{{ message }}</h1>
<p>Count is: {{ counter.count }}</p>
</template>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<script setup>
import { ref } from 'vue'

const titleClass = ref('title')
</script>

// 使用 v-bind 动态绑定一个对象为 attribute。
// 如 <div v-bind:id="dynamicId"></div> 把 div 的 id 属性动态绑定为 dynamicId

<template>
<h1 :class="titleClass">Make me red</h1>
</template>

<style>
.title {
color: red;
}
</style>

这部分设涉及 CSS,todo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<script setup>
import { ref } from 'vue'

const count = ref(0)

function increment() {
count.value++
}
</script>


// 设置监听 DOM 事件
// v-on:click="handler" 或 @click="handler",handler 是 script 中定义的函数
// click 是最简单的点击的监听。可以用 click.xxxx.xxxx 指定更丰富的事件,以及 keyup 监听按键事件等
<template>
<button @click="increment">Count is: {{ count }}</button>
</template>

vue 提供了v-if v-for 等逻辑类 attribute。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<script setup>
import { ref } from 'vue'

const awesome = ref(true)

function toggle() {
awesome.value = !awesome.value
}
</script>

<template>
<button @click="toggle">Toggle</button>
// 条件判断。
<h1 v-if="awesome">Vue is awesome!</h1>
<h1 v-else>Oh no 😢</h1>
</template>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
// todo list demo

<script setup>
import { ref } from 'vue'

// 给每个 todo 对象一个唯一的 id
// 这里使用了 js 原生的变量定义,与 vue 的 ref 有哪些差异?
let id = 0

// 注意:用 vue 的 ref 功能创建的对象要使用 .value 访问
const newTodo = ref('')
const todos = ref([
{ id: id++, text: 'Learn HTML' },
{ id: id++, text: 'Learn JavaScript' },
{ id: id++, text: 'Learn Vue' }
])

function addTodo() {
todos.value.push(
{ id: id++, text: newTodo.value }
)

newTodo.value = ''
}

function removeTodo(todo) {
todos.value = todos.value.filter(function(item) { // js 的 lambda 功能
return item !== todo;
})
/*
一种语法糖:(item) => item !== todo
*/
}
</script>

<template>
<form @submit.prevent="addTodo">
<input v-model="newTodo" required placeholder="new todo">
<button>Add Todo</button>
</form>
<ul>
<li v-for="todo in todos" :key="todo.id">
{{ todo.text }}
<button @click="removeTodo(todo)">X</button>
</li>
</ul>
</template>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
<script setup>
import { ref, computed } from 'vue'

let id = 0

const newTodo = ref('')
const hideCompleted = ref(false)
const todos = ref([
{ id: id++, text: 'Learn HTML', done: true },
{ id: id++, text: 'Learn JavaScript', done: true },
{ id: id++, text: 'Learn Vue', done: false }
])

const filteredTodos = computed(() => {
// 根据 `todos.value` & `hideCompleted.value`
// 返回过滤后的 todo 项目
return hideCompleted.value ? todos.value.filter((t) => !t.done) : todos.value
})

function addTodo() {
todos.value.push({ id: id++, text: newTodo.value, done: false })
newTodo.value = ''
}

function removeTodo(todo) {
todos.value = todos.value.filter((t) => t !== todo)
}
</script>

<template>
<form @submit.prevent="addTodo">
<input v-model="newTodo" required placeholder="new todo">
<button>Add Todo</button>
</form>
<ul>
<li v-for="todo in filteredTodos" :key="todo.id">
// v-model 语法,自动把 checkbox 的结果绑定给 todo.done
<input type="checkbox" v-model="todo.done">
<span :class="{ done: todo.done }">{{ todo.text }}</span>
<button @click="removeTodo(todo)">X</button>
</li>
</ul>
<button @click="hideCompleted = !hideCompleted">
{{ hideCompleted ? 'Show all' : 'Hide completed' }}
</button>
</template>

<style>
.done {
text-decoration: line-through;
}
</style>

vue 提供的 computed 功能可以动态监测元素的更新。

这体现“声明式”和“命令式”的区别,声明式编程可以编写组件功能,而如何更新状态这类问题交给框架实现。想起了高程用 EasyX 写 GUI 的恐惧。

具体地,在“点击 hideCompleted”和“完成一个 todo”时,computed 都会更新。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
<script setup>
import { ref, onMounted } from 'vue'

const pElementRef = ref(null)

// 在挂载时运行 onMounted 函数
onMounted(() => {
pElementRef.value.textContent = 'mounted!'
})
</script>

<template>
<p ref="pElementRef">Hello</p>
</template>

设计“声明周期”概念,todo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<script setup>
import { ref, watch } from 'vue'

const todoId = ref(1)
const todoData = ref(null)

async function fetchData() {
todoData.value = null
const res = await fetch(
`https://jsonplaceholder.typicode.com/todos/${todoId.value}`
)
todoData.value = await res.json()
}

fetchData()

watch(todoId, fetchData)
</script>

<template>
<p>Todo id: {{ todoId }}</p>
<button @click="todoId++" :disabled="!todoData">Fetch next todo</button>
<p v-if="!todoData">Loading...</p>
<pre v-else>{{ todoData }}</pre>
</template>

watch监视一个值(ref类),在变化时调用后面的函数。

当然,用之前的dom事件监视的方法也可以实现一样的功能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<script setup>
import { ref, watch } from 'vue'

const todoId = ref(0)
const todoData = ref(null)

async function fetchNextData() {
todoId.value++
todoData.value = null
const res = await fetch(
`https://jsonplaceholder.typicode.com/todos/${todoId.value}`
)
todoData.value = await res.json()
}

fetchNextData()

</script>

<template>
<p>Todo id: {{ todoId }}</p>
<button @click="fetchNextData" :disabled="!todoData">Fetch next todo</button>
<p v-if="!todoData">Loading...</p>
<pre v-else>{{ todoData }}</pre>
</template>

这段代码中出现的 async await 等用于实现异步,即产生一个新的子线程,与其他进程同时运行。

由于 fetch 其他网页的信息有延迟,在这段时间内还有其他组件需要维护,所以需要使用异步。

1
2
3
4
5
6
7
8
<script setup>
import ChildComp from './ChildComp.vue'
</script>

<template>
<!-- render child component -->
<ChildComp />
</template>

import。我们c++23怎么你了?

1
2
3
4
5
6
<!-- ChildComp.vue -->
<script setup>
const props = defineProps({
msg: String
})
</script>

子组件中的 prop 类似于参数,通过defineProps 定义。defineProps 是宏函数,使用时无需导入。

父组件可以按如下方法向子组件传入 prop 的具体值:

1
<ChildComp :msg="greeting" />

子组件不能改变 prop 的值。不过有 default 定义缺省值等操作,todo

除了接收 props,子组件还可以向父组件触发事件:

1
2
3
4
5
6
7
<script setup>
// 声明触发的事件
const emit = defineEmits(['response'])

// 带参数触发
emit('response', 'hello from child')
</script>

这里 emit 的第一个参数是事件名称,而之后的参数会传给父组件。

在父组件中:

1
<ChildComp @response="(msg) => childMsg = msg" />

@response 触发了事件,之后调用引号内的匿名函数。

这段逻辑有点复杂。

最后一个是插槽 slots,用于直接向子组件传递 html 代码(模板片段)

在子组件中:

1
2
3
4
<!-- ChildComp.vue -->
<template>
<slot>Fallback content</slot>
</template>

而在父组件中,若像这样不传递任何内容,则会显示默认的 Fallback content

1
<ChildComp></ChildComp>

而把父组件改成这样:

1
2
3
4
5
<ChildComp>
<h1>
Message: {{ msg }}
</h1>
</ChildComp>

中间的 <h1> 也会一起传入,显示一个硕大的 Message。因此其能够传递 html 代码,而不仅是基于 js 的信息。


done!

是一种平衡树。设阶数 mm(即 mm 叉树),则每个节点含 m1m - 1 个元素。

插入时都是加在叶子节点内。当一个节点超过 m1m - 1 个元素,取中间一个向上推,左右两边分开作为儿子。

这保证了每个节点至少含有 (m1)/2(m - 1) / 2 个元素,较为“饱满”。同时这个分裂操作保证左右形态是对称的,维持树高在 log 级。

而对于删除操作,如果删除的元素位于非叶子(internal)节点,可以与其前驱或后继交换,转换为删除叶子上元素。

在删除后,若“至少含有 (m1)/2(m - 1) / 2 个元素”的限制被打破,可以从同层级的左右节点移元素过来;若左右节点也不足,则该节点与左右节点之一合并。

左节点、父亲、当前节点的大小分别有如下上界:

(m1)/2,1,(m1)/21(m - 1) / 2, 1, (m - 1) / 2 - 1

故合并之后的节点不多于 m1m - 1 个元素。

以上的做法维持了 B-Tree 的两条限制:节点大小、树形态。于是可以证明复杂度是 log 的。


B-Tree 算法比我之前见到的平衡树有趣,但似乎竞赛中少有使用。在工业界 B-Tree 有所应用,如文件系统和数据库等。

好难写。唉,DS。

接下来就是软件部分了。

6

汇编编译器。

C(计算)指令有固定格式,直接替换;A(A 寄存器声明)和 L(标签)指令包含自定义符号,可以用 hash table 维护。

注意 L 指令定义的标签是全局生效的,可以在前文跳转到后文定义的标签。因此采用 2-pass,即读取两遍,先后处理 L 指令、A / C 指令。

我用 C 语言完成了这个项目。写 C 需要考虑很多细节问题,我没有系统学过,写得很不规范。之后得补课了。

1 ~ 5 是硬件部分。

1

用 nand 实现所有逻辑门。

硬件模拟基于 HDL 语言,写完在 HardwareSimulator 里跑测试。

一些 HDL 的语法:

1
2
3
4
// 输入常量
And16(a = a, b[0..14] = false, b[15] = true, out = out);
// 输出多个数据
Mux16(a = a, b = b, sel = sel, out = out, out = outCopy);

2

用门实现运算。

先实现两个 bool 的加法 HalfAdder,以及三个 bool 的 FullAdder。这两个门的功能是:输入若干 bool,输出当前位和 sum 和进位 carry。

有了 FullAdder 就可以实现多位的进位加法。

在此基础上,ALU 一个元件可以实现多种运算(主要是加减和位运算等),见 P36。至于乘除法等运算,会在更高层次实现。

3

主时钟计时,控制所有元件的状态在周期内不变。

DFF 是把前一时刻的输入作为此时输出的元件,即 $ out(t) = in(t - 1) $。它实现数据随周期传递。

结合 1 中的 Mux 和 DMux 门可以实现寄存器。多个寄存器并列,就是内存了。

4

机器语言、汇编语言。

Hack 有三个直接访问的寄存器:

  • A:地址。
  • M:内存。
  • D:用于中转。

Hack 机器语言每条指令 16 位,分为跳转和计算两类:

  • 跳转(A 指令):以 0 开头,后面若干位表示地址。执行指令后,跳转到指定的地址,并把该地址赋给 A,指向的内存赋给 M。
  • 计算(C 指令):以 1 开头。执行时,默认已经用 A 指令指定了 A 和 M。
    • 2 ~ 3:第 2、3 位不使用。值统一赋 1。
    • 4 ~ 10(comp):计算法则由 ALU 指定。第 4 位指定参与计算的寄存器,5 ~ 9 位是给 ALU 的参数。
    • 11 ~ 13(dest):分别记录计算结果是否要赋给 A M D。
    • 14 ~ 16(jump):计算完成后,跳转(goto)到指定语句的条件。
      如果不跳转,就执行下一行;如果跳转,其目标语句要先用 A 记录。
      可以看到,C 指令实际上集成了计算、赋值、goto 的功能。

对于 I/O,提供键盘(输入)和屏幕(输出),其数据记录在内存中。程序中常数的输入则要用以下方法:

1
2
3
4
5
@1234
D=A // 把 D 设为 1234

@1
D=-A // 把 D 设为 -1

用助记符编码机器语言:

  • 跳转(A 指令):@address
  • 计算(C 指令):dest = comp; jump,jump 和 dest 常会省略其一,例如:
    1
    2
    3
    4
    5
    @address
    D=A+1 // 只计算不跳转

    @LOOP
    0;JMP // 只跳转不计算

这就是 Hack 上的汇编了。更多语法内容直接看程序。

5

体系结构。

冯诺依曼结构:数据输入输出 CPU,而 CPU 与内存交互,即为计算机。这种结构能够“存储程序”,即把程序作为数据的一部分,从而自由执行任意功能。

CPU 处理的对象是机器语言。其功能可以简单描述为:

  • 输入:要实现的功能。包含一行 16 位指令 instruction,内存中的数据 inM,复位 reset。

  • 输出:对内存的操作,包含计算结果 outM,是否修改内存 writeM(bool),地址 addressM。同时,为了实现 jump 等功能,还输出一位 pc 指定下一条指令。

至于其内部实现,主要使用的模块是 ALU 和 A、D 两个寄存器。

我们注意到一点:A 寄存器同时处理指令位置和数据位置,二者含义不同。这部分内存是只读的指令内存(ROM),需要事先写好机器码。也就是说,在硬件层是不支持“存储程序”的,没有可编程性,或许这就是叫做硬件的原因。

还剩一个 reset 接口并没有分给 RAM 或 ROM,它类似电源键。于是有了最高级抽象级别的芯片 Computer。Computer 是一个时序芯片,通过 reset 回到初始状态,通过键盘输入与屏幕输出与外界交互,通过 ROM 编程实现功能。

upd 20240707:

隔太久全忘了,已放弃。

孩子们,我回不来了。

总结大一上做的事。

Missing Semester

介绍常用工具的入门课。视频大部分看了,作业太难了只选做了一些。

熟练运用工具需要在实践中大量训练,这门课的主要意义在于拓展视野,以后遇到问题的时候能迅速学习正确的工具解决问题。“正确”除了实用之外,也有些审美与习惯的考量。比如终端操作,统一管理 dotfile,这些是符合程序员审美与习惯的方式,也更为高效。

浙大有学生开了类似的课,还介绍了 markdown、LaTeX 等,也符合这样的特征。我还不习惯 LaTeX,markdown 倒是挺熟的,用 marp 插件做 ppt 很方便。之前和学生组织说大一下我也想开类似的课,现在觉得纯属发电,毫无可行性且我jb谁啊。

CS61A

今天做完了 CS61A 的最后一个 HW,这篇本来是叫 CS61A 总结的。

这门课断断续续从 11 月做到现在终于结束了,基本上是有空想起来就做几题的状态,中间还有期末前一个月也没怎么碰。这门课质量确实很高,对得起网上的好评;但网上评论所说的这门课带来的震撼我却没有感受到,也没有全神贯注地投入其中,感觉课程内容挺平淡的。

CS61A 涉及了 python、scheme 和 SQL 三种语言,内容书上说主要是关于抽象(abstraction 不是网络用语)。我并不完全理解“抽象”的含义,但我想这涵盖了几个方面:

  • 过程抽象。包括第一章的内容,以及后面的 scheme 函数式编程等。python 足够高级(语言上的),能够很方便地操作函数,一些 lambda 操作和 iterator 等内容很有意思。scheme 有点折磨。
  • 数据抽象。包括第二章的链表与树等。这部分比较平凡。
  • 围绕抽象的设计原则。包括面向对象等,写完 project 后加深了一些理解。
  • 编译。是挺抽象的?

CS61A 的前身是 MIT 的 SICP,所以也可以这样解释,Structure 程序的结构,Interpretation 编译原理,加上作为入门课的 python 语法和 SQL 等内容。

我个人觉得内容平淡则可能是竞赛的影响,一些内容以前见过。思维上确实简单了点,但很多旧思维模式其实是有害的。写了这么多年的 C with STL 并没有什么设计或者架构可言,我也不会写实际中具有一定规模的代码,甚至还有卡常解封装等坏习惯。比起在平地盖楼的神奇,拆除违章建筑是很无聊的过程,不过我相信这样的转变有意义。

名义上是完结撒花了,但也有很多偷懒,中间有些 Lab HW 跳过了,project 中 cat 没做,考试题也没做,当然这些意义并不大;留了一些遗憾,scheme 还不够熟悉,书和早期的几节课没认真看。另外 CS61A 和真正的 SICP 是有区别的,如果有时间我想看看原版课和原著,这就不知要拖到什么时候了。

CS50x

入门通识课,选了几节课看。

全栈当然很有吸引力,但无穷无尽的配环境、读文档、找 API 是折磨人的。还有某国特色 GFW 拦路,一般这时候心态就崩了。

学业

GPA 全寄,令人感叹。但 CS61A 告诉我们:Don’t Compare. 什么自我安慰(

课内没有做什么有意义的事,除了这个建模做的船挺酷的。

计划

勉强算是出新手村了,之后能学的方向有很多。

  • CS61B:java、算法与数据结构
  • CS61C:C、汇编、OS。OS 和计算机组成的资料还挺多的,比如更入门的 Nand2Tetris,或者更难的直接看 CSAPP
  • Python 全栈:很多应用方面的,并不紧急,但 py 到用时方恨少。想写个脚本抢课。
  • AI:机器学习。这年代可以算常识,总要学一点。

……以及其他方向,我也想玩单片机,想了解一点 CTF,想做游戏等等似乎不务正业的事。时间和精力是有限的。

Scheme

CS 61A Scheme Specification

Scheme Built-In Procedure Reference

函数式。

在编程语言的语境中的“函数”含义与数学中的函数有区别,比如返回值可以是 void,大部分时候程序中的函数是要实现一些功能的。在 OOP 中这就直接叫做 method 而非 function 了。

值得注意的是 scheme 中的 define 是不含 return 的,只要列出一个值就是其结果,不存在“返回值”的概念。因此函数式更强调的是数学中的函数。scheme 中描述序列的 list 也不是像数组一样对内存的操作,而是基于 Pair 建立的类似于括号序列的结构。

Eval / Apply

Eval 是分析表达式(evaluate?),Apply 是实现函数。

对于一段表达式,编译器先调用 Eval,分析其 operator(操作,即对应的函数)与 operand(即参数),之后 Eval 调用 Apply。在 Apply 运行过程中,需要分析 operand,这会调用 Eval。因此它们相互调用,一轮相当于解开一次括号,就是编译器的逻辑。

这有点像自然语言的谓语宾语,嵌套就相当于从句。还有一个重要的概念是环境,即 python 中的 frame 或者作用域 scope 等,环境按调用的从属关系组成树状结构。有 Lexical / Dynamic Scope 的区别,略。

形式化描述大致如下:

1
2
def Eval(expression, environment):
...
1
2
def Apply(operator, expression):
operator(expression)

Lab 11

Q4 要求实现 define 的一部分功能。由于 define 的内容有数据、运算和之前 define 的表达式等,需要递归把 pair 展开再分别 calc_eval。要时刻明确 Eval 的对象是表达式,数据、运算、Pair 等都需要考虑应不应该 Eval。

Eval 的实现上,要充分利用 python 的函数,如果参数是运算符,返回就应该是函数,以保持功能一致。其实完形填空题不这样写大概率是过不了的,如果自作主张不 Eval 运算直接 Apply 就寄了。

list 的实现比较抽象,需要特别处理 nil

这部分内容还是有点难度,然后课程给的实现也很奇怪,可能到 project 才能解锁完全体吧。

介绍了网络原理、html、CSS、JavaScript 基础知识。

写 Lab 因为不会用 html 就嗯学了一点 js 的语法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
const CHECK = new Array (
new Array (1, 0, 0),
new Array (0, 0, 1),
new Array (0, 1, 0),
);

for (let index = 1; index <= 2; index++) {
for (let choice = 1; choice <= 3; choice++) {
let correct = CHECK[index - 1][choice - 1];
let button = document.querySelector(
'#choice_' + index.toString() + '_' + choice.toString()
);
let question = document.querySelector(
'#section_' + index.toString()
)
console.log(question.innerHTML);
if (correct) {
button.addEventListener('click', function() {
button.style.backgroundColor = 'green';
question.innerHTML += '<br>Correct';
});
} else {
button.addEventListener('click', function() {
button.style.backgroundColor = 'red';
question.innerHTML += '<br>Incorrect';
});
}
}
}

拿数组存答案有点抽象了,这个用 html 的 class 就行。

网页,但是格式寄了

Week 7

Iterator

例如遍历序列,for x in s 会创建 iterator,不妨记为 i = iter (s)。不断取 x = next(i)x 会依次赋值,而 i 会向后移动。

到达结尾报错 StopIteration。可以用 try 处理异常:

1
2
3
4
5
try:
while True:
x = next(i)
# ...
except StopIteration

即为 for 的等价实现

Generator

一类生成 iterator 的函数。

函数内部 yield 返回的值会依次被迭代。

generator 的运行是懒惰的,调用一次 next 会执行到下一次 yield。故 generator 可以 yield 无穷次,生成不会停止的 iterator。

可用 yield for gen()yield generator gen() 的所有迭代值。

Week 8

面向对象。

Week 5

Cat

抽空写完。

Week 6

2.3

1
seq[start:end:step]

2.4

list 的直接引用传递的是地址。

1
2
a = []
b = a

此后 a, b 会同步改变。

若想要只传递值,可以用 list(s)s[:]s 复制一份。

tuple 是不可变的序列,用括号表示。但 tuple 中的序列元素能改变。

数据分为可变(mutable)与不可变,二者有本质区别,比如可变数据无 hash 值,因此不可作为字典 key。可变数据拥有身份,isis not 是判断身份是否一致的函数。


局部状态这部分写的极为抽象。

1
2
wd = make_withdraw(12)
wd2 = wd

这绑定的是同一个函数。

只有函数调用才能引入新帧。

抽空看完 2.4。