فك ترميز URL في JavaScript — decodeURIComponent()
استخدم فك تشفير URL أونلاين المجاني مباشرةً في متصفحك — لا حاجة للتثبيت.
جرّب فك تشفير URL أونلاين أونلاين ←تظهر السلاسل المُرمَّزة بالنسبة المئوية في كود JavaScript باستمرار — يصل استعلام بحث على شكل q=standing+desk%26price%3A200، وإعادة توجيه OAuth على شكل next=https%3A%2F%2Fdashboard.internal%2F، ومسار تخزين على شكل reports%2F2025%2Fq1.pdf. فك ترميز URL في JavaScript يتلخص في اختيار الدالة الصحيحة من بين ثلاث دوال مدمجة: decodeURIComponent() وdecodeURI() وURLSearchParams — والاختيار بينها هو السبب الجذري لمعظم حالات تلف البيانات الصامت التي رأيتها في قواعد الكود الإنتاجية، ولا سيما حالة حافة + كمسافة وفك الترميز المزدوج. للفك السريع لمرة واحدة دون كتابة كود، أداة فك ترميز URL من ToolDeck تعالج ذلك فوراً في المتصفح. يغطي هذا البرنامج التعليمي لفك ترميز URL في JavaScript الدوال الثلاث بعمق (ES2015+ / Node.js 10+): متى تستخدم كل منها، وكيف تختلف في التعامل مع المسافات والأحرف المحجوزة، وفك الترميز من الملفات وطلبات HTTP، ومعالجة الأخطاء بأمان، والأخطاء الأربعة التي تسبب أكثر الأخطاء الإنتاجية خفاءً.
- ✓decodeURIComponent() يفك ترميز جميع التسلسلات المُرمَّزة بالنسبة المئوية — وهو الخيار الصحيح لقيم معاملات الاستعلام الفردية ومقاطع المسار
- ✓decodeURI() يحافظ على الأحرف الهيكلية في URI مثل / ? & = # : — استخدمه فقط عند فك ترميز سلسلة URL كاملة، وأبداً للقيم الفردية
- ✓URLSearchParams.get() يُعيد قيماً مفكوكة الترميز بالفعل — استدعاء decodeURIComponent() فوقها يُحدث فك ترميز مزدوجاً
- ✓decodeURIComponent() لا يفك ترميز + كمسافة — للبيانات المُرمَّزة بصيغة النماذج (application/x-www-form-urlencoded)، استخدم URLSearchParams أو استبدل + قبل فك الترميز
- ✓غلِّف decodeURIComponent() دائماً في try/catch عندما تأتي المدخلات من بيانات المستخدم — رمز % المنفرد أو التسلسل غير المكتمل يُلقي URIError
ما هو فك ترميز URL؟
ترميز النسبة المئوية (المُعرَّف رسمياً في RFC 3986) يستبدل الأحرف غير الآمنة أو ذات الأهمية الهيكلية في URL برمز % متبوعاً برقمين سداسيين — قيمة البايت UTF-8 للحرف. فك ترميز URL يعكس هذا التحويل: كل تسلسل %XX يُحوَّل إلى بايته الأصلي، ويُفسَّر تسلسل البايتات الناتج كنص UTF-8. تُفَكّ مسافة من %20، وشرطة مائلة من %2F، والحرف غير ASCII ü من %C3%BC (تمثيله بـ UTF-8 بايتين).
الأحرف التي لا تُرمَّز أبداً — تُسمى الأحرف غير المحجوزة — هي الحروف A–Z وa–z، والأرقام 0–9، والرموز - _ . ~. كل شيء آخر إما له دور هيكلي في URL (مثل / الذي يفصل مقاطع المسار أو & الذي يفصل معاملات الاستعلام) أو يجب ترميزه عند استخدامه كبيانات. النتيجة العملية: فلتر بحث مثل status=active&tier=premium المُرمَّز كقيمة معامل استعلام واحدة يبدو مختلفاً تماماً عن الأصل.
// بعد فك ترميز URL — فلتر استعلام Elasticsearch الأصلي "q=price:[200 TO 800] AND brand:Northwood & status:in-stock"
// مُرمَّز بالنسبة المئوية — كما يُستلم في طلب HTTP أو حمولة webhook "q=price%3A%5B200+TO+800%5D%20AND%20brand%3ANorthwood%20%26%20status%3Ain-stock"
decodeURIComponent() — الدالة القياسية لفك ترميز القيم
decodeURIComponent() هي دالة العمل الرئيسية لفك ترميز URL في JavaScript. تفك ترميز كل تسلسل %XX في سلسلة الإدخال — بما في ذلك الأحرف ذات المعنى الهيكلي في URL، مثل %2F (شرطة مائلة)، %3F (علامة استفهام)، %26 (علامة &)، و %3D (علامة يساوي). يجعلها ذلك الخيار الصحيح لفك ترميز قيم معاملات الاستعلام الفردية ومقاطع المسار، لكنها الخيار الخاطئ لفك ترميز URL كامل — حيث يجب أن تبقى تلك الأحرف الهيكلية مُرمَّزة. إنها دالة عامة: لا يلزم استيراد في أي بيئة JavaScript.
مثال عملي بسيط
// فك ترميز قيم معاملات الاستعلام الفردية — الحالة الشائعة
const city = decodeURIComponent('S%C3%A3o%20Paulo') // 'São Paulo'
const district = decodeURIComponent('It%C3%A1im%20Bibi') // 'Itáim Bibi'
const category = decodeURIComponent('office%20furniture') // 'office furniture'
const filter = decodeURIComponent('price%3A%5B200+TO+800%5D') // 'price:[200+TO+800]'
// ملاحظة: + لا يُفَكّ ترميزه كمسافة — انظر قسم + في جدول المقارنة
console.log(city) // São Paulo
console.log(district) // Itáim Bibi
console.log(category) // office furniture
console.log(filter) // price:[200+TO+800]فك ترميز URL إعادة التوجيه المستخرج من معامل استعلام
// URL إعادة التوجيه كان مُرمَّزاً بالنسبة المئوية عند تضمينه كقيمة معامل
// الجانب المُستلِم: استخرجه ثم استخدمه — لا يلزم فك ترميز يدوي مع URLSearchParams
const incomingUrl = 'https://auth.company.com/callback' +
'?next=https%3A%2F%2Fdashboard.internal%2Freports%3Fview%3Dweekly%26team%3Dplatform' +
'&session_id=sid_7x9p2k'
const url = new URL(incomingUrl)
const rawNext = url.searchParams.get('next') // فُكَّ ترميزه تلقائياً بواسطة URLSearchParams
const sessionId = url.searchParams.get('session_id') // 'sid_7x9p2k'
// rawNext مفكوك الترميز بالفعل: 'https://dashboard.internal/reports?view=weekly&team=platform'
// لا تستدعِ decodeURIComponent(rawNext) مجدداً — سيكون فك ترميز مزدوجاً
const nextUrl = new URL(rawNext!)
console.log(nextUrl.hostname) // dashboard.internal
console.log(nextUrl.searchParams.get('view')) // weekly
console.log(nextUrl.searchParams.get('team')) // platformفك ترميز مقاطع المسار غير ASCII وUnicode
// REST API بمقاطع مسار مدوَّلة
// كل بايت UTF-8 للحرف الأصلي كان مُرمَّزاً بالنسبة المئوية بشكل منفصل
const encodedSegments = [
'%E6%9D%B1%E4%BA%AC', // 東京 (طوكيو) — 3 بايتات لكل حرف
'M%C3%BCnchen', // München — ü مُرمَّز كبايتين
'caf%C3%A9', // café — é كـ NFC مدمجة
'S%C3%A3o%20Paulo', // São Paulo
]
encodedSegments.forEach(seg => {
console.log(decodeURIComponent(seg))
})
// 東京
// München
// café
// São Paulo
// استخراج مفتاح ملف من URL واجهة تخزين
// مفتاح الكائن احتوى على / لذا رُمِّز كـ %2F داخل مقطع المسار
const storageUrl = 'https://storage.api.example.com/v1/objects/reports%2F2025%2Fq1-financials.pdf'
const rawKey = new URL(storageUrl).pathname.replace('/v1/objects/', '')
// .pathname يفك ترميز تشفير مستوى URL لكن %2F (كـ %252F على مستوى URL) يبقى
// استخدم decodeURIComponent للخطوة الأخيرة:
const fileKey = decodeURIComponent(rawKey) // 'reports/2025/q1-financials.pdf'
console.log(fileKey)decodeURIComponent() يُلقي URIError عندما يحتوي الإدخال على % غير متبوع برقمين سداسيين صالحين — مثلاً رمز % المنفرد في نهاية سلسلة أو تسلسل مثل %GH. غلِّفه دائماً في try/catch عند فك ترميز مدخلات مُقدَّمة من المستخدم. أنماط فك الترميز الآمن مُغطَّاة في قسم معالجة الأخطاء أدناه.دوال فك ترميز URL في JavaScript — جدول مرجعي للأحرف
تختلف دوال فك الترميز المدمجة الثلاث في التسلسلات المُرمَّزة التي تفك ترميزها بالضبط. يوضح الجدول السلوك للأحرف الأكثر أهمية عملياً:
| مُرمَّز | الحرف | decodeURIComponent() | decodeURI() | URLSearchParams |
|---|---|---|---|---|
| %20 | مسافة | مسافة ✅ | مسافة ✅ | مسافة ✅ |
| + | + (نموذج) | + (محفوظ) | + (محفوظ) | مسافة ✅ |
| %2B | + حرفي | + ✅ | + ✅ | + ✅ |
| %26 | & | & ✅ | & (محفوظ) ❌ | & ✅ |
| %3D | = | = ✅ | = (محفوظ) ❌ | = ✅ |
| %3F | ? | ? ✅ | ? (محفوظ) ❌ | ? ✅ |
| %23 | # | # ✅ | # (محفوظ) ❌ | # ✅ |
| %2F | / | / ✅ | / (محفوظ) ❌ | / ✅ |
| %3A | : | : ✅ | : (محفوظ) ❌ | : ✅ |
| %40 | @ | @ ✅ | @ (محفوظ) ❌ | @ ✅ |
| %25 | % | % ✅ | % ✅ | % ✅ |
| %C3%BC | ü | ü ✅ | ü ✅ | ü ✅ |
الصفان الحاسمان هما + والأحرف الهيكلية (%26، %3D، %3F). URLSearchParams يفك ترميز + كمسافة لأنه يتبع مواصفة application/x-www-form-urlencoded — صحيح لإرسالات نماذج HTML، لكنه يختلف عن طريقة تعامل decodeURIComponent() مع نفس الحرف. أما decodeURI() فيتجاوز بصمت %26 و %3D و %3F — وهو ما يبدو صحيحاً حتى تحتوي قيمة فعلياً على علامة & أو = مُرمَّزة.
decodeURI() — فك ترميز URL كامل دون كسر هيكله
decodeURI() هي نظير encodeURI(). تفك ترميز سلسلة URL كاملة مع الحفاظ على الأحرف ذات المعنى الهيكلي في URI: ; , / ? : @ & = + $ #. تُترك هذه في صيغتها المُرمَّزة بالنسبة المئوية (أو كأحرف حرفية إذا ظهرت غير مُرمَّزة في الإدخال). يجعل ذلك decodeURI() آمنة لتعقيم عناوين URL الكاملة التي قد تحتوي على أحرف غير ASCII في المسار أو اسم المضيف، دون الانهيار العرضي لهيكل سلسلة الاستعلام.
تعقيم URL بمقاطع مسار غير ASCII
// URL CDN بمقاطع مسار مدوَّلة وسلسلة استعلام منظَّمة // decodeURI() يفك ترميز المسار غير ASCII لكن يحافظ على ? & = سليمة const encodedUrl = 'https://cdn.example.com/assets/%E6%9D%B1%E4%BA%AC%2F2025%2Fq1-report.pdf' + '?token=eyJ0eXAiOiJKV1QiLCJhbGci&expires=1735689600' const readable = decodeURI(encodedUrl) console.log(readable) // https://cdn.example.com/assets/東京/2025/q1-report.pdf?token=eyJ0eXAiOiJKV1QiLCJhbGci&expires=1735689600 // ↑ غير ASCII مفكوك الترميز؛ ? & = محفوظة — URL يبقى صالحاً هيكلياً // decodeURIComponent سيدمر URL — : / ? & = كلها تُفَكّ دفعة واحدة const broken = decodeURIComponent(encodedUrl) // 'https://cdn.example.com/assets/東京/2025/q1-report.pdf?token=eyJ0eXAiOiJKV1QiLCJhbGci&expires=1735689600' // يبدو هنا مماثلاً، لكن URL مثل 'https%3A%2F%2F...' كان سيُدمَّر
URL على decodeURI() عندما تحتاج أيضاً للوصول إلى مكونات URL الفردية. new URL(str) يُوحِّد الإدخال، ويتحقق من هيكله، ويُعرِّض .pathname و .searchParams و .hostname كخصائص مفكوكة الترميز بالفعل. احتفظ بـ decodeURI() للحالات التي تحتاج فيها فقط إلى نتيجة سلسلة ولا تستطيع استخدام مُنشئ URL (مثلاً في بيئات Node.js 6 القديمة جداً التي تفتقر إلى فئة URL العامة).URLSearchParams — فك ترميز تلقائي لسلاسل الاستعلام
URLSearchParams هو الطريقة الاصطلاحية لتحليل سلاسل الاستعلام في JavaScript الحديث. كل قيمة يُعيدها .get() أو .getAll() أو التكرار تُفَكّ ترميزها تلقائياً — بما في ذلك + كمسافة للبيانات المُرمَّزة بصيغة النماذج. متاح عالمياً في جميع المتصفحات الحديثة وNode.js 10+، ولا يتطلب استيراداً، ويتعامل مع حالات الحافة التي يُخطئ في معالجتها تقسيم السلاسل يدوياً.
تحليل سلسلة استعلام واردة
// تحليل سلسلة استعلام callback webhook أو إعادة توجيه OAuth
const rawSearch =
'?event_id=evt_9c2f4a1b' +
'&product_name=Standing+Desk+Pro' +
'&filter=price%3A%5B200+TO+800%5D' +
'&tag=ergonomic&tag=adjustable' +
'&redirect=https%3A%2F%2Fdashboard.internal%2Forders%3Fview%3Dpending'
const params = new URLSearchParams(rawSearch)
console.log(params.get('event_id')) // 'evt_9c2f4a1b'
console.log(params.get('product_name')) // 'Standing Desk Pro' ← + مفكوك كمسافة
console.log(params.get('filter')) // 'price:[200+TO+800]' ← %3A و %5B مفكوكان
console.log(params.getAll('tag')) // ['ergonomic', 'adjustable']
console.log(params.get('redirect')) // 'https://dashboard.internal/orders?view=pending'
// التكرار عبر جميع المعاملات
for (const [key, value] of params) {
console.log(`${key}: ${value}`)
}
// event_id: evt_9c2f4a1b
// product_name: Standing Desk Pro
// filter: price:[200+TO+800]
// tag: ergonomic
// tag: adjustable
// redirect: https://dashboard.internal/orders?view=pendingتحليل معاملات الاستعلام من URL المتصفح الحالي
// URL الحالي: https://app.example.com/search
// ?q=standing+desk
// &category=office+furniture
// &sort=price_asc
// &page=2
interface SearchFilters {
query: string | null
category: string | null
sort: string
page: number
}
function getSearchFilters(): SearchFilters {
const params = new URLSearchParams(window.location.search)
return {
query: params.get('q'), // 'standing desk'
category: params.get('category'), // 'office furniture'
sort: params.get('sort') ?? 'relevance',
page: Number(params.get('page') ?? '1'),
}
}
const filters = getSearchFilters()
console.log(filters.query) // standing desk (+ مفكوك تلقائياً)
console.log(filters.category) // office furnitureفك ترميز البيانات المُرمَّزة بـ URL من الملفات واستجابات API
يظهر سيناريوان باستمرار في المشاريع الفعلية: معالجة ملف على القرص يحتوي على بيانات مُرمَّزة بالنسبة المئوية (سجلات الوصول، تصدير البيانات، ملفات التقاط webhook)، وتحليل URL طلب HTTP وارد في خادم Node.js. كلاهما يتبع نفس المبدأ — استخدام مُنشئ URL أو URLSearchParams بدلاً من تقسيم السلاسل يدوياً — لكن التفاصيل تختلف.
قراءة وفك ترميز السجلات المُرمَّزة بـ URL من ملف
import { createReadStream } from 'fs'
import { createInterface } from 'readline'
// ملف: orders-export.txt — سجل مُرمَّز بـ URL واحد لكل سطر
// customer_name=%D8%A3%D8%AD%D9%85%D8%AF+%D8%A7%D9%84%D8%AE%D8%A7%D9%84%D8%AF%D9%8A&order_id=ord_9c2f4a&product=Standing+Desk+Pro&total=562%20SAR
// customer_name=%D9%81%D8%A7%D8%B7%D9%85%D8%A9+%D8%A7%D9%84%D9%85%D9%86%D8%B5%D9%88%D8%B1&order_id=ord_7b3a1c&product=Ergonomic+Chair&total=349%20SAR
// customer_name=%D8%B1%D9%8A%D8%A7%D8%B6%D9%8A+%D8%B9%D9%85%D9%8A%D9%84&order_id=ord_2e8d5f&product=Monitor+Arm&total=229%20SAR
interface Order {
customerName: string | null
orderId: string | null
product: string | null
total: string | null
}
async function parseOrdersFile(filePath: string): Promise<Order[]> {
const fileStream = createReadStream(filePath, { encoding: 'utf-8' })
const rl = createInterface({ input: fileStream, crlfDelay: Infinity })
const orders: Order[] = []
for await (const line of rl) {
if (!line.trim()) continue
// URLSearchParams يفك ترميز + كمسافة وتسلسلات %XX تلقائياً
const params = new URLSearchParams(line)
orders.push({
customerName: params.get('customer_name'), // 'أحمد الخالدي'
orderId: params.get('order_id'), // 'ord_9c2f4a'
product: params.get('product'), // 'Standing Desk Pro'
total: params.get('total'), // '562 SAR'
})
}
return orders
}
const orders = await parseOrdersFile('./orders-export.txt')
console.log(orders[0])
// { customerName: 'أحمد الخالدي', orderId: 'ord_9c2f4a', product: 'Standing Desk Pro', total: '562 SAR' }تحليل سجل وصول Nginx لفك ترميز استعلامات البحث
import { createReadStream } from 'fs'
import { createInterface } from 'readline'
// سطور سجل وصول Nginx تبدو هكذا:
// 192.168.1.42 - - [11/Mar/2026:10:23:01 +0000] "GET /api/search?q=standing%20desk%26brand%3ANorthwood&sort=price_asc HTTP/1.1" 200 1842
async function extractSearchQueries(logFile: string): Promise<string[]> {
const rl = createInterface({ input: createReadStream(logFile), crlfDelay: Infinity })
const queries: string[] = []
for await (const line of rl) {
// استخراج مسار الطلب من سطر السجل
const match = line.match(/"GET ([^ ]+) HTTP/)
if (!match) continue
try {
const requestUrl = new URL(match[1], 'http://localhost')
const query = requestUrl.searchParams.get('q')
if (query) queries.push(query) // URLSearchParams يفك الترميز تلقائياً
} catch {
// تخطِّ الأسطر ذات التنسيق المشوَّه — قد تحتوي سجلات الوصول على إدخالات مبتورة
}
}
return queries
}
const queries = await extractSearchQueries('/var/log/nginx/access.log')
console.log(queries)
// ['standing desk&brand:Northwood', 'ergonomic chair', 'monitor arm 27 inch']تحليل معاملات الاستعلام في خادم HTTP لـ Node.js
import http from 'http'
// URL الوارد: /api/products?q=standing+desk&warehouse=eu%2Dwest%2D1&minStock=10&cursor=eyJpZCI6MTIzfQ%3D%3D
const server = http.createServer((req, res) => {
// أنشئ URL كاملاً — الوسيط الثاني المطلوب من مُنشئ URL هو القاعدة
const requestUrl = new URL(req.url!, 'http://localhost')
const searchQuery = requestUrl.searchParams.get('q') // 'standing desk'
const warehouseId = requestUrl.searchParams.get('warehouse') // 'eu-west-1'
const minStock = Number(requestUrl.searchParams.get('minStock') ?? '0')
const cursor = requestUrl.searchParams.get('cursor') // 'eyJpZCI6MTIzfQ=='
if (!warehouseId) {
res.writeHead(400, { 'Content-Type': 'application/json' })
res.end(JSON.stringify({ error: 'warehouse parameter is required' }))
return
}
const cursorData = cursor ? JSON.parse(Buffer.from(cursor, 'base64').toString()) : null
res.writeHead(200, { 'Content-Type': 'application/json' })
res.end(JSON.stringify({ searchQuery, warehouseId, minStock, cursorData }))
})
server.listen(3000)عندما تحتاج أثناء التطوير إلى فحص URL مُرمَّز — لفهم ما يُرسله webhook قبل كتابة كود التحليل — الصقه مباشرةً في أداة فك ترميز URL من ToolDeck لترى الصيغة المفكوكة فوراً دون تشغيل سكريبت.
فك ترميز URL من سطر الأوامر
لسكريبتات Shell أو مسارات CI أو الفحص السريع لمرة واحدة للسلاسل المُرمَّزة، تعمل عدة مناهج دون كتابة سكريبت كامل. أوامر Node.js أحادية السطر متعددة المنصات؛ على macOS وLinux، python3 متاح دائماً.
# ── أوامر Node.js أحادية السطر ───────────────────────────────────────────
# فك ترميز قيمة مُرمَّزة بالنسبة المئوية واحدة
node -e "console.log(decodeURIComponent(process.argv[1]))" "S%C3%A3o%20Paulo%20%26%20Rio"
# São Paulo & Rio
# تحليل سلسلة استعلام وطباعة كل زوج مفتاح=قيمة (مفكوك الترميز)
node -e "
const params = new URLSearchParams(process.argv[1])
for (const [k, v] of params) console.log(`${k} = ${v}`)
" "q=standing+desk&warehouse=eu%2Dwest%2D1&minStock=10"
# q = standing desk
# warehouse = eu-west-1
# minStock = 10
# فك ترميز وطباعة جميلة لجسم JSON مُرمَّز بـ URL (شائع في تصحيح webhook)
node -e "
const raw = decodeURIComponent(process.argv[1])
console.log(JSON.stringify(JSON.parse(raw), null, 2))
" '%7B%22event%22%3A%22purchase%22%2C%22amount%22%3A149.99%2C%22currency%22%3A%22EUR%22%7D'
# {
# "event": "purchase",
# "amount": 149.99,
# "currency": "EUR"
# }
# ── أمر Python أحادي السطر (متاح في معظم أنظمة macOS/Linux) ─────────────
# unquote_plus يفك ترميز + أيضاً كمسافة — صحيح للبيانات المُرمَّزة بصيغة النماذج
python3 -c "
from urllib.parse import unquote_plus
import sys
print(unquote_plus(sys.argv[1]))
" "Standing+Desk+%26+Ergonomic+Chair"
# Standing Desk & Ergonomic Chair
# ── curl — تسجيل وفك ترميز URL إعادة التوجيه من رأس الاستجابة ───────────
curl -sI "https://api.example.com/short/abc123" | grep -i location | node -e "
const line = require('fs').readFileSync('/dev/stdin', 'utf8')
const url = line.replace(/^location:s*/i, '').trim()
console.log(decodeURIComponent(url))
"فك الترميز الأنيق مع decode-uri-component
المدمج decodeURIComponent() يُلقي URIError على أي تسلسل مشوَّه — بما في ذلك % في النهاية بدون رقمين سداسيين. في كود الإنتاج الذي يعالج عناوين URL مُقدَّمة من المستخدم أو أطراف ثالثة — سجلات استعلامات البحث، عناوين URL للنقر من حملات البريد الإلكتروني، بيانات ويب مكتسحة — التسلسلات المشوَّهة شائعة بما يكفي للتسبب في أعطال. حزمة decode-uri-component (~30 مليون تنزيل أسبوعي على npm) تتعامل مع التسلسلات المشوَّهة بأناقة بإرجاع التسلسل الأصلي دون تغيير بدلاً من الإلقاء.
npm install decode-uri-component # or pnpm add decode-uri-component
import decodeUriComponent from 'decode-uri-component'
// الدالة الأصلية تُلقي عند الإدخال المشوَّه — يمكن أن تُعطّل معالج الطلب
try {
decodeURIComponent('product%name%') // ❌ URIError: URI malformed
} catch (e) {
console.error('الأصلية ألقت:', (e as Error).message)
}
// decode-uri-component تُعيد التسلسل الخام بدلاً من الإلقاء
console.log(decodeUriComponent('product%name%'))
// product%name% ← التسلسلات المشوَّهة تُترك كما هي، لا إلقاء
// التسلسلات الصالحة تُفَكّ ترميزها بشكل صحيح
console.log(decodeUriComponent('S%C3%A3o%20Paulo%20%26%20Rio'))
// São Paulo & Rio
// مختلطة: التسلسلات الصالحة تُفَكّ، غير الصالحة تُحفظ
console.log(decodeUriComponent('Berlin%20Office%20%ZZ%20HQ'))
// Berlin Office %ZZ HQ ← %ZZ ليس سداسياً صالحاً — يُترك كما هو
// الاستخدام العملي في مسار معالجة السجلات
const rawSearchQueries = [
'standing%20desk%20ergonomic',
'price%3A%5B200+TO+800%5D',
'50%25+off', // ← مع decodeURIComponent كان سيُلقي (% منفرد)
'wireless%20keyboard%20%26%20mouse',
]
const decoded = rawSearchQueries.map(q => decodeUriComponent(q))
console.log(decoded)
// ['standing desk ergonomic', 'price:[200+TO+800]', '50%25+off', 'wireless keyboard & mouse']استخدم decodeURIComponent() مع try/catch لكود التطبيق حيث تريد معرفة الأمر فوراً إذا وصل إدخال غير متوقع. الجأ إلى decode-uri-component في مسارات البيانات ومعالجات السجلات ومعالجات webhook حيث تريد الاستمرار في المعالجة حتى عندما تحتوي بعض المدخلات على ترميز غير صالح.
التعامل مع سلاسل الترميز المئوي المشوَّهة
رمز % المنفرد، أو تسلسل غير مكتمل مثل %A، أو زوج غير صالح مثل %GH كلها تجعل decodeURIComponent() يُلقي URIError: URI malformed. أي إدخال يتحكم فيه المستخدم — استعلامات البحث، أجزاء URL، حقول النماذج التي تحتوي عناوين URL، المعاملات من روابط حملات البريد الإلكتروني — يمكن أن يحتوي على هذه التسلسلات. غلاف آمن ضروري لأي كود يواجه الخارج.
غلافات فك ترميز آمنة للسيناريوهات الشائعة
// ── 1. فك ترميز آمن أساسي — يُعيد السلسلة الأصلية عند الخطأ ──────────
function safeDecode(encoded: string): string {
try {
return decodeURIComponent(encoded)
} catch {
return encoded // أعِد الإدخال الخام إذا فشل فك الترميز — لا تُعطِّل أبداً
}
}
// ── 2. فك ترميز آمن + التعامل مع + كمسافة (لقيم مُرمَّزة بصيغة النماذج) ─
function safeFormDecode(formEncoded: string): string {
try {
return decodeURIComponent(formEncoded.replace(/+/g, ' '))
} catch {
return formEncoded.replace(/+/g, ' ') // استبدل + على الأقل حتى لو فشل الباقي
}
}
// ── 3. محلل سلسلة استعلام آمن كامل → كائن عادي ──────────────────────
function parseQueryString(queryString: string): Record<string, string> {
const result: Record<string, string> = {}
try {
const params = new URLSearchParams(queryString)
for (const [key, value] of params) {
result[key] = value // URLSearchParams يتعامل مع جميع عمليات فك الترميز داخلياً
}
} catch {
// أعِد فارغاً بصمت عند الإدخال المشوَّه تماماً
}
return result
}
// الاستخدام
console.log(safeDecode('S%C3%A3o%20Paulo')) // São Paulo
console.log(safeDecode('search%20for%2050%25+off')) // search for 50%+off
// → في الواقع جيد؛ % هنا تعني %25
console.log(safeDecode('malformed%string%')) // malformed%string% (لا إلقاء)
console.log(safeFormDecode('standing+desk+pro')) // standing desk pro
console.log(parseQueryString('q=hello+world&tag=node%20js&page=2'))
// { q: 'hello world', tag: 'node js', page: '2' }الأخطاء الشائعة
رأيت هذه الأنماط الأربعة تُسبِّب تلفاً صامتاً للبيانات أو أعطالاً غير متوقعة في الإنتاج — عادةً فقط عندما تحتوي قيمة ما على حرف خاص، مما يعني أنها تنجح في اختبارات الوحدة وتظهر مع بيانات المستخدم الفعلية.
الخطأ الأول — فك الترميز المزدوج لقيمة URLSearchParams
المشكلة: URLSearchParams.get() يُعيد سلسلة مفكوكة الترميز بالفعل. استدعاء decodeURIComponent() فوقها يفك الترميز مرتين — محوِّلاً أي % متبقٍّ في الناتج المفكوك إلى %25، مما يُفسِد البيانات. الحل: استخدم القيمة من URLSearchParams.get() مباشرةً — لا يلزم فك ترميز إضافي.
// ✅ استخدم URLSearchParams.get() مباشرةً — فك الترميز تلقائي
const qs = new URLSearchParams('rate=50%25&redirect=https%3A%2F%2Fdashboard.internal%2F')
const rate = qs.get('rate') // '50%' ← صحيح، لا خطوة إضافية لازمة
const redirect = qs.get('redirect') // 'https://dashboard.internal/'
const parsed = new URL(redirect!) // آمن للبناء — مفكوك بالفعل
console.log(parsed.hostname) // dashboard.internal// ❌ URLSearchParams فكّ الترميز بالفعل — فك الترميز مجدداً يُفسِد القيم التي تحتوي %
const qs = new URLSearchParams('rate=50%25&redirect=https%3A%2F%2Fdashboard.internal%2F')
const rawRate = qs.get('rate') // '50%' ← مفكوك بالفعل
const wrongRate = decodeURIComponent(rawRate)
// '50%25' ← % فُكَّ ترميزه إلى %25 في المرور الثاني — خاطئ مجدداً الآنالخطأ الثاني — عدم فك ترميز + كمسافة للبيانات المُرمَّزة بصيغة النماذج
المشكلة: إرسالات النماذج وبعض مكتبات OAuth تُرمِّز المسافات كـ + (application/x-www-form-urlencoded). استدعاء decodeURIComponent() على هذه البيانات يُبقي + كعلامة جمع حرفية. الحل: استخدم URLSearchParams لتحليل البيانات المُرمَّزة بصيغة النماذج، أو استبدل + بمسافة قبل استدعاء decodeURIComponent().
// ✅ استخدم URLSearchParams — يتبع مواصفة application/x-www-form-urlencoded
const formBody = 'customer_name=%D8%A3%D8%AD%D9%85%D8%AF+%D8%A7%D9%84%D8%AE%D8%A7%D9%84%D8%AF%D9%8A&product=Standing+Desk+Pro&qty=2'
const params = new URLSearchParams(formBody)
console.log(params.get('customer_name')) // 'أحمد الخالدي' ← + فُكَّ ترميزه صحيحاً كمسافة
console.log(params.get('product')) // 'Standing Desk Pro'// ❌ decodeURIComponent يعامل + كعلامة جمع حرفية، ليس مسافة
// نقطة نهاية رمز OAuth تُرسِل: grant_type=authorization_code&code=SplxlOBeZQQYb...
// &redirect_uri=https%3A%2F%2Fapp.example.com%2Fcallback
// بعض تطبيقات OAuth القديمة تستخدم + للمسافات في الرموز أيضاً
const formBody = 'customer_name=%D8%A3%D8%AD%D9%85%D8%AF+%D8%A7%D9%84%D8%AE%D8%A7%D9%84%D8%AF%D9%8A&product=Standing+Desk+Pro&qty=2'
const [, rawVal] = formBody.split('&')[0].split('=')
const name = decodeURIComponent(rawVal)
console.log(name) // '%D8%A3%D8%AD%D9%85%D8%AF+%D8%A7%D9%84%D8%AE%D8%A7%D9%84%D8%AF%D9%8A' ← + لم يتحوَّل إلى مسافةالخطأ الثالث — استخدام decodeURI() لقيم معاملات الاستعلام الفردية
المشكلة: decodeURI() لا يفك ترميز %26 (&)، أو %3D (=)، أو %3F (?) — أحرف مُرمَّزة بشكل شائع داخل قيم المعاملات. استخدامه لفك ترميز قيمة واحدة يُبقي تلك التسلسلات سليمة، مُنتِجاً ناتجاً خاطئاً بصمت. الحل: استخدم decodeURIComponent() للقيم الفردية؛ احتفظ بـ decodeURI() لسلاسل URL الكاملة.
// ✅ decodeURIComponent يفك ترميز جميع التسلسلات بما في ذلك & و = const encodedFilter = 'status%3Dactive%26tier%3Dpremium%26region%3Deu-west' const filter = decodeURIComponent(encodedFilter) console.log(filter) // 'status=active&tier=premium®ion=eu-west' ← مفكوك الترميز بشكل صحيح
// ❌ decodeURI لا يفك ترميز & و = — القيمة تبقى مشوَّهة const encodedFilter = 'status%3Dactive%26tier%3Dpremium%26region%3Deu-west' const filter = decodeURI(encodedFilter) console.log(filter) // 'status%3Dactive%26tier%3Dpremium%26region%3Deu-west' ← %3D و %26 لم يُفَكّا
الخطأ الرابع — عدم تغليف decodeURIComponent في try/catch للمدخلات من المستخدم
المشكلة: رمز %المنفرد غير المتبوع برقمين سداسيين — شائع في استعلامات البحث التي يكتبها المستخدمون (“50% خصم”، “100% قطن”) — يجعل decodeURIComponent() يُلقي URIError: URI malformed، مُعطِّلاً معالج الطلب إذا لم يُمسك. الحل: غلِّف دائماً في try/catch عندما تنبثق المدخلات من بيانات المستخدم أو أجزاء URL أو الأنظمة الخارجية.
// ✅ غلِّف في try/catch — ارجع إلى الإدخال الخام إذا فشل فك الترميز
app.get('/api/search', (req, res) => {
let query: string
try {
query = decodeURIComponent(req.query.q as string)
} catch {
query = req.query.q as string // استخدم القيمة الخام بدلاً من التعطُّل
}
// واصل المعالجة بأمان — query إما مفكوكة أو خام
})// ❌ المستخدم كتب '100% قطن' في صندوق البحث — % المنفرد يُعطِّل الخادم
// GET /api/search?q=100%25+cotton ← هذه الحالة المحددة جيدة (%25 = %)
// GET /api/search?q=100%+cotton ← هذه تُعطِّل (% لا يتبعها رقمان سداسيان)
app.get('/api/search', (req, res) => {
const query = decodeURIComponent(req.query.q as string)
// ↑ يُلقي URIError: URI malformed لـ '100% قطن' → خطأ 500 غير معالج
})مقارنة سريعة: decodeURIComponent مقابل decodeURI مقابل URLSearchParams
| الأسلوب | يفك %XX | يفك + كمسافة | يُلقي عند الخطأ | يفك & = ? # | حالة الاستخدام | يتطلب تثبيتاً |
|---|---|---|---|---|---|---|
| decodeURIComponent() | ✅ الكل | ❌ لا | ✅ URIError | ✅ نعم | القيم الفردية ومقاطع المسار | No |
| decodeURI() | ✅ معظم | ❌ لا | ✅ URIError | ❌ لا | سلاسل URL الكاملة | No |
| URLSearchParams | ✅ الكل | ✅ نعم | ❌ صامت | ✅ نعم | تحليل سلسلة الاستعلام مع فك ترميز تلقائي | No |
| مُنشئ URL | ✅ الكل | ✅ نعم | ✅ TypeError | ✅ نعم | تحليل وتوحيد URL الكامل | No |
| decode-uri-component | ✅ الكل | ❌ لا | ❌ صامت | ✅ نعم | فك ترميز دُفعي متحمِّل للمدخلات المشوَّهة | npm install |
| querystring.unescape() | ✅ الكل | ❌ لا | ❌ صامت | ✅ نعم | Node.js القديم (مهمل منذ v16) | No (مدمج) |
في الغالبية العظمى من الحالات، يتلخص الاختيار في ثلاثة سيناريوهات. استخدم URLSearchParams لتحليل سلاسل الاستعلام — يتعامل مع فك الترميز وقاعدة +-كمسافة والمفاتيح المكررة تلقائياً. استخدم decodeURIComponent() (مُغلَّفاً في try/catch) لقيمة واحدة أو مقطع مسار، خاصةً عندما تتوقع شرطات مائلة مُرمَّزة كـ %2F داخل مقطع. استخدم decodeURI() فقط عندما يكون لديك سلسلة URL كاملة بأحرف غير ASCII في مسارها وتحتاج الأحرف الهيكلية (/ ? & =) لتبقى مُرمَّزة في الناتج.
الأسئلة المتكررة
الأدوات ذات الصلة
لفك الترميز بنقرة واحدة دون كتابة أي كود، الصق سلسلتك المُرمَّزة بالنسبة المئوية مباشرةً في أداة فك ترميز URL من ToolDeck — تفك الترميز فوراً في المتصفح، والنتيجة جاهزة للنسخ في كودك أو طرفيتك.
Alex is a front-end and Node.js developer with extensive experience building web applications and developer tooling. He is passionate about web standards, browser APIs, and the JavaScript ecosystem. In his spare time he contributes to open-source projects and writes about modern JavaScript patterns, performance optimisation, and everything related to the web platform.
Marcus specialises in JavaScript performance, build tooling, and the inner workings of the V8 engine. He has spent years profiling and optimising React applications, working on bundler configurations, and squeezing every millisecond out of critical rendering paths. He writes about Core Web Vitals, JavaScript memory management, and the tools developers reach for when performance really matters.