0%

Web API

  • REST - Representational State Transfer
  • SOAP - Simple Object Access Protocol
  • GraphQL - A query language for your API

difference between REST and SOAP

Feature REST SOAP
Protocol Can use any protocol but typically uses HTTP/HTTPS Uses HTTP, SMTP, TCP, and more
Standards No official standard, uses HTTP methods (GET, POST, PUT, DELETE) Official standard by W3C
Performance Lightweight, less data overhead Heavyweight, more data overhead due to XML usage
Message format Can use multiple formats (JSON, XML, etc.) Uses XML
Security Uses web security standards Uses WS-Security which is more robust
Transaction support No official support Official support
State management Stateless Stateful

说明:

  1. SOAP by itself is a protocol, but it can use any protocol to transport its messages, REST is not a protocol, it typically uses HTTP/HTTPS.
  2. REST is stateless which means each request from client to server must contain all of the information necessary to understand the request, and cannot take advantage of any stored context on the server. Session state is therefore kept entirely on the client.

JavaScript File Collecting

今天收到老婆的一个需求,她有一个文件夹,里面包含若干文件和子文件夹,子文件夹又包含文件及子文件夹,她需要将这些文件全部收集到一个文件夹中。收集后的文件要从1开始命名,依次递增。我想到了 Node.js,可以使用 Node.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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
const fs = require('fs').promises;
const path = require('path');

let fileIndex = 3001;

async function copyAndRenameFiles(srcDir, destDir) {
try {
// 确保目标目录存在
await fs.access(destDir);
} catch {
// 如果不存在,则创建目标目录
await fs.mkdir(destDir, { recursive: true });
}

// 读取srcDir中的所有文件和文件夹
const entries = await fs.readdir(srcDir, { withFileTypes: true });

for (const entry of entries) {
const srcPath = path.join(srcDir, entry.name);
console.log(srcPath);

if (entry.isDirectory()) {
// 如果是文件夹,则递归调用
await copyAndRenameFiles(srcPath, destDir);
} else if (entry.isFile()) {
const destBaseName = `${fileIndex}`; // 新文件的基本名称
const destExtname = path.extname(entry.name); // 获取原文件的扩展名
const destPath = path.join(destDir, `${destBaseName}${destExtname}`); // 目标文件完整路径
// 如果是文件,则复制到目标文件夹并重命名
await fs.copyFile(srcPath, destPath);
fileIndex++; // 更新文件索引
}
}

return fileIndex; // 返回下一个可用的文件索引
}

// 使用示例
const sourceFolder = 'xxx'; // 顶层文件夹路径
const destinationFolder = 'xxx'; // 目标文件夹路径

// 从1开始编号
copyAndRenameFiles(sourceFolder, destinationFolder)
.then((finalFileIndex) => {
console.log(
`所有文件已成功复制到目标文件夹,并从1开始重新编号到${
finalFileIndex - 1
}。`
);
})
.catch((error) => {
console.error('在复制过程中发生错误:', error);
});

代码实现-ChatGPT4

这个版本一次通过,一行未改。

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
const fs = require('fs').promises;
const path = require('path');

// 这里使用参数arrayOfFiles递归收集所有文件,思路很巧妙。
async function getAllFiles(dirPath, arrayOfFiles) {
const files = await fs.readdir(dirPath);

arrayOfFiles = arrayOfFiles || [];

for (const file of files) {
const filepath = path.join(dirPath, file);
const stat = await fs.stat(filepath);

if (stat.isDirectory()) {
arrayOfFiles = await getAllFiles(filepath, arrayOfFiles);
} else {
arrayOfFiles.push(filepath);
}
}

return arrayOfFiles;
}

async function copyFilesWithNumberedNames(sourceDir, targetDir) {
try {
// Create target directory if it does not exist
await fs.mkdir(targetDir, { recursive: true });

// Get all files from the source directory
const filesToCopy = await getAllFiles(sourceDir);

// Copy each file with a new numbered name
let fileNumber = 1;
for (const file of filesToCopy) {
const newFilename = `${fileNumber}${path.extname(file)}`;
const targetPath = path.join(targetDir, newFilename);
await fs.copyFile(file, targetPath);
console.log(`Copied ${file} to ${targetPath}`);
fileNumber++;
}

console.log('All files have been copied successfully.');
} catch (err) {
console.error('An error occurred:', err);
}
}

