نوشتن برنامه در جاوا اسکریپت در ابتدا قابل دسترسی است. زبان بخشنده است و شما به توانمندی های آن عادت می کنید. با گذشت زمان و تجربه کار بر روی پروژه های پیچیده، شما شروع به درک چیزهایی مانند کنترل و دقت در جریان توسعه می کنید.

یکی دیگر از چیزهایی که ممکن است شروع به قدردانی کنید، قابل پیش بینی بودن است، اما این تضمین در جاوا اسکریپت بسیار کمتر است. در حالی که مقادیر اولیه به اندازه کافی پیش بینی کننده هستند، اشیا اینطور نیستند. هنگامی که یک شی را به عنوان ورودی دریافت می کنید، باید همه چیز را بررسی کنید:

  • آیا این یک شی است؟
  • آیا آن ملک مورد نظر شما را دارد؟
  • وقتی یک ملک نگه می دارد undefinedآیا ارزش آن همین است یا خود ملک مفقود شده است؟

قابل درک است اگر این سطح از عدم قطعیت شما را کمی پارانویا کند به این معنا که شروع به زیر سوال بردن همه انتخاب های خود کنید. پس از آن، کد شما دفاعی می شود. شما بیشتر به این فکر می کنید که آیا تمام موارد معیوب را مدیریت کرده اید یا نه (احتمالاً این کار را نکرده اید). و در پایان، برنامه شما بیشتر مجموعه ای از چک است تا ارزش واقعی برای پروژه.

با ساخت اشیا اولیه، بسیاری از نقاط شکست بالقوه به یک مکان منتقل می شوند – جایی که اشیا در آن مقداردهی اولیه می شوند. اگر می توانید مطمئن شوید که اشیاء شما با مجموعه خاصی از ویژگی ها مقداردهی اولیه شده اند و آن ویژگی ها دارای مقادیر خاصی هستند، لازم نیست مواردی مانند وجود ویژگی ها را در هر جای دیگری از برنامه خود بررسی کنید. شما می توانید آن را تضمین کنید undefined در صورت نیاز یک ارزش است.

بیایید به یکی از راه هایی که می توانیم اشیاء اولیه بسازیم نگاه کنیم. این تنها راه یا حتی جالب ترین راه نیست. بلکه هدف آن نشان دادن این است که کار با اشیاء فقط خواندنی نباید دست و پا گیر یا دشوار باشد.

توجه داشته باشید: همچنین به شما توصیه می‌کنم قسمت اول مجموعه را بررسی کنید، جایی که برخی از جنبه‌های جاوا اسکریپت را پوشش دادم که به نزدیک‌تر کردن اشیا به مقادیر اولیه کمک می‌کند، که در عوض به ما امکان می‌دهد از ویژگی‌های زبان رایج که معمولاً با یک شی مرتبط نیستند، بهره ببریم. مانند مقایسه و عملگرهای حسابی.

ساخت اجسام اولیه به صورت عمده

ساده ترین، بیشتر اولیه راه (جناسی مورد نظر) برای ایجاد یک شی بدوی به شرح زیر است:

const my_object = Object.freeze({});

این خط منفرد منجر به یک شی می شود که می تواند هر چیزی را نشان دهد. برای مثال، می‌توانید یک رابط زبانه‌دار را با استفاده از یک شی خالی برای هر برگه پیاده‌سازی کنید.

import React, { useState } from "react";

const summary_tab = Object.freeze({});
const details_tab = Object.freeze({});

function TabbedContainer({ summary_children, details_children }) {
    const [ active, setActive ] = useState(summary_tab);
  
    return (
        <div className="tabbed-container">
            <div className="tabs">
                <label
                    className={active === summary_tab ? "active" : ""}
                    onClick={() => {
                        setActive(summary_tab);
                    }}
                >
                    Summary
                </label>
                <label
                    className={active === details_tab ? "active": ""}
                    onClick={() => {
                        setActive(details_tab);
                    }}
                >
                    Details
                </label>
            </div>
            <div className="tabbed-content">
                {active === summary_tab && summary_children}
                {active === details_tab && details_children}
            </div>
        </div>
    );
}

export default TabbedContainer;

اگه مثل من هستی همین tabs عنصر فقط فریاد می زند تا دوباره کار شود. با نگاهی دقیق متوجه خواهید شد که عناصر برگه شبیه به هم هستند و به دو چیز مانند یک نیاز دارند مرجع شی و الف رشته برچسب. بیایید شامل label ملک در tabs اشیاء و خود اشیاء را به یک آرایه منتقل می کنند. و از آنجایی که ما قصد تغییر نداریم tabs به هر ترتیب، اجازه دهید تا زمانی که در آن هستیم، آن آرایه را فقط خواندنی کنیم.

