<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
    <channel>
        <title>نوشته های Navid Barsalari</title>
        <link>https://virgool.io/feed/@navidbarsalari</link>
        <description>مهندس ارشد نرم‌افزار | تکنیکال لید | +۱۰ سال سابقه
علاقه‌مند به System Design، توسعه بک‌اند (Go / Node.js) و معماری دیتابیس. تمرکز فعلی من روی ساخت و توسعه سرویس‌های مقیاس‌پذیر B2B است.</description>
        <language>fa</language>
        <pubDate>2026-06-16 10:07:33</pubDate>
        <image>
            <url>https://files.virgool.io/upload/users/194232/avatar/1dheUO.png?height=120&amp;width=120</url>
            <title>Navid Barsalari</title>
            <link>https://virgool.io/@navidbarsalari</link>
        </image>

                    <item>
                <title>CORS: راهنمای جامع از مفاهیم پایه تا Security Best Practices</title>
                <link>https://virgool.io/@navidbarsalari/cors-%D8%B1%D8%A7%D9%87%D9%86%D9%85%D8%A7%DB%8C-%D8%AC%D8%A7%D9%85%D8%B9-%D8%A7%D8%B2-%D9%85%D9%81%D8%A7%D9%87%DB%8C%D9%85-%D9%BE%D8%A7%DB%8C%D9%87-%D8%AA%D8%A7-security-best-practices-p2hntfr9r7np</link>
                <description>مقدمه: مشکلی که CORS حل می‌کندفرض کنید وبسایت evil.com بدون اجازه شما بخواهد به bank.com درخواست بزند و موجودی حسابتان را بخواند. مرورگر چطور جلوی این کار را بگیرد؟پاسخ: Same-Origin Policy و CORS.۱. Same-Origin Policy: دیوار امنیتی مرورگرها۱.۱ تعریف OriginOrigin = Protocol + Domain + Porthttps://api.example.com:443/users└─┬─┘ └──────┬──────┘ └┬┘│ │ │Protocol Domain Portمثال‌های Same-Origin:// Origin: https://example.com

✅ https://example.com/api/users        // Same
✅ https://example.com:443/data         // Port 443 پیش‌فرض HTTPS
❌ http://example.com                   // Protocol متفاوت
❌ https://api.example.com              // Subdomain متفاوت
❌ https://example.com:8080             // Port متفاوت
❌ https://example.org                  // Domain متفاوت
۱.۲ چرا این محدودیت وجود دارد؟سناریوی حمله بدون Same-Origin Policy:// کاربر لاگین است در bank.com
// حالا وارد evil.com می‌شود

// evil.com می‌تواند این کار را بکند:
fetch(&#039;https://bank.com/api/account/balance&#039;, {
  credentials: &#039;include&#039; // Cookie های bank.com ارسال می‌شود
})
.then(res =&gt; res.json())
.then(data =&gt; {
  // 💀 موجودی حساب کاربر را می‌دزدد
  sendToAttacker(data.balance);
});Same-Origin Policy جلوی این حمله را می‌گیرد:مرورگر درخواست را ارسال می‌کند (نمی‌تواند جلوگیری کند)سرور پاسخ می‌دهدمرورگر پاسخ را به JavaScript نمی‌دهد ❌۲.۱ نحوه کار CORS┌─────────────┐ ┌─────────────┐│ Frontend │ │ Backend ││ (Browser) │ │ Server │└──────┬──────┘ └──────┬──────┘│ ││ GET /api/users ││ Origin: https://frontend.com │├─────────────────────────────────&gt;││ ││ │ ✅ بررسی Origin│ ││ 200 OK ││ Access-Control-Allow-Origin: ││ https://frontend.com ││&lt;─────────────────────────────────┤│ │✅ مرورگر │پاسخ را تحویل │JavaScript می‌دهد │اگر Header نباشد:// Console Error:
// Access to fetch at &#039;https://api.example.com/users&#039; 
// from origin &#039;https://frontend.com&#039; has been blocked 
// by CORS policy: No &#039;Access-Control-Allow-Origin&#039; 
// header is present on the requested resource.۳. انواع درخواست‌های CORS۳.۱ Simple Requestsدرخواست‌هایی که بدون Preflight اجرا می‌شوند:شرایط:Method: فقط GET, POST, HEADHeaders: فقط Safe Headers مثل Content-Type, AcceptContent-Type: فقط application/x-www-form-urlencoded, multipart/form-data, text/plainمثال:// ✅ Simple Request
fetch(&#039;https://api.example.com/users&#039;, {
  method: &#039;GET&#039;,
  headers: {
    &#039;Accept&#039;: &#039;application/json&#039;
  }
})پاسخ سرور:HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://frontend.com
Content-Type: application/json

