CPP_头文件互相包含

CPP_头文件互相包含

[!Error]+
编写 C++ 代码时偶尔会遇到两个类需要相互引用的情况,如果在 h 文件中相互包含会导致 “has not been declared” 等声明问题,此时需要使用前置声明的方式来解决该问题

Code Example

以一个实际场景为例:假设有工具类 Search Helper 提供一系列搜索功能,还有另一个 ObjectAttr 定义一系列操作对象的属性,在 ObjectAttr 中需要引用 SearchHelper 提供的某个基础函数,而 SearchHelper 中部分针对 ObjectAttr 设计的某些功能则需要将 ObjectAttr 中的类作为入参,此时我们可能会在 h 文件中相互引用;

在 obj_definition.h 中有 GetSelfNearObj 函数,其过程中需要调用 search_helper 中的 GetDistObj 函数进行辅助运算

1
2
3
4
5
6
7
8
9
10
11
#include "util/search_helper.h"
namespace object_info{
class ObjectAttr{
public:
LocationType location;
int ObjId;
int NearObjId;
private:
void GetSelfNearObj();
};
}

在 search_helper.h 中有以下函数定义

1
2
3
4
5
6
7
#include "base/obj_definition.h"
namespace search_helper{
class SearchHelper{
public:
int GetDistObj(const object_info::ObjectAttr & obj1, const object_info::ObjectAttr & obj2);
};
}

这种情况下编译会出现未定义,未声明,not a type 之类缺少类型说明符的错误。

1
error: ... has not been declared

Fix & Analysis

上述错误是由于两个文件构成了循环依赖,类 SearchHelper 依赖于 ObjectAttrObjectAttr 又依赖于 SearchHelper;因此编译器会报错导致无法通过编译;

在这种情况下,可以通过前向声明的方式来解决这种问题,即不去进行循环引用,在search_helper 中不 #include obj_definition.h,但是对需要的 ObjectAttr 进行一个空声明,然后在 .cpp 中 #include obj_definition.h 具体如下,将 H 文件修改为;

1
2
3
4
5
6
7
8
9
10
namespace objct_info{
class ObjectAttr;
}

namespace search_helper{
class SearchHelper{
public:
int GetDistObj(const object_info::ObjectAttr & obj1, const object_info::ObjectAttr & obj2);
};
}

在对应的 .cpp 文件中添加对 obj_definition 的包含,这样在编译过程中,ObjectAttr 会被自动链接到正确的定义;

1
2
3
4
#include "base/obj_definition.h"

// origin cpp file below...
// ...

上述例子展示了不同命名空间下的情况,这里秉持的原则就是不同文件中对类的前置声明要保持一致,无论是命名空间、参数、类型等,在同一个命名空间中的情况要更简单,可以以此类推,这里不再赘述;

Reference

  1. c++ 头文件互相包含问题
  2. c++ 类声明 类前置声明范例

LearnWeb21-JS06-异步JS

LearnWeb21-JS06-异步JS

[!summary]+
如果页面的功能较为复杂,且涉及到了从服务端获取数据等操作,如果简单的使用同步编程,等待一个个任务按顺序执行,由于网络或者某些时间复杂度较高的操作,导致网页加载时间过长,或者使用逻辑不合理(加载某些资源的同时无法进行浏览等),因此异步编程的特性在 web 端是十分重要的。

通过异步编程使一个长时间运行的任务运行的同时能够对网页做出其他的操作和对其他事件做出相应,而不需等待该任务完成,以下的这些功能就是最常见需要异步完成的;

  • fetch 发起 http 请求
  • getUserMedia() 获取用户的摄像头和麦克风
  • showOpenFilePicker() 请求用户选择文件以供访问。

基于事件处理程序实现异步

事件处理的逻辑实际上也是一种接近异步编程的方式,对应的函数不是即时执行,而是等事件被触发后在进行调用。一些早期的异步 API 就是这样使用事件的。

一些早期的异步 API 就是这样来使用事件,例如 XMLHttpRequest 可以使用 JS 向远程服务器发起 HTTP 请求,这类网络请求操作都会比较耗时,因此通常会使用异步,以下面的例子进行后续说明;

1
2
3
4
5
6
7
8
9
10
11
12
const log = document.querySelector(".event-log");
document.querySelector("#xhr").addEventListener)("click", () => {
log.textContent = "";
const xhr = new XMLHttpRequest();
xhr.addEvenetLister("loadend", () => {
log.textContent = `${log.textContent} 完成, 状态码: ${xhr.status}`;
});
xhr.open("GET",
"https://URL/dir/file.json");
xhr.send():
log.textContent = `${log.textContent} 请求已发起\n`;
});