const sourceDirectory = 'xxx'; // Replace with your source directory path
const targetDirectory = 'xxx'; // Replace with your target directory path

copyFilesWithNumberedNames(sourceDirectory, targetDirectory);

JavaScript Return

今天一起了解一下 JavaScript 中的 return 语句。

语法

return 语句用于指定函数的返回值。当 JavaScript 遇到 return 语句时,函数将立即返回,并且不再执行任何后续的语句。

1
return [expression]; // expression 是要返回的值
1
2
3
4
5
function add(a, b) {
return a + b;
}

console.log(add(1, 2)); // 3

如果省略 expression,或者 return 语句没有在函数中,则返回值为 undefined

1
return; // undefined

return后面省略表达式,返回值为undefined

1
2
3
4
5
6
7
function foo() {
const a = 1;
const b = 2;
return;
}

console.log(foo()); // undefined

没有return语句,也返回undefined

1
2
3
4
5
6
function bar() {
const a = 1;
const b = 2;
}

console.log(bar()); // undefined

返回对象

return 语句返回复杂值时,return不能独占一行,否则会报错。

下面这样写会导致语法错误。

1
2
3
4
5
6
7
function getPerson() {
return // return 不能独占一行
{
name: 'zdd',
age: 18
};
}

正确的写法是:

1
2
3
4
5
6
function getPerson() {
return { // '{' 与 'return' 在同一行
name: 'zdd',
age: 18
};
}

try…catch…finally

如果在 try-catch-finally 语句块中使用了 return 语句,则执行顺序如下:

  1. 先计算return后面的表达式的值。
  2. 然后执行 finally 语句块中的代码。
  3. 最后return步骤1中计算出的值。
1
2
3
4
5
6
7
8
9
function foo() {
try {
console.log(1);
return 2;
} finally {
console.log('3');
}
}
console.log(foo()); // 1 3 2

再看一个复杂的例子

1
2
3
4
5
6
7
8
9
10
11
function foo() {
let a = 1;
try {
console.log(a);
return a++;
} finally {
console.log(a);
}
}

console.log(foo()); // 1 2 1