const tab_kinds = Object.freeze([
    Object.freeze({ label: "Summary" }),
    Object.freeze({ label: "Details" })
]);

این چیزی است که ما نیاز داریم، اما پرمخاطب است. رویکردی که اکنون به آن نگاه خواهیم کرد اغلب برای پنهان کردن عملیات تکراری برای کاهش کد به داده ها استفاده می شود. به این ترتیب، زمانی که داده ها نادرست هستند، آشکارتر می شود. آنچه ما نیز می خواهیم این است که freeze اشیاء (از جمله آرایه) به طور پیش فرض به جای اینکه چیزی باشد که باید آن را به خاطر بسپاریم تا تایپ کنیم. به همین دلیل، این واقعیت که ما باید هر بار یک نام ویژگی را مشخص کنیم، جایی برای خطاهایی مانند اشتباهات تایپی باز می کند.

برای اینکه آرایه های اشیاء اولیه را به راحتی و به طور مداوم مقداردهی اولیه کنم، از a استفاده می کنم populate تابع. من در واقع یک عملکرد واحد ندارم که این کار را انجام دهد. من معمولاً هر بار بر اساس آنچه در لحظه نیاز دارم، یکی را ایجاد می کنم. در مورد خاص این مقاله، این یکی از موارد ساده تر است. در اینجا نحوه انجام آن آمده است:

function populate(...names) {
    return function(...elements) {
        return Object.freeze(
            elements.map(function (values) {
                return Object.freeze(names.reduce(
                    function (result, name, index) {
                        result[name] = values[index];
                        return result;
                    },
                    Object.create(null)
                ));
            })
        );
    };
}

اگر آن یک احساس متراکم است، در اینجا یکی است که قابل خواندن تر است:

function populate(...names) {
    return function(...elements) {
        const objects = [];

        elements.forEach(function (values) {
            const object = Object.create(null);

            names.forEach(function (name, index) {
                object[name] = values[index];
            });

            objects.push(Object.freeze(object));
        });

        return Object.freeze(objects);
    };
}

با این نوع تابع در دست، می‌توانیم همان آرایه از اشیاء زبانه‌دار را ایجاد کنیم:

const tab_kinds = populate(
    "label"
)(
    [ "Summary" ],
    [ "Details" ]
);

هر آرایه در فراخوانی دوم مقادیر اشیاء به دست آمده را نشان می دهد. حالا فرض کنید می خواهیم خواص بیشتری اضافه کنیم. ما باید یک نام جدید به فراخوانی اول و یک مقدار برای هر آرایه در تماس دوم اضافه کنیم.

const tab_kinds = populate(
    "label",
    "color",
    "icon"
)(                                          
    [ "Summary", colors.midnight_pink, "💡" ],
    [ "Details", colors.navi_white, "🔬" ]
);

با توجه به فضای خالی، می توانید آن را شبیه یک جدول کنید. به این ترتیب، تشخیص خطا در تعاریف بزرگ بسیار آسان تر است.

شاید متوجه شده باشید populate تابع دیگری را برمی گرداند. چند دلیل برای حفظ آن در دو فراخوانی تابع وجود دارد. اول، من دوست دارم که چگونه دو تماس به هم پیوسته یک خط خالی ایجاد می کنند که کلیدها و مقادیر را از هم جدا می کند. دوم، من دوست دارم بتوانم این نوع ژنراتورها را برای اشیاء مشابه ایجاد کنم. به عنوان مثال، فرض کنید که باید آن اشیاء برچسب را برای اجزای مختلف ایجاد کنیم و می خواهیم آنها را در آرایه های مختلف ذخیره کنیم.

بیایید به مثال برگردیم و ببینیم که با آن چه به دست آوردیم populate تابع:

import React, { useState } from "react";
import populate_label from "./populate_label";

const tabs = populate_label(
    [ "Summary" ],
    [ "Details" ]
);

const [ summary_tab, details_tab ] = tabs;