xhr 按钮点击后,声明一个 XMLHttpRequest 对象,并监听其 loadend 事件,然后发送请求,并将字符串修改为请求已发起,该字符串会在触发了 loadend 加载完了请求事件后改为,已完成。

事件处理程序本身是一种特殊类型的回调函数(函数作为参数传递到另一个函数),多层回调函数的嵌套会导致代码难以理解和 debug,因此后面大多数 API 不在使用回调函数去处理异步的情况。

Promise 现代 JS 异步编程的基础

Promise 是现代 JavaScript 中异步编程的基础。它是一个由异步函数返回的对象,可以指示操作当前所处的状态。在 Promise 返回给调用者的时候,操作往往还没有完成,但 Promise 对象提供了方法来处理操作最终的成功或失败。

一个基于 Promise 的 API,异步函数会启动操作并直接返回一个 Promise 对象,可以将后续的处理函数附加到该对象上,当操作完成时(成功、失败),执行对应的处理函数。

以 fetch() 为例

1
2
3
4
5
6
7
8
9
10
const fetchPromise = fetch(
"https://mdn.github.io/learning-area/javascript/apis/fetching-data/can-store/products.json",
);
console.log(fetchPromise);

fetchPromise.then((response) => {
console.log(`已受到响应 ${response.status}`);
});

console.log("已发送请求...");
1
Promise { <state>: "pending"}
  1. 调用 fetch() 将返回的 promise 存储到 fetchPromise 变量
  2. Promise 的变量输出结果如下
  3. 将处理函数传递给 promise 变量的 then 函数,当 fetch 操作成功的时候,promise 就会调用对应的处理函数。

整体的处理逻辑还是比较清晰的,关键是使用 promise 变量的 then 函数去处理 fetch 的各种不同结果。

链式使用 Promise

在前面通过 fetch 获取 response 对象的时候,我们需要使用对应的 json() 方法将其转换为 js 专属的对象,这里的 json 实际上也是一个异步方法,由此我们可以链式的实现异步如下:

1
2
3
4
5
6
7
8
9
10
const fetchPromise = fetch(
"https://mdn.github.io/learning-area/javascript/apis/fetching-data/can-store/products.json",
);

fetchPromisze.then((response) => {
const jsonPromise = response.json();
jsonPromise.then((json) => {
console.log(json[0].name);
});
});

这种情况在多层嵌套的时候也会堆叠得很难理解,但是相比于回调函数,每一级的回调都会有个 promise 的即时返回值来指示对应异步函数中的完成状态。

由于 promise 是一个即时返回值,因此上述的代码可以简写为:

1
2
3
4
5
fetchPromise
.then((response) => response.json())
.then((data) => {
console.log(data[0].name);
});

不必在第一个 then 中调用下一个 then,可以直接返回对应的 promise 对象,对对应的 promise 对象调用处理即可,这样可以避免多级缩进叠加;

处理异常返回值

promise 中的状态值进行检查,如果状态码不是 ok 就需要对应的抛出错误:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const fetchPromise = fetch(
"https://mdn.github.io/learning-area/javascript/apis/fetching-data/can-store/products.json",
);

fetchPromise
.then((response) => {
if(!response.ok){
throw new Error(`HTTP 请求错误 ${response.status}`);
}
})
.then((json) => {
console.log(json[0].name);
});

此外由于这种异步的函数返回机制,如果要按照上面的方式逐个进行错误处理非常的困难,需要在每个嵌套层中进行处理,为了避免这种麻烦,Promise 对象提供了一个 catch() 方法(类似 then),当调用成功时触发的是 then 而调用失败了就会调用 catch 中定义的处理函数。

catch 添加到 Promise 的末尾,他可以在任何异步函数失败的时候调用,下面是一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const fetchPromise = fetch(
"bad-scheme://mdn.github.io/learning-area/javascript/apis/fetching-data/can-store/products.json",
);

fetchPromise
.then((response) => {
if (!response.ok) {
throw new Error(`HTTP 请求错误:${response.status}`);
}
return response.json();
})
.then((json) => {
console.log(json[0].name);
})
.catch((error) => {
console.error(`无法获取产品列表:${error}`);
});

Promise 的状态值

需要注意 promise 对应的成功和失败的含义随着 API 的不同而不同,例如 fetch 认为服务器返回一个错误如(404 not found)时请求成功,但如果网络错误阻止请求被发送,则认为请求失败。