{&quot;users&quot;: [...]۳.۲ Preflight Requestsدرخواست‌هایی که قبل از ارسال اصلی، یک درخواست OPTIONS ارسال می‌شود.چه زمانی Preflight اتفاق می‌افتد؟Method: PUT, DELETE, PATCHHeaders سفارشی: Authorization, X-Custom-HeaderContent-Type: application/jsonمثال:// ❌ Preflight Request
fetch(&#039;https://api.example.com/users&#039;, {
  method: &#039;POST&#039;,
  headers: {
    &#039;Content-Type&#039;: &#039;application/json&#039;,
    &#039;Authorization&#039;: &#039;Bearer token123&#039;
  },
  body: JSON.stringify({ name: &#039;Ali&#039; })
});
جریان کامل:┌─────────────┐ ┌─────────────┐│ Browser │ │ Server │└──────┬──────┘ └──────┬──────┘│ ││ 1️⃣ OPTIONS /api/users (Preflight)││ Origin: https://frontend.com ││ Access-Control-Request-Method: ││ POST ││ Access-Control-Request-Headers: ││ Authorization, Content-Type │├─────────────────────────────────&gt;││ ││ │ ✅ بررسی│ ││ 200 OK ││ Access-Control-Allow-Origin: ││ https://frontend.com ││ Access-Control-Allow-Methods: ││ POST, GET, OPTIONS ││ Access-Control-Allow-Headers: ││ Authorization, Content-Type ││ Access-Control-Max-Age: 86400 ││&lt;─────────────────────────────────┤│ ││ 2️⃣ POST /api/users (Actual) ││ Authorization: Bearer token123 ││ Content-Type: application/json ││ Body: {“name”: “Ali”} │├─────────────────────────────────&gt;││ ││ 201 Created ││ Access-Control-Allow-Origin: ││ https://frontend.com ││&lt;─────────────────────────────────┤۴. CORS Headers: راهنمای کامل۴.۱ Response Headers (سرور به مرورگر)Access-Control-Allow-Origin ⭐مهم‌ترین Header - مشخص می‌کند کدام Origin مجاز است.# ✅ یک Origin خاص
Access-Control-Allow-Origin: https://frontend.com

# ⚠️ همه (خطرناک برای API های حساس)
Access-Control-Allow-Origin: *

# ❌ چند Origin (غیرمجاز - باید Dynamic باشد)
Access-Control-Allow-Origin: https://app1.com, https://app2.com
پیاده‌سازی Dynamic:// NestJS
@Controller(&#039;users&#039;)
export class UsersController {
  @Get()
  getUsers(@Req() req: Request, @Res() res: Response) {
    const allowedOrigins = [
      &#039;https://frontend.com&#039;,
      &#039;https://app.frontend.com&#039;
    ];
    
    const origin = req.headers.origin;
    if (allowedOrigins.includes(origin)) {
      res.setHeader(&#039;Access-Control-Allow-Origin&#039;, origin);
    }
    
    return res.json({ users: [...] });
  }
}
Access-Control-Allow-MethodsMethod های مجاز را مشخص می‌کند.Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONSHeader های سفارشی مجاز.Access-Control-Allow-Headers: Authorization, Content-Type, X-Request-ID
Access-Control-Allow-Credentials 🔐اجازه ارسال Cookie و Authentication Headers.Access-Control-Allow-Credentials: true
⚠️ نکته امنیتی:# ❌ ترکیب خطرناک
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true

# این ترکیب مجاز نیست! مرورگر Error می‌دهد.✅ روش صحیح:Access-Control-Allow-Origin: https://frontend.com
Access-Control-Allow-Credentials: true// Frontend
fetch(&#039;https://api.example.com/profile&#039;, {
  credentials: &#039;include&#039; // Cookie ها ارسال می‌شود
});
Access-Control-Max-Ageمدت زمان Cache کردن Preflight (ثانیه).Access-Control-Max-Age: 86400  # 24 ساعت
Header هایی که JavaScript می‌تواند بخواند.Access-Control-Expose-Headers: X-Total-Count, X-Page-Number
بدون این Header:fetch(&#039;https://api.example.com/users&#039;)
  .then(res =&gt; {
    console.log(res.headers.get(&#039;X-Total-Count&#039;)); // ❌ null
  });با این Header:// ✅ مقدار را می‌خواند
console.log(res.headers.get(&#039;X-Total-Count&#039;)); // &quot;150&quot;۴.۲ Request Headers (مرورگر به سرور)Originمرورگر خودکار اضافه می‌کند.Origin: https://frontend.comAccess-Control-Request-Method (Preflight)Access-Control-Request-Method: DELETEAccess-Control-Request-Headers (Preflight)Access-Control-Request-Headers: Authorization, X-Custom-Header۵. چرا CORS فقط در مرورگر است؟۵.۱ تفاوت Browser vs Postman/cURL// ✅ Postman/cURL - بدون محدودیت
curl https://bank.com/api/balance
// پاسخ را می‌گیرد حتی بدون CORS Headers

// ❌ Browser - با محدودیت
fetch(&#039;https://bank.com/api/balance&#039;)
// Error: CORS policy blocked
چرا؟سناریوی خطرناک (فقط در Browser):// کاربر در gmail.com لاگین است
// evil.com این کد را اجرا می‌کند:

fetch(&#039;https://gmail.com/api/emails&#039;, {
  credentials: &#039;include&#039; // Cookie های Gmail ارسال می‌شود
})
.then(res =&gt; res.json())
.then(emails =&gt; {
  // 💀 ایمیل‌های کاربر را می‌دزدد
  sendToAttacker(emails);
});CORS جلوی این حمله را می‌گیرد چون:مرورگر درخواست را می‌فرستد (با Cookie)Gmail پاسخ می‌دهدمرورگر چک می‌کند: آیا Access-Control-Allow-Origin شامل evil.com است؟❌ خیر → پاسخ را به JavaScript نمی‌دهد// Backend (Node.js)
const axios = require(&#039;axios&#039;);

// ✅ بدون مشکل
const response = await axios.get(&#039;https://api.example.com/users&#039;);دلیل:Backend Context کاربر نداردCookie های کاربر ارسال نمی‌شودحمله CSRF/XSS معنا ندارد۶. پیاده‌سازی CORS در Backend۶.۱ NestJS// main.ts
import { NestFactory } from &#039;@nestjs/core&#039;;
import { AppModule } from &#039;./app.module&#039;;

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  
  // ✅ روش 1: ساده (Development)
  app.enableCors();
  
  // ✅ روش 2: پیشرفته (Production)
  app.enableCors({
    origin: [&#039;https://frontend.com&#039;, &#039;https://app.frontend.com&#039;],
    methods: [&#039;GET&#039;, &#039;POST&#039;, &#039;PUT&#039;, &#039;DELETE&#039;, &#039;PATCH&#039;],
    allowedHeaders: [&#039;Content-Type&#039;, &#039;Authorization&#039;],
    credentials: true,
    maxAge: 3600,
  });
  
  // ✅ روش 3: Dynamic
  app.enableCors({
    origin: (origin, callback) =&gt; {
      const allowedOrigins = [
        &#039;https://frontend.com&#039;,
        /\.example\.com$/, // همه subdomain های example.com
      ];
      
      if (!origin || allowedOrigins.some(allowed =&gt; 
        typeof allowed === &#039;string&#039; 
          ? allowed === origin 
          : allowed.test(origin)
      )) {
        callback(null, true);
      } else {
        callback(new Error(&#039;Not allowed by CORS&#039;));
      }
    },
    credentials: true,
  });
  
  await app.listen(3000);
}
bootstrap();
۶.۲ Express.jsconst express = require(&#039;express&#039;);
const cors = require(&#039;cors&#039;);

const app = express();

// ✅ روش 1: همه Origins
app.use(cors());

// ✅ روش 2: محدود
app.use(cors({
  origin: &#039;https://frontend.com&#039;,
  credentials: true,
  optionsSuccessStatus: 200
}));

// ✅ روش 3: چند Origin
const allowedOrigins = [
  &#039;https://frontend.com&#039;,
  &#039;https://app.frontend.com&#039;
];

app.use(cors({
  origin: (origin, callback) =&gt; {
    if (!origin || allowedOrigins.includes(origin)) {
      callback(null, true);
    } else {
      callback(new Error(&#039;Not allowed by CORS&#039;));
    }
  },
  credentials: true
}));

// ✅ روش 4: Per-Route
app.get(&#039;/public&#039;, cors(), (req, res) =&gt; {
  res.json({ message: &#039;Public API&#039; });
});

app.get(&#039;/private&#039;, cors({
  origin: &#039;https://frontend.com&#039;,
  credentials: true
}), (req, res) =&gt; {
  res.json({ message: &#039;Private API&#039; });
});
۶.۳ Django (Python)# settings.py
INSTALLED_APPS = [
    &#039;corsheaders&#039;,
]

MIDDLEWARE = [
    &#039;corsheaders.middleware.CorsMiddleware&#039;,
    &#039;django.middleware.common.CommonMiddleware&#039;,
]

# ✅ Development
CORS_ALLOW_ALL_ORIGINS = True

# ✅ Production
CORS_ALLOWED_ORIGINS = [
    &quot;https://frontend.com&quot;,
    &quot;https://app.frontend.com&quot;,
]

CORS_ALLOW_CREDENTIALS = True

CORS_ALLOW_METHODS = [
    &#039;DELETE&#039;,
    &#039;GET&#039;,
    &#039;OPTIONS&#039;,
    &#039;PATCH&#039;,
    &#039;POST&#039;,
    &#039;PUT&#039;,
]

CORS_ALLOW_HEADERS = [
    &#039;accept&#039;,
    &#039;authorization&#039;,
    &#039;content-type&#039;,
    &#039;x-csrf-token&#039;,
]
۶.۴ Laravel (PHP)// config/cors.php
return [
    &#039;paths&#039; =&gt; [&#039;api/*&#039;],
    
    &#039;allowed_methods&#039; =&gt; [&#039;*&#039;],
    
    &#039;allowed_origins&#039; =&gt; [
        &#039;https://frontend.com&#039;,
        &#039;https://app.frontend.com&#039;,
    ],
    
    &#039;allowed_origins_patterns&#039; =&gt; [
        &#039;/\.example\.com$/&#039;,
    ],
    
    &#039;allowed_headers&#039; =&gt; [&#039;*&#039;],
    
    &#039;exposed_headers&#039; =&gt; [&#039;X-Total-Count&#039;],
    
    &#039;max_age&#039; =&gt; 3600,
    
    &#039;supports_credentials&#039; =&gt; true,
];
۷. مشکلات رایج و راه‌حل۷.۱ Error: No ‘Access-Control-Allow-Origin’ headerAccess to fetch at ‘https://api.example.com/users’from origin ‘https://frontend.com’ has been blockedby CORS policy: No ‘Access-Control-Allow-Origin’header is present on the requested resource.راه‌حل:// Backend
app.use(cors({
  origin: &#039;https://frontend.com&#039;
}));**۷.۲ Error: Credentials flag is true, but Allow-Origin is *کد اشتباه:// ❌ Backend
app.use(cors({
  origin: &#039;*&#039;,
  credentials: true
}));

// Frontend
fetch(&#039;https://api.example.com/profile&#039;, {
  credentials: &#039;include&#039;
});Error:The value of the ‘Access-Control-Allow-Origin’ headermust not be the wildcard ‘*’ when the request’scredentials mode is ‘include’.راه‌حل:                                                                    javascript// ✅ Backend
app.use(cors({
  origin: &#039;https://frontend.com&#039;, // Origin مشخص
  credentials: true
}));۷.۳ Preflight OPTIONS درخواست 403 می‌دهدمشکل: Middleware های Authentication قبل از CORS اجرا می‌شوند.// ❌ ترتیب اشتباه
app.use(authMiddleware); // OPTIONS را Block می‌کند
app.use(cors());

// ✅ ترتیب صحیح
app.use(cors());
app.use(authMiddleware);
یا:app.use((req, res, next) =&gt; {
  if (req.method === &#039;OPTIONS&#039;) {
    return res.sendStatus(200); // Preflight را بدون Auth پاس کن
  }
  next();
});

app.use(authMiddleware);۷.۴ localhost با Port های مختلف// Frontend: http://localhost:3000
// Backend: http://localhost:4000

// ❌ این کار نمی‌کند
fetch(&#039;http://localhost:4000/api/users&#039;);
// Error: CORS

// ✅ Backend باید اجازه بدهد
app.use(cors({
  origin: &#039;http://localhost:3000&#039;
}));۷.۵ Custom Headers در Preflight// Frontend
fetch(&#039;https://api.example.com/users&#039;, {
  headers: {
    &#039;X-Custom-Header&#039;: &#039;value&#039;
  }
});Error:Request header field X-Custom-Header is not allowedby Access-Control-Allow-Headers in preflight response.راه‌حل:// Backend
app.use(cors({
  allowedHeaders: [&#039;Content-Type&#039;, &#039;Authorization&#039;, &#039;X-Custom-Header&#039;]
}));۸. Security Best Practices۸.۱ هرگز origin: &#039;*&#039; با credentials: true استفاده نکنید// 💀 خطرناک
app.use(cors({
  origin: &#039;*&#039;,
  credentials: true
}));
چرا؟ هر سایتی می‌تواند با Cookie های کاربر به API شما درخواست بزند.۸.۲ Whitelist Origins را محدود کنید// ✅ فقط Origin های مجاز
const allowedOrigins = [
  &#039;https://frontend.com&#039;,
  &#039;https://app.frontend.com&#039;,
  process.env.NODE_ENV === &#039;development&#039; &amp;&amp; &#039;http://localhost:3000&#039;
].filter(Boolean);

app.use(cors({
  origin: (origin, callback) =&gt; {
    if (!origin || allowedOrigins.includes(origin)) {
      callback(null, true);
    } else {
      callback(new Error(&#039;Not allowed by CORS&#039;));
    }
  }
}));۸.۳ Preflight Cachingapp.use(cors({
  maxAge: 86400 // 24 ساعت
}));فایده: کاهش ۹۰٪ درخواست‌های OPTIONS.۸.۴ محدود کردن Methods// ✅ فقط Method های مورد نیاز
app.use(cors({
  methods: [&#039;GET&#039;, &#039;POST&#039;, &#039;PUT&#039;, &#039;DELETE&#039;]
}));

// ❌ همه Method ها
app.use(cors({
  methods: &#039;*&#039;
}));۸.۵ HTTPS اجباری در Productionapp.use(cors({
  origin: (origin, callback) =&gt; {
    if (process.env.NODE_ENV === &#039;production&#039; &amp;&amp; 
        origin &amp;&amp; !origin.startsWith(&#039;https://&#039;)) {
      callback(new Error(&#039;HTTPS required&#039;));
    } else {
      callback(null, true);
    }
  }
}));۹. CORS vs Proxy۹.۱ مشکل CORS در Developmentjavascript// Frontend (localhost:3000)
fetch(&#039;https://api.example.com/users&#039;);
// Error: CORS
راه‌حل 1: فعال کردن CORS در Backendjavascript// Backend
app.use(cors({
  origin: &#039;http://localhost:3000&#039;
}));
راه‌حل 2: استفاده از Proxy (بدون تغییر Backend)javascript// vite.config.js (Vite)
export default {
  server: {
    proxy: {
      &#039;/api&#039;: {
        target: &#039;https://api.example.com&#039;,
        changeOrigin: true,
        rewrite: (path) =&gt; path.replace(/^\/api/, &#039;&#039;)
      }
    }
  }
}

// Frontend
fetch(&#039;/api/users&#039;); // ✅ بدون CORS Error
// Vite آن را به https://api.example.com/users تبدیل می‌کند
javascript// webpack.config.js (React/Webpack)
module.exports = {
  devServer: {
    proxy: {
      &#039;/api&#039;: {
        target: &#039;https://api.example.com&#039;,
        changeOrigin: true,
        pathRewrite: { &#039;^/api&#039;: &#039;&#039; }
      }
    }
  }
}
مقایسه:CORS:✅ Production-ready✅ کنترل دقیق❌ نیاز به تغییر BackendProxy:✅ فقط Development✅ بدون تغییر Backend❌ در Production کار نمی‌کند۱۰. CORS در معماری Microservices۱۰.۱ API Gateway Pattern┌──────────┐│ Frontend │└────┬─────┘││ https://api.example.com│┌────▼─────────┐│ API Gateway │ ← فقط اینجا CORS فعال است└────┬─────────┘│├──────────┬──────────┬──────────┐│ │ │ │┌────▼────┐ ┌──▼───┐ ┌────▼────┐ ┌───▼────┐│ Auth │ │ User │ │ Order │ │ Payment││ Service │ │Service│ │ Service │ │Service │└─────────┘ └──────┘ └─────────┘ └────────┘↑ ↑ ↑ ↑└──────────┴──────────┴──────────┘Internal (بدون CORS)پیاده‌سازی:                                            content_copy                        javascript// API Gateway (NestJS)
async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  
  app.enableCors({
    origin: &#039;https://frontend.com&#039;,
    credentials: true
  });
  
  await app.listen(3000);
}

// Microservices (بدون CORS)
// auth-service, user-service, order-service
// هیچ CORS config ندارند چون فقط از Gateway صدا زده می‌شوند
۱۱. Testing CORS۱۱.۱ Manual Testing با cURL                                            content_copy                        bash# Preflight Request
curl -X OPTIONS https://api.example.com/users \
  -H &quot;Origin: https://frontend.com&quot; \
  -H &quot;Access-Control-Request-Method: POST&quot; \
  -H &quot;Access-Control-Request-Headers: Authorization&quot; \
  -v

# بررسی Response Headers:
# Access-Control-Allow-Origin: https://frontend.com
# Access-Control-Allow-Methods: POST, GET, OPTIONS
# Access-Control-Allow-Headers: Authorization
                                            content_copy                        bash# Actual Request
curl -X POST https://api.example.com/users \
  -H &quot;Origin: https://frontend.com&quot; \
  -H &quot;Authorization: Bearer token&quot; \
  -H &quot;Content-Type: application/json&quot; \
  -d &#039;{&quot;name&quot;:&quot;Ali&quot;}&#039; \
  -v
۱۱.۲ Automated Testing (Jest)                                            content_copy                        typescript// cors.spec.ts
import { Test } from &#039;@nestjs/testing&#039;;
import { INestApplication } from &#039;@nestjs/common&#039;;
import * as request from &#039;supertest&#039;;
import { AppModule } from &#039;./app.module&#039;;

describe(&#039;CORS&#039;, () =&gt; {
  let app: INestApplication;

  beforeAll(async () =&gt; {
    const moduleRef = await Test.createTestingModule({
      imports: [AppModule],
    }).compile();

    app = moduleRef.createNestApplication();
    app.enableCors({
      origin: &#039;https://frontend.com&#039;,
      credentials: true,
    });
    await app.init();
  });

  it(&#039;should allow requests from allowed origin&#039;, () =&gt; {
    return request(app.getHttpServer())
      .get(&#039;/users&#039;)
      .set(&#039;Origin&#039;, &#039;https://frontend.com&#039;)
      .expect(200)
      .expect(&#039;Access-Control-Allow-Origin&#039;, &#039;https://frontend.com&#039;);
  });

  it(&#039;should block requests from disallowed origin&#039;, () =&gt; {
    return request(app.getHttpServer())
      .get(&#039;/users&#039;)
      .set(&#039;Origin&#039;, &#039;https://evil.com&#039;)
      .expect((res) =&gt; {
        expect(res.headers[&#039;access-control-allow-origin&#039;]).toBeUndefined();
      });
  });

  it(&#039;should handle preflight correctly&#039;, () =&gt; {
    return request(app.getHttpServer())
      .options(&#039;/users&#039;)
      .set(&#039;Origin&#039;, &#039;https://frontend.com&#039;)
      .set(&#039;Access-Control-Request-Method&#039;, &#039;POST&#039;)
      .set(&#039;Access-Control-Request-Headers&#039;, &#039;Authorization&#039;)
      .expect(200)
      .expect(&#039;Access-Control-Allow-Methods&#039;, /POST/)
      .expect(&#039;Access-Control-Allow-Headers&#039;, /Authorization/);
  });

  afterAll(async () =&gt; {
    await app.close();
  });
});
۱۲. سوالات مصاحبهسطح Junior:Q1: CORS چیست و چرا وجود دارد؟A: مکانیزمی برای کنترل دسترسی Cross-Origin در مرورگر.جلوی حملات XSS/CSRF را می‌گی</description>
                <category>Navid Barsalari</category>
                <author>Navid Barsalari</author>
                <pubDate>Fri, 22 May 2026 12:15:10 +0330</pubDate>
            </item>
                    <item>
                <title>Set و WeakSet در JavaScript: راهنمای جامع از مبتدی تا حرفه‌ای</title>
                <link>https://virgool.io/@navidbarsalari/set-%D9%88-weakset-%D8%AF%D8%B1-javascript-%D8%B1%D8%A7%D9%87%D9%86%D9%85%D8%A7%DB%8C-%D8%AC%D8%A7%D9%85%D8%B9-%D8%A7%D8%B2-%D9%85%D8%A8%D8%AA%D8%AF%DB%8C-%D8%AA%D8%A7-%D8%AD%D8%B1%D9%81%D9%87-%D8%A7%DB%8C-fnwmyvboji23</link>
                <description>مقدمه: چرا Array کافی نیست؟فرض کنید می‌خواهید لیستی از کاربران آنلاین را نگه دارید. با Array می‌نویسید:const onlineUsers = [];

function addUser(userId) {
  if (!onlineUsers.includes(userId)) {
    onlineUsers.push(userId);
  }
}

addUser(101);
addUser(102);
addUser(101); // تکراری!
مشکل چیست؟Performance: ()includes باید کل آرایه را بگردد → O(n)تکرار: باید خودمان چک کنیم که آیتم تکراری نباشدحذف: ()splice برای حذف یک آیتم خاص کند استراه حل: Set - یک ساختار داده که فقط مقادیر یکتا نگه می‌دارد و عملیات‌هایش O(1) است.بخش ۱: Set - مجموعه‌های یکتا۱.۱ ایجاد و استفاده پایه// ایجاد Set خالی
const mySet = new Set();

// ایجاد با مقادیر اولیه
const numbers = new Set([1, 2, 3, 3, 4]); // {1, 2, 3, 4}

// اضافه کردن
mySet.add(10);
mySet.add(20);
mySet.add(10); // تکراری، اضافه نمی‌شود

console.log(mySet.size); // 2

// چک کردن وجود
console.log(mySet.has(10)); // true

// حذف
mySet.delete(10);

// پاک کردن همه
mySet.clear();
۱.۲ تفاوت Set با Arrayمقادیر تکراری:Array: مجاز است، می‌توانید چندین بار یک مقدار را اضافه کنیدSet: غیرمجاز، هر مقدار فقط یک بار ذخیره می‌شوددسترسی با Index:Array: arr[0] برای دسترسی به اولین عنصرSet: ندارد، باید با Iterator یا تبدیل به Array دسترسی پیدا کنیدجستجو (has یا includes):Array: پیچیدگی )O(n) - باید کل آرایه را بگرددSet: پیچیدگی O(1) - جستجوی فوری با Hash Tableحذف عنصر:Array: پیچیدگی O(n) - باید پیدا کند و Index ها را جابجا کندSet: پیچیدگی O(1) - حذف فوریحفظ ترتیب:هر دو ترتیب اضافه شدن (insertion order) را حفظ می‌کنندبخش ۲: کاربردهای واقعی Set۲.۱ حذف تکراری از آرایه// روش قدیمی (کند)
function removeDuplicates(arr) {
  return arr.filter((item, index) =&gt; arr.indexOf(item) === index);
}

// روش حرفه‌ای با Set
function removeDuplicatesFast(arr) {
  return [...new Set(arr)];
}

const numbers = [1, 2, 2, 3, 4, 4, 5];
console.log(removeDuplicatesFast(numbers)); // [1, 2, 3, 4, 5]
                                                                   Performance:Array method: O(n^2)
Set method:O(n)۲.۲ عملیات ریاضی روی مجموعه‌ها// اجتماع (Union)
function union(setA, setB) {
  return new Set([...setA, ...setB]);
}

// اشتراک (Intersection)
function intersection(setA, setB) {
  return new Set([...setA].filter(x =&gt; setB.has(x)));
}

// تفاضل (Difference)
function difference(setA, setB) {
  return new Set([...setA].filter(x =&gt; !setB.has(x)));
}

// مثال
const admins = new Set([&#039;user1&#039;, &#039;user2&#039;, &#039;user3&#039;]);
const moderators = new Set([&#039;user2&#039;, &#039;user3&#039;, &#039;user4&#039;]);

console.log(union(admins, moderators)); 
// Set {&#039;user1&#039;, &#039;user2&#039;, &#039;user3&#039;, &#039;user4&#039;}

console.log(intersection(admins, moderators)); 
// Set {&#039;user2&#039;, &#039;user3&#039;}

console.log(difference(admins, moderators)); 
// Set {&#039;user1&#039;}
۲.۳ ردیابی کاربران آنلاین (Real-time)class OnlineUsersTracker {
  constructor() {
    this.onlineUsers = new Set();
  }

  userConnected(userId) {
    this.onlineUsers.add(userId);
    console.log(`User ${userId} is online. Total: ${this.onlineUsers.size}`);
  }

  userDisconnected(userId) {
    this.onlineUsers.delete(userId);
    console.log(`User ${userId} went offline. Total: ${this.onlineUsers.size}`);
  }

  isOnline(userId) {
    return this.onlineUsers.has(userId); // O(1)
  }

  getOnlineCount() {
    return this.onlineUsers.size;
  }
}

// استفاده در WebSocket
const tracker = new OnlineUsersTracker();
tracker.userConnected(101);
tracker.userConnected(102);
tracker.userConnected(101); // تکراری، اثری ندارد
console.log(tracker.isOnline(101)); // true
۲.۴ جلوگیری از Callback تکراریclass EventEmitter {
  constructor() {
    this.listeners = new Map(); // event -&gt; Set of callbacks
  }

  on(event, callback) {
    if (!this.listeners.has(event)) {
      this.listeners.set(event, new Set());
    }
    this.listeners.get(event).add(callback);
  }

  off(event, callback) {
    if (this.listeners.has(event)) {
      this.listeners.get(event).delete(callback);
    }
  }

  emit(event, data) {
    if (this.listeners.has(event)) {
      this.listeners.get(event).forEach(callback =&gt; callback(data));
    }
  }
}

// استفاده
const emitter = new EventEmitter();
const handler = (data) =&gt; console.log(&#039;Received:&#039;, data);

emitter.on(&#039;message&#039;, handler);
emitter.on(&#039;message&#039;, handler); // تکراری، فقط یک بار اضافه می‌شود
emitter.emit(&#039;message&#039;, &#039;Hello&#039;); // فقط یک بار اجرا می‌شود
بخش ۳: WeakSet - مدیریت حافظه هوشمند۳.۱ تفاوت Set و WeakSetنوع مقادیر:Set: هر نوع داده‌ای (number, string, object, …)WeakSet: فقط Object (نمی‌توان primitive اضافه کرد)Garbage Collection:Set: رفرنس قوی نگه می‌دارد، مانع آزادسازی حافظه می‌شودWeakSet: رفرنس ضعیف، اجازه می‌دهد Garbage Collector حافظه را آزاد کندقابلیت Iterate:Set: بله، می‌توان با for...of یا forEach پیمایش کردWeakSet: خیر، غیرقابل پیمایش استمتدهای موجود:Set: add, has, delete, clear, sizeWeakSet: فقط add, has, delete (بدون clear و size)کاربرد اصلی:Set: ذخیره دائمی داده‌های یکتاWeakSet: ذخیره موقت برای Metadata و Tracking۳.۲ مشکل Memory Leak با Set// ❌ مشکل: Memory Leak
let user = { id: 1, name: &#039;Gholi&#039; };
const processedUsers = new Set();
processedUsers.add(user);

user = null; // می‌خواهیم user را آزاد کنیم

// اما Set هنوز رفرنس دارد!
// Garbage Collector نمی‌تواند حافظه را آزاد کند
console.log(processedUsers.size); // 1


--------
// ✅ راه حل: WeakSet
let user = { id: 1, name: &#039;Gholi&#039; };
const processedUsers = new WeakSet();
processedUsers.add(user);

user = null; // حالا Garbage Collector می‌تواند حافظه را آزاد کند

// WeakSet به صورت خودکار آبجکت را حذف می‌کند

۳.۳ کاربرد واقعی: ردیابی DOM Elementsclass DOMTracker {
  constructor() {
    this.trackedElements = new WeakSet();
  }

  track(element) {
    if (!(element instanceof HTMLElement)) {
      throw new Error(&#039;Only DOM elements can be tracked&#039;);
    }
    this.trackedElements.add(element);
    console.log(&#039;Element tracked&#039;);
  }

  isTracked(element) {
    return this.trackedElements.has(element);
  }
}

// استفاده
const tracker = new DOMTracker();
let button = document.createElement(&#039;button&#039;);

tracker.track(button);
console.log(tracker.isTracked(button)); // true

// وقتی button از DOM حذف شود
button.remove();
button = null;

// WeakSet به صورت خودکار حافظه را آزاد می‌کند
// Memory Leak نداریم!
۳.۴ کاربرد: Private Data Patternconst privateData = new WeakSet();

class SecureUser {
  constructor(name) {
    this.name = name;
    privateData.add(this); // علامت‌گذاری به عنوان معتبر
  }

  static isValid(user) {
    return privateData.has(user);
  }
}

// استفاده
const user1 = new SecureUser(&#039;Gholi&#039;);
const fakeUser = { name: &#039;Hacker&#039; };

console.log(SecureUser.isValid(user1)); // true
console.log(SecureUser.isValid(fakeUser)); // false
۳.۵ کاربرد: جلوگیری از Circular Referencefunction deepClone(obj, cloned = new WeakSet()) {
  // جلوگیری از Infinite Loop
  if (cloned.has(obj)) {
    throw new Error(&#039;Circular reference detected&#039;);
  }

  if (typeof obj !== &#039;object&#039; || obj === null) {
    return obj;
  }

  cloned.add(obj);

  const clone = Array.isArray(obj) ? [] : {};
  for (let key in obj) {
    clone[key] = deepClone(obj[key], cloned);
  }

  return clone;
}

// مثال با Circular Reference
const user = { name: &#039;Gholi&#039; };
user.self = user; // Circular!

try {
  deepClone(user);
} catch (e) {
  console.log(e.message); // &quot;Circular reference detected&quot;
}
بخش ۴: نکات Performance و بهینه‌سازی۴.۱ مقایسه سرعت// Benchmark: جستجو در 1 میلیون آیتم
const size = 1_000_000;

// Array
const arr = Array.from({ length: size }, (_, i) =&gt; i);
console.time(&#039;Array includes&#039;);
arr.includes(999_999);
console.timeEnd(&#039;Array includes&#039;); // ~10ms

// Set
const set = new Set(arr);
console.time(&#039;Set has&#039;);
set.has(999_999);
console.timeEnd(&#039;Set has&#039;); // ~0.001ms (1000x سریع‌تر!)
۴.۲ مصرف حافظه// Array: 8 bytes per item (number) + overhead
const arr = new Array(1_000_000).fill(1);

// Set: ~24 bytes per item (hash table overhead)
const set = new Set(arr);

// نتیجه: Set حافظه بیشتری مصرف می‌کند
// اما سرعت بالاتری دارد
قانون کلی:اگر داده‌ها کمتر از ۱۰۰۰ آیتم: Array کافی استاگر نیاز به جستجوی مکرر: Setاگر نیاز به Index: Arrayاگر نیاز به Garbage Collection: WeakSet۴.۳ بهینه‌سازی: Bulk Operations// ❌ کند: اضافه کردن تک تک
const set = new Set();
for (let i = 0; i &lt; 10000; i++) {
  set.add(i);
}

// ✅ سریع: ایجاد با آرایه
const fastSet = new Set(Array.from({ length: 10000 }, (_, i) =&gt; i));
بخش ۵: نکات امنیتی۵.۱ جلوگیری از Prototype Pollution// ❌ خطرناک
const userRoles = new Set();
userRoles.add(&#039;admin&#039;);

// حمله
Object.prototype.isAdmin = true;
console.log(userRoles.isAdmin); // true (خطرناک!)

// ✅ امن
function hasRole(set, role) {
  return set.has(role); // فقط از متدهای Set استفاده کن
}
۵.۲ Sanitization ورودیclass SecureSet {
  constructor() {
    this.data = new Set();
  }

  add(value) {
    // فقط مقادیر معتبر
    if (typeof value === &#039;string&#039; &amp;&amp; value.length &lt; 100) {
      this.data.add(value);
    } else {
      throw new Error(&#039;Invalid input&#039;);
    }
  }

  has(value) {
    return this.data.has(value);
  }
}
۵.۳ Rate Limiting با Setclass RateLimiter {
  constructor(maxRequests, windowMs) {
    this.maxRequests = maxRequests;
    this.windowMs = windowMs;
    this.requests = new Map(); // IP -&gt; Set of timestamps
  }

  isAllowed(ip) {
    const now = Date.now();
    
    if (!this.requests.has(ip)) {
      this.requests.set(ip, new Set());
    }

    const userRequests = this.requests.get(ip);

    // حذف timestamp های قدیمی
    for (let timestamp of userRequests) {
      if (now - timestamp &gt; this.windowMs) {
        userRequests.delete(timestamp);
      }
    }

    if (userRequests.size &gt;= this.maxRequests) {
      return false; // Rate limit exceeded
    }

    userRequests.add(now);
    return true;
  }
}

// استفاده
const limiter = new RateLimiter(5, 60000); // 5 requests per minute

console.log(limiter.isAllowed(&#039;192.168.1.1&#039;)); // true
// ... 5 بار دیگر
console.log(limiter.isAllowed(&#039;192.168.1.1&#039;)); // false (blocked)
بخش ۶: سوالات مصاحبهسوال ۱: تفاوت Set و Array چیست؟پاسخ:Set فقط مقادیر یکتا نگه می‌داردعملیات has, add, delete در Set: O(1)O(1)O(1)، در Array: O(n)O(n)O(n)Set Index ندارد، Array داردSet برای جستجوی سریع، Array برای ترتیب و دسترسی Indexسوال ۲: چه زمانی از WeakSet استفاده کنیم؟پاسخ:وقتی می‌خواهیم Object ها را Track کنیم بدون اینکه مانع Garbage Collection شویممثال: ردیابی DOM Elements، Private Data، جلوگیری از Circular Referenceسوال ۳: چرا WeakSet قابل Iterate نیست؟پاسخ:چون Garbage Collector می‌تواند هر لحظه آبجکت‌ها را حذف کند. اگر Iterate می‌شد، نتیجه غیرقابل پیش‌بینی بود.سوال ۴: کد زیر چه خروجی دارد؟const set = new Set([1, 2, 3]);

set.add(1);

set.add(&#039;1&#039;);

console.log(set.size);پاسخ: 4چون 1 (number) و &#039;1&#039; (string) دو مقدار متفاوت هستند (strict equality).سوال ۵: چطور Set را به Array تبدیل کنیم؟const set = new Set([1, 2, 3]);

// روش 1: Spread
const arr1 = [...set];

// روش 2: Array.from
const arr2 = Array.from(set);

// روش 3: Loop
const arr3 = [];
set.forEach(item =&gt; arr3.push(item));
سوال ۶: پیاده‌سازی Set با Objectclass MySet {
  constructor() {
    this.items = {};
    this.length = 0;
  }

  add(value) {
    if (!this.has(value)) {
      this.items[value] = true;
      this.length++;
    }
    return this;
  }

  has(value) {
    return this.items.hasOwnProperty(value);
  }

  delete(value) {
    if (this.has(value)) {
      delete this.items[value];
      this.length--;
      return true;
    }
    return false;
  }

  clear() {
    this.items = {};
    this.length = 0;
  }

  get size() {
    return this.length;
  }
}
بخش ۷: Best Practices✅ استفاده صحیح// 1. برای یکتاسازی
const uniqueIds = new Set(arrayWithDuplicates);

// 2. برای جستجوی سریع
const allowedUsers = new Set([&#039;user1&#039;, &#039;user2&#039;, &#039;user3&#039;]);
if (allowedUsers.has(currentUser)) { /* ... */ }

// 3. برای عملیات مجموعه‌ای
const admins = new Set([...]);
const moderators = new Set([...]);
const allStaff = new Set([...admins, ...moderators]);

// 4. WeakSet برای DOM tracking
const clickedElements = new WeakSet();
element.addEventListener(&#039;click&#039;, () =&gt; {
  clickedElements.add(element);
});
❌ استفاده نادرست// 1. استفاده از Set برای داده‌های کم
const smallList = new Set([1, 2, 3]); // Array بهتر است

// 2. نیاز به Index
const set = new Set([1, 2, 3]);
// set[0] ❌ کار نمی‌کند

// 3. WeakSet با Primitive
const weak = new WeakSet();
weak.add(123); // ❌ TypeError

// 4. Iterate روی WeakSet
const weak = new WeakSet();
for (let item of weak) { } // ❌ TypeError
جمع‌بندیSet:برای مقادیر یکتاPerformance بالا O(1)قابل Iterateمصرف حافظه بیشتر از ArrayWeakSet:فقط ObjectGarbage Collection دوستانهغیرقابل Iterateبرای Metadata و Trackingچه زمانی استفاده کنیم؟حذف تکراری از آرایه:const unique = [...new Set(array)];انتخاب: Setجستجوی مکرر (بیش از ۱۰۰۰ بار):if (mySet.has(value)) { }انتخاب: Setترتیب مهم است + نیاز به Index:const item = arr[5];انتخاب: ArrayTrack کردن Objects بدون Memory Leak:const processed = new WeakSet();
processed.add(obj);انتخاب: WeakSetDOM Element tracking:const clicked = new WeakSet();
element.addEventListener(&#039;click&#039;, () =&gt; clicked.add(element));
انتخاب: WeakSetداده‌های کمتر از ۱۰۰۰ آیتم:const smallList = [1, 2, 3];انتخاب: Array (سادگی مهم‌تر از Performance)Rate Limiting:const requests = new Map(); // IP -&gt; Set of timestampsنکته نهایی: Set و WeakSet ابزارهای قدرتمندی هستند، اما باید در جای مناسب استفاده شوند. همیشه Performance و Memory را با هم در نظر بگیرید.</description>
                <category>Navid Barsalari</category>
                <author>Navid Barsalari</author>
                <pubDate>Sun, 10 May 2026 13:51:26 +0330</pubDate>
            </item>
                    <item>
                <title>الگوریتم در گولنگ (قسمت سوم): Valid Parentheses و فرار از تله‌های Tech Lead!</title>
                <link>https://virgool.io/@navidbarsalari/%D8%A7%D9%84%DA%AF%D9%88%D8%B1%DB%8C%D8%AA%D9%85-%D8%AF%D8%B1-%DA%AF%D9%88%D9%84%D9%86%DA%AF-%D9%82%D8%B3%D9%85%D8%AA-%D8%B3%D9%88%D9%85-valid-parentheses-%D9%88-%D9%81%D8%B1%D8%A7%D8%B1-%D8%A7%D8%B2-%D8%AA%D9%84%D9%87-%D9%87%D8%A7%DB%8C-tech-lead-rb4pmliyeedg</link>
                <description>در قسمت قبلی با هم الگوریتم Two Sum را شخم زدیم و دیدیم چطور یک HashMap ساده می‌تواند ما را از شر حلقه‌های تودرتو و نگاه‌های سنگین Tech Lead نجات دهد. قصد داریم در این مجموعه مقاله، مسیر حل الگوریتم‌ها را با همین فرمون جلو برویم.الگوریتم دومی که مورد بحث قرار می‌دهیم، یکی دیگر از غول‌های مرحله اول (Screening) مصاحبه‌های بین‌المللی است: Valid Parentheses (پرانتزهای معتبر).صورت سوالValid ParenthesesGiven a string s containing just the characters &#039;(&#039;, &#039;)&#039;, &#039;{&#039;, &#039;}&#039;, &#039;[&#039; and &#039;]&#039;, determine if the input string is valid.An input string is valid if:Open brackets must be closed by the same type of brackets.Open brackets must be closed in the correct order.Every close bracket has a corresponding open bracket of the same type.Input: s = “()”Output: trueInput: s = “()[]{}”Output: trueInput: s = “(]”Output: falseInput: s = “([)]”Output: falseConstraints:1≤ s.length ≤10 ^4s consists of parentheses only &#039;()[]{}&#039;.درک مسئله: ذهن انسان در برابر ماشینبیایید بی‌درنگ برویم سراغ مسئله. اگر به عنوان یک انسان به رشته &quot;{[()]}&quot; نگاه کنیم، مغز ما به طور خودکار تقارن را تشخیص می‌دهد. چشم ما از وسط شروع می‌کند، می‌بیند () کنار هم هستند، بعد [] آن‌ها را احاطه کرده و در نهایت {}. در کسری از ثانیه می‌گوییم “معتبر است!”.اما کامپیوتر کورِ بیچاره تقارن سرش نمی‌شود. او رشته را مثل یک تونل تاریک می‌بیند و فقط می‌تواند کاراکترها را یکی‌یکی از چپ به راست بخواند (پیمایش کند).پس ذهن برخی از برنامه‌نویسان تازه‌کار می‌رود سراغ یک ایده ظاهراً هوشمندانه: «خب، اگر رشته معتبر باشه، حتماً یک جایی توش () یا [] یا {} چسبیده به هم وجود داره. بیایم تو یه حلقه، هی این جفت‌ها رو پیدا کنیم و حذفشون کنیم. اگر در نهایت هیچی نموند، یعنی رشته معتبره!»اگر بخواهیم این منطق را تبدیل به کد گولنگ کنیم، به چیزی شبیه به این می‌رسیم:package main

import (
	&quot;fmt&quot;
	&quot;strings&quot;
)

// Bad Practice: String manipulation in a loop
func isValidBad(s string) bool {
	// Keep looping as long as we find a valid pair
	for strings.Contains(s, &quot;()&quot;) || strings.Contains(s, &quot;[]&quot;) || strings.Contains(s, &quot;{}&quot;) {
		s = strings.ReplaceAll(s, &quot;()&quot;, &quot;&quot;)
		s = strings.ReplaceAll(s, &quot;[]&quot;, &quot;&quot;)
		s = strings.ReplaceAll(s, &quot;{}&quot;, &quot;&quot;)
	}
	// If the string is completely empty, it was valid
	return len(s) == 0
}

func main() {
	fmt.Println(isValidBad(&quot;{[()]}&quot;)) // true
}
کد کار کرد! اما… (ورود مجدد Tech Lead 😠)شما با غرور کد را ران می‌کنید، خروجی درست است و فکر می‌کنید الگوریتم را هک کرده‌اید! اما وقتی Tech Lead کد را می‌بیند، دستش را روی پیشانی‌اش می‌گذارد و درخواست یک جلسه 1-on-1 می‌دهد.چرا این کد در یک مصاحبه واقعی درجا رد می‌شود؟۱. فاجعه پرفورمنس و تخصیص حافظه (Memory Allocation):در گولنگ (و خیلی از زبان‌های دیگر)، استرینگ‌ها Immutable (تغییرناپذیر) هستند. یعنی وقتی شما strings.ReplaceAll را صدا می‌زنید، گولنگ رشته قبلی را تغییر نمی‌دهد، بلکه یک رشته کاملاً جدید در حافظه می‌سازد! اگر طول رشته ما 10410^4104 باشد (طبق محدودیت‌ها)، شما دارید هزاران رشته جدید می‌سازید و Garbage Collector را به مرز جنون می‌رسانید.۲. زمان اجرای وحشتناک (Time Complexity):هر بار که strings.Contains یا ReplaceAll اجرا می‌شود، کل رشته از اول اسکن می‌شود. پیچیدگی زمانی این روش در بدترین حالت نزدیک به O(n2)O(n^2)O(n2) است.تغییر زاویه دید: ترفند طلاییتک‌لید به شما می‌گوید: «به جای اینکه هی رشته رو بسازی و خراب کنی، از ساختار داده‌ای استفاده کن که دقیقاً برای همین کار ساخته شده: Stack (پشته).»پشته یا LIFO (Last In, First Out) مثل یک دسته بشقاب است. آخرین بشقابی که روی هم می‌گذارید، اولین بشقابی است که برمی‌دارید. در مسئله ما: آخرین براکتی که باز شده، باید اولین براکتی باشد که بسته می‌شود.فرض کنید رشته ما &quot;{ [ } ]&quot; است:کاراکتر { را می‌بینیم. چون باز است، می‌گذاریمش توی پشته.کاراکتر [ را می‌بینیم. باز است، می‌رود روی پشته (الان بالاترین عنصر پشته [ است).کاراکتر } را می‌بینیم. این یک براکت بسته است! باید بررسی کنیم آیا با بالاترین عنصر پشته جفت می‌شود؟ بالاترین عنصر [ است. آیا } با [ جفت است؟ خیر! پس رشته نامعتبر است.با این روش، فقط یک بار روی رشته حرکت می‌کنیم (Single Pass).راه‌حل بهینه و حرفه‌ای (The Optimal Solution)در گولنگ ساختار داده اختصاصی به نام Stack نداریم، اما نیازی هم نداریم! اسلایس‌ها (Slices) در گولنگ به راحتی نقش یک پشته را بازی می‌کنند.کدمان را به شکلی می‌نویسیم که پیچیدگی زمانی آن O(n) و پیچیدگی فضایی آن حداکثر O(n) باشد.func isValid(s string) bool {
	// Base check: If the length is odd, it&#039;s impossible to be valid
	if len(s)%2 != 0 {
		return false
	}

	// Map to keep track of matching brackets
	// Key: Closing bracket, Value: Corresponding Opening bracket
	brackets := map[rune]rune{
		&#039;)&#039;: &#039;(&#039;,
		&#039;}&#039;: &#039;{&#039;,
		&#039;]&#039;: &#039;[&#039;,
	}

	// Our stack (using a slice of runes)
	var stack []rune

	for _, char := range s {
		// Is it a closing bracket? (Checking if it exists as a key in our map)
		if openBracket, isClose := brackets[char]; isClose {
			
			// 1. Stack must not be empty
			// 2. The top of the stack must match the required opening bracket
			if len(stack) &gt; 0 &amp;&amp; stack[len(stack)-1] == openBracket {
				// Pop: Remove the top element from the stack
				stack = stack[:len(stack)-1]
			} else {
				// Mismatch or empty stack with a closing bracket
				return false
			}
		} else {
			// It&#039;s an opening bracket, push it onto the stack
			stack = append(stack, char)
		}
	}

	// If the stack is empty at the end, all brackets were matched perfectly!
	return len(stack) == 0
}
کالبدشکافی خط به خط:if len(s)%2 != 0:این یک نکته طلایی (Early Return) است. اگر طول رشته فرد باشد (مثلاً ۳)، محال است تمام براکت‌ها جفت شوند. با همین یک خط (O(1)O(1)O(1))، کلی از ورودی‌های غلط را بدون محاسبات سنگین فیلتر می‌کنیم. مصاحبه‌گرها عاشق این بهینه‌سازی‌های کوچک هستند!brackets := map[rune]rune{ ... }:استفاده از مپ باعث می‌شود اگر فردا روزی مدیر محصول گفت «براکت‌های &lt; &gt; را هم ساپورت کنید»، کدهای ما پر از if/else های زشت نشود.var stack []rune:ما پشته خود را به صورت یک اسلایس از نوع rune (نماینده کاراکترهای یونیکد در گو) تعریف می‌کنیم.if openBracket, isClose := brackets[char]; isClose:یک تیر و دو نشان! هم چک می‌کنیم کاراکتر فعلی جزو براکت‌های بسته (کلیدهای مپ) هست یا نه، و هم براکتِ بازِ متناظرش را می‌گیریم.stack[len(stack)-1]:این دستور دقیقاً بالاترین (آخرین) عنصر پشته را می‌خواند.stack = stack[:len(stack)-1]:این عملیات Pop در گولنگ است! با استفاده از قابلیت Slicing، به گولنگ می‌گوییم: «یک اسلایس جدید بساز که شامل همه عناصر قبلی باشد، به جز آخری.»return len(stack) == 0:این هندل‌کننده یکی از خبیثانه‌ترین تله‌های مصاحبه است! فرض کنید ورودی (&quot;(((&quot;) باشد. هیچ خطایی در طول حلقه نمی‌خوریم، اما در نهایت پشته پر می‌ماند. اگر پشته خالی بود، یعنی همه چی به درستی جفت و حذف شده است.تست کیس‌ها (Edge Cases) و اثبات کددوباره سراغ Table-Driven Tests می‌رویم تا به Tech Lead ثابت کنیم کدمان ضدگلوله است.package main

import &quot;testing&quot;

func TestIsValid(t *testing.T) {
	tests := []struct {
		name     string
		input    string
		expected bool
	}{
		{&quot;Empty string&quot;, &quot;&quot;, true},
		{&quot;Simple valid&quot;, &quot;()&quot;, true},
		{&quot;Multiple valid&quot;, &quot;()[]{}&quot;, true},
		{&quot;Nested valid&quot;, &quot;{[()]}&quot;, true},
		{&quot;Simple invalid&quot;, &quot;(]&quot;, false},
		{&quot;Wrong order&quot;, &quot;([)]&quot;, false},
		{&quot;Odd length&quot;, &quot;()[&quot;, false}, // Killer Case 1
		{&quot;Start with close&quot;, &quot;][&quot;, false}, // Killer Case 2
		{&quot;Only open brackets&quot;, &quot;((((&quot;, false}, // Killer Case 3
		{&quot;Only close brackets&quot;, &quot;))))&quot;, false}, // Killer Case 4
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := isValid(tt.input)
			if result != tt.expected {
				t.Errorf(&quot;isValid(%q) = %v; want %v&quot;, tt.input, result, tt.expected)
			}
		})
	}
}
جمع‌بندی نکات این الگوریتم:قدرت پشته (Stack): هرجا در الگوریتم‌ها با مفاهیمی مثل “ترتیب معکوس”، “تقارن تو در تو” یا “Undo” مواجه شدید، اول از همه به Stack فکر کنید.فرار از String Manipulation: دستکاری رشته‌ها در حلقه‌های طولانی یک قاتل خاموش پرفورمنس است.مدیریت Edge Case ها: حالت‌هایی مثل شروع شدن با براکت بسته ]، یا پر ماندن پشته در انتها ((( دقیقاً همان جاهایی هستند که تفاوت یک جونیور و سنیور را در مصاحبه مشخص می‌کنند.</description>
                <category>Navid Barsalari</category>
                <author>Navid Barsalari</author>
                <pubDate>Sat, 09 May 2026 12:50:36 +0330</pubDate>
            </item>
                    <item>
                <title>ترجمه کتاب JavaScript: The Definitive Guide, 7th Edition</title>
                <link>https://virgool.io/@navidbarsalari/%D8%AA%D8%B1%D8%AC%D9%85%D9%87-%DA%A9%D8%AA%D8%A7%D8%A8-javascript-the-definitive-guide-7th-edition-pqgl1bm854vb</link>
                <description>۱.۱ کاوش در جاوااسکریپتوقتی در حال یادگیری یک زبان برنامهنویسی جدید هستید، مهم است که مثالهای موجود در کتاب را اجرا کنید، سپس آنها را تغییر دهید و دوباره امتحان کنید تا درکتان از زبان را بیازمایید. برای انجام این کار، به یک مفسر جاوااسکریپت نیاز دارید.سادهترین راه برای امتحان کردن چند خط کد جاوااسکریپت این است که ابزارهای توسعهدهندهٔ مرورگر وب خود را باز کنید (با فشردن یکی از کلیدهای F12، ترکیب Ctrl+Shift+I یا Command+Option+I)، سپس به زبانهٔ «Console» بروید. در آنجا میتوانید کد مورد نظر خود را در خط فرمان وارد کنید و نتیجه را همان لحظه مشاهده کنید.ابزارهای توسعهدهندهٔ مرورگر معمولاً به صورت یک پنل در پایین یا سمت راست پنجرهٔ مرورگر نمایش داده میشوند، اما اغلب میتوانید آنها را جدا کرده و در یک پنجرهٔ مستقل نمایش دهید (همانطور که در شکل ۱‑۱ نشان داده شده است)، که این حالت معمولاً کار را راحتتر میکند.شکل ۱‑۱راه دیگری برای امتحان کردن کدهای جاوااسکریپت این است که Node را از آدرسhttps://nodejs.org دانلود و نصب کنید.پس از نصب Node روی سیستم، کافی است یک پنجرهٔ ترمینال باز کنید و دستور node را وارد کنید تا یک نشست تعاملی (Interactive Session) جاوااسکریپت مانند نمونهٔ زیر آغاز شود:$ node
Welcome to Node.js v12.13.0.
Type &quot;.help&quot; for more information.
&gt; .help
.break Sometimes you get stuck, this gets you out
.clear Alias for .break
.editor Enter editor mode
.exit Exit the repl
.help Print this help message
.load Load JS from a file into the REPL session
.save Save all evaluated commands in this REPL session to a file
Press ^C to abort current expression, ^D to exit the repl
&gt; let x = 2, y = 3;
undefined
&gt; x + y
5
&gt; (x === 2) &amp;&amp; (y === 3)
true
&gt; (x &gt; 3) || ۱.۲ سلام دنیا (Hello World)وقتی آماده شدید که با بخشهای طولانیتری از کد کار کنید، محیطهای تعاملی خطبهخط دیگر چندان مناسب نخواهند بود و احتمالاً ترجیح میدهید کدهای خود را در یک ویرایشگر متن بنویسید. از آنجا میتوانید کد را کپی کرده و در کنسول جاوااسکریپت یا در یک نشست Node جایگذاری کنید.یا میتوانید کد خود را در یک فایل ذخیره کنید (پسوند رایج فایلهای جاوااسکریپت js. است) و سپس آن فایل را با استفاده از Node اجرا نمایید:$ node snippet.jsاگر از Node بهصورت غیرتعاملی (مانند روش قبلی) استفاده کنید، مقدار کدهایی را که اجرا میکنید بهطور خودکار چاپ نمیکند؛ بنابراین لازم است خودتان این کار را انجام دهید. میتوانید از تابع console.log() برای نمایش متن و سایر مقادیر جاوااسکریپت در پنجرهٔ ترمینال یا در کنسول ابزارهای توسعهدهندهٔ مرورگر استفاده کنید.برای مثال، اگر فایلی به نام hello.js بسازید و این خط کد را در آن قرار دهید:console.log(&quot;Hello World!&quot;);و اگر این فایل را با دستور node hello.js اجرا کنید، پیام «Hello World!» را خواهید دید که در خروجی چاپ شده است.اگر بخواهید همین پیام را در کنسول جاوااسکریپت یک مرورگر وب مشاهده کنید، یک فایل جدید به نام hello.html بسازید و متن زیر را در آن قرار دهید:
&lt;script &gt;
سپس فایل hello.html را با استفاده از یک نشانی file:// شبیه نمونهٔ زیر در مرورگر باز کنید:file:///Users/username/javascript/hello.html                        اکنون، پنجرهٔ ابزارهای توسعهدهندهٔ مرورگر را باز کنید تا این پیام خوشامدگویی را در کنسول مشاهده کنید.۱.۳ مرور اجمالی جاوااسکریپتاین بخش با استفاده از مثالهای کد، یک معرفی سریع از زبان جاوااسکریپت ارائه میدهد.پس از این فصل مقدماتی، وارد جزئیات سطح پایینِ جاوااسکریپت میشویم:فصل ۲ مواردی مانند توضیحات (comments)، نقطهویرگولها (semicolons) و مجموعهکاراکتر یونیکد را تشریح میکند.فصل ۳ جذابتر میشود: در آن متغیرهای جاوااسکریپت و مقادیری را که میتوانید به این متغیرها نسبت دهید توضیح میدهیم.در ادامه چند نمونهکد ارائه شده است تا نکات برجستهٔ این دو فصل را نشان دهد:// هر چیزی که بعد از دو اسلش بیاید، یک کامنت انگلیسی است.
// کامنتها را با دقت بخوانید؛ آنها کد جاوااسکریپت را توضیح میدهند.

// یک متغیر یک نام نمادین برای یک مقدار است.
// متغیرها با کلیدواژهٔ let اعلان میشوند:
let x; // اعلانِ متغیری به نام x.

// با علامت = میتوان به متغیرها مقدار نسبت داد.
x = 0; // اکنون متغیر x مقدار 0 دارد.

x // =&gt; 0: مقدار یک متغیر همان چیزی است که به آن نسبت داده شده.

// جاوااسکریپت از انواع مختلفی از مقادیر پشتیبانی میکند:
x = 1;            // عددها
x = 0.01;         // عددها میتوانند صحیح یا اعشاری باشند.
x = &quot;hello world&quot;; // رشتههای متنی داخل گیومه.
x = &#039;JavaScript&#039;;  // کوتیشنِ تکی هم رشته را مشخص میکند.
x = true;         // مقدار Boolean
x = false;        // مقدار Boolean دیگر
x = null;         // null مقداری ویژه است بهمعنای «هیچ مقدار».
x = undefined;    // undefined مقدار ویژهٔ دیگری است، مشابه null.
دو نوع بسیار مهم دیگر که برنامههای جاوااسکریپت میتوانند دستکاری کنند، آبجکتها و آرایهها هستند. این دو موضوع در فصلهای ۶ و ۷ به تفصیل بررسی میشوند، اما آنقدر مهماند که بسیار قبلتر از رسیدن به آن فصلها با آنها روبهرو خواهید شد:                                            // مهمترین نوع داده در جاوااسکریپت «آبجکت» است.
// آبجکت مجموعهای از زوجهای نام/مقدار است؛ یا یک نقشه از رشته به مقدار.
let book = {          // آبجکتها در آکولاد قرار میگیرند.
  topic: &quot;JavaScript&quot;, // ویژگی topic دارای مقدار &quot;JavaScript&quot; است.
  edition: 7           // ویژگی edition دارای مقدار 7 است.
};                     // این آکولاد پایان آبجکت را مشخص میکند.

// برای دسترسی به ویژگیهای آبجکت از . یا [] استفاده میکنیم:
book.topic        // =&gt; &quot;JavaScript&quot;
book[&quot;edition&quot;]   // =&gt; 7: روش دیگری برای دسترسی به ویژگیها.

// با انتساب مقدار میتوان ویژگیهای جدید ایجاد کرد:
book.author = &quot;Flanagan&quot;;
book.contents = {}; // {} یک آبجکت خالی بدون ویژگی.

// دسترسی شرطی به ویژگیها با ?. (ویژگی ES2020):
book.contents?.ch01?.sect1
// =&gt; undefined: چون book.contents ویژگی ch01 ندارد.
                        جاوااسکریپت همچنین از آرایهها (لیستهایی با اندیس عددی) پشتیبانی میکند:                                            let primes = [2, 3, 5, 7]; // آرایهای با ۴ مقدار، داخل [ و ].
primes[0]         // =&gt; 2: اولین عضو آرایه (اندیس ۰)
primes.length     // =&gt; 4: تعداد اعضای آرایه
primes[primes.length-1] // =&gt; 7: آخرین عضو آرایه

primes[4] = 9;    // افزودن یک عضو جدید با انتساب مقدار
primes[4] = 11;   // تغییر مقدار یک عضو موجود

let empty = [];   // [] آرایهٔ خالی بدون عضو
empty.length      // =&gt; 0
                        آرایهها و آبجکتها میتوانند شامل آرایهها و آبجکتهای دیگر نیز باشند:let points = [          // آرایهای با دو عضو
  {x: 0, y: 0},         // هر عضو یک آبجکت است
  {x: 1, y: 1}
];


let data = {            // آبجکتی با دو ویژگی
  trial1: [[1,2], [3,4]], // مقدار هر ویژگی یک آرایه است
  trial2: [[2,3], [4,5]]  // اعضای این آرایهها نیز آرایهاند
};                        نوشتار کامنتها در مثالهای کدشاید در مثالهای قبلی متوجه شده باشید که بعضی از کامنتها با یک علامت فلش (=&gt;) شروع میشوند. این علامت نشاندهندهٔ مقدار خروجیِ کدی است که در خط قبل اجرا میشود و تلاش من برای شبیهسازی محیط تعاملی جاوااسکریپت — مانند کنسول مرورگر — در یک کتاب چاپی است.این کامنتهای // =&gt; همچنین نقش یک «ادعا» (assertion) را دارند، و من ابزاری نوشتهام که کدها را اجرا میکند و بررسی میکند که آیا واقعاً مقدار ذکرشده در کامنت تولید میشود یا نه. امیدوارم این روش به کاهش خطاهای موجود در کتاب کمک کرده باشد.دو سبک مرتبط دیگر از کامنت/ادعا نیز وجود دارد.اگر کامنتی به شکل // a == 42 دیدید، یعنی پس از اجرای کدی که قبل از این کامنت آمده، مقدار متغیر a برابر ۴۲ خواهد بود.اگر کامنتی به شکل // ! دیدید، یعنی کدی که در خط قبل قرار دارد یک استثنا (exception) ایجاد میکند (و توضیح مربوط به نوع استثنا معمولاً بعد از علامت تعجب نوشته میشود).در سراسر کتاب با این نوع کامنتها روبهرو خواهید شد.نحوۀ نشاندادهشده برای فهرستکردن عناصر یک آرایه درون کروشهها، یا نگاشت نام ویژگیهای یک آبجکت به مقدارهایشان درون آکولادها، یک «عبارت مقداردهی اولیه» (initializer expression) نامیده میشود و تنها یکی از موضوعات فصل ۴ است.عبارت (expression) عبارتی در جاوااسکریپت است که میتواند ارزیابی شود و یک مقدار تولید کند. برای مثال، استفاده از . یا [] برای ارجاع به مقدار یک ویژگی آبجکت یا یکی از عناصر آرایه، یک عبارت محسوب میشود.یکی از رایجترین روشها برای ساختن عبارت در جاوااسکریپت، استفاده از عملگرها است:// عملگرها روی مقدارها (عملوندها) عمل میکنند و یک مقدار جدید میسازند.
// عملگرهای حسابی از سادهترینها هستند:
3 + 2      // =&gt; 5: جمع
3 - 2      // =&gt; 1: تفریق
3 * 2      // =&gt; 6: ضرب
3 / 2      // =&gt; 1.5: تقسیم
points[1].x - points[0].x // =&gt; 1: عملوندهای پیچیدهتر نیز قابل استفادهاند
&quot;3&quot; + &quot;2&quot;  // =&gt; &quot;32&quot;: + برای عددها جمع و برای رشتهها اتصال انجام میدهد
جاوااسکریپت تعدادی عملگر میانبُر (shorthand) برای عملیات حسابی تعریف کرده است:let count = 0; // تعریف یک متغیر
count++;      // افزایش یکواحدی
count--;      // کاهش یکواحدی
count += 2;   // افزودن ۲؛ معادل count = count + 2
count *= 3;   // ضرب در ۳؛ معادل count = count * 3
count         // =&gt; 6: نام متغیرها نیز خود یک عبارت هستندعملگرهای تساوی و رابطهای بررسی میکنند که آیا دو مقدار با هم برابر، نابرابر، کمتر، بیشتر و … هستند. این نوع عبارتها همیشه به یک مقدار بولین (true یا false) ارزیابی میشوند:let x = 2, y = 3; // علامت = در اینجا انتساب است، نه مقایسهی تساوی
x === y   // =&gt; false: تساوی
x !== y   // =&gt; true: نابرابری
x &lt; y     // =&gt; true: کوچکتر بودن
x &lt;= y    // =&gt; true: کوچکتر یا مساوی
x &gt; y     // =&gt; false: بزرگتر بودن
x &gt;= y    // =&gt; false: بزرگتر یا مساوی

&quot;two&quot; === &quot;three&quot; // =&gt; false: رشتهها متفاوتاند
&quot;two&quot; &gt; &quot;three&quot;   // =&gt; true: &quot;tw&quot; از نظر حروف الفبا بعد از &quot;th&quot; قرار میگیرد
false === (x &gt; y) // =&gt; true: مقدار false برابر با false است
                        عملگرهای منطقی مقادیر بولین را با هم ترکیب یا آنها را وارونه میکنند:(x === 2) &amp;&amp; (y === 3) // =&gt; true: هر دو مقایسه درست هستند. &amp;&amp; یعنی AND
(x &gt; 3) || (y &lt; 3)     // =&gt; false: هیچیک درست نیست. || یعنی OR
!(x === y)             // =&gt; true: علامت ! مقدار بولین را معکوس میکند                        اگر عبارات جاوااسکریپت را مانند «عبارت‌های زبانی» در نظر بگیریم، آن‌گاه دستورات (statements) در جاوااسکریپت شبیه «جمله‌های کامل» هستند. موضوع فصل ۵ همین دستورات است.مترجم:  یک Statement سادهlet x = 10;                        به‌طور خلاصه، یک عبارت(expression) چیزی است که یک مقدار تولید می‌کند اما کاری انجام نمی‌دهد؛ یعنی هیچ تغییری در وضعیت برنامه ایجاد نمی‌کند.مترجم: یک Expression ساده3 + 4در مقابل، دستورها ارزش بازگشتی ندارند، اما وضعیت برنامه را تغییر می‌دهند.شما تاکنون تعریف متغیرها و دستورهای انتساب را در مثال‌های قبلی دیده‌اید.دستهٔ گستردهٔ دیگرِ دستورات، ساختارهای کنترلی مانند دستورات شرطی و حلقه‌ها هستند.پس از توضیح تابع‌ها در ادامه، نمونه‌هایی از این ساختارها را خواهید دید.تابع یک بلوک نام‌دار و پارامترگذاری‌شده از کد جاوااسکریپت است که یک‌بار آن را تعریف می‌کنید و سپس می‌توانید بارها و بارها آن را فراخوانی کنید.تابع‌ها تا فصل ۸ به‌صورت رسمی شرح داده نمی‌شوند، اما درست مانند آبجکت‌ها و آرایه‌ها، قبل از رسیدن به آن فصل نیز بارها با آن‌ها روبه‌رو خواهید شد.در ادامه چند مثال ساده آورده شده است:// تابع‌ها بلوک‌های پارامترگذاری‌شده‌ای از کد جاوااسکریپت هستند که می‌توان آن‌ها را فراخوانی کرد.
function plus1(x) {        // تعریف تابعی به نام &quot;plus1&quot; با پارامتر x
  return x + 1;            // برگرداندن مقداری که یک واحد بیشتر از ورودی است
}                          // بدنهٔ تابع داخل آکولاد قرار می‌گیرد

plus1(y)                   // =&gt; 4: مقدار y برابر با 3 است، پس این فراخوانی مقدار 1+3 را برمی‌گرداند

let square = function(x) { // تابع‌ها مقدار هستند و می‌توان آن‌ها را به متغیر نسبت داد
  return x * x;            // محاسبهٔ مقدار تابع
};                         // نقطه‌ویرگول پایان دستور انتساب را مشخص می‌کند

square(plus1(y))           // =&gt; 16: در این عبارت هر دو تابع فراخوانی می‌شونددر ES6 و نسخه‌های بعد از آن، یک نحوِ کوتاه‌تر برای تعریف تابع‌ها معرفی شد. این نحوِ مختصر از =&gt; برای جدا کردن فهرست آرگومان‌ها از بدنهٔ تابع استفاده می‌کند و به همین دلیل تابع‌هایی که با این شیوه نوشته می‌شوند را توابع پیکانی (arrow functions) می‌نامند.arrow functions معمولاً زمانی به کار می‌روند که بخواهید یک تابعِ بی‌نام را به‌عنوان آرگومان به تابع دیگری ارسال کنید.کد قبلی را اگر با توابع پیکانی بازنویسی کنیم، به این شکل درمی‌آید:const plus1 = x =&gt; x + 1;   // ورودی x به خروجی x + 1 نگاشت می‌شود
const square = x =&gt; x * x;  // ورودی x به خروجی x * x نگاشت می‌شود

plus1(y)       // =&gt; 4: روش فراخوانی تابع تغییری نمی‌کند
square(plus1(y)) // =&gt; 16وقتی از تابع‌ها همراه با آبجکت‌ها استفاده می‌کنیم، به آن‌ها متد گفته می‌شود:// وقتی تابع‌ها به‌عنوان یک ویژگی در آبجکت قرار می‌گیرند، آن‌ها را «متد» می‌نامیم.
// همهٔ آبجکت‌های جاوااسکریپت (از جمله آرایه‌ها) متد دارند.
let points = [
  { x: 1, y: 1 },
  { x: 2, y: 2 }
];
let a = [];        // ایجاد یک آرایهٔ خالی
a.push(1, 2, 3);   //   push() عناصر را به آرایه اضافه می‌کند توسط متد 
a.reverse();       // متدی دیگر: وارونه کردن ترتیب عناصر

// ما می‌توانیم متدهای خودمان را هم تعریف کنیم.
// به ابجکتی اشاره دارد که متد روی آن تعریف شده است this کلید واژه به
//   که قبلا با آن کار کردیم points در این مثال، همان آرایهٔ 
points.dist = function() {   // تعریف متدی برای محاسبهٔ فاصلهٔ بین دو نقطه
  let p1 = this[0];          // اولین عنصر آرایه
  let p2 = this[1];          // دومین عنصر آرایه
  let a = p2.x - p1.x;       // x اختلاف مختصات 
  let b = p2.y - p1.y;       // y اختلاف مختصات 

  return Math.sqrt(a*a +     // قضیهٔ فیثاغورس
    b*b);                    // ریشهٔ دوم را محاسبه می‌کند Math.sqrt() تابع 
};

points.dist()   // =&gt; Math.sqrt(2): فاصلهٔ بین دو نقطهٔ تعریف‌شده
                        و حالا، همان‌طور که وعده داده شد، چند تابع که در بدنهٔ آن‌ها نمونه‌هایی از ساختارهای کنترلی رایج جاوااسکریپت به کار رفته است: //  C, C++, Java دستورات جاوااسکریپت شامل شرط‌ها و حلقه‌ها هستند و نحوی مشابه 
// و زبان‌های دیگر دارند.
function abs(x) {         // تابعی برای محاسبه مقدار قدرمطلق
  if (x &gt;= 0) {           // دستور if ...
    return x;             // اگر شرط درست باشد، این بخش اجرا می‌شود.
  }                       // پایان بخش if
  else {                  // بخش اختیاری else اگر شرط نادرست باشد اجرا می‌شود
    return -x;
  }                       //  آکولادها زمانی که فقط یک دستور در هر بخش باشد، 
 // اختیاری‌اند.
}                         // توجه کنید که return داخل if/else آمده است.

abs(-10) === abs(10)      // =&gt; true
                        function sum(array) {     // تابعی برای محاسبهٔ مجموع عناصر یک آرایه
  let s = 0;              // مقدار اولیهٔ مجموع
  for (let x of array) {  // حلقهٔ for/of: هر عنصر از آرایه در x قرار می‌گیرد
    s += x;               // افزودن مقدار عنصر به مجموع
  }                       // پایان حلقه
  return s;               // بازگرداندن مقدار مجموع
}

sum(primes)               // =&gt; 28: مجموع ۵ عدد اول اولیّه 2+3+5+7+11function factorial(n) {   // تابعی برای محاسبهٔ فاکتوریل
  let product = 1;        // مقدار اولیهٔ ضرب
  while (n &gt; 1) {         // تا وقتی مقدار n بیشتر از ۱ باشد ادامه بده
    product *= n;         // معادل product = product * n
    n--;                  // معادل n = n - 1
  }                       // پایان حلقه
  return product;         // بازگرداندن حاصل
}
factorial(4)

function factorial2(n) {     // نسخهٔ دیگر تابع فاکتوریل با یک حلقه متفاوت
  let i, product = 1;        // مقدار اولیه 1
  for (i = 2; i &lt;= n; i++)   // i به‌طور خودکار از 2 تا عدد n افزایش می‌یابد
    product *= i;            // این کار در هر تکرار انجام می‌شود. برای حلقه‌های تک‌خطی {} لازم نیست
  return product;            // بازگرداندن مقدار فاکتوریل
}

factorial2(5)                // =&gt; 120: حاصل ضرب 1×2×3×4×5جاوااسکریپت از یک سبک برنامه‌نویسی شی‌ءگرا پشتیبانی می‌کند، اما این سبک تفاوت‌های مهمی با زبان‌های شی‌ءگرای «کلاسیک» مانند Java یا C++ دارد.فصل 9 کتاب برنامه‌نویسی شی‌ءگرا در جاوااسکریپت را با مثال‌های فراوان به‌طور کامل بررسی می‌کند.در اینجا یک مثال بسیار ساده آمده که نشان می‌دهد چگونه می‌توان یک کلاس برای نمایش نقاط هندسی دوبعدی ساخت. آبجکت‌هایی که نمونهٔ این کلاس هستند یک متد به نام distance() دارند که فاصلهٔ نقطه تا مرکز مختصات را محاسبه می‌کند: class Point {                // طبق قرارداد نام کلاس با حرف بزرگ آغاز می‌شود.
  constructor(x, y) {        // تابع سازنده، نمونه‌های جدید را مقداردِهی اولیه 
    this.x = x;              // this به شیء جدیدی اشاره دارد که در حال ساخته‌شدن است.
    this.y = y;              // ذخیرهٔ آرگومان‌ها به‌عنوان ویژگی‌های شیء.
  }                          // در تابع سازنده نیاز به return نیست.

  distance() {                // متدی برای محاسبهٔ فاصلهٔ نقطه تا مبدأ
    return Math.sqrt(         // بازگرداندن ریشهٔ دومِ مجموع مربع‌ها
      this.x * this.x +       // this اشاره به همان شیء Point دارد که متد روی آن فراخوانی شده
      this.y * this.y
    );
  }
}برای ساختن نمونهٔ جدید از کلاس، باید از کلیدواژهٔ new استفاده کنید:let p = new Point(1, 1);     // یک نقطهٔ هندسی در مختصات (1,1)
p.distance()                 // =&gt; Math.SQRT2این تور مقدماتی دربارهٔ نحو و امکانات پایه‌ای جاوااسکریپت در اینجا پایان می‌یابد.اما کتاب با فصل‌های مستقل و جامع ادامه پیدا می‌کند که هرکدام یک بخش مهم از زبان را پوشش می‌دهند:فصل 10: ماژول‌هانشان می‌دهد چگونه فایل‌های جاوااسکریپت می‌توانند از تابع‌ها و کلاس‌های تعریف‌شده در فایل‌های دیگر استفاده کنند.فصل 11: کتابخانه استاندارد جاوااسکریپت (JavaScript Standard Library)معرفی توابع و کلاس‌های داخلی زبان که برای تمام برنامه‌ها قابل استفاده‌اند، شامل:ساختارهای داده مهم مانند Map و Setکلاس regular expression برای الگوهای متنیتابع‌هایی برای سریالی‌سازی داده‌های جاوااسکریپتو موارد بسیار دیگرفصل 12: Iteratorها و Generatorهاتوضیح نحوهٔ کار حلقه‌ی for/ofو اینکه چگونه می‌توان کلاس‌های دلخواه را با این حلقه سازگار کرد.همچنین تابع‌های generator و دستور yield آموزش داده می‌شود.فصل 13: جاوااسکریپت ناهمزمانبررسی عمیق برنامه‌نویسی async در جاوااسکریپت شامل:callbackهارویدادها (events)Promiseهاکلیدواژه‌های async و awaitگرچه خود زبان ذاتاً ناهمزمان نیست، اما APIهای مرورگر و Node تقریباً همگی ناهمزمان هستند.فصل 14: متاپروگرامینگ (Metaprogramming)معرفی قابلیت‌های پیشرفتهٔ زبان مانند:ProxyReflectionDynamic property accessاین قابلیت‌ها برای توسعه‌دهندگان کتابخانه‌ها بسیار مفید است.فصل 15: جاوااسکریپت در مرورگرآشنایی با محیط اجرایی مرورگر، نحوهٔ اجرای اسکریپت‌ها، و مهم‌ترین APIهای تحت وب.این فصل مفصل‌ترین بخش کتاب است.فصل 16: جاوااسکریپت سمت سرور با Node.jsمعرفی محیط اجرایی Node، مدل برنامه‌نویسی رویدادمحور، ساختار داده‌ها و APIهای کلیدی.فصل 17: ابزارها و افزونه‌های جاوااسکریپتمعرفی ابزارهای مهم توسعه، زبان‌های افزوده (مانند TypeScript)، بسته‌بندی‌کننده‌ها، و امکانات دیگر.۱.۴ مثال: هیستوگرام فراوانی کاراکترهامثال 1‑1 یک برنامهٔ Node است که متن را از ورودی استاندارد (stdin) می‌خواند، یک هیستوگرام فراوانی کاراکترها از آن تولید می‌کند، و سپس هیستوگرام را چاپ می‌کند.$ node charfreq.js &lt; charfreq.jsنمونهٔ خروجی برنامه به این صورت است:T: ########### 11.22%
E: ########## 10.15%
R: ####### 6.68%
S: ###### 6.44%
A: ###### 6.16%
N: ###### 5.81%
O: ##### 5.45%
I: ##### 4.54%
H: #### 4.07%
C: ### 3.36%
L: ### 3.20%
U: ### 3.08%
/: ### 2.88%در این خروجی:حرفی مانند T در سمت چپ چاپ می‌شودسپس تعدادی # متناسب با میزان فراوانی آنو در نهایت درصد فراوانی آن کاراکتر در کل متناین مثال از مجموعه‌ای از قابلیت‌های پیشرفتهٔ جاوااسکریپت استفاده می‌کند و قرار است نشان بدهد برنامه‌های واقعی جاوااسکریپت چه شکلی هستند.در این مرحله نباید انتظار داشته باشید که تمام کد را کاملاً درک کنید؛ اما مطمئن باشید تمام این مفاهیم در فصل‌های بعدی توضیح داده خواهند شد.مثال 1‑1. محاسبهٔ هیستوگرام فراوانی کاراکتر با جاوااسکریپت/**
 * This Node program reads text from standard input, computes the frequency
 * of each letter in that text, and displays a histogram of the most
 * frequently used characters. It requires Node 12 or higher to run.
 * In a Unix-type environment you can invoke the program like this:
 *   node charfreq.js &lt; corpus.txt
 */این کلاس Map را گسترش می‌دهد تا متد get() در صورت نبودن کلید، مقدار پیش‌فرض را برگرداند                                            class DefaultMap extends Map {
  constructor(defaultValue) {
    super();                      // فراخوانی سازندهٔ کلاس پدر
    this.defaultValue = defaultValue; // ذخیرهٔ مقدار پیش‌فرض
  }

  get(key) {
    if (this.has(key)) {          // اگر کلید موجود بود
      return super.get(key);      // مقدار واقعی را از Map پدر بگیر
    }
    else {
      return this.defaultValue;   // وگرنه مقدار پیش‌فرض را برگردان
    }
  }
}                        این تابع async از ورودی استاندارد متن را می‌خواند و هیستوگرام نهایی را می‌سازد                                            async function histogramFromStdin() {
  process.stdin.setEncoding(&quot;utf-8&quot;); // دریافت رشتهٔ یونیکد به‌جای بایت خام

  let histogram = new Histogram();

  for await (let chunk of process.stdin) { // خواندن تکه‌تکهٔ ورودی
    histogram.add(chunk);
  }

  return histogram;
}
                              /**
 * This Node program reads text from standard input, computes the frequency
 * of each letter in that text, and displays a histogram of the most
 * frequently used characters. It requires Node 12 or higher to run.
 * In a Unix-type environment you can invoke the program like this:
 *   node charfreq.js &lt; corpus.txt
 */

// This class extends Map so that the get() method returns the specified
// value instead of null when the key is not in the map
class DefaultMap extends Map {
  constructor(defaultValue) {
    super();                       // Invoke superclass constructor
    this.defaultValue = defaultValue; // Remember the default value
  }

  get(key) {
    if (this.has(key)) {           // If the key is already in the map
      return super.get(key);       // return its value from superclass.
    }
    else {
      return this.defaultValue;    // Otherwise return the default value
    }
  }
}

// This class computes and displays letter frequency histograms
class Histogram {
  constructor() {
    this.letterCounts = new DefaultMap(0); // Map from letters to counts
    this.totalLetters = 0;                 // How many letters in all
  }

  // This function updates the histogram with the letters of text.
  add(text) {
    // Remove whitespace from the text, and convert to upper case
    text = text.replace(/\s/g, &quot;&quot;).toUpperCase();

    // Now loop through the characters of the text
    for (let character of text) {
      let count = this.letterCounts.get(character); // Get old count
      this.letterCounts.set(character, count + 1);  // Increment it
      this.totalLetters++;
    }
  }

  // Convert the histogram to a string that displays an ASCII graphic
  toString() {
    // Convert the Map to an array of [key,value] arrays
    let entries = [...this.letterCounts];

    // Sort the array by count, then alphabetically
    entries.sort((a, b) =&gt; {          // A function to define sort order.
      if (a[1] === b[1]) {            // If the counts are the same
        return a[0] &lt; b[0] ? -1 : 1;  // sort alphabetically.
      } else {                        // If the counts differ
        return b[1] - a[1];           // sort by largest count.
      }
    });

    // Convert the counts to percentages
    for (let entry of entries) {
      entry[1] = entry[1] / this.totalLetters * 100;
    }

    // Drop any entries less than 1%
    entries = entries.filter(entry =&gt; entry[1] &gt;= 1);

    // Now convert each entry to a line of text
    let lines = entries.map(
      ([l, n]) =&gt;
        `${l}: ${&quot;#&quot;.repeat(Math.round(n))} ${n.toFixed(2)}%`
    );

    // And return the concatenated lines, separated by newline characters.
    return lines.join(&quot;\n&quot;);
  }
}

// This async (Promise-returning) function creates a Histogram object,
// asynchronously reads chunks of text from standard input, and adds those chunks to
// the histogram. When it reaches the end of the stream, it returns this histogram
async function histogramFromStdin() {
  process.stdin.setEncoding(&quot;utf-8&quot;);  // Read Unicode strings, not bytes
  let histogram = new Histogram();

  for await (let chunk of process.stdin) {
    histogram.add(chunk);
  }

  return histogram;
}

// This one final line of code is the main body of the program.
// It makes a Histogram object from standard input, then prints the histogram.
histogramFromStdin().then(histogram =&gt; {
  console.log(histogram.toString());
});
             ۱.۵ خلاصهاین کتاب جاوااسکریپت را از پایین‌ترین سطح تا بالاترین سطح آموزش می‌دهد.یعنی ابتدا از جزئیات سطح پایین مانند:کامنت‌هاشناسه‌هامتغیرهاانواع دادهشروع می‌کند، سپس به:عباراتدستوراتاشیاءتوابعمی‌رسد، و در نهایت سراغ مفاهیم سطح بالا مانند:کلاس‌هاماژول‌هامی‌رود.من کلمهٔ “Definitive” در عنوان این کتاب را بسیار جدی گرفته‌ام، و فصل‌های پیش رو زبان جاوااسکریپت را با سطحی از دقت توضیح می‌دهند که ممکن است در ابتدا کمی سنگین به نظر برسد.اما تسلط واقعی بر جاوااسکریپت نیازمند درک همین جزئیات است، و امیدوارم زمانی بگذارید و کتاب را کامل و از ابتدا تا انتها بخوانید.بااین‌حال، لازم نیست در اولین خواندن این کار را انجام دهید.اگر در بخشی احساس کردید گیر کرده‌اید، کافی است به فصل بعد بروید.</description>
                <category>Navid Barsalari</category>
                <author>Navid Barsalari</author>
                <pubDate>Sun, 03 May 2026 17:32:07 +0330</pubDate>
            </item>
                    <item>
                <title>الگوریتم‌ها در Golang (قسمت دوم): Reverse String/Array و یه دردسر دیگه با Tech Lead!</title>
                <link>https://virgool.io/@navidbarsalari/%D8%A7%D9%84%DA%AF%D9%88%D8%B1%DB%8C%D8%AA%D9%85-%D9%87%D8%A7-%D8%AF%D8%B1-golang-%D9%82%D8%B3%D9%85%D8%AA-%D8%AF%D9%88%D9%85-reverse-stringarray-%D9%88-%DB%8C%D9%87-%D8%AF%D8%B1%D8%AF%D8%B3%D8%B1-%D8%AF%DB%8C%DA%AF%D9%87-%D8%A8%D8%A7-tech-lead-cvw2jf58deoc</link>
                <description>خب، بعد از اون ماجرای Two Sum که Tech Lead ما رو تو مصاحبه داغون کرد، امروز می‌خوایم یه الگوریتم دیگه رو حل کنیم که خیلی ساده به نظر می‌رسه، اما جزئیات شیطانی داره که می‌تونه تو مصاحبه گیرت بیاره!🎯 هدف این سری مقالاتقراره از ساده شروع کنیم و به تدریج به سمت الگوریتم‌های پیچیده‌تر بریم. همه چیز رو با Golang پیاده‌سازی می‌کنیم و سعی می‌کنیم مثل یه مصاحبه واقعی پیش بریم.📝 الگوریتم امروز: Reverse String/Arrayصورت مسئلهیه آرایه یا استرینگ بهت میدن، باید برعکسش کنی. اگه میشه In-place (بدون استفاده از حافظه اضافی) انجامش بده.مثال‌ها:Input: “hello”Output: “olleh”Input: [1, 2, 3, 4, 5]Output: [5, 4, 3, 2, 1]Input: “a”Output: “a”Input: []Output: []محدودیت‌ها:طول آرایه/استرینگ: 0 &lt;= length &lt;= 10^5باید کارآمد باشه (نه O(n2)O(n^2)O(n2)!)باید Unicode رو درست هندل کنه (مثل فارسی و ایموجی)🤔 انسان vs ماشین: چطور این مسئله رو حل می‌کنیم؟وقتی ما انسان‌ها می‌خوایم یه کلمه رو برعکس کنیم، خیلی راحت از آخر شروع می‌کنیم و به اول می‌رسیم. مثلاً &quot;سلام&quot; رو می‌بینیم، بلافاصله می‌گیم &quot;مالس&quot;.اما کامپیوتر نمی‌تونه همینطوری یه‌دفعه ببینه! باید یه الگوریتم سیستماتیک بهش بدیم.❌ راه‌حل ساده (اما نادرست برای مصاحبه!)اولین چیزی که به ذهن می‌رسه اینه که از توابع آماده استفاده کنیم:package main

import &quot;fmt&quot;

func reverseStringNaive(s string) string {
    // تو زبان‌هایی مثل Python می‌تونی بنویسی: s[::-1]
    // اما تو Go چی؟ تابع آماده نداریم!
    // پس باید خودمون بنویسیم
    return s // ❌ این کار نمی‌کنه!
}

func main() {
    fmt.Println(reverseStringNaive(&quot;hello&quot;))
}مشکل: تو Golang تابع built-in برای Reverse وجود نداره! پس باید خودمون پیاده‌سازی کنیم.💥 اولین تلاش: راه‌حل Brute Forceخب، بیا یه راه ساده امتحان کنیم. یه آرایه جدید بسازیم و از آخر به اول کپی کنیم:package main

import &quot;fmt&quot;

// ❌ این راه‌حل چند مشکل داره!
func reverseArrayNaive(nums []int) []int {
    n := len(nums)
    result := make([]int, n) // حافظه اضافی O(n)
    
    for i := 0; i &lt; n; i++ {
        result[i] = nums[n-1-i]
    }
    
    return result
}

func main() {
    arr := []int{1, 2, 3, 4, 5}
    reversed := reverseArrayNaive(arr)
    fmt.Println(&quot;Original:&quot;, arr)
    fmt.Println(&quot;Reversed:&quot;, reversed)
}
خروجی:Original: [1 2 3 4 5]Reversed: [5 4 3 2 1]خب، کار می‌کنه! اما…😤 Tech Lead وارد می‌شه: “این چیه نوشتی؟!”Tech Lead کدت رو نگاه می‌کنه و می‌گه:❌ مشکل ۱: استفاده از حافظه اضافی بی‌دلیل“چرا یه آرایه جدید ساختی؟ می‌تونستی همون آرایه اصلی رو In-place تغییر بدی! این O(n)O(n)O(n) فضای اضافی لازم نیست.”❌ مشکل ۲: برای String چی؟“خب این برای آرایه کار می‌کنه، اما String چی؟ String ها تو Go Immutable هستن! نمی‌تونی مستقیم تغییرشون بدی.”❌ مشکل ۳: Unicode رو فراموش کردی!بیا یه تست بزنیم:func reverseStringWrong(s string) string {
    bytes := []byte(s) // ❌ اشتباه!
    left, right := 0, len(bytes)-1
    
    for left &lt; right {
        bytes[left], bytes[right] = bytes[right], bytes[left]
        left++
        right--
    }
    
    return string(bytes)
}

func main() {
    fmt.Println(reverseStringWrong(&quot;hello&quot;))  // ✅ کار می‌کنه
    fmt.Println(reverseStringWrong(&quot;سلام&quot;))   // ❌ خراب می‌شه!
    fmt.Println(reverseStringWrong(&quot;Hello👋&quot;)) // ❌ ایموجی خراب می‌شه!
}
روجی:ollehÙ�اÙ�س // ❌ خراب شده!�👋olleH // ❌ ایموجی خراب شده!چرا؟چون []byte هر بایت UTF-8 رو جداگانه می‌بینه. کاراکترهای فارسی و ایموجی چند بایتی هستن، پس وقتی بایت‌ها رو جابه‌جا می‌کنی، کاراکتر خراب می‌شه!✅ راه‌حل بهینه: Two Pointer Techniqueحالا بیا درست حل کنیم!برای Array (In-place):package main

import &quot;fmt&quot;

// reverseArray reverses an integer slice in-place
// Time Complexity: O(n)
// Space Complexity: O(1)
func reverseArray(nums []int) {
    if len(nums) &lt; 2 {
        return // Edge case: empty or single element
    }
    
    left, right := 0, len(nums)-1
    
    for left &lt; right {
        // Swap elements
        nums[left], nums[right] = nums[right], nums[left]
        left++
        right--
    }
}

func main() {
    arr := []int{1, 2, 3, 4, 5}
    fmt.Println(&quot;Before:&quot;, arr)
    reverseArray(arr)
    fmt.Println(&quot;After:&quot;, arr)
}
خروجی:Before: [1 2 3 4 5]After: [5 4 3 2 1]توضیح:دو pointer داریم: left از اول و right از آخرتا وقتی left &lt; right، عناصر رو Swap می‌کنیمهر بار left یکی جلو و right یکی عقب می‌رهوقتی به هم رسیدن، تموم شده!پیچیدگی:زمانی: O(n) - فقط یک بار روی نصف آرایه حرکت می‌کنیمفضایی: O(1) - هیچ حافظه اضافی نمی‌خوادبرای String (با دقت Unicode):package main

import &quot;fmt&quot;

// reverseString reverses a string correctly handling Unicode
// Time Complexity: O(n)
// Space Complexity: O(n) - due to rune slice conversion
func reverseString(s string) string {
    if len(s) &lt; 2 {
        return s // Edge case
    }
    
    // ⚠️ Convert to []rune, NOT []byte!
    runes := []rune(s)
    left, right := 0, len(runes)-1
    
    for left &lt; right {
        runes[left], runes[right] = runes[right], runes[left]
        left++
        right--
    }
    
    return string(runes)
}

func main() {
    // Test with different character sets
    fmt.Println(reverseString(&quot;hello&quot;))      // ASCII
    fmt.Println(reverseString(&quot;سلام&quot;))       // Persian
    fmt.Println(reverseString(&quot;Hello👋&quot;))    // Emoji
    fmt.Println(reverseString(&quot;مرحبا&quot;))      // Arabic
}
خروجی:ollehمالس👋olleHابحرمچرا []rune و نه []byte?[]byte: هر بایت UTF-8 رو جداگانه می‌بینه → کاراکترهای چند بایتی خراب می‌شن[]rune: هر کاراکتر Unicode رو یک واحد می‌بینه → درست کار می‌کنهمثال:“سلام” in UTF-8:As []byte: [216 179 217 132 216 167 217 133] (8 bytes)As []rune: [1587 1604 1575 1605] (4 runes)اگه []byte استفاده کنی، بایت‌ها رو جابه‌جا می‌کنی و کاراکتر خراب می‌شه!🧪 تست کردن با Table-Driven Testsحالا باید کدمون رو تست کنیم. تو Go از Table-Driven Tests استفاده می‌کنیم:package main

import (
    &quot;reflect&quot;
    &quot;testing&quot;
)

func TestReverseArray(t *testing.T) {
    tests := []struct {
        name     string
        input    []int
        expected []int
    }{
        {
            name:     &quot;Normal case - odd length&quot;,
            input:    []int{1, 2, 3, 4, 5},
            expected: []int{5, 4, 3, 2, 1},
        },
        {
            name:     &quot;Normal case - even length&quot;,
            input:    []int{1, 2, 3, 4},
            expected: []int{4, 3, 2, 1},
        },
        {
            name:     &quot;Single element&quot;,
            input:    []int{42},
            expected: []int{42},
        },
        {
            name:     &quot;Empty array&quot;,
            input:    []int{},
            expected: []int{},
        },
        {
            name:     &quot;Two elements&quot;,
            input:    []int{1, 2},
            expected: []int{2, 1},
        },
        {
            name:     &quot;Negative numbers&quot;,
            input:    []int{-1, -2, -3},
            expected: []int{-3, -2, -1},
        },
        {
            name:     &quot;Mixed positive and negative&quot;,
            input:    []int{-5, 0, 5, 10},
            expected: []int{10, 5, 0, -5},
        },
    }

    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            // Make a copy since reverseArray modifies in-place
            input := make([]int, len(tt.input))
            copy(input, tt.input)
            
            reverseArray(input)
            
            if !reflect.DeepEqual(input, tt.expected) {
                t.Errorf(&quot;reverseArray() = %v, want %v&quot;, input, tt.expected)
            }
        })
    }
}

func TestReverseString(t *testing.T) {
    tests := []struct {
        name     string
        input    string
        expected string
    }{
        {
            name:     &quot;Simple ASCII&quot;,
            input:    &quot;hello&quot;,
            expected: &quot;olleh&quot;,
        },
        {
            name:     &quot;Single character&quot;,
            input:    &quot;a&quot;,
            expected: &quot;a&quot;,
        },
        {
            name:     &quot;Empty string&quot;,
            input:    &quot;&quot;,
            expected: &quot;&quot;,
        },
        {
            name:     &quot;Persian text&quot;,
            input:    &quot;سلام&quot;,
            expected: &quot;مالس&quot;,
        },
        {
            name:     &quot;Arabic text&quot;,
            input:    &quot;مرحبا&quot;,
            expected: &quot;ابحرم&quot;,
        },
        {
            name:     &quot;Emoji&quot;,
            input:    &quot;Hello👋&quot;,
            expected: &quot;👋olleH&quot;,
        },
        {
            name:     &quot;Mixed emoji&quot;,
            input:    &quot;🚀Go💻&quot;,
            expected: &quot;💻oG🚀&quot;,
        },
        {
            name:     &quot;Palindrome&quot;,
            input:    &quot;racecar&quot;,
            expected: &quot;racecar&quot;,
        },
        {
            name:     &quot;Persian palindrome&quot;,
            input:    &quot;ساکاس&quot;,
            expected: &quot;ساکاس&quot;,
        },
        {
            name:     &quot;Numbers as string&quot;,
            input:    &quot;12345&quot;,
            expected: &quot;54321&quot;,
        },
    }

    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            result := reverseString(tt.input)
            if result != tt.expected {
                t.Errorf(&quot;reverseString() = %q, want %q&quot;, result, tt.expected)
            }
        })
    }
}
اجرای تست:  go test -vخروجی:=== RUN TestReverseArray=== RUN TestReverseArray/Normal_case_-_odd_length=== RUN TestReverseArray/Normal_case_-_even_length…— PASS: TestReverseArray (0.00s)=== RUN TestReverseString=== RUN TestReverseString/Simple_ASCII=== RUN TestReverseString/Persian_text…— PASS: TestReverseString (0.00s)PASS🚨 دام‌های رایج که باید ازشون دوری کنی۱. فراموش کردن Edge Cases                                            content_copy                        go// ❌ Crash می‌کنه اگه آرایه خالی باشه!
func reverseBad(nums []int) {
    for i := 0; i &lt; len(nums)/2; i++ {
        // اگه nums خالی باشه، len(nums)-1-i منفی می‌شه!
        nums[i], nums[len(nums)-1-i] = nums[len(nums)-1-i], nums[i]
    }
}
تست:reverseBad([]int{}) // ❌ Panic: index out of range!
۲. استفاده از []byte برای String// ❌ Unicode رو خراب می‌کنه
func reverseStringBroken(s string) string {
    bytes := []byte(s)
    // ... swap logic ...
    return string(bytes)
}

// Test:
reverseStringBroken(&quot;سلام&quot;) // ❌ Output: gibberish!

۳. ساختن آرایه جدید بی‌دلیل// ❌ O(n) space - غیر بهینه!
func reverseWithExtraSpace(nums []int) []int {
    result := make([]int, len(nums))
    for i := 0; i &lt; len(nums); i++ {
        result[i] = nums[len(nums)-1-i]
    }
    return result
}
چرا بد است؟حافظه اضافی O(n) می‌خوادوقتی می‌تونی In-place حل کنی، چرا حافظه هدر بدی؟۴. استفاده از strconv یا String Concatenation تو حلقه// ❌ خیلی کند! O(n^2) می‌شه
func reverseStringSlow(s string) string {
    result := &quot;&quot;
    for i := len(s) - 1; i &gt;= 0; i-- {
        result += string(s[i]) // String concatenation is O(n)!
    }
    return result
}
چرا کند است؟هر بار += یه string جدید می‌سازه (String ها Immutable هستن)پیچیدگی می‌شه O(n2)!📊 مقایسه راه‌حل‌ها۱. ساختن آرایه جدیدresult := make([]int, len(nums))
for i := 0; i &lt; len(nums); i++ {
    result[i] = nums[len(nums)-1-i]
}
پیچیدگی زمانی: O(n)پیچیدگی فضایی: O(n)Unicode Safe: ✅In-place: ❌۲. Two Pointer برای Arrayleft, right := 0, len(nums)-1
for left &lt; right {
    nums[left], nums[right] = nums[right], nums[left]
    left++
    right--
}

پیچیدگی زمانی: O(n)پیچیدگی فضایی: O(1) ⭐ بهترین!Unicode Safe: ✅In-place: ✅۳. Two Pointer برای String با []runerunes := []rune(s)
left, right := 0, len(runes)-1
for left &lt; right {
    runes[left], runes[right] = runes[right], runes[left]
    left++
    right--
}
return string(runes)
پیچیدگی زمانی: O(n)پیچیدگی فضایی: O(n)Unicode Safe: ✅ ⭐ درست!In-place: ❌ (چون String ها Immutable هستن)۴. استفاده از []byte برای Stringbytes := []byte(s)
// ... swap logic ...
return string(bytes)                      پیچیدگی زمانی: O(n)O(n)O(n)پیچیدگی فضایی: O(n)O(n)O(n)Unicode Safe: ❌ ⚠️ فارسی و ایموجی خراب می‌شه!In-place: ❌۵. String Concatenation (بدترین روش!)result := &quot;&quot;
for i := len(s) - 1; i &gt;= 0; i-- {
    result += string(s[i])
}پیچیدگی زمانی: O(n2)O(n^2)O(n2) ⚠️ خیلی کند!پیچیدگی فضایی: O(n)O(n)O(n)Unicode Safe: ✅In-place: ❌نتیجه‌گیری:برای Array: روش Two Pointer با O(1)O(1)O(1) فضا بهترینهبرای String: روش Two Pointer با []rune تنها راه درست و Unicode-safe هست💡 نکات طلایی برای مصاحبه۱. همیشه Edge Cases رو بپرسقبل از شروع کد نوشتن:“آیا ورودی می‌تونه خالی باشه؟”“آیا باید In-place باشه یا می‌تونم آرایه جدید برگردونم؟”“آیا String می‌تونه کاراکترهای غیر ASCII داشته باشه؟”۲. Trade-off رو توضیح بده“برای آرایه می‌تونیم In-place باشیم با O(1) فضا، اما برای String چون Immutable هست، مجبوریم []rune بسازیم که O(n) فضا می‌خواد.”۳. Unicode رو فراموش نکن“از []rune استفاده می‌کنم تا کاراکترهای چند بایتی مثل فارسی و ایموجی خراب نشن.”۴. پیچیدگی رو بگو“پیچیدگی زمانی O(n) چون یک بار روی آرایه حرکت می‌کنیم. پیچیدگی فضایی برای آرایه O(1) چون In-place هست، اما برای String O(n) چون باید []rune بسازیم.”🎓 خلاصه و نکات کلیدی✅ Two Pointer Technique: بهترین روش برای Reverse کردن✅ In-place: برای آرایه ممکنه، برای String نه✅ []rune not []byte: برای Unicode Safety✅ Edge Cases: خالی، تک عنصری، Palindrome✅ پیچیدگی: O(n) زمانی، O(1) یا O(n) فضایی🚀 کد نهایی کامل                                            package main

import &quot;fmt&quot;

// reverseArray reverses an integer slice in-place
// Time: O(n), Space: O(1)
func reverseArray(nums []int) {
    if len(nums) &lt; 2 {
        return
    }
    
    left, right := 0, len(nums)-1
    for left &lt; right {
        nums[left], nums[right] = nums[right], nums[left]
        left++
        right--
    }
}

// reverseString reverses a string correctly handling Unicode
// Time: O(n), Space: O(n)
func reverseString(s string) string {
    if len(s) &lt; 2 {
        return s
    }
    
    runes := []rune(s)
    left, right := 0, len(runes)-1
    
    for left &lt; right {
        runes[left], runes[right] = runes[right], runes[left]
        left++
        right--
    }
    
    return string(runes)
}

func main() {
    // Test array
    arr := []int{1, 2, 3, 4, 5}
    fmt.Println(&quot;Before:&quot;, arr)
    reverseArray(arr)
    fmt.Println(&quot;After:&quot;, arr)
    
    // Test string
    fmt.Println(&quot;\nString tests:&quot;)
    fmt.Println(reverseString(&quot;hello&quot;))
    fmt.Println(reverseString(&quot;سلام دنیا&quot;))
    fmt.Println(reverseString(&quot;Hello👋World🌍&quot;))
}
                        حالا آماده‌ای که Tech Lead رو با یه راه‌حل تمیز، بهینه و Unicode-safe قانع کنی! 🎯</description>
                <category>Navid Barsalari</category>
                <author>Navid Barsalari</author>
                <pubDate>Tue, 28 Apr 2026 20:00:25 +0330</pubDate>
            </item>
                    <item>
                <title>خداحافظی با آبجکت و آرایه! در جاوااسکریپت (راهنمای جامع Map و Set برای برنامه‌نویسان ارشد)</title>
                <link>https://virgool.io/@navidbarsalari/%D8%AE%D8%AF%D8%A7%D8%AD%D8%A7%D9%81%D8%B8%DB%8C-%D8%A8%D8%A7-%D8%A2%D8%A8%D8%AC%DA%A9%D8%AA-%D9%88-%D8%A2%D8%B1%D8%A7%DB%8C%D9%87-%D8%AF%D8%B1-%D8%AC%D8%A7%D9%88%D8%A7%D8%A7%D8%B3%DA%A9%D8%B1%DB%8C%D9%BE%D8%AA-%D8%B1%D8%A7%D9%87%D9%86%D9%85%D8%A7%DB%8C-%D8%AC%D8%A7%D9%85%D8%B9-map-%D9%88-set-%D8%A8%D8%B1%D8%A7%DB%8C-%D8%A8%D8%B1%D9%86%D8%A7%D9%85%D9%87-%D9%86%D9%88%DB%8C%D8%B3%D8%A7%D9%86-%D8%A7%D8%B1%D8%B4%D8%AF-mytesqrlveyb</link>
                <description>بیشتر ما برنامه‌نویس‌های جاوااسکریپت، به محض اینکه می‌خواهیم لیستی از داده‌ها را ذخیره کنیم یک Array می‌سازیم و اگر داده‌هایمان ساختار کلید-مقدار (Key-Value) داشته باشند، سراغ Object می‌رویم. این ترکیب برای ۹۰ درصد مواقع عالی کار می‌کند؛ اما در مصاحبه‌های استخدامی شرکت‌های بزرگ (و پروژه‌هایی که روی هزاران رکورد کار می‌کنند)، گیر افتادن در همین عادت ساده می‌تواند باعث ریجکت شدن رزومه یا افت شدید پرفورمنس شود.در این مقاله، ساختارهای داده مدرن جاوااسکریپت (Map و Set) را کالبدشکافی می‌کنیم تا ببینیم چرا، کجا و چگونه باید جایگزین روش‌های سنتی شوند.۱. دوئل اول: Object در برابر Mapآبجکت‌ها پایه‌ای‌ترین ساختار در جاوااسکریپت هستند، اما در واقع برای ساختار داده‌ایِ “دیکشنری” (Dictionary) ساخته نشده‌اند. Map در ES6 معرفی شد تا یک “هش‌مپ” (Hash Map) واقعی در اختیار ما قرار دهد.تفاوت‌های مرگبار در مصاحبه و پرفورمنس:محدودیت کلیدها (Keys): در یک Object، کلیدها فقط می‌توانند String یا Symbol باشند. اگر یک عدد یا یک آبجکت دیگر را به عنوان کلید بدهید، جاوااسکریپت در پس‌زمینه آن را به رشته (استرینگ) تبدیل می‌کند! (مثلاً [object Object]). اما در Map، هر چیزی می‌تواند کلید باشد.ترتیب پیمایش (Iteration Order): یکی از باگ‌های اعصاب‌خردکن آبجکت‌ها این است که تضمین صددرصدی برای حفظ ترتیب کلیدها نمی‌دهند (به خصوص اگر کلیدها عدد باشند، جاوااسکریپت آن‌ها را مرتب می‌کند). اما Map دقیقاً ترتیب اضافه شدن (Insertion Order) را حفظ می‌کند.سایز و اندازه: برای پیدا کردن تعداد آیتم‌های یک آبجکت باید بنویسید Object.keys(obj).length که در پس‌زمینه یک آرایه جدید می‌سازد و پیچیدگی زمانی آن O(n)O(n)O(n) است. اما Map یک پراپرتی آماده به نام size دارد که در زمان O(1)O(1)O(1) سایز را به شما می‌دهد.پرفورمنس در حذف و اضافه: اگر سناریویی دارید که مدام در حال set و delete کردن کلیدها هستید (مثل پیاده‌سازی یک سیستم Caching)، موتور V8 جاوااسکریپت Map را برای این کار بهینه‌سازی کرده است و سرعت به مراتب بالاتری دارد.کد نمونه:javascript// استفاده از آبجکت به عنوان کلید در Map برای ذخیره دیتای اضافی

const userMetaData = new Map();
const userDomElement = document.getElementById(&#039;btn-login&#039;);

// بدون اینکه خود عنصر DOM را دستکاری کنیم، به آن دیتا وصل می‌کنیم
userMetaData.set(userDomElement, { clickCount: 0, lastClicked: Date.now() });

console.log(userMetaData.get(userDomElement).clickCount); // 0
نقطه ضعف Map (تله مصاحبه):متد JSON.stringify به صورت پیش‌فرض Map را نمی‌فهمد و خروجی خالی {} می‌دهد! برای تبدیل Map به JSON باید ابتدا آن را به آرایه یا آبجکت تبدیل کنید (Object.fromEntries(myMap)).۲. دوئل دوم: Array در برابر Setفرض کنید یک آرایه از اعداد دارید و می‌خواهید چک کنید آیا شناسه 100 در آن وجود دارد یا نه. یا اینکه می‌خواهید رکوردهای تکراری را از آن حذف کنید. اینجاست که Set وارد بازی می‌شود. Set کالکشنی است که هیچ مقدار تکراری در آن مجاز نیست.چرا Set پرفورمنس شما را نجات می‌دهد؟سرعت جستجو (The O(1)O(1)O(1) Magic): وقتی از array.includes(100) استفاده می‌کنید، جاوااسکریپت باید از خانه اول تا آخر را بگردد تا آن را پیدا کند. پیچیدگی این کار O(n)O(n)O(n) است. اما در Set، وقتی از set.has(100) استفاده می‌کنید، به لطف الگوریتم Hash، جستجو با پیچیدگی زمانی O(1)O(1)O(1) انجام می‌شود. در دیتاست‌های بزرگ، این تفاوت یعنی تبدیل شدن یک سرچ کُند به یک سرچ آنی!حذف تکراری‌ها (یک خط کد جادویی): پرتکرارترین سوال مصاحبه: “چطور آرایه زیر را یونیک کنیم؟”javascriptconst duplicates = [1, 1, 2, 3, 3, 4];
const uniqueArray = [...new Set(duplicates)]; // [1, 2, 3, 4]
عملیات‌های پیشرفته با Set (برای مصاحبه‌های Senior):آرایه‌ها متدهای آماده‌ای برای عملیات ریاضی مجموعه‌ها ندارند، اما با Set می‌توانید به راحتی اشتراک (Intersection) و تفاضل (Difference) دو مجموعه داده را پیدا کنید:javascriptconst setA = new Set([1, 2, 3]);
const setB = new Set([3, 4, 5]);

// اشتراک (داده‌های مشترک): [3]
const intersection = new Set([...setA].filter(x =&gt; setB.has(x)));

// تفاضل (داده‌هایی که فقط در A هستند): [1, 2]
const difference = new Set([...setA].filter(x =&gt; !setB.has(x)));
۳. جایزه ویژه مصاحبه: WeakMap و WeakSet (مدیریت حافظه)اگر در مصاحبه‌ای تسلط خود را روی Map نشان دهید، سوال بعدی ۱۰۰٪ این خواهد بود: “خب، فرق Map با WeakMap چیست؟”جواب در یک مفهوم حیاتی خلاصه می‌شود: Garbage Collection (زباله‌روب حافظه).در Map معمولی، تا زمانی که کلید شما داخل Map حضور دارد، آن دیتای مرجع از مموری رم پاک نمی‌شود. این مسئله وقتی روی عناصر DOM یا آبجکت‌های سنگین کار می‌کنید می‌تواند باعث Memory Leak (نشت حافظه) شود.اما در WeakMap (و WeakSet):کلیدها الزاماً باید Object باشند (عدد یا استرینگ قبول نمی‌کند).پیوند “ضعیفی” (Weak) با مموری دارند. اگر آن آبجکت در هیچ کجای دیگر از برنامه شما استفاده نشود، موتور جاوااسکریپت منتظر WeakMap نمی‌ماند و آن را کاملاً از رم و همچنین از داخل WeakMap پاک می‌کند.کاربرد واقعی: پیاده‌سازی متغیرهای Private در کلاس‌ها یا ذخیره دیتای موقت برای عناصر DOM (که وقتی کاربر از صفحه رفت و آن عنصر DOM حذف شد، دیتای متصل به آن هم خودکار از حافظه آزاد شود).🎯 تقلب‌نامه نهایی (Cheat Sheet)برای اینکه در کسری از ثانیه بهترین ابزار را انتخاب کنید:🔹 کی از Object استفاده کنیم؟ساختار داده‌تان از قبل مشخص است (مثل اطلاعات یک User).داده‌ها قرار است مستقیماً به JSON تبدیل شده و به بک‌اند ارسال شوند.نیاز به تعریف متد (Functions) روی داده‌ها دارید.🔹 کی از Map استفاده کنیم؟دیکشنری بزرگی دارید که مدام در حال آپدیت، حذف و اضافه شدن کلیدهاست.نیاز دارید کلیدی غیر از String داشته باشید (مثل ذخیره کانفیگ برای یک Object).پرفورمنس و حفظ ترتیبِ اضافه شدن کلیدها برایتان حیاتی است.🔹 کی از Array استفاده کنیم؟ترتیب و ایندکس (Index 0, 1, 2) داده‌ها مهم است.داده‌های تکراری مشکلی ندارند.نیاز به متدهای زنجیره‌ای مثل map(), filter(), reduce() دارید.🔹 کی از Set استفاده کنیم؟با لیستی از مقادیر یکتا (Unique) سروکار دارید.نیاز دارید با بالاترین سرعت ممکن (O(1)) چک کنید که یک آیتم در لیست وجود دارد یا خیر (جایگزین array.includes).</description>
                <category>Navid Barsalari</category>
                <author>Navid Barsalari</author>
                <pubDate>Sat, 25 Apr 2026 11:40:59 +0330</pubDate>
            </item>
                    <item>
                <title>کالبدشکافی حلقه‌ها در جاوااسکریپت: کی، کجا و چرا؟ (از for تا map و for…of)</title>
                <link>https://virgool.io/@navidbarsalari/%DA%A9%D8%A7%D9%84%D8%A8%D8%AF%D8%B4%DA%A9%D8%A7%D9%81%DB%8C-%D8%AD%D9%84%D9%82%D9%87-%D9%87%D8%A7-%D8%AF%D8%B1-%D8%AC%D8%A7%D9%88%D8%A7%D8%A7%D8%B3%DA%A9%D8%B1%DB%8C%D9%BE%D8%AA-%DA%A9%DB%8C-%DA%A9%D8%AC%D8%A7-%D9%88-%DA%86%D8%B1%D8%A7-%D8%A7%D8%B2-for-%D8%AA%D8%A7-map-%D9%88-forof-mz9moo30owok</link>
                <description>احتمالاً برای شما هم پیش آمده که بخواهید روی یک آرایه پیمایش کنید و ناگهان با منوی باز جاوااسکریپت روبه‌رو می‌شوید: for معمولی بنویسم؟ map کنم؟ forEach بزنم؟ یا از for...of و for...in استفاده کنم؟در نگاه اول شاید بگویید: «همه‌شون یه کار رو میکنن، چه فرقی داره؟» اما در یک مصاحبه فنی بین‌المللی یا در Code Review های شرکت‌های بزرگ (مثل FAANG)، انتخاب حلقه اشتباه می‌تواند باعث رد شدن کد شما شود! در این مقاله قصد داریم این حلقه‌ها را کالبدشکافی کنیم تا دیگر هرگز در انتخاب آن‌ها شک نکنید.۱. حلقه کلاسیک for: پیرمرد پرسرعتاین همان حلقه‌ای است که از زبان C به ارث برده‌ایم.                                            content_copy                        javascriptconst arr = [10, 20, 30];
for (let i = 0; i &lt; arr.length; i++) {
  if (arr[i] === 20) break; 
  console.log(arr[i]);
}
کی استفاده کنیم؟وقتی پرفورمنس (Performance) در بالاترین حد اهمیت است (مثلاً در الگوریتم‌های پیچیده با پیچیدگی زمانی O(n) یا بالاتر).وقتی نیاز دارید وسط کار با break از حلقه خارج شوید یا با continue از یک مرحله بپرید.وقتی گام‌های حرکت شما خاص است (مثلاً دو تا دو تا جلو رفتن: i += 2).چرا در کد روزمره کمتر استفاده می‌شود؟چون Boilerplate (کد تکراری) زیادی دارد و احتمال خطای “Off-by-one” (اشتباه در شرط &lt; یا &lt;=) در آن بالاست.۲. متد forEach: کارگر بی‌ادعا (Side Effects)متد forEach یک تابع می‌گیرد و آن را روی تک‌تک اعضای آرایه اجرا می‌کند.                                            content_copy                        javascriptconst users = [&#039;Ali&#039;, &#039;Sara&#039;, &#039;John&#039;];
users.forEach((user, index) =&gt; {
  console.log(`User ${index}: ${user}`);
});
نکات حیاتی (خوراک مصاحبه):راه فرار ندارید! شما نمی‌توانید از forEach با دستور break خارج شوید. اگر از return داخل آن استفاده کنید، فقط شبیه به continue عمل می‌کند و به دور بعدی می‌رود.تله Async/Await: اگر داخل forEach از await استفاده کنید، جاوااسکریپت منتظر تمام شدن آن نمی‌ماند! برای کارهای Asynchronous، استفاده از forEach یک اشتباه مهلک است.هدف اصلی: فقط زمانی از forEach استفاده کنید که قصد دارید تغییری در دنیای بیرون بدهید (Side Effect)، مثلاً ذخیره در دیتابیس یا لاگ گرفتن.۳. متد map: کارخانه تبدیلمتد map سلطان برنامه‌نویسی Functional در جاوااسکریپت است.                                            content_copy                        javascriptconst prices = [10, 20, 30];
const pricesWithTax = prices.map(price =&gt; price * 1.09);
// pricesWithTax: [10.9, 21.8, 32.7]
تذکر جدی یک Tech Lead:خیلی از برنامه‌نویس‌ها برای تمیزتر شدن کد، به جای forEach از map استفاده می‌کنند، در حالی که اصلاً به خروجی آن نیازی ندارند!متد map همیشه یک آرایه جدید در مموری می‌سازد. اگر فقط می‌خواهید روی داده‌ها پیمایش کنید و آرایه جدیدی نمی‌خواهید، استفاده از map باعث هدررفت حافظه (با پیچیدگی فضایی O(n)) می‌شود. map یعنی: «این آرایه را بگیر و یک آرایه جدید با همین تعداد عضو به من تحویل بده».۴. حلقه for...in: تله‌ای برای آرایه‌ها!این حلقه برای پیمایش کلیدها (Keys) در یک Object طراحی شده است، نه مقادیر یک آرایه.                                            content_copy                        javascriptconst person = { name: &#039;John&#039;, age: 30 };
for (const key in person) {
  console.log(key, person[key]); 
}
چرا نباید روی آرایه از for...in استفاده کنیم؟این حلقه روی ایندکس‌ها به عنوان String پیمایش می‌کند (&quot;0&quot;, &quot;1&quot;) نه اعداد!ترتیب اجرای آن در جاوااسکریپت تضمین‌شده نیست.ممکن است پراپرتی‌هایی که به Prototype آرایه اضافه شده‌اند را هم ناخواسته وارد حلقه کند.خلاصه: for...in را فقط و فقط برای گشتن در دل Object ها استفاده کنید.۵. حلقه for...of: پادشاه مدرن جاوااسکریپت (ES6)این حلقه بهترین ترکیب از خوانایی و قدرت است و روی هر چیزی که Iterable باشد (آرایه، استرینگ، Map، Set) کار می‌کند.                                            content_copy                        javascriptconst numbers = [1, 2, 3];
for (const num of numbers) {
  if (num === 2) break; // به راحتی متوقف می‌شود
  console.log(num);
}
چرا for...of فوق‌العاده است؟سینتکس بسیار تمیز و خوانایی دارد.برخلاف forEach، از break و continue به خوبی پشتیبانی می‌کند.بهترین گزینه برای پیمایش‌های Asynchronous است (استفاده از for await...of).🎯 جمع‌بندی: تقلب‌نامه برای به خاطر سپردن (Cheat Sheet)برای اینکه در زمان کد زدن (یا مصاحبه) سریع تصمیم بگیرید، این جملات را گوشه ذهن‌تان بسپارید:🔹 کلاسیک for: وقتی الگوریتم پیچیده داری و پرفورمنس برات حیاتیه (محاسبات O(n2) و…).🔹 forEach: وقتی فقط میخوای یه کاری روی اعضا انجام بدی (Side Effect) و نیازی به توقف حلقه نداری.🔹 map: وقتی یک آرایه داری و می‌خوای از روی اون، یک آرایه جدید با همون طول بسازی (Transform).🔹 for...in: فقط برای گشتن روی کلیدهای یک Object (به آرایه‌ها نزدیکش نکنید!).🔹 for...of: بهترین و تمیزترین راه پیش‌فرض برای پیمایش آرایه‌ها (با قابلیت پشتیبانی از break و async).</description>
                <category>Navid Barsalari</category>
                <author>Navid Barsalari</author>
                <pubDate>Wed, 22 Apr 2026 12:16:34 +0330</pubDate>
            </item>
                    <item>
                <title>🚀 جاوااسکریپت/تایپ‌اسکریپت: Null در مقابل Undefined (تله‌ی مقادیر خالی)</title>
                <link>https://virgool.io/@navidbarsalari/%F0%9F%9A%80-%D8%AC%D8%A7%D9%88%D8%A7%D8%A7%D8%B3%DA%A9%D8%B1%DB%8C%D9%BE%D8%AA%D8%AA%D8%A7%DB%8C%D9%BE-%D8%A7%D8%B3%DA%A9%D8%B1%DB%8C%D9%BE%D8%AA-null-%D8%AF%D8%B1-%D9%85%D9%82%D8%A7%D8%A8%D9%84-undefined-%D8%AA%D9%84%D9%87-%DB%8C-%D9%85%D9%82%D8%A7%D8%AF%DB%8C%D8%B1-%D8%AE%D8%A7%D9%84%DB%8C-jrkyitixpqm8</link>
                <description>🧠 خلاصه‌ی مطلبدر دنیای جاوااسکریپت و تایپ‌اسکریپت، هر دو مقدار null و undefined نشان‌دهنده “عدم وجود داده” هستند، اما منشأ و معنای آن‌ها کاملاً متفاوت است:undefined: متغیر تعریف شده، اما هنوز هیچ مقداری به آن اختصاص داده نشده است (حالت پیش‌فرض موتور جاوااسکریپت).null: یک مقدار خالی که توسط برنامه‌نویس به صورت عمدی به یک متغیر داده می‌شود تا بگوید “اینجا عمداً خالی است”.درک این تفاوت برای جلوگیری از باگ‌های عجیب و مدیریت درست داده‌ها (مخصوصاً در ارتباط با APIها) حیاتی است.🏗️ نوع Undefined چیست؟وقتی جاوااسکریپت نمی‌داند مقدار یک چیز چیست، به صورت خودکار به آن undefined می‌دهد. این اتفاق در حالت‌های زیر رخ می‌دهد:۱. متغیری تعریف شده ولی مقداردهی نشده است.۲. تابعی که چیزی return نمی‌کند.۳. دسترسی به پراپرتی‌ای از یک آبجکت که وجود ندارد.                                                                   javascriptlet name;
console.log(name); // خروجی: undefined

const user = { age: 25 };
console.log(user.email); // خروجی: undefined
پیام این نوع این است: «من وجود دارم، اما موتور جاوااسکریپت هنوز مقداری برای من پیدا نکرده است.»🛑 نوع Null چیست؟مقدار null هرگز به صورت خودکار توسط خود زبان جاوااسکریپت اختصاص داده نمی‌شود. این شما هستید که به عنوان برنامه‌نویس تصمیم می‌گیرید یک متغیر را با null خالی کنید.javascriptlet activeUser = &quot;Ali&quot;;
// کاربر از سیستم خارج می‌شود
activeUser = null;
پیام این نوع این است: «این متغیر قبلاً مقداری داشته یا در آینده خواهد داشت، اما در این لحظه عمداً خالی نگه داشته شده است.»🔍 تفاوت‌های کلیدی: Null در مقابل Undefinedمعنا: undefined یعنی “تعریف نشده”، اما null یعنی “خالیِ عمدی”.عامل ایجاد: undefined معمولاً توسط سیستم/موتور جاوااسکریپت تولید می‌شود، اما null توسط برنامه‌نویس نوشته می‌شود.باگ تاریخی typeof: اگر از typeof استفاده کنید، typeof undefined برابر undefined است، اما typeof null به اشتباه object برمی‌گرداند! (این یک باگ قدیمی در جاوااسکریپت است که هرگز اصلاح نشد).برابری: در بررسی شل (==) این دو با هم برابرند، اما در بررسی سخت‌گیرانه (===) برابر نیستند.null == undefined 👉 truenull === undefined 👉 false🔥 ترفند: عملگر Nullish Coalescing (??)یکی از بهترین راه‌ها برای هندل کردن این دو، استفاده از عملگر ?? است. این عملگر فقط زمانی مقدار جایگزین را برمی‌گرداند که متغیر شما دقیقاً null یا undefined باشد (برخلاف || که با عدد صفر یا رشته خالی هم تریگر می‌شود).javascriptlet userScore = 0;

// غلط: استفاده از OR باعث میشه صفر نادیده گرفته بشه
// باعث میشه صفر نادیده گرفته بشه OR استفاده از 
let finalScore1 = userScore || 10; // خروجی: 10 ❌

// درست Nullish Coalescing  استفاده از 
let finalScore2 = userScore ?? 10; // خروجی: 0 ✅
🧠 سخن پایانیundefined = «سیستم می‌گوید: من هیچ ایده‌ای ندارم مقدار این چیست!» 🤷‍♂️null = «برنامه‌نویس می‌گوید: من این جعبه را عمداً خالی گذاشتم!» 📦اگر به دنبال این هستید که کدی تمیزتر بنویسید:👉 هرگز متغیرهای خود را به صورت دستی برابر undefined قرار ندهید؛ اگر می‌خواهید چیزی را خالی کنید، از null استفاده کنید.✍️ جمله‌ای برای به خاطر سپردنundefined یعنی هنوز تکلیفش مشخص نیست؛ null یعنی تکلیفش اینه که خالی باشه! </description>
                <category>Navid Barsalari</category>
                <author>Navid Barsalari</author>
                <pubDate>Tue, 21 Apr 2026 10:32:31 +0330</pubDate>
            </item>
                    <item>
                <title>🚀 ترفندهای پنهان پرفورمنس در TypeScript</title>
                <link>https://virgool.io/@navidbarsalari/%F0%9F%9A%80-%D8%AA%D8%B1%D9%81%D9%86%D8%AF%D9%87%D8%A7%DB%8C-%D9%BE%D9%86%D9%87%D8%A7%D9%86-%D9%BE%D8%B1%D9%81%D9%88%D8%B1%D9%85%D9%86%D8%B3-%D8%AF%D8%B1-typescript-u0phoq21ohzm</link>
                <description>🧠 خلاصهTypeScript معمولاً به‌عنوان یک ابزار بدون هزینه در زمان اجرا شناخته می‌شود.اما واقعیت این است که نحوه استفاده شما از TypeScript می‌تواند روی:حجم باندلسرعت اجراتجربه توسعه‌دهندهتأثیر مستقیم بگذارد.در این مقاله، ترفندهایی را بررسی می‌کنیم که اغلب نادیده گرفته می‌شوند اما در پروژه‌های واقعی تفاوت محسوسی ایجاد می‌کنند.⚡ ۱. استفاده از const enum به‌جای enumوقتی از enum معمولی استفاده می‌کنی، TypeScript در خروجی JavaScript یک object واقعی تولید می‌کند.این یعنی:کد بیشتر در باندلدسترسی در runtimeاما در const enum:const enum Status {
  Pending,
  Completed
}TypeScript مقدارها را مستقیماً جایگزین می‌کند و هیچ objectای ساخته نمی‌شود.📌 نتیجه:حجم کمتردسترسی سریع‌تراگر به reverse mapping یا iteration نیاز نداری، const enum انتخاب بهتری است.🧩 ۲. استفاده از Union Type به‌جای enumدر خیلی از موارد اصلاً نیازی به enum نداری.type Direction = &quot;Up&quot; | &quot;Down&quot; | &quot;Left&quot; | &quot;Right&quot;;مزایا:هیچ کدی در خروجی تولید نمی‌شودساده‌تر و خواناترکاملاً type-safe📌 این یکی از ساده‌ترین راه‌ها برای کاهش حجم باندل است.🔍 ۳. پرهیز از anyاستفاده از any فقط مشکل type safety نیست.مشکلات پنهان:inference را خراب می‌کندrefactor را سخت می‌کندباعث خطاهای runtime می‌شودبهتر است از unknown یا type دقیق استفاده کنی.🧠 ۴. استفاده از as constconst config = {
  mode: &quot;dark&quot;
} as const;بدون as const:مقدارها به string عمومی تبدیل می‌شوندبا as const:مقدارها literal می‌شوندreadonly می‌شوند📌 نتیجه:inference بهترجلوگیری از باگ📦 ۵. ساده نگه داشتن typeهاtypeهای خیلی پیچیده (مثل genericهای تو در تو):کامپایل را کند می‌کنندIDE را سنگین می‌کنند📌 قانون ساده:اگر فهمیدنش سخته، احتمالاً زیادی پیچیده‌ست 😄🚫 ۶. استفاده زیاد از Barrel Fileالگوی رایج:export * from &quot;./utils&quot;;مشکل:ممکن است tree-shaking به‌درستی کار نکندکدهای استفاده‌نشده وارد باندل شوندراه بهتر:import { fn } from &quot;./utils/fn&quot;;⚙️ ۷. فعال کردن isolatedModulesدر tsconfig:{
  &quot;compilerOptions&quot;: {
    &quot;isolatedModules&quot;: true
  }
}مزایا:build سریع‌ترسازگاری بهتر با ابزارهایی مثل esbuild و swc🔥 ۸. جدا کردن type-check از buildtype checking هزینه‌بر است.بهتر است:جدا اجرا شودbuild سریع‌تر شودمثلاً:build با esbuildtype-check با tsc🧪 ۹. استفاده از readonlytype User = {
  readonly name: string;
};مزایا:جلوگیری از تغییر ناخواستهکمک به بهینه‌سازی در runtime🧠 جمع‌بندیTypeScript در runtime وجود ندارد،اما تصمیم‌هایی که با آن می‌گیری کاملاً واقعی هستند.با چند تغییر ساده می‌توانی:باندل سبک‌تر داشته باشیbuild سریع‌تر داشته باشیکد maintainableتری بنویسی✍️ جمله‌ای که باید یادت بماندTypeScript رایگان است، اما استفاده اشتباه از آن هزینه دارد.</description>
                <category>Navid Barsalari</category>
                <author>Navid Barsalari</author>
                <pubDate>Mon, 20 Apr 2026 16:30:25 +0330</pubDate>
            </item>
                    <item>
                <title>📦 تأثیر TypeScript روی حجم باندل (با مثال واقعی)</title>
                <link>https://virgool.io/@navidbarsalari/%F0%9F%93%A6-%D8%AA%D8%A3%D8%AB%DB%8C%D8%B1-typescript-%D8%B1%D9%88%DB%8C-%D8%AD%D8%AC%D9%85-%D8%A8%D8%A7%D9%86%D8%AF%D9%84-%D8%A8%D8%A7-%D9%85%D8%AB%D8%A7%D9%84-%D9%88%D8%A7%D9%82%D8%B9%DB%8C-mdar2vz0jmrm</link>
                <description>🧠 خلاصهTypeScript بهصورت مستقیم حجم باندل را افزایش نمیدهد.اما الگوهایی که در آن استفاده میکنید، میتوانند تأثیر زیادی داشته باشند.⚙️ اصل ماجراTypeها در TypeScript حذف میشوند:type User = {
  name: string;
};خروجی:const user = { name: &quot;Navid&quot; };🔍 مثال ۱: enum vs const enum vs unionوقتی از enum معمولی استفاده میکنی:یک object واقعی در JavaScript ساخته میشوداین یعنی کد بیشتر در باندلاما اگر از const enum استفاده کنی:هیچ objectای ساخته نمیشودمقدارها مستقیم جایگزین میشوندو اگر از union type استفاده کنی:type Status = &quot;Pending&quot; | &quot;Completed&quot;;هیچ خروجیای تولید نمیشود📌 نتیجه ساده:enum → کد اضافهconst enum → بدون هزینهunion → کاملاً رایگان🧩 مثال ۲: Barrel File &#40;index.ts&#41;این الگو خیلی رایجه:// index.ts
export * from &quot;./utils&quot;;
export * from &quot;./helpers&quot;;مشکل چیه؟وقتی اینطوری import میکنی:import { smallFn } from &quot;./index&quot;;ممکنه bundler نتونه درست tree-shake کنهو کدهای استفادهنشده هم وارد باندل بشن.📌 راه بهتر:import { smallFn } from &quot;./utils/smallFn&quot;;⚙️ مثال ۳: Helper Functionها در targetهای قدیمیاگر target پروژهات قدیمی باشه (مثلاً ES5):TypeScript مجبور میشه helper function اضافه کنه، مثل:__extends__assignاین helperها ممکنه چندین بار در فایلهای مختلف تکرار بشن.📌 راه حل:در tsconfig:{
  &quot;compilerOptions&quot;: {
    &quot;importHelpers&quot;: true
  }
}و نصب:npm install tslib✅ helperها shared میشن✅ حجم باندل کمتر میشه🧠 مثال ۴: Decoratorهااگر از decorator استفاده کنی، مخصوصاً با:&quot;emitDecoratorMetadata&quot;: trueTypeScript کدهای اضافی برای metadata تولید میکنه.📌 نتیجه:چندین KB افزایش حجم باندلاگر واقعاً لازم نیست، استفاده نکن.📉 یک مثال واقعی (بدون جدول)روی یک پروژه کوچک (React + TypeScript):وقتی از enum زیاد استفاده شد → حجم باندل حدود 132KB بودبا جایگزینی const enum → رسید به 124KBبا union type → شد 121KBحذف barrel file → رسید به 115KBفعال کردن importHelpers → شد 112KB📌 یعنی حدود 15٪ کاهش حجم فقط با چند تصمیم ساده⚠️ یک سوءتفاهم مهمخیلیها فکر میکنن:TypeScript باعث بزرگ شدن باندل میشهاما واقعیت:❌ خود TypeScript نه✅ الگوهای اشتباه بله🚀 بهترین کارهایی که باید انجام بدیتا جای ممکن از union یا const enum استفاده کناز barrel file زیاد استفاده نکنimportHelpers رو فعال کنtarget رو مدرن انتخاب کن (ES2020+)decorator رو فقط وقتی لازم داری استفاده کن🧠 جمعبندیTypeScript در runtime وجود نداره،اما تصمیمهایی که باهاش میگیری کاملاً واقعی هستن.اگر درست استفاده کنی:هم type safety داریهم performanceهم باندل سبک✍️ جملهای که باید یادت بمونهTypeScript حجم باندل رو زیاد نمیکنه، انتخابهای تو این کارو میکنن.</description>
                <category>Navid Barsalari</category>
                <author>Navid Barsalari</author>
                <pubDate>Mon, 20 Apr 2026 16:25:07 +0330</pubDate>
            </item>
                    <item>
                <title>🚀  تایپ‌اسکریپت: Never در مقابل Void (تله‌ی بازگشت توابع)</title>
                <link>https://virgool.io/@navidbarsalari/%F0%9F%9A%80-%D8%AA%D8%A7%DB%8C%D9%BE-%D8%A7%D8%B3%DA%A9%D8%B1%DB%8C%D9%BE%D8%AA-never-%D8%AF%D8%B1-%D9%85%D9%82%D8%A7%D8%A8%D9%84-void-%D8%AA%D9%84%D9%87-%DB%8C-%D8%A8%D8%A7%D8%B2%DA%AF%D8%B4%D8%AA-%D8%AA%D9%88%D8%A7%D8%A8%D8%B9-jzdosnlhrufl</link>
                <description>🧠 خلاصه‌ی مطلبدر تایپ‌اسکریپت، دو نوع void و never هر دو نشان‌دهنده &quot;عدم وجود مقدار&quot; هستند، اما مفهوم آن‌ها کاملاً متفاوت است:void: تابع به پایان می‌رسد، اما هیچ مقدار معناداری برنمی‌گرداند.never: تابع هرگز به پایان نمی‌رسد (یا خطا پرتاب می‌کند یا وارد حلقه‌ی بی‌نهایت می‌شود).درک این تفاوت برای مدیریت خطاها و نوشتن کدهای ایمن (Robust) حیاتی است.🏗️ نوع void چیست؟زمانی از void استفاده می‌کنیم که تابع یک کار جانبی (Side Effect) انجام می‌دهد (مثل چاپ در کنسول یا تغییر یک وضعیت) اما چیزی برای بازگشت به فراخواننده ندارد.TypeScriptfunction logMessage(msg: string): void {
  console.log(msg);
  //  است undefined در واقع خروجی  
}
اگر سعی کنید مقداری را برگردانید، تایپ‌اسکریپت خطا می‌دهد. پیام این تایپ این است: «این تابع کارش تمام می‌شود، اما خروجی خاصی ندارد.»🛑 نوع never چیست؟تایپ never نشان‌دهنده وضعیتی است که تابع نمی‌تواند به پایان برسد. این یعنی اجرای کد در آن نقطه متوقف یا منحرف می‌شود.&quot;This function finishes, but ignore whatever it returns.&quot;function throwError(msg: string): never {
  throw new Error(msg);
}

function infiniteLoop(): never {
  while (true) { }
}
وقتی تابعی از نوع never باشد، هر کدی که بعد از فراخوانی آن نوشته شود، از نظر تایپ‌اسکریپت &quot;Unreachable&quot; یا غیرقابل دسترس است.🔍 تفاوت‌های کلیدی: void در مقابل neverاز آنجایی که جدول‌ها در ویرگول گاهی بد نمایش داده می‌شوند، تفاوت‌ها را اینجا لیست کرده‌ام:مفهوم: در void چیزی برنمی‌گردد، اما در never اصلاً بازگشتی در کار نیست.وضعیت اجرا: در void تابع با موفقیت تمام می‌شود، اما در never تابع متوقف شده یا می‌هنگد.کاربرد: void برای لاگ گرفتن و تغییرات ساده است؛ never برای مدیریت خطا و حلقه‌های بی‌نهایت.خروجی نهایی: خروجی void در واقع undefined است، اما never هیچ خروجی‌ای ندارد.🔥 ترفند بررسی کامل (Exhaustive Check)اینجاست که never قدرت خود را نشان می‌دهد. می‌توانید از آن برای مطمئن شدن از اینکه تمام حالت‌های یک Union Type در دستور switch پوشش داده شده‌اند، استفاده کنید:TypeScripttype Action = &quot;run&quot; | &quot;stop&quot;;

function handleAction(action: Action) {
  switch (action) {
    case &quot;run&quot;:
      return &quot;Running...&quot;;
    case &quot;stop&quot;:
      return &quot;Stopped.&quot;;
    default:
      // اضافه کنید، اینجا خطای کامپایل می‌گیرید! jump اگر بعداً حالتی مثل 
      const _exhaustiveCheck: never = action;
      return _exhaustiveCheck;
  }
}
اگر &quot;jump&quot; را به Action اضافه کنید اما فراموش کنید آن را در switch بنویسید، تایپ‌اسکریپت به شما هشدار می‌دهد چون &quot;jump&quot; نمی‌تواند در یک متغیر از نوع never قرار بگیرد.🧠 سخن پایانیvoid = «کارم تموم شد، ولی چیزی برات ندارم.» 🤝never = «من دیگه از این تابع برنمی‌گردم!» 💀اگر به دنبال این هستید که:باگ‌ها را در زمان کدنویسی شکار کنید.منطق برنامه‌تان را کامل و بدون نقص بنویسید.👉 همیشه برای توابعی که خطا پرتاب می‌کنند یا اجرای برنامه را متوقف می‌کنند، از never استفاده کنید.✍️ جمله‌ای برای به خاطر سپردناز void وقتی استفاده کن که تابع تمام می‌شود؛ از never وقتی که نباید تمام شود.</description>
                <category>Navid Barsalari</category>
                <author>Navid Barsalari</author>
                <pubDate>Mon, 20 Apr 2026 15:09:38 +0330</pubDate>
            </item>
                    <item>
                <title>الگوریتم در گولنگ (قسمت اول): Two Sum و برخورد سخت با دیوار Tech Lead!</title>
                <link>https://virgool.io/@navidbarsalari/%D8%A7%D9%84%DA%AF%D9%88%D8%B1%DB%8C%D8%AA%D9%85-%D8%AF%D8%B1-%DA%AF%D9%88%D9%84%D9%86%DA%AF-%D9%82%D8%B3%D9%85%D8%AA-%D8%A7%D9%88%D9%84-two-sum-%D9%88-%D8%A8%D8%B1%D8%AE%D9%88%D8%B1%D8%AF-%D8%B3%D8%AE%D8%AA-%D8%A8%D8%A7-%D8%AF%DB%8C%D9%88%D8%A7%D8%B1-tech-lead-uazl6gntrfoo</link>
                <description>قصد داریم در یک مجموعه مقاله، نحوه حل و بررسی یک سری از الگوریتم‌ها را با هم شخم بزنیم که قاعدتاً از سطح پایین شروع شده و به بالاترین سطح خواهد رسید.الگوریتم اولی که مورد بحث قرار می‌دهیم، پای ثابت مصاحبه‌ها یعنی Two Sum است.صورت سوالTwo Sum

Given an array of integers nums and an integer target, return the indices of the two numbers that add up to target.

Input: nums = [2, 7, 11, 15], target = 9

Output: [0, 1] // nums[0] + nums[1] = 2 + 7 = 9

Input: nums = [3, 2, 4], target = 6

Output: [1, 2]

Input: nums = [3, 3], target = 6

Output: [0, 1]

Constraints:

2 &lt;= nums.length &lt;= 10^4
-10^9 &lt;= nums[i] &lt;= 10^9
-10^9 &lt;= target &lt;= 10^9
Only one valid answer existsدرک مسئله: ذهن انسان در برابر ماشیندر بحث چالش‌های برنامه‌نویسی، فهمیدن صورت سوال از خود کد زدن مهم‌تر است؛ مخصوصاً وقتی در یک مصاحبه فنی لایو هستید و قلب‌تان در دهان‌تان می‌تپد!بیایید بی‌درنگ برویم سراغ مسئله. ما باید مجموع دو عدد را پیدا کنیم. اگر به عنوان یک انسان به آرایه [2, 7, 11, 15] و هدف 999 نگاه کنیم، در کسری از ثانیه چشم‌مان عدد ۲ و ۷ را اسکن می‌کند و می‌گوییم “یافتم!”. اما همان‌طور که می‌دانید، ماشین‌ها مثل ما بینایی محیطی ندارند. کامپیوتر کورِ بیچاره نمی‌تواند همه اعداد را با هم ببیند.پس ذهن ما به صورت پیش‌فرض می‌رود سراغ پیمایش یکی‌یکی عناصر: اول ۱۵ را برمی‌داریم، با ۱۱ جمع می‌کنیم، می‌شود ۲۶ (هدف ما نیست). بعد می‌رویم سراغ بعدی و الی آخر…اگر بخواهیم همین منطق خام را تبدیل به کد گولنگ کنیم، به چیزی شبیه به این می‌رسیم:package main

import &quot;fmt&quot;

func twoSum(nums []int, target int) []int {
	// Base check
	if len(nums) &lt; 2 {
		return nil
	}
	
	result := []int{}
	// Iterate through each element
	for i := 0; i &lt; len(nums); i++ {
		// Compare with all subsequent elements
		for j := i + 1; j &lt; len(nums); j++ {
			if nums[i]+nums[j] == target {
				result = append(result, i, j)
				return result
			}
		}
	}
	return result
}

func main() {
	arr := []int{2, 7, 11, 15}
	res := twoSum(arr, 9)
	fmt.Println(res)
}

کالبدشکافی کد بالا:در این کد ما از دو حلقه for تودرتو استفاده کرده‌ایم. حلقه اول با ایندکس i روی تک‌تک عناصر آرایه حرکت می‌کند. در همان حال، حلقه دوم با ایندکس j از خانه بعدی (i + 1) شروع به حرکت می‌کند تا عدد فعلی را با تمام اعداد جلویی خودش جمع کند. در خط if nums[i]+nums[j] == target چک می‌کنیم که آیا مجموع این دو عدد با هدف برابر است یا خیر. اگر برابر بود، مستقیماً یک اسلایس (Slice) شامل هر دو ایندکس []int{i, j} را بر میگرداندکد کار کرد! اما… (ورود Tech Lead )شما با خوشحالی کد را می‌زنید، ران می‌کنید، خروجی درست است (Happy Path را پاس می‌کند) و لبخند رضایت بر لبان‌تان می‌نشیند. کد را برای Code Review می‌فرستید. اما اگر مرورگر کد شما یک Tech Lead سخت‌گیر (مثلاً در گوگل) باشد، کد را با یک کامنت تند و تیز به شما برمی‌گرداند.بیایید ببینیم این کد چه ایراداتی دارد و چرا در یک مصاحبه واقعی رد می‌شود:۱. فاجعه پیچیدگی زمانی (Wrong Complexity):استفاده از حلقه تودرتو (Nested Loop) یعنی شما دارید زمان را می‌کشید! پیچیدگی زمانی این کد O(n2) است. شاید برای ۴ تا عدد جواب بدهد، اما اگر طول آرایه ما ۱۰ به توان ۴ باشد (طبق محدودیت‌ها)، این کد به نفس‌نفس می‌افتد. ما یک راه‌حل O(n) نیاز داریم.۲. منطق اعتبارسنجی روی هواست!گاهی اوقات بچه‌ها برای محکم‌کاری میان شرط‌هایی مثل target &gt;= 10000 می‌نویسند تا جلوی خطای ورودی را بگیرند. اما دقت کنید! محدودیت طول آرایه ۱۰ به توان ۴ است، در حالی که عدد target می‌تواند تا ۱۰ به توان ۹ باشد . قاطی کردن این محدودیت‌ها با هم نشان می‌دهد صورت سوال را دقیق نخوانده‌اید.۳. چاپ کردن داخل یک تابع منطقی؟ هرگز!هیچ‌وقت داخل تابعی که قرار است یک ابزار (Library Function) باشد، از fmt.Println استفاده نکنید. تابع شما فقط باید دیتا (یا Error) برگرداند. این وظیفه فراخواننده (Caller) است که تصمیم بگیرد دیتا را چاپ کند، در دیتابیس ذخیره کند یا به API بفرستد.۴. ساختن کلیدهای استرینگ در حلقه‌های داغ (Performance Killer):برخی برای فرار از حلقه تودرتو، سعی می‌کنند ترکیب اعداد را به صورت یک رشته (String) مثل &quot;a,b&quot; در یک Map ذخیره کنند تا تکراری‌ها را هندل کنند. استفاده از توابعی مثل strconv.Itoa داخل یک حلقه که هزاران بار می‌چرخد، باعث تخصیص حافظه (Memory Allocation) شدید شده و در بررسی‌های پرفورمنس شما را نابود می‌کند.یه چیزی شبیه این :)package main

import (
	&quot;fmt&quot;
	&quot;strconv&quot;
)

// Bad practice: Using strings and strconv for caching inside a loop
func twoSumStrconv(nums []int, target int) []int {
	seenPairs := make(map[string]bool)

	for i := 0; i &lt; len(nums); i++ {
		for j := i + 1; j &lt; len(nums); j++ {
			a, b := nums[i], nums[j]
			
			// Order them to create a unique key
			if a &gt; b {
				a, b = b, a
			}
			
			// Creating strings in a hot loop -&gt; High memory allocation!
			key := strconv.Itoa(a) + &quot;,&quot; + strconv.Itoa(b)
			
			if !seenPairs[key] &amp;&amp; nums[i]+nums[j] == target {
				return []int{i, j}
			}
			seenPairs[key] = true
		}
	}
	return nil
}
استفاده از توابعی مثل strconv.Itoa و جمع کردن رشته‌ها (+) داخل حلقه‌ای که هزاران بار می‌چرخد، باعث تخصیص حافظه (Memory Allocation) شدید شده و در بررسی‌های پرفورمنس شما را نابود می‌کند. ضمن اینکه این کد هنوز درگیر حلقه تودرتو و پیچیدگی O(n2)O(n^2)O(n2) است!تغییر زاویه دید: ترفند طلاییتک‌لید به شما می‌گوید: «به جای اینکه بگردی ببینی فلان عدد با چی جمع بشه می‌شه هدف، از خودت بپرس: من برای رسیدن به هدف چی کم دارم؟ و آیا قبلاً اون چیزی که کم دارم رو دیدم؟»اینجاست که ساختار داده HashMap (در گولنگ همان map) مثل یک قهرمان وارد می‌شود.فرض کنید target = 9 است:عدد اول ۲ است. می‌پرسیم: “برای رسیدن به ۹ چی کم دارم؟” جواب: ۷. آیا قبلاً ۷ را دیده‌ام؟ نه. پس خود ۲ و ایندکس آن را در دفترچه‌ام (Map) یادداشت می‌کنم.عدد بعدی ۷ است. می‌پرسیم: “برای رسیدن به ۹ چی کم دارم؟” جواب: ۲. آیا قبلاً ۲ را دیده‌ام؟ بله! در دفترچه‌ام هست. تمام شد! ما جواب را در یک دور گشتن (Single Pass) پیدا کردیم.راه‌حل بهینه و حرفه‌ای (The Optimal Solution)با این دیدگاه، کدمان را به شکلی می‌نویسیم که پیچیدگی زمانی آن O(n) و پیچیدگی فضایی آن (به خاطر استفاده از مپ) O(n) باشد. در این حالت، جستجو در مپ زمانش O(1) است.func twoSum(nums []int, target int) []int {
	// Create a map to store numbers we have seen so far
	// Key: the number itself, Value: its index in the array
	seen := make(map[int]int)

	for i, num := range nums {
		// What do we need to reach the target?
		complement := target - num
		
		// Have we seen this complement before? (O(1) lookup)
		if index, ok := seen[complement]; ok {
			// If yes, return its index and the current index
			return []int{index, i}
		}
		
		// If not, record the current number and its index in our map
		seen[num] = i
	}
	
	// Return nil if no solution is found (though constraints say one exists)
	return nil 
}
توضیح خط به خط:seen := make(map[int]int):در گولنگ، map همان ساختار داده معروف Hash Table (یا Dictionary در پایتون) است. ما در اینجا یک دفترچه یادداشت به نام seen (به معنی “دیده‌شده‌ها”) می‌سازیم.کلید (Key) مپ: خودِ عدد را ذخیره می‌کند (نوع int).مقدار (Value) مپ: ایندکسِ (موقعیت) آن عدد در آرایه را ذخیره می‌کند (نوع int).for i, num := range nums:یک حلقه که روی آرایه nums حرکت می‌کند. در هر چرخش، i شماره خانه (ایندکس) و num خودِ عدد را به ما می‌دهد. ما فقط یک بار روی آرایه حرکت می‌کنیم (Single Pass).complement := target - num:این قلب تپنده‌ی منطق ماست. complement یعنی “مکمل” یا “متمم”. ما از خودمان می‌پرسیم: «حالا که عدد فعلی من num است، برای رسیدن به target چه عددی کم دارم؟»مثال: اگر هدف ما 999 باشد و عدد فعلی ما 222، متمم می‌شود 9−2=79 - 2 = 79−2=7.if index, ok := seen[complement]; ok:حالا می‌رویم سراغ دفترچه یادداشتمان (seen). می‌گوییم: «آیا عدد متمم (مثلاً 7) قبلاً در دفترچه من ثبت شده است؟»در گولنگ، وقتی کلیدی را از یک مپ می‌خوانید، دو خروجی به شما می‌دهد: اولی مقدار آن کلید (index) و دومی یک متغیر بولین (ok) که می‌گوید آیا اصلاً این کلید وجود داشت یا نه.جستجو در مپ بسیار سریع است و پیچیدگی زمانی آن O(1) می‌باشد.return []int{index, i}:اگر متغیر ok برابر true بود (یعنی متمم را قبلاً دیده‌ایم)، تبریک می‌گویم! شما جواب را پیدا کردید. ما بلافاصله یک آرایه شامل ایندکسِ عدد قبلی (index) و ایندکسِ عدد فعلی (i) را برمی‌گردانیم.seen[num] = i:اگر به جواب نرسیدیم (یعنی ok برابر false بود)، عدد فعلی (num) را به عنوان کلید و ایندکس آن (i) را به عنوان مقدار در دفترچه یادداشت می‌کنیم تا شاید در چرخه‌های بعدی، متممِ یک عدد دیگر باشد.تست کیس‌ها (Edge Cases) و اثبات کدبرای اینکه نشان دهیم کد ما چقدر قدرتمند است، باید آن را با تست‌کیس‌های مختلف (اعداد منفی، صفر، تکراری‌ها و…) بسنجیم. در گولنگ بهترین راه استفاده از Table-Driven Tests است.کد زیر را می‌توانید در فایلی مثل twosum_test.go قرار دهید تا تمام لبه‌های تاریک (Edge Cases) را تست کند:package main

import (
	&quot;reflect&quot;
	&quot;testing&quot;
)

func TestTwoSum(t *testing.T) {
	// Table-driven tests covering various edge cases
	tests := []struct {
		name     string
		nums     []int
		target   int
		expected []int
	}{
		{
			name:     &quot;Happy Path - Simple positive numbers&quot;,
			nums:     []int{2, 7, 11, 15},
			target:   9,
			expected: []int{0, 1},
		},
		{
			name:     &quot;Edge Case - Negative numbers&quot;,
			nums:     []int{-3, 4, 3, 90},
			target:   0,
			expected: []int{0, 2},
		},
		{
			name:     &quot;Edge Case - Duplicate numbers&quot;,
			nums:     []int{3, 3},
			target:   6,
			expected: []int{0, 1},
		},
		{
			name:     &quot;Edge Case - Zero target with negatives&quot;,
			nums:     []int{-5, 0, 5},
			target:   0,
			expected: []int{0, 2},
		},
		{
			name:     &quot;Edge Case - Large Target (Constraint testing)&quot;,
			nums:     []int{1000000000, 2, 4, 500000000}, // 10^9 and 5*10^8
			target:   1500000000,
			expected: []int{0, 3},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := twoSum(tt.nums, tt.target)
			// reflect.DeepEqual is great for comparing slices
			if !reflect.DeepEqual(result, tt.expected) {
				t.Errorf(&quot;got %v, want %v&quot;, result, tt.expected)
			}
		})
	}
}
جمع‌بندی نکات این الگوریتم:پیمایش تک‌مرحله‌ای (Single Pass): نیازی نیست همه چیز را چند بار حساب کنید. با استفاده از حافظه اضافی (Map)، زمان را می‌خریم.جلوگیری از محاسبات تکراری: ما فقط به گذشته نگاه می‌کنیم (seen). این باعث می‌شود یک عنصر را با خودش جمع نکنیم (چون در حلقه تودرتو اگر حواستان نباشد ممکن است ایندکس i را با خودش جمع کنید).سرعت جستجو: خاصیت اصلی Hashmap ها پیدا کردن مقادیر در زمان O(1) است که کلید فرار ما از کابوس O(n2) بود.در مقالات بعدی سراغ چالش‌های خفن‌تری می‌رویم که ذهن‌تان را حسابی قلقلک بدهد!</description>
                <category>Navid Barsalari</category>
                <author>Navid Barsalari</author>
                <pubDate>Wed, 15 Apr 2026 11:56:14 +0330</pubDate>
            </item>
                    <item>
                <title>بررسی فنی معماری MVCC در PostgreSQL</title>
                <link>https://virgool.io/@navidbarsalari/%D8%A8%D8%B1%D8%B1%D8%B3%DB%8C-%D9%81%D9%86%DB%8C-%D9%85%D8%B9%D9%85%D8%A7%D8%B1%DB%8C-mvcc-%D8%AF%D8%B1-postgresql-g2zbwm1d3t0m</link>
                <description>در دیتابیس‌های قدیمی‌تر، مدیریت همزمانی (Concurrency) بر پایه قفل‌گذاری (Locking) استوار بود. یعنی وقتی یک تراکنش در حال خواندن یک ردیف بود و تراکنش دیگری می‌خواست همان ردیف را آپدیت کند، دیتابیس آن ردیف را قفل می‌کرد و یکی از تراکنش‌ها باید منتظر دیگری می‌ماند. این گلوگاه در سیستم‌های با تراکنش بالا به شدت مشکل‌ساز است.پستگرس برای حل این مشکل از معماری MVCC (Multi-Version Concurrency Control) استفاده می‌کند. در ادامه مکانیزم این معماری و مفاهیم مرتبط با آن را به صورت فنی بررسی می‌کنیم.۱. معماری MVCC چیست؟ (Multi-Version Concurrency Control)قانون طلایی MVCC در این جمله خلاصه می‌شود: «خواندن، نوشتن را مسدود نمی‌کند و نوشتن، خواندن را مسدود نمی‌کند.»وقتی شما در حال آپدیت یک دیتا هستید، کاربر دیگری که کوئری SELECT می‌زند، نسخه قبلی و پایدار دیتا را می‌بیند. این کار تا زمانی که تراکنش شما کامیت (Commit) شود ادامه دارد. در واقع، دیتابیس نسخه‌های متعددی از یک داده را در یک زمان واحد نگهداری می‌کند تا ایزوله‌سازی تراکنش‌ها (Isolation) به درستی پیاده شود.مشکل اساسی: چرا اصلاً به MVCC نیاز داریم؟ در سیستم‌های سنتی (مثلاً مدل Two-Phase Locking یا 2PL)، تضمین یکپارچگی داده‌ها (Consistency) با قفل کردن (Locking) انجام می‌شد:اگر من دارم می‌خوانم (Shared Lock)، تو نمی‌توانی بنویسی (آپدیت/دلیت کنی). باید صبر کنی.اگر من دارم می‌نویسم (Exclusive Lock)، تو حتی نمی‌توانی بخوانی. باید صبر کنی تا کار من تمام شود.در سیستم‌هایی با ترافیک بالا (High Concurrency)، این مدل باعث ایجاد صف‌های طولانی و افت شدید پرفورمنس می‌شود. MVCC خلق شد تا نیاز به قفل‌گذاری برای خواندن (Read Locking) را به طور کامل از بین ببرد.۲. MVCC در پستگرس چگونه کار می‌کند؟برای پیاده‌سازی این «چند نسخه‌ای» بودن، پستگرس دیتاها را درجا (In-place) تغییر نمی‌دهد. هر ردیف در جدول‌های پستگرس یک Tuple نامیده می‌شود. پستگرس در سطح دیسک، به هر Tuple چندین هدر (Header) و ستون سیستمی مخفی اضافه می‌کند که مهم‌ترین آن‌ها دو مورد زیر هستند:ستون xmin: شناسه تراکنشی (TXID) که این رکورد را ایجاد (INSERT) کرده است.ستون xmax: شناسه تراکنشی (TXID) که این رکورد را حذف (DELETE) یا آپدیت کرده است. (اگر رکورد هنوز معتبر باشد و حذف نشده باشد، مقدار این ستون 0 است).پستگرس با استفاده از این دو فیلد و مقایسه آن‌ها با TXID فعلی، تصمیم می‌گیرد که آیا یک رکورد برای تراکنش جاری قابل رؤیت (Visible) است یا خیر.بررسی ۳ عملیات اصلی:عملیات INSERT: یک Tuple جدید ساخته می‌شود. مقدار xmin برابر با شناسه تراکنش شما (TXID) می‌شود و مقدار xmax برابر با 0 خواهد بود (چون هنوز حذف نشده است).عملیات DELETE: رکورد اصلاً از روی هارد دیسک پاک نمی‌شود! پستگرس فقط مقدار xmax آن رکورد را برابر با TXID تراکنش شما قرار می‌دهد. این یعنی این رکورد از این لحظه به بعد منقضی شده است.عملیات UPDATE: در پستگرس چیزی به اسم آپدیت درجا (In-place Update) وجود ندارد. یک UPDATE در واقع ترکیبی از یک DELETE و یک INSERT است. یعنی Tuple قدیمی مقدار xmax می‌گیرد (منقضی می‌شود) و یک Tuple کاملاً جدید با دیتای جدید، در یک بلاک جدید روی دیسک با xmin جدید نوشته می‌شود.۳. رکوردهای مرده (Dead Tuples) چه هستند؟با توجه به مکانیزم بالا، متوجه می‌شویم که هر بار که دیتایی را UPDATE یا DELETE می‌کنیم، نسخه قدیمی آن دیتا همچنان روی هارد دیسک (و در حافظه RAM هنگام کش شدن) باقی می‌ماند.به این نسخه‌های قدیمی که مقدار xmax دارند و آن تراکنش پایان یافته است، Dead Tuples گفته می‌شود. این رکوردها دیگر برای هیچ تراکنش فعالی در دیتابیس قابل خواندن نیستند.مشکل Dead Tuples چیست؟اگر دیتابیس شما عملیات UPDATE و DELETE زیادی داشته باشد، تعداد Dead Tuples به شدت بالا می‌رود. این پدیده باعث Table Bloat (تورم جدول) می‌شود. تورم نه‌تنها حجم دیسک را هدر می‌دهد، بلکه مستقیماً روی پرفورمنس تأثیر می‌گذارد.مثلاً اگر جدول شما ۱ میلیون رکورد زنده و ۵ میلیون Dead Tuple داشته باشد، هنگام اجرای یک کوئری (مخصوصاً Sequential Scan)، پستگرس مجبور است تمام آن ۶ میلیون رکورد را از روی دیسک بخواند و پردازنده باید برای تک‌تک آن‌ها قوانین رؤیت‌پذیری (xmin و xmax) را چک کند. این کار باعث تحمیل بار سنگینی به I/O و CPU می‌شود.۴. عملیات Vacuuming چیست؟برای حل مشکل Dead Tuples و جلوگیری از تورم بی‌نهایت، پستگرس فرآیندی به نام VACUUM دارد. VACUUM مانند یک فرآیند زباله‌روب (Garbage Collector) است که به صورت دوره‌ای جداول را اسکن و تمیز می‌کند.وظایف اصلی VACUUM:آزاد کردن فضا (Space Reclamation): وکیوم می‌گردد و تمام Dead Tuples را پیدا می‌کند. اما آن‌ها را فیزیکی حذف نمی‌کند؛ بلکه فضایی که آن‌ها اشغال کرده‌اند را در ساختاری به نام FSM (Free Space Map) علامت‌گذاری می‌کند. از این پس، عملیات INSERT یا UPDATEهای بعدی می‌دانند که می‌توانند دیتای خود را در این فضاهای علامت‌گذاری شده بنویسند.نکته فنی: دستور VACUUM معمولی حجم فایل دیتابیس در هارد دیسک را کم نمی‌کند (سیستم‌عامل فضای خالی شده را نمی‌بیند)، فقط فضا را در داخل فایل بازیافت می‌کند. برای بازگرداندن فضا به سیستم‌عامل باید از VACUUM FULL استفاده کرد که جدول را کاملاً قفل (Exclusive Lock) می‌کند و یک کپی جدید از دیتای زنده می‌سازد.جلوگیری از فاجعه Transaction ID Wraparound: شناسه‌های تراکنش (TXIDTXIDTXID) در پستگرس یک عدد صحیح ۳۲ بیتی هستند (یعنی ظرفیت حدود  ۲ ضرب در ۱۰ به توان ۹ که معادل ۲ میلیارد تراکنش  ). وقتی دیتابیس به این مرز برسد، اعداد دوباره از صفر شروع می‌شوند (Wraparound). اگر این اتفاق بیفتد، دیتابیس ناگهان تراکنش‌های گذشته را به عنوان تراکنش‌های آینده می‌بیند و کل دیتاها ناپدید می‌شوند! VACUUM با بررسی رکوردهای خیلی قدیمی، فیلد xmin آن‌ها را به یک مقدار ویژه به نام FrozenXID تغییر می‌دهد (فرآیند Freezing). این کار به پستگرس می‌فهماند که این رکوردها آن‌قدر قدیمی‌اند که برای همه تراکنش‌ها در همه زمان‌ها معتبرند.آپدیت کردن آمار و Visibility Map: وکیوم ساختاری به نام VM (Visibility Map) را آپدیت می‌کند که نشان می‌دهد کدام بلاک‌های دیسک فقط شامل رکوردهای زنده هستند. این کار باعث تسریع فوق‌العاده Index-Only Scans می‌شود. همچنین معمولاً VACUUM با دستور ANALYZE همراه می‌شود تا Query Planner پستگرس آمار دقیقی از توزیع داده‌ها داشته باشد و بهترین Execution Plan را انتخاب کند.اتووکیوم (AutoVacuum):مدیریت دستی وکیوم کار دشواری است. پستگرس یک پروسه بک‌گراند (Daemon) به نام autovacuum دارد که مدام آمار تغییرات جداول را مانیتور می‌کند. اگر متوجه شود تعداد Dead Tuples در یک جدول از یک آستانه مشخص (معمولاً حدود 20%20\%20%) بالاتر رفته است، به صورت خودکار عملیات پاکسازی را در پس‌زمینه روی آن جدول اجرا می‌کند. خاموش کردن autovacuum در محیط پروداکشن یکی از بزرگترین اشتباهاتی است که منجر به توقف دیتابیس (به دلیل Wraparound) یا افت شدید پرفورمنس خواهد شد.۵. بهای MVCC چیست؟ (Trade-offs)هیچ معماری‌ای بی‌نقص نیست. پستگرس برای این سرعت بالا و قفل نشدن دیتا، هزینه‌های زیر را می‌پردازد:بزرگ شدن سایز رکوردها: به هر ردیف حداقل ۲۳ بایت هدر (شامل همین xminxminxmin و xmaxxmaxxmax و موارد کنترلی دیگر) اضافه می‌شود.نوشتن مضاعف (Write Amplification): یک آپدیت ساده، یک ردیف کامل جدید می‌نویسد و روی ایندکس‌ها هم تاثیر می‌گذارد.تولید زباله (Dead Tuples): رکوردهایی که از فیلتر قوانین رویت‌پذیری رد نمی‌شوند، روی دیسک می‌مانند و این ما را به همان بحث Vacuum می‌رساند.۶.موتور پستگرس چگونه تصمیم می‌گیرد؟ (Visibility Rules)قبل از اینکه بریم سراغ نحوه تصمیم گیری پستگرس باید با چند مفهوم آشنا بشیممفهوم Snapshot (عکس‌برداری از زمان):قلب تپنده‌ی MVCC در پستگرس، مفهومی به نام Snapshot است.وقتی شما یک تراکنش (Transaction) را شروع می‌کنید، پستگرس یک “عکس” از وضعیت فعلی تمام تراکنش‌های دیتابیس می‌گیرد. اما این عکس، کپی کردن دیتا نیست (چون وحشتناک کند می‌شود)، بلکه ثبت وضعیت شناسه‌های تراکنش (TXID) است.یک Snapshot شامل سه جزء اصلی ریاضی است:xmin (در سطح اسنپ‌شات): قدیمی‌ترین تراکنشی که هنوز در حال اجراست (Active). هر تراکنشی که شناسه‌اش کوچکتر از این عدد باشد، قطعاً تمام شده (کامیت شده) و دیتای آن برای ما قابل دیدن است.xmax (در سطح اسنپ‌شات): اولین TXID که هنوز به هیچ تراکنشی اختصاص داده نشده است (تراکنش‌های آینده). هر تراکنشی که شناسه‌اش بزرگتر یا مساوی این عدد باشد، مربوط به آینده است و ما نباید دیتای آن را ببینیم.xip_list: لیستی از شناسه‌های تراکنش‌هایی که بین xmin و xmax قرار دارند و همین الان در حال اجرا هستند. کارهایی که اینها می‌کنند هنوز کامیت نشده، پس برای ما نامرئی است.بریم سراغ نحوه تصمیم گیری پستگرس:حالا فرض کن کوئری SELECT * FROM users را اجرا می‌کنی. پستگرس شروع می‌کند به خواندن ردیف‌ها (Tuples) از روی هارد دیسک.همانطور که در  قبلا گفتم، هر Tuple روی دیسک دو هدر دارد: tuple_xmin (سازنده) و tuple_xmax (حذف‌کننده).پستگرس برای هر تک‌تک رکوردهایی که می‌خواند، قوانین رویت‌پذیری (Visibility Rules) زیر را با استفاده از Snapshot شما چک می‌کند:مرحله اول: آیا این رکورد اصلاً برای من خلق شده است؟ (بررسی سازنده - tuple_xmin)اگر tuple_xmin ≥ snapshot_xmax باشد: این رکورد توسط تراکنشی در آینده ساخته شده. نامرئی (رد می‌شود).اگر tuple_xmin داخل لیست xip_list باشد: این رکورد توسط تراکنشی ساخته شده که هنوز تمام نشده (کامیت نشده). → نامرئی (رد می‌شود).اگر هیچ‌کدام از بالا نبود، یعنی رکورد در گذشته‌ی معتبر کامیت شده است. حالا می‌رویم مرحله دوم.مرحله دوم: آیا این رکورد هنوز زنده است یا پاک/آپدیت شده؟ (بررسی حذف‌کننده - tuple_xmax)اگر  tuple_xmax=0 باشد: رکورد کاملاً زنده است. → مرئی (به شما نشان داده می‌شود).اگر tuple_xmax ≥ snapshot_xmax باشد: رکورد توسط تراکنشی در آینده پاک شده است. پس در زمان (اسنپ‌شات) ما، هنوز زنده است! → مرئی (به شما نشان داده می‌شود).اگر tuple_xmax داخل لیست xip_list باشد: یک تراکنش دیگر همین الان در حال پاک کردن یا آپدیت این رکورد است، اما هنوز کامیت نکرده. پس برای ما هنوز معتبر است. → مرئی (به شما نشان داده می‌شود).اگر هیچ‌کدام نبود، یعنی رکورد در گذشته‌ی معتبر پاک شده است. →نامرئی (رد می‌شود - این همان Dead Tuple است!).۷. جمع‌بندیمعماری MVCC در پستگرس یک شاهکار مهندسی برای مدیریت همزمانی بالا (High Concurrency) است. با حذف نیاز به قفل‌گذاری برای عملیات خواندن، دیتابیس می‌تواند هزاران درخواست همزمان را بدون ایجاد گلوگاه پردازش کند.اما همان‌طور که دیدیم، این معماری بدون هزینه نیست. حفظ نسخه‌های متعدد از داده‌ها منجر به تولید رکوردهای مرده (Dead Tuples) می‌شود. درک عمیق نحوه کار ستون‌های سیستمی مانند xmin و xmax، و نحوه تصمیم‌گیری موتور دیتابیس بر اساس Snapshot، به ما نشان می‌دهد که چرا پروسه‌هایی مانند AutoVacuum فقط یک «ابزار جانبی» نیستند، بلکه قلب تپنده‌ی نگهداری از سلامت، سرعت و پایداری پستگرس محسوب می‌شوند. به عنوان یک توسعه‌دهنده یا DBA، شناخت این لایه‌های زیرین به ما کمک می‌کند تا کوئری‌های بهتری بنویسیم و دیتابیس را برای بارهای کاری سنگین بهینه‌تر پیکربندی کنیم.۸. نکات طلایی و تکمیلی (Pro Tips)برای اینکه دید بازتری نسبت به عملکرد موتور پستگرس داشته باشید، بد نیست به این ۳ نکته حیاتی نیز توجه کنید:۱. مراقب تراکنش‌های طولانی (Long-Running Transactions) باشید:اگر یک کوئری SELECT یا یک تراکنش باز، ساعت‌ها طول بکشد، Snapshot آن همچنان قدیمی‌ترین xmin فعال را در سیستم نگه می‌دارد. این یعنی پروسه VACUUM در این مدت نمی‌تواند هیچ Dead Tuple ای را که تراکنش‌های دیگر تولید کرده‌اند پاک کند (چون پیش خود می‌گوید شاید این تراکنش طولانی به دیدن آن‌ها نیاز داشته باشد). نتیجه‌ی این اتفاق، تورم ناگهانی و شدید جداول (TableBloat) است.۲. معجزه آپدیت‌های HOT  (Heap−OnlyTuples): در بخش Trade-offs گفتیم که آپدیت‌ها در پستگرس باعث WriteAmplification  (نوشتن مضاعف در جداول و ایندکس‌ها) می‌شوند. پستگرس برای کاهش این هزینه، مکانیزم هوشمندانه‌ای به نام HOT دارد. اگر شما ستونی را UPDATE کنید که روی آن هیچ Index ای تعریف نشده باشد، و در همان بلاکِ دیسک فضای خالی وجود داشته باشد، پستگرس ایندکس‌ها را درگیر نمی‌کند و رکورد جدید را در همان بلاک می‌نویسد. این کار هزینه آپدیت را به طرز چشمگیری کاهش می‌دهد.۳. تیونینگ (Tuning) تنظیمات AutoVacuum برای جداول بزرگ:تنظیمات پیش‌فرض autovacuum برای جداول کوچک تا متوسط عالی است (مقدار پیش‌فرض پارامتر autovacuum_vacuum_scale_factor=0.2  است، یعنی وقتی 20% رکوردها تغییر کردند وکیوم اجرا شود). اما اگر جدولی 100 میلیون رکورد داشته باشد، پستگرس صبر می‌کند تا 20 میلیون Dead Tuple ایجاد شود و بعد وکیوم را اجرا کند که این فاجعه است! در دیتابیس‌های بزرگ، حتماً باید این درصد را برای جداول حجیم کاهش دهید تا وکیوم‌ها کوچکتر، سریع‌تر و مکرر انجام شوند.</description>
                <category>Navid Barsalari</category>
                <author>Navid Barsalari</author>
                <pubDate>Wed, 15 Apr 2026 11:55:33 +0330</pubDate>
            </item>
                    <item>
                <title>⚖️ مقایسه‌ی مهاجرت تدریجی و مهاجرت یک‌باره — کدام برای پروژه‌ی شما مناسب‌تر است؟</title>
                <link>https://virgool.io/@navidbarsalari/%E2%9A%96%EF%B8%8F-%D9%85%D9%82%D8%A7%DB%8C%D8%B3%D9%87-%DB%8C-%D9%85%D9%87%D8%A7%D8%AC%D8%B1%D8%AA-%D8%AA%D8%AF%D8%B1%DB%8C%D8%AC%DB%8C-%D9%88-%D9%85%D9%87%D8%A7%D8%AC%D8%B1%D8%AA-%DB%8C%DA%A9-%D8%A8%D8%A7%D8%B1%D9%87-%E2%80%94-%DA%A9%D8%AF%D8%A7%D9%85-%D8%A8%D8%B1%D8%A7%DB%8C-%D9%BE%D8%B1%D9%88%DA%98%D9%87-%DB%8C-%D8%B4%D9%85%D8%A7-%D9%85%D9%86%D8%A7%D8%B3%D8%A8-%D8%AA%D8%B1-%D8%A7%D8%B3%D8%AA-bsrmi447e8wm</link>
                <description>این مقاله ادامه‌ی دو پست قبلی من درباره‌ی  این مقاله ادامه‌ی دو پست قبلی من درباره‌ی مهاجرت تدریجی از Nuxt به Next.js و بازنویسی کامل از Node.js به Go است.اگر هنوز آن‌ها را نخوانده‌اید، پیشنهاد می‌کنم ابتدا از آن‌ها شروع کنید تا با دو سناریوی واقعی آشنا شوید.مقدمهدر مسیر مدرن‌سازی نرم‌افزار، همیشه یک سؤال کلیدی مطرح است:«آیا باید پروژه را مرحله‌به‌مرحله به‌روزرسانی کنیم یا از نو بسازیم؟»این تصمیم ساده به نظر می‌رسد، اما می‌تواند سرنوشت فنی و حتی مالی کل شرکت را تغییر دهد.در این مقاله، با تکیه بر دو تجربه‌ی واقعی، تفاوت‌ها، مزایا و چالش‌های دو رویکرد Incremental Migration و Big Bang Migration را بررسی می‌کنیم.🚀 ۱. مهاجرت تدریجی (Incremental Migration)در این رویکرد، سیستم فعلی را به‌صورت مرحله‌به‌مرحله به فناوری جدید منتقل می‌کنیم.هر بخش جدید در کنار سیستم قدیمی بالا می‌آید و به مرور کل سیستم جایگزین می‌شود.🔹 نمونه‌ی واقعی:ما از Nuxt (Vue) به Next.js (React) رفتیم، بدون اینکه سرویس حتی یک دقیقه از کار بیفتد.🔹 ویژگی‌ها:بدون downtimeریسک پایین‌ترامکان یادگیری تدریجی تیمزمان طولانی‌تر تا اتمام کامل🔹 چالش‌ها:همزیستی دو سیستم (تداخل Auth، استایل، داده)نیاز به DevOps قوی برای مدیریت ترافیک بین دو اپپیچیدگی در CI/CD💥 ۲. مهاجرت یک‌باره (Big Bang Migration)در مقابل، این روش یعنی بازنویسی کامل — همه‌چیز از نو، در یک پرتاب (launch) واحد.سیستم قدیمی خاموش می‌شود، نسخه‌ی جدید بالا می‌آید.🔹 نمونه‌ی واقعی:ما بک‌اند را از Node.js به Go بازنویسی کردیم، چون ساختار قبلی قابل نگهداری نبود.🔹 ویژگی‌ها:طراحی کاملاً جدیدحذف کامل بدهی فنی (technical debt)عملکرد بهینه‌ترانتشار سریع‌تر از نظر زمان اجرا (ولی طولانی‌تر در آماده‌سازی)🔹 چالش‌ها:ریسک بالا (اگر شکست بخورد، کل سیستم از کار می‌افتد)نیاز به تست و مستندسازی بسیار دقیقاحتمال تأخیر در لانچ🧭 انتخاب درست چیه؟هیچ پاسخ مطلقی وجود نداره، ولی چند قاعده‌ی طلایی هست:⚙️ ترکیب هر دو رویکرددر پروژه‌های بزرگ، اغلب ترکیب این دو جواب می‌دهد:Frontend را با مهاجرت تدریجی پیش ببر (مثلاً Nuxt → Next)Backend را با بازنویسی کامل انجام بده (مثلاً Node → Go)به این ترتیب هم ریسک را کنترل می‌کنی و هم از مزایای هر دو جهان استفاده می‌کنی.نتیجه‌گیریمهاجرت نرم‌افزار فقط تغییر فریم‌ورک نیست؛یک تصمیم معماری و فرهنگی است.Incremental Migration مثل تغییر قطعات ماشین در حین حرکت است.Big Bang Migration مثل خرید یک ماشین جدید با همان پلاک است.مهم این است که:تصمیم بگیری کدام مسیر با واقعیت تیم، بودجه و کاربران تو هم‌خوانی دارد.</description>
                <category>Navid Barsalari</category>
                <author>Navid Barsalari</author>
                <pubDate>Sat, 18 Oct 2025 12:45:59 +0330</pubDate>
            </item>
                    <item>
                <title>💥 مهاجرت یک‌باره (Big Bang Migration): بازسازی کامل از Node.js به Go</title>
                <link>https://virgool.io/@navidbarsalari/%F0%9F%92%A5-%D9%85%D9%87%D8%A7%D8%AC%D8%B1%D8%AA-%DB%8C%DA%A9-%D8%A8%D8%A7%D8%B1%D9%87-big-bang-migration-%D8%A8%D8%A7%D8%B2%D8%B3%D8%A7%D8%B2%DB%8C-%DA%A9%D8%A7%D9%85%D9%84-%D8%A7%D8%B2-nodejs-%D8%A8%D9%87-go-t0wua8jvzoge</link>
                <description>مقدمهگاهی وقت‌ها نمی‌شه با وصله‌پینه و ریفکتورهای کوچک آینده‌ی پروژه رو ساخت.به کد نگاه می‌کنی، پر از تودرتوهای قدیمی و منطق‌های سنگینه، و می‌گی:&quot;دیگه این درست‌شدنی نیست. باید از صفر بسازیمش.&quot;اینجاست که مهاجرت یک‌باره یا Big Bang Migration مطرح می‌شه.حرکتی جسورانه که در اون، سیستم قدیمی به‌طور کامل با نسخه‌ی جدید جایگزین می‌شه — در یک لانچ واحد.ریسکیه؟ قطعاً.ولی گاهی تنها راه واقعیه.در این مقاله تجربه‌ی واقعی ما از بازنویسی بک‌اند از Node.js به Go رو توضیح می‌دم؛ مسیری پرچالش اما موفق.⚡ مهاجرت Big Bang چیست؟در این مدل، سیستم فعلی رو یک‌جا و کامل با سیستم جدید جایگزین می‌کنی.هیچ اجرای همزمانی بین قدیم و جدید وجود نداره.همه‌چیز طراحی، پیاده‌سازی و تست می‌شه — و در یک لحظه سوئیچ می‌کنی.مثل خراب‌کردن یه خونه و ساخت دوباره‌ی اون روی همون زمین.🧠 چرا ما Big Bang رو انتخاب کردیم؟بک‌اند ما با Node.js شروع شده بود، اما طی ۴ سال به هیولایی ۲۰۰ هزار خطی تبدیل شد:حجم زیاد منطق CPU-bound (آنالیز، پردازش ویدیو)مشکلات مقیاس‌پذیری در ترافیک بالالیک حافظه و باگ‌های asyncعملکرد ناپایدار در بار زیادبهینه‌سازی‌ها جواب نمی‌دادن.به Go نگاه کردیم — زبان سریع‌تر، همزمانی ذاتی (goroutine) و مدیریت حافظه‌ی بهتر.اما مهاجرت تدریجی ممکن نبود؛ دو محیط کاملاً متفاوت بودن.پس تصمیم گرفتیم همه‌چیز رو از اول بسازیم.🧭 برنامه‌ریزی مهاجرتکلید موفقیت در Big Bang، برنامه‌ریزی دقیقه.1. توقف توسعه فیچر جدیدبرای دو ماه هیچ فیچر تازه‌ای اضافه نکردیم تا سیستم فعلی پایدار بمونه.2. مستندسازی کاملتمام APIها، مدل‌های داده، و وظایف پس‌زمینه رو مستند کردیم تا قرارداد رفتاری نسخه‌ی جدید مشخص باشه.3. همسان‌سازی لایه‌ی APIدر نسخه‌ی Go، تمام endpointها دقیقاً مثل Node طراحی شدن تا فرانت‌اند یا اپ موبایل نیازی به تغییر نداشته باشه.4. تست در محیط جداسرویس Go رو در محیط staging بالا آوردیم، با دیتابیس کپی‌شده از production.هر روز با داده‌ی واقعی تست فشار (load test) می‌گرفتیم.5. شبیه‌سازی ترافیکقبل از لانچ نهایی، یه هفته درخواست‌های واقعی کاربرها رو shadow کردیم تا رفتار سیستم رو در شرایط واقعی بسنجیم.6. روز مهاجرتدر ساعت ۲ بامداد، با تغییر تنظیمات Load Balancer، ترافیک از Node به Go منتقل شد:service backend set-target go-app
در عرض ۳۰ ثانیه همه‌ی درخواست‌ها به سرویس جدید رسید.7. طرح بازگشت (Rollback)اگر مشکلی پیش می‌اومد، با یک تغییر ساده می‌تونستیم ترافیک رو به Node برگردونیم — بدون تغییر در دیتابیس.⚙️ نکات فنیاستفاده از Go Fiber به‌عنوان وب‌فریم‌ورک سریع و سبکGORM برای ORM با PostgreSQLاشتراک JWT tokens با فرانت‌اند (بدون تغییر در auth)پردازش وظایف با goroutine و Redis Streamمانیتورینگ با Prometheus + Grafanaنمونه‌ای ساده از ساختار Go:app := fiber.New()

app.Get(&quot;/api/users/:id&quot;, getUserHandler)
app.Post(&quot;/api/upload&quot;, uploadHandler)
app.Post(&quot;/api/aggregate&quot;, aggregateHandler)

log.Fatal(app.Listen(&quot;:8080&quot;))
همه‌ی کارهایی که قبلاً چند پروسه‌ی Node لازم داشت، حالا با goroutineها انجام می‌شد.⏱ کل فرآیند ۳ ماه توسعه و فقط یک شب انتشار طول کشید.بدون هیچ قطعی جدی — فقط یک بازه‌ی کوتاه فقط‌خواندنی هنگام سینک دیتابیس.⚠️ درس‌های آموخته‌شده۱. Big Bang یعنی ریسک بزرگ.یک باگ می‌تونه کل سیستم رو از کار بندازه. تست و مانیتورینگ حیاتی‌ان.۲. محیط موازی (Shadow Testing) نجات‌بخشه.قبل از لانچ، ترافیک واقعی رو به‌صورت مخفی به Go فرستادیم تا باگ‌ها زودتر شناسایی شن.۳. ارتباط تیمی حیاتی‌ه.از dev تا پشتیبانی، همه از برنامه‌ی مهاجرت خبر داشتن — هیچ شوکی در سازمان پیش نیومد.۴. قراردادهای API رو ثابت نگه دار.بازنویسی به معنی تغییر رفتار نیست. تطابق APIها باعث شد کاربران هیچ تغییری حس نکنن.🧭 چه زمانی Big Bang انتخاب مناسبه؟مناسب زمانی که...نامناسب زمانی که...سیستم فعلی غیرقابل نگه‌داریههنوز می‌شه بخش‌به‌بخش ریفکتور کرددو محیط فنی قابل همزیستی نیستنمی‌تونی قدیم و جدید رو کنار هم اجرا کنیتست پوشش کامل داریتست و staging نداریمی‌تونی کمی downtime بپذیریسرویس ۲۴/۷ فعاله🏁 جمع‌بندیمهاجرت یک‌باره شرط‌بندی بزرگیه — ولی اگه درست برنامه‌ریزی بشه، می‌تونه همه‌چیز رو متحول کنه.ما با این بازنویسی از Node به Go تونستیم:سرعت سیستم رو دو برابر کنیممصرف منابع رو نصف کنیمو بک‌اند تمیز و مقیاس‌پذیری بسازیمگاهی برای ساخت آینده، باید گذشته رو کامل کنار بذاری.</description>
                <category>Navid Barsalari</category>
                <author>Navid Barsalari</author>
                <pubDate>Sat, 18 Oct 2025 12:40:49 +0330</pubDate>
            </item>
                    <item>
                <title>🚀 مهاجرت تدریجی (Incremental Migration): تکامل بدون شکستن تولید</title>
                <link>https://virgool.io/@navidbarsalari/%F0%9F%9A%80-%D9%85%D9%87%D8%A7%D8%AC%D8%B1%D8%AA-%D8%AA%D8%AF%D8%B1%DB%8C%D8%AC%DB%8C-incremental-migration-%D8%AA%DA%A9%D8%A7%D9%85%D9%84-%D8%A8%D8%AF%D9%88%D9%86-%D8%B4%DA%A9%D8%B3%D8%AA%D9%86-%D8%AA%D9%88%D9%84%DB%8C%D8%AF-c7i5juylwvtf</link>
                <description>مقدمهبازسازی یک سیستم در حال اجرا مثل عوض کردن موتور هواپیما وسط پروازه — باید مدرن‌سازی انجام بدی در حالی که سیستم همچنان کار می‌کنه.اینجاست که مفهوم مهاجرت تدریجی (Incremental Migration) وارد می‌شه.به‌جای اینکه کل پروژه رو از نو بسازی، سیستم رو مرحله‌به‌مرحله مهاجرت می‌دی و در هر گام، پایداری رو حفظ می‌کنی.این استراتژی مخصوصاً وقتی جواب می‌ده که بخوای از یه فریم‌ورک مدرن به یه فریم‌ورک مدرن‌تر بری، مثلاً:مهاجرت از Nuxt (Vue) به Next.js (React)یا جداسازی یک Next.js monolith به React frontend + NestJS backendدر این مقاله، یاد می‌گیری چطور می‌تونی یه پروژه‌ی فعال در بازار رو بدون توقف، ریفکتور و مدرن‌سازی کنی.مهاجرت تدریجی چیست؟در مهاجرت تدریجی، سیستم رو در چند فاز کوچک جابه‌جا می‌کنی.به‌جای یک بازنویسی بزرگ، بین سیستم قدیمی و جدید پل می‌سازی تا مدتی کنار هم کار کنن.مثل وقتی که داری خونه‌ت رو بازسازی می‌کنی ولی هنوز توش زندگی می‌کنی — یه اتاق رو تعمیر می‌کنی، بعد میری سراغ بعدی.چرا مهاجرت تدریجی؟✅ بدون زمان ازکارافتادگی (Downtime) — کاربران همچنان از سیستم استفاده می‌کنن.✅ ریسک پایین‌تر — هر مرحله کوچیک و قابل‌آزمایش هست.✅ یادگیری تدریجی تیم — اعضا کم‌کم با تکنولوژی جدید آشنا می‌شن.✅ تحویل مداوم (Continuous Delivery) — هر فاز می‌تونه مستقل منتشر بشه.🧠 سناریو ۱: مهاجرت از Nuxt به Next.jsفرض کن یه داشبورد SaaS داری که با Nuxt 2 ساخته شده و هزاران کاربر داره.تیمت می‌خواد به Next.js مهاجرت کنه چون:پشتیبانی از TypeScript بهتره،ابزارهای اکوسیستم React قوی‌ترن،و SSR سریع‌تره.اما نمی‌تونی سرویس رو متوقف کنی. پس چطور انجامش بدی؟گام‌به‌گام مهاجرت1. تعیین مرزهاابتدا بخش‌های مختلف اپ رو شناسایی کن (مثل Dashboard، Profile، Admin Panel).یک قسمت ایزوله مثل Dashboard رو برای شروع انتخاب کن.2. ساخت پروژه‌ی Next.js جدیددر کنار کد Nuxt فعلی، یه پروژه‌ی Next.js جدید بساز.فعلاً هر دو کنار هم وجود دارن.3. تنظیم مسیرها با پراکسیبا استفاده از Nginx یا Gateway می‌تونی درخواست‌ها رو بین دو اپ تقسیم کنی:مسیر /dashboard → به Next.jsبقیه مسیرها → به Nuxtکاربرها هیچ تفاوتی حس نمی‌کنن.4. اشتراک سشن و طراحیتوکن‌های احراز هویت (JWT) یا کوکی‌ها رو مشترک کن تا کاربر بدون لاگین مجدد بین دو سیستم جابه‌جا شه.همچنین CSS و تم رو در یه پکیج مشترک بذار تا ظاهر ثابت بمونه.5. مهاجرت مرحله‌ایبه‌مرور صفحات، کامپوننت‌ها و APIها رو منتقل کن.وقتی همه بخش‌ها به Next منتقل شدن، پروژه‌ی Nuxt رو حذف کن — بدون حتی یک لحظه downtime.⚙️ سناریو ۲: مهاجرت از Node.js به Goبک‌اند سیستم با Node.js نوشته شده و با گذر زمان کند و سنگین شده.تیم تصمیم گرفته قسمت‌های CPU-محور (مثل پردازش تصویر و آنالیز داده‌ها) رو به Go منتقل کنه.اما نمی‌خوای بک‌اند رو از پایه بازنویسی کنی.مراحل انجام1. شناسایی نقاط گلوگاهبا پروفایلینگ، دو بخش پرمصرف پیدا کردیم:تحلیل داده‌هاپردازش تصویر2. ساخت سرویس جانبی در Goبه‌جای جایگزینی کامل، یه سرویس Go کنار Node ساختیم.Node همچنان API اصلی رو داشت ولی وظایف سنگین رو به Go می‌سپرد:app.post(&#039;/api/aggregate&#039;, async (req, res) =&gt; {
  const result = await fetch(&#039;http://go-service:8080/aggregate&#039;, {
    method: &#039;POST&#039;,
    body: JSON.stringify(req.body)
  });
  res.json(await result.json());
});3. استخراج تدریجیبه‌مرور کارهای دیگر مثل Queue و Jobها رو هم به Go منتقل کردیم تا در نهایت Node فقط دروازه (Gateway) باقی موند.📈 نتیجه نهاییبعد از سه ماه:هیچ قطعی نداشتیم، عملکرد بهبود پیدا کرد، و تیم همزمان فیچرهای جدید رو توسعه داد.🧭 نکات کلیدیمهاجرت تدریجی سریع نیست، ولی ایمنه.از پراکسی یا API Gateway برای همزیستی دو سیستم استفاده کن.Auth و داده مشترک حیاتی‌ان.هر گام کوچیک، یه پیروزی بزرگه.در کل:مهاجرت تدریجی یعنی حرکت بدون شکستن چیزها.</description>
                <category>Navid Barsalari</category>
                <author>Navid Barsalari</author>
                <pubDate>Sat, 18 Oct 2025 10:47:47 +0330</pubDate>
            </item>
                    <item>
                <title>آموزش ES6  مبحث Var</title>
                <link>https://virgool.io/@navidbarsalari/%D8%A2%D9%85%D9%88%D8%B2%D8%B4-es6-%D9%85%D8%A8%D8%AD%D8%AB-var-uv8oupqtjzed</link>
                <description>آموزش فارسی جاوا اسکریپت (es6) مبحث متغیر،در این ویدیو به بررسی دقیق Var  از سطح مقدماتی تا پیشرفته می پردازیم. تمام  موضوعات مورد نیاز در پروژه و مصاحبه های فنی را بررسی می کنیم:لینک ویدیو در کانال یوتیوب : https://youtu.be/VTbnwD88GrQhttps://bit.ly/30W8qjb لینک پلی لیست1.معرفی متغیر در جاوا اسکریپت2.چرا var را بررسی می کنیم؟3.خصوصیات متغیر از نوع var4.تعریف متغییر و اولین مثال5.  var scoping (function scope , global scope)6.اولین مثال در block scope7. دومین مثال block scope8. سومین مثال  block scope9.var hoistingبرای دیدن ویدیو ها بیشتر کانال ما را سابسکرایب کنید</description>
                <category>Navid Barsalari</category>
                <author>Navid Barsalari</author>
                <pubDate>Sat, 20 Jun 2020 17:03:15 +0430</pubDate>
            </item>
                    <item>
                <title>وقتی مصاحبه کننده می پرسد &quot;آیا سوالی دارید؟&quot;</title>
                <link>https://virgool.io/@navidbarsalari/%D9%88%D9%82%D8%AA%DB%8C-%D9%85%D8%B5%D8%A7%D8%AD%D8%A8%D9%87-%DA%A9%D9%86%D9%86%D8%AF%D9%87-%D9%85%DB%8C-%D9%BE%D8%B1%D8%B3%D8%AF-%D8%A2%DB%8C%D8%A7-%D8%B3%D9%88%D8%A7%D9%84%DB%8C-%D8%AF%D8%A7%D8%B1%DB%8C%D8%AF-otknboxhrfiw</link>
                <description>این پایان مصاحبه نیست.در واقع اگر مصاحبه کننده درباره استخدام شما مطمئن نباشد یا نظر مساعدی در مورد شما ندارد، این آخرین شانس شما برای تغییر نظر مصاحبه کننده است.سؤالاتی را مطرح کنید که 1) مختص شرکت است، 2) با نقش شما مطابقت دارد و 3) نشان می‌دهد که شما علاقه مند هستید.سوالات بد:چه آموزش‌هایی به کارمندان داده می‌شود؟سوال بهتر:&quot;چه اهدافی در این نقش (role) برای من در نظر گرفته شده؟&quot;اگر آموزش برای شما مهم است، می‌توانید ادامه دهید: &quot;و چه آموزش‌هایی برای کمک به من در دستیابی به این اهداف برای شرکت وجود خواهد داشت؟&quot;آدرس کانال یوتیوب و صفحه گیت هاب برای دیدن ویدیو و مقالات بیشتر در زمینه برنامه نویسی.</description>
                <category>Navid Barsalari</category>
                <author>Navid Barsalari</author>
                <pubDate>Mon, 25 May 2020 09:52:01 +0430</pubDate>
            </item>
                    <item>
                <title>وقتی گوگل ما رو بچه 5 ساله فرض میکنه!</title>
                <link>https://virgool.io/@navidbarsalari/%D9%88%D9%82%D8%AA%DB%8C-%DA%AF%D9%88%DA%AF%D9%84-%D9%85%D8%A7-%D8%B1%D9%88-%D8%A8%DA%86%D9%87-5-%D8%B3%D8%A7%D9%84%D9%87-%D9%81%D8%B1%D8%B6-%D9%85%DB%8C%DA%A9%D9%86%D9%87-fwlnb1bqq210</link>
                <description>وقتی گوگل ما رو بچه 5 ساله فرض میکنه!اگر به یک مقاله پیچیده در گوگل برخوردید که فهمش براتون سخت بود ...گوگل راه حلی برای این مسئله داره. به این صورت که قبل واژه ای که میخواید سرچ کنید، بنویسید eli5عبارت eli5 یعنی explain like im five(در واقع از گوگل در خواست می‌کنید که جوری توضیح بده که انگار 5 سالمه! )انجمن‌هایی مانند ردیت، مباحث پیچیده رو به زبان ساده توضیح میدنلینک یه سری از انجمن و سایت‌های که از eli5 استفاده می‌کنند:https://eli5.readthedocs.io/https://www.reddit.com/r/explainlikeimfive/https://pypi.org/project/eli5/آدرس کانال یوتیوب و صفحه گیت هاب برای دیدن ویدیو و مقالات بیشتر در زمینه برنامه نویسی.</description>
                <category>Navid Barsalari</category>
                <author>Navid Barsalari</author>
                <pubDate>Wed, 20 May 2020 01:02:11 +0430</pubDate>
            </item>
            </channel>
</rss>