Django 是一款使用 Python 编程语言开发的 Web 应用程序框架,其内置了一些强大的数据库操作功能,包括增删改查等操作。Django 使用的是 ORM(对象关系映射)技术,可以通过 Python 对象操作数据库,而不需要直接编写 SQL 语句,简化了开发人员的工作。它遵循了 MVC(Model-View-Controller)的设计模式,并采用了 MTV(Model-Template-View)的设计思想,是一款功能强大、易于上手、高度可定制的 Web 应用程序框架。
在使用 Django 进行数据库增删改查操作时,其效率取决于多个因素,包括但不限于以下几个方面:
- 数据库类型和配置:不同的数据库类型和配置对性能有着重要影响。Django 支持多种数据库类型,包括 MySQL、PostgreSQL、SQLite 等,每种数据库类型都有不同的性能特点和优化方式。
- 数据库模型设计:良好的数据库模型设计可以提高数据库操作的效率。合理的模型设计可以使得查询操作更加高效,同时减少数据冗余和数据不一致的问题。
- 数据量和数据结构:数据量和数据结构对数据库操作效率有重要影响。在数据量较大的情况下,查询操作可能会变得缓慢,需要进行分页、缓存等优化操作。
- 硬件和网络环境:硬件配置和网络环境也会对数据库操作效率有重要影响。如果硬件配置较低或网络延迟较高,数据库操作的效率也会相应降低。
总的来说,Django 提供了一些优秀的数据库操作功能,可以帮助开发人员快速地进行数据库增删改查等操作。但是,其效率还是取决于多个因素,需要在具体情况下进行评估和优化。
节流(Throttle
)和防抖(Debounce
)对于前端开发人员来说应该是十分熟悉的,节流(Throttle
)和防抖(Debounce
)是两种可以节省性能的编程技术,两者的目的都是为了优化性能,提高用户体验,都是基于 DOM 事件限制正在执行的 JavaScript 数量的方法。
在 JavaScript 中,防抖和节流都是用来限制函数执行频率的技术,常用于解决某些高频触发的问题,比如滚动事件、拖拽事件、鼠标的移动事件、键盘按键事件、输入框实时搜索等等。
防抖和节流都可以避免函数被频繁调用,减少计算资源的浪费,提高性能。
具体来说:
- 防抖函数会在事件触发 n 秒后执行,如果在这段时间内事件又被触发,则会重新计算执行时间,直到等待时间结束,最后一次触发的事件才会被执行。防抖函数的作用是对于一些高频触发的事件,避免过多地执行操作,从而提高程序性能。
- 节流函数会在事件触发后 n 秒内最多执行一次,如果在这段时间内事件又被触发,则会忽略该次事件的执行,直到等待时间结束,下一个事件才会被执行。节流函数的作用是对于一些高频触发的事件,降低操作频率,避免过多地执行操作,从而提高程序性能。
防抖和节流的实现方法有多种,其中常用的方式是使用定时器来控制函数执行的时间间隔。在使用防抖和节流函数时需要注意适当设置时间间隔,以保证函数不会被过度限制或者过度调用。
关于防抖和节流更详细的介绍可以阅读下面文章:
获取 URL 种的参数是经常要处理的功能。在 URL 中,查询参数字符串值通常提供有关请求的信息,例如搜索参数或正在使用的对象的 ID。如果在前端处理任何业务或请求逻辑,了解如何从 URL 中检索查询字符串值非常重要。
// url https://www.zhihu.com/index.shtml?name=devpoint&id=100
// 格式化 search parameters
{
name: "devpoint",
id: "100",
}
常规方式
这是大多数人使用它的方式。
const parseQuery = (search = window.location.search) => {
const query = {};
search
.slice(1)
.split("&")
.forEach((it) => {
const [key, value] = it.split("=");
query[key] = decodeURIComponent(value);
});
return query;
};
console.log(parseQuery("?name=devpoint&id=100")); // { name: 'devpoint', id: '100' }
使用 reduce
const parseQuery = (search = window.location.search) =>
search
.replace(/(^\?)|(&$)/g, "")
.split("&")
.reduce((query, it) => {
const [key, value] = it.split("=");
query[key] = decodeURIComponent(value);
return query;
}, {});
console.log(parseQuery("?name=devpoint&id=100")); // { name: 'devpoint', id: '100' }
从 2022 IEEE 编程语言榜单结果来看,Python 再次摘得桂冠 ,前 10 的排名如下:
- Python
- C
- C++
- C#
- Java
- SQL
- JavaScript
- R
- HTML
- TypeScript
python
Python 是一种高级编程语言,由 Guido van Rossum 于 1989 年创造。Python 旨在提供一种简单、易于阅读和编写的语言,同时也具备强大的功能和扩展性,可以用于多种应用程序开发,包括 Web 应用程序、游戏开发、数据分析、科学计算等领域。
Python 具有许多特性,包括:
- 简单易学:其语法简单易懂,容易学习和上手。
- 可读性强:采用简洁的语法和缩进来表示代码块,使得代码易于阅读和理解。
- 面向对象:支持面向对象编程,可以定义类和对象,使得代码组织更加清晰和灵活。
- 丰富的标准库:Python 有一个丰富的标准库,包含了大量的模块和函数,可以方便地完成各种任务。
- 跨平台性:Python 可以在多种操作系统和平台上运行,包括 Windows、Mac、Linux 等。
- 强大的第三方库支持:Python 有大量的第三方库和框架,可以方便地扩展和增强 Python 的功能。
随着 ChatGPT 的持续火爆,带飞了整个 AI 行业,而 Python 在人工智能(AI)领域得到了广泛的应用和认可。Python 提供了大量的 AI 库和框架,例如 TensorFlow、PyTorch、Keras、Scikit-learn 等,使得 Python 成为了一个非常流行的人工智能编程语言。
JavaScript
JavaScript 是一种面向对象、动态、弱类型的编程语言,用于在网页浏览器中编写交互式的前端网页应用程序。JavaScript 是一种基于原型、事件驱动、函数式编程范式的语言。
JavaScript 最初由 Netscape 公司的 Brendan Eich 在 1995 年创建,用于给网页增加交互性和动态性。随着 Web 技术的发展,JavaScript 逐渐成为 Web 前端开发的核心语言之一。
JavaScript 与 HTML 和 CSS 一起构成了 Web 技术的三大基石。JavaScript 通过 DOM
(文档对象模型)和 BOM
(浏览器对象模型)提供了对网页内容和浏览器功能的访问和操作,可以实现各种动态效果、交互式操作和数据处理等功能。
随着 Web 技术的发展,JavaScript 也得到了广泛的应用和发展。JavaScript 不仅可以用于前端开发,也可以用于后端开发、移动应用开发、桌面应用开发等领域。同时,JavaScript 的生态系统非常丰富,有大量的库、框架和工具可以用于开发各种应用程序。
JavaScript 在人工智能(AI)领域的应用相对较少,但也有一些相关的应用场景。JavaScript 主要应用于 Web 前端开发,包括网页应用程序和移动应用程序的开发。在这些应用程序中,JavaScript 主要用于实现交互式操作和数据处理等功能。
随着 Web 技术的发展和 AI 技术的普及,JavaScript 在 AI 领域的应用也在不断扩大。一些 AI 库和框架也提供了 JavaScript 版本,例如 TensorFlow.js
、Brain.js
、Synaptic.js
等,这些库和框架可以在网页浏览器中直接运行,方便了 Web 前端开发者使用 AI 技术。
Python VS JavaScript
这里只是简单的做个比较,有个认识。两者都是流行的编程语言,但是它们在很多方面也有很大的不同,下面从最基本的知识点出发做个对比。
- 用途:Python 通常用于后端开发、数据分析、科学计算等领域,而 JavaScript 通常用于前端开发、Web 应用程序、交互式动态效果等领域。
- 语法:Python 的语法相对简单,易于学习和阅读。Python 采用缩进来表示代码块,而 JavaScript 则使用花括号来表示代码块。
- 变量:Python 是一种动态类型语言,不需要在代码中显式地声明变量的类型。而 JavaScript 是一种弱类型语言,可以在变量中存储任何类型的值,也可以在代码中显式地声明变量的类型。
- 函数:Python 和 JavaScript 的函数都是一等公民,可以作为参数传递给其他函数,也可以从函数中返回。但是,Python 中的函数定义需要使用
def
关键字,而 JavaScript 中的函数定义可以使用function
关键字或箭头函数。 - 模块:Python 有一个强大的模块系统,可以方便地组织代码和共享代码。JavaScript 也有模块系统,但是在不同的环境下实现有所不同,例如在浏览器中使用 ES6 模块,而在
Node.js
中主要使用 CommonJS。
总的来说,Python 和 JavaScript 在很多方面都有所不同,但它们都是强大的编程语言,具有广泛的用途和应用场景。选择使用哪种语言,取决于具体的需求和项目要求。
在 JavaScript 中,可以通过循环遍历数组并创建一个新的链表节点,然后将其插入到链表中来将一个数组转换为链表。如下代码所示:
class Node {
constructor(value, next = null) {
this.value = value;
this.next = next;
}
}
function arrayToLinkedList(arr) {
if (!arr || !arr.length) {
return null;
}
let head = new Node(arr[0]);
let current = head;
for (let i = 1; i < arr.length; i++) {
let newNode = new Node(arr[i]);
current.next = newNode;
current = newNode;
}
return head;
}
在上面的代码中,arrayToLinkedList
函数接受一个数组作为参数,返回一个链表的头节点。使用循环遍历数组,为每个元素创建一个新的链表节点。然后,它将新的节点插入到链表中,并将 current
指针指向新的节点,以便在下一个迭代中使用。最后,该函数返回链表的头节点。
使用方式如下:
const arrayList = [1, 2, 3, 4, 5, "a"];
const head = arrayToLinkedList(arrayList);
// 遍历链表并打印每个节点的值
let current = head;
while (current) {
console.log(current.value);
current = current.next;
}
链表用途
JavaScript 中的链表主要用于实现一些数据结构和算法。以下是一些常见的应用场景:
- 实现栈和队列:链表可以用来实现栈和队列,通过在链表头部插入和删除元素,实现栈的
FILO
(先进后出)和队列的FIFO
(先进先出)特性。 - 缓存淘汰算法:链表可以用于实现 LRU(最近最少使用)算法,其中链表的头部存储最近使用的数据,尾部存储最久未使用的数据,当缓存空间满时,将尾部的数据删除。
- 多级反转问题:链表可以用于解决一些多级反转问题,例如将一个链表中每
K
个节点进行反转,或将一个链表中相邻的节点反转等。 - 排序算法:链表可以用于实现一些排序算法,例如归并排序、快速排序等。
WebAssembly(缩写为WASM)是一种二进制格式的代码,它可以在现代Web浏览器中运行。它被设计为一种低级的虚拟机,可以在浏览器中运行高性能的程序,可以通过编译其他语言的代码来实现。
下面是WebAssembly可以做的一些事情:
- 前端开发:WebAssembly 可以与JavaScript一起使用,以提高Web应用程序的性能。例如,可以使用WebAssembly编写一些计算密集型的算法,以替代JavaScript来提高性能。
- 游戏开发:可以用于在Web浏览器中运行游戏。它可以提供比JavaScript更快的运行速度和更好的性能,使得在Web浏览器中运行的游戏更加流畅。
- 跨平台开发:可以在不同的平台上运行,包括Web浏览器、桌面和移动设备。这使得开发人员可以使用相同的代码来创建跨平台应用程序。
- 数据科学和机器学习:可以用于加速数据科学和机器学习应用程序,使得这些应用程序可以在Web浏览器中运行。
除了上述提到的应用场景,WebAssembly还有以下几个优点:
- 高性能:WebAssembly代码可以编译成原生代码,因此它可以提供比JavaScript更快的运行速度和更好的性能。这使得它非常适合执行计算密集型的任务,如图形处理、音视频处理和机器学习等。
- 安全性:WebAssembly是一种沙箱化的执行环境,因此它可以提供比JavaScript更高的安全性。它的指令集是基于栈的,而不是基于寄存器的,这使得它更难受到缓冲区溢出等安全漏洞的攻击。
- 可移植性:WebAssembly可以在不同的平台上运行,包括Web浏览器、桌面和移动设备。这使得开发人员可以使用相同的代码来创建跨平台应用程序,从而减少了开发时间和成本。
- 扩展性:WebAssembly可以与现有的Web技术(如JavaScript、CSS和HTML)集成使用,从而可以扩展Web应用程序的功能和性能。
WebAssembly 虽然是一门新的技术,但现在有不少区块链项目在使用,用来编写智能合约,提高智能合约的性能和安全性。
下面是一些使用WebAssembly的区块链项目:
- EOSIO:EOSIO是一个开源的区块链平台,可以用于创建高性能的去中心化应用程序。它使用WebAssembly作为其智能合约的执行引擎,从而提高了智能合约的性能和安全性。
- NEAR Protocol:NEAR Protocol是一个区块链平台,旨在为开发者提供易于使用、高性能和安全的去中心化应用程序。它使用WebAssembly作为其智能合约的执行引擎,并提供了一些工具和框架来帮助开发者编写和部署WebAssembly智能合约。
- Solana:Solana是一个高性能的区块链平台,可以处理数千个交易,每秒钟处理数百万个交易。它使用WebAssembly作为其智能合约的执行引擎,从而实现高性能的智能合约执行。
要启动一个 CentOS 镜像,可以在 Docker Hub 中查找需要的镜像,运行以下命令:
docker run -it centos
运行后将从 Docker Hub 下载 CentOS 镜像(如果它还没有被下载过),并在交互模式下启动容器。能够看到一个新的 shell
提示符,该提示符表示已经进入 CentOS 容器内部。
如果想在后台模式下运行 CentOS 容器,使用以下命令:
docker run -d centos
将在后台启动容器,并输出容器的 ID
。
无论哪种方式,都可以使用 docker ps
命令来列出所有正在运行的容器。要停止容器,可以使用 docker stop
命令,后跟容器的 ID 或名称。如下:
docker stop <CONTAINER_ID>
请注意,如果希望在容器内运行某些命令或应用程序,可以将它们附加到 docker run
命令的末尾。例如,要在 CentOS 容器中运行 ls
命令,可以使用以下命令:
docker run -it centos ls
将启动一个新的 CentOS 容器,运行 ls
命令,然后退出容器。
在 ES6 中,可以使用闭包来模拟私有变量的实现。具体地说,可以使用一个立即执行函数表达式 IIFE
来创建一个函数作用域,在函数作用域内定义私有变量,然后通过返回一个对象,使得外部无法直接访问私有变量。
例如,以下是一个使用闭包实现私有变量的示例:
const Counter = (() => {
let count = 0; // 私有变量
return {
increment() {
count++;
console.log(`count: ${count}`);
},
reset() {
count = 0;
console.log("count reset");
},
};
})();
Counter.increment(); // 输出 "count: 1"
Counter.increment(); // 输出 "count: 2"
Counter.reset(); // 输出 "count reset"
在上述示例中,count
变量是私有的,外部无法直接访问它,但是可以通过调用返回的对象中的方法来操作它。这里使用了箭头函数和对象字面量来定义返回的对象,使得代码更加简洁。
每天学习10个实用JavaScript代码片段,加深对 JavaScript 语法的理解,积累代码优化经验。
1、FizzBuzz
下面代码用于实现经典的 FizzBuzz
问题,将1
到100
的数字遍历,如果是3
的倍数则输出 Fizz
,如果是5
的倍数则输出 Buzz
,如果既是3
的倍数又是5
的倍数则输出 FizzBuzz
。
const fizzBuzz = () => {
for (let i = 1; i <= 100; )
console.log((i % 3 ? "" : "Fizz") + (i % 5 ? "" : "Buzz") || i), i++;
};
console.log(fizzBuzz());
2、斐波那契数列
斐波那契数列(Fibonacci sequence
),又称黄金分割数列,因数学家莱昂纳多·斐波那契(Leonardo Fibonacci)以兔子繁殖为例子而引入,故又称为“兔子数列”,指的是这样一个数列:1、1、2、3、5、8、13、21、34、……
在数学上,斐波那契数列以如下被以递推的方法定义:F(0)=1,F(1)=1, F(n)=F(n - 1)+F(n - 2)(n ≥ 2,n ∈ N*)
,在现代物理、准晶体结构、化学等领域,斐波那契数列都有直接的应用
斐波那契数列通常是面试常见的问题,下面代码是用一个非常简短的递归函数,返回斐波那契数列中第n
个数字的值。
const fib = (n) => (n <= 2 ? 1 : fib(n - 1) + fib(n - 2));
const createFibNumber = (count = 10) => {
const arrayFibs = [];
for (let i = 0; i < count; i++) {
arrayFibs.push(fib(i));
}
return arrayFibs;
};
console.log(createFibNumber(10)); // [ 1, 1, 1, 2, 3, 5, 8, 13, 21, 34 ]
3、快速排序
下面代码实现了快速排序,通过递归将数组分割成较小和较大的两个子数组,最终将它们合并以获得排序的结果。
const quickSort = (arrayNums) =>
arrayNums.length
? [
...quickSort(arrayNums.slice(1).filter((x) => x <= arrayNums[0])),
arrayNums[0],
...quickSort(arrayNums.slice(1).filter((x) => x > arrayNums[0])),
]
: [];
const arrayNumbers = [1, 3, 5, 6, 2, 4, 11, 30, 100, 40];
console.log(quickSort(arrayNumbers)); // [ 1, 2, 3, 4, 5, 6, 11, 30, 40, 100 ]
4、判断是否为回文
下面代码用于判断一个字符串是否为回文,它将字符串转换为一个字符数组并翻转它,然后将它们重新连接起来,并将其与原字符串进行比较。
const isPalindrome = (str) => str == str.split("").reverse().join("");
console.log(isPalindrome("deved")); // true
console.log(isPalindrome("devedev")); // false
5、数组去重
下面代码用于对一个数组进行去重操作,使用ES6中的Set数据结构,将数组转换为一个Set,然后再将其转换为一个数组。
const unique = (arr) => [...new Set(arr)];
const arrayNumbers = [1, 2, 3, 4, 2, 1];
console.log(unique(arrayNumbers)); // [ 1, 2, 3, 4 ]
6、打印九九乘法表
下面代码可以打印出九九乘法表,它通过两个嵌套的for
循环遍历每个数字,然后打印出相应的乘法表达式及其结果。
const createNumbers = () => {
const array = [];
for (let i = 1; i <= 9; i++)
for (let j = 1; j <= i; j++)
array.push(`${j}*${i}=${j * i}${j === i ? "\n" : " "}`);
return array.join("");
};
console.log(createNumbers());
输出结果如下:
1*1=1
1*2=2 2*2=4
1*3=3 2*3=6 3*3=9
1*4=4 2*4=8 3*4=12 4*4=16
1*5=5 2*5=10 3*5=15 4*5=20 5*5=25
1*6=6 2*6=12 3*6=18 4*6=24 5*6=30 6*6=36
1*7=7 2*7=14 3*7=21 4*7=28 5*7=35 6*7=42 7*7=49
1*8=8 2*8=16 3*8=24 4*8=32 5*8=40 6*8=48 7*8=56 8*8=64
1*9=9 2*9=18 3*9=27 4*9=36 5*9=45 6*9=54 7*9=63 8*9=72 9*9=81
7、数组求和
下面代码用于对一个数组中的数字进行求和,使用 reduce 方法将数组中的所有数字相加起来,并返回最终的结果。
const sum = (arr) => arr.reduce((acc, current) => acc + current, 0);
console.log(sum([1, 3, 5, 7, 9, 10])); // 35
8、获取URL参数
下面代码用于从URL中获取特定的参数值,使用 URLSearchParams
API来获取URL查询参数,并使用get
方法获取特定的参数值。
const getParam = (url, name) =>
new URLSearchParams(new URL(url).search).get(name);
console.log(getParam("https://www.devpoint.cn?name=devpoint", "name")); // devpoint
9、判断一个数字是否为质数
下面代码用于判断一个数字是否为质数,使用一个for
循环遍历2
到数字的平方根之间的所有数字,并检查它们是否可以整除该数字。
const isPrime = (n) => {
if (n <= 1) return false;
for (let i = 2; i <= Math.sqrt(n); i++) {
if (n % i === 0) return false;
}
return true;
};
console.log(isPrime(1)); // false
console.log(isPrime(2)); // true
console.log(isPrime(3)); // true
10、数组扁平化
下面代码用于将一个嵌套的数组扁平化为一个一维数组,使用 reduce 方法递归地遍历嵌套数组,并将其合并为一个新的数组。
const flatten = (arr) =>
arr.reduce(
(acc, val) => acc.concat(Array.isArray(val) ? flatten(val) : val),
[]
);
console.log(flatten([1, [1, 3], [4, 5, 6], [7, [8, [9]]]])); // [ 1, 1, 3, 4, 5, 6, 7, 8, 9 ]
11、阶乘
下面代码用于计算一个数字的阶乘,使用递归方法,将数字乘以它减一的阶乘。
const factorial = (n) => (n < 2 ? 1 : n * factorial(n - 1));
console.log(factorial(4)); // 24
总结
阅读开源项目是学习提升编码能力最快捷的方式,当然现在可能可以借助 ChatGPT 来生成一些函数,其生成的函数技巧性也很高。
关于这个问题,现在应该有新的认知,先回到问题本身,最佳的学习顺序应该是:
学习顺序
- HTML:HTML是构建Web页面的基础语言,学习HTML可以熟悉如何创建文本、图像、链接等基本内容,并且组织网页结构,这是WEB交互的基础,有了这个基础才能设计独特的交互效果。
- CSS:
CSS
是用来美化Web页面的样式表语言。通过CSS,可以控制文本、图像、背景、布局等各个方面的样式,让页面更加美观和易于阅读。掌握其基础,在现代WEB开发中建议不用原生 CSS ,而是使用 LESS、SASS 等,其具有变量、基础、函数组织能力可以更好的维护项目。 - JavaScript:JavaScript 是一种脚本语言,是用来控制Web页面的交互和动态效果,例如响应用户操作、验证表单、创建动画、处理数据等等。熟练掌握应该是前端开发者应该具备的技能,现代WEB开发中,更多的使用工程化框架来构建,如React、Vue等等。
- PHP:PHP是一种服务器端脚本语言,用来处理Web应用程序的数据和逻辑。通过PHP,你可以实现用户注册、登录、发送电子邮件、处理表单数据、连接数据库等功能。这是过去WEB开发架构中常见的后端服务开发技术,现阶段实际上有很多可替代的架构,如Next.js、Node.js、Go等等。
- MySQL:MySQL是一种流行的开源关系型数据库管理系统,用来存储和管理Web应用程序的数据。学习MySQL可以让你了解如何创建和管理数据库、存储和查询数据等。
上面提到的应该是过去WEB开发中比较常见技术架构,现在应该有更多的选择,如
HTML+CSS+JavaScript+Node.js+Mongodb
;如果是React,则应该是HTML+CSS+JavaScript+Next.js+Mongodb
。
当然不管那种技术架构,HTML+CSS+JavaScript
是基础,更高阶的可以往 HTML+CSS+TypeScript
的方向去。
除了上面的这些技能,对于开发者来说,建议掌握 Docker,过去对于开发者来说,环境的配置通常是比较繁琐的,就拿PHP来说,配置开发环境需要安装 Apache、PHP版本,存储需要安装 Mysql,如果掌握了 Docker ,环境配置就变得简单,并且不会污染自己本机的环境。
学习资料推荐
对于HTML和CSS,建议从模仿开始,去阅读一些开源代码和比较不错的HTML模版,从模仿这些模版开始。开始之前可以先看下基础的东西:
CSS权威指南(第四版)(上下册):国际公认的HTML、CSS和Web标准领域的专家全新力作。Web视觉呈现技术。全面阐述CSS的实现方式,深入分析全新的CSS规范。
对于JavaScript,就有很多的书籍,下面书籍自己都阅读学习过了,不多,书籍算是掌握基本功的重要途径,剩下主要提升就是 GitHub。
JavaScript 指南原书第7版犀牛书:在过去的十年中,Node.js 使得 JavaScript 编程可以在 web 浏览器之外进行,Node 的巨大成功意味着 JavaScript 现在也是软件开发人员最常用的编程语言。无论你是从零开始还是已经开始专业地使用 JavaScript,这本书都会帮助你掌握这门语言。
JavaScript程序设计 第4版 第四版 红宝石书:第4版涵盖ECMAScript 2019,全面、深入地介绍了JavaScript开发者必须掌握的前端开发技术,涉及JavaScript的基础特性和特性 。
数据结构与算法JavaScript描述:用JavaScript描述数据结构与算法的开山之作,汇聚了作者多年的实战经验。这本实战指南通过丰富的示例,向读者透彻讲解了在JavaScript环境下,如何通过一系列存储机制(包括链表、栈、队列和图)高效地达到编程目的。
除了阅读书籍阅读源代码外,还可以去刷刷算法题。
JavaScript设计模式与开发实践:设计模式是软件设计中经过了大量实际项目验证的可复用的优 秀解决方案,它有助于程序员写出可复用和可维护性高的程序。许多优 秀的JavaScript开源框架都运用了不少设计模式,越来越多的程序员从设计模式中获益,也许是改善了自己编写的某个软件,也许是更好地理解了面向对象的编程思想。
学习设计模式的目的是:为了代码可重用性、让代码更容易被他人理解、保证代码可靠性。 设计模式使代码编写真正工程化;设计模式是软件工程的基石脉络,如同大厦的结构一样。
- JavaScript 设计模式之策略模式
- JavaScript 设计模式之代理模式
- ES6 类聊 JavaScript 设计模式之创建型模式
- ES6 类聊 JavaScript 设计模式之结构型模式
- ES6 类聊 JavaScript 设计模式之行为型模式(一)
Docker
的优势很容易让Web开发人员所忽略,对于开发运维来说,它被认为太过技术性、没有必要。先来看看其有什么优势:
- 简化配置和部署:Docker可以打包应用程序及其依赖项,并在任何地方轻松部署,从而简化了配置和部署的过程。
- 跨平台支持:Docker容器可以在任何操作系统上运行,而不需要修改代码,这使得开发、测试和部署更加灵活和高效。
- 资源利用率高:由于Docker容器共享操作系统内核,因此它们比传统虚拟机更加轻量级,可以更好地利用硬件资源。
- 可靠性和安全性:Docker容器提供隔离环境,使得应用程序和依赖项在容器中运行更加安全和可靠,同时也避免了不同应用程序之间的冲突。
- 易于维护:Docker容器可以根据需要快速创建、销毁和重建,使得应用程序的维护更加容易和高效。在资源扩容收缩方面可以很灵活。
对于开发者而言,还是有必要认识一下Docker,花点时间学习可以提高开发效率、让运营变得简单。
先谈谈个人感受,过去开发PHP程序的时候,需要自己配置Apache等环境,如果换了电脑又得来一遍。有了Docker,开发环境就只需要一句命令或者自己制作符合自己的镜像,这样开发的时候不管在哪里,只要安装Docker,运行起来就可以开始开发调试了。生产环境也是如此,过去如果要配置10台甚至更多的环境,需要一个一个来,有了Docker,实际上就只要执行一个shell文件就可以了,而且配置都可以是完全一样的。
如果对Docker有兴趣,推荐一本书籍《Docker实战(第二版)》
HTTP Cookie(也叫 Web Cookie 或浏览器 Cookie)是服务器发送到用户浏览器并保存在本地的一小块数据,它会在浏览器下次向同一服务器再发起请求时被携带并发送到服务器上。是WEB项目开发需要涉及的内容。
Cookie操作
如果应用程序是完全客户端 SPA(单页应用程序),可能不需要 cookie
,使用 localStorage
可以解决问题。如果是使用 Next.js 或者 Node.js 提供服务器接口需要身份验证 Token 的可以考虑使用 cookie
。
通常认为 cookie
是复数形式,但事实是它们存储在单个字符串值中,必须对其进行解析才能将它们分解为单独的键/值对
。
console.log(document.cookie); // _gcl_au=1.1.1660316496.1636468606; _ga=GA1.2.221099298.1636468607; _gid=GA1.2.1474751041.1636468607;
可以通过 ;
拆分字符串来将它们分开,然后映射每个值并使用 =
将其拆分为 ,最终将得到相应的键/值
对。下面是一个完整的方法集:
const useCookie = (options = { days: 30, path: "/" }) => {
const { days: expiresDays, path: cookiePath } = options;
const set = (name, value) => {
const exp = new Date();
exp.setTime(exp.getTime() + expiresDays * 24 * 60 * 60 * 1000);
const strExpires = exp.toGMTString();
const cookieValue = escape(value);
document.cookie = `${name}=${cookieValue};expires=${strExpires};path=${cookiePath}`;
};
const get = (name) => {
let arr;
const reg = new RegExp("(^| )" + name + "=([^;]*)(;|$)");
if ((arr = document.cookie.match(reg))) {
return unescape(arr[2]);
} else {
return null;
}
};
// 删除cookie
const remove = (name) => {
document.cookie = name + "=;expires=" + new Date(0).toGMTString();
};
// 清除所有 cookie
const clear = () =>
document.cookie
.split(";")
.forEach(
(cookie) =>
(document.cookie = cookie
.replace(/^ +/, "")
.replace(
/=.*/,
`=;expires=${new Date().toUTCString()};path=${cookiePath}}`
))
);
/**
* 获取所有的 cookie
* @returns
*/
const all = () =>
document.cookie
.split(";")
.map((item) => item.split("="))
.reduce(
(acc, [k, v]) => (acc[k.trim().replace('"', "")] = v) && acc,
{}
);
return {
set,
get,
clear,
remove,
all,
};
};
const cookieHelper = useCookie();
cookieHelper.set("name", "DevPoint");
cookieHelper.set("city", "Shenzhen");
console.log(cookieHelper.get("name")); // DevPoint
console.log(cookieHelper.all()); // { name: "DevPoint", city: "Shenzhen" }
cookieHelper.remove("name");
console.log(cookieHelper.all()); // { city: "Shenzhen" }
出于安全考虑,某些 cookie
可能被标记为 仅 HTTP
,这意味着此类 cookie
不能从客户端的 JavaScript 代码中获取到。
Cookie 安全
Cookie 安全主要涉及以下两个方面:
防止窃取
攻击者可以通过窃取用户的 Cookie 来实现会话劫持等攻击,因此在设置 Cookie 时应当注意以下安全问题:
- 在服务器端设置 HttpOnly 属性,防止脚本获取 Cookie。
- 使用 secure 属性,只允许在 HTTPS 连接中传输 Cookie。
- 设置 SameSite 属性,防止跨站请求伪造攻击(CSRF)。
防止篡改
攻击者还可以通过修改 Cookie 的值来实现攻击,因此在使用 Cookie 时应当注意以下安全问题:
- 在服务器端对 Cookie 的值进行加密和签名,防止篡改。
- 对于关键操作,应当在服务端进行校验,避免因为 Cookie 被篡改而造成安全漏洞。
综上所述,Cookie 安全需要从多个方面进行考虑和防护。
Cookie 和 Token
应用开发一般都少不了身份验证,而身份验证机制的稳定性对所有应用程序都变得至关重要。具体选择何种方式进行身份验证可以根据项目及团队情况来衡量,在决定之前需要先理解WEB身份验证常见的两种方式:基于 Cookie
的身份验证和基于令牌(Token
)的身份验证。
在 JavaScript 中,可以使用 substr()
、slice()
和 substring()
方法截取字符串。
substring()
substring()
方法返回一个字符串在开始索引到结束索引之间的一个子集,或从开始索引直到字符串的末尾的一个子集。语法如下:
str.substring(indexStart[, indexEnd])
参数说明:
indexStart
:需要截取的第一个字符的索引,该索引位置的字符作为返回的字符串的首字母。indexEnd
:可选。一个0
到字符串长度之间的整数,以该数字为索引的字符不包含在截取的字符串内。
如果 startIndex
大于 endIndex
,则会交换它们的位置。来看一段示例代码:
let str = "深耕WEB开发10+年,拥有一颗工匠的心";
console.log(str.substring(7)); // 10+年,拥有一颗工匠的心
console.log(str.substring(0, 5)); // 深耕WEB
console.log(str.substring(7, 5)); // 开发
console.log(str.substring(5, 5)); // 空
substr()
substr()
方法返回一个字符串中从指定位置开始到指定字符数的字符。语法如下:
str.substr(start[, length])
第一个参数是截取开始的位置,第二个参数是截取的长度。如下代码:
let str = "深耕WEB开发10+年,拥有一颗工匠的心";
let result = str.substr(1, 4);
console.log(result); // 耕WEB
上述代码的 1
表示从第二个字符开始截取,4
表示截取的长度为 4
个字符。
substr()
方法在截取字符串时是基于字符位置的,如果截取长度超过了字符串的长度,它会一直截取到字符串的末尾。如果第一个参数是负数,则从字符串的末尾开始计数。
如果可以的话,建议使用 substring()
来取代 substr()
。
slice()
slice()
方法提取某个字符串的一部分,并返回一个新的字符串,且不会改动原字符串,与 substring()
方法类似。语法如下:
str.slice(startIndex, endIndex)
其中,startIndex
表示要截取的子字符串的起始位置(包含该位置),endIndex
表示要截取的子字符串的结束位置(不包含该位置)。如果只传入 startIndex
,则会从该位置一直截取到字符串末尾。如果 startIndex
大于 endIndex
,则会返回空字符串。例如:
let str = "深耕WEB开发10+年,拥有一颗工匠的心";
console.log(str.slice(7)); // 10+年,拥有一颗工匠的心
console.log(str.slice(0, 5)); // 深耕WEB
console.log(str.slice(7, 5)); // ""
console.log(str.slice(5, 5)); // ""
substring()
和 substr()
的区别
substring()
和 substr()
都是截取字符串的方法,但它们有以下区别:
- 参数不同:
substring()
方法的第一个参数是起始位置,第二个参数是终止位置(不包括该位置的字符);substr()
方法的第一个参数是起始位置,第二个参数是截取的长度。 - 负数处理不同:
substring()
方法不接受负数参数,如果传入负数,会被自动转换为0
;substr()
方法可以接受负数参数,表示倒数计数。 - 返回值不同:
substring()
方法返回从起始位置到终止位置之间的字符;substr()
方法返回从起始位置开始指定长度的字符。
ES6中新增了
slice()
方法,但它和substring()
方法非常相似,也是接受起始位置和终止位置作为参数,不同之处是slice()
方法可以接受负数参数,表示从字符串末尾开始计数。substr()
虽然没有被完全遗弃,但还是建议使用substring()
来取代substr()
。
JavaScript 可以使用以下代码实现进制转换:
1、十进制转二进制
使用 toString()
方法,将十进制数转换为二进制字符串,如下:
const decimalToBinary = (decimalNumber) => decimalNumber.toString(2);
console.log(decimalToBinary(11)); // 1011
2、十进制转八进制
const decimalToOctal = (decimalNumber) => decimalNumber.toString(8);
console.log(decimalToOctal(11)); // 13
3、十进制转十六进制
const decimalToHex = (decimalNumber) => decimalNumber.toString(16);
console.log(decimalToHex(11)); // b
4、二进制转十进制
const binaryToDecimal = (binaryString) => parseInt(binaryString, 2);
console.log(binaryToDecimal(1011)); // 11
5、八进制转十进制
const octalToDecimal = (binaryString) => parseInt(binaryString, 8);
console.log(octalToDecimal(13)); // 11
6、十六进制转十进制
const hexToDecimal = (binaryString) => parseInt(binaryString, 16);
console.log(hexToDecimal("b")); // 11
7、二进制转十六进制
const binaryToHex = (binaryString) => {
const decimalNumber = parseInt(binaryString, 2);
return decimalNumber.toString(16);
};
console.log(binaryToHex(1011)); // b
8、八进制转二进制
const octalToBinary = (octalString) => {
const decimalNumber = parseInt(octalString, 8);
return decimalNumber.toString(2);
};
console.log(octalToBinary(13)); // 1011
9、十六进制转二进制
const hexToBinary = (hexString) => {
const decimalNumber = parseInt(hexString, 16);
return decimalNumber.toString(2);
};
console.log(hexToBinary("b")); // 1011
10、八进制转十六进制
const octalToHex = (octalString) => {
const decimalNumber = parseInt(octalString, 8);
return decimalNumber.toString(16);
};
console.log(octalToHex(13)); // b
下面将上述代码封装成一个类,完整代码如下:
class NumberConverter {
static decimalToBinary(decimalNumber) {
return decimalNumber.toString(2);
}
static decimalToOctal(decimalNumber) {
return decimalNumber.toString(8);
}
static decimalToHex(decimalNumber) {
return decimalNumber.toString(16);
}
static binaryToDecimal(binaryString) {
return parseInt(binaryString, 2);
}
static octalToDecimal(octalString) {
return parseInt(octalString, 8);
}
static hexToDecimal(hexString) {
return parseInt(hexString, 16);
}
static binaryToHex(binaryString) {
const decimalNumber = parseInt(binaryString, 2);
return decimalNumber.toString(16);
}
static octalToBinary(octalString) {
const decimalNumber = parseInt(octalString, 8);
return decimalNumber.toString(2);
}
static hexToBinary(hexString) {
const decimalNumber = parseInt(hexString, 16);
return decimalNumber.toString(2);
}
static octalToHex(octalString) {
const decimalNumber = parseInt(octalString, 8);
return decimalNumber.toString(16);
}
}
console.log(NumberConverter.decimalToBinary(11)); // 1011
console.log(NumberConverter.decimalToOctal(11)); // 13
以下是一个将字符串转换为二进制字符串的 JavaScript 函数:
function stringToBinary(string) {
let binaryString = "";
for (let i = 0; i < string.length; i++) {
// 将字符转换为 Unicode 编码
const charCode = string.charCodeAt(i);
// 将 Unicode 编码转换为二进制字符串
const charBinary = charCode.toString(2);
// 将二进制字符串补齐 8 位
const paddedCharBinary = charBinary.padStart(8, "0");
// 将每个字符的二进制字符串拼接到总的二进制字符串中
binaryString += paddedCharBinary;
}
return binaryString;
}
函数接受一个字符串作为参数,返回一个二进制字符串。通过遍历字符串中的每个字符,将其转换为 Unicode 编码,再将 Unicode 编码转换为二进制字符串,最后将每个字符的二进制字符串拼接到总的二进制字符串中。如果一个字符的二进制字符串不足 8
位,函数会在左侧用 0
填充,使其成为 8
位二进制字符串。
使用方法如下:
const chineseString = stringToBinary("全栈工匠");
console.log(chineseString); // 101000101101000110100000001000101110111100101101001100100000
const englishString = stringToBinary("hu");
console.log(englishString); // 0110100001110101
在本节中,将创建一个Node.js的“Hello World”应用程序,并将该应用构建成Docker镜像,并从容器启动。正常情况下,该镜像可以部署到生产服务器上,Docker Compose 将用于覆盖一些设置以创建开发和调试环境。这样可以在主机PC上进行编码开发,这样文件将在一个持续运行的容器中执行。这有几个好处:
- Docker 将管理所有依赖项—— 不需要安装和维护runtimes
- 这个过程与本地开发没什么不同——可以使用任何喜欢的编辑器和工具
- 容器是隔离的——应用程序影响到主机PC,如使删除文件
- 任何时候都可以将应用程序分发给其他开发人员或测试人员——应用程序可以在任何其他设备上以零配置相同的方式运行。
本节创建的代码文件在项目
https://github.com/QuintionTang/docker-nodejs
中
基于容器的应用开发
Docker 简化了 Web 开发:任何的 Web 应用程序都可以在单个容器中运行。
但是……如果想将类似的容器部署到实时生产服务器,应用程序通常是无状态的。这样可以启动任意数量的实例,任何实例都可以对请求做出响应。实际上,应用程序不应该将基本状态数据存储在本地文件或内存中。
例如:当用户登录时,应用程序将登录凭据存储在内存中。在开发过程中使用单个容器,都可以按预期运行没有问题。
如果将应用程序部署到生产服务器并在两个以上容器中运行,这些容器通过负载均衡接收请求。用户访问系统由
container1
处理其登录。那么下一个请求可能就由container2
提供服务,容器之间并没有共享登录状态,这个时候就会出现未登录的情况。
当然上面的问题是可以通过解决的,为隔离的容器提供一个中心存储服务,维护应用的持久化存储数据,例如数据库。
无状态 Web 应用程序是一个不错的方式。这样在生产环境中随着用户情况的增加可以快速进行扩缩容,自动添加更多的机器/容器。在解决实际需求的时候就需要考虑是否适合无状态,如果对有状态的应用程序进行转换可能是不可行的。
这些在开发过程中都无关紧要,因为通常只会在单个容器中运行应用程序。如果不实用,就不必在生产中使用容器。
什么是 Node.js
这个想必大部份掘金的小伙伴都知道,这里不展开介绍,引用一段简单的说明。
Node.js
是一种流行的、高性能 JavaScript 运行时,使用 Chrome
浏览器的 V8 JavaScript
引擎构建。它通常用于服务器端 Web 开发,但也已被前端或客户端用来构建工具、桌面应用程序、嵌入式系统等所采用。
安装 Node.js 后,可以使用以下命令执行 JavaScript 文件:
node index.js
单入口脚本文件是什么?理论上它可以命名为任何名称,通常项目都使用index.js
作为入口。
前面的内容一直在使用 Docker Hub 提供的 Docker 镜像。本节将介绍如何构建自己的 Docker 镜像,该镜像可以在开发和生产环境中安装和执行应用程序。
可能你对 Node.js
不感兴趣,但是不管使用何种语言(PHP、Python、Ruby、Go、Rust等)都适合使用 Docker 。
Hello World应用概述
该项目将使用Node.js的Express.js框架创建了一个“Hello World”应用程序。
应用运行地址为:http://localhost:3000/
,返回纯文本格式:Hello World!
。
从客户端 Ajax 请求调用相同的 URL 会返回 JSON 编码的对象:
{ "message": "Hello World!" }
当传入请求的HTTP 标头设置为时,可以识别 Ajax 调用。这是由大多数 Ajax 库添加了:X-Requested-With
、XMLHttpRequest
。
可以向 URL 路径添加字符串,例如http://localhost:3000/devpoint
将返回 Hello Devpint!
,响应内容为:
{ "message": "Hello Devpoint!" }
项目初始化
在项目目录中执行以下代码初始化项目:
npm init
输入基本的信息后,会在项目根目录下生成 package.json
。
接下来安装 express
,执行一下命令:
npm install express --save
为了开发过程中能够响应代码的变更,接下来安装 Nodemon,执行以下命令:
npm install nodemon --save-dev
nodemo
用来监听 node.js 项目中文件的更改并自动重启服务的工具,接下来为项目增加监听规则,如需要忽略的目录:
{
"script": "./index.js",
"ext": "js json",
"ignore": [
"node_modules/"
],
"legacyWatch": true,
"delay": 200,
"verbose": true
}
修改项目 package.json
,在scripts属性下添加启动命令:
"start": "node ./index.js",
"debug": "nodemon --trace-warnings --inspect=0.0.0.0:9229 ./index.js",
这样在终端可以执行一下的命令:
npm start
: 一般用于生产环境npm run debug
:用于开发调试
应用脚本 index.js
脚本在根路由下定义简单的响应请求
"use strict";
const port = process.env.NODE_PORT || 3005, // 定义HTTP默认端口或者从NODE_PORT环境变量获取
express = require("express"),
app = express();
// 根路由
app.get("/:title?", (req, res) => {
const message = `Hello ${req.params.title || "Devpoint"}!`;
if (req.xhr) {
res.set("Access-Control-Allow-Origin", "*").json({ message });
} else {
res.send(message);
}
});
// 启动HTTP服务
app.listen(port, () => console.log(`server running on port ${port}`));
接下来开始执行脚本:
npm run debug
打开浏览器输入http://localhost:3005/
,可以看到响应的响应,如下
现在可以尝试去修改脚本 index.js
的内容,当有更新的时候,终端会重启服务,刷新浏览器即可看到更新。
到目前为止,一个简单的NodeJS应用程序已经完成。接下来将介绍如何在Docker环境里面运行调试。
后续可以结合谷歌浏览器调试Node.js应用。
ES6,全称 ECMAScript 6,是 JavaScript 的一个版本。它是在 2015 年发布的,是 JavaScript 的一个重大更新,引入了许多新的语言特性和 API,包括箭头函数、类、模块、解构赋值、Promise、let 和 const 等等。这些新特性和 API 大大改进了 JavaScript 的开发体验,使得开发者们能够更加轻松地编写复杂的应用程序。
ES6 也被称为 ES2015,是因为它是 ECMAScript 标准的第 6 版。自 ES6 发布以来,JavaScript 社区也陆续发布了许多新的版本,包括 ES7、ES8、ES9 等等。每个版本都会引入新的语言特性和 API,以改进 JavaScript 的功能和可用性。但是,ES6 是 JavaScript 发展历史中的一个里程碑,为 JavaScript 开发带来了许多重大的变革。
ES6 提供了多种方式去除字符串前后空格
trim()
trim()
方法,这是字符串原型对象上自带的方法,会返回去除了前后空格的新字符串。
const str = " 全栈工匠 ";
const trimmedStr = str.trim();
console.log(trimmedStr); // 全栈工匠
trimStart() 和 trimEnd()
trimStart()
和 trimEnd()
方法:这两个方法分别用于去除字符串开头和结尾的空格,也是字符串原型对象上自带的方法。
const str = " 全栈工匠 ";
const trimmedStart = str.trimStart(); // 或 str.trimLeft()
const trimmedEnd = str.trimEnd(); // 或 str.trimRight()
console.log(trimmedStart); // '全栈工匠 '
console.log(trimmedEnd); // ' 全栈工匠'
正则表达式
使用正则表达式的 replace()
方法,将开头和结尾的空格替换为一个空字符串。
const str = " 全栈工匠 ";
const trimmedStr = str.replace(/^\s+|\s+$/g, "");
console.log(trimmedStr); // '全栈工匠'
在 JavaScript 中,可以使用 arguments
对象来实现可变参数。 arguments
是一个类数组对象,包含传递给函数的所有参数。可以在函数内部通过访问 arguments
对象来访问所有参数。
以下是一个使用 arguments 对象实现可变参数的示例:
function sum() {
let total = 0;
for (let i = 0; i < arguments.length; i++) {
total += arguments[i];
}
return total;
}
console.log(sum(1, 2, 3)); // 6
console.log(sum(4, 5, 6, 7)); // 22
在 ES6 中还可以通过扩展运算符来实现可变参数。
function sum(...numbers) {
let total = 0;
for (let i = 0; i < numbers.length; i++) {
total += numbers[i];
}
return total;
}
console.log(sum(1, 2, 3)); // 6
console.log(sum(4, 5, 6, 7)); // 22
在JavaScript中,null
和 undefined
都表示无值(no value)的含义,但是它们在具体使用中有一些不同之处:
undefined
表示未定义,即声明了但未赋值的变量的默认值,或者访问对象不存在的属性时返回的值。例如:const x; console.log(x);
输出undefined
。null
表示空值,即一个变量被明确地赋值为空。例如:const x = null; console.log(x);
输出null
。
虽然它们都表示无值,但在某些情况下,它们是不等价的。例如,使用双等号(==
)比较时,null
和 undefined
会被视为相等的值,但在使用严格相等运算符(===
)比较时,它们是不相等的。同时,在使用 typeof
运算符时,null
会被视为一个对象,而 undefined
会被视为未定义的变量。
总结大概如下:
null
是一个赋值。这没有任何意义。undefined
表示变量已声明但尚未定义。null
是一个对象。undefined
是类型undefined
。null !== undefined
但是null == undefined
。
在 JavaScript 中,valueOf()
方法是一个可以被对象调用的方法,用于返回该对象的原始值。它可以被所有对象类型(包括自定义对象)调用,并返回一个原始值,例如字符串、数字、布尔值等。
对象
下面是一个示例,展示了在 JavaScript 中对象使用 valueOf()
方法:
let myObj = {
name: "Quintion",
age: 30,
toString() {
return `${this.name} 年龄 ${this.age}`;
},
valueOf() {
return this.age;
},
};
console.log(myObj.toString()); // Quintion 年龄 30
console.log(myObj.valueOf()); // 30
数字
在 JavaScript 中的数字是 Number
类型的实例,具有 valueOf()
方法,返回一个数字对象的原始值。
例如,如果有一个数字对象 num
,可以使用 valueOf()
方法获取它的原始值,如下所示:
const num = new Number(42);
console.log(num.valueOf()); // 42
大多数情况下,JavaScript 引擎会自动将数字对象转换为数字类型,因此通常不需要使用 valueOf()
方法来获取数字对象的值。例如:
const num = new Number(42);
const result = num + 10;
console.log(result); // 52
字符串
字符串是 String
类型的实例,也具有 valueOf()
方法,返回一个字符串对象的原始值。
例如,如果有一个字符串对象 str
,可以使用 valueOf()
方法获取它的原始值,如下所示:
const str = new String("hello");
console.log(str.valueOf()); // 'hello'
同样,JavaScript 引擎会自动将字符串对象转换为字符串类型,因此不需要使用 valueOf()
方法来获取字符串对象的值。例如:
const str = new String("hello");
const result = str + " world";
console.log(result); // 'hello world'
上面代码 str
会被自动转换为字符串类型,因此可以将它与另一个字符串连接起来。