apply、bind、call对比

在 JavaScript 中,applybindcall 是用于改变函数执行上下文(即 this 关键字)的方法。它们在函数调用时非常有用。下面分别介绍这三个方法,并给出手写实现的代码。

1. apply

apply 方法调用一个函数,并显式地指定 this 的值和参数列表(以数组或类数组对象的形式)。

1
2
3
4
5
6
7
function greet(greeting, punctuation) {
console.log(greeting + ", " + this.name + punctuation);
}

const person = { name: "Alice" };

greet.apply(person, ["Hello", "!"]); // 输出: Hello, Alice!

2. call

call 方法与 apply 类似,也是调用一个函数,并显式地指定 this 的值和参数列表(以逗号分隔的形式)。

1
2
3
4
5
6
7
function greet(greeting, punctuation) {
console.log(greeting + ", " + this.name + punctuation);
}

const person = { name: "Alice" };

greet.call(person, "Hello", "!"); // 输出: Hello, Alice!

3. bind

bind 方法创建一个新的函数,当这个新函数被调用时,其 this 值会被指定为 bind 方法的第一个参数,其余参数将作为新函数的参数。

1
2
3
4
5
6
7
8
function greet(greeting, punctuation) {
console.log(greeting + ", " + this.name + punctuation);
}

const person = { name: "Alice" };

const greetPerson = greet.bind(person);
greetPerson("Hello", "!"); // 输出: Hello, Alice!

手写实现

下面是 applycallbind 的手写实现:

apply

1
2
3
4
5
6
7
8
9
10
11
12
13
Function.prototype.myApply = function (context = window, args = []) {
if (typeof this !== "function") {
throw new TypeError(this + " is not a function");
}

const fnSymbol = Symbol();
context[fnSymbol] = this;

const result = context[fnSymbol](...args);

delete context[fnSymbol];
return result;
};

call

1
2
3
4
5
6
7
8
9
10
11
12
13
Function.prototype.myCall = function (context = window, ...args) {
if (typeof this !== "function") {
throw new TypeError(this + " is not a function");
}

const fnSymbol = Symbol();
context[fnSymbol] = this;

const result = context[fnSymbol](...args);

delete context[fnSymbol];
return result;
};

bind

1
2
3
4
5
6
7
8
9
10
11
Function.prototype.myBind = function (context, ...args) {
if (typeof this !== "function") {
throw new TypeError(this + " is not a function");
}

const self = this;

return function (...newArgs) {
return self.apply(context, args.concat(newArgs));
};
};

示例

1
2
3
4
5
6
7
8
9
10
11
function greet(greeting, punctuation) {
console.log(greeting + ", " + this.name + punctuation);
}

const person = { name: "Alice" };

greet.myApply(person, ["Hello", "!"]); // 输出: Hello, Alice!
greet.myCall(person, "Hello", "!"); // 输出: Hello, Alice!

const greetPerson = greet.myBind(person);
greetPerson("Hello", "!"); // 输出: Hello, Alice!

对比

方法 是否调用 参数形式
apply 数组或类数组对象
call 逗号分隔的参数列表
bind 逗号分隔的参数列表

通过这些手写实现,可以更好地理解 applycallbind 的内部工作原理。

CommonJS中的exports、module.exports、和this

在 CommonJS 模块系统中,exportsmodule.exportsthis 是三个非常重要的对象,它们用于导出模块中的变量、函数或类。下面分别介绍这三个对象,并通过示例代码进行说明。

1. exports

exports 是一个对象,用于将模块中的变量、函数或类导出。默认情况下,exportsmodule.exports 的一个引用。

2. module.exports

module.exports 是一个对象,用于显式地导出模块中的内容。与 exports 不同的是,module.exports 可以被赋值为任何类型的值,包括对象、函数、类等。

3. this

在模块的上下文中,this 通常指向 exports 对象。这意味着你可以使用 this 来导出模块中的内容。

示例代码

假设我们有以下两个文件:example.jsmain.js

example.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// example.js
this.a = 1;
exports.b = 2;
exports = {
c: 3,
};
mudule.exports = {
d: 4,
};
exports.e = 5;
this.f = 6;
// ---当前结果---
// this {a:1,b:2,f:6}
// exports {c:3,e:5}
// module.exports {d:4}