为啥结果是1 2 1呢?

  • try中`console.log(a)输出1,这个很好理解。
  • return a++,这里的a++是先返回a的值,然后再执行a++,所以return返回的是1
  • finallyconsole.log(a)输出的是2,因为a++执行了一次。

所以执行的顺序是:

  1. 先计算return后面的表达式,a = 2.
  2. 然后执行finally中的代码,输出a = 2
  3. 最后return返回1.

JavaScript Regex Groups

分组匹配是正则表达式中非常重要的一部分,它允许我们将多个字符组合在一起,并对它们进行操作。在JavaScript正则表达式中,我们可以使用括号 () 来创建一个分组。在这篇文章中,我们将学习如何使用分组匹配。

匿名分组

匿名分组的格式如下,其中 xxx 是要匹配的内容。匿名分组返回的匹配没有名字,只能通过数组下标的形式访问。

1
(xxx)

给定字符串Personal info: name: zdd, age: 18, gender: male,如果要从中提取出姓名,年龄和性别,则可以使用匿名分组来实现。

1
2
3
4
5
6
7
const str = `Personal info: name: zdd, age: 18, gender: male`;
const regex = /name: (\w+), age: (\d+), gender: (\w+)/;
const match = str.match(regex);
console.log(match); // 返回整个匹配 name: zdd, age: 18, gender: male
console.log(match[1]); // 第一个分组:zdd
console.log(match[2]); // 第二个分组:18
console.log(match[3]); // 第三个分组:male

在上面的例子中,正则表达式/name: (\w+), age: (\d+), gender: (\w+)/中有三个(),所以一共包含三个分组:

  • 第一个分组: \(w+) - 匹配zdd
  • 第二个分组:(\d+) - 匹配18
  • 第三个分组:(\w+) - 匹配male

使用匿名分组时,返回值是一个数组,数组第一个元素是整个匹配,我们要的分组匹配结果从数组第二个元素(下标1)开始,返回值如下:

1
2
3
4
5
6
7
8
9
[
'name: zdd, age: 18, gender: male',
'zdd', // 第一组
'18', // 第二组
'male', // 第三组
index: 0,
input: 'name: zdd, age: 18, gender: male',
groups: undefined
]

注意,使用匿名分组时,返回值中groups属性值为undefined,这个值只有使用命名匹配时才有值。

命名分组

命名分组的格式如下,其中 name 是分组的名称,xxx 是要匹配的内容。命名匹配返回的分组有名字,除了通过数组下标访问外,还可以通过对象属性的方式访问。

1
(?<name>xxx)

还是以上面的字符串为例,我们使用命名分组来提取姓名,年龄和性别。

1
2
3
4
5
6
7
const str = `Personal info: name: zdd, age: 18, gender: male`;
const regex = /name: (?<name>\w+), age: (?<age>\d+), gender: (?<gender>\w+)/;
const match = str.match(regex);
console.log(match.groups); // [Object: null prototype] { name: 'zdd', age: '18', gender: 'male' }
console.log(match.groups.name); // zdd
console.log(match.groups.age); // 18
console.log(match.groups.gender); // male

match返回值如下:与匿名匹配相比,命名匹配的返回值中groups属性不再是undefined,而是一个对象,对象的属性名就是分组的名称,属性值就是匹配的结果。

1
2
3
4
5
6
7
8
9
[
'name: zdd, age: 18, gender: male',
'zdd',
'18',
'male',
index: 0,
input: 'name: zdd, age: 18, gender: male',
groups: [Object: null prototype] { name: 'zdd', age: '18', gender: 'male' }
]

命名匹配的好处是,除了可以使用数组下标来访问分组匹配外,还可以使用groups字段来获取分组匹配。比如上面的match.groups.name就表示name分组对应的值。

再强调一次:groups字段只有在命名匹配时才有值,匿名匹配时用不到这个字段。

javascript regex summary

js中正则表达式的几种应用场景:

验证

  • RegExp.prototype.test

提取

  • String.prototype.match
  • String.prototype.matchAll
  • RegExp.prototype.exec
  1. match/exec方法,如果没有分组匹配,则通常取matches[0],如果有分组匹配,则取matches[1]、matches[2]...

替换

  • String.prototype.replace
  • String.prototype.replaceAll

拆分

  • String.prototype.split

javascript regex exec

exec是正则表达式的方法,用来返回匹配的字符串。

exec的返回值

exec的返回值是一个数组,数组的第一个元素是匹配的字符串,后面的元素是分组匹配的值。看一个列子:

1
2
3
4
const str = 'There are 3 dogs, 5 cats, 2 birds and 1 cow';
const regex = /(\d+) (cat|dog|bird|cow)/g;
const match = regex.exec(str);
console.log(match);

返回值如下:

1
2
3
4
5
6
7
8
[
'3 dog', // 匹配的整个字符串
'3', // 第一个分组匹配的值
'dog', // 第二个分组匹配的值
index: 10, // 匹配值对应的下标
input: 'There are 3 dogs, 5 cats, 2 birds and 1 cow', // 原始字符串
groups: undefined // 分组匹配的值
]

循环处理

exec通常配合while循环使用,一边遍历,一边处理结果。注意,如果使用while循环处理,需要正则表达式中添加/g标志。

1
2
3
4
5
6
7
8
9
const str = 'There are 3 dogs, 5 cats, 2 birds and 1 cow';
const regex = /(\d+) (cat|dog|bird|cow)/g; // 添加/g标志
let match;
const result = {};
while ((match = regex.exec(str)) !== null) {
result[match[2]] = match[1];
}

console.log(result);

输出结果:

1
2
3
4
5
6
{
dog: '3',
cat: '5',
bird: '2',
cow: '1'
}

/g标志

如果想要使用while循环来遍历所有匹配的字符串,需要使用/g标志。否则会导致while死循环。

match/matchAll vs exec

  1. match/matchAll - 一次性返回所有匹配的字符串。
  2. exec - 逐个返回匹配的字符串。

match/matchAll使用/g时,需要对结果进行遍历。
exec是一边遍历,一边处理结果。

javascript正则表达式之match

先说一个违反直觉的事情,虽然match是js正则表达式操作中非常重要的一个方法,但是它却不是regex对象的方法,而是字符串对象的方法。

match用來返回符合某个正则表达式的字符串,如果没有找到匹配的字符串则返回null

先看一个例子,下面的代码用来返回字符串str中的第一个数字字符。

1
2
3
const str = "There are 3 dogs, 5 cats, 2 birds and 1 cow";
const matches = str.match(/\d/);
console.log(matches[0]); // 3, 返回第一个匹配。

match的返回值

match的返回值比较特殊,分以下几种情况,

  • 如果没有找到匹配,返回null
  • 如果找到了匹配,返回一个数组。

返回数组时,又分为以下几种情况

  1. 如果正则表达式没有g标志(且未分组),返回一个数组,看如下代码。
1
2
3
const str = "There are 3 dogs, 5 cats, 2 birds and 1 cow";
const matches = str.match(/\d/);
console.log(matches);

返回值如下:

1
2
3
4
5
6
[
'3', // 匹配
index: 10, // 匹配值对应的下标
input: 'There are 3 dogs, 5 cats, 2 birds and 1 cow', // 原始字符串
groups: undefined // 分组匹配的值
]

此时,你需要的匹配值是matches[0]

  1. 如果正则表达式没有g标志且分组,(注意(\d)中的小括弧表示分组),返回一个数组,比之上面的返回值,多一个分组对应的值。
1
2
3
const str = "There are 3 dogs, 5 cats, 2 birds and 1 cow";
const matches = str.match(/(\d) cats/);
console.log(matches);

返回值如下:

1
2
3
4
5
6
7
8
[
'5 cats', // 匹配的整个字符串
'5', // 第一个分组匹配的值
index: 18, // 匹配值对应的下标
input: 'There are 3 dogs, 5 cats, 2 birds and 1 cow', // 原始字符串
groups: undefined // 分组匹配的值
]

此时,你需要的匹配值是matches[1]。(既然分组了,就要用分组的值呀,否则还分组干嘛呢?)

  1. 如果正则表达式有g标志(无论是否分组),返回一个数组,数组中的元素是所有匹配的值。
1
2
3
const str = "There are 3 dogs, 5 cats, 2 birds and 1 cow";
const matches = str.match(/\d/g); // 或者 str.match(/(\d)/g);
console.log(matches);

返回值如下:

1
[ '3', '5', '2', '1' ]

matchAll

match方法使用/g标记时,只能返回匹配的字符串(上面第三点),而无法返回分组信息,如果要返回分组信息,那么可以使用matchAll方法。

matchAll方法返回一个迭代器,而不是数组,所以不能直接输出,可以使用for...of循环遍历匹配的所有结果。

1
2
3
4
5
const str = "There are 3 dogs, 5 cats, 2 birds and 1 cow";
const matches = str.matchAll(/(\d) (cats|dogs)/g);
for (const match of matches) {
console.log(match);
}

输出如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[
'3 dogs', // 匹配的整个字符串
'3', // 第一个分组匹配的值
'dogs', // 第二个分组匹配的值
index: 10, // 匹配值对应的下标
input: 'There are 3 dogs, 5 cats, 2 birds and 1 cow', // 原始字符串
groups: undefined // 分组匹配的值
]
[
'5 cats', // 匹配的整个字符串
'5', // 第一个分组匹配的值
'cats', // 第二个分组匹配的值
index: 18, // 匹配值对应的下标
input: 'There are 3 dogs, 5 cats, 2 birds and 1 cow', // 原始字符串
groups: undefined // 分组匹配的值
]

假设有如下需求,将There are 3 dogs, 5 cats, 2 birds and 1 cow这句话中每种动物及其数量提出出来,放到一个对象中,则可以使用matchAll方法。

1
2
3
4
5
6
7
const str = 'There are 3 dogs, 5 cats, 2 birds and 1 cow';
const matches = str.matchAll(/(\d+) (cat|dog|bird|cow)/g);
const result = {};
for (const match of matches) {
result[match[2]] = match[1];
}
console.log(result);

输出如下:

1
{ dog: '3', cat: '5', bird: '2', cow: '1' }

整个需求如果要使用match方法来做,还是比较复杂的。

总结

什么时候用test, 什么时候用match

  1. 如果你只是想判断字符串是否满足某个规则,用test
  2. 如果你想要得到匹配的子串,用match
  3. 简单的字符串包含操作可以直接使用indexOfincludes等方法。

javascript-regex-test

简介

javascript对正则表达式的支持非常好,你可以通过多种方式在js中使用正则表达式,常用的有test, exec, match等方法。本文主要介绍test方法。test是javascript中Regex对象上的一个方法,用来检测字符串是否满足某种规则,如果你想要检测字符串是否满足某个规则,而不需要提取匹配后的字符串,那么test是最好的选择。举个例子:判断一个字符串中是否包含单词hello

1
2
3
4
5
6
7
8
const regex = new RegExp('hello');
const str = 'hello, world!';

if (regex.test(str)) {
console.log(true);
} else {
console.log(false);
}

由于js中正则表达式可以用//包裹,所以我们可以简化一下上面的代码:

1
2
3
const regex = /hello/;
const str = 'hello, world!';
console.log(regex.test(str)); // true

再简化一下,甚至可以写成一行:

1
console.log(/hello/.test('hello, world!')); // true

规则:regex.test(str), 这个表达式中,调用者是正则表达式,参数是字符串,返回值是布尔值。

大小写无关的匹配

默认情况下,正则表达式是匹配时是区分大小写的,如果要忽略大小写进行匹配,可以在正则表达式后面加上/i标志。

1
console.log(/hello/i.test('Hello, world!')); // true

多行匹配

默认情况下,正则表达式是单行匹配的,如果要多行匹配,可以在正则表达式后面加上/m标志。

1
2
3
4
5
6
7
const str = `
Hi,
hello,
world.
`;

console.log(/^hello/.test(str)); // false

因为默认情况下,^只匹配字符串的开头,所以上面的代码返回false。如果要多行匹配,可以这样写:

1
2
3
4
5
6
const str = `
Hi,
hello,
world.
`;
console.log(/^hello/m.test(str)); // true

/m表示多行匹配,即使hello不在字符串的首行,也能匹配到。
注意,这里/m真正的含义如下:

  • 如果不加/m^$匹配整个字符串的开头和结尾。
  • 如果加了/m^$匹配每一行的开头和结尾。

以上面代码为例,如果不加/m,则^hello匹配整个字符串的开头,但是整个字符串的开头是Hi,,所以返回false。如果加了/m,则^hello匹配每一行的开头,而第二行的开头是hello,所以返回true

注意, 如果没有给test传递参数,那么test会尝试匹配字符串undefined

1
console.log(/undefined/.test()); // true

全局匹配

默认情况下,test只会匹配第一个满足条件的字符串,如果要全局匹配,可以在正则表达式后面加上/g标志。

注意:你永远不需要在test方法上加/g标志,因为test方法只返回一个布尔值,而不是匹配到的字符串。
使用/g标志会改变正则表达式的lastIndex属性,这个属性会影响到test方法的行为,导致每次test匹配都从lastIndex开始,而不是从字符串的开头开始。

下面的代码中,为何第二次调用test方法返回false呢?

1
2
3
4
5
6
7
const regex = /foo/g;

// lastIndex = 0;
console.log(regex.test('foo')); // true

// lastIndex = 3;
console.log(regex.test('foo')); // false

分析一下:第一次匹配开始之前,lastIndex = 0, 指向字符串开头,第一次匹配成功后,lastIndex = 3, 下一次匹配从则从下标3开始,但是字符串foo长度为3,后面已经没有字符了,所以导致第二次匹配失败!崩溃吗?

/g选项通常用于String.prototype.replace方法。

1
2
3
const str = 'foobarfoo';
console.log(str.replace(/foo/, '')); // barfoo, 只替换了第一个foo
console.log(str.replace(/foo/g, '')); // bar, 替换了所有的foo

javascript中还有一个String.prototype.replaceAll方法,看名字好像会替换所有满足条件的匹配,但是使用这个方法时,必须添加/g标志,否则会报错,真是脱裤子放屁呀!

1
2
3
const str = 'foobarfoo';
console.log(str.replaceAll(/foo/, '')); // TypeError: String.prototype.replaceAll called with a non-global RegExp argument
console.log(str.replaceAll(/foo/g, '')); // bar

References

Hoisting in JavaScript

JavaScript Hoisting refers to the process whereby the interpreter appears to move the declaration of functions, variables, classes, or imports to the top of their scope, prior to execution of the code.

Variable Hoisting

1
2
console.log(a); // undefined
var a = 1;

The above code is equivalent to:

1
2
3
var a;
console.log(a); // undefined
a = 1;

注意变量提示只提升到它所在的作用域的顶部,而不是全局作用域的顶部。

1
2
3
4
5
6
7
8
9
function outer() {
console.log(a); // ReferenceError: a is not defined
function inner() {
console.log(a); // undefined
var a = 1;
}
inner();
}
outer();

The above code is equivalent to:

1
2
3
4
5
6
7
8
9
10
11
function outer() {
console.log(a); // ReferenceError: a is not defined
function inner() {
var a; // a is hoisted to the top of its enclosing function `inner`.
console.log(a); // undefined
a = 1;
}

inner();
}
outer();

Function Hoisting

1
2
3
4
foo(); // hello
function foo() {
console.log('hello');
}

The above code is equivalent to:

1
2
3
4
function foo() {
console.log('foo');
}
console.log(foo); // [Function: foo]

注意函数提升和变量提升一样,只提升到它所在的作用域的顶部,而不是全局作用域的顶部。

1
2
3
4
5
6
7
8
9
10
inner(); // ReferenceError: inner is not defined

function outer() {
inner(); // 'Hello'

function inner() {
console.log('Hello');
}
}
outer();

Hoisting in ES6

ES6中的letconst不会被提升,所以在使用之前必须先声明。letconst会产生暂时性死区(Temporal Dead Zone)。

1
2
console.log(a); // ReferenceError: Cannot access 'a' before initialization
let a = 1;

关于这一点实际上是有争议的,有些观点认为,letconst是被提升的,只是在TDZ中,不能访问。看下面的代码:

1
2
3
4
5
const x = 1;
{
console.log(x); // ReferenceError: Cannot access 'x' before initialization
const x = 2;
}

如果我们把const x = 2;注释掉,那么代码就可以正常运行,此时x使用的是外层的x。这说明const x = 2;是被提升的(进而掩盖了外层的x),只是在TDZ中不能访问。

1
2
3
4
5
const x = 1;
{
console.log(x); // 1
// const x = 2;
}

Temporal dead zone (TDZ)

A variable declared with let, const, or class is said to be in a "temporal dead zone" (TDZ) from the start of the block until code execution reaches the place where the variable is declared and initialized.
下面的代码中,

1
2
3
4
5
6
7
8
9
10
11
12
// Temporal dead zone (TDZ), TDZ 从block开始的地方开始,到其定义的地方结束。
// 在TDZ中访问let定义的变量会产生ReferenceError。
// 而var定义的变量则不存在此问题,因为var有hoisting(变量提升)
{
// TDZ starts at beginning of scope
console.log(bar); // undefined
console.log(foo); // ReferenceError
var bar = 1;
let foo = 2; // End of TDZ (for foo)
const xxx = 3;
}

Arrow function is not hoisted.

1
2
3
4
foo(); // ReferenceError: Cannot access 'foo' before initialization
const foo = () => {
console.log('Hello');
}

Function expression is not hoisted.

1
2
3
4
foo(); // ReferenceError: Cannot access 'foo' before initialization
const foo = function() {
console.log('foo');
}

difference between variable hoisting and function hoisting

变量提升只提升变量的声明,不提升赋值,所以变量提升后的值是 undefined。

1
2
console.log(a); // undefined
var a = 1;

函数提升是整体提升,所以可以放心的调用。

1
2
3
4
foo(); // 'hello'
function foo() {
console.log('hello');
}

在Modern JavaScript中,应避免使用变量提升和函数提升,遵循以下三点可以保证:

  1. Use let and const instead of var
  2. Use strict mode.
  3. Always declare variables and functions before using them.

面试题

第一题

以下代码输出什么?

1
2
3
4
5
6
7
8
9
10
11
12
13
function foo() {
function bar() {
return 3;
}

return bar();

function bar() {
return 8;
}
}

console.log(foo()); // 8

答案:输出8。

提升后的代码如下,注意:JavaScript中允许同名函数存在,后面的函数会覆盖前面的函数。

1
2
3
4
5
6
7
8
9
10
11
function foo() {
function bar() {
return 3;
}

function bar() {
return 8;
}

return bar();
}

第二题

以下代码输出什么?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function foo() {
var a = 1;

function b() {
a = 10;
return;
function a() {}
}

b();
console.log(a);
}

foo();

答案:1

提升后的代码如下,function a()的声明提升至function b()的顶部,而a = 10;是对function a()的再赋值,不会影响外部的变量a

1
2
3
4
5
6
7
8
9
10
11
12
13
function foo() {
var a;
a = 1;

function b() {
function a() {}
a = 10;
return;
}

b();
console.log(a);
}

如果我们把function a() {}注释掉,那么代码就会输出10,因为此时a = 10;是对外部变量a的赋值。

第三题

以下代码输出什么?

1
2
3
4
5
```

