فك ترميز URL في JavaScript — decodeURIComponent()

·Front-end & Node.js Developer·مراجعة بواسطةMarcus Webb·نُشر

استخدم فك تشفير 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 المُرمَّز كقيمة معامل استعلام واحدة يبدو مختلفاً تماماً عن الأصل.

After · text
Before · text
// بعد فك ترميز 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.

مثال عملي بسيط

JavaScript (browser / Node.js)
// فك ترميز قيم معاملات الاستعلام الفردية — الحالة الشائعة

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 إعادة التوجيه المستخرج من معامل استعلام

JavaScript
// 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

JavaScript
// 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

JavaScript
// 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+، ولا يتطلب استيراداً، ويتعامل مع حالات الحافة التي يُخطئ في معالجتها تقسيم السلاسل يدوياً.

تحليل سلسلة استعلام واردة

JavaScript (browser / 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 المتصفح الحالي

JavaScript (browser)
// 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 من ملف

JavaScript (Node.js 10+)
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 لفك ترميز استعلامات البحث

JavaScript (Node.js)
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

JavaScript (Node.js 10+)
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 متاح دائماً.

bash
# ── أوامر 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) تتعامل مع التسلسلات المشوَّهة بأناقة بإرجاع التسلسل الأصلي دون تغيير بدلاً من الإلقاء.

bash
npm install decode-uri-component
# or
pnpm add decode-uri-component
JavaScript (Node.js)
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، المعاملات من روابط حملات البريد الإلكتروني — يمكن أن يحتوي على هذه التسلسلات. غلاف آمن ضروري لأي كود يواجه الخارج.

غلافات فك ترميز آمنة للسيناريوهات الشائعة

JavaScript
// ── 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() مباشرةً — لا يلزم فك ترميز إضافي.

After · JavaScript
Before · JavaScript
// ✅ استخدم 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().

After · JavaScript
Before · JavaScript
// ✅ استخدم 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 الكاملة.

After · JavaScript
Before · JavaScript
// ✅ decodeURIComponent يفك ترميز جميع التسلسلات بما في ذلك & و =
const encodedFilter = 'status%3Dactive%26tier%3Dpremium%26region%3Deu-west'
const filter        = decodeURIComponent(encodedFilter)
console.log(filter)
// 'status=active&tier=premium&region=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 أو الأنظمة الخارجية.

After · JavaScript
Before · JavaScript
// ✅ غلِّف في 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 في مسارها وتحتاج الأحرف الهيكلية (/ ? & =) لتبقى مُرمَّزة في الناتج.

الأسئلة المتكررة

ما الفرق بين decodeURIComponent()‎ وdecodeURI()‎ في JavaScript؟
decodeURIComponent()‏ يفك ترميز كل تسلسل مُرمَّز بالنسبة المئوية في السلسلة، بما في ذلك الأحرف ذات المعنى الهيكلي في URL — & (%26)، = (%3D)، ? (%3F)، # (%23)، / (%2F)، و : (%3A). مصمم لفك ترميز قيم معاملات الاستعلام الفردية أو مقاطع المسار. decodeURI()‏ يحافظ على تلك الأحرف الهيكلية — لا يفك ترميز %26 أو %3D أو %3F أو %23 أو %2F أو %3A — لأنه مصمم لتعقيم URL كامل دون كسر هيكل سلسلة الاستعلام. استخدام decodeURI()‏ على قيمة معامل واحدة تحتوي & أو = مُرمَّزة سيُبقي تلك التسلسلات غير مفكوكة بصمت، مُنتِجاً ناتجاً خاطئاً.
لماذا يُلقي decodeURIComponent()‎ URIError لبعض السلاسل؟
decodeURIComponent()‏ يُلقي URIError: URI malformed عندما يحتوي الإدخال على % غير متبوع برقمين سداسيين صالحين بالضبط. أسباب شائعة: % منفرد في نهاية سلسلة ("50% خصم" مكتوبة من مستخدم)، تسلسل غير مكتمل ("%A")، أو زوج غير سداسي ("%GH"). يحدث هذا في الغالب مع استعلامات البحث التي يكتبها المستخدم أو القيم المُلصَقة من نص لم يُقصَد ترميزه في URL. الحل هو تغليف decodeURIComponent()‏ في كتلة try/catch وإعادة السلسلة الخام عند الخطأ. حزمة npm decode-uri-component تقدم نفس الحل دون الحاجة لغلاف try/catch.
هل URLSearchParams يفك ترميز القيم المُرمَّزة بالنسبة المئوية تلقائياً؟
نعم. كل قيمة يُعيدها URLSearchParams.get()‏ وURLSearchParams.getAll()‏ مفكوكة الترميز بالكامل — يجب ألا تستدعي decodeURIComponent()‏ أبداً على ناتجها. URLSearchParams يتبع أيضاً مواصفة application/x-www-form-urlencoded التي تفك ترميز + كمسافة، مما يجعله صحيحاً لإرسالات نماذج HTML واستجابات رموز OAuth. الحالة الوحيدة التي لا يستطيع فيها URLSearchParams المساعدة هي فك ترميز قيمة مُرمَّزة مستقلة ليست جزءاً من سلسلة استعلام — لذلك استخدم decodeURIComponent()‏ مع try/catch.
كيف أفك ترميز علامة + كمسافة في JavaScript؟
decodeURIComponent()‏ يعامل + كعلامة جمع حرفية — لا يُحوِّل + إلى مسافة. هذا مقصود: + كمسافة هي اتفاقية application/x-www-form-urlencoded، منفصلة عن معيار ترميز النسبة المئوية. لفك ترميز + كمسافة، استخدم URLSearchParams (الذي يتبع مواصفة النماذج المُرمَّزة)، أو استبدل + قبل استدعاء decodeURIComponent(): decodeURIComponent(str.replace(/\+/g, ' ')). استبدال + بـ %20 قبل فك الترميز يعمل أيضاً لكنه أكثر تفصيلاً. دائماً فضِّل URLSearchParams لتحليل سلاسل الاستعلام الكاملة — يتعامل بشكل صحيح مع %20 و + على حدٍّ سواء.
كيف أفك ترميز سلسلة URL في Node.js؟
استخدم نفس الدوال العامة المتاحة في جميع المتصفحات — لا يلزم استيراد في Node.js 10+. decodeURIComponent()‏ للقيم الفردية، decodeURI()‏ لسلاسل URL الكاملة، URLSearchParams لسلاسل الاستعلام. لعناوين URL طلبات HTTP الواردة، استخدم new URL(req.url, 'http://localhost') — مُنشئ URL يُوحِّد URL ويُعرِّض .searchParams (مفكوكة تلقائياً) و.pathname (مفكوكة على مستوى URL). دالة querystring.unescape()‏ القديمة لا تزال موجودة لكنها مهملة منذ Node.js 16 — فضِّل decodeURIComponent()‏ بدلاً منها.
كيف أحلِّل سلسلة استعلام URL إلى كائن في JavaScript؟
استخدم URLSearchParams مع Object.fromEntries(): const params = Object.fromEntries(new URLSearchParams(queryString)). هذا يُعطي كائناً عادياً بجميع مفاتيح المعاملات والقيم المفكوكة. لاحظ أن Object.fromEntries()‏ يحتفظ فقط بالقيمة الأخيرة للمفاتيح المكررة — للمفاتيح المتكررة مثل tag=one&tag=two، استخدم URLSearchParams.getAll('tag') بدلاً من ذلك. في المتصفح، new URLSearchParams(window.location.search)‏ يُحلِّل سلسلة استعلام الصفحة الحالية تلقائياً. في Node.js، حلِّل من new URL(req.url, base).searchParams.

الأدوات ذات الصلة

لفك الترميز بنقرة واحدة دون كتابة أي كود، الصق سلسلتك المُرمَّزة بالنسبة المئوية مباشرةً في أداة فك ترميز URL من ToolDeck — تفك الترميز فوراً في المتصفح، والنتيجة جاهزة للنسخ في كودك أو طرفيتك.

AC
Alex ChenFront-end & Node.js Developer

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.

MW
Marcus Webbالمراجع التقني

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.