main.js

1
2
3
// main.js
const example = require("./example");
console.log(example); // {d:4}

在上面的 main.js 文件中,我们分别调用了 example.js 中通过 exportsmodule.exportsthis 导出的函数。

总结

  • exports 是一个对象,用于将模块中的内容导出。
  • module.exports 可以被赋值为任何类型的值,用于显式地导出模块中的内容。(require 引入得到的东西)
  • this 通常指向 exports 对象,可以用于快捷地导出模块中的内容。

在实际开发中,推荐使用 module.exports 来导出模块,因为它提供了更大的灵活性。

ReactPHP实现简单的scoket服务器

首先,确保已经安装了 ReactPHP 库:

1
composer require react/react

然后,创建一个 PHP 文件,比如socket_server.php,并添加以下代码:

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
<?php

require 'vendor/autoload.php';

use React\Socket\Server;
use React\Socket\ConnectionInterface;
use React\EventLoop\Factory as LoopFactory;

// 创建事件循环
$loop = LoopFactory::create();

// 创建TCP服务器
$server = new Server('127.0.0.1:8080', $loop);

// 当有新的连接时
$server->on('connection', function (ConnectionInterface $conn) {
echo "New connection from {$conn->getRemoteAddress()}\n";

// 当接收到数据时
$conn->on('data', function ($data) use ($conn) {
echo "Received data: $data\n";

// 回显数据给客户端
$conn->write("Echo: $data\n");
});

// 当连接关闭时
$conn->on('close', function () use ($conn) {
echo "Connection closed from {$conn->getRemoteAddress()}\n";
});
});

echo "Server running at tcp://127.0.0.1:8080\n";

// 运行事件循环
$loop->run();

这个脚本创建了一个 TCP 服务器,监听在127.0.0.1:8080。当有新的连接时,它会打印出连接的客户端地址,并在接收到数据时回显数据给客户端。当连接关闭时,它会打印出连接关闭的消息。

要运行这个服务器,只需在命令行中执行:

1
php socket_server.php

然后,你可以使用 telnet 或其他 Socket 客户端工具连接到这个服务器,比如:

1
telnet 127.0.0.1 8080

连接成功后,你可以输入一些文本,服务器会回显你输入的内容。

为什么在JavaScript中['1', '5', '11'].map(parseInt) 返回 [1, NaN, 3]

在 JavaScript 中,['1', '5', '11'].map(parseInt) 返回 [1, NaN, 3] 是因为 map 方法和 parseInt 函数的参数传递方式导致的。

map 方法

map 方法会对数组中的每个元素执行一次提供的函数,并返回一个新数组,其中包含每次函数调用的结果。map 方法的回调函数接收三个参数:

  1. 当前处理的元素。
  2. 当前元素的索引。
  3. 原数组。

parseInt 函数

parseInt 函数用于将字符串解析为整数。它接受两个参数:

  1. 要解析的字符串。
  2. 可选的基数(即进制数)。

具体解释

['1', '5', '11'].map(parseInt) 执行时,map 方法会对数组中的每个元素调用 parseInt 函数。具体来说,parseInt 函数的调用顺序如下:

  1. parseInt('1', 0):基数 0 被视为十进制。
  2. parseInt('5', 1):基数 1 是无效的,parseInt 无法解析基数小于 2 或大于 36 的字符串。
  3. parseInt('11', 2):基数 2 表示二进制,11 在二进制中是 3

因此,map 方法返回的结果是 [1, NaN, 3]

修正方法

如果你希望将字符串数组中的每个元素转换为整数,可以使用箭头函数和 Number 函数,如下所示:

1
["1", "5", "11"].map(Number); // 返回 [1, 5, 11]

或者,如果你确实需要使用 mapparseInt,可以显式地传递索引参数,并确保不传递无效的基数:

1
["1", "5", "11"].map((item, index) => parseInt(item, 10)); // 返回 [1, 5, 11]

这样,parseInt 函数将始终以十进制解析字符串。

在 JavaScript 中,parseInt 函数的第二个参数是基数(也称为进制数),用于指定要解析的字符串的基数。基数的有效范围是 2 到 36。如果省略了基数,parseInt 会默认使用 10 进制。