### 第四题
以下代码输出什么?
```javascript

Reference:

MDN: Hoisting

TCP Open and Close

TPC连接需要三次握手

第一次握手(客户端发送 SYN 报文给服务器,服务器接收该报文):客户端什么都不能确认;服务器确认了对方发送正常,自己接收正常。

第二次握手(服务器响应 SYN 报文给客户端,客户端接收该报文):客户端确认了:自己发送、接收正常,对方发送、接收正常;服务器确认了:对方发送正常,自己接收正常。

第三次握手(客户端发送 ACK 报文给服务器):客户端确认了:自己发送、接收正常,对方发送、接收正常; 服务器确认了:自己发送、接收正常,对方发送、接收正常

tcp-open

TCP断开需要四次挥手

四次挥手过程如下图所示,流程如下:

  1. 客户端数据数据发送完毕,想关闭连接,发送FIN给服务器端。此时客户端进入FIN-WAIT-1状态

  2. 服务器端收到客户端的FIN,并返回ACK给客户端,确认已经收到客户端的关闭通知。

    当客户端收到服务器端的ACK通知后,不能立即关闭连接,因为服务器端可能还要继续发送数据。所以此时客户端进入FIN-WAIT-2状态。

    也就是在这个时间点上,客户端只是不再发送数据了,但是可能还要接收数据。

  3. 待服务器端数据发送完毕,发送FIN给客户端,告知客户端,我也发完了,准备关闭。此时服务器端进入CLOSE-WAIT状态。

  4. 客户端收到来自服务端的FIN后,返回ACK给服务端,表示我收到你的关闭通知了,你可以关闭了,服务端收到客户端的ACK后,立即关闭,状态变为CLOSE。客户端发送完ACK后,进入TIME-WAIT状态,且等待2ms后自动关闭,状态变为CLOSE。

所以,为啥要等2ms才能关闭?

tcp-close