JavaScript Skill
Instructions
When writing JavaScript:
- Modern Syntax
// Use const by default, let when needed const API_URL = 'https://api.example.com'; let count = 0;
// Arrow functions
const add = (a, b) => a + b;
const greet = name => Hello, ${name}!;
// Destructuring const { name, email } = user; const [first, second, ...rest] = items;
// Spread operator const newArray = [...oldArray, newItem]; const newObject = { ...oldObject, newProp: value };
// Template literals
const message = User ${name} has ${count} items;
// Optional chaining const city = user?.address?.city;
// Nullish coalescing const value = input ?? defaultValue;
- Async/Await
// Async function async function fetchData(url) { try { const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP error: ${response.status}`);
}
const data = await response.json();
return data;
} catch (error) { console.error('Fetch failed:', error); throw error; } }
// Parallel requests async function fetchAll(urls) { const promises = urls.map(url => fetch(url)); const responses = await Promise.all(promises); return Promise.all(responses.map(r => r.json())); }
// With timeout async function fetchWithTimeout(url, timeout = 5000) { const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), timeout);
try { const response = await fetch(url, { signal: controller.signal }); return await response.json(); } finally { clearTimeout(timeoutId); } }
- Array Methods
const users = [ { id: 1, name: 'Alice', age: 25, active: true }, { id: 2, name: 'Bob', age: 30, active: false }, { id: 3, name: 'Charlie', age: 35, active: true }, ];
// map - transform items const names = users.map(user => user.name);
// filter - select items const activeUsers = users.filter(user => user.active);
// find - get first match const bob = users.find(user => user.name === 'Bob');
// some/every - check conditions const hasActive = users.some(user => user.active); const allActive = users.every(user => user.active);
// reduce - aggregate const totalAge = users.reduce((sum, user) => sum + user.age, 0);
// Chaining const activeNames = users .filter(user => user.active) .map(user => user.name) .sort();
- DOM Manipulation
// Selecting elements const element = document.querySelector('.class'); const elements = document.querySelectorAll('.class');
// Creating elements
const div = document.createElement('div');
div.className = 'card';
div.innerHTML = <h2>${title}</h2> <p>${description}</p>;
// Event handling element.addEventListener('click', (event) => { event.preventDefault(); // Handle click });
// Event delegation document.querySelector('.list').addEventListener('click', (event) => { if (event.target.matches('.item')) { handleItemClick(event.target); } });
// IntersectionObserver (lazy loading, animations) const observer = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { entry.target.classList.add('visible'); observer.unobserve(entry.target); } }); }, { threshold: 0.1 });
document.querySelectorAll('.animate').forEach(el => observer.observe(el));
- Classes
class User { #privateField; // Private field
constructor(name, email) { this.name = name; this.email = email; this.#privateField = 'secret'; }
// Getter get displayName() { return this.name.toUpperCase(); }
// Setter set displayName(value) { this.name = value.trim(); }
// Method
greet() {
return Hello, I'm ${this.name};
}
// Static method static create(data) { return new User(data.name, data.email); } }
// Inheritance class Admin extends User { constructor(name, email, role) { super(name, email); this.role = role; }
greet() {
return ${super.greet()} and I'm an ${this.role};
}
}
- Modules
// Named exports export const API_URL = 'https://api.example.com'; export function fetchData() { /* ... / } export class User { / ... */ }
// Default export export default function main() { /* ... */ }
// Importing import main, { API_URL, fetchData, User } from './module.js';
// Dynamic import const module = await import('./heavy-module.js');
- Error Handling
// Custom error class ValidationError extends Error { constructor(message, field) { super(message); this.name = 'ValidationError'; this.field = field; } }
// Try-catch with specific handling try { await submitForm(data); } catch (error) { if (error instanceof ValidationError) { showFieldError(error.field, error.message); } else if (error instanceof NetworkError) { showToast('Network error. Please try again.'); } else { console.error('Unexpected error:', error); showToast('Something went wrong.'); } }
- Local Storage
// Store data const saveData = (key, data) => { localStorage.setItem(key, JSON.stringify(data)); };
// Retrieve data const getData = (key, defaultValue = null) => { const stored = localStorage.getItem(key); return stored ? JSON.parse(stored) : defaultValue; };
// Remove data const removeData = (key) => { localStorage.removeItem(key); };
- Debounce & Throttle
// Debounce - wait until stopped function debounce(func, delay) { let timeoutId; return (...args) => { clearTimeout(timeoutId); timeoutId = setTimeout(() => func(...args), delay); }; }
const debouncedSearch = debounce((query) => { fetchResults(query); }, 300);
// Throttle - limit frequency function throttle(func, limit) { let inThrottle; return (...args) => { if (!inThrottle) { func(...args); inThrottle = true; setTimeout(() => inThrottle = false, limit); } }; }
const throttledScroll = throttle(() => { updatePosition(); }, 100);
- Best Practices
-
Use const by default
-
Prefer arrow functions
-
Use async/await over callbacks
-
Handle all errors
-
Avoid global variables
-
Use meaningful variable names
-
Keep functions small and focused
-
Comment complex logic
-
Use strict equality (=== )
-
Validate user input
WordPress-Specific JavaScript
- Enqueueing Scripts Properly
function theme_enqueue_scripts() { // Frontend script wp_enqueue_script( 'theme-main', get_template_directory_uri() . '/assets/js/main.js', array(), // dependencies '1.0.0', true // in footer );
// With jQuery dependency
wp_enqueue_script(
'theme-jquery-script',
get_template_directory_uri() . '/assets/js/custom.js',
array('jquery'),
'1.0.0',
true
);
// Pass PHP data to JavaScript
wp_localize_script('theme-main', 'themeData', array(
'ajaxUrl' => admin_url('admin-ajax.php'),
'restUrl' => rest_url('theme/v1/'),
'nonce' => wp_create_nonce('theme_nonce'),
'i18n' => array(
'loading' => __('Loading...', 'theme'),
'error' => __('An error occurred', 'theme'),
),
));
} add_action('wp_enqueue_scripts', 'theme_enqueue_scripts');
- AJAX with admin-ajax.php
// Frontend JavaScript async function submitForm(formData) { const data = new FormData(); data.append('action', 'theme_submit_form'); data.append('nonce', themeData.nonce); data.append('name', formData.name); data.append('email', formData.email);
try {
const response = await fetch(themeData.ajaxUrl, {
method: 'POST',
body: data,
credentials: 'same-origin',
});
const result = await response.json();
if (result.success) {
return result.data;
} else {
throw new Error(result.data.message || 'Request failed');
}
} catch (error) {
console.error('AJAX Error:', error);
throw error;
}
}
// PHP handler add_action('wp_ajax_theme_submit_form', 'theme_handle_form'); add_action('wp_ajax_nopriv_theme_submit_form', 'theme_handle_form');
function theme_handle_form() { // Verify nonce if (!wp_verify_nonce($_POST['nonce'], 'theme_nonce')) { wp_send_json_error(array('message' => 'Invalid nonce')); }
// Sanitize input
$name = sanitize_text_field($_POST['name']);
$email = sanitize_email($_POST['email']);
// Process...
wp_send_json_success(array('message' => 'Form submitted'));
}
- REST API Requests
// GET request
async function getPosts() {
const response = await fetch(${themeData.restUrl}posts, {
headers: {
'X-WP-Nonce': themeData.nonce,
},
});
return response.json();
}
// POST request
async function createPost(data) {
const response = await fetch(${themeData.restUrl}posts, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-WP-Nonce': themeData.nonce,
},
body: JSON.stringify(data),
});
return response.json();
}
// Using wp.apiFetch (Gutenberg) wp.apiFetch({ path: '/wp/v2/posts' }).then(posts => { console.log(posts); });
- jQuery Compatibility
// WordPress jQuery no-conflict wrapper (function($) { $(document).ready(function() { // Your jQuery code here $('.element').on('click', function() { $(this).toggleClass('active'); }); }); })(jQuery);
// Or with modern syntax jQuery(($) => { $('.element').on('click', function() { $(this).toggleClass('active'); }); });
- Gutenberg/Block Editor JavaScript
// Using wp.data for state const { select, dispatch } = wp.data;
// Get current post const post = select('core/editor').getCurrentPost();
// Get blocks const blocks = select('core/block-editor').getBlocks();
// Using wp.hooks for filters wp.hooks.addFilter( 'blocks.registerBlockType', 'theme/modify-block', (settings, name) => { if (name === 'core/paragraph') { settings.attributes.customAttr = { type: 'string', default: '', }; } return settings; } );
// Using wp.i18n for translations const { __, _n, sprintf } = wp.i18n; const message = __('Hello World', 'theme'); const items = sprintf(_n('%d item', '%d items', count, 'theme'), count);
- WordPress JavaScript Best Practices
-
Always use nonces for security in AJAX/REST requests
-
Use wp_localize_script() to pass data from PHP to JS
-
Wrap jQuery code in no-conflict wrapper
-
Prefer REST API over admin-ajax for new projects
-
Use wp.apiFetch in Gutenberg context
-
Namespace your code to avoid conflicts
-
Load scripts in footer when possible (true as last param)
-
Use dependencies array correctly (e.g., array('jquery', 'wp-element') )
-
Handle errors gracefully with user-friendly messages
-
Test in both frontend and admin contexts