JWT+Passport.js身份驗(yàn)證實(shí)現(xiàn)完整步驟
前言
在互聯(lián)網(wǎng)應(yīng)用開發(fā)中,「身份驗(yàn)證」「權(quán)限控制」「跨系統(tǒng)登錄」是保障安全與用戶體驗(yàn)的核心需求。在現(xiàn)代 Web 應(yīng)用中,登錄注冊是用戶使用系統(tǒng)的入口,而 JWT+Passport.js 實(shí)現(xiàn)身份驗(yàn)證、RBAC 落地權(quán)限控制、Redis 支撐單點(diǎn)登錄(SSO),共同構(gòu)成完整的用戶身份管理體系。
一、基礎(chǔ)概念
1. 身份驗(yàn)證 vs 授權(quán)
| 概念 | 核心目標(biāo) | 常見場景/方式 |
|---|---|---|
| 身份驗(yàn)證 | 確認(rèn)當(dāng)前用戶信息(第一道防線) | 賬號密碼、手機(jī)驗(yàn)證碼、人臉/指紋登錄 |
| 授權(quán) | 用戶允許第三方(App/小程序)訪問特定資源 | 1. 登錄微信時(shí)授權(quán)昵稱/手機(jī)號; 2. 安裝 App 授權(quán)相機(jī)/位置權(quán)限 |
2. 憑證
- 作用:連接「身份驗(yàn)證」與「授權(quán)」的橋梁,用于標(biāo)識用戶身份。
- 互聯(lián)網(wǎng)場景:用戶登錄后,服務(wù)器會返回
SessionID(會話ID)或Token(令牌);后續(xù)請求攜帶這個(gè)憑證,服務(wù)器才能識別身份并判斷是否有權(quán)操作。
3. Cookie vs Session:傳統(tǒng)會話維持方案
傳統(tǒng) Web 應(yīng)用常用這對組合維持用戶登錄狀態(tài),但存在明顯差異:
| 對比維度 | Cookie(客戶端存儲) | Session(服務(wù)端存儲) |
|---|---|---|
| 存儲位置 | 瀏覽器(文本文件,≤4KB) | 服務(wù)器(數(shù)據(jù)庫/內(nèi)存,無大小限制) |
| 安全性 | 可被用戶修改,風(fēng)險(xiǎn)高 | 用戶無法直接訪問,更安全(需防CSRF) |
| 生命周期 | 可設(shè)長有效期(如“記住登錄”) | 短,關(guān)閉瀏覽器/超時(shí)后失效 |
核心問題:Session依賴服務(wù)端存儲,多服務(wù)器部署時(shí)需同步會話(如用Redis);Cookie受同源策略限制,跨域名無法共享——這也是后續(xù)引入Token的原因。
4. Token vs JWT:無狀態(tài)身份憑證
為解決Session的“服務(wù)端存儲依賴”,無狀態(tài)的Token應(yīng)運(yùn)而生,而JWT是Token的一種標(biāo)準(zhǔn)化實(shí)現(xiàn)。
- Token:加密字符串,會話信息編碼在 Token 中,服務(wù)器無需存Session,僅解析 Token 即可驗(yàn)證身份(如訪問令牌、刷新令牌)。
| 類型 | 作用 | 核心流程 |
|---|---|---|
| 訪問令牌 | 授權(quán)訪問特定資源/操作(有效期短) | 登錄請求→服務(wù)器驗(yàn)證→發(fā)令牌→客戶端存令牌→請求帶令牌→服務(wù)器驗(yàn)證 |
| 刷新令牌 | 刷新過期訪問令牌(有效期長,免重登) | 訪問令牌過期→帶刷新令牌求新令牌→服務(wù)器驗(yàn)證后發(fā)新令牌 |
- JWT:遵循RFC 7519標(biāo)準(zhǔn),基于JSON結(jié)構(gòu),自身加密存儲用戶信息(如用戶ID、角色),服務(wù)器解密后直接驗(yàn)證,無需查數(shù)據(jù)庫,效率更高。
JWT核心優(yōu)勢:服務(wù)端無狀態(tài)、支持跨域、驗(yàn)證效率高,是當(dāng)前分布式應(yīng)用的首選身份憑證。
二、登錄注冊與Passport+JWT的關(guān)系
1. 核心職責(zé)劃分
- 注冊:用戶提交賬號密碼,系統(tǒng)驗(yàn)證合法性后創(chuàng)建用戶記錄(存數(shù)據(jù)庫)
- 登錄:用戶提交賬號密碼,系統(tǒng)驗(yàn)證通過后生成 JWT
令牌(身份憑證) - Passport+JWT:攔截請求,驗(yàn)證 JWT 令牌有效性,確認(rèn)用戶身份并賦予訪問權(quán)限
2. 完整流程可視化
flowchart LR
A[用戶] -->|1. 注冊| B[提交賬號密碼]
B --> C{驗(yàn)證合法性
(用戶名唯一等)}
C -->|合法| D[創(chuàng)建用戶記錄
(密碼加密存儲)]
A -->|2. 登錄| E[提交賬號密碼]
E --> F{驗(yàn)證賬號密碼
(與數(shù)據(jù)庫比對)}
F -->|通過| G[生成JWT令牌
(包含用戶ID等信息)]
A -->|3. 訪問受保護(hù)接口| H[請求頭攜帶JWT]
H --> I[Passport攔截請求]
I --> J[JWT策略驗(yàn)證令牌]
J -->|有效| K[解析用戶信息
(附在req.user)]
K --> L[接口處理邏輯]
J -->|無效| M[返回401未授權(quán)]
三、代碼實(shí)現(xiàn)
Passport.js是Node.js生態(tài)最流行的身份驗(yàn)證中間件,支持“本地賬號密碼”“OAuth”“JWT”等多種策略。我們以NestJS(Node.js框架)為例,一步步實(shí)現(xiàn)JWT認(rèn)證。
1. 核心原理
Passport.js基于“策略模式”擴(kuò)展驗(yàn)證方式:
- 用
passport-local策略處理“賬號密碼登錄”,驗(yàn)證通過后生成JWT; - 用
passport-jwt策略處理“接口訪問驗(yàn)證”,解析請求頭中的JWT,確認(rèn)用戶身份。
2. 安裝依賴
# 核心依賴:Passport本地策略、JWT策略、Nest集成包、加密 pnpm add @nestjs/passport passport passport-local @nestjs/jwt passport-jwt bcryptjs # 類型定義(開發(fā)依賴) pnpm add -D @types/passport-local @types/passport-jwt @types/bcryptjs
3. 實(shí)現(xiàn)注冊功能
注冊核心是合法驗(yàn)證與密碼加密,避免明文存儲密碼導(dǎo)致安全風(fēng)險(xiǎn)。注冊生成的加密用戶記錄是登錄驗(yàn)證的唯一依據(jù)——沒有注冊的用戶無法通過登錄獲取JWT令牌。
// src/auth/auth.controller.ts
import { Controller, Post, Body, HttpException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { genSalt, hash } from 'bcryptjs';
import { User } from '../users/entities/user.entity'; // 用戶實(shí)體(含id/username/password/role)
@Controller('auth')
export class AuthController {
constructor(
@InjectRepository(User) private userRepo: Repository<User>,
) {}
// 注冊接口
@Post('register')
async register(
@Body() dto: { username: string; password: string },
) {
// 1. 驗(yàn)證用戶名是否已存在(防重復(fù)注冊)
const existUser = await this.userRepo.findOne({
where: { username: dto.username },
});
if (existUser) {
throw new HttpException('用戶名已存在', 400);
}
// 2. 密碼加密(核心!bcrypt自動加鹽,無需手動處理)
const salt = await genSalt(10); // 生成鹽值(復(fù)雜度10)
const hashedPassword = await hash(dto.password, salt);
// 3. 創(chuàng)建用戶記錄(存入數(shù)據(jù)庫,角色默認(rèn)為普通用戶)
await this.userRepo.save({
username: dto.username,
password: hashedPassword, // 存儲加密后的密碼
role: 'user', // 初始角色(后續(xù)可通過管理員修改)
});
return { message: '注冊成功,請登錄' };
}
}
4.實(shí)現(xiàn)登錄功能(本地策略)
登錄核心是驗(yàn)證用戶合法性并生成JWT令牌,令牌將作為后續(xù)訪問受保護(hù)接口的“通行證”。
4.1 配置Local策略(賬號密碼驗(yàn)證)
先定義 LocalStrategy,校驗(yàn)用戶輸入的賬號密碼是否正確(密碼需用bcrypt加密存儲,避免明文)。
// src/auth/local.strategy.ts
import { BadRequestException } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { InjectRepository } from '@nestjs/typeorm';
import { compareSync } from 'bcryptjs'; // 密碼比對(bcrypt加密)
import { Strategy } from 'passport-local';
import { Repository } from 'typeorm';
import { User } from '../users/entities/user.entity'; // 用戶實(shí)體
// 繼承PassportStrategy,指定策略類型為local
export class LocalStrategy extends PassportStrategy(Strategy) {
constructor(
@InjectRepository(User) private userRepo: Repository<User>,
) {
super({
usernameField: 'username', // 對應(yīng)請求體中的“用戶名”字段
passwordField: 'password', // 對應(yīng)請求體中的“密碼”字段
});
}
// 驗(yàn)證邏輯:Passport會自動調(diào)用該方法
async validate(username: string, password: string) {
// 1. 根據(jù)用戶名查用戶(需額外查詢密碼,默認(rèn)不返回)
const user = await this.userRepo
.createQueryBuilder('user')
.addSelect('user.password') // 顯式查詢密碼
.where('user.username = :username', { username })
.getOne();
// 2. 校驗(yàn)用戶是否存在、密碼是否匹配
if (!user || !compareSync(password, user.password)) {
throw new BadRequestException('用戶名或密碼錯(cuò)誤');
}
// 3. 返回用戶信息(后續(xù)會被注入到req.user中)
return user;
}
}
4.2 配置JWT模塊
登錄驗(yàn)證通過后,需要生成JWT給客戶端,后續(xù)接口訪問需攜帶該令牌。在 AuthModule 中注冊JWT模塊,指定密鑰(從配置文件讀取,避免硬編碼)和過期時(shí)間:
// src/auth/auth.module.ts
import { Module } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { JwtModule } from '@nestjs/jwt';
import { PassportModule } from '@nestjs/passport';
import { TypeOrmModule } from '@nestjs/typeorm';
import { User } from '../users/entities/user.entity';
import { AuthService } from './auth.service';
import { LocalStrategy } from './local.strategy';
import { JwtStrategy } from './jwt.strategy';
// 異步配置JWT(從環(huán)境變量讀取密鑰)
const jwtModule = JwtModule.registerAsync({
inject: [ConfigService],
useFactory: (config: ConfigService) => ({
secret: config.get('JWT_SECRET', 'dev-secret-123'), // 密鑰(生產(chǎn)環(huán)境需復(fù)雜)
signOptions: { expiresIn: '4h' }, // JWT過期時(shí)間(4小時(shí))
}),
});
@Module({
imports: [
TypeOrmModule.forFeature([User]), // 導(dǎo)入用戶Repository
PassportModule,
jwtModule, // 注冊JWT模塊
],
providers: [AuthService, LocalStrategy, JwtStrategy], // 注入服務(wù)和策略
exports: [AuthService, JwtModule], // 導(dǎo)出JWT模塊,供其他模塊使用
})
export class AuthModule {}
4.3 實(shí)現(xiàn)JWT生成邏輯
在 AuthService 中,用 JwtService 生成JWT(Payload包含用戶ID、用戶名、角色,供后續(xù)權(quán)限判斷):
// src/auth/auth.service.ts
import { Injectable } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { User } from '../users/entities/user.entity';
@Injectable()
export class AuthService {
constructor(private jwtService: JwtService) {}
// 生成JWT(登錄成功后調(diào)用)
login(user: Partial<User>) {
// Payload:存儲用戶核心信息(避免敏感數(shù)據(jù),如密碼)
const payload = {
id: user.id,
username: user.username,
role: user.role // 角色信息,后續(xù)RBAC權(quán)限控制用
};
// 簽名生成JWT令牌
const token = this.jwtService.sign(payload);
return { token }; // 返回給客戶端
}
}
4.4 實(shí)現(xiàn)登錄接口
用 AuthGuard('local') 觸發(fā)Local策略驗(yàn)證,驗(yàn)證通過后調(diào)用 login 生成JWT:
// src/auth/auth.controller.ts
import { Controller, Post, Req, UseGuards } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { AuthService } from './auth.service';
@Controller('auth')
export class AuthController {
constructor(private authService: AuthService) {}
// 登錄接口:使用local策略驗(yàn)證
@UseGuards(AuthGuard('local'))
@Post('login')
login(@Req() req) {
// req.user由LocalStrategy的validate方法返回
return this.authService.login(req.user);
}
}
5. Passport+JWT實(shí)現(xiàn)接口驗(yàn)證(身份確認(rèn))
客戶端登錄后,需在請求頭攜帶 Authorization: Bearer <JWT>,我們用Passport的 passport-jwt 策略負(fù)責(zé)攔截請求、驗(yàn)證令牌有效性,確認(rèn)用戶身份。
5.1 定義JWT策略
// src/auth/jwt.strategy.ts
import { UnauthorizedException } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { PassportStrategy } from '@nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt';
import { User } from '../users/entities/user.entity';
import { AuthService } from './auth.service';
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor(
private config: ConfigService,
private authService: AuthService,
) {
super({
// 從請求頭的Authorization: Bearer <JWT>中提取令牌
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKey: config.get('JWT_SECRET'), // 用相同密鑰解密
ignoreExpiration: false, // 不忽略過期(過期則返回401)
});
}
// 驗(yàn)證JWT有效性(解密后調(diào)用)
async validate(payload: Partial<User>) {
// 可額外校驗(yàn)用戶狀態(tài)(如是否被禁用)
const user = await this.authService.findUserById(payload.id);
if (!user) {
throw new UnauthorizedException('令牌無效或用戶不存在');
}
return user; // 返回用戶信息,注入到req.user
}
}
5.2 封裝JWT守衛(wèi)(統(tǒng)一錯(cuò)誤處理)
自定義 JwtAuthGuard,統(tǒng)一處理“未登錄”錯(cuò)誤提示,避免重復(fù)代碼:
// src/auth/jwt-auth.guard.ts
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {
// 處理驗(yàn)證結(jié)果
handleRequest(err: any, user: any) {
if (err || !user) {
throw new UnauthorizedException('請先登錄');
}
return user;
}
}
5.3 保護(hù)接口
在需要登錄的接口上使用 JwtAuthGuard,未攜帶有效JWT會被攔截:
// src/users/users.controller.ts
import { Controller, Get, Req, UseGuards } from '@nestjs/common';
import { JwtAuthGuard } from '../auth/jwt-auth.guard';
@Controller('users')
export class UsersController {
// 需登錄才能訪問的接口:獲取當(dāng)前用戶信息
@UseGuards(JwtAuthGuard)
@Get('info')
getUserInfo(@Req() req) {
// req.user由JwtStrategy的validate方法返回
return req.user;
}
}
6. RBAC 權(quán)限控制
6.1 RolesGuard 實(shí)現(xiàn)
自定義RolesGuard,校驗(yàn)用戶角色是否符合接口要求,實(shí)現(xiàn)權(quán)限細(xì)化:
// src/auth/roles.guard.ts
import { Injectable, CanActivate, ExecutionContext, ForbiddenException } from '@nestjs/common';
@Injectable()
export class RolesGuard implements CanActivate {
constructor(private requiredRoles: string[]) {} // 接收需要的角色列表
canActivate(context: ExecutionContext): boolean {
const request = context.switchToHttp().getRequest();
const user = request.user; // 從req.user中獲取用戶角色(JWT驗(yàn)證后注入)
// 校驗(yàn)用戶角色是否在允許的列表中
const hasPermission = this.requiredRoles.some(role => user.role === role);
if (!hasPermission) {
throw new ForbiddenException('無權(quán)限訪問,需管理員權(quán)限');
}
return true;
}
}
// 自定義裝飾器:簡化角色配置(如@Roles('admin'))
import { SetMetadata } from '@nestjs/common';
export const Roles = (...roles: string[]) => SetMetadata('roles', roles);
6.2 接口示例
// src/admin/admin.controller.ts
import { Controller, Get, UseGuards } from '@nestjs/common';
import { JwtAuthGuard } from '../auth/jwt-auth.guard';
import { RolesGuard, Roles } from '../auth/roles.guard';
@Controller('admin')
@UseGuards(JwtAuthGuard) // 先驗(yàn)證登錄狀態(tài)
export class AdminController {
// 僅角色為admin的用戶可訪問:查詢所有用戶(管理員權(quán)限)
@Get('all-users')
@Roles('admin') // 指定需要的角色
@UseGuards(new RolesGuard(['admin'])) // 應(yīng)用角色守衛(wèi)
getAllUsers() {
// 業(yè)務(wù)邏輯:查詢數(shù)據(jù)庫所有用戶信息
return { data: [], message: '所有用戶列表' };
}
}
7.JWT過期處理:避免頻繁重新登錄
JWT一旦生成無法主動作廢,若有效期過短會影響用戶體驗(yàn),過長則增加安全風(fēng)險(xiǎn)。解決方案是**“訪問令牌+刷新令牌”雙令牌機(jī)制**:
| 令牌類型 | 有效期 | 作用 | 安全策略 |
|---|---|---|---|
| 訪問令牌 | 短(如30分鐘) | 訪問受保護(hù)接口(核心憑證) | 過期后用刷新令牌獲取新令牌 |
| 刷新令牌 | 長(如7天) | 僅用于刷新訪問令牌(無接口訪問權(quán)限) | 存儲在Redis中,支持主動作廢(如登出) |
實(shí)現(xiàn)邏輯:
- 登錄時(shí)同時(shí)生成訪問令牌(accessToken)和刷新令牌(refreshToken);
- 刷新令牌存儲在Redis中(鍵:refreshToken,值:用戶ID+過期時(shí)間);
- 訪問令牌過期時(shí),客戶端攜帶刷新令牌請求
/auth/refresh-token接口; - 服務(wù)器驗(yàn)證刷新令牌(Redis中是否存在、是否過期),驗(yàn)證通過則生成新的訪問令牌。
// src/auth/auth.service.ts(新增刷新令牌邏輯)
import { InjectRedis } from '@nestjs-modules/ioredis';
import { Redis } from 'ioredis';
import { v4 as uuidv4 } from 'uuid'; // 生成唯一刷新令牌
@Injectable()
export class AuthService {
// ... 其他代碼 ...
// 登錄時(shí)生成雙令牌
async loginWithDoubleToken(user: Partial<User>) {
// 1. 生成訪問令牌(短有效期)
const accessToken = this.jwtService.sign(
{ sub: user.id, username: user.username, role: user.role },
{ expiresIn: '30m' },
);
// 2. 生成刷新令牌(唯一ID,長有效期)
const refreshToken = uuidv4();
const refreshTokenExpire = 7 * 24 * 60 * 60; // 7天(秒)
// 3. 刷新令牌存儲到Redis(支持主動作廢)
await this.redis.set(
`refresh_token:${refreshToken}`,
user.id,
'EX',
refreshTokenExpire,
);
return {
accessToken,
refreshToken,
expiresIn: 30 * 60, // 訪問令牌有效期(秒)
};
}
// 刷新訪問令牌
async refreshToken(refreshToken: string) {
// 1. 驗(yàn)證刷新令牌是否存在于Redis
const userId = await this.redis.get(`refresh_token:${refreshToken}`);
if (!userId) {
throw new UnauthorizedException('刷新令牌無效或已過期');
}
// 2. 查詢用戶信息
const user = await this.findUserById(Number(userId));
if (!user) {
throw new UnauthorizedException('用戶不存在');
}
// 3. 生成新的訪問令牌
const newAccessToken = this.jwtService.sign(
{ sub: user.id, username: user.username, role: user.role },
{ expiresIn: '30m' },
);
return { accessToken: newAccessToken, expiresIn: 30 * 60 };
}
// 登出:刪除Redis中的刷新令牌(主動作廢)
async logout(refreshToken: string) {
await this.redis.del(`refresh_token:${refreshToken}`);
return { message: '登出成功' };
}
}
四、關(guān)鍵安全與最佳實(shí)踐
身份管理系統(tǒng)的安全性直接決定應(yīng)用安全,需嚴(yán)格遵循以下最佳實(shí)踐:
1. 密碼安全:從存儲到傳輸全鏈路防護(hù)
- 存儲加密:必須使用bcrypt、Argon2等自適應(yīng)哈希算法加密密碼,禁止明文或MD5等弱哈希存儲;
- 傳輸加密:所有登錄注冊接口必須使用HTTPS,防止密碼在傳輸過程中被截??;
- 強(qiáng)度校驗(yàn):注冊時(shí)強(qiáng)制密碼復(fù)雜度(如8位以上+字母+數(shù)字+特殊符號),避免弱密碼。
2. JWT安全:避免令牌泄露與篡改
- 密鑰管理:JWT密鑰需復(fù)雜(如32位以上隨機(jī)字符串),生產(chǎn)環(huán)境中存儲在環(huán)境變量或密鑰管理服務(wù)(如AWS KMS),禁止硬編碼;
- 令牌存儲:前端優(yōu)先將JWT存儲在
httpOnly Cookie(防XSS攻擊),若用localStorage需額外做XSS防護(hù)(如輸入過濾、CSP策略); - Payload控制:JWT的Payload僅存儲非敏感信息(如用戶ID、角色),禁止包含密碼、手機(jī)號等敏感數(shù)據(jù)。
3. 接口安全:防御常見攻擊
- 防CSRF攻擊:使用
httpOnly Cookie存儲令牌時(shí),需配合CSRF Token驗(yàn)證; - 限流防護(hù):登錄、注冊接口添加限流(如1分鐘內(nèi)最多5次請求),防止暴力給破解;
- 異常處理:統(tǒng)一錯(cuò)誤提示(如“賬號或密碼錯(cuò)誤”而非“用戶名不存在”),避免泄露用戶存在性信息。
4. 權(quán)限最小化:避免過度授權(quán)
- 基于RBAC模型嚴(yán)格劃分角色(如user、admin、super_admin),每個(gè)角色僅賦予必要權(quán)限;
- 敏感操作(如修改密碼、刪除數(shù)據(jù))需二次驗(yàn)證(如短信驗(yàn)證碼、當(dāng)前密碼確認(rèn))。
總結(jié)
到此這篇關(guān)于JWT+Passport.js身份驗(yàn)證實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)JWT+Passport.js身份驗(yàn)證內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
詳解js location.href和window.open的幾種用法和區(qū)別
這篇文章主要介紹了詳解js location.href和window.open的幾種用法和區(qū)別,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-12-12
JS格式化數(shù)字保留兩位小數(shù)點(diǎn)示例代碼
式化數(shù)字保留兩位小數(shù)點(diǎn)實(shí)現(xiàn)的方法有很多,在接下來的文章中將為大家詳細(xì)介紹下如何使用js來實(shí)現(xiàn)2013-10-10
curring的概念將函數(shù)式編程的概念和默認(rèn)參數(shù)以及可變參數(shù)結(jié)合在一起.一個(gè)帶n個(gè)參數(shù),curried的函數(shù)固化第一個(gè)參數(shù)為固定參數(shù),并返回另一個(gè)帶n-1個(gè)參數(shù)的函數(shù)對象,分別類似于LISP的原始函數(shù)car和cdr的行為。currying能泛化為偏函數(shù)應(yīng)用(partial function application, PFA),p 這種函數(shù)將任意數(shù)量(順序)的參數(shù)的函數(shù)轉(zhuǎn)化為另一個(gè)帶剩余參數(shù)的函數(shù)對象2012-02-02
JavaScript使用Replace進(jìn)行字符串替換的方法
這篇文章主要介紹了JavaScript使用Replace進(jìn)行字符串替換的方法,涉及Replace進(jìn)行一次替換與全部替換的技巧,非常具有實(shí)用價(jià)值,需要的朋友可以參考下2015-04-04
JavaScript實(shí)現(xiàn)事件的中斷傳播和行為阻止方法示例
這篇文章主要給大家介紹了利用JavaScript實(shí)現(xiàn)事件的中斷傳播和行為阻止的方法示例,文中給出了詳細(xì)的介紹和示例代碼,相信對大家的理解和學(xué)習(xí)具有一定的參考借鑒價(jià)值,需要的朋友們下面來一起看看吧。2017-01-01
基于Day.js更優(yōu)雅的處理JavaScript中的日期
Day.js它能夠幫助我們處理JavaScript中的日期,本文就詳細(xì)的介紹一下Day.js的具體使用,可以更簡單的處理JavaScript中的日期和時(shí)間2021-09-09
JavaScript動態(tài)創(chuàng)建div屬性和樣式示例代碼
動態(tài)創(chuàng)建div屬性和樣式在某些情況下還是比較實(shí)用的,下面為大家詳細(xì)介紹下js中div屬性和樣式的動態(tài)創(chuàng)建,感興趣的朋友可以參考下2013-10-10