基数为 0

当基数为 0 时,parseInt 会根据字符串的内容自动选择合适的基数。具体规则如下:

  • 如果字符串以 “0x” 或 “0X” 开头,则基数被设置为 16(十六进制)。
  • 如果字符串以 “0” 开头,则基数被设置为 8(八进制)。
  • 如果字符串以其他任何字符开头,则基数被设置为 10(十进制)。

例如:

1
2
3
parseInt("0x10", 0); // 返回 16,因为字符串以 "0x" 开头,基数被设置为16
parseInt("010", 0); // 返回 8,因为字符串以 "0" 开头,基数被设置为8
parseInt("10", 0); // 返回 10,因为字符串以其他字符开头,基数被设置为10

基数为 1

当基数为 1 时,parseInt 会尝试解析一个无效的字符串,因为 1 不是有效的进制数。在这种情况下,parseInt 会返回 NaN(Not-a-Number)。

例如:

1
parseInt("5", 1); // 返回 NaN,因为基数1是无效的

因此,在使用 parseInt 时,应该始终显式地指定一个有效的基数,以避免意外的结果。

使用 JWT 进行身份验证

本文由 chatgpt 生成,可能存在错误。

JWT(JSON Web Token)详解

JWT(JSON Web Token)是一种用于在网络应用环境间安全地传递信息的开放标准(RFC 7519)。它通过数字签名的方式来确保信息的完整性和真实性。JWT 主要由三部分组成:

  1. Header(头部):通常包含令牌的类型(JWT)和所使用的签名算法(如 HMAC SHA256 或 RSA)。
  2. Payload(有效载荷):包含声明(claims),即需要传递的信息。有效载荷可以包含用户身份和一些元数据。
  3. Signature(签名):通过将编码后的头部、有效载荷和一个密钥(secret)组合成的结果,以确保信息未被篡改。

JWT 的结构如下:

1
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

JWT 的使用场景

  • 身份验证:用户登录后,后端生成 JWT 并返回给客户端,客户端在后续请求中携带该令牌,后端可以通过验证 JWT 来确认用户身份。
  • 信息交换:JWT 可以安全地传递信息,因其可以被签名(例如,使用公钥/私钥对)。

PHP 后端示例

下面是一个使用 PHP 创建和验证 JWT 的示例。

Composer 安装依赖

首先,安装 firebase/php-jwt 库:

1
composer require firebase/php-jwt

JWT 生成与验证示例

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
<?php
require 'vendor/autoload.php';

use \Firebase\JWT\JWT;

$key = "your_secret_key"; // 密钥
$payload = [
"iss" => "http://yourdomain.com", // 签发者
"aud" => "http://yourdomain.com", // 受众
"iat" => time(), // 签发时间
"exp" => time() + 3600, // 过期时间
"data" => [
"id" => 123,
"name" => "John Doe"
]
];

// 生成 JWT
$jwt = JWT::encode($payload, $key);
echo "生成的 JWT: " . $jwt . "\n";

// 验证 JWT
try {
$decoded = JWT::decode($jwt, $key, ['HS256']);
print_r($decoded);
} catch (Exception $e) {
echo "验证失败: " . $e->getMessage();
}
?>

Node.js 后端示例

在 Node.js 中,我们可以使用 jsonwebtoken 库来处理 JWT。

安装依赖

1
npm install jsonwebtoken

JWT 生成与验证示例

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
const jwt = require("jsonwebtoken");

const secretKey = "your_secret_key"; // 密钥

// 生成 JWT
const payload = {
iss: "http://yourdomain.com", // 签发者
aud: "http://yourdomain.com", // 受众
iat: Math.floor(Date.now() / 1000), // 签发时间
exp: Math.floor(Date.now() / 1000) + 3600, // 过期时间
data: {
id: 123,
name: "John Doe",
},
};

const token = jwt.sign(payload, secretKey);
console.log("生成的 JWT:", token);

// 验证 JWT
jwt.verify(token, secretKey, (err, decoded) => {
if (err) {
console.log("验证失败:", err.message);
} else {
console.log("验证成功:", decoded);
}
});