Status Desc
Pending 待定 还在请求中,尚未有确定的结果,也是初始状态
fulfilled 已兑现 操作成功的标准返回,后续进入调用 then 的逻辑
rejected 已拒绝 操作失败的标准返回,后续进入调用 catch 的逻辑

有时用已敲定(settled)来同时表示已兑现和已拒绝;如果一个 Promise 已敲定,或者他被”锁定”以跟踪另一个 Promise 的状态,那么就是已解决(resolved)的。

组式使用 Promise (合并使用)

当操作由多个异步函数组成,如果需要串行完成那就需要 promise 链,如果需要组合使用多个 promise,相互之间不依赖但是需要所有 promise 都实现的情况,可以考虑合并多个异步函数的使用。

使用 Promise.all() 接受一个 Promise 数组,并返回一个单一的 Promise,使用该操作通过合并来简化对一批 promise 的处理,由 promise.all() 返回的 promise 有以下的特性:

  • 什么时候调用 then?当数组中所有 promise 都兑现时;
  • 传入 then 的形式式什么?提供一个包含所有响应的数组,顺序与传入 all 的顺序一致;
  • 什么时候拒绝/调用catch?任何一个promise 没有兑现的时候,调用 catch,并提供被拒绝的 promise 抛出的错误;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
const fetchPromise1 = fetch(
"https://mdn.github.io/learning-area/javascript/apis/fetching-data/can-store/products.json",
);
const fetchPromise2 = fetch(
"https://mdn.github.io/learning-area/javascript/apis/fetching-data/can-store/not-found",
);
const fetchPromise3 = fetch(
"https://mdn.github.io/learning-area/javascript/oojs/json/superheroes.json",
);
//const fetchPromise3 = fetch(
// "bad-scheme://mdn.github.io/learning-area/javascript/oojs/json/superheroes.json",
//);

Promise.all([fetchPromise1, fetchPromise2, fetchPromise3])
.then((responses) => {
for (const response of responses) {
console.log(`${response.url}${response.status}`);
}
})
.catch((error) => {
console.error(`获取失败:${error}`);
});

选择使用 Promise (任一)

如果需要一组Promise 中某一个实现即可,这种时候可以使用 promise.any(), 任意一个被兑现时便兑现,仅当所有 Promise 被拒绝的时候才拒绝。

async 和 await

在函数的开头添加 async 关键词可以使得一个函数成为一个异步函数:

1
2
3
async function myFunction(){
...
}

await 关键词则使得我们不再并行执行异步函数,而是在原地等待该异步函数执行完成,也就是讲异步函数当成同步函数来使用,直到其 Promise 相应彻底完成,如下可以将 fetch 改写成同步函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
async function fetchProducts() {
try {
// 在这一行之后,我们的函数将等待 `fetch()` 调用完成
// 调用 `fetch()` 将返回一个“响应”或抛出一个错误
const response = await fetch(
"https://mdn.github.io/learning-area/javascript/apis/fetching-data/can-store/products.json",
);
if (!response.ok) {
throw new Error(`HTTP 请求错误:${response.status}`);
}
// 在这一行之后,我们的函数将等待 `response.json()` 的调用完成
// `response.json()` 调用将返回 JSON 对象或抛出一个错误
const json = await response.json();
console.log(json[0].name);
} catch (error) {
console.error(`无法获取产品列表:${error}`);
}
}

fetchProducts();

这个 fetchProducts() 还是一个异步函数,因此不能按照以下的方法调用:

1
2
const promise = fetchProducts();
console.log(promise[0].name); // “promise”是一个 Promise 对象,因此这句代码无法正常工作

相反的,需要按照 promise 的方式去调用:

1
2
const promise = fetchProducts();
promise.then((data) => console.log(data[0].name));

同样地,请注意你只能在 async 函数中使用 await,除非你的代码是 JavaScript 模块。这意味着你不能在普通脚本中这样做:

你可能会在需要使用 Promise 链地方使用 async 函数,这也使得 Promise 的工作更加直观。

请记住,就像一个 Promise 链一样,await 强制异步操作以串联的方式完成。如果下一个操作的结果取决于上一个操作的结果,这是必要的,但如果不是这样,像 Promise.all() 这样的操作会有更好的性能。

Promise 实战

前面讨论如何使用返回 promise 的 APIs,这一节研究如何实现返回 Promise 的 Apis,这与基于使用 promise 的 APIs 相比,是一个不太常见的任务。参考文献 mdn如何实现基于promise的api | 菜鸟教程JavaScript Promise | 廖雪峰JavaScript