function TabbedContainer({ summary_children, details_children }) {
    const [ active, setActive ] = useState(summary_tab);
      
    return (
        <div className="tabbed-container">
            <div className="tabs">
                {tabs.map((tab) => (
                    <label
                        key={tab.label}
                        className={tab === active ? "active" : ""}
                        onClick={() => {
                            setActive(tab);
                        }}
                    >
                        {tab.label}
                    </label>
                )}
            </div>
            <div className="tabbed-content">
                {summary_tab === active && summary_children}
                {details_tab === active && details_children}
            </div>
        </div>
    );
}

export default TabbedContainer;

استفاده از اشیاء اولیه نوشتن منطق UI را ساده می کند.

با استفاده از توابعی مانند populate برای ایجاد این اشیا و دیدن اینکه داده ها چگونه به نظر می رسند، دست و پا گیر کمتری است.

بیشتر بعد از پرش! ادامه مطلب زیر ↓

آن رادیو را بررسی کنید

یکی از گزینه های جایگزین برای رویکرد بالا که من با آن مواجه شدم، حفظ آن است active حالت – آیا برگه انتخاب شده است یا نه – به عنوان یک ویژگی از ذخیره می شود tabs هدف – شی:

const tabs = [
    {
        label: "Summary",
        selected: true
    },
    {
        label: "Details",
        selected: false
    },
];

به این ترتیب جایگزین می کنیم tab === active با tab.selected. این ممکن است به نظر یک پیشرفت باشد، اما نگاه کنید که چگونه باید برگه انتخاب شده را تغییر دهیم:

function select_tab(tab, tabs) {
    tabs.forEach((tab) => tab.selected = false);
    tab.selected = true;
}

از آنجا که این منطق برای یک دکمه رادیویی است، تنها یک عنصر را می توان در یک زمان انتخاب کرد. بنابراین، قبل از تنظیم یک عنصر برای انتخاب، ابتدا باید مطمئن شویم که همه عناصر دیگر انتخاب نشده اند. بله، احمقانه است که این کار را برای آرایه ای با تنها دو عنصر انجام دهید، اما دنیای واقعی پر از لیست های طولانی تر از این مثال است.

با یک شی بدوی، ما نیاز داریم یک متغیر واحد که وضعیت انتخاب شده را نشان می دهد. پیشنهاد می‌کنم متغیر را روی یکی از عناصر تنظیم کنید تا آن را به عنصر انتخاب‌شده فعلی تبدیل کنید یا آن را روی آن تنظیم کنید undefined اگر پیاده سازی شما اجازه هیچ انتخابی را نمی دهد.

با عناصر چند گزینه ای مانند چک باکس، رویکرد تقریبا یکسان است. متغیر انتخاب را با یک آرایه جایگزین می کنیم. هر بار که یک عنصر انتخاب می شود، آن را به آن آرایه یا در مورد Redux، یک آرایه جدید با وجود آن عنصر ایجاد می کنیم. برای لغو انتخاب آن، یا آن را به هم متصل می کنیم یا عنصر را فیلتر می کنیم.

let selected = []; // Nothing is selected.

// Select.
selected = selected.concat([ to_be_selected ]);

// Unselect.
selected = selected.filter((element) => element !== to_be_unselected);

// Check if an element is selected.
selected.includes(element);

باز هم، این ساده و مختصر است. لازم نیست به یاد داشته باشید که آیا ملک نامیده می شود selected یا active; شما از خود شی برای تعیین آن استفاده می کنید. هنگامی که برنامه شما پیچیده تر می شود، آن خطوط کمترین احتمال را خواهند داشت که دوباره ساخته شوند.

در پایان، این وظیفه یک عنصر لیست نیست که تصمیم بگیرد که آیا انتخاب شده است یا خیر. نباید این اطلاعات را در حالت خود نگه دارد. به عنوان مثال، اگر به طور همزمان انتخاب شود و در یک زمان در چندین لیست انتخاب نشده باشد، چه؟

جایگزینی برای رشته ها

آخرین چیزی که می خواهم به آن دست بزنم نمونه ای از استفاده از رشته است که اغلب با آن مواجه می شوم.

متن یک مبادله خوب برای قابلیت همکاری است. شما چیزی را به عنوان یک رشته تعریف می کنید و فوراً نمایشی از یک زمینه را دریافت می کنید. مثل این است که با خوردن شکر، انرژی فوری دریافت کنید. مانند شکر، بهترین حالت این است که در درازمدت چیزی دریافت نکنید. گفته می‌شود که برآورده نمی‌شود و شما ناگزیر دوباره گرسنه می‌شوید.

مشکل رشته ها این است که آنها برای انسان هستند. طبیعی است که ما چیزها را با نامگذاری از یکدیگر متمایز کنیم. اما یک برنامه معنای آن نام ها را نمی فهمد.

اکثر ویرایشگرهای کد و محیط های توسعه یکپارچه (IDE) رشته ها را درک نمی کنند. به عبارت دیگر، ابزارهای شما به شما نمی گویند که آیا رشته درست است یا نه.

برنامه شما فقط می داند که آیا دو رشته هستند یا خیر برابر یا نه. و حتی پس از آن، گفتن اینکه آیا رشته‌ها برابر یا نابرابر هستند، لزوماً بینشی در مورد اینکه آیا هر یک از آن رشته‌ها دارای اشتباه تایپی هستند یا خیر، ارائه نمی‌کند.

قبل از اجرای برنامه، اشیاء راه های بیشتری برای مشاهده اینکه مشکلی وجود دارد ارائه می دهند. از آنجایی که نمی‌توانید برای اشیاء ابتدایی لفظی داشته باشید، باید از جایی مرجع دریافت کنید. به عنوان مثال، اگر یک متغیر باشد و اشتباه تایپی داشته باشید، یک عدد دریافت می کنید خطای مرجع. ابزارهایی وجود دارند که می توانند این نوع چیزها را قبل از ذخیره شدن فایل دریافت کنند.

اگر قرار بود اشیاء خود را از یک آرایه یا یک شی دیگر دریافت کنید، جاوا اسکریپت زمانی که ویژگی یا شاخصی وجود ندارد به شما خطایی نمی دهد. چیزی که به دست می آورید همین است undefined، و این چیزی است که می توانید آن را بررسی کنید. شما باید یک چیز را بررسی کنید. با استفاده از رشته ها، شگفتی هایی دارید که ممکن است بخواهید از آنها اجتناب کنید، مانند زمانی که آنها خالی هستند.

یکی دیگر از استفاده‌های رشته‌ها که من سعی می‌کنم از آن اجتناب کنم، بررسی این است که آیا به شی مورد نظر خود می‌رسیم یا خیر. معمولاً این کار با ذخیره یک رشته در یک ویژگی به نام انجام می شود id. مثلاً فرض کنید یک متغیر داریم. برای بررسی اینکه آیا شیء مورد نظر ما را در خود نگه می دارد یا خیر، ممکن است نیاز داشته باشیم که آیا رشته ای در آن وجود دارد یا خیر id دارایی با چیزی که انتظار داریم مطابقت دارد. برای انجام این کار، ابتدا بررسی می کنیم که آیا متغیر دارای یک شی است یا خیر. اگر متغیر یک شی را نگه می دارد، اما شی فاقد آن است id اموال، سپس ما می گیریم undefined، و ما خوب هستیم. با این حال، اگر یکی از مقادیر پایین آن متغیر را داشته باشیم، نمی‌توانیم مستقیماً ویژگی را بخواهیم. در عوض، باید کاری انجام دهیم که یا مطمئن شویم که فقط اشیا به این نقطه می رسند یا هر دو بررسی را در جای خود انجام دهیم.

const myID = "Oh, it's so unique";

function magnification(value) {
    if (value && typeof value === "object" && value.id === myID) {
        // do magic
    }
}

در اینجا نحوه انجام همین کار با اشیاء اولیه آورده شده است:

import data from "./the file where data is stored";

function magnification(value) {
    if (value === data.myObject) {
        // do magic
    }
}

مزیت رشته ها این است که آنها یک چیز واحد هستند که می توانند برای شناسایی داخلی استفاده شوند و بلافاصله در گزارش ها قابل تشخیص هستند. مطمئناً استفاده از آنها آسان است، اما با افزایش پیچیدگی پروژه، دوست شما نیستند.

پیدا می کنم سود کمی در اتکا به رشته ها برای هر چیزی غیر از خروجی برای کاربر وجود دارد. عدم قابلیت همکاری رشته‌ها در اشیاء اولیه را می‌توان به تدریج و بدون نیاز به تغییر نحوه مدیریت عملیات‌های اساسی، مانند مقایسه، حل کرد.

بسته بندی

کار مستقیم با اشیاء ما را از دام های ناشی از روش های دیگر رها می کند. کد ما ساده تر می شود زیرا آنچه برنامه شما باید انجام دهد را می نویسیم. با سازماندهی کد خود با اشیاء اولیه، کمتر تحت تأثیر ماهیت پویا جاوا اسکریپت و برخی از چمدان های آن قرار می گیریم. اشیاء اولیه به ما تضمین های بیشتر و درجه ای از پیش بینی پذیری بیشتری می دهند.

مطالعه بیشتر در SmashingMag

سرمقاله Smashing
(gg, yk, il)