אובייקטים הם היחידה העיקרית של אנקפסולציה בתכנות מונחה עצמים. במאמר זה אתאר מספר דרכים לבניית אובייקטים ב- JavaScript. הם:
- חפץ מילולי
- Object.create ()
- שיעורים
- פונקציות במפעל
חפץ מילולי
ראשית, עלינו להבחין בין מבני נתונים לאובייקטים מונחי עצמים. למבני נתונים יש נתונים ציבוריים וללא התנהגות. כלומר אין להם שיטות.
אנו יכולים ליצור אובייקטים כאלה בקלות באמצעות התחביר המילולי של האובייקט. זה נראה כמו זה:
const product = { name: 'apple', category: 'fruits', price: 1.99 } console.log(product);
אובייקטים ב- JavaScript הם אוספים דינמיים של צמדי ערך מפתח. המפתח הוא תמיד מחרוזת ועליו להיות ייחודי באוסף. הערך יכול פרימיטיבי, אובייקט, או אפילו פונקציה.
אנו יכולים לגשת לנכס באמצעות הנקודה או הסימון הריבועי.
console.log(product.name); //"apple" console.log(product["name"]); //"apple"
הנה דוגמה שבה הערך הוא אובייקט אחר.
const product = { name: 'apple', category: 'fruits', price: 1.99, nutrients : { carbs: 0.95, fats: 0.3, protein: 0.2 } }
ערך carbs
הנכס הוא אובייקט חדש. כך אנו יכולים לגשת carbs
לנכס.
console.log(product.nutrients.carbs); //0.95
שמות נכסים קצרים
שקול את המקרה שבו מאוחסנים ערכי המאפיינים שלנו במשתנים.
const name = 'apple'; const category = 'fruits'; const price = 1.99; const product = { name: name, category: category, price: price }
JavaScript תומך במה שמכונה שמות נכסים קצרים. זה מאפשר לנו ליצור אובייקט רק בעזרת שם המשתנה. זה ייצור נכס עם אותו שם. האובייקט הבא מילולי שווה ערך לקודם.
const name = 'apple'; const category = 'fruits'; const price = 1.99; const product = { name, category, price }
Object.create
לאחר מכן, בואו נסתכל כיצד ליישם אובייקטים עם אובייקטים התנהגותיים, מונחים עצמים.
ב- JavaScript יש מה שמכונה מערכת האב-טיפוס המאפשרת שיתוף התנהגות בין אובייקטים. הרעיון העיקרי הוא ליצור אובייקט שנקרא אב-טיפוס עם התנהגות נפוצה ואז להשתמש בו בעת יצירת אובייקטים חדשים.
מערכת האב-טיפוס מאפשרת לנו ליצור אובייקטים שעורשים התנהגות מאובייקטים אחרים.
בואו ניצור אובייקט אב טיפוס שמאפשר לנו להוסיף מוצרים ולקבל את המחיר הכולל מעגלת הקניות.
const cartPrototype = { addProduct: function(product){ if(!this.products){ this.products = [product] } else { this.products.push(product); } }, getTotalPrice: function(){ return this.products.reduce((total, p) => total + p.price, 0); } }
שימו לב שהפעם ערך הנכס addProduct
הוא פונקציה. אנו יכולים גם לכתוב את האובייקט הקודם באמצעות צורה קצרה יותר הנקראת תחביר שיטת הקיצור.
const cartPrototype = { addProduct(product){/*code*/}, getTotalPrice(){/*code*/} }
cartPrototype
הוא אובייקט אב הטיפוס שמחזיק את ההתנהגות הנפוצה המיוצגת על ידי שתי שיטות, addProduct
ו getTotalPrice
. ניתן להשתמש בו לבניית אובייקטים אחרים היורשים התנהגות זו.
const cart = Object.create(cartPrototype); cart.addProduct({name: 'orange', price: 1.25}); cart.addProduct({name: 'lemon', price: 1.75}); console.log(cart.getTotalPrice()); //3
cart
האובייקט יש cartPrototype
כמו אב הטיפוס שלו. זה יורש ממנה את ההתנהגות. cart
בעל תכונה נסתרת המצביעה על אובייקט האב-טיפוס.
כאשר אנו משתמשים בשיטה על אובייקט, שיטה זו מחפשת תחילה על האובייקט עצמו ולא על אב הטיפוס שלו.
זֶה
שים לב שאנחנו משתמשים במילת מפתח מיוחדת הנקראת this
לגישה ולשינוי הנתונים על האובייקט.
זכור כי פונקציות הן יחידות התנהגות עצמאיות ב- JavaScript. הם לא בהכרח חלק מאובייקט. כאשר הם כן, עלינו לקבל הפניה המאפשרת לפונקציה לגשת לחברים אחרים על אותו אובייקט. this
הוא הקשר הפונקציה. זה נותן גישה לנכסים אחרים.
נתונים
אתה עשוי לתהות מדוע לא הגדרנו ואותחלנו את products
המאפיין באובייקט האב טיפוס עצמו.
אנחנו לא צריכים לעשות את זה. יש להשתמש באבות טיפוס כדי לשתף התנהגות, ולא נתונים. שיתוף נתונים יוביל לכך שיהיו אותם מוצרים בכמה חפצי עגלה. שקול את הקוד שלהלן:
const cartPrototype = { products:[], addProduct: function(product){ this.products.push(product); }, getTotalPrice: function(){} } const cart1 = Object.create(cartPrototype); cart1.addProduct({name: 'orange', price: 1.25}); cart1.addProduct({name: 'lemon', price: 1.75}); console.log(cart1.getTotalPrice()); //3 const cart2 = Object.create(cartPrototype); console.log(cart2.getTotalPrice()); //3
הן cart1
ו cart2
אובייקטים לרשת את ההתנהגות הנפוצה מן cartPrototype
גם לשתף אותם הנתונים. אנחנו לא רוצים את זה. יש להשתמש באבות טיפוס כדי לשתף התנהגות, ולא נתונים.
מעמד
מערכת האב-טיפוס אינה דרך נפוצה לבניית עצמים. מפתחים מכירים יותר בניית חפצים מחוץ לשיעורים.
התחביר הכיתתי מאפשר דרך מוכרת יותר ליצור אובייקטים החולקים התנהגות משותפת. זה עדיין יוצר את אותו אב-טיפוס מאחורי הסצנה, אך התחביר ברור יותר ואנחנו גם נמנעים מהנושא הקודם שקשור לנתונים. הכיתה מציעה מקום ספציפי להגדרת הנתונים המיוחדים לכל אובייקט.
הנה אותו אובייקט שנוצר באמצעות תחביר הסוכר בכיתה:
class Cart{ constructor(){ this.products = []; } addProduct(product){ this.products.push(product); } getTotalPrice(){ return this.products.reduce((total, p) => total + p.price, 0); } } const cart = new Cart(); cart.addProduct({name: 'orange', price: 1.25}); cart.addProduct({name: 'lemon', price: 1.75}); console.log(cart.getTotalPrice()); //3 const cart2 = new Cart(); console.log(cart2.getTotalPrice()); //0
שימו לב כי בכיתה יש שיטת קונסטרוקטור שאותחלה את הנתונים המיוחדים לכל אובייקט חדש. הנתונים בבונה אינם משותפים בין מופעים. על מנת ליצור מופע חדש, אנו משתמשים new
במילת המפתח.
אני חושב שתחביר הכיתה ברור ומוכר יותר עבור רוב המפתחים. עם זאת, הוא עושה דבר דומה, הוא יוצר אב-טיפוס עם כל השיטות ומשתמש בו להגדרת אובייקטים חדשים. ניתן לגשת לאב-טיפוס באמצעות Cart.prototype
.
מתברר שמערכת האב-טיפוס גמישה מספיק כדי לאפשר תחביר הכיתה. כך שניתן לדמות את מערכת הכיתה באמצעות מערכת האב-טיפוס.
נכסים פרטיים
הדבר היחיד הוא products
שהנכס על האובייקט החדש הוא ציבורי כברירת מחדל.
console.log(cart.products); //[{name: "orange", price: 1.25} // {name: "lemon", price: 1.75}]
אנו יכולים להפוך אותו לפרטי באמצעות #
קידומת hash .
Private properties are declared with #name
syntax. #
is a part of the property name itself and should be used for declaring and accessing the property. Here is an example of declaring products
as a private property:
class Cart{ #products constructor(){ this.#products = []; } addProduct(product){ this.#products.push(product); } getTotalPrice(){ return this.#products.reduce((total, p) => total + p.price, 0); } } console.log(cart.#products); //Uncaught SyntaxError: Private field '#products' must be declared in an enclosing class
Factory Functions
Another option is to create objects as collections of closures.
Closure is the ability of a function to access variables and parameters from the other function even after the outer function has executed. Take a look at the cart
object built with what is called a factory function.
function Cart() { const products = []; function addProduct(product){ products.push(product); } function getTotalPrice(){ return products.reduce((total, p) => total + p.price, 0); } return { addProduct, getTotalPrice } } const cart = Cart(); cart.addProduct({name: 'orange', price: 1.25}); cart.addProduct({name: 'lemon', price: 1.75}); console.log(cart.getTotalPrice()); //3
addProduct
and getTotalPrice
are two inner functions accessing the variable products
from their parent. They have access to the products
variable event after the parent Cart
has executed. addProduct
and getTotalPrice
are two closures sharing the same private variable.
Cart
is a factory function.
The new object cart
created with the factory function has the products
variable private. It cannot be accessed from the outside.
console.log(cart.products); //undefined
Factory functions don’t need the new
keyword but you can use it if you want. It will return the same object no matter if you use it or not.
Recap
Usually, we work with two types of objects, data structures that have public data and no behavior and object-oriented objects that have private data and public behavior.
Data structures can be easily built using the object literal syntax.
JavaScript offers two innovative ways of creating object-oriented objects. The first is using a prototype object to share the common behavior. Objects inherit from other objects. Classes offer a nice sugar syntax to create such objects.
The other option is to define objects are collections of closures.
For more on closures and function programming techniques check out my book series Functional Programming with JavaScript and React.
The Functional Programming in JavaScript book is coming out.