以一个普通的回调转换为 Promise 的例子来说明:

1
2
3
4
5
6
7
8
9
10
const output = document.querySelector("#output");
const button = document.querySelector("#set-alarm");

function setAlarm() {
window.setTimeout(() => {
output.textContent = "Wake up!";
}, 1000);
}

button.addEventListener("click", setAlarm);

利用 Promise 来构造后会变成如下的实现:

1
2
3
4
5
6
7
8
9
10
11
function alarm(person, delay) {
return new Promise((resolve, reject) => {
if (delay < 0) {
throw new Error("Alarm delay must not be negative");
}
window.setTimeout(() => {
resolve(`Wake up, ${person}!`);
}, delay);
});
}

该函数会创建并返回一个新的 Promise,其中需要说明的是,Promise 本身需要两个参数 resolve 和 reject,当执行成功了就会调用 resolve(类似 return),如果失败了,就会自动调用 reject,(throw error 的部分),两者都可以讲任何类型的单个参数传递,具体而言参考后续调用如下:

1
2
3
4
5
button.addEventListener("click", () => {
alarm(name.value, delay.value)
.then((message) => (output.textContent = message))
.catch((error) => (output.textContent = `Couldn't set alarm: ${error}`));
});

可以讲一个函数变成具备原生异步功能的 Promise?由此我们就可以对其使用 await 或者 async 来灵活的决定该 Function 要同步或者异步执行。

Workers 页面线程简介

[!summary]+
Worker使页面能在单独执行的线程中运行一些任务,避免因为一个长期运行的同步任务使整个任务完全没有响应。

一些多线程的 Principle,避免同时访问相同的变量带来的意外等:

  • 主代码和你的 worker 代码永远不能直接访问彼此的变量
  • Workers 和主代码运行在完全分离的环境中,只有通过相互发送消息来进行交互
  • 这意味着 workers 不能访问 DOM(窗口、文档、页面元素等等)

有三种不同类型的 workers,不过该章节只会介绍第一个,其他两个简要的讨论。

  • dedicated workers
  • shared workers
  • service workers

以页面中的质数生成器为例,如果作为同步任务执行,在计算过程中整个页面将会卡住,按照以下的方式来讲计算交付于另一个 worker。

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
// 在 "generate.js" 中创建一个新的 worker
const worker = new Worker("./generate.js");

// 当用户点击 "Generate primes" 时,给 worker 发送一条消息。
// 消息中的 command 属性是 "generate", 还包含另外一个属性 "quota",即要生成的质数。
document.querySelector("#generate").addEventListener("click", () => {
const quota = document.querySelector("#quota").value;
worker.postMessage({
command: "generate",
quota: quota,
});
});

// 当 worker 给主线程回发一条消息时,为用户更新 output 框,包含生成的质数(从 message 中获取)。
worker.addEventListener("message", (message) => {
document.querySelector("#output").textContent =
`Finished generating ${message.data} primes!`;
});

document.querySelector("#reload").addEventListener("click", () => {
document.querySelector("#user-input").value =
'Try typing in here immediately after pressing "Generate primes"';
document.location.reload();
});

通过 worker.postMessage 来和对应的线程传递信息,在对应的 worker 中,可以按照以下的方式来接受和传递信息;

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
// 监听主线程中的消息。
// 如果消息中的 command 是 "generate",则调用 `generatePrimse()`
addEventListener("message", (message) => {
if (message.data.command === "generate") {
generatePrimes(message.data.quota);
}
});

// 生成质数 (非常低效)
function generatePrimes(quota) {
function isPrime(n) {
for (let c = 2; c <= Math.sqrt(n); ++c) {
if (n % c === 0) {
return false;
}
}
return true;
}

const primes = [];
const maximum = 1000000;

while (primes.length < quota) {
const candidate = Math.floor(Math.random() * (maximum + 1));
if (isPrime(candidate)) {
primes.push(candidate);
}
}

// 完成后给主线程发送一条包含我们生成的质数数量的消息消息。
postMessage(primes.length);
}

worker 要做的第一件事情就是开始监听来自主脚本的消息。这通过使用 addEventListener() 实现,它在 worker 中是一个全局函数。在 message 事件处理器内部,事件的 data 属性包含一个来自主脚本的参数的副本。

[!Note]+
备注:要运行此站点,你必须运行一个本地 web 服务器,因为 file:// URLs 不允许加载 workers。参考我们的设置一个本地测试服务器的指导。完成后,你应该可以点击 “Generate primes” 并且使你的主页面保持响应。 如果你在创建和运行这个样例的过程中有疑问,你可以在 https://github.com/mdn/learning-area/blob/main/javascript/asynchronous/workers/finished 查看完成后的版本,并且在 https://mdn.github.io/learning-area/javascript/asynchronous/workers/finished 进行在线尝试。

