Node.js: Mongoose ODM
آخر تحديث: 2026-08-26
Bob’s data validation code, written using the native MongoDB driver, was scattered across various routes—the registration endpoint checked email format, the publish-article endpoint validated タイトル length, and the change-password endpoint verified that the new and old passwords were different. Every time a new フィールド was added, he had to add a piece of if (!フィールド) logic to the corresponding controller, resulting in the same email validation regex being repeated across three routes. After switching to Mongoose, Bob centralized all validation rules in the schema definition—defining them once and applying them everywhere—which reduced the controller code by 60%.
ستتعلم:
- العلاقات واستخدامات البنية ثلاثية المستويات: المخطط، والنموذج، والوثيقة
- التعريفات التصريحية لأنواع الحقول وعوامل التحقق من الصحة (مطلوب / نوع قائمة / الحد الأدنى / الحد الأقصى / المطابقة)
- أنماط الحقول المحسوبة للسمة "virtual"
- اعتراض دورة حياة الروابط المسبقة واللاحقة
- المكالمات المتسلسلة في مُنشئ الاستعلامات
- الفهارس (index / فريدة) وتحسين الأداء
- امتدادات مخصصة لأساليب المثيلات والأساليب الثابتة
- استخدم
populateلتنفيذ استعلامات «join» للمراجع المشتركة بين المستندات
1. البنية الأساسية لـ Mongoose
Mongoose هي مكتبة لتعيين الكائنات إلى المستندات (ODM) مخصصة لـ MongoDB، توفر طبقة لنمذجة البيانات تعتمد على المخطط فوق برنامج التشغيل الأصلي. وينقسم المفهوم الأساسي إلى ثلاث طبقات: المخطط يحدد البنية → يتم ترجمة النموذج إلى مُنشئ → المستند هو مثيل للنموذج.
▶ مثال:(1) العلاقة بين المخطط → النموذج → المستند
graph LR
A["Schema<br/>Defining Structures and Verification"] -->|mongoose.model() Compilation| B["Model<br/>Constructor + Query Interface"]
B -->|new Model() Instantiation| C["Document<br/>Examples of Verified Documents"]
C -->|.save() Persistence| D[("MongoDB<br/>Gathering")]
B -->|Model.find() etc.| D
D -->|Back| C
(2) التركيب والتوصيل
▶ مثال: تثبيت Mongoose والاتصال بـ MongoDB
npm install mongoose
const mongoose = require('mongoose');
mongoose.connect('mongodb://localhost:27017/myapp')
.then(() => console.log('MongoDB connected'))
.catch(err => console.error('Connection error:', err));
(3) المخطط والنموذج البسيطان
▶ مثال: تعريف مخطط المستخدم وإنشاء نموذج
const userSchema = new mongoose.Schema({
name: String,
email: String,
age: Number
});
const User = mongoose.model('User', userSchema);
| المفهوم | الدور | التشبيه |
|---|---|---|
| المخطط | المخطط التفصيلي / التعريف الهيكلي | الرسومات المعمارية |
| النموذج | المُصمم + واجهة تشغيل قاعدة البيانات | فريق البناء |
| الوثيقة | أمثلة على الوثائق التي تم التحقق منها | المنازل المكتملة |
2. أنواع حقول المخطط
يوفر Mongoose تعيينات أنواع غنية لكل حقل، وهو ما يتجاوز بكثير النقص في قيود الأنواع الموجودة في برنامج التشغيل الأصلي.
(1) مرجع سريع لأنواع الحقول
| نوع Mongoose | النوع المقابل في JavaScript | مثال | الوصف |
|---|---|---|---|
String |
سلسلة | name: String |
القص التلقائي (يتطلب التهيئة) |
Number |
الرقم | age: Number |
يدعم الحد الأدنى / الحد الأقصى |
Boolean |
منطقية | active: Boolean |
يتم تحويلها تلقائيًا إلى 0/1/"true" |
Date |
التاريخ | createdAt: Date |
طرق التاريخ المدمجة |
ObjectId |
معرّف الكائن | author: mongoose.Schema.Types.ObjectId |
المراجع إلى الوثائق الأخرى |
Array |
مصفوفة | tags: [String] |
مصفوفة من المستندات الفرعية أو مصفوفة من الأنواع |
Mixed |
كائن | meta: mongoose.Schema.Types.Mixed |
أي نوع، بدون تحقق من الصحة |
Buffer |
المخزن المؤقت | avatar: Buffer |
البيانات الثنائية |
Map |
Map | prefs: { type: Map, of: String } |
ES6 Map structure |
Decimal128 |
Decimal128 | price: mongoose.Schema.Types.Decimal128 |
High-precision decimal |
(2) The complete syntax for field definitions
▶ Example: Detailed Explanation of Field Options
const productSchema = new mongoose.Schema({
name: {
type: String,
required: [true, 'The product name cannot be left blank.'],
trim: true,
minlength: 2,
maxlength: 100
},
price: {
type: Number,
required: true,
min: [0, 'Prices cannot be negative.'],
default: 0
},
category: {
type: String,
enum: ['electronics', 'books', 'clothing', 'food'],
lowercase: true
},
tags: [String],
metadata: {
type: mongoose.Schema.Types.Mixed,
default: {}
}
});
3. Validator
Validation is at the heart of Mongoose—it frees data validation from controllers and centralizes it in schema declarations.
(1) Validator Quick Reference
| Validator | Applicable Type | Description | Example |
|---|---|---|---|
required |
All | Required fields | required: [true, 'Cannot be empty'] |
enum |
String | Enum value constraint | enum: ['A', 'B', 'C'] |
min |
Number / Date | Minimum value | min: 0 |
max |
الرقم / التاريخ | القيمة القصوى | max: 150 |
minlength |
سلسلة | الحد الأدنى للطول | minlength: 6 |
maxlength |
سلسلة | الطول الأقصى | maxlength: 200 |
match |
سلسلة | مطابقة التعبير النمطي | match: [/^\S+@\S+\.\S+$/, 'Invalid email format'] |
validate |
الكل | وظائف التحقق المخصصة | validate: v => v > 0 |
(2) أدوات التحقق المخصصة
▶ مثال: أدوات التحقق المخصصة ورسائل الخطأ
const userSchema = new mongoose.Schema({
password: {
type: String,
required: true,
validate: {
validator: function(v) {
return /^(?=.*[A-Z])(?=.*\d).{8,}$/.test(v);
},
message: props => `${props.value} Password does not meet requirements: at least 8 characters, includes uppercase and digits`
}
},
phone: {
type: String,
validate: {
validator: function(v) {
return /^1[3-9]\d{9}$/.test(v);
},
message: 'The phone number format is incorrect'
}
}
});
(3) التحقق من توقيت الزناد
يتم تشغيل عملية التحقق تلقائيًا في الأوقات التالية: new Model().save() وModel.create(). يمكنك تشغيلها يدويًّا باستخدام الطريقة validate(). أما updateOne() / updateMany()، وما إلى ذلك، فلا تؤدي إلى تشغيل عملية التحقق تلقائيًّا؛ بل يجب عليك تكوين الخيار runValidators: true.
▶ مثال: تمكين التحقق من صحة عمليات التحديث
User.updateOne(
{ email: 'bob@test.com' },
{ age: -5 },
{ runValidators: true }
);
4. الخصائص الافتراضية
لا يتم تخزين السمات الافتراضية في قاعدة البيانات؛ بل يتم حسابها ديناميكيًا فقط أثناء عمليات الاستعلام، مما يجعلها مناسبة للحقول المشتقة.
(1) التعريف والاستخدام
▶ مثال: السمة الافتراضية «الاسم الكامل للمستخدم»
const userSchema = new mongoose.Schema({
firstName: String,
lastName: String,
email: String
});
userSchema.virtual('fullName')
.get(function() {
return `${this.firstName} ${this.lastName}`;
})
.set(function(v) {
const parts = v.split(' ');
this.firstName = parts[0];
this.lastName = parts[1] || '';
});
const User = mongoose.model('User', userSchema);
const user = new User({ firstName: 'Bob', lastName: 'Smith' });
console.log(user.fullName);
Bob Smith
(2) virtual و toJSON
لا يتم تضمين السمات الافتراضية بشكل افتراضي في مخرجات toJSON() وtoObject(). ويجب تمكينها صراحةً في خيارات المخطط:
const userSchema = new mongoose.Schema({
firstName: String,
lastName: String
}, {
toJSON: { virtuals: true },
toObject: { virtuals: true }
});
5. الخطافات (البرامج الوسيطة)
يتم تنفيذ «الهوكات» (البرامج الوسيطة) تلقائيًا في مراحل محددة من دورة حياة المستند، وتُستخدم في المعالجة المسبقة للبيانات، والتسجيل، والعمليات المتتالية، وغيرها.
(1) أنواع الخطافات وشروط التشغيل
| نوع الخطاف | شروط التشغيل | الاستخدامات الشائعة |
|---|---|---|
pre('save') |
قبل الحفظ | تجزئة كلمة المرور، تنسيق البيانات، طابع زمني للتحديث |
post('save') |
بعد الحفظ | إرسال الإشعارات، تسجيل المدخلات |
pre('remove') |
قبل الحذف | الحذف التسلسلي للوثائق المرتبطة |
post('remove') |
بعد الحذف | تنظيف الموارد والسجلات |
pre('find') |
قبل الاستعلام | معايير التصفية الافتراضية (مثل الحذف المؤقت) |
post('find') |
بعد الاستعلام | إخفاء هوية البيانات |
pre('updateOne') |
قبل التحديث | طابع زمني للتحديث |
post('aggregate') |
بعد التجميع | التسجيل |
(2) بالنسبة إلى «هوكس»
▶ مثال: إجراء التجزئة التلقائية لكلمات المرور قبل الحفظ
const bcrypt = require('bcrypt');
userSchema.pre('save', async function(next) {
if (!this.isModified('password')) return next();
this.password = await bcrypt.hash(this.password, 10);
next();
});
(3) ربطات المنشورات
▶ مثال: إرسال رسالة بريد إلكتروني ترحيبية بعد الحفظ
userSchema.post('save', function(doc, next) {
console.log(`User ${doc.email} Saved`);
next();
});
(4) ربط الاستعلامات
▶ مثال: تصفية المستندات المحذوفة تلقائيًا أثناء إجراء الاستعلام
userSchema.pre('find', function() {
this.where({ deletedAt: null });
});
6. أداة إنشاء الاستعلامات
يدعم مُنشئ الاستعلامات في Mongoose الاستدعاءات المتسلسلة، وهي أكثر سهولة في الاستخدام مقارنة بالمعلمات الكائنية التي يستخدمها برنامج التشغيل الأصلي.
(1) طريقة الاستعلام المتسلسل
▶ مثال: استدعاءات متسلسلة لمنشئ الاستعلامات
const users = await User.find()
.where('age').gte(18).lte(65)
.where('role').equals('admin')
.sort({ createdAt: -1 })
.select('name email age')
.limit(10)
.skip(0);
console.log(users);
(2) Comparison of Common Query Methods
| تنفيذ برنامج التشغيل الأصلي | أداة إنشاء الاستعلامات في Mongoose | الوصف |
|---|---|---|
db.users.find({ age: { $gte: 18 } }) |
User.find().where('age').gte(18) |
البحث حسب الحالة |
db.users.find().sort({ name: 1 }) |
User.find().sort({ name: 1 }) |
فرز |
db.users.find().limit(10) |
User.find().limit(10) |
عدد الإدخالات محدود بـ |
db.users.find().skip(20) |
User.find().skip(20) |
تخطي |
db.users.find({}, { name: 1 }) |
User.find().select('name') |
مرشح الحقل |
(3) تغليف الاستعلامات المقسمة إلى صفحات
▶ مثال: الوظائف المساعدة للاستعلامات المقسمة إلى صفحات
async function paginate(Model, filter = {}, page = 1, limit = 10) {
const skip = (page - 1) * limit;
const [docs, total] = await Promise.all([
Model.find(filter).skip(skip).limit(limit).sort({ createdAt: -1 }),
Model.countDocuments(filter)
]);
return {
data: docs,
total,
page,
totalPages: Math.ceil(total / limit)
};
}
const result = await paginate(User, { role: 'user' }, 2, 10);
7. الفهرس
تعد الفهارس عنصراً أساسياً في أداء استعلامات قاعدة البيانات. ويدعم Mongoose التعريف التصريحي للفهارس في المخطط.
(1) الفهارس أحادية العمود والفهارس المركبة
▶ مثال: تعريف فهرس في المخطط
const userSchema = new mongoose.Schema({
email: {
type: String,
required: true,
unique: true
},
username: {
type: String,
index: true
},
region: String,
status: String
});
userSchema.index({ region: 1, status: 1 });
| نوع الفهرس | التعريف | الوصف |
|---|---|---|
| فهرس فريد | unique: true |
يجب أن تكون قيم الحقول فريدة |
| الفهرس العادي | index: true |
البحث السريع |
| المؤشر المركب | schema.index({ a: 1, b: -1 }) |
المؤشر المركب متعدد المجالات |
| فهرس النص | schema.index({ title: 'text' }) |
البحث عن النص الكامل |
▶ مثال:(2) الإنشاء التلقائي للفهرس في بيئة التطوير
mongoose.connect(uri, { autoIndex: true });
في بيئة الإنتاج، يُوصى بتعطيل autoIndex وإنشاء الفهارس يدويًّا باستخدام البرنامج النصي للترحيل لتجنب تأخيرات بدء التشغيل.
8. طرق المثيل والطرق الثابتة
تتيح لك Mongoose توسيع نطاق المخطط باستخدام طرق مخصصة، والتي تنقسم إلى فئتين: طرق المثيل والطرق الثابتة.
(1) طرق الكائنات
تعمل طرق المثيل على مستند واحد؛ استخدم this للوصول إلى المستند الحالي.
▶ مثال: طريقة مقارنة كلمات المرور
userSchema.methods.comparePassword = function(candidate) {
return bcrypt.compare(candidate, this.password);
};
const user = await User.findOne({ email: 'bob@test.com' });
const isMatch = await user.comparePassword('mypassword');
(2) الطرق الثابتة
يتم تعريف الطرق الثابتة في النموذج (Model) ولا تعتمد على مثيلات المستندات، مما يجعلها مناسبة لدعم الاستعلامات.
▶ مثال: البحث عن الطرق الثابتة حسب الدور
userSchema.statics.findByRole = function(role) {
return this.find({ role }).sort({ createdAt: -1 });
};
const admins = await User.findByRole('admin');
| النوع | التعريف | الاستدعاء | هذا المرجع |
|---|---|---|---|
| طرق المثيل | schema.methods.xxx = function |
doc.xxx() |
مثيل المستند |
| طريقة ثابتة | schema.statics.xxx = function |
Model.xxx() |
النموذج |
9. ملء الاستعلامات المدمجة
تقوم مونجوس populate() بتنفيذ عملية مطابقة المراجع بين مستندات MongoDB، على غرار عملية JOIN في SQL.
(1) التعريفات المرجعية وعمليات الربط
▶ مثال: المقالات المرتبطة بالمؤلفين
const postSchema = new mongoose.Schema({
title: String,
content: String,
author: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
required: true
}
});
const Post = mongoose.model('Post', postSchema);
const posts = await Post.find().populate('author', 'firstName lastName email');
(2) التعبئة متعددة المستويات والتصفية الشرطية
▶ مثال: عمليات الربط والفلترة متعددة المستويات
const commentSchema = new mongoose.Schema({
content: String,
author: { type: mongoose.Schema.Types.ObjectId, ref: 'User' },
post: { type: mongoose.Schema.Types.ObjectId, ref: 'Post' }
});
const Comment = mongoose.model('Comment', commentSchema);
const comments = await Comment.find()
.populate('author', 'firstName lastName')
.populate({
path: 'post',
select: 'title content',
match: { status: 'published' }
});
(3) Performance Considerations for populate
populate() في الأساس، يتضمن هذا إصدار استعلامات إضافية ثم دمج النتائج؛ وهو ليس عملية «JOIN» حقيقية. ولا تزال مشكلة «N+1» قائمة — فإذا كانت هناك 100 مقالة مرتبطة بـ 100 مؤلف مختلف، فسيؤدي ذلك إلى إصدار 101 استعلامًا. بالنسبة للسيناريوهات التي تتضمن ارتباطات متكررة، يُنصح بالنظر في تضمين المستندات أو $lookup التجميع.
10. مقارنة بين Mongoose وبرنامج التشغيل الأصلي
| البعد | برنامج التشغيل الأصلي لـ MongoDB | Mongoose ODM |
|---|---|---|
| التحقق من صحة البيانات | عبارات if/else مكتوبة يدويًّا ومتناثرة في جميع أنحاء وحدة التحكم | التحقق من الصحة التصريحي القائم على المخطط، والمُدار مركزيًّا |
| قيود الأنواع | لا توجد؛ يمكن لأي حقل تخزين أي قيمة | أنواع مفروضة بموجب المخطط؛ تحويل تلقائي |
| الاستعلامات المجمعة | التجميع اليدوي $lookup |
populate() يتم تنفيذه في سطر واحد |
| ربطات دورة الحياة | لا شيء | ربطات ما قبل/بعد |
| السمة الافتراضية | لا شيء | الحقل الافتراضي المحسوب ديناميكيًا |
| إدارة الفهرس | الدليل createIndex() |
إعلان المخطط + الإنشاء التلقائي |
| واجهة برمجة التطبيقات للاستعلام | معلمات الكائنات find({ age: { $gte: 18 } }) |
مُنشئ متسلسل + معلمات الكائنات |
| منحنى التعلم | منخفض؛ يشبه إلى حد كبير بناء الجملة الأصلي لـ MongoDB | متوسط؛ يتطلب فهمًا للمخطط والنموذج والوثيقة |
| المرونة | عالية، تحكم كامل | متوسطة، يتم تجاهل الحقول خارج المخطط افتراضيًّا |
| الأداء | أفضل قليلاً، بدون طبقة وسيطة | أقل قليلاً، بسبب العبء الإضافي الناتج عن التحقق من الصحة والخطافات |
11. مثال شامل: نماذج بيانات المستخدم والمقالات
ادمج المخططات، وعمليات التحقق من الصحة، والخطافات، والسمات الافتراضية، وطرق المثيلات، واستعلامات الربط لتشكيل نظام نموذج بيانات متكامل.
▶ مثال: models/User.js
const mongoose = require('mongoose');
const bcrypt = require('bcrypt');
const userSchema = new mongoose.Schema({
firstName: {
type: String,
required: [true, 'The last name cannot be left blank.'],
trim: true
},
lastName: {
type: String,
required: [true, 'The name cannot be empty'],
trim: true
},
email: {
type: String,
required: [true, 'The email address cannot be left blank.'],
unique: true,
lowercase: true,
match: [/^\S+@\S+\.\S+$/, 'The email address format is incorrect.']
},
password: {
type: String,
required: [true, 'The password cannot be empty.'],
minlength: 8,
validate: {
validator: function(v) {
return /^(?=.*[A-Z])(?=.*\d).{8,}$/.test(v);
},
message: 'Password must be at least 8 characters, must include uppercase and digits'
}
},
role: {
type: String,
enum: ['user', 'admin'],
default: 'user'
},
age: {
type: Number,
min: [0, 'Age cannot be a negative number.'],
max: [150, 'Age must not exceed150']
},
createdAt: {
type: Date,
default: Date.now
}
}, {
toJSON: { virtuals: true },
toObject: { virtuals: true }
});
userSchema.virtual('fullName').get(function() {
return `${this.firstName} ${this.lastName}`;
});
userSchema.pre('save', async function(next) {
if (!this.isModified('password')) return next();
this.password = await bcrypt.hash(this.password, 10);
next();
});
userSchema.methods.comparePassword = function(candidate) {
return bcrypt.compare(candidate, this.password);
};
userSchema.statics.findByRole = function(role) {
return this.find({ role }).sort({ createdAt: -1 });
};
module.exports = mongoose.model('User', userSchema);
▶ مثال: models/Post.js
const mongoose = require('mongoose');
const postSchema = new mongoose.Schema({
title: {
type: String,
required: [true, 'The title cannot be left blank.'],
trim: true,
minlength: [2, 'Title must be at least2characters'],
maxlength: [200, 'Most Titles200characters']
},
content: {
type: String,
required: [true, 'Content cannot be empty']
},
author: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
required: true
},
status: {
type: String,
enum: ['draft', 'published', 'archived'],
default: 'draft'
},
tags: [{
type: String,
lowercase: true
}],
viewCount: {
type: Number,
default: 0,
min: 0
},
createdAt: {
type: Date,
default: Date.now
},
updatedAt: {
type: Date,
default: Date.now
}
});
postSchema.index({ status: 1, createdAt: -1 });
postSchema.index({ tags: 1 });
postSchema.virtual('excerpt').get(function() {
return this.content.substring(0, 100) + '...';
});
postSchema.pre('save', function(next) {
if (this.isModified('content')) {
this.updatedAt = new Date();
}
next();
});
postSchema.post('remove', async function(doc) {
await mongoose.model('Comment').deleteMany({ post: doc._id });
});
postSchema.statics.findPublished = function() {
return this.find({ status: 'published' })
.populate('author', 'firstName lastName email')
.sort({ createdAt: -1 });
};
module.exports = mongoose.model('Post', postSchema);
▶ مثال: الاستعلامات وعمليات الربط
const mongoose = require('mongoose');
const User = require('./models/User');
const Post = require('./models/Post');
async function main() {
await mongoose.connect('mongodb://localhost:27017/blog');
const user = await User.create({
firstName: 'Bob',
lastName: 'Smith',
email: 'bob@example.com',
password: 'Secure123',
role: 'admin',
age: 28
});
const post = await Post.create({
title: 'Mongoose Getting Started Guide',
content: 'Mongoose is MongoDB ODM library, providing schema-driven data modeling...',
author: user._id,
status: 'published',
tags: ['mongodb', 'mongoose', 'nodejs']
});
const published = await Post.findPublished();
console.log(published[0].excerpt);
console.log(published[0].author.fullName);
const match = await user.comparePassword('Secure123');
console.log('Password match:', match);
await mongoose.connection.close();
}
main();
node app.js
Mongoose is MongoDB ODM library, providing schema-driven data modeling......
Bob Smith
Password match: true
❓ أسئلة شائعة
Model.collection.pre-save لحساب القيمة وتعيينها. بشكل افتراضي، لا تظهر الحقول الافتراضية في مخرجات JSON؛ لذا يجب عليك تعيين toJSON: { virtuals: true }.populate؟populate ليس عملية ربط (JOIN) في SQL؛ فهو يتضمن أساسًا تنفيذ استعلامات إضافية ثم دمج النتائج. يكون الأداء جيدًا عندما يكون عدد المستندات ذات الصلة قليلًا؛ ومع ذلك، فإن وجود عدد كبير من المستندات ذات الصلة المختلفة قد يتسبب في مشكلة استعلام N+1. بالنسبة للسيناريوهات عالية التكرار، يُنصح بالنظر في تضمين المستندات، أو استخدام $lookup يدويًّا، أو التخزين المؤقت.ValidationError عند فشل التحقق من الصحة، ويحتوي الكائن errors على تفاصيل الخطأ لكل حقل. يمكنك استخدام Object.values(err.errors).map(e => e.message) لاستخراج جميع الرسائل، وبالاقتران مع برمجيات Express الوسيطة لمعالجة الأخطاء، إرجاع رمز حالة موحد هو 400.this في دالة الـ hook pre-save؟this إلى مثيل Document الذي على وشك الحفظ. ملاحظة: سيؤدي استخدام الدوال السهمية إلى فقدان ربط this؛ يجب أن تكون دالة الخطاف دالة عادية. يمكن استخدام this.isModified('field') لتحديد ما إذا كان الحقل قد تم تعديله أم لا.schema.add({ newField: String }) لإضافتها ديناميكيًا. ومع ذلك، لن يتم تحديث النماذج المُجمَّعة تلقائيًّا؛ فستحتاج إلى إعادة تجميعها أو استخدام الملحق schema.plugin(). نوصي بالتخطيط لهيكل الحقول بعناية في بداية المشروع.📖 ملخص
- المفاهيم الأساسية واستخدامات بنية Mongoose Core
- المفاهيم الأساسية وأنماط استخدام أنواع حقول المخطط
- المفاهيم الأساسية لأدوات التحقق من الصحة وكيفية استخدامها
- المفاهيم الأساسية للخصائص الافتراضية وكيفية استخدامها
- المفاهيم الأساسية واستخدامات «الهوكات» (البرامج الوسيطة)
- المفاهيم الأساسية لاستخدام أداة إنشاء الاستعلامات
- المفاهيم الأساسية للفهارس وكيفية استخدامها
- المفاهيم الأساسية واستخدامات الطرق الخاصة بالمثيلات والطرق الثابتة
📝 تمارين
- أكمل جميع أمثلة الأكواد الواردة في هذا الدرس وتأكد من أن كل منها يعمل بشكل صحيح.
- قم بتعديل المثال الشامل وأضف الإضافات الخاصة بك
- راجع الوثائق الرسمية، وحدد واجهة برمجة تطبيقات (API) واحدة أو اثنتين لم يتم تناولهما في هذا الدرس، واكتب كود اختبار لهما.
- التأمل: كيف ستطبق ما تعلمته في هذا الدرس على مشروع في الواقع العملي؟
- حاول أن تجمع بين ما تعلمته في هذا الدرس والمواد التي درستها في الدروس السابقة لإنشاء مشروع صغير.