JavaScript
初识
JavaScript是一种由ECMAScript标准规范定义的编程语言。它的实现可以是开源的。
在浏览器中,JavaScript 是单线程执行的。这意味着在任何给定时间点,JavaScript 代码只能由一个线程执行。这个单线程通常称为主线程或 UI 线程。
NodeJS中JavaScript 也可以实现多线程编程,例如Worker Threads API 允许开发者在 Node.js 中创建独立的线程,这些线程可以执行 CPU 密集型任务、并行处理数据或执行其他需要并发执行的操作。这些线程是由操作系统调度和管理的。
但是,需要注意的是,Node.js 是单线程的事件驱动模型,主线程上的事件循环仍然是单线程的,因此在任何给定时刻只有一个事件在主线程上执行。而 Worker 线程的执行是在独立的 JavaScript 执行环境中进行的,它们可以并行执行代码,它们之间通过线程间的消息传递机制进行通信,但不会影响主线程的事件循环。
启动每个 Worker 线程都会消耗一定的系统资源,包括内存和 CPU 资源。每个 Worker 线程都有自己的 JavaScript 执行环境和相关的资源,因此在启动大量的 Worker 线程时需要考虑系统资源的限制。
引擎
JavaScript引擎(或称为JavaScript解释器),遵循ECMAScript规范,并将其转换为可在浏览器或其他环境中执行的机器码或字节码。
V8和SpiderMonkey引擎(开源)在执行JavaScript代码时,首先会对代码进行解析和编译,然后生成优化的中间表示(Intermediate Representation,IR),作用就是为了优化过程涉及诸如内联函数、去除冗余代码、变量的寄存器分配等技术,垃圾回收优化,最后将IR转换为本地机器代码,旨在提高JavaScript代码的执行效率。
参考:
引擎实现
通常通常所说的浏览器引擎就是指用于解释和执行JavaScript代码的软件组件。
- JavaScript的主要开源实现之一是Mozilla基金会的SpiderMonkey引擎,它是Mozilla Firefox浏览器的JavaScript引擎
- V8引擎是由Google开发和维护的,用于Chrome浏览器和Node.js等项目
ECMAScript
ECMAScript(简称ES)是一种脚本语言标准,用于定义JavaScript语言的语法和语义。它由Ecma国际(前身为欧洲计算机制造商协会)制定,并且定期进行更新和修订。
参考:
基础
数据类型
基本数据类型
当将一个基本数据类型的变量赋值给另一个变量时,会直接将原始数据的值复制给新变量,而新变量与原始变量是完全独立的,修改其中一个变量的值不会影响另一个变量。
- 包括数字(Number)
- 字符串(String)
- 布尔值(Boolean)
- null
- undefined
- Symbol
1 | javascriptCopy codelet a = 10; |
引用数据类型
当将一个引用类型的变量赋值给另一个变量时,只是复制了原始数据的引用地址,而不是复制实际数据。因此,新变量与原始变量共享同一个对象(或数组、函数等),修改其中一个变量的属性或元素,会影响到另一个变量。
- 对象(Object)
- 数组(Array)
- 函数(Function)
- 正则(RegExp)
- 日期(Date)
1 | let arr1 = [1, 2, 3]; |
var、let、const
参考:
关键字 | 作用域 | 同一作用域重复声明 | 绑定全局对象 | |
---|---|---|---|---|
var | 如果:函数内,则只作用于函数内,跟随函数生命周期 如果:函数外,则脚本内全局,跟随脚本生命周期 |
允许,将覆盖之前的定义。 | 绑定 | |
let | 所处的代码块{} 内有效,外代码块与被包含代码块内外不互相影响,内的let只有效内,外let只有效外。 |
不允许 | 不绑定 (即:console.log(this.[let变量]) // undefined) |
|
const(声明只读,声明必须初始化) | 与let相同 | 不允许,不可被改写覆盖 |
需要注意的是,const
和 let
是在 ES6 (ECMAScript 2015) 中引入的,而 var
是旧版本 JavaScript 中使用的声明变量的方式。在现代的 JavaScript 开发中,推荐使用 const
和 let
来声明变量,根据具体的需求选择合适的关键字。const
适用于声明不需要重新赋值的常量或引用类型,而 let
适用于需要重新赋值的变量,同时提供了更好的作用域控制。var
在大多数情况下已经不推荐使用,但在特定情况下仍然可以使用,例如需要兼容旧版本 JavaScript 或在全局作用域中声明变量。
var 与 let作用域:
1 | function varTest() { |
var与let同一作用域:
var示例:
1
2
3
4
5
6
7
8
9
10
11
12
13var a = 1;
var a = 2;
console.log(a) // 2
function test() {
var a = 3;
var a = 4;
console.log(a) // 4
}
test()
let示例:
1
2
3
4if(false) {
let a = 1;
let a = 2; // SyntaxError: Identifier 'a' has already been declared
}
let 与 const :
相同
- 二者都是块级作用域
- 都不能和它所在作用域内的其他变量或函数拥有相同的名称
异同
const
声明的常量必须初始化,而let
声明的变量不用const 定义常量的值不能通过再赋值修改,也不能再次声明(数组、对象内部数据可修改)。而 let 定义的变量值可以修改。
1 | let a; |
开发常用
JS数组合并
参考:
ES6
的语法,简单实用1
2
3
4
5
6
7
8let arr = [1, 2]
let arr2 = [3, 4]
// ...可去除对象大括号、数组中括号
arr = [...arr, ...arr2]
console.log(arr)
// [1, 2, 3, 4]push
结合...[]
arr.push.apply(arr, arr2)
的作用是将arr2
数组中的元素依次添加到arr
数组中。这里使用了push
方法来向arr
数组中添加元素,apply
方法将arr2
数组中的元素作为参数传递给push
方法,并使用arr
数组作为this
值绑定到push
方法中。这样就可以将arr2
数组中的元素添加到arr
数组中了。1
2
3
4
5
6
7let arr = [1, 2]
let arr2 = [3, 4]
arr.push.apply(arr, arr2)
console.log(arr)
// [1, 2, 3, 4]1
2
3
4
5
6
7let arr = [1, 2]
let arr2 = [3, 4]
arr.push(...arr2)
console.log(arr)
// [1, 2, 3, 4]
${}在 Javascript 的字符串作用
参考:
字符串插值
1 | var foo = 'bar'; |
1 | const one = 1; |
打印多行
1 | console.log(`foo |
模板文字执行隐式类型转换
1 | let fruits = ["mango","orange","pineapple","papaya"]; |
find()修改对象属性
1 | var u = [ |
find找到了数组中符合条件的对象后返回给p,直接更改p即可准确更改元素中的值。
some()
some方法同样用于检测是否有满足条件的元素,如果有,则不继续检索后面的元素,直接返回true,如果都不符合,则返回一个false。
1 | let arr = [100,20,50,58,6,69,36,45,78,66,45] |
数组添加元素
splice()
它用于在数组中添加或删除元素,并返回被删除的元素。
1 | const arr = [1, 2, 3, 4, 5]; |
1 | array.splice(start[, deleteCount[, item1[, item2[, ...]]]]) |
start
:指定添加或删除元素的起始位置,可以为负数,表示从数组末尾开始算起的位置。deleteCount
:可选参数,表示要删除的元素个数,如果省略该参数或为 0,则不删除任何元素。item1, item2, ...
:可选参数,表示要添加到数组中的元素。
过滤数组空元素
过滤去除数组的空元素,返回false会删除,即null或者undefine。
参考:
例:
1 | var arr = ['2','3','',null,undefined,'7',' ','9']; |
柯里化
在柯里化中,原始函数接受多个参数,但通过柯里化的转换,它可以被分解成一系列嵌套的函数,每个函数只接受一个参数。每次调用这些嵌套的函数之一,它们会部分应用之前的参数,并返回一个新的函数,等待传入下一个参数。JavaScript 中的函数可以通过手动编写或使用库(如 Lodash、Ramda)来进行柯里化。
它很灵活,可以避免重复传入参数,当你传入第一个参数的时候,该函数就已经具有了第一个参数的状态(闭包)。
举例
(state) => (gCode) => {}
是一个函数的箭头函数表达式
1 | const setState = (state) => (gCode) => { |
Lodash
Lodash 的设计目标是提供高性能、模块化和易于使用的实用函数。它封装了许多常见的操作,帮助开发人员减少编写重复代码的工作量,提高开发效率。Lodash 的函数库被广泛应用于前端开发和后端开发,可以在浏览器环境和 Node.js 环境中使用。
参考:
- 数组操作:
1 | javascriptCopy codeimport { chunk, filter, map } from 'lodash'; |
- 对象操作:
1 | javascriptCopy codeimport { pick, omit } from 'lodash'; |
- 函数式编程:
1 | javascriptCopy codeimport { curry, flow } from 'lodash'; |
Promise
当 JavaScript 主线程在执行过程中遇到 async 函数或者包含 await 关键字的操作时,主线程会被暂停,等待 await 后面的 Promise 对象解析完成。这种暂停是异步的,意味着主线程会继续执行其他同步任务,直到遇到 await 处的异步操作完成为止。一旦 Promise 被解析完成,主线程会恢复执行 await 后面的代码。这种机制确保了异步操作在执行过程中不会阻塞主线程的运行。
同步:
- 同步操作意味着任务按顺序执行,每个任务必须等待前一个任务完成后才能开始。
- 在同步模式下,如果一个任务正在执行,程序将会等待(阻塞)直到任务完成,然后才继续执行下一个任务。
- 这种方式简单直观,但可能导致程序效率低下,特别是在等待某些耗时操作(如文件读写、网络请求)时。
异步:
- 异步操作允许任务在等待另一个任务完成时开始执行,不需要按顺序等待每个任务完成。
- 在异步模式下,程序可以发起一个任务,然后立即转而执行其他任务,而不是等待第一个任务完成。
- 异步通常通过回调函数、事件监听、Promise、async/await 等机制实现,提高了程序的效率和响应性。
传统异步和async/await异步
传统异步会导致两个主要的问题分别是:
- 代码的可读性:通过使用
async/await
,代码的结构会更加清晰,易于理解,易于try...catch
。传统嵌套在then()
的方式复杂可读性差。- 一般情况下传统方式只能返回Promise状态,不能返回Promise对象,特殊情况看最后传统方式返回Promise对象的代码。
参考:
传统方式异步
假设我们要实现一个异步操作,首先发起 HTTP 请求,然后再将请求结果传递给另一个异步操作处理,最后返回最终的处理结果。这个过程涉及到异步回调函数的嵌套,一般会使用 Promise 的 then 方法来处理。下面是一个简单的示例代码:
1 | function main() { |
可见以上情况 Promise 对象有可能会变成 reject 状态,如果不是 Promise 对象,无法利用 Promise 所带来的传播性质来有效地抛出错误。
async/await异步
- async:当一个函数被标记为
async
时,它就没了提心吊胆的稗子会返回一个 Promise 对象。- await:如果有需要等待异步操作完成的代码,可以使用
await
关键字来等待结果。在等待的过程中,函数的执行会暂停,直到异步操作完成并返回结果后再继续执行。
而如果使用异步函数直接返回 Promise 值的方式,就可以避免传统的异步嵌套问题,代码更加清晰易读。下面是使用异步函数的示例代码:
1 | async function main() { |
注意此段代码对比传统方式出现了try...catch
。
传统方式返回Promise对象
在很多 Node.js 应用中,仍然会使用这种传统的异步嵌套方式,并且会将异步操作封装成 Promise 对象,以便能够在 Promise 上继续使用 Promise 相关的 API,如 Promise chaining、try-catch 错误处理等。
下面是一个例子,展示了如何使用 Promise 封装一个传统异步嵌套的异步操作:
1 | function traditionalAsyncOperation(callback) { |
好难啊。。。
JS类&自执行函数
先看一段TS编译后的JS代码:
1 | class Student { |
编译后js:
1 | var Student = /** @class */ (function () { |
其中this.
的意思是,哪行代码new
了Student
,此行代码new
的这个对象中就会被赋值成员变量firstName、middleInitial、lastName、fullName。
引入.html
1 |
|
执行结果是Hello, Jane User
。
立即调用函数表达式
立即调用函数表达式(Immediately Invoked Function Expression,IIFE)
1 | var Student = (function () { |
在这个 IIFE 内部,我们定义了一个名为 Student
的类作为返回值。这个类定义完成后立即返回,返回的值将会被赋给 var Student
变量。这种方式可以让我们在不污染全局作用域的情况下使用这个 Student
类。