我们刚刚创建的 worker 被称为 dedicated worker。这意味着它由一个脚本实例使用。

不过,还有其他类型的 worker:

  • SharedWorker 可以由运行在不同窗口中的多个不同脚本共享。
  • Service worker 的行为就像代理服务器,缓存资源以便于 web 应用程序可以在用户离线时工作。他们是渐进式 Web 应用的关键组件。

LearnWeb20-JS05-JSON使用

LearnWeb20-JS05-JSON使用

[!summary]+
本篇是 Mdn 使用 JSON 一文的阅读笔记,对 web 开发中的 JSON 进行了介绍

JSON 简介

JavaScript Object Notation(JSON)是将结构化数据表示为 JavaScript 对象的标准格式,通常用于网页上的表示和传输数据(服务端,客户端),熟悉 JSON 对象的创建,传输,解析,对于 JS 来说也是一门基本功了。

JSON 可以存在单独的文件中,后缀为 .json,同时在进行网络传输时,MIME 类型application/json

基本操作介绍

对 JSON 这类标记语言进行的操作通常就是下面的两种:

  • 将字符串转换为原生对象的过程称为反序列化(deserialization)
  • 将原生对象转换为字符串进行网络传输的字符串的过程则成为序列化(serialization)

因此可以理解为这就是一个 object-string 的相互转换过程,因此在一个语言中如何使用 json 这种标记语言,最核心的就是上述的这两个操作,随着后续的发展,JSON 在除了 js 的其他语言中也被广泛的使用到。

JSON 结构和“语法”

整个 JSON 是一个字符串,其非常类似于 JS 对象字面量的写法(无需命名对象名),且其中仅包含要传输的数据(属性)而不包含方法

一般而言有两种方法来编写 JSON 文件,一种是类似对象字面量(字典),存储一个单体对象的方式;另一种则是 JSON 数组,最外层用数组的形式,在数组里面可以存储多个单独对象。下面分别给出示例:

单对象形式,对象字面量(like 字典),js 中反序列化获取之后就会得到一个对象,使用对象的方法去调用其中的属性即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
{
"squadName": "Super hero squad",
"homeTown": "Metro City",
"formed": 2016,
"menbers": [
{
"name": "Molecule Man",
"age": 29,
"secretIdentity": "Dan Jukes",
"powers": [
"Radiation resistance", "Turning tiny", "Radiation blast"
]
},
{
"name": "Madame Uppercut",
"age": 39,
"secretIdentity": "Jane Wilson",
"powers": [
"Million tonne punch","Damage resistance", "Superhuman reflexes"
]
}
]
}

数组多对象形式,下面这种写法也是一种合法的 JSON,js 中反序列化获取后则会得到一个数组对象,使用下标去索引即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[
{
"name": "Molecule Man",
"age": 29,
"secretIdentity": "Dan Jukes",
"powers": ["Radiation resistance", "Turning tiny", "Radiation blast"]
},
{
"name": "Madame Uppercut",
"age": 39,
"secretIdentity": "Jane Wilson",
"powers": [
"Million tonne punch",
"Damage resistance",
"Superhuman reflexes"
]
}
]

同时 JSON 还有以下的一些编写规范:

  1. 纯数据,只包含属性不包含方法;
  2. 要求字符串和属性名称使用双引号,单引号无效;
  3. 错位的 ,; 都可能导致 json 文件出错,要做好检查,可以使用JSONLint 这样的程序来验证;
  4. JSON 实际上可以使任何可以有效包含在 JSON 中的数据类型的形式,例如,单个字符串或者单个数字也是一个有效的 JSON 对象;

LearnWeb19-JS04-类与对象

LearnWeb19-JS04-类与对象

[!summary]+
JS 中的一切变量皆为对象,可以将对象理解为一个包含相关数据和方法的集合(变量 & 函数)我们也将其称之为属性和方法,就像我们在 python 里做的那样,本篇为 mdn_JS对象基础mdn_JS对象原型 的阅读笔记

如果 面向对象编程基本概念 不太清楚的话,可以看一下这个链接,了解一下下面这些基本概念:derive 派生 | oriented 面向 | polymorphism 多态 | override 重写/重载 | encapsulation 封装 | private 私有 | delegation 委派 |

从声明对象开始

手动声明对象(字面量)

在 JS 中声明一个对象实际上和声明一个字典一样,使用 {} 就可以声明一个对象,{}中可以包含属性甚至函数,下面给出一个例子:

1
2
3
4
5
6
7
8
9
10
const person = {
name: ["aiken", "metis"],
age: 26,
bio: function () {
console.log(`${this.name[0]} ${this.name[1]} now is ${this.age} years old`);
}
introduce() {
console.log(`hello! i'm ${this.name[0]}.`);
}
}

可以看出该声明的对象中,不仅包含属性: name, age, 还包含方法 bio, introduce,可以看出方法存在两种不同的写法,更常用的是第二种简写。

这种手动写出对象的内容来创建的特定对象叫做对象字面量(object literal),与从定义好的类实例化出来的对象不同。

基于函数来批量声明对象

当我们需要批量创建多个同类对象的时候,按照上面的方法来定义就会显得十分麻烦,这个时候我们可以使用函数来批量声明对象。

1
2
3
4
5
6
7
8
9
10
11
12
function createPerson(name, age)
{
const obj = {};
obj.name = name;
obj.age = age;
obj.bio = function(){
console.log(`${this.name} now is ${this.age} years old`);
};
return obj;
}
const aiken = createPerson('aiken','26');
const metisy = createPerson('metis', '25');

通过函数来声明对象的时候:用首先定义一个空对象,然后去修改对象属性和对象的方法,实现批量处理。

“this” 使用和含义

this 指代代码运行时调用 this 的对象本身,这在定义单个对象字面量的时候可能没什么用,但是当我们有多个对象,这样这种时候通过使用 this,就可以使得函数定义是更通用的,就像上面的例子中,aiken.biometisy.bio 都能正确的打印出其年纪和名称。

使用类(构造函数)来声明对象

使用类来声明对象是各种编程语言中最通用的一种声明对象的方式,JS 一切皆为对象的设计思想,使得 JS 中定义类的方式和定义函数的方式实际上是十分相似的,这里主要的区分在于用构造函数声明新对象的时候,我们使用 new 关键字。下面给出一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
function Person(name, age)
{
// 命名类的时候和其他语言一样,使用大写字母开头。
this.name = name;
this.age = age;
this.bio = function(){
console.log(`${this.name} now is ${this.age} years old`);
};
}

const aiken = new Person('aiken', 26);
const metis = new Person('metis', 25);

可以看出,使用构造函数的方式的时候,我们无需指定返回值,但需要使用 new 关键词去声明新的对象。


LearnWeb18-JS03-事件

LearnWeb18-JS03-事件

[!summary]+
web-js 中主要的编程方式就是需要结合browser 的事件和属性来实现对页面的动态控制,事件章节可以说是web 动态编程中的核心部分了,了解主要存在和需要被控制的事件是相当重要的。

常见的浏览器事件

下面列出一些常见的事件类型和具体事件,更多的事件可以参考Mdn,在设计页面的时候可以考虑我们希望获得什么效果来找寻是否有对应的事件来构建对应的动态响应。

事件类型 具体事件
鼠标事件 点击、选择、悬停、拖拽、滚轮、焦点(focus, blur)
键盘事件 按键、剪切板、文本输入
窗口事件 调整大小、窗口关闭
页面事件 加载结束、错误发生、CSS 变换、DOM 事件
自定义事件 点击按钮,表单提交
多媒体事件 视频播放、暂停、结束

在确定了事件之后,就需要对事件附加一个事件处理器(监听器),当事件触发的时候,运行指定的js 代码对该事件做出相应。

事件处理器

[!summary]+
通常而言,我们使用特定的 element 调用 addEventListener(event, function) 添加一个事件监听器,当参数中指定的事件 event 在对应元素上发生,就调用对应的 function 执行相应的变动,如果不是通用的函数,这里经常可以看到使用匿名函数去定义对应的操作。

添加事件处理器

因此对于一个事件而言,实际上包含的操作有以下的几个:找到 DOM 中要操作的对应元素、添加事件监听器、选择对应的事件、定义对应的操作函数;

1
const btn = document.querySelector("button");
1
2
3
4
5
6
7
8
function random(number) {
return Math.floor(Math.random() * (number + 1));
}

function changeBackground() {
const rndCol = `rgb(${random(255)}, ${random(255)}, ${random(255)})`;
document.body.style.backgroundColor = rndCol;
}
1
btn.addEventListener("click", changeBackground);

可以为同一个事件设置多个处理器,也就是添加多个 function,对于添加事件来说其中 addEventListener 是最为通用的,可以绑定多种事件,不过除了 addEventListener 之外,还有一些特殊事件的添加监听的方法,例如 click 事件有一个内联的监听器:onclick

1
btn.onlick = changeBackground;

虽然有一些示例在 html 中绑定事件内联,但是最好还是分开在 js 中绑定更好。


LearnWeb17-JS02-Intro

LearnWeb17-JS02-Intro

JS 基础语法

该部分的学习除了 MDN 的相关知识,会结合数据结构的内容来进行学。

语言的第一印象和 python 有许多相似的地方。语法上可能大差不差,在循环和其他一些变量上又有一些和 C#,CPP 相似的地方。

Basic Rules 基础规则简介

首先介绍基本的编写规则:如注释、缩进规则、变量规则等…

  • 注释:CPP 相同使用 ///* */ 进行行/块注释。
  • 句尾 ; :单行单条语句结束可以无需 ; (但为了规范和明确可以加上),同行多个语句可以用 ; 进行语句的区分。

变量

声明变量使用:var, let, const 三个关键词;其中 letconst 是相似的,用于声明块级作用域的局部变量,只有在声明的位置之后才能使用,唯一的区别在于 const 声明的常量不能用赋值运算符直接更改,(但如果是个对象,它的属性可以被添加、更新、删除)

变量定义:(另起一个 Paragraph 表示尊重) JS 为非强类型语言(即类似 python 而非 cpp),为动态类型语言,变量声明无需指定类型。但有以下几个注意的事项:

  • (不推荐)不带关键字的变量声明会默认为全局变量。
  • (推荐)可以使用 letvar 关键字定义变量。
  • (最推荐)最推荐使用 let 进行变量的声明,var 对变量定义位置的要求更低,使用 var 编写可能会方便,但是在后续维护和阅读中可能会体验很差。使用 let 然后和别的语言一样声明和使用变量。
  • 可以使用 typeof 来检查变量类型

其中对于变量的定义上,Var 和 let 的详细差别可以参考 var与let的区别var变量提升,简单的讲 var 的定义会先于所有的语句执行,声明一个全局的变量。下面简单介绍一下各种不同的数据类型:

对象:JavaScript 里一切皆对象,一切皆可储存在变量里。这一点要牢记于心,字典也是一种对象,定义方式和 Python 一致:

1
2
let dog = { name: "Spot", breed: "Dalmatian" };
dog.name // 访问name属性。

数值: JS 只有一种数值类型 Number,不需要像 cpp 执行 int 和 float 之类的转换,说到数值类型,就需要对基本的运算符进行说明,这里简单列一下支持的一些运算符类型

Idx Col1 Col2 Col3 Col4 Col5 Colo6
基础 + - * / % **
自增自减 *= (var)++ (var)— += -= /=
比较 !== === < > <= >=

基本运算符:和大多数语言的基本运算符保持一致,这里需要特殊说明的只有相等的判断符,区别于其他语言,JS 中使用三个等号来判断相等;

1
2
if a == b:
print("a is equal to b")
1
2
3
4
if (a === b)
{
console.log("a is equal to b");
}

这里的函数定义和条件判断都更接近 cpp,使用{}将代码块来区分,而非单纯使用缩进。


《定投十年财务自由》读书笔记

《定投十年财务自由》读书笔记

[!summary]+
本篇读书笔记主要记录书中一些投资理财相关的概念、一些思维方式、一些典型的策略等等,用于自己后续理财的基础。

“投资理财越早开始越好,无需拘泥于启动资金的多少,时间才是投资理财中最重要的增长因子”。


财务自由

需要多少钱才能财富自由

4%法则(William Bengen):通过投资一组资产,每年从退休金中提取不超过 4%的金额用来支付自己的生活所需,那么直到自己趋势,退休金都花不完。

因此如果每年需要 40w 的开销,那么就需要超过 1000w 的资产来实现一个比较稳定的财富自由,按照自己每年的消费可以进行简单的换算。

如何避免财富缩水

[!summary]+
财富缩水主要的原因就在于通货膨胀,对通货膨胀有所了解才能更好的从通货膨胀中保护自己的财富。

很多时候通货膨胀看的是 CPI(居民消费价格指数),最近几年国内的 CPI 同比增长一年为 2%~3%,也就是说物价每年上涨 2%~3%,但是实际上只看 CPI 是不够的,这是因为 CPI 实际上仅包含了必需消费,而如果要保证生活质量的水平不变,需要跑赢的是”可选消费通货膨胀率”。

这是因为任何一个社会,优质的教育和医疗资源的价格增长速度是比较显著的高于 CPI 的,因此可选消费指数才是我们要跑赢的目标。而这个增长速度则通常和 M2 的增长速度相关。

M2 是广义货币的量,代表社会广义货币的增长速度,最近三年的同比大概在 8%~9%,但是该指数并非完全和可选消费指数相等同。

综合而言,我们通常需要考虑的膨胀指数应该介于 CPI 和 M2 增速之间,因此资产增值的速度应该在 3%~9%之间,才能避免财富缩水,如果考虑一些不稳定时局的原因,可能还要存一些避险资产例如黄金。

通常来说,只要长期投资债券基金(包括相关的理财产品)是可以跑赢 CPI 的,债券通常的长期平均收益都在 6%上下,但若想要获得更高的收益可能就得上股票这种高风险高收益的了。


LearnWeb15-Web实战01-首页设计

LearnWeb15-Web实战01-首页设计

[!summary]+
设计个人首页作为 HTML 和 CSS 的实战,主要设计以下的几个部分,导航栏,侧边栏,logo,页脚,背景,以及一个简单的个人介绍页面,首先不考虑使用框架和库,仅对整体流程做熟悉,使用纯 HTML 和 CSS 进行基础实现。后续考虑使用框架和组件库进行重写。

CheckList for Web Design : Using this website to checkout those element u missed in your design. Prepare for those elements. Get Ready and Start.

image.png

Buger 下拉菜单(侧边菜单)

https://alvarotrigo.com/blog/hamburger-menu-css/

flex 布局设计

image.png

使用 html css 进行布局时,如果计划使用 flex 等布局,避免无谓的划分子集的 div 等 box,避免多余的额外对齐工作,原型和草稿确认布局是很重要的。例如上述的导航栏,分成左右两个 div 即可,如果将搜索框等独立出来,在后续对齐和确认间距的时候会多出很多麻烦。

可以缩减为仅使用一个 div,使用 flex 的布局技巧来使得元素一半左对齐一半右对齐即可,下面时具体说明。可以参考文献 1 的《使用自动的外边距在主轴上对齐》章节。

如果希望让 flex 子元素靠右显示,可以在子元素中定义 margin-left: auto; (auto 也可使用其他数值单位替代),参考 👍MDN弹性盒子容器中的对齐方式 | flex子元素靠右 | Flex的最后一个元素靠右 | Flexbox

  • align-items 如果没有设置正确的话,所有的元素会按照撑满 flex 容器的高度去对齐。
  • 使用 margin:auto 可以实现元素的居中
  • 使用 align-self 可以实现单个 flex 子项的不同对齐方式。

可能接着阅读更多布局相关的资料,加深对布局的认识,以及正确选择合适的布局:Mdn Layout Cookbook


LearnWeb15-CSS09-媒体查询与响应式设计
LearnWeb14-CSS08-CSS布局

LearnWeb14-CSS08-CSS布局

[!summary]+
这里 CSS 的布局指的是通过控制元素的属性:宽度,高度,块(不同类型),内联等来实现在 HTML 的基础上对整体页面的布局进行调整。将其排布在页面上。

CSS 页面布局技术允许我们拾取网页中的元素,并且控制它们相对正常布局流、周边元素、父容器或者主视口/窗口的位置。在这个模块中将涉及更多关于页面布局技术的细节:

布局模式介绍

正常布局流:指的是不对页面进行任何布局控制的时候,浏览器默认的 HTML 布局方式,实际上就是按照源码中的先后次序依次出现,在 HTML 布局的过程中,最为重要的就是元素的块和内联两种布局方式。这里需要注意的就是,块的方向和元素的书写方向的关系。

而当我们希望使用 CSS 来改变正常的布局形式的时候,通常会使用以下的一些属性,或者说布局技术,来覆盖掉默认的 HTML 布局行为。

  • display 属性:改变元素在 HTML 中的渲染形式,如 block inline inline-block 还有 CSS 引入的 CSS GridFlexbox.
  • float 属性:使用 float 属性能改变块级元素换行的特性,实现类似换行的效果,其值指定其他元素围绕块级元素的哪一边。
  • position 属性:利用 position 来设置盒子在盒子中的位置,在嵌套的情况下就可以产生多样的排布
  • 表格布局:表格的布局方式可以用在非表格内容上,可以使用display: table和相关属性在非表元素上使用
  • 多列布局: Multi-column layout 属性可以让块按列布局,比如报纸的内容就是一列一列排布的。

在讨论布局时,display 最多用到的属性为 flexgrid 属性,利用这两个属性值来改变布局。