avatar
Siz Long

My name is Siz. I am a computer science graduate student specializing in backend development with Golang and Python, seeking opportunities in innovative tech projects. My personal website is me.longsizhuo.com .Connect with me on LinkedIn: https://www.linkedin.com/in/longsizhuo/.

  • Resume
  • Archives
  • Categories
  • Photos
  • Music



{{ date }}

{{ time }}

avatar
Siz Long

My name is Siz. I am a computer science graduate student specializing in backend development with Golang and Python, seeking opportunities in innovative tech projects. My personal website is me.longsizhuo.com .Connect with me on LinkedIn: https://www.linkedin.com/in/longsizhuo/.

  • 主页
  • Resume
  • Archives
  • Categories
  • Photos
  • Music

Frontend Interview Preparation Guide

  2023-06-12
字数统计: 2.2k字   |   阅读时长: 13min

Frontend Interview Preparation Guide

This guide covers essential topics for frontend interviews, focusing on conceptual understanding rather than coding challenges. It’s designed to help you prepare for interviews at companies like Kuaishou that focus on React, Next.js, and general frontend knowledge.

HTML & CSS Fundamentals

Critical Rendering Path

  • Document Object Model (DOM): How browsers parse HTML into a tree structure
  • CSS Object Model (CSSOM): How CSS is processed
  • Render Tree: Combination of DOM and CSSOM
  • Layout/Reflow: Calculating element positions and dimensions
  • Paint: Filling in pixels
  • Composite: Layering elements in the correct order

CSS Specificity

  • Specificity hierarchy: Inline styles > IDs > Classes/attributes/pseudo-classes > Elements
  • Calculating specificity: 1-0-0-0 for inline, 0-1-0-0 for ID, 0-0-1-0 for class, 0-0-0-1 for element
  • !important: Overrides normal rules but should be avoided

Box Model

  • Content, padding, border, and margin: How they affect element sizing
  • box-sizing: content-box vs. border-box
  • Collapsing margins: When and why vertical margins collapse

Positioning and Layout

  • Static, relative, absolute, fixed, sticky: Different positioning methods
  • Flexbox: Main axis, cross axis, flex-grow, flex-shrink, flex-basis
  • Grid: Templates, areas, auto-placement, fractional units
  • Media queries: Responsive design implementation

CSS Preprocessors

  • Sass/SCSS: Variables, nesting, mixins, partials, inheritance
  • Less: Similar to Sass with different syntax
  • Benefits: Maintainability, reusability, organization

JavaScript Core Concepts

Closures

  • Definition: Function + lexical environment where it was declared
  • Use cases: Data privacy, partial application, maintaining state
  • Memory implications: Preventing memory leaks
1
2
3
4
5
6
7
8
9
10
function createCounter() {
let count = 0;
return function() {
return ++count;
};
}

const counter = createCounter();
console.log(counter()); // 1
console.log(counter()); // 2

Scope

  • Global scope: Variables available throughout code
  • Function scope: Variables defined within functions
  • Block scope: Variables defined within blocks (let, const)
  • Lexical scope: How nested functions access variables from parent scopes

Prototypes and Inheritance

  • Prototype chain: How JavaScript objects inherit properties
  • Constructor functions: Creating objects with shared methods
  • Object.create(): Creating objects with specific prototypes
  • Class syntax: Syntactic sugar over prototypal inheritance
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// Prototypal inheritance
function Person(name) {
this.name = name;
}

Person.prototype.sayHello = function() {
return `Hello, my name is ${this.name}`;
};

// Class syntax (ES6+)
class Person {
constructor(name) {
this.name = name;
}

sayHello() {
return `Hello, my name is ${this.name}`;
}
}

this Keyword

  • Global context: this refers to the global object
  • Function context: Depends on how the function is called
  • Method context: this refers to the object the method belongs to
  • Arrow functions: this is lexically bound to surrounding context
  • Binding methods: bind(), call(), apply()

Event Loop and Asynchronous JavaScript

  • Call stack: Where function calls are tracked
  • Task queue: Where callbacks from async operations wait
  • Microtask queue: Higher priority queue (Promises)
  • Event loop algorithm: How tasks get moved to the call stack
  • setTimeout, Promises, async/await: Different ways to handle async code
1
2
3
4
5
6
7
8
9
10
11
12
13
console.log('Start');

setTimeout(() => {
console.log('Timeout');
}, 0);

Promise.resolve().then(() => {
console.log('Promise');
});

console.log('End');

// Output: Start, End, Promise, Timeout

ES6+ Features

  • let/const: Block-scoped variables
  • Arrow functions: Shorter syntax and lexical this
  • Template literals: String interpolation
  • Destructuring: Extracting values from objects and arrays
  • Spread/rest: Working with multiple values
  • Default parameters: Fallback values for function arguments
  • Classes: Syntactic sugar for constructor functions
  • Modules: Import/export syntax
  • Optional chaining: Safe access to nested properties obj?.prop?.field
  • Nullish coalescing: Default values only for null/undefined value ?? default

DOM and Browser APIs

DOM Manipulation

  • Selectors: getElementById, querySelector, etc.
  • Creating/modifying elements: createElement, appendChild, innerHTML, textContent
  • Event handling: addEventListener, event delegation, bubbling vs. capturing
  • Performance concerns: Batch DOM updates, documentFragment

Browser Storage

  • Cookies: Small, sent with HTTP requests, security concerns
  • localStorage: Persistent, larger capacity (5MB), synchronous
  • sessionStorage: Cleared when session ends
  • IndexedDB: For larger structured data
  • Cache API: Used with Service Workers

Browser Rendering Performance

  • Layout thrashing: Forcing multiple reflows
  • Debouncing and throttling: Limiting frequent events
  • requestAnimationFrame: Syncing with the browser’s rendering cycle
  • Web Workers: Offloading heavy computation

React Deep Dive

Virtual DOM

  • Concept: Lightweight representation of the actual DOM
  • Reconciliation: How React efficiently updates the DOM
  • Fiber architecture: Enables concurrent mode and time-slicing
  • Keys: How React tracks element identity

Component Types

  • Function vs. Class components: Different approaches, same result
  • Pure components: Automatic shallow comparison optimization
  • Higher-order components (HOCs): Functions that enhance components
  • Render props: Sharing code via props
  • Compound components: Related components that work together

State Management

  • Component state: Local state within components
  • Lifting state up: Sharing state between components
  • Context API: Avoiding prop drilling
  • Redux: Predictable state container
    • Actions, reducers, store, middleware
    • Redux Toolkit simplifications
  • Zustand/Recoil/Jotai: Modern alternatives

Hooks

  • useState: Managing local state
  • useEffect: Side effects (data fetching, subscriptions)
  • useContext: Consuming React context
  • useReducer: Complex state logic
  • useCallback/useMemo: Performance optimizations
  • useRef: Persisting mutable values
  • Custom hooks: Extracting reusable logic
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function useLocalStorage(key, initialValue) {
const [storedValue, setStoredValue] = useState(() => {
try {
const item = window.localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
} catch (error) {
console.log(error);
return initialValue;
}
});

const setValue = value => {
try {
const valueToStore = value instanceof Function ? value(storedValue) : value;
setStoredValue(valueToStore);
window.localStorage.setItem(key, JSON.stringify(valueToStore));
} catch (error) {
console.log(error);
}
};

return [storedValue, setValue];
}

React Performance Optimization

  • Preventing unnecessary renders: React.memo
  • Code splitting: React.lazy and Suspense
  • Virtualization: Efficiently rendering large lists
  • Web Vitals: Core Web Vitals metrics in React applications
  • React Profiler: Identifying performance bottlenecks

React Router

  • Declarative routing: Managing navigation in SPAs
  • Route parameters and query strings: Accessing URL data
  • Nested routes: Creating complex layouts
  • Navigation guards: Preventing unauthorized access
  • Code-splitting at route level: Reducing bundle size

Next.js Concepts

Rendering Methods

  • Server-side rendering (SSR): getServerSideProps
  • Static site generation (SSG): getStaticProps, getStaticPaths
  • Incremental Static Regeneration (ISR): Revalidation
  • Client-side rendering: When to use it
  • Server Components vs. Client Components: Next.js 13+ App Router

Data Fetching Strategies

  • API routes: Creating backend endpoints
  • SWR/React Query: Data fetching and caching
  • GraphQL integration: Working with Apollo or Relay
  • Serverless functions: Using Next.js as a backend

Advanced Next.js Features

  • Image optimization: next/image
  • Middleware: Customizing request handling
  • Internationalization: Multi-language support
  • Authentication patterns: Various approaches
  • Deployment strategies: Vercel vs. self-hosting

Network and Protocols

HTTP

  • Request/response cycle: How web communication works
  • HTTP methods: GET, POST, PUT, PATCH, DELETE
  • Status codes: 200s, 300s, 400s, 500s
  • Headers: Content-Type, Authorization, Cache-Control

HTTP/2 and HTTP/3

  • Multiplexing: Multiple requests over one connection
  • Server push: Proactively sending resources
  • Header compression: Reducing overhead
  • QUIC protocol: UDP-based transport (HTTP/3)

RESTful Services

  • Resource-oriented architecture: Designing APIs
  • Statelessness: No client session on server
  • HATEOAS: Hypermedia as the engine of application state
  • GraphQL alternatives: Different approach to APIs

Caching

  • Browser cache: Cache-Control, ETag, Last-Modified
  • CDN caching: Edge distribution of assets
  • Service Worker cache: Offline capabilities
  • Cache invalidation strategies: Time-based, version-based

CORS

  • Same-origin policy: Security restriction
  • Cross-Origin Resource Sharing: Controlled relaxation
  • Preflight requests: OPTIONS method for complex requests
  • Credentials: Including cookies in cross-origin requests

Web Security

XSS (Cross-Site Scripting)

  • Stored XSS: Malicious script stored on server
  • Reflected XSS: Script included in request
  • DOM-based XSS: Vulnerability in client-side code
  • Prevention: Content Security Policy, sanitization, escaping

CSRF (Cross-Site Request Forgery)

  • Attack mechanism: Tricking users into unwanted actions
  • Prevention: CSRF tokens, SameSite cookies
  • Double Submit Cookie: Additional protection layer

Authentication Best Practices

  • JWT: Structure, signing, common pitfalls
  • OAuth 2.0/OpenID Connect: Delegated authentication
  • Secure cookies: HttpOnly, Secure, SameSite flags
  • Multi-factor authentication: Additional security layer

Content Security Policy

  • Script-src: Controlling JavaScript sources
  • Frame-ancestors: Preventing clickjacking
  • Report-uri: Monitoring violations
  • Implementation: Headers vs. meta tags

Performance Optimization

Core Web Vitals

  • Largest Contentful Paint (LCP): Loading performance
  • First Input Delay (FID): Interactivity
  • Cumulative Layout Shift (CLS): Visual stability
  • Interaction to Next Paint (INP): Responsiveness to interactions

Resource Optimization

  • Images: Format selection, compression, responsive images
  • JavaScript: Bundling, code splitting, tree shaking
  • CSS: Critical CSS, removing unused styles
  • Fonts: Font loading strategies, subset embedding

Caching Strategies

  • HTTP caching: Cache-Control, ETag
  • Service Worker caching: Cache API, strategies
  • Application state caching: Memoization, persistent storage

Lazy Loading

  • Images and iframes: Native lazy loading
  • Components: Dynamic imports with React.lazy
  • Routes: Code splitting in routers
  • Intersection Observer API: Custom lazy loading

Performance Measurement

  • Lighthouse: Automated audits
  • Chrome DevTools: Performance panel, Memory panel
  • Web Vitals measurement: Real User Monitoring (RUM)
  • Custom performance marks: Performance API

Frontend Architecture

Component Design

  • Atomic design: Atoms, molecules, organisms, templates, pages
  • Micro-frontends: Breaking monoliths into smaller apps
  • Design systems: Consistent UI/UX across applications
  • Component libraries: Building reusable component collections

State Management Approaches

  • Centralized vs. distributed: Different paradigms
  • Unidirectional data flow: Predictable state changes
  • Immutability: Benefits for state management
  • Reactive programming: Working with observable streams

Testing Strategies

  • Unit testing: Testing isolated components
  • Integration testing: Testing component interactions
  • End-to-end testing: Testing entire flows
  • Testing Library, Jest, Cypress: Popular tools
  • Test-driven development: Writing tests before implementation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// React Testing Library example
import { render, screen, fireEvent } from '@testing-library/react';
import Counter from './Counter';

test('increments counter when button is clicked', () => {
render(<Counter />);

const button = screen.getByRole('button', { name: /increment/i });
const counter = screen.getByText(/count: 0/i);

fireEvent.click(button);

expect(screen.getByText(/count: 1/i)).toBeInTheDocument();
});

Build Tools and Module Bundlers

  • Webpack, Vite, esbuild: Different bundling approaches
  • Babel: JavaScript transpilation
  • PostCSS: CSS transformations
  • Code splitting: Optimizing bundle size
  • Production optimizations: Minification, compression

CI/CD for Frontend

  • Continuous integration: Automated testing on commits
  • Continuous deployment: Automated deployments
  • Preview environments: Testing changes before production
  • A/B testing infrastructure: Testing features with real users

Behavioral Interview Preparation

Talking About Past Projects

  • STAR method: Situation, Task, Action, Result
  • Focus on impact: Business value of your work
  • Technical challenges: How you overcame difficulties
  • Collaboration: Working with other teams/disciplines

System Design Questions

  • Requirements gathering: Clarifying the problem
  • User flows: Mapping out user interactions
  • Architecture decisions: Explaining your choices
  • Trade-offs: Acknowledging limitations
  • Scalability considerations: Planning for growth

Common Frontend Design Questions

  • News feed design: Infinite scrolling, updates
  • Chat application: Real-time considerations
  • E-commerce product page: Performance, accessibility
  • Dashboard with real-time updates: Data visualization

Culture Fit Questions

  • Teamwork examples: Collaboration stories
  • Conflict resolution: Handling disagreements
  • Learning approach: How you stay updated
  • Work style preferences: Remote/office, communication

Interview Preparation Checklist

Before the Interview

  • Research the company and their products
  • Review the job description for required skills
  • Prepare questions to ask interviewers
  • Review recent portfolio projects
  • Practice explaining technical concepts simply
  • Set up your environment for potential coding exercises

Technical Review

  • HTML semantics and accessibility
  • CSS layouts and responsive design
  • JavaScript core concepts
  • React fundamentals and patterns
  • Performance optimization techniques
  • Browser APIs and DOM manipulation
  • Network requests and REST APIs
  • State management approaches

Mock Interview Practice

  • Technical explanation practice
  • Whiteboarding practice
  • Coding exercise practice
  • System design practice
  • Behavioral question practice

Common Interview Questions

HTML & CSS

  1. How does the browser’s rendering engine work?
  2. Explain the CSS box model.
  3. How would you make a website responsive?
  4. What are the different ways to visually hide content?
  5. Explain CSS specificity and the cascade.

JavaScript

  1. What is closure and how would you use it?
  2. Explain event delegation.
  3. What is the difference between == and ===?
  4. How does prototypal inheritance work?
  5. Explain the event loop in JavaScript.
  6. What is hoisting?
  7. Explain async/await and how it relates to Promises.

React

  1. How does the Virtual DOM work?
  2. Explain the component lifecycle (or hooks equivalents).
  3. What are keys in React and why are they important?
  4. How would you optimize a React application’s performance?
  5. What is the difference between state and props?
  6. Explain context API and when you would use it.
  7. How do you handle side effects in React?

Next.js

  1. Explain the different rendering methods in Next.js.
  2. How does Next.js handle routing?
  3. What are API routes in Next.js?
  4. Explain the purpose of _app.js and _document.js.
  5. How would you implement authentication in a Next.js app?

Web Performance

  1. How would you diagnose and fix performance issues?
  2. Explain Core Web Vitals and how to improve them.
  3. What techniques would you use to optimize loading times?
  4. How does code splitting improve performance?
  5. Explain browser caching and how to leverage it.

Architecture & Design

  1. How would you structure a large-scale React application?
  2. What state management approach would you use and why?
  3. How would you design a component library?
  4. Explain your testing strategy for a frontend application.
  5. How would you handle API integration in a frontend application?

Final Tips

  1. Be honest about your knowledge: It’s okay to say “I don’t know” and explain how you would find the answer.
  2. Think aloud: Explain your thought process during coding or design questions.
  3. Ask clarifying questions: Ensure you understand what the interviewer is asking.
  4. Show enthusiasm: Demonstrate your passion for frontend development.
  5. Follow up: Send a thank you email and address any questions you felt you could have answered better.

Remember that interviews are also an opportunity for you to evaluate the company. Prepare thoughtful questions about their technology stack, development processes, and team culture to determine if it’s the right fit for you.

  • Web Development
  • JavaScript
  • React
  • Interview
  • Career
  • Frontend

show all >>

Next.js Fundamentals

  2023-06-11
字数统计: 2.8k字   |   阅读时长: 17min

Next.js Fundamentals

Next.js is a React framework that enables server-side rendering, static site generation, and other advanced features with minimal configuration.

Introduction to Next.js

Next.js was created by Vercel and provides:

  • Server-side rendering (SSR): Renders pages on the server for better SEO and initial load performance
  • Static site generation (SSG): Pre-renders pages at build time for optimal performance
  • Automatic code splitting: Only loads JavaScript needed for each page
  • Built-in CSS/Sass support: Import CSS/Sass files directly in components
  • API routes: Create API endpoints as Node.js serverless functions
  • Developer experience: Hot reloading, error reporting, and more

Setting Up a Next.js Project

Creating a New Project

1
2
3
npx create-next-app my-nextjs-app
cd my-nextjs-app
npm run dev

Project Structure

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
my-nextjs-app/
├── .next/ # Build output directory
├── node_modules/ # Dependencies
├── pages/ # Application pages and API routes
│ ├── api/ # API routes
│ ├── _app.js # Custom App component
│ ├── _document.js # Custom Document component
│ └── index.js # Home page
├── public/ # Static assets
├── styles/ # CSS/Sass files
├── components/ # React components
├── .gitignore # Git ignore file
├── next.config.js # Next.js configuration
├── package.json # Project dependencies and scripts
└── README.md # Project documentation

Pages and Routing

Next.js has a file-system based router built on the concept of pages:

Basic Pages

1
2
3
4
5
6
7
8
9
// pages/index.js - Route: /
export default function Home() {
return <h1>Home Page</h1>;
}

// pages/about.js - Route: /about
export default function About() {
return <h1>About Page</h1>;
}

Dynamic Routes

1
2
3
4
5
6
7
8
9
// pages/posts/[id].js - Route: /posts/:id
import { useRouter } from 'next/router';

export default function Post() {
const router = useRouter();
const { id } = router.query;

return <h1>Post: {id}</h1>;
}

Nested Dynamic Routes

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// pages/[category]/[product].js - Route: /:category/:product
import { useRouter } from 'next/router';

export default function Product() {
const router = useRouter();
const { category, product } = router.query;

return (
<div>
<h1>Category: {category}</h1>
<h2>Product: {product}</h2>
</div>
);
}

Catch-All Routes

1
2
3
4
5
6
7
8
9
10
// pages/blog/[...slug].js - Routes: /blog/2020/01/01, /blog/category/post
import { useRouter } from 'next/router';

export default function BlogPost() {
const router = useRouter();
const { slug } = router.query;
// slug will be an array like ['2020', '01', '01'] or ['category', 'post']

return <h1>Blog Post: {slug?.join('/')}</h1>;
}

Navigation

Link Component

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import Link from 'next/link';

export default function Navigation() {
return (
<nav>
<Link href="/">Home</Link>
<Link href="/about">About</Link>
<Link href="/blog/hello-world">Blog Post</Link>

{/* With dynamic route */}
<Link href={`/posts/${postId}`}>Post</Link>

{/* With object syntax for complex paths */}
<Link href={{
pathname: '/posts/[id]',
query: { id: postId },
}}>
Post
</Link>
</nav>
);
}

Programmatic Navigation

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import { useRouter } from 'next/router';

export default function LoginButton() {
const router = useRouter();

function handleLogin() {
// Authenticate user
router.push('/dashboard');

// Or with dynamic route
router.push(`/profile/${userId}`);

// Or with URL object
router.push({
pathname: '/posts/[id]',
query: { id: postId },
});
}

return <button onClick={handleLogin}>Log in</button>;
}

Data Fetching

Next.js has built-in data fetching methods for different use cases:

getStaticProps (Static Site Generation)

Use for data available at build time:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// pages/posts.js
export default function Posts({ posts }) {
return (
<ul>
{posts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
);
}

// This runs at build time in production
export async function getStaticProps() {
const res = await fetch('https://jsonplaceholder.typicode.com/posts');
const posts = await res.json();

return {
props: {
posts,
},
// Revalidate every 10 seconds (incremental static regeneration)
revalidate: 10,
};
}

getStaticPaths (Dynamic Routes with SSG)

Use with dynamic routes and getStaticProps:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
// pages/posts/[id].js
export default function Post({ post }) {
return (
<div>
<h1>{post.title}</h1>
<p>{post.body}</p>
</div>
);
}

// Generate the paths at build time
export async function getStaticPaths() {
const res = await fetch('https://jsonplaceholder.typicode.com/posts');
const posts = await res.json();

// Get the paths we want to pre-render
const paths = posts.slice(0, 10).map((post) => ({
params: { id: post.id.toString() },
}));

return {
paths,
// fallback: false means other routes 404
// fallback: true generates pages on demand
// fallback: 'blocking' is similar but waits for generation
fallback: 'blocking',
};
}

// Fetch data for each page
export async function getStaticProps({ params }) {
const res = await fetch(`https://jsonplaceholder.typicode.com/posts/${params.id}`);
const post = await res.json();

// If no post is found, return 404
if (!post.id) {
return {
notFound: true,
};
}

return {
props: {
post,
},
revalidate: 60, // Regenerate after 60 seconds
};
}

getServerSideProps (Server-Side Rendering)

Use for data that must be fetched at request time:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// pages/profile.js
export default function Profile({ user }) {
return (
<div>
<h1>{user.name}</h1>
<p>Email: {user.email}</p>
</div>
);
}

// This runs on every request
export async function getServerSideProps(context) {
// context contains request parameters, headers, cookies
const { req, res, params, query } = context;

// Example: cookie-based authentication
const cookies = req.headers.cookie;

// Fetch data from an API
const response = await fetch('https://api.example.com/user', {
headers: {
cookie: cookies
}
});

const user = await response.json();

// Redirect if not authenticated
if (!user) {
return {
redirect: {
destination: '/login',
permanent: false,
},
};
}

return {
props: {
user,
},
};
}

Client-Side Data Fetching

For data that doesn’t need SEO or can be loaded after the page loads:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import { useState, useEffect } from 'react';

export default function Dashboard() {
const [data, setData] = useState(null);
const [isLoading, setIsLoading] = useState(true);

useEffect(() => {
async function fetchData() {
const response = await fetch('/api/dashboard-data');
const data = await response.json();
setData(data);
setIsLoading(false);
}

fetchData();
}, []);

if (isLoading) return <p>Loading...</p>;
if (!data) return <p>No data</p>;

return (
<div>
<h1>Dashboard</h1>
<p>Total users: {data.totalUsers}</p>
</div>
);
}

SWR for Data Fetching

Next.js recommends SWR for client-side data fetching:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import useSWR from 'swr';

// Reusable fetcher function
const fetcher = (url) => fetch(url).then((res) => res.json());

export default function Profile() {
const { data, error, isLoading } = useSWR('/api/user', fetcher);

if (error) return <div>Failed to load</div>;
if (isLoading) return <div>Loading...</div>;

return (
<div>
<h1>Hello {data.name}!</h1>
<p>Email: {data.email}</p>
</div>
);
}

API Routes

Next.js allows you to create API endpoints within your application:

1
2
3
4
// pages/api/hello.js
export default function handler(req, res) {
res.status(200).json({ message: 'Hello World!' });
}

Request Methods

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// pages/api/users.js
export default function handler(req, res) {
const { method } = req;

switch (method) {
case 'GET':
// Get data from your database
res.status(200).json({ users: ['John', 'Jane'] });
break;
case 'POST':
// Create data in your database
res.status(201).json({ message: 'User created' });
break;
default:
res.setHeader('Allow', ['GET', 'POST']);
res.status(405).end(`Method ${method} Not Allowed`);
}
}

Dynamic API Routes

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// pages/api/users/[id].js
export default function handler(req, res) {
const { id } = req.query;
const { method } = req;

switch (method) {
case 'GET':
// Get user data from your database
res.status(200).json({ id, name: `User ${id}` });
break;
case 'PUT':
// Update user data in your database
res.status(200).json({ id, message: 'User updated' });
break;
case 'DELETE':
// Delete user data from your database
res.status(200).json({ id, message: 'User deleted' });
break;
default:
res.setHeader('Allow', ['GET', 'PUT', 'DELETE']);
res.status(405).end(`Method ${method} Not Allowed`);
}
}

Styling in Next.js

Next.js supports various styling options:

Global CSS

1
2
3
4
5
6
// pages/_app.js
import '../styles/globals.css';

export default function MyApp({ Component, pageProps }) {
return <Component {...pageProps} />;
}

CSS Modules

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// styles/Home.module.css
.container {
min-height: 100vh;
padding: 0 0.5rem;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}

// pages/index.js
import styles from '../styles/Home.module.css';

export default function Home() {
return (
<div className={styles.container}>
<h1>Hello Next.js</h1>
</div>
);
}

Sass/SCSS

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// Install Sass
// npm install sass

// styles/Home.module.scss
.container {
min-height: 100vh;

.title {
color: #0070f3;
text-decoration: none;

&:hover {
text-decoration: underline;
}
}
}

// pages/index.js
import styles from '../styles/Home.module.scss';

export default function Home() {
return (
<div className={styles.container}>
<h1 className={styles.title}>Hello Next.js</h1>
</div>
);
}

CSS-in-JS (Styled JSX)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
export default function Button() {
return (
<>
<button>Click me</button>
<style jsx>{`
button {
background: #0070f3;
color: white;
border: none;
padding: 8px 16px;
border-radius: 4px;
cursor: pointer;
}
button:hover {
background: #0051a2;
}
`}</style>
</>
);
}

Image Optimization

Next.js includes an Image component for automatic image optimization:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import Image from 'next/image';

export default function Avatar() {
return (
<div>
{/* Local images require width and height */}
<Image
src="/profile.jpg" // Route to the image file in public directory
alt="Profile"
width={500}
height={500}
priority // Load image immediately (LCP)
/>

{/* Remote images require width, height, or layout="fill" */}
<Image
src="https://example.com/profile.jpg"
alt="Profile"
width={500}
height={500}
loader={customLoader} // Optional custom loader
/>

{/* Fill container (parent must have position: relative) */}
<div style={{ position: 'relative', width: '100%', height: '300px' }}>
<Image
src="/banner.jpg"
alt="Banner"
fill
style={{ objectFit: 'cover' }}
/>
</div>
</div>
);
}

Head Management

Next.js has a built-in component for managing <head> elements:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import Head from 'next/head';

export default function Page() {
return (
<>
<Head>
<title>My Page Title</title>
<meta name="description" content="My page description" />
<meta property="og:title" content="My Page Title" />
<meta property="og:description" content="My page description" />
<meta property="og:image" content="https://example.com/og-image.jpg" />
<link rel="icon" href="/favicon.ico" />
</Head>
<main>
<h1>Welcome to my page</h1>
</main>
</>
);
}

Custom Document and App

Custom Document

Customize the HTML document structure:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// pages/_document.js
import Document, { Html, Head, Main, NextScript } from 'next/document';

export default class MyDocument extends Document {
render() {
return (
<Html lang="en">
<Head>
<link
href="https://fonts.googleapis.com/css2?family=Inter&display=swap"
rel="stylesheet"
/>
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
);
}
}

Custom App

For persistent layouts, global state, or additional providers:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// pages/_app.js
import '../styles/globals.css';
import Layout from '../components/Layout';
import { ThemeProvider } from '../context/ThemeContext';

export default function MyApp({ Component, pageProps }) {
// Use the layout defined at the page level, if available
const getLayout = Component.getLayout || ((page) => <Layout>{page}</Layout>);

return (
<ThemeProvider>
{getLayout(<Component {...pageProps} />)}
</ThemeProvider>
);
}

// pages/index.js - Example of page-level layout
import AdminLayout from '../components/AdminLayout';

export default function Home() {
return <h1>Home Page</h1>;
}

// Define a custom layout for this page
Home.getLayout = function getLayout(page) {
return <AdminLayout>{page}</AdminLayout>;
};

Environment Variables

Next.js comes with built-in support for environment variables:

1
2
3
4
5
# .env.local (not committed to git)
DB_PASSWORD=supersecretpassword

# .env (committed to git)
NEXT_PUBLIC_API_URL=https://api.example.com
1
2
3
4
5
// Access in Node.js environment (SSR, API routes)
console.log(process.env.DB_PASSWORD);

// Access in browser (must be prefixed with NEXT_PUBLIC_)
console.log(process.env.NEXT_PUBLIC_API_URL);

Deployment

Static Export

For static sites that can be hosted anywhere:

1
2
3
4
5
6
7
# Configure in next.config.js
module.exports = {
output: 'export',
}

# Build and export
npm run build

Vercel (Recommended)

Deploy directly from Git:

1
2
3
4
5
# Install Vercel CLI
npm install -g vercel

# Deploy
vercel

Other Hosting Providers

For Node.js hosting:

1
2
3
4
5
# Build the application
npm run build

# Start the production server
npm start

Advanced Features

Middleware

Execute code before requests are completed:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';

export function middleware(request: NextRequest) {
// Get a cookie
const theme = request.cookies.get('theme');

// Clone the request headers
const requestHeaders = new Headers(request.headers);

// Set a request header
requestHeaders.set('x-theme', theme?.value || 'light');

// You can also redirect
if (!request.cookies.get('authenticated') && request.nextUrl.pathname.startsWith('/dashboard')) {
return NextResponse.redirect(new URL('/login', request.url));
}

// Continue the request with modified headers
return NextResponse.next({
request: {
headers: requestHeaders,
},
});
}

// Configure which paths should be matched
export const config = {
matcher: ['/dashboard/:path*', '/api/:path*'],
};

Internationalized Routing

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
// next.config.js
module.exports = {
i18n: {
// Locales you want to support
locales: ['en', 'fr', 'es'],
// Default locale
defaultLocale: 'en',
// Domain-specific locales
domains: [
{
domain: 'example.com',
defaultLocale: 'en',
},
{
domain: 'example.fr',
defaultLocale: 'fr',
},
{
domain: 'example.es',
defaultLocale: 'es',
},
],
},
};

// Use in components
import { useRouter } from 'next/router';

export default function LocaleSwitcher() {
const router = useRouter();
const { locales, locale: activeLocale } = router;

const handleLocaleChange = (e) => {
const locale = e.target.value;
router.push(router.pathname, router.asPath, { locale });
};

return (
<select
onChange={handleLocaleChange}
defaultValue={activeLocale}
>
{locales.map((locale) => (
<option key={locale} value={locale}>
{locale}
</option>
))}
</select>
);
}

Best Practices

  1. Use Static Generation when possible for better performance
  2. Incremental Static Regeneration for dynamic content that changes infrequently
  3. Server-Side Rendering for pages that need request-time data
  4. Client-side fetching for private, user-specific data
  5. Optimize images with the Next.js Image component
  6. Code splitting happens automatically per page
  7. Pre-fetch links automatically with the Link component
  8. API Routes for backend functionality instead of external APIs
  9. Environment Variables for configuration
  10. TypeScript support for better developer experience

Learning Resources

  • Next.js Documentation
  • Next.js GitHub Repository
  • Next.js Learn Course
  • Vercel Tutorials
  • Next.js Discord Community
  • Next.js Weekly Newsletter
  • Mastering Next.js
  • Web Development
  • JavaScript
  • React
  • Frontend Framework
  • Next.js
  • SSR

show all >>

React Hooks

  2023-06-10
字数统计: 4.2k字   |   阅读时长: 26min

React Hooks

React Hooks were introduced in React 16.8 as a way to use state and other React features without writing a class. They enable functional components to have capabilities previously only available in class components.

Introduction to Hooks

Hooks are functions that let you “hook into” React state and lifecycle features from function components. They don’t work inside classes — they let you use React without classes.

Why Hooks?

  • Reuse stateful logic between components without complex patterns like render props or higher-order components
  • Split complex components into smaller functions based on related pieces (such as setting up a subscription or fetching data)
  • Use more of React’s features without classes - avoiding issues with this, binding event handlers, and making hot reloading more reliable

Basic Hooks

useState

The useState hook lets you add React state to functional components:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import React, { useState } from 'react';

function Counter() {
// Declare a state variable "count" with initial value 0
const [count, setCount] = useState(0);

return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}

Array Destructuring

1
2
3
4
// This is equivalent to:
const countState = useState(0);
const count = countState[0];
const setCount = countState[1];

Multiple State Variables

1
2
3
4
5
6
7
function UserForm() {
const [name, setName] = useState('');
const [age, setAge] = useState(0);
const [email, setEmail] = useState('');

// ...
}

Functional Updates

When the new state depends on the previous state, pass a function to the state setter:

1
2
3
4
5
6
7
8
9
10
11
function Counter() {
const [count, setCount] = useState(0);

return (
<>
Count: {count}
<button onClick={() => setCount(prevCount => prevCount + 1)}>+</button>
<button onClick={() => setCount(prevCount => prevCount - 1)}>-</button>
</>
);
}

Lazy Initial State

If the initial state is expensive to compute, you can provide a function which will be executed only on the initial render:

1
2
3
4
const [state, setState] = useState(() => {
const initialState = someExpensiveComputation(props);
return initialState;
});

useEffect

The useEffect hook lets you perform side effects in functional components. It serves the same purpose as componentDidMount, componentDidUpdate, and componentWillUnmount in React classes.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import React, { useState, useEffect } from 'react';

function Example() {
const [count, setCount] = useState(0);

// Similar to componentDidMount and componentDidUpdate:
useEffect(() => {
// Update the document title using the browser API
document.title = `You clicked ${count} times`;
});

return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}

Effect with Cleanup

Some effects need cleanup, like subscriptions or timers:

1
2
3
4
5
6
7
8
useEffect(() => {
const subscription = props.source.subscribe();

// Cleanup function (equivalent to componentWillUnmount):
return () => {
subscription.unsubscribe();
};
});

Controlling When Effects Run

You can control when effects run by providing a dependency array as the second argument:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Only runs on mount and unmount (empty dependencies)
useEffect(() => {
console.log('Component mounted');
return () => console.log('Component unmounted');
}, []);

// Runs when count changes
useEffect(() => {
document.title = `Count: ${count}`;
}, [count]);

// Runs on every render (no dependency array)
useEffect(() => {
console.log('Component rendered');
});

Multiple Effects

You can use multiple useEffect calls in the same component to separate concerns:

1
2
3
4
5
6
7
8
9
10
11
12
13
function UserProfile({ userId }) {
// These could be separated into different useEffects
useEffect(() => {
// Fetch user data
fetchUser(userId).then(data => setUser(data));
}, [userId]);

useEffect(() => {
// Set up analytics
const analytics = setupAnalytics();
return () => analytics.cleanup();
}, []);
}

useContext

The useContext hook lets you subscribe to React context without introducing nesting:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import React, { useContext } from 'react';

// Create a context
const ThemeContext = React.createContext('light');

function ThemedButton() {
// Use the context
const theme = useContext(ThemeContext);

return <button className={theme}>Themed Button</button>;
}

function App() {
return (
<ThemeContext.Provider value="dark">
<ThemedButton />
</ThemeContext.Provider>
);
}

Additional Hooks

useReducer

useReducer is usually preferable to useState when you have complex state logic that involves multiple sub-values or when the next state depends on the previous one:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import React, { useReducer } from 'react';

// Reducer function
function reducer(state, action) {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
default:
throw new Error();
}
}

function Counter() {
// Initialize state with useReducer
const [state, dispatch] = useReducer(reducer, { count: 0 });

return (
<>
Count: {state.count}
<button onClick={() => dispatch({ type: 'increment' })}>+</button>
<button onClick={() => dispatch({ type: 'decrement' })}>-</button>
</>
);
}

Lazy Initialization

1
2
// The initialArg (second argument) is passed to the init function (third argument)
const [state, dispatch] = useReducer(reducer, initialArg, init);

When to Use

  • Complex state transitions
  • Multiple sub-values in state
  • When the next state depends on the previous one
  • When your state logic is complex enough to be extracted outside the component

useCallback

useCallback returns a memoized version of the callback that only changes if one of the dependencies has changed:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import React, { useState, useCallback } from 'react';

function Parent() {
const [count, setCount] = useState(0);

// This function is recreated only when count changes
const handleClick = useCallback(() => {
console.log(`Clicked: ${count}`);
}, [count]);

return <Child onClick={handleClick} />;
}

// Child uses React.memo to prevent unnecessary renders
const Child = React.memo(({ onClick }) => {
console.log("Child rendered");
return <button onClick={onClick}>Click me</button>;
});

useMemo

useMemo returns a memoized value that only recalculates when one of its dependencies changes:

1
2
3
4
5
6
7
8
9
10
11
import React, { useMemo, useState } from 'react';

function ExpensiveCalculation({ a, b }) {
// Value is only recalculated when a or b changes
const result = useMemo(() => {
console.log('Calculating...');
return a * b;
}, [a, b]);

return <div>Result: {result}</div>;
}

When to Use

  • Complex computations that might slow down rendering
  • Values used in multiple places in the render
  • Values used in the dependency arrays of other hooks
  • Preventing unnecessary renders of child components

useRef

useRef returns a mutable ref object whose .current property is initialized to the passed argument. The object persists for the full lifetime of the component:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import React, { useRef, useEffect } from 'react';

function TextInputWithFocusButton() {
// Create a ref
const inputRef = useRef(null);

// Focus the input element
const focusInput = () => {
inputRef.current.focus();
};

return (
<>
<input ref={inputRef} type="text" />
<button onClick={focusInput}>Focus Input</button>
</>
);
}

UseRef for Variables

useRef can be used for more than just DOM refs. It’s useful for keeping any mutable value around without causing a re-render when it changes:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function TimerExample() {
const [seconds, setSeconds] = useState(0);
const timerRef = useRef(null);

useEffect(() => {
timerRef.current = setInterval(() => {
setSeconds(prev => prev + 1);
}, 1000);

return () => clearInterval(timerRef.current);
}, []);

const stopTimer = () => {
clearInterval(timerRef.current);
};

return (
<div>
<p>Seconds: {seconds}</p>
<button onClick={stopTimer}>Stop</button>
</div>
);
}

useImperativeHandle

useImperativeHandle customizes the instance value that is exposed when using ref with forwardRef:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import React, { useRef, useImperativeHandle, forwardRef } from 'react';

// Create a forward ref component
const FancyInput = forwardRef((props, ref) => {
const inputRef = useRef();

// Expose only certain methods to parent
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current.focus();
},
reset: () => {
inputRef.current.value = '';
}
}));

return <input ref={inputRef} />;
});

function Parent() {
const fancyInputRef = useRef();

return (
<>
<FancyInput ref={fancyInputRef} />
<button onClick={() => fancyInputRef.current.focus()}>
Focus Input
</button>
<button onClick={() => fancyInputRef.current.reset()}>
Reset
</button>
</>
);
}

useLayoutEffect

useLayoutEffect is identical to useEffect, but it fires synchronously after all DOM mutations. Use it to read layout from the DOM and synchronously re-render:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function Tooltip({ text, position }) {
const tooltipRef = useRef();

// This runs synchronously after DOM mutations
useLayoutEffect(() => {
const element = tooltipRef.current;
// These measurements happen synchronously after DOM mutations
const { height, width } = element.getBoundingClientRect();

// Position the tooltip based on its size and the position prop
element.style.top = `${position.y - height}px`;
element.style.left = `${position.x - width / 2}px`;
}, [position]);

return <div ref={tooltipRef} className="tooltip">{text}</div>;
}

useEffect vs useLayoutEffect

  • useEffect runs asynchronously after render is painted to the screen
  • useLayoutEffect runs synchronously after DOM mutations but before the browser paints
  • Use useLayoutEffect for DOM measurements and mutations that should be visible immediately

useDebugValue

useDebugValue can be used to display a label for custom hooks in React DevTools:

1
2
3
4
5
6
7
8
9
10
import React, { useState, useDebugValue } from 'react';

function useCustomHook(initialValue) {
const [value, setValue] = useState(initialValue);

// This will show up in React DevTools
useDebugValue(value ? 'Online' : 'Offline');

return [value, setValue];
}

Custom Hooks

Custom Hooks let you extract component logic into reusable functions. A custom Hook is a JavaScript function whose name starts with “use” and that may call other Hooks:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import { useState, useEffect } from 'react';

// Custom hook for window dimensions
function useWindowSize() {
const [windowSize, setWindowSize] = useState({
width: window.innerWidth,
height: window.innerHeight,
});

useEffect(() => {
// Handler to call on window resize
function handleResize() {
setWindowSize({
width: window.innerWidth,
height: window.innerHeight,
});
}

// Add event listener
window.addEventListener('resize', handleResize);

// Call handler right away to update initial size
handleResize();

// Remove event listener on cleanup
return () => window.removeEventListener('resize', handleResize);
}, []); // Empty array ensures this only runs on mount and unmount

return windowSize;
}

// Using the custom hook
function MyComponent() {
const size = useWindowSize();

return (
<div>
Window width: {size.width}px, height: {size.height}px
</div>
);
}

Common Custom Hooks

useFetch - For Data Fetching

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
function useFetch(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);

useEffect(() => {
const abortController = new AbortController();

async function fetchData() {
try {
setLoading(true);
const response = await fetch(url, { signal: abortController.signal });

if (!response.ok) {
throw new Error(`HTTP error: ${response.status}`);
}

const json = await response.json();
setData(json);
setError(null);
} catch (err) {
if (err.name !== 'AbortError') {
setError(err.message);
setData(null);
}
} finally {
setLoading(false);
}
}

fetchData();

return () => abortController.abort();
}, [url]);

return { data, loading, error };
}

// Usage
function Posts() {
const { data, loading, error } = useFetch('https://jsonplaceholder.typicode.com/posts');

if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error}</div>;

return (
<ul>
{data?.map(post => (
<li key={post.id}>{post.title}</li>
))}
</ul>
);
}

useLocalStorage - For Persistent State

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
function useLocalStorage(key, initialValue) {
// State to store our value
const [storedValue, setStoredValue] = useState(() => {
try {
// Get from local storage by key
const item = window.localStorage.getItem(key);
// Parse stored json or if none return initialValue
return item ? JSON.parse(item) : initialValue;
} catch (error) {
// If error also return initialValue
console.log(error);
return initialValue;
}
});

// Return a wrapped version of useState's setter function that
// persists the new value to localStorage.
const setValue = value => {
try {
// Allow value to be a function so we have same API as useState
const valueToStore =
value instanceof Function ? value(storedValue) : value;
// Save state
setStoredValue(valueToStore);
// Save to local storage
window.localStorage.setItem(key, JSON.stringify(valueToStore));
} catch (error) {
// A more advanced implementation would handle the error case
console.log(error);
}
};

return [storedValue, setValue];
}

// Usage
function App() {
const [name, setName] = useLocalStorage('name', 'Bob');

return (
<div>
<input
type="text"
placeholder="Enter your name"
value={name}
onChange={e => setName(e.target.value)}
/>
</div>
);
}

useForm - For Form Handling

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
function useForm(initialValues = {}) {
const [values, setValues] = useState(initialValues);
const [errors, setErrors] = useState({});

const handleChange = (e) => {
const { name, value } = e.target;
setValues(prevValues => ({
...prevValues,
[name]: value
}));
};

const handleSubmit = (onSubmit) => (e) => {
e.preventDefault();
onSubmit(values);
};

const reset = () => {
setValues(initialValues);
setErrors({});
};

return {
values,
errors,
handleChange,
handleSubmit,
reset,
setValues,
setErrors
};
}

// Usage
function SignupForm() {
const { values, handleChange, handleSubmit } = useForm({
email: '',
password: ''
});

const submitForm = (formValues) => {
console.log('Form submitted:', formValues);
// Handle submission logic here
};

return (
<form onSubmit={handleSubmit(submitForm)}>
<input
type="email"
name="email"
value={values.email}
onChange={handleChange}
placeholder="Email"
/>
<input
type="password"
name="password"
value={values.password}
onChange={handleChange}
placeholder="Password"
/>
<button type="submit">Sign Up</button>
</form>
);
}

Rules of Hooks

There are two essential rules to follow when using Hooks:

Only Call Hooks at the Top Level

Don’t call Hooks inside loops, conditions, or nested functions. Always use Hooks at the top level of your React function.

❌ Incorrect:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function Bad() {
const [count, setCount] = useState(0);

if (count > 0) {
// This is against the rules!
const [name, setName] = useState('John');
}

// This is also against the rules!
for (let i = 0; i < count; i++) {
useEffect(() => {
console.log(i);
});
}

// ...
}

✅ Correct:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function Good() {
const [count, setCount] = useState(0);
const [name, setName] = useState('John');

useEffect(() => {
console.log(count);
}, [count]);

// Conditional rendering inside useEffect is ok
useEffect(() => {
if (count > 0) {
console.log(`Count: ${count}, Name: ${name}`);
}
}, [count, name]);

// ...
}

Only Call Hooks from React Functions

Don’t call Hooks from regular JavaScript functions. You can:

  • Call Hooks from React function components
  • Call Hooks from custom Hooks

❌ Incorrect:

1
2
3
4
5
// This is not a React component or a custom Hook
function normalFunction() {
// This is against the rules!
const [count, setCount] = useState(0);
}

✅ Correct:

1
2
3
4
5
6
7
8
9
10
11
// This is a React function component
function ComponentFunction() {
const [count, setCount] = useState(0);
return <div>{count}</div>;
}

// This is a custom Hook (note the 'use' prefix)
function useCustomHook() {
const [count, setCount] = useState(0);
return count;
}

Advanced Patterns with Hooks

State Reducer Pattern

Combine useReducer with custom logic to create reusable components with customizable state management:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
// The base useCounter hook with reducer
function useCounter(initialState, reducer) {
const [state, dispatch] = useReducer(reducer, initialState);

const increment = () => dispatch({ type: 'increment' });
const decrement = () => dispatch({ type: 'decrement' });
const reset = () => dispatch({ type: 'reset', payload: initialState });

return [state, { increment, decrement, reset }];
}

// Default reducer if none is provided
function defaultReducer(state, action) {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
case 'reset':
return action.payload;
default:
return state;
}
}

// Usage with custom reducer
function Counter() {
// Custom reducer that doesn't allow negative numbers
function customReducer(state, action) {
switch (action.type) {
case 'decrement':
// Don't allow negative numbers
return { count: Math.max(0, state.count - 1) };
default:
// For other actions, use the default reducer
return defaultReducer(state, action);
}
}

const [state, { increment, decrement, reset }] = useCounter({ count: 0 }, customReducer);

return (
<div>
Count: {state.count}
<button onClick={increment}>+</button>
<button onClick={decrement}>-</button>
<button onClick={reset}>Reset</button>
</div>
);
}

Compound Components Pattern

Use React context to create related components that share state:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
import React, { useState, useContext, createContext } from 'react';

// Create context for the toggle state
const ToggleContext = createContext();

// Main Toggle component
function Toggle({ children }) {
const [on, setOn] = useState(false);
const toggle = () => setOn(!on);

return (
<ToggleContext.Provider value={{ on, toggle }}>
{children}
</ToggleContext.Provider>
);
}

// Child components
Toggle.On = function ToggleOn({ children }) {
const { on } = useContext(ToggleContext);
return on ? children : null;
};

Toggle.Off = function ToggleOff({ children }) {
const { on } = useContext(ToggleContext);
return on ? null : children;
};

Toggle.Button = function ToggleButton(props) {
const { on, toggle } = useContext(ToggleContext);
return (
<button onClick={toggle} {...props}>
{on ? 'ON' : 'OFF'}
</button>
);
};

// Usage
function App() {
return (
<Toggle>
<Toggle.Button />
<Toggle.On>The toggle is on!</Toggle.On>
<Toggle.Off>The toggle is off!</Toggle.Off>
</Toggle>
);
}

Hooks Composition

Compose multiple hooks together for complex functionality:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// Combine multiple hooks into one custom hook
function useUserProfile(userId) {
// Use our custom hooks
const { data: user, loading: userLoading, error: userError } = useFetch(`/api/users/${userId}`);
const { data: posts, loading: postsLoading, error: postsError } = useFetch(`/api/users/${userId}/posts`);
const { data: friends, loading: friendsLoading, error: friendsError } = useFetch(`/api/users/${userId}/friends`);

// Combine the loading states
const loading = userLoading || postsLoading || friendsLoading;

// Combine the error states
const error = userError || postsError || friendsError;

// Combine the data
const data = {
user,
posts,
friends
};

return { data, loading, error };
}

// Usage
function UserProfile({ userId }) {
const { data, loading, error } = useUserProfile(userId);

if (loading) return <Spinner />;
if (error) return <ErrorMessage error={error} />;

return (
<div>
<h1>{data.user.name}</h1>
<h2>Posts ({data.posts.length})</h2>
<PostList posts={data.posts} />
<h2>Friends ({data.friends.length})</h2>
<FriendList friends={data.friends} />
</div>
);
}

Performance Optimization

When to Memoize

  • Use React.memo for components that render often with the same props
  • Use useCallback for event handlers passed to optimized child components
  • Use useMemo for expensive calculations used in rendering
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
import React, { useState, useCallback, useMemo } from 'react';

// Expensive calculation
function calculateFactorial(n) {
if (n <= 1) return 1;
return n * calculateFactorial(n - 1);
}

function FactorialCalculator() {
const [number, setNumber] = useState(5);
const [darkMode, setDarkMode] = useState(false);

// Memoize expensive calculation
const factorial = useMemo(() => {
console.log("Calculating factorial...");
return calculateFactorial(number);
}, [number]);

// Memoize event handler
const handleChange = useCallback((e) => {
setNumber(parseInt(e.target.value));
}, []);

// Memoize style object
const style = useMemo(() => ({
backgroundColor: darkMode ? '#333' : '#FFF',
color: darkMode ? '#FFF' : '#333',
}), [darkMode]);

return (
<div style={style}>
<label>
Number:
<input type="number" value={number} onChange={handleChange} />
</label>
<p>Factorial: {factorial}</p>
<button onClick={() => setDarkMode(!darkMode)}>
Toggle Dark Mode
</button>
</div>
);
}

// Use React.memo to prevent re-rendering when props don't change
const MemoizedFactorialDisplay = React.memo(function FactorialDisplay({ number, factorial }) {
console.log(`Rendering FactorialDisplay for ${number}`);
return <p>Factorial of {number} is {factorial}</p>;
});

Avoiding Common Performance Mistakes

  1. Creating Functions in Render: Each render creates new function instances
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Bad: Function recreated on every render
function BadExample() {
const [count, setCount] = useState(0);

return <button onClick={() => setCount(count + 1)}>Count: {count}</button>;
}

// Better: Use useCallback for handlers passed to child components
function BetterExample() {
const [count, setCount] = useState(0);

const handleClick = useCallback(() => {
setCount(prevCount => prevCount + 1);
}, []);

return <button onClick={handleClick}>Count: {count}</button>;
}
  1. Creating Objects in Render: Similar to functions, new objects cause unnecessary renders
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// Bad: New object created on every render
function BadStyles() {
const [active, setActive] = useState(false);

// This style object is recreated on every render
const style = {
color: active ? 'red' : 'black',
fontWeight: active ? 'bold' : 'normal'
};

return <div style={style}>Text</div>;
}

// Better: Memoize object creation
function BetterStyles() {
const [active, setActive] = useState(false);

const style = useMemo(() => ({
color: active ? 'red' : 'black',
fontWeight: active ? 'bold' : 'normal'
}), [active]);

return <div style={style}>Text</div>;
}
  1. Not Adding Dependencies to useEffect: Can lead to stale closures
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// Bad: Missing dependency
function BadEffect({ id }) {
const [data, setData] = useState(null);

useEffect(() => {
fetchData(id).then(setData);
// Missing id dependency ⚠️
}, []);

return <div>{data ? data.name : 'Loading...'}</div>;
}

// Better: Include all dependencies
function GoodEffect({ id }) {
const [data, setData] = useState(null);

useEffect(() => {
fetchData(id).then(setData);
}, [id]); // ✅ id dependency included

return <div>{data ? data.name : 'Loading...'}</div>;
}

Testing Hooks

Basic Testing with React Testing Library

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
// useCounter.js
import { useState } from 'react';

function useCounter(initialCount = 0) {
const [count, setCount] = useState(initialCount);

const increment = () => setCount(prevCount => prevCount + 1);
const decrement = () => setCount(prevCount => prevCount - 1);
const reset = () => setCount(initialCount);

return { count, increment, decrement, reset };
}

export default useCounter;

// useCounter.test.js
import { renderHook, act } from '@testing-library/react-hooks';
import useCounter from './useCounter';

test('should increment counter', () => {
const { result } = renderHook(() => useCounter());

act(() => {
result.current.increment();
});

expect(result.current.count).toBe(1);
});

test('should decrement counter', () => {
const { result } = renderHook(() => useCounter());

act(() => {
result.current.decrement();
});

expect(result.current.count).toBe(-1);
});

test('should reset counter', () => {
const { result } = renderHook(() => useCounter(10));

act(() => {
result.current.reset();
});

expect(result.current.count).toBe(10);
});

Testing Components that Use Hooks

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
// Counter.js
import React from 'react';
import useCounter from './useCounter';

function Counter() {
const { count, increment, decrement, reset } = useCounter(0);

return (
<div>
<p>Count: {count}</p>
<button onClick={increment}>Increment</button>
<button onClick={decrement}>Decrement</button>
<button onClick={reset}>Reset</button>
</div>
);
}

export default Counter;

// Counter.test.js
import React from 'react';
import { render, screen, fireEvent } from '@testing-library/react';
import Counter from './Counter';

test('renders counter with buttons', () => {
render(<Counter />);

expect(screen.getByText(/count: 0/i)).toBeInTheDocument();
expect(screen.getByText('Increment')).toBeInTheDocument();
expect(screen.getByText('Decrement')).toBeInTheDocument();
expect(screen.getByText('Reset')).toBeInTheDocument();
});

test('increments the count when increment button is clicked', () => {
render(<Counter />);

fireEvent.click(screen.getByText('Increment'));

expect(screen.getByText(/count: 1/i)).toBeInTheDocument();
});

test('decrements the count when decrement button is clicked', () => {
render(<Counter />);

fireEvent.click(screen.getByText('Decrement'));

expect(screen.getByText(/count: -1/i)).toBeInTheDocument();
});

test('resets the count when reset button is clicked', () => {
render(<Counter />);

fireEvent.click(screen.getByText('Increment'));
fireEvent.click(screen.getByText('Increment'));
fireEvent.click(screen.getByText('Reset'));

expect(screen.getByText(/count: 0/i)).toBeInTheDocument();
});

Learning Resources

  • React Hooks Documentation - Official docs
  • Thinking in React Hooks - Visual guide to hooks
  • useHooks - Collection of custom hooks
  • React Hooks Cheatsheet - Quick reference
  • Epic React - Kent C. Dodds’ courses on React
  • Making Sense of React Hooks - Dan Abramov’s explanation
  • A Complete Guide to useEffect - Deep dive on useEffect
  • Web Development
  • JavaScript
  • React
  • Hooks
  • Frontend Framework

show all >>

React Fundamentals

  2023-06-09
字数统计: 3.6k字   |   阅读时长: 22min

React Fundamentals

React is a JavaScript library for building user interfaces, particularly single-page applications. It’s used to handle the view layer for web and mobile apps and allows you to create reusable UI components.

Introduction to React

React was created by Facebook and released in 2013. It’s maintained by Facebook and a community of individual developers and companies. React’s key features include:

  • Declarative: You describe what your UI should look like, and React efficiently updates and renders the right components when data changes.
  • Component-Based: Build encapsulated components that manage their own state, then compose them to make complex UIs.
  • Learn Once, Write Anywhere: React can also render on the server using Node, and power mobile apps using React Native.

Setting Up a React Application

Using Create React App

The easiest way to start with React is using Create React App:

1
2
3
npx create-react-app my-app
cd my-app
npm start

This creates a new React application with a development server, modern JavaScript features, and optimized production build.

Project Structure

A typical Create React App project has this structure:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
my-app/
README.md
node_modules/
package.json
public/
index.html
favicon.ico
src/
App.css
App.js
App.test.js
index.css
index.js
logo.svg

Manual Setup

For more control, you can set up React manually with webpack or another build tool:

1
2
3
4
5
mkdir my-react-app
cd my-react-app
npm init -y
npm install react react-dom
npm install --save-dev webpack webpack-cli webpack-dev-server babel-loader @babel/core @babel/preset-env @babel/preset-react html-webpack-plugin

Then create webpack and Babel configuration files.

React Components

Components are the core building blocks of React applications. Each component is a JavaScript function or class that optionally accepts inputs (props) and returns a React element describing what should appear on the screen.

Functional Components

Functional components are the simplest way to define a component:

1
2
3
4
5
6
7
8
9
10
11
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}

// Arrow function syntax
const Welcome = (props) => {
return <h1>Hello, {props.name}</h1>;
};

// Usage
<Welcome name="John" />

Class Components

Class components offer more features like local state and lifecycle methods:

1
2
3
4
5
6
7
8
9
10
import React, { Component } from 'react';

class Welcome extends Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}

// Usage
<Welcome name="John" />

Component Composition

Components can refer to other components in their output:

1
2
3
4
5
6
7
8
9
function App() {
return (
<div>
<Welcome name="Sara" />
<Welcome name="Cahal" />
<Welcome name="Edite" />
</div>
);
}

Extracting Components

Break down components into smaller components for better reuse and separation of concerns:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
function Comment(props) {
return (
<div className="Comment">
<UserInfo user={props.author} />
<div className="Comment-text">
{props.text}
</div>
<div className="Comment-date">
{formatDate(props.date)}
</div>
</div>
);
}

function UserInfo(props) {
return (
<div className="UserInfo">
<Avatar user={props.user} />
<div className="UserInfo-name">
{props.user.name}
</div>
</div>
);
}

function Avatar(props) {
return (
<img className="Avatar"
src={props.user.avatarUrl}
alt={props.user.name}
/>
);
}

JSX

JSX is a syntax extension to JavaScript that looks similar to HTML but comes with the full power of JavaScript. It’s recommended to use it with React to describe what the UI should look like.

JSX Basics

1
2
3
4
5
6
7
const element = <h1>Hello, world!</h1>;

const name = 'John Doe';
const element = <h1>Hello, {name}</h1>;

const user = { firstName: 'John', lastName: 'Doe' };
const element = <h1>Hello, {formatName(user)}</h1>;

JSX Represents Objects

Babel compiles JSX down to React.createElement() calls:

1
2
3
4
5
6
7
8
9
// This JSX:
const element = <h1 className="greeting">Hello, world!</h1>;

// Compiles to:
const element = React.createElement(
'h1',
{className: 'greeting'},
'Hello, world!'
);

JSX Attributes

1
2
3
4
5
6
7
8
// String literals
const element = <div tabIndex="0"></div>;

// JavaScript expressions
const element = <img src={user.avatarUrl}></img>;

// Self-closing tags
const element = <img src={user.avatarUrl} />;

JSX Children

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Empty elements
const element = <div></div>;

// Elements with children
const element = (
<div>
<h1>Hello!</h1>
<h2>Good to see you here.</h2>
</div>
);

// Mixed content
const element = (
<div>
<h1>Hello, {name}!</h1>
<p>You have {unreadMessages.length} unread messages.</p>
</div>
);

JSX Prevents Injection Attacks

React DOM escapes any values embedded in JSX before rendering them, helping to prevent XSS attacks:

1
2
3
const title = response.potentiallyMaliciousInput;
// This is safe:
const element = <h1>{title}</h1>;

Props

Props (short for “properties”) are inputs to React components. They allow you to pass data from a parent component to a child component.

Passing Props

1
2
3
4
5
6
7
8
9
// Parent component passing props
function App() {
return <Welcome name="Sara" age={25} isAdmin={true} />;
}

// Child component receiving props
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}

Props are Read-Only

A component must never modify its own props. All React components must act like pure functions with respect to their props:

1
2
3
4
5
6
7
8
9
// Correct
function sum(a, b) {
return a + b;
}

// Incorrect (modifies input)
function withdraw(account, amount) {
account.total -= amount;
}

Default Props

You can define default values for props:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function Button(props) {
return <button className={props.className}>{props.label}</button>;
}

Button.defaultProps = {
className: 'default-button',
label: 'Click Me'
};

// For class components
class Button extends React.Component {
// ...
}

Button.defaultProps = {
className: 'default-button',
label: 'Click Me'
};

Type Checking with PropTypes

You can use PropTypes to document the intended types of properties:

1
2
3
4
5
6
7
8
9
10
11
import PropTypes from 'prop-types';

function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}

Welcome.propTypes = {
name: PropTypes.string.isRequired,
age: PropTypes.number,
isAdmin: PropTypes.bool
};

State and Lifecycle

State allows React components to change their output over time in response to user actions, network responses, and anything else.

Adding State to a Class Component

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {date: new Date()};
}

render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
</div>
);
}
}

Lifecycle Methods

Components have lifecycle methods that allow you to run code at particular times:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {date: new Date()};
}

// After the component output is rendered to the DOM
componentDidMount() {
this.timerID = setInterval(
() => this.tick(),
1000
);
}

// Before component is removed from the DOM
componentWillUnmount() {
clearInterval(this.timerID);
}

tick() {
this.setState({
date: new Date()
});
}

render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
</div>
);
}
}

Updating State

Never modify state directly; use setState() instead:

1
2
3
4
5
// Wrong
this.state.comment = 'Hello';

// Correct
this.setState({comment: 'Hello'});

State updates may be asynchronous:

1
2
3
4
5
6
7
8
9
// Wrong
this.setState({
counter: this.state.counter + this.props.increment,
});

// Correct
this.setState((state, props) => ({
counter: state.counter + props.increment
}));

State updates are merged:

1
2
3
4
5
6
7
8
9
this.state = {
posts: [],
comments: []
};

// Only updates posts, comments remains the same
this.setState({
posts: ['New post']
});

Data Flows Down

State is often called local or encapsulated, and is not accessible to any component other than the one that owns and sets it. A component may pass its state down as props to its child components:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function FormattedDate(props) {
return <h2>It is {props.date.toLocaleTimeString()}.</h2>;
}

class Clock extends React.Component {
// ...
render() {
return (
<div>
<h1>Hello, world!</h1>
<FormattedDate date={this.state.date} />
</div>
);
}
}

Handling Events

React events are named using camelCase, and you pass a function as the event handler, rather than a string:

1
2
3
4
5
6
7
8
9
// HTML
<button onclick="activateLasers()">
Activate Lasers
</button>

// React
<button onClick={activateLasers}>
Activate Lasers
</button>

Event Handlers

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Toggle extends React.Component {
constructor(props) {
super(props);
this.state = {isToggleOn: true};

// Binding is necessary to make `this` work in callback
this.handleClick = this.handleClick.bind(this);
}

handleClick() {
this.setState(state => ({
isToggleOn: !state.isToggleOn
}));
}

render() {
return (
<button onClick={this.handleClick}>
{this.state.isToggleOn ? 'ON' : 'OFF'}
</button>
);
}
}

Alternative Class Syntax with Public Class Fields

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Toggle extends React.Component {
// No constructor needed
state = {isToggleOn: true};

// Public class fields syntax ensures `this` is bound
handleClick = () => {
this.setState(state => ({
isToggleOn: !state.isToggleOn
}));
}

render() {
return (
<button onClick={this.handleClick}>
{this.state.isToggleOn ? 'ON' : 'OFF'}
</button>
);
}
}

Passing Arguments to Event Handlers

1
2
3
<button onClick={(e) => this.deleteRow(id, e)}>Delete Row</button>

<button onClick={this.deleteRow.bind(this, id)}>Delete Row</button>

Conditional Rendering

In React, you can create distinct components that encapsulate the behavior you need, then render only some of them depending on the state of your application.

If Statements

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function UserGreeting(props) {
return <h1>Welcome back!</h1>;
}

function GuestGreeting(props) {
return <h1>Please sign up.</h1>;
}

function Greeting(props) {
const isLoggedIn = props.isLoggedIn;
if (isLoggedIn) {
return <UserGreeting />;
}
return <GuestGreeting />;
}

Inline If with Logical && Operator

1
2
3
4
5
6
7
8
9
10
11
12
13
function Mailbox(props) {
const unreadMessages = props.unreadMessages;
return (
<div>
<h1>Hello!</h1>
{unreadMessages.length > 0 &&
<h2>
You have {unreadMessages.length} unread messages.
</h2>
}
</div>
);
}

Inline If-Else with Conditional Operator

1
2
3
4
5
6
7
8
9
10
11
render() {
const isLoggedIn = this.state.isLoggedIn;
return (
<div>
{isLoggedIn
? <LogoutButton onClick={this.handleLogoutClick} />
: <LoginButton onClick={this.handleLoginClick} />
}
</div>
);
}

Preventing Component from Rendering

1
2
3
4
5
6
7
8
9
10
11
function WarningBanner(props) {
if (!props.warn) {
return null;
}

return (
<div className="warning">
Warning!
</div>
);
}

Lists and Keys

Rendering Multiple Components

1
2
3
4
5
6
7
8
9
10
11
function NumberList(props) {
const numbers = props.numbers;
const listItems = numbers.map((number) =>
<li key={number.toString()}>
{number}
</li>
);
return (
<ul>{listItems}</ul>
);
}

Keys

Keys help React identify which items have changed, added, or removed:

1
2
3
4
5
const todoItems = todos.map((todo) =>
<li key={todo.id}>
{todo.text}
</li>
);

Keys should be unique among siblings, but don’t need to be globally unique:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
function Blog(props) {
const sidebar = (
<ul>
{props.posts.map((post) =>
<li key={post.id}>
{post.title}
</li>
)}
</ul>
);

const content = props.posts.map((post) =>
<div key={post.id}>
<h3>{post.title}</h3>
<p>{post.content}</p>
</div>
);

return (
<div>
{sidebar}
<hr />
{content}
</div>
);
}

Extracting Components with Keys

Keys only make sense in the context of the surrounding array:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function ListItem(props) {
// No need to specify the key here
return <li>{props.value}</li>;
}

function NumberList(props) {
const numbers = props.numbers;
const listItems = numbers.map((number) =>
// Key should be specified inside the array
<ListItem key={number.toString()} value={number} />
);
return (
<ul>
{listItems}
</ul>
);
}

Forms

HTML form elements work a bit differently in React because they naturally keep some internal state.

Controlled Components

In React, mutable state is typically kept in the state property of components, and only updated with setState(). Form elements that are controlled by React in this way are called “controlled components”:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class NameForm extends React.Component {
constructor(props) {
super(props);
this.state = {value: ''};

this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}

handleChange(event) {
this.setState({value: event.target.value});
}

handleSubmit(event) {
alert('A name was submitted: ' + this.state.value);
event.preventDefault();
}

render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Name:
<input type="text" value={this.state.value} onChange={this.handleChange} />
</label>
<input type="submit" value="Submit" />
</form>
);
}
}

Handling Multiple Inputs

When you need to handle multiple controlled inputs, you can add a name attribute to each element and let the handler function choose what to do based on the value of event.target.name:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
class Reservation extends React.Component {
constructor(props) {
super(props);
this.state = {
isGoing: true,
numberOfGuests: 2
};

this.handleInputChange = this.handleInputChange.bind(this);
}

handleInputChange(event) {
const target = event.target;
const value = target.type === 'checkbox' ? target.checked : target.value;
const name = target.name;

this.setState({
[name]: value
});
}

render() {
return (
<form>
<label>
Is going:
<input
name="isGoing"
type="checkbox"
checked={this.state.isGoing}
onChange={this.handleInputChange} />
</label>
<br />
<label>
Number of guests:
<input
name="numberOfGuests"
type="number"
value={this.state.numberOfGuests}
onChange={this.handleInputChange} />
</label>
</form>
);
}
}

Form Controls

React supports various HTML form elements:

  • Text inputs: <input type="text">, <textarea>
  • Checkboxes: <input type="checkbox">
  • Radio buttons: <input type="radio">
  • Select dropdowns: <select>, <option>
  • File inputs: <input type="file"> (uncontrolled)

Lifting State Up

Often, several components need to reflect the same changing data. It’s recommended to lift the shared state up to their closest common ancestor.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
function BoilingVerdict(props) {
if (props.celsius >= 100) {
return <p>The water would boil.</p>;
}
return <p>The water would not boil.</p>;
}

class Calculator extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.state = {temperature: ''};
}

handleChange(e) {
this.setState({temperature: e.target.value});
}

render() {
const temperature = this.state.temperature;
return (
<fieldset>
<legend>Enter temperature in Celsius:</legend>
<input
value={temperature}
onChange={this.handleChange} />
<BoilingVerdict
celsius={parseFloat(temperature)} />
</fieldset>
);
}
}

When we need to support both Celsius and Fahrenheit, we lift state to the shared parent component:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
class Calculator extends React.Component {
constructor(props) {
super(props);
this.handleCelsiusChange = this.handleCelsiusChange.bind(this);
this.handleFahrenheitChange = this.handleFahrenheitChange.bind(this);
this.state = {temperature: '', scale: 'c'};
}

handleCelsiusChange(temperature) {
this.setState({scale: 'c', temperature});
}

handleFahrenheitChange(temperature) {
this.setState({scale: 'f', temperature});
}

render() {
const scale = this.state.scale;
const temperature = this.state.temperature;
const celsius = scale === 'f' ? tryConvert(temperature, toCelsius) : temperature;
const fahrenheit = scale === 'c' ? tryConvert(temperature, toFahrenheit) : temperature;

return (
<div>
<TemperatureInput
scale="c"
temperature={celsius}
onTemperatureChange={this.handleCelsiusChange} />
<TemperatureInput
scale="f"
temperature={fahrenheit}
onTemperatureChange={this.handleFahrenheitChange} />
<BoilingVerdict
celsius={parseFloat(celsius)} />
</div>
);
}
}

Composition vs Inheritance

React has a powerful composition model, and recommends using composition instead of inheritance to reuse code between components.

Containment

Components that don’t know their children ahead of time can use the special children prop to pass children elements directly into their output:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function FancyBorder(props) {
return (
<div className={'FancyBorder FancyBorder-' + props.color}>
{props.children}
</div>
);
}

function WelcomeDialog() {
return (
<FancyBorder color="blue">
<h1 className="Dialog-title">
Welcome
</h1>
<p className="Dialog-message">
Thank you for visiting our spacecraft!
</p>
</FancyBorder>
);
}

Specialization

Sometimes we think about components as being “special cases” of other components. In React, this is also achieved by composition, where a more “specific” component renders a more “generic” one and configures it with props:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
function Dialog(props) {
return (
<FancyBorder color="blue">
<h1 className="Dialog-title">
{props.title}
</h1>
<p className="Dialog-message">
{props.message}
</p>
{props.children}
</FancyBorder>
);
}

function WelcomeDialog() {
return (
<Dialog
title="Welcome"
message="Thank you for visiting our spacecraft!" />
);
}

class SignUpDialog extends React.Component {
constructor(props) {
super(props);
this.state = {login: ''};
this.handleChange = this.handleChange.bind(this);
this.handleSignUp = this.handleSignUp.bind(this);
}

handleChange(e) {
this.setState({login: e.target.value});
}

handleSignUp() {
alert(`Welcome aboard, ${this.state.login}!`);
}

render() {
return (
<Dialog
title="Mars Exploration Program"
message="How should we refer to you?">
<input value={this.state.login}
onChange={this.handleChange} />
<button onClick={this.handleSignUp}>
Sign Me Up!
</button>
</Dialog>
);
}
}

Thinking in React

Here’s a thought process for building React apps:

  1. Start with a mock: Break the UI into a component hierarchy
  2. Build a static version: Build components that reuse other components and pass data using props. Don’t use state yet.
  3. Identify the minimal representation of UI state: Figure out what state your app needs.
  4. Identify where your state should live: Identify which component should own which state.
  5. Add inverse data flow: Support data flowing from forms back up to parent components.

React Developer Tools

React Developer Tools is a browser extension for Chrome and Firefox that allows you to inspect the React component hierarchy in the browser’s developer tools:

  • Inspect component props and state
  • Track which components re-render
  • Profile performance
  • Find components that cause unintended side effects

Best Practices

Keep Components Small and Focused

Each component should ideally do one thing well. If a component grows too large, consider breaking it down into smaller subcomponents.

Use Functional Components When Possible

Functional components are simpler, more concise, and generally easier to understand than class components.

Use PropTypes for Type Checking

Define PropTypes for your components to document the API and catch bugs early.

Use CSS-in-JS or CSS Modules

Consider using CSS-in-JS libraries like styled-components or CSS Modules to scope styles to specific components.

Use Fragment to Avoid Unnecessary DOM Nodes

Use React Fragments to group lists of children without adding extra nodes to the DOM:

1
2
3
4
5
6
7
8
9
function ListItems() {
return (
<>
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
</>
);
}

Use Short-Circuit Evaluation for Conditional Rendering

1
{isLoggedIn && <LogoutButton />}

Use Ternary Operator for Simple Conditionals

1
{isLoggedIn ? <UserGreeting /> : <GuestGreeting />}

Use Error Boundaries to Catch Rendering Errors

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}

static getDerivedStateFromError(error) {
// Update state so the next render will show the fallback UI.
return { hasError: true };
}

componentDidCatch(error, errorInfo) {
// You can also log the error to an error reporting service
logErrorToMyService(error, errorInfo);
}

render() {
if (this.state.hasError) {
// You can render any custom fallback UI
return <h1>Something went wrong.</h1>;
}

return this.props.children;
}
}

// Usage
<ErrorBoundary>
<MyComponent />
</ErrorBoundary>

Learning Resources

  • React Documentation - Official React docs
  • React Tutorial - Official React tutorial
  • React Blog - Official React blog
  • Create React App - Set up a modern web app by running one command
  • React DevTools - React Developer Tools extension
  • React Patterns - Common React patterns
  • Egghead.io React Courses - Video tutorials
  • React Enlightenment - React book
  • Web Development
  • JavaScript
  • React
  • Frontend Framework

show all >>

Browser APIs & DOM Manipulation

  2023-06-08
字数统计: 4.6k字   |   阅读时长: 29min

Browser APIs & DOM Manipulation

Modern web browsers provide a rich set of APIs (Application Programming Interfaces) that allow JavaScript to interact with the browser environment and manipulate web page content. This guide covers the DOM (Document Object Model) and various browser APIs that are essential for frontend development.

The Document Object Model (DOM)

The DOM is a programming interface for HTML documents. It represents the page as nodes and objects that JavaScript can interact with.

DOM Structure

The DOM represents an HTML document as a tree of nodes:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
document
└── html
├── head
│ ├── title
│ ├── meta
│ └── link
└── body
├── header
│ └── nav
├── main
│ ├── h1
│ ├── p
│ └── div
└── footer

Selecting DOM Elements

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// By ID (returns a single element)
const header = document.getElementById('header');

// By class name (returns a live HTMLCollection)
const paragraphs = document.getElementsByClassName('paragraph');

// By tag name (returns a live HTMLCollection)
const divs = document.getElementsByTagName('div');

// By CSS selector (returns the first match)
const mainSection = document.querySelector('main');

// By CSS selector (returns all matches as a static NodeList)
const buttons = document.querySelectorAll('.btn');

// Finding elements relative to another element
const firstListItem = document.querySelector('ul li');
const list = firstListItem.parentElement;
const nextItem = firstListItem.nextElementSibling;
const allItems = list.children;

Manipulating DOM Elements

Creating Elements

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// Create a new element
const newDiv = document.createElement('div');

// Add content to the element
newDiv.textContent = 'Hello, World!';
// or
newDiv.innerHTML = '<span>Hello</span>, World!';

// Add classes
newDiv.className = 'container highlight';
// or
newDiv.classList.add('container');
newDiv.classList.add('highlight');
newDiv.classList.remove('highlight');
newDiv.classList.toggle('active');
newDiv.classList.contains('container'); // true

// Set attributes
newDiv.id = 'main-container';
newDiv.setAttribute('data-test', 'value');
newDiv.getAttribute('id'); // 'main-container'
newDiv.hasAttribute('data-test'); // true
newDiv.removeAttribute('data-test');

// Set styles
newDiv.style.color = 'red';
newDiv.style.backgroundColor = 'white';
newDiv.style.cssText = 'color: red; background-color: white;';

Modifying the DOM

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Append elements
document.body.appendChild(newDiv);

// Insert before a specific element
const referenceElement = document.getElementById('reference');
document.body.insertBefore(newDiv, referenceElement);

// Modern insertion methods
parentElement.append(element1, element2); // Adds at the end, accepts multiple nodes
parentElement.prepend(element); // Adds at the beginning
referenceElement.before(element); // Adds before the reference element
referenceElement.after(element); // Adds after the reference element
referenceElement.replaceWith(element); // Replaces the reference element

// Remove elements
element.remove(); // Modern way
// or
element.parentElement.removeChild(element); // Old way, more compatible

Cloning Elements

1
2
3
// Clone an element (false = without children, true = with children)
const clone = element.cloneNode(false);
const deepClone = element.cloneNode(true);

DOM Properties

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// Text content
element.textContent = 'New text'; // Sets text (safer, no HTML parsing)
element.innerText = 'New text'; // Similar, but respects CSS styling
element.innerHTML = '<b>Bold text</b>'; // Sets HTML content (careful with XSS)

// Element dimensions and position
const rect = element.getBoundingClientRect(); // Position relative to viewport
console.log(rect.width, rect.height, rect.top, rect.left);

element.offsetWidth; // Width including padding and border
element.offsetHeight; // Height including padding and border
element.clientWidth; // Width including padding but not border
element.clientHeight; // Height including padding but not border

// Scroll position
element.scrollTop; // Pixels scrolled vertically
element.scrollLeft; // Pixels scrolled horizontally
element.scrollTo(0, 0); // Scroll to specific coordinates
element.scrollIntoView(); // Scroll the element into view

DOM Events

DOM events allow JavaScript to register different event handlers on elements in an HTML document.

Event Handling

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// Adding event listeners
element.addEventListener('click', function(event) {
console.log('Element clicked!', event);
});

// With arrow function
element.addEventListener('mouseover', (event) => {
console.log('Mouse over!');
});

// Removing event listeners (reference to the original function needed)
function handleClick(event) {
console.log('Clicked!');
}

element.addEventListener('click', handleClick);
element.removeEventListener('click', handleClick);

// Older methods (avoid using)
element.onclick = function() {
console.log('Clicked!');
};

Event Object Properties

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
element.addEventListener('click', function(event) {
// General event properties
console.log(event.type); // "click"
console.log(event.target); // Element that triggered the event
console.log(event.currentTarget); // Element the listener is attached to
console.log(event.timeStamp); // Time the event was created

// Mouse event properties
console.log(event.clientX, event.clientY); // Coordinates relative to viewport
console.log(event.pageX, event.pageY); // Coordinates relative to document
console.log(event.button); // Mouse button pressed

// Keyboard event properties
console.log(event.key); // Key value ("a", "Enter", etc.)
console.log(event.code); // Physical key code ("KeyA", "Enter", etc.)
console.log(event.ctrlKey, event.shiftKey, event.altKey); // Modifier keys
});

Event Propagation

Events in the DOM propagate in three phases:

  1. Capture Phase: From the root down to the target
  2. Target Phase: The event reaches the target
  3. Bubbling Phase: From the target back up to the root
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// Capturing phase (third parameter true)
parent.addEventListener('click', function(event) {
console.log('Parent captured!');
}, true);

// Bubbling phase (default behavior)
child.addEventListener('click', function(event) {
console.log('Child bubbled!');

// Stop propagation to parent elements
event.stopPropagation();

// Prevent default browser behavior
event.preventDefault();
});

// Event delegation (handling events for multiple elements)
document.getElementById('todo-list').addEventListener('click', function(event) {
// Check if clicked element is a list item
if (event.target.tagName === 'LI') {
event.target.classList.toggle('completed');
}
});

Common DOM Events

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// Mouse events
element.addEventListener('click', handler); // Single click
element.addEventListener('dblclick', handler); // Double click
element.addEventListener('mouseover', handler); // Mouse enters element
element.addEventListener('mouseout', handler); // Mouse leaves element
element.addEventListener('mousedown', handler); // Mouse button pressed
element.addEventListener('mouseup', handler); // Mouse button released
element.addEventListener('mousemove', handler); // Mouse moved

// Keyboard events
element.addEventListener('keydown', handler); // Key pressed
element.addEventListener('keyup', handler); // Key released
element.addEventListener('keypress', handler); // Key pressed (character keys)

// Form events
form.addEventListener('submit', handler); // Form submitted
input.addEventListener('focus', handler); // Element received focus
input.addEventListener('blur', handler); // Element lost focus
input.addEventListener('change', handler); // Value changed (after blur)
input.addEventListener('input', handler); // Value changed (immediately)

// Document/Window events
window.addEventListener('load', handler); // Page fully loaded
document.addEventListener('DOMContentLoaded', handler); // DOM fully loaded
window.addEventListener('resize', handler); // Window resized
window.addEventListener('scroll', handler); // Window scrolled

Custom Events

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Creating a custom event
const customEvent = new CustomEvent('userLoggedIn', {
detail: { userId: 123, username: 'john_doe' },
bubbles: true,
cancelable: true
});

// Dispatching the event
document.dispatchEvent(customEvent);

// Listening for the custom event
document.addEventListener('userLoggedIn', function(event) {
console.log('User logged in:', event.detail.username);
});

Browser Storage APIs

Browsers provide several ways to store data on the client side.

Local Storage

Data persists until explicitly cleared. Available across browser sessions.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Store data
localStorage.setItem('username', 'john_doe');
localStorage.setItem('preferences', JSON.stringify({ theme: 'dark', fontSize: 16 }));

// Retrieve data
const username = localStorage.getItem('username');
const preferences = JSON.parse(localStorage.getItem('preferences'));

// Remove specific item
localStorage.removeItem('username');

// Clear all data
localStorage.clear();

// Storage event (fires when storage changes in another tab/window)
window.addEventListener('storage', function(event) {
console.log('Storage changed:', event.key, event.oldValue, event.newValue);
});

Session Storage

Similar to localStorage, but data is cleared when the session ends (window/tab closed).

1
2
3
4
5
// Same API as localStorage
sessionStorage.setItem('sessionId', 'abc123');
const sessionId = sessionStorage.getItem('sessionId');
sessionStorage.removeItem('sessionId');
sessionStorage.clear();

Cookies

Traditional way to store small amounts of data. Sent with HTTP requests.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// Set a cookie
document.cookie = "username=john_doe; expires=Fri, 31 Dec 2023 23:59:59 GMT; path=/; Secure; SameSite=Strict";

// Read all cookies
const allCookies = document.cookie; // "username=john_doe; sessionId=abc123"

// Parse cookies
function getCookie(name) {
const cookies = document.cookie.split('; ');
for (const cookie of cookies) {
const [cookieName, cookieValue] = cookie.split('=');
if (cookieName === name) {
return cookieValue;
}
}
return null;
}

// Delete a cookie (set expiration in the past)
document.cookie = "username=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/;";

IndexedDB

A more powerful, asynchronous client-side storage API for larger amounts of structured data.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// Open a database
const request = indexedDB.open('MyDatabase', 1);

// Create schema (called when DB is created or version changes)
request.onupgradeneeded = function(event) {
const db = event.target.result;
const store = db.createObjectStore('users', { keyPath: 'id' });
store.createIndex('by_name', 'name', { unique: false });
};

// On success
request.onsuccess = function(event) {
const db = event.target.result;

// Start a transaction
const transaction = db.transaction(['users'], 'readwrite');
const store = transaction.objectStore('users');

// Add data
store.add({ id: 1, name: 'John', email: 'john@example.com' });

// Get data
const getRequest = store.get(1);
getRequest.onsuccess = function() {
console.log('User:', getRequest.result);
};

// Transaction complete
transaction.oncomplete = function() {
console.log('Transaction completed');
db.close();
};
};

// On error
request.onerror = function(event) {
console.error('Database error:', event.target.error);
};

Network Requests

XMLHttpRequest (Legacy)

The older way to make HTTP requests, still supported by all browsers.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
const xhr = new XMLHttpRequest();

xhr.open('GET', 'https://api.example.com/data', true);

xhr.onload = function() {
if (xhr.status >= 200 && xhr.status < 300) {
const data = JSON.parse(xhr.responseText);
console.log('Data:', data);
} else {
console.error('Request failed with status:', xhr.status);
}
};

xhr.onerror = function() {
console.error('Request failed');
};

xhr.send();

// POST request with data
const postXhr = new XMLHttpRequest();
postXhr.open('POST', 'https://api.example.com/submit', true);
postXhr.setRequestHeader('Content-Type', 'application/json');
postXhr.send(JSON.stringify({ name: 'John', email: 'john@example.com' }));

Fetch API (Modern)

The modern way to make HTTP requests, based on Promises.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
// Basic GET request
fetch('https://api.example.com/data')
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
return response.json(); // Parse as JSON
})
.then(data => {
console.log('Data:', data);
})
.catch(error => {
console.error('Fetch error:', error);
});

// POST request with options
fetch('https://api.example.com/submit', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer token123'
},
body: JSON.stringify({ name: 'John', email: 'john@example.com' })
})
.then(response => response.json())
.then(data => console.log('Success:', data))
.catch(error => console.error('Error:', error));

// Using async/await
async function fetchData() {
try {
const response = await fetch('https://api.example.com/data');
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
const data = await response.json();
console.log('Data:', data);
return data;
} catch (error) {
console.error('Fetch error:', error);
throw error;
}
}

// Fetch with abort controller
const controller = new AbortController();
const signal = controller.signal;

fetch('https://api.example.com/data', { signal })
.then(response => response.json())
.then(data => console.log('Data:', data))
.catch(error => {
if (error.name === 'AbortError') {
console.log('Fetch aborted');
} else {
console.error('Fetch error:', error);
}
});

// Abort the fetch after 5 seconds
setTimeout(() => controller.abort(), 5000);

Geolocation API

Access the user’s geographical location.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
// Check if geolocation is supported
if ('geolocation' in navigator) {
// Get current position
navigator.geolocation.getCurrentPosition(
// Success callback
function(position) {
const latitude = position.coords.latitude;
const longitude = position.coords.longitude;
console.log(`Location: ${latitude}, ${longitude}`);

// Other available data
console.log('Accuracy:', position.coords.accuracy, 'meters');
console.log('Altitude:', position.coords.altitude);
console.log('Timestamp:', position.timestamp);
},
// Error callback
function(error) {
switch(error.code) {
case error.PERMISSION_DENIED:
console.error('User denied geolocation permission');
break;
case error.POSITION_UNAVAILABLE:
console.error('Location information unavailable');
break;
case error.TIMEOUT:
console.error('Location request timed out');
break;
default:
console.error('Unknown error:', error.message);
}
},
// Options
{
enableHighAccuracy: true, // More accurate position
timeout: 5000, // Time to wait for response in ms
maximumAge: 0 // Don't use cached position
}
);

// Watch position (gets updates when user moves)
const watchId = navigator.geolocation.watchPosition(
position => console.log(`Updated location: ${position.coords.latitude}, ${position.coords.longitude}`),
error => console.error('Error watching position:', error),
{ enableHighAccuracy: true }
);

// Stop watching
// navigator.geolocation.clearWatch(watchId);
} else {
console.log('Geolocation is not supported by this browser');
}

History API

Manipulate the browser history.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// Navigate back
history.back();

// Navigate forward
history.forward();

// Go to a specific point in history
history.go(-2); // Back 2 pages
history.go(1); // Forward 1 page

// Add a new entry to the browser history
// This updates the URL without reloading the page
history.pushState({ pageId: 123 }, 'Page Title', '/new-url');

// Replace the current history entry
history.replaceState({ pageId: 124 }, 'New Title', '/updated-url');

// Listen for navigation events (back/forward buttons)
window.addEventListener('popstate', function(event) {
console.log('Navigation occurred:', event.state);
});

Web Storage API

Web Storage (localStorage and sessionStorage) is covered in the Browser Storage section above.

Canvas API

Create and manipulate graphics and animations.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
// Get canvas element
const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');

// Set canvas dimensions
canvas.width = 500;
canvas.height = 300;

// Basic drawing
ctx.fillStyle = 'blue'; // Set fill color
ctx.fillRect(10, 10, 100, 50); // Draw filled rectangle

ctx.strokeStyle = 'red'; // Set stroke color
ctx.lineWidth = 5; // Set line width
ctx.strokeRect(150, 10, 100, 50); // Draw rectangle outline

// Draw a line
ctx.beginPath();
ctx.moveTo(10, 100);
ctx.lineTo(200, 150);
ctx.stroke();

// Draw a circle
ctx.beginPath();
ctx.arc(300, 100, 30, 0, Math.PI * 2); // x, y, radius, startAngle, endAngle
ctx.fill();

// Draw text
ctx.font = '20px Arial';
ctx.fillStyle = 'black';
ctx.fillText('Hello, Canvas!', 10, 200);

// Draw image
const img = new Image();
img.onload = function() {
ctx.drawImage(img, 350, 150, 100, 100); // image, x, y, width, height
};
img.src = 'image.jpg';

// Animation
let x = 0;
function animate() {
// Clear canvas
ctx.clearRect(0, 0, canvas.width, canvas.height);

// Draw a moving rectangle
ctx.fillRect(x, 50, 50, 50);

// Update position
x = (x + 2) % canvas.width;

// Request next frame
requestAnimationFrame(animate);
}
animate();

Web Audio API

Process and synthesize audio.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
// Create audio context
const AudioContext = window.AudioContext || window.webkitAudioContext;
const audioCtx = new AudioContext();

// Play a simple tone
function playTone(frequency = 440, duration = 1) {
// Create oscillator
const oscillator = audioCtx.createOscillator();
oscillator.type = 'sine'; // sine, square, sawtooth, triangle
oscillator.frequency.value = frequency; // frequency in Hz

// Create gain node (for volume control)
const gainNode = audioCtx.createGain();
gainNode.gain.value = 0.5; // 50% volume

// Connect the nodes
oscillator.connect(gainNode);
gainNode.connect(audioCtx.destination);

// Start and stop
oscillator.start();
oscillator.stop(audioCtx.currentTime + duration);
}

// Play a 440Hz tone for 1 second when clicked
document.getElementById('playButton').addEventListener('click', () => {
playTone(440, 1);
});

// Load and play an audio file
async function playAudio(url) {
// Fetch the file
const response = await fetch(url);
const arrayBuffer = await response.arrayBuffer();

// Decode the audio
const audioBuffer = await audioCtx.decodeAudioData(arrayBuffer);

// Create buffer source
const source = audioCtx.createBufferSource();
source.buffer = audioBuffer;

// Connect to destination
source.connect(audioCtx.destination);

// Play
source.start();
}

// Play audio file when clicked
document.getElementById('playSound').addEventListener('click', () => {
playAudio('sound.mp3');
});

WebRTC

Enables real-time communication in the browser.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
// Basic WebRTC peer connection setup
const configuration = { iceServers: [{ urls: 'stun:stun.l.google.com:19302' }] };
const peerConnection = new RTCPeerConnection(configuration);

// Handle ICE candidates
peerConnection.onicecandidate = event => {
if (event.candidate) {
// Send the candidate to the remote peer via your signaling server
sendToSignalingServer({ type: 'ice-candidate', candidate: event.candidate });
}
};

// Handle connection state changes
peerConnection.onconnectionstatechange = event => {
console.log('Connection state:', peerConnection.connectionState);
};

// Handle receiving streams
peerConnection.ontrack = event => {
const remoteVideo = document.getElementById('remoteVideo');
if (remoteVideo.srcObject !== event.streams[0]) {
remoteVideo.srcObject = event.streams[0];
}
};

// Get local media
async function startVideoCall() {
try {
// Get local stream
const localStream = await navigator.mediaDevices.getUserMedia({
video: true,
audio: true
});

// Display local video
const localVideo = document.getElementById('localVideo');
localVideo.srcObject = localStream;

// Add tracks to the peer connection
localStream.getTracks().forEach(track => {
peerConnection.addTrack(track, localStream);
});

// Create and send offer (if initiator)
const offer = await peerConnection.createOffer();
await peerConnection.setLocalDescription(offer);

// Send the offer to the remote peer via your signaling server
sendToSignalingServer({ type: 'offer', offer });

} catch (error) {
console.error('Error starting video call:', error);
}
}

// Function to handle received offers, answers, and ICE candidates
// (You would implement this to work with your signaling server)
function handleSignalingMessage(message) {
switch(message.type) {
case 'offer':
peerConnection.setRemoteDescription(new RTCSessionDescription(message.offer));
peerConnection.createAnswer()
.then(answer => peerConnection.setLocalDescription(answer))
.then(() => sendToSignalingServer({
type: 'answer',
answer: peerConnection.localDescription
}));
break;
case 'answer':
peerConnection.setRemoteDescription(new RTCSessionDescription(message.answer));
break;
case 'ice-candidate':
peerConnection.addIceCandidate(new RTCIceCandidate(message.candidate));
break;
}
}

Web Workers

Run JavaScript in background threads.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// Create a worker
const worker = new Worker('worker.js');

// Send message to worker
worker.postMessage({ action: 'calculate', data: [1, 2, 3, 4, 5] });

// Receive message from worker
worker.onmessage = function(event) {
console.log('Result from worker:', event.data);
};

// Handle errors
worker.onerror = function(error) {
console.error('Worker error:', error.message);
};

// Terminate worker when done
function terminateWorker() {
worker.terminate();
}

// Content of worker.js:
/*
// Listen for messages
self.onmessage = function(event) {
const { action, data } = event.data;

if (action === 'calculate') {
// Perform calculation
const result = data.reduce((sum, num) => sum + num, 0);

// Send result back
self.postMessage(result);
}
};

// Handle errors
self.onerror = function(error) {
console.error('Worker internal error:', error.message);
};
*/

Service Workers

A type of web worker that acts as a proxy server between web applications, the browser, and the network.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
// Register a service worker
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/service-worker.js')
.then(registration => {
console.log('Service Worker registered with scope:', registration.scope);
})
.catch(error => {
console.error('Service Worker registration failed:', error);
});
}

// Check if a service worker is active
navigator.serviceWorker.ready
.then(registration => {
console.log('Service Worker is active');
});

// Send a message to the service worker
navigator.serviceWorker.controller.postMessage({
type: 'CACHE_NEW_ROUTE',
payload: '/new-route'
});

// Listen for messages from the service worker
navigator.serviceWorker.addEventListener('message', event => {
console.log('Message from Service Worker:', event.data);
});

// Content of service-worker.js:
/*
// Service Worker installation
self.addEventListener('install', event => {
// Perform install steps (like caching resources)
event.waitUntil(
caches.open('v1')
.then(cache => {
return cache.addAll([
'/',
'/index.html',
'/styles.css',
'/script.js',
'/image.jpg'
]);
})
);
});

// Service Worker activation (cleanup old caches)
self.addEventListener('activate', event => {
event.waitUntil(
caches.keys().then(cacheNames => {
return Promise.all(
cacheNames.filter(cacheName => {
return cacheName !== 'v1';
}).map(cacheName => {
return caches.delete(cacheName);
})
);
})
);
});

// Intercept fetch requests
self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request)
.then(response => {
// Cache hit - return response
if (response) {
return response;
}

// Clone the request
const fetchRequest = event.request.clone();

return fetch(fetchRequest).then(response => {
// Check if valid response
if(!response || response.status !== 200 || response.type !== 'basic') {
return response;
}

// Clone the response
const responseToCache = response.clone();

caches.open('v1')
.then(cache => {
cache.put(event.request, responseToCache);
});

return response;
});
})
);
});

// Handle messages from the main thread
self.addEventListener('message', event => {
console.log('Message received in SW:', event.data);
// Respond to the message
event.source.postMessage('Message received!');
});
*/

Intersection Observer

Detect when an element is visible in the viewport.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// Create the observer
const observer = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
console.log('Element is visible:', entry.target);

// Load images when they become visible
if (entry.target.dataset.src) {
entry.target.src = entry.target.dataset.src;
entry.target.removeAttribute('data-src');
// Stop observing the element after loading
observer.unobserve(entry.target);
}
}
});
}, {
root: null, // Use the viewport as root
rootMargin: '0px', // No margin
threshold: 0.1 // 10% of the element must be visible
});

// Observe multiple elements
document.querySelectorAll('.lazy-load-image').forEach(image => {
observer.observe(image);
});

// Lazy loading example:
/*
<img class="lazy-load-image" src="placeholder.jpg" data-src="actual-image.jpg" alt="Lazy loaded image">
*/

Mutation Observer

Watch for changes in the DOM.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// Create a mutation observer
const observer = new MutationObserver((mutations, observer) => {
mutations.forEach(mutation => {
if (mutation.type === 'childList') {
console.log('Child nodes added or removed:', mutation.addedNodes, mutation.removedNodes);
} else if (mutation.type === 'attributes') {
console.log(`Attribute '${mutation.attributeName}' changed:`,
mutation.target.getAttribute(mutation.attributeName));
}
});
});

// Start observing an element
const targetElement = document.getElementById('observed-element');
observer.observe(targetElement, {
childList: true, // Watch for changes to child elements
attributes: true, // Watch for attribute changes
characterData: true, // Watch for text content changes
subtree: true, // Also watch all descendants
attributeOldValue: true, // Record old attribute values
characterDataOldValue: true // Record old text content
});

// Stop observing
function stopObserving() {
observer.disconnect();
}

Web Animations API

Create and control animations in JavaScript.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// Get element to animate
const element = document.getElementById('animated-element');

// Create animation
const animation = element.animate([
// Keyframes
{ transform: 'translateX(0px)', opacity: 1 },
{ transform: 'translateX(100px)', opacity: 0.5, offset: 0.8 }, // 80% mark
{ transform: 'translateX(200px)', opacity: 0 }
], {
// Timing options
duration: 2000, // 2 seconds
easing: 'ease-in-out',
delay: 500, // 0.5 second delay
iterations: 3, // Play 3 times
direction: 'alternate', // Alternate direction on each iteration
fill: 'forwards' // Keep final state
});

// Animation controls
animation.pause(); // Pause
animation.play(); // Resume
animation.reverse(); // Play in reverse
animation.finish(); // Jump to end
animation.cancel(); // Stop and remove effects

// Animation events
animation.onfinish = () => {
console.log('Animation finished');
};

animation.oncancel = () => {
console.log('Animation cancelled');
};

// Get animation state
console.log('Current time:', animation.currentTime);
console.log('Playback rate:', animation.playbackRate);

// Change playback speed
animation.playbackRate = 2; // Double speed

Drag and Drop API

Enable drag and drop functionality.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
// Make an element draggable
const draggableElement = document.getElementById('draggable');
draggableElement.draggable = true;

// Drag events on the draggable element
draggableElement.addEventListener('dragstart', event => {
console.log('Drag started');

// Set data to be dragged
event.dataTransfer.setData('text/plain', draggableElement.id);

// Set drag image (optional)
const dragImage = new Image();
dragImage.src = 'dragicon.png';
event.dataTransfer.setDragImage(dragImage, 10, 10);

// Set drag effect
event.dataTransfer.effectAllowed = 'move'; // 'copy', 'move', 'link', 'copyMove', etc.
});

draggableElement.addEventListener('dragend', event => {
console.log('Drag ended');
});

// Set up drop target
const dropTarget = document.getElementById('drop-target');

// Prevent default to allow drop
dropTarget.addEventListener('dragover', event => {
event.preventDefault();
// Change appearance to indicate valid drop target
dropTarget.classList.add('drag-over');
});

// Handle when drag enters/leaves target
dropTarget.addEventListener('dragenter', event => {
event.preventDefault();
dropTarget.classList.add('drag-over');
});

dropTarget.addEventListener('dragleave', () => {
dropTarget.classList.remove('drag-over');
});

// Handle the drop
dropTarget.addEventListener('drop', event => {
event.preventDefault();
dropTarget.classList.remove('drag-over');

// Get the dragged data
const id = event.dataTransfer.getData('text/plain');
const draggedElement = document.getElementById(id);

// Append the element to the drop target
dropTarget.appendChild(draggedElement);

console.log('Element dropped');
});

File API

Working with files from the user’s filesystem.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
// File input element
const fileInput = document.getElementById('file-input');

// Handle file selection
fileInput.addEventListener('change', event => {
const files = event.target.files;

for (const file of files) {
console.log('File name:', file.name);
console.log('File type:', file.type);
console.log('File size:', file.size, 'bytes');
console.log('Last modified:', new Date(file.lastModified));

// Read file contents
readFile(file);
}
});

// Read file as text
function readFile(file) {
const reader = new FileReader();

reader.onload = event => {
const content = event.target.result;
console.log('File content:', content);
};

reader.onerror = error => {
console.error('File reading error:', error);
};

if (file.type.startsWith('text/')) {
reader.readAsText(file); // Read as text
} else if (file.type.startsWith('image/')) {
reader.readAsDataURL(file); // Read as data URL for images

// Display the image
reader.onload = event => {
const imageElement = document.createElement('img');
imageElement.src = event.target.result;
document.body.appendChild(imageElement);
};
} else {
reader.readAsArrayBuffer(file); // Read as ArrayBuffer for binary files
}
}

// Drag and drop files
const dropZone = document.getElementById('drop-zone');

dropZone.addEventListener('dragover', event => {
event.preventDefault();
dropZone.classList.add('drag-over');
});

dropZone.addEventListener('dragleave', () => {
dropZone.classList.remove('drag-over');
});

dropZone.addEventListener('drop', event => {
event.preventDefault();
dropZone.classList.remove('drag-over');

const files = event.dataTransfer.files;
console.log('Files dropped:', files.length);

for (const file of files) {
readFile(file);
}
});

Broadcast Channel API

Communicate between browsing contexts (tabs, windows, iframes).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
// Create a channel
const channel = new BroadcastChannel('app_channel');

// Send a message to all other contexts
function sendMessage(message) {
channel.postMessage({
type: 'notification',
text: message,
timestamp: Date.now()
});
}

// Listen for messages
channel.onmessage = event => {
console.log('Message received:', event.data);
displayNotification(event.data);
};

// Handle errors
channel.onmessageerror = error => {
console.error('Message error:', error);
};

// Close the channel when done
function closeChannel() {
channel.close();
}

// Example usage
document.getElementById('send-button').addEventListener('click', () => {
const message = document.getElementById('message-input').value;
sendMessage(message);
});

// Display notification
function displayNotification(data) {
const notification = document.createElement('div');
notification.className = 'notification';
notification.textContent = `${data.text} (${new Date(data.timestamp).toLocaleTimeString()})`;
document.body.appendChild(notification);

// Remove after a few seconds
setTimeout(() => {
notification.remove();
}, 5000);
}

Learning Resources

  • MDN Web Docs - Web APIs
  • MDN Web Docs - Document Object Model (DOM)
  • JavaScript.info - Browser: Document, Events, Interfaces
  • Google Developers - Web Fundamentals
  • Can I Use - Browser compatibility tables
  • DOM Enlightenment - Free online book
  • Web Development
  • JavaScript
  • DOM
  • Browser APIs

show all >>

ES6+ Features

  2023-06-07
字数统计: 3.4k字   |   阅读时长: 21min

ES6+ Features

ES6 (ECMAScript 2015) introduced significant enhancements to JavaScript, and subsequent versions have continued to add powerful features. This guide explores the key features from ES6 and beyond that have transformed modern JavaScript development.

Overview of ECMAScript Evolution

  • ES6/ES2015: Major update with many new features
  • ES7/ES2016: Smaller update (Array.prototype.includes, Exponentiation Operator)
  • ES8/ES2017: Async/await, Object methods
  • ES9/ES2018: Rest/spread for objects, Promise.finally, async iteration
  • ES10/ES2019: Array.prototype.flat, Object.fromEntries
  • ES11/ES2020: Optional chaining, Nullish coalescing, BigInt
  • ES12/ES2021: String.prototype.replaceAll, Promise.any, Logical assignment
  • ES13/ES2022: Top-level await, Class fields, Error cause

ES6 (ES2015) Features

Let and Const Declarations

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// var - function-scoped, can be redeclared
var x = 1;
var x = 2; // Valid

// let - block-scoped, can be reassigned
let y = 1;
y = 2; // Valid
// let y = 2; // Error: y has already been declared

// const - block-scoped, cannot be reassigned
const z = 1;
// z = 2; // Error: Assignment to constant variable

// Block scoping
if (true) {
let blockScoped = 'only available in this block';
const alsoBlockScoped = 'same here';
}
// console.log(blockScoped); // Error: blockScoped is not defined

// const with objects/arrays
const person = { name: 'John' };
person.name = 'Jane'; // Valid - the object properties can be changed
// person = {}; // Error - reassigning the binding is not allowed

const numbers = [1, 2, 3];
numbers.push(4); // Valid - you can modify array contents
// numbers = []; // Error - reassigning the binding is not allowed

Arrow Functions

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// Traditional function expression
const add = function(a, b) {
return a + b;
};

// Arrow function
const addArrow = (a, b) => a + b;

// Arrow function with single parameter (parentheses optional)
const square = x => x * x;

// Arrow function with multiple statements
const calculate = (a, b) => {
const result = a * b;
return result + 10;
};

// Empty parameter list requires parentheses
const getRandomNumber = () => Math.random();

// Arrow functions do not have their own this
const object = {
data: [1, 2, 3],
processData() {
// Arrow function inherits this from outer scope
this.data.forEach(item => {
console.log(item, this.data); // this refers to object
});
}
};

Template Literals

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
const name = 'Alice';
const age = 28;

// String concatenation (old way)
const message1 = 'My name is ' + name + ' and I am ' + age + ' years old.';

// Template literals
const message2 = `My name is ${name} and I am ${age} years old.`;

// Multi-line strings
const multiLine = `This is a string
that spans multiple
lines without escape characters.`;

// Tagged templates
function highlight(strings, ...values) {
return strings.reduce((result, str, i) => {
const value = values[i] || '';
return `${result}${str}<span class="highlight">${value}</span>`;
}, '');
}

const username = 'Admin';
const highlightedText = highlight`Hello ${username}, welcome back!`;
// "<span class="highlight">Hello </span>Admin<span class="highlight">, welcome back!</span>"

Destructuring Assignment

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
// Object destructuring
const person = {
firstName: 'John',
lastName: 'Doe',
age: 30,
address: {
city: 'New York',
country: 'USA'
}
};

// Basic destructuring
const { firstName, lastName } = person;
console.log(firstName, lastName); // "John" "Doe"

// Destructuring with new variable names
const { firstName: first, lastName: last } = person;
console.log(first, last); // "John" "Doe"

// Nested destructuring
const { address: { city, country } } = person;
console.log(city, country); // "New York" "USA"

// Default values
const { zipCode = '10001' } = person;
console.log(zipCode); // "10001" (default value since it doesn't exist)

// Rest pattern
const { firstName: fName, ...rest } = person;
console.log(rest); // { lastName: "Doe", age: 30, address: { city: "New York", country: "USA" } }

// Array destructuring
const numbers = [1, 2, 3, 4, 5];

// Basic array destructuring
const [first, second, ...remaining] = numbers;
console.log(first, second, remaining); // 1 2 [3, 4, 5]

// Skipping elements
const [a, , c] = numbers;
console.log(a, c); // 1 3

// Default values
const [x, y, z = 10] = [1, 2];
console.log(x, y, z); // 1 2 10

// Swapping variables
let a1 = 'first';
let b1 = 'second';
[a1, b1] = [b1, a1];
console.log(a1, b1); // "second" "first"

// Function parameter destructuring
function printPersonInfo({ firstName, lastName, age = 'unknown' }) {
console.log(`${firstName} ${lastName}, ${age} years old`);
}
printPersonInfo(person); // "John Doe, 30 years old"

Default Parameters

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// Basic default parameters
function greet(name = 'Guest') {
return `Hello, ${name}!`;
}
console.log(greet()); // "Hello, Guest!"
console.log(greet('Alice')); // "Hello, Alice!"

// Default parameters with expressions
function calculateTax(amount, taxRate = amount * 0.1) {
return amount + taxRate;
}
console.log(calculateTax(100)); // 110
console.log(calculateTax(100, 20)); // 120

// Default parameters based on other parameters
function createUser(name, role = 'user', id = `${name}-${role}-${Date.now()}`) {
return { name, role, id };
}
console.log(createUser('john')); // { name: 'john', role: 'user', id: 'john-user-1623456789' }

Rest and Spread Operators

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// Rest operator in function parameters
function sum(...numbers) {
return numbers.reduce((total, num) => total + num, 0);
}
console.log(sum(1, 2, 3, 4, 5)); // 15

// Rest with other parameters
function multiply(multiplier, ...numbers) {
return numbers.map(num => num * multiplier);
}
console.log(multiply(2, 1, 2, 3)); // [2, 4, 6]

// Spread operator with arrays
const array1 = [1, 2, 3];
const array2 = [4, 5, 6];
const combined = [...array1, ...array2];
console.log(combined); // [1, 2, 3, 4, 5, 6]

// Spread to copy arrays
const original = [1, 2, 3];
const copy = [...original];
original.push(4);
console.log(original); // [1, 2, 3, 4]
console.log(copy); // [1, 2, 3]

// Spread with objects (ES9+)
const defaults = { theme: 'light', fontSize: 16 };
const userPrefs = { fontSize: 20, showSidebar: true };
const mergedPrefs = { ...defaults, ...userPrefs };
console.log(mergedPrefs); // { theme: 'light', fontSize: 20, showSidebar: true }

Enhanced Object Literals

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// Property shorthand
const name = 'John';
const age = 30;
const person = { name, age };
console.log(person); // { name: 'John', age: 30 }

// Method shorthand
const calculator = {
add(a, b) {
return a + b;
},
subtract(a, b) {
return a - b;
}
};
console.log(calculator.add(5, 3)); // 8

// Computed property names
const propertyName = 'status';
const value = 'active';
const user = {
[propertyName]: value,
[`user_${Date.now()}`]: true
};
console.log(user); // { status: 'active', user_1623456789: true }

Classes

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// Class declaration
class Person {
// Constructor
constructor(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
}

// Method
getFullName() {
return `${this.firstName} ${this.lastName}`;
}

// Static method
static createAnonymous() {
return new Person('Anonymous', 'User');
}
}

const person1 = new Person('John', 'Doe');
console.log(person1.getFullName()); // "John Doe"

const anonymous = Person.createAnonymous();
console.log(anonymous.getFullName()); // "Anonymous User"

// Inheritance
class Employee extends Person {
constructor(firstName, lastName, position) {
// Call parent constructor
super(firstName, lastName);
this.position = position;
}

// Override parent method
getFullName() {
return `${super.getFullName()} (${this.position})`;
}
}

const employee = new Employee('Jane', 'Smith', 'Developer');
console.log(employee.getFullName()); // "Jane Smith (Developer)"

Modules

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// Exporting (in math.js)
export const PI = 3.14159;

export function add(a, b) {
return a + b;
}

export function subtract(a, b) {
return a - b;
}

const privateFunction = () => {
console.log('This is private');
};

export default class Calculator {
multiply(a, b) {
return a * b;
}
}

// Importing (in app.js)
import Calculator, { PI, add } from './math.js';
import * as math from './math.js';

console.log(PI); // 3.14159
console.log(add(2, 3)); // 5

const calc = new Calculator();
console.log(calc.multiply(4, 5)); // 20

console.log(math.subtract(10, 4)); // 6

Promises

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
// Creating a promise
const fetchData = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
const data = { id: 1, name: 'Product' };
const success = true;

if (success) {
resolve(data);
} else {
reject(new Error('Failed to fetch data'));
}
}, 1000);
});
};

// Using a promise
fetchData()
.then(data => {
console.log('Success:', data);
return { ...data, processed: true };
})
.then(processedData => {
console.log('Processed:', processedData);
})
.catch(error => {
console.error('Error:', error.message);
})
.finally(() => {
console.log('Operation completed');
});

// Promise.all - wait for all promises to resolve
Promise.all([
fetchData(),
fetchData() // Another promise
])
.then(results => {
console.log('All results:', results);
})
.catch(error => {
console.error('At least one promise failed:', error);
});

// Promise.race - resolve when first promise resolves/rejects
Promise.race([
fetchData(),
new Promise((_, reject) => setTimeout(() => reject(new Error('Timeout')), 2000))
])
.then(result => {
console.log('First to complete:', result);
})
.catch(error => {
console.error('First to fail:', error);
});

Iterators and Generators

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
// Iterators
const array = [1, 2, 3];
const iterator = array[Symbol.iterator]();

console.log(iterator.next()); // { value: 1, done: false }
console.log(iterator.next()); // { value: 2, done: false }
console.log(iterator.next()); // { value: 3, done: false }
console.log(iterator.next()); // { value: undefined, done: true }

// Custom iterable
const customIterable = {
data: [10, 20, 30],

[Symbol.iterator]() {
let index = 0;
const data = this.data;

return {
next() {
if (index < data.length) {
return { value: data[index++], done: false };
} else {
return { value: undefined, done: true };
}
}
};
}
};

for (const item of customIterable) {
console.log(item); // 10, 20, 30
}

// Generators
function* numberGenerator() {
yield 1;
yield 2;
yield 3;
}

const numGen = numberGenerator();
console.log(numGen.next()); // { value: 1, done: false }
console.log(numGen.next()); // { value: 2, done: false }
console.log(numGen.next()); // { value: 3, done: false }
console.log(numGen.next()); // { value: undefined, done: true }

// Generator with data received via next()
function* conversation() {
const name = yield 'What is your name?';
const hobby = yield `Hello, ${name}! What is your hobby?`;
yield `${name} likes ${hobby}. That's great!`;
}

const convo = conversation();
console.log(convo.next().value); // "What is your name?"
console.log(convo.next('John').value); // "Hello, John! What is your hobby?"
console.log(convo.next('coding').value); // "John likes coding. That's great!"

Map and Set

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
// Map - key-value pairs (any type of keys)
const userMap = new Map();

// Adding entries
userMap.set('name', 'John');
userMap.set(42, 'answer');
userMap.set(true, 'boolean key');

// Object as key
const userObj = { id: 1 };
userMap.set(userObj, 'object as key');

// Getting values
console.log(userMap.get('name')); // "John"
console.log(userMap.get(userObj)); // "object as key"

// Checking if key exists
console.log(userMap.has(true)); // true
console.log(userMap.has('missing')); // false

// Size and deletion
console.log(userMap.size); // 4
userMap.delete(42);
console.log(userMap.size); // 3

// Iteration
for (const [key, value] of userMap) {
console.log(key, value);
}

// Set - collection of unique values
const uniqueNumbers = new Set([1, 2, 3, 3, 4, 4, 5]);
console.log(uniqueNumbers); // Set(5) { 1, 2, 3, 4, 5 }

// Adding values
uniqueNumbers.add(6);
uniqueNumbers.add(1); // Ignored as 1 already exists

// Checking if value exists
console.log(uniqueNumbers.has(3)); // true

// Size and deletion
console.log(uniqueNumbers.size); // 6
uniqueNumbers.delete(4);
console.log(uniqueNumbers.size); // 5

// Iteration
for (const num of uniqueNumbers) {
console.log(num);
}

// Converting Set to Array
const uniqueArray = [...uniqueNumbers];
console.log(uniqueArray); // [1, 2, 3, 5, 6]

// Using Set to remove duplicates from array
const numbers = [1, 2, 2, 3, 4, 4, 5];
const unique = [...new Set(numbers)];
console.log(unique); // [1, 2, 3, 4, 5]

Symbol

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
// Creating symbols
const sym1 = Symbol();
const sym2 = Symbol('description');
const sym3 = Symbol('description'); // sym2 !== sym3

// Symbols as object keys
const obj = {
[sym1]: 'value for sym1',
regularProp: 'regular value'
};

console.log(obj[sym1]); // "value for sym1"

// Symbols are not enumerable with for...in
for (const key in obj) {
console.log(key); // Only "regularProp"
}

// Getting symbols
console.log(Object.getOwnPropertySymbols(obj)); // [Symbol()]

// Well-known symbols
class CustomIterable {
constructor(items) {
this.items = items;
}

// Implement iteration protocol
[Symbol.iterator]() {
let index = 0;
const items = this.items;

return {
next() {
if (index < items.length) {
return { value: items[index++], done: false };
}
return { value: undefined, done: true };
}
};
}
}

const customList = new CustomIterable(['a', 'b', 'c']);
for (const item of customList) {
console.log(item); // "a", "b", "c"
}

Post-ES6 Features

Async/Await (ES2017)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
// Async function declaration
async function fetchUser(id) {
try {
const response = await fetch(`https://api.example.com/users/${id}`);
if (!response.ok) {
throw new Error('Failed to fetch user');
}
const user = await response.json();
return user;
} catch (error) {
console.error('Error fetching user:', error);
throw error;
}
}

// Using an async function
fetchUser(1)
.then(user => console.log('User:', user))
.catch(error => console.error('Failed:', error));

// Async arrow functions
const fetchProduct = async (id) => {
const response = await fetch(`https://api.example.com/products/${id}`);
return response.json();
};

// Async with array methods
const fetchMultipleUsers = async (ids) => {
// Sequential execution
const users = [];
for (const id of ids) {
const user = await fetchUser(id);
users.push(user);
}
return users;

// Parallel execution
// return Promise.all(ids.map(id => fetchUser(id)));
};

// Async with class methods
class UserAPI {
async getUser(id) {
// Implementation...
}

static async getAllUsers() {
// Implementation...
}
}

Object Methods (ES2017)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
const person = {
name: 'John',
age: 30,
city: 'New York'
};

// Object.values
console.log(Object.values(person)); // ["John", 30, "New York"]

// Object.entries
console.log(Object.entries(person));
// [["name", "John"], ["age", 30], ["city", "New York"]]

// Using Object.entries with destructuring
for (const [key, value] of Object.entries(person)) {
console.log(`${key}: ${value}`);
}

// Object.fromEntries (ES2019)
const entries = [
['name', 'Alice'],
['age', 25],
['city', 'London']
];

const personObj = Object.fromEntries(entries);
console.log(personObj); // { name: "Alice", age: 25, city: "London" }

// Object.fromEntries with Map
const map = new Map([
['name', 'Bob'],
['age', 40]
]);

const obj = Object.fromEntries(map);
console.log(obj); // { name: "Bob", age: 40 }

Rest/Spread for Objects (ES2018)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// Rest with object destructuring
const { name, ...rest } = { name: 'John', age: 30, city: 'New York' };
console.log(name); // "John"
console.log(rest); // { age: 30, city: "New York" }

// Spread with objects
const defaults = { theme: 'light', fontSize: 16 };
const userSettings = { fontSize: 20, showSidebar: true };

// Merging objects (later properties override earlier ones)
const settings = { ...defaults, ...userSettings };
console.log(settings);
// { theme: "light", fontSize: 20, showSidebar: true }

// Conditional spread
const hasPermission = true;
const userWithPerms = {
name: 'Jane',
...(hasPermission && { role: 'admin' })
};
console.log(userWithPerms); // { name: "Jane", role: "admin" }

Optional Chaining (ES2020)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
const user = {
name: 'John',
address: {
street: '123 Main St',
city: 'New York'
},
getFullName() {
return `${this.name} Doe`;
}
};

// Without optional chaining
let cityOld;
if (user && user.address && user.address.city) {
cityOld = user.address.city;
}

// With optional chaining
const city = user?.address?.city;
console.log(city); // "New York"

const zipCode = user?.address?.zipCode;
console.log(zipCode); // undefined

// Optional chaining with function calls
const fullName = user?.getFullName?.();
console.log(fullName); // "John Doe"

// Optional chaining with array elements
const users = [{ name: 'John' }];
console.log(users?.[0]?.name); // "John"
console.log(users?.[1]?.name); // undefined

Nullish Coalescing (ES2020)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// Logical OR - returns right side when left is falsy
// Falsy values: false, 0, "", null, undefined, NaN
const name1 = "" || "Default Name";
console.log(name1); // "Default Name" (problem if empty string is valid)

const count1 = 0 || 10;
console.log(count1); // 10 (problem if 0 is valid)

// Nullish coalescing - only falls back when left is null or undefined
const name2 = "" ?? "Default Name";
console.log(name2); // "" (empty string is respected as a valid value)

const count2 = 0 ?? 10;
console.log(count2); // 0 (zero is respected as a valid value)

const nullValue = null ?? "Default";
console.log(nullValue); // "Default"

const undefinedValue = undefined ?? "Default";
console.log(undefinedValue); // "Default"

// Combining with optional chaining
const user = null;
console.log(user?.name ?? "Guest"); // "Guest"

Logical Assignment (ES2021)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// Logical OR assignment (||=)
let user1 = { name: 'John' };
user1.name ||= 'Guest';
console.log(user1.name); // "John"

user1.role ||= 'User';
console.log(user1.role); // "User"

// Logical AND assignment (&&=)
let user2 = { name: 'Admin', verified: true };
user2.verified &&= false;
console.log(user2.verified); // false

// Nullish coalescing assignment (??=)
let user3 = { name: 'Jane' };
user3.name ??= 'Guest';
console.log(user3.name); // "Jane"

user3.role ??= 'User';
console.log(user3.role); // "User"

// Empty string example
let user4 = { name: '' };
user4.name ||= 'Guest'; // Assigns 'Guest' because empty string is falsy
user4.name ??= 'Default'; // Doesn't assign because empty string is not null/undefined
console.log(user4.name); // "Guest"

BigInt (ES2020)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// Creating BigInt values
const bigInt1 = 9007199254740991n; // Append 'n' to create a BigInt literal
const bigInt2 = BigInt(9007199254740991); // Or use the BigInt() function

// Operations with BigInt
const sum = bigInt1 + BigInt(1000);
console.log(sum); // 9007199254741991n

// BigInt can represent integers of arbitrary size
const reallyBig = 9007199254740991n * 9007199254740991n;
console.log(reallyBig); // 81129638414606663681390495662081n

// Cannot mix BigInt and regular numbers
// const invalid = bigInt1 + 1; // TypeError

// Comparisons work
console.log(10n === BigInt(10)); // true
console.log(10n == 10); // true (type coercion)

// BigInt with conditionals
if (bigInt1 > 1000n) {
console.log('Really big!');
}

Private Class Fields (ES2022)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
class BankAccount {
// Public field
owner;

// Private field (with # prefix)
#balance;

// Private static field
static #interestRate = 0.01;

constructor(owner, initialBalance) {
this.owner = owner;
this.#balance = initialBalance;
}

// Public method
deposit(amount) {
this.#balance += amount;
return this.#balance;
}

// Public method accessing private field
getBalance() {
return this.#balance;
}

// Private method
#calculateInterest() {
return this.#balance * BankAccount.#interestRate;
}

// Public method using private method
addInterest() {
const interest = this.#calculateInterest();
this.#balance += interest;
return interest;
}
}

const account = new BankAccount('John', 1000);
console.log(account.owner); // "John"
console.log(account.getBalance()); // 1000

account.deposit(500);
console.log(account.getBalance()); // 1500

console.log(account.addInterest()); // 15 (interest amount)

// Cannot access private fields directly
// console.log(account.#balance); // SyntaxError
// account.#calculateInterest(); // SyntaxError

Top-level Await (ES2022)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// Before ES2022, await could only be used inside async functions
async function getData() {
const response = await fetch('https://api.example.com/data');
return response.json();
}

// With ES2022, await can be used at the top level in modules
// Note: This only works in ES modules, not CommonJS
const response = await fetch('https://api.example.com/data');
const data = await response.json();
console.log(data);

// Useful for conditional imports
let mathModule;
if (data.useAdvancedMath) {
mathModule = await import('./advanced-math.js');
} else {
mathModule = await import('./basic-math.js');
}

// Or for initializing an app
const config = await fetch('/api/config').then(r => r.json());
const db = await initDatabase(config);
startApp(db);

Learning Resources

  • MDN Web Docs - JavaScript
  • Exploring ES6
  • ES6 Features
  • What’s New in ES2022
  • JavaScript Info - Modern JavaScript
  • Babel - JavaScript compiler that allows you to use newer syntax
  • Web Development
  • JavaScript
  • ES6
  • ECMAScript
  • Modern JavaScript

show all >>

JavaScript Advanced Concepts

  2023-06-06
字数统计: 3.2k字   |   阅读时长: 19min

JavaScript Advanced Concepts

Moving beyond the basics, JavaScript has many powerful advanced features that enable sophisticated application development. This guide covers the key advanced concepts that every frontend developer should understand.

Scope and Closures

Execution Context and Scope Chain

Every time JavaScript code runs, it’s within an execution context. There are three types:

  • Global Execution Context: Default context for code that’s not inside any function
  • Function Execution Context: Created when a function is called
  • Eval Execution Context: Created when code is executed in an eval function

Each context has:

  • Variable Environment: Variables, functions, and arguments
  • Scope Chain: References to outer variable environments
  • this Value: The value of this in that context
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
let globalVar = "I'm global";

function outer() {
let outerVar = "I'm in outer";

function inner() {
let innerVar = "I'm in inner";
console.log(innerVar); // Accessible: own scope
console.log(outerVar); // Accessible: outer function's scope
console.log(globalVar); // Accessible: global scope
}

inner();
// console.log(innerVar); // Error: not accessible (inner scope)
}

outer();
// console.log(outerVar); // Error: not accessible (outer function scope)

Closures

A closure is created when a function retains access to its lexical scope even when executed outside that scope:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
function createCounter() {
let count = 0; // Private variable

return {
increment() {
count++;
return count;
},
decrement() {
count--;
return count;
},
getValue() {
return count;
}
};
}

const counter = createCounter();
console.log(counter.increment()); // 1
console.log(counter.increment()); // 2
console.log(counter.getValue()); // 2
console.log(counter.decrement()); // 1

// count is not directly accessible
// console.log(counter.count); // undefined

Closures are commonly used for:

  • Data encapsulation (private variables)
  • Factory functions
  • Event handlers and callbacks
  • Partially applied functions

Module Pattern

Closures enable the module pattern, a way to create private variables and methods:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
const calculator = (function() {
// Private variables/methods
let result = 0;

function validateNumber(num) {
return typeof num === 'number' && !isNaN(num);
}

// Public API
return {
add(num) {
if (validateNumber(num)) {
result += num;
}
return this; // For method chaining
},
subtract(num) {
if (validateNumber(num)) {
result -= num;
}
return this;
},
getResult() {
return result;
},
reset() {
result = 0;
return this;
}
};
})(); // IIFE - Immediately Invoked Function Expression

calculator.add(5).add(10).subtract(3);
console.log(calculator.getResult()); // 12
calculator.reset();
console.log(calculator.getResult()); // 0

This Binding

The value of this depends on how a function is called:

Default Binding

When a function is called without any context, this refers to the global object (or undefined in strict mode):

1
2
3
4
5
function showThis() {
console.log(this);
}

showThis(); // window in browser (or global in Node.js), or undefined in strict mode

Implicit Binding

When a function is called as a method of an object, this refers to that object:

1
2
3
4
5
6
7
8
9
10
11
12
const user = {
name: "Alice",
greet() {
console.log(`Hello, I'm ${this.name}`);
}
};

user.greet(); // "Hello, I'm Alice"

// But if we extract the method:
const greetFunction = user.greet;
greetFunction(); // "Hello, I'm undefined" (default binding applies)

Explicit Binding

You can explicitly set this using call, apply, or bind:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function introduce(greeting, punctuation) {
console.log(`${greeting}, I'm ${this.name}${punctuation}`);
}

const person = { name: "Bob" };

// Using call (comma-separated arguments)
introduce.call(person, "Hi", "!"); // "Hi, I'm Bob!"

// Using apply (arguments as array)
introduce.apply(person, ["Hello", "."]); // "Hello, I'm Bob."

// Using bind (returns a new function with bound this)
const boundIntroduce = introduce.bind(person, "Hey");
boundIntroduce("..."); // "Hey, I'm Bob..."

New Binding

When a function is used as a constructor with new, this refers to the newly created object:

1
2
3
4
5
6
7
8
9
function Person(name) {
this.name = name;
this.sayName = function() {
console.log(`My name is ${this.name}`);
};
}

const alice = new Person("Alice");
alice.sayName(); // "My name is Alice"

Arrow Functions and Lexical This

Arrow functions don’t have their own this. They inherit this from the surrounding scope:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const obj = {
name: "Object",
regularMethod: function() {
console.log(`Regular method: ${this.name}`);

// Inner function loses this context
function innerFunction() {
console.log(`Inner function: ${this.name}`); // undefined (or global)
}

// Arrow function preserves this context
const arrowFunction = () => {
console.log(`Arrow function: ${this.name}`); // "Object"
};

innerFunction();
arrowFunction();
}
};

obj.regularMethod();

Prototypes and Inheritance

Prototype Chain

JavaScript uses a prototype chain for inheritance. Every object has a prototype (except for the base Object.prototype):

1
2
3
4
const arr = [];
console.log(arr.__proto__ === Array.prototype); // true
console.log(Array.prototype.__proto__ === Object.prototype); // true
console.log(Object.prototype.__proto__); // null

Creating Objects with Prototypes

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// Constructor function
function Animal(name) {
this.name = name;
}

Animal.prototype.makeSound = function() {
return "Generic animal sound";
};

// Inheritance with constructor functions
function Dog(name, breed) {
Animal.call(this, name); // Call parent constructor
this.breed = breed;
}

// Set up prototype chain
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog; // Fix constructor property

// Override method
Dog.prototype.makeSound = function() {
return "Woof!";
};

// Add new method
Dog.prototype.fetch = function() {
return `${this.name} is fetching!`;
};

const animal = new Animal("Generic Animal");
const dog = new Dog("Rex", "German Shepherd");

console.log(animal.makeSound()); // "Generic animal sound"
console.log(dog.makeSound()); // "Woof!"
console.log(dog.fetch()); // "Rex is fetching!"
console.log(dog instanceof Dog); // true
console.log(dog instanceof Animal); // true
console.log(dog instanceof Object); // true

Class Syntax (ES6+)

ES6 introduced a cleaner syntax for working with prototypes via classes:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
class Animal {
constructor(name) {
this.name = name;
}

makeSound() {
return "Generic animal sound";
}

// Static method (on the class, not instances)
static isAnimal(obj) {
return obj instanceof Animal;
}
}

class Dog extends Animal {
constructor(name, breed) {
super(name); // Call parent constructor
this.breed = breed;
}

makeSound() {
return "Woof!";
}

fetch() {
return `${this.name} is fetching!`;
}

// Getter
get description() {
return `${this.name} is a ${this.breed}`;
}

// Setter
set nickname(value) {
this._nickname = value;
}

get nickname() {
return this._nickname;
}
}

const dog = new Dog("Max", "Labrador");
console.log(dog.makeSound()); // "Woof!"
console.log(dog.description); // "Max is a Labrador"
dog.nickname = "Maxie";
console.log(dog.nickname); // "Maxie"
console.log(Animal.isAnimal(dog)); // true

Asynchronous JavaScript

Callbacks

Traditional way to handle asynchronous operations, but can lead to “callback hell”:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
function fetchData(callback) {
setTimeout(() => {
const data = { id: 1, name: "Product" };
callback(null, data); // Error-first callback pattern
}, 1000);
}

fetchData((error, data) => {
if (error) {
console.error("Error:", error);
return;
}

console.log("Data:", data);

// Nested callback - leads to "callback hell" with more nesting
fetchRelatedData(data.id, (error, relatedData) => {
if (error) {
console.error("Error:", error);
return;
}

console.log("Related data:", relatedData);
});
});

Promises

Promises provide a cleaner way to work with asynchronous code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
function fetchData() {
return new Promise((resolve, reject) => {
setTimeout(() => {
const data = { id: 1, name: "Product" };
// Simulate success
resolve(data);
// Or failure: reject(new Error("Failed to fetch data"))
}, 1000);
});
}

function fetchRelatedData(id) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve([{ id: 101, productId: id, name: "Related Item" }]);
}, 1000);
});
}

// Promise chaining
fetchData()
.then(data => {
console.log("Data:", data);
return fetchRelatedData(data.id);
})
.then(relatedData => {
console.log("Related data:", relatedData);
})
.catch(error => {
console.error("Error:", error);
})
.finally(() => {
console.log("Operation complete (success or failure)");
});

// Promise combinators
Promise.all([fetchData(), fetchRelatedData(1)])
.then(([data, relatedData]) => {
console.log("All results:", data, relatedData);
})
.catch(error => {
console.error("Any error:", error);
});

Promise.race([
fetchData(),
new Promise((_, reject) => setTimeout(() => reject(new Error("Timeout")), 2000))
])
.then(result => console.log("First to complete:", result))
.catch(error => console.error("First to fail:", error));

Async/Await

Built on promises, async/await provides an even cleaner syntax for asynchronous code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
async function fetchAllData() {
try {
// await pauses execution until the promise resolves
const data = await fetchData();
console.log("Data:", data);

const relatedData = await fetchRelatedData(data.id);
console.log("Related data:", relatedData);

return { data, relatedData }; // This is wrapped in a Promise
} catch (error) {
console.error("Error in fetchAllData:", error);
throw error; // Re-throw for callers to handle
}
}

// Calling the async function
fetchAllData()
.then(result => console.log("Final result:", result))
.catch(error => console.error("Caught at call site:", error));

// Parallel operations with async/await
async function fetchInParallel() {
try {
// Start both operations at once
const dataPromise = fetchData();
const relatedPromise = fetchRelatedData(1);

// Wait for both to complete
const [data, related] = await Promise.all([dataPromise, relatedPromise]);

console.log("Parallel results:", data, related);
} catch (error) {
console.error("Parallel error:", error);
}
}

Generators and Iterators

Iterators

Objects that implement a next() method:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const rangeIterator = {
start: 0,
end: 3,
current: 0,

// Required iterator method
next() {
if (this.current < this.end) {
return { value: this.current++, done: false };
}
return { value: undefined, done: true };
}
};

let result = rangeIterator.next();
while (!result.done) {
console.log(result.value); // 0, 1, 2
result = rangeIterator.next();
}

Iterables

Objects that implement the iterator protocol via Symbol.iterator:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
const rangeIterable = {
start: 0,
end: 3,

// This makes the object iterable
[Symbol.iterator]() {
let current = this.start;
const end = this.end;

return {
next() {
if (current < end) {
return { value: current++, done: false };
}
return { value: undefined, done: true };
}
};
}
};

// Now we can use for...of
for (const num of rangeIterable) {
console.log(num); // 0, 1, 2
}

// Or spread operator
const numbers = [...rangeIterable]; // [0, 1, 2]

Generators

Functions that can pause and resume execution:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
function* rangeGenerator(start, end) {
for (let i = start; i < end; i++) {
yield i;
}
}

// Using the generator
const gen = rangeGenerator(0, 3);
console.log(gen.next()); // { value: 0, done: false }
console.log(gen.next()); // { value: 1, done: false }
console.log(gen.next()); // { value: 2, done: false }
console.log(gen.next()); // { value: undefined, done: true }

// Generator is iterable
for (const num of rangeGenerator(0, 3)) {
console.log(num); // 0, 1, 2
}

// Infinite generator with control
function* idGenerator() {
let id = 1;
while (true) {
const increment = yield id++; // yield returns control and accepts a value
if (increment) {
id += increment;
}
}
}

const ids = idGenerator();
console.log(ids.next().value); // 1
console.log(ids.next().value); // 2
console.log(ids.next(10).value); // 13 (2 + 1 + 10)

Async Generators and Iterators

Combining generators with async/await:

1
2
3
4
5
6
7
8
9
10
11
12
13
async function* asyncGenerator() {
yield await Promise.resolve(1);
yield await Promise.resolve(2);
yield await Promise.resolve(3);
}

async function useAsyncGenerator() {
for await (const value of asyncGenerator()) {
console.log(value); // 1, 2, 3
}
}

useAsyncGenerator();

Functional Programming Concepts

Pure Functions

Functions that:

  1. Always return the same output for the same input
  2. Have no side effects (don’t modify external state)
1
2
3
4
5
6
7
8
9
10
11
// Pure function
function add(a, b) {
return a + b;
}

// Impure function (side effect)
let total = 0;
function addToTotal(value) {
total += value; // Side effect: modifies external state
return total;
}

Higher-Order Functions

Functions that take and/or return other functions:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// Function that returns a function
function multiply(factor) {
return function(number) {
return number * factor;
};
}

const double = multiply(2);
const triple = multiply(3);

console.log(double(5)); // 10
console.log(triple(5)); // 15

// Function that takes a function
function applyOperation(a, b, operation) {
return operation(a, b);
}

const sum = applyOperation(5, 3, (a, b) => a + b); // 8
const difference = applyOperation(5, 3, (a, b) => a - b); // 2

Composition

Combining multiple functions to create a new function:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// Utility for function composition
const compose = (...functions) => input =>
functions.reduceRight((acc, fn) => fn(acc), input);

const pipe = (...functions) => input =>
functions.reduce((acc, fn) => fn(acc), input);

// Example functions
const addOne = x => x + 1;
const double = x => x * 2;
const square = x => x * x;

// Compose: execute from right to left
const transformCompose = compose(square, double, addOne);
console.log(transformCompose(3)); // square(double(addOne(3))) = square(double(4)) = square(8) = 64

// Pipe: execute from left to right
const transformPipe = pipe(addOne, double, square);
console.log(transformPipe(3)); // square(double(addOne(3))) = square(double(4)) = square(8) = 64

Currying

Transforming a function with multiple arguments into a sequence of functions each with a single argument:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// Regular function
function add(a, b, c) {
return a + b + c;
}

// Curried version
function curriedAdd(a) {
return function(b) {
return function(c) {
return a + b + c;
};
};
}

// Usage
console.log(add(1, 2, 3)); // 6
console.log(curriedAdd(1)(2)(3)); // 6

// Partial application
const addOne = curriedAdd(1);
const addOneAndTwo = addOne(2);
console.log(addOneAndTwo(3)); // 6

// Currying utility
function curry(fn) {
return function curried(...args) {
if (args.length >= fn.length) {
return fn.apply(this, args);
} else {
return function(...args2) {
return curried.apply(this, args.concat(args2));
};
}
};
}

const curriedAddUtil = curry(add);
console.log(curriedAddUtil(1)(2)(3)); // 6
console.log(curriedAddUtil(1, 2)(3)); // 6
console.log(curriedAddUtil(1, 2, 3)); // 6

Immutability

Working with data without modifying the original:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// Modifying arrays
const numbers = [1, 2, 3, 4];

// Bad (mutates original)
numbers.push(5);

// Good (returns new array)
const newNumbers = [...numbers, 5];
const filteredNumbers = numbers.filter(n => n > 2);
const doubledNumbers = numbers.map(n => n * 2);

// Modifying objects
const person = { name: "Alice", age: 30 };

// Bad (mutates original)
person.age = 31;

// Good (returns new object)
const updatedPerson = { ...person, age: 31 };
const withoutAge = (({ age, ...rest }) => rest)(person);

Proxy and Reflect

Proxy

Objects that intercept and redefine operations for another object:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
const target = {
name: "Target",
age: 25
};

const handler = {
get(target, prop, receiver) {
console.log(`Getting ${prop}`);
return prop in target ? target[prop] : `No property ${prop}`;
},

set(target, prop, value, receiver) {
console.log(`Setting ${prop} to ${value}`);
if (prop === "age" && typeof value !== "number") {
throw new TypeError("Age must be a number");
}
target[prop] = value;
return true; // Indicate success
},

has(target, prop) {
console.log(`Checking if ${prop} exists`);
return prop in target;
},

deleteProperty(target, prop) {
console.log(`Deleting ${prop}`);
delete target[prop];
return true; // Indicate success
}
};

const proxy = new Proxy(target, handler);

// Test operations
console.log(proxy.name); // Getting name, Target
console.log(proxy.unknown); // Getting unknown, No property unknown

proxy.age = 30; // Setting age to 30
// proxy.age = "thirty"; // TypeError: Age must be a number

console.log("name" in proxy); // Checking if name exists, true

delete proxy.age; // Deleting age

Reflect

Object with static methods for interceptable operations:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// Using Reflect with Proxy
const target = { name: "Target", age: 25 };

const handler = {
get(target, prop, receiver) {
console.log(`Getting ${prop}`);
return Reflect.get(target, prop, receiver);
},

set(target, prop, value, receiver) {
console.log(`Setting ${prop} to ${value}`);
return Reflect.set(target, prop, value, receiver);
}
};

const proxy = new Proxy(target, handler);
proxy.name = "New Target"; // Setting name to New Target
console.log(proxy.name); // Getting name, New Target

// Direct use of Reflect
console.log(Reflect.has(target, "age")); // true
console.log(Reflect.ownKeys(target)); // ["name", "age"]
console.log(Reflect.getPrototypeOf(target)); // [Object: null prototype] {}
Reflect.defineProperty(target, "property", { value: 42 });

Memory Management and Optimization

Memory Lifecycle

  1. Allocation: When memory is allocated by the JavaScript engine
  2. Usage: When the program reads/writes to that memory
  3. Release: When the memory is no longer needed and can be freed

Garbage Collection

JavaScript automatically manages memory using garbage collection:

  • Mark and Sweep: The most common algorithm used in modern browsers
    1. The garbage collector builds a list of “roots” (global objects)
    2. It marks these roots and all references from these roots
    3. It collects all non-marked objects

Memory Leaks

Common causes of memory leaks:

  1. Accidental Global Variables:
1
2
3
function leak() {
leakedVariable = "I'm leaked"; // Missing var/let/const
}
  1. Forgotten Timers or Callbacks:
1
2
3
4
5
6
7
8
function setupTimer() {
const largeData = new Array(10000000).fill('x');

setInterval(() => {
// This keeps largeData in memory indefinitely
console.log(largeData.length);
}, 1000);
}
  1. Closures Capturing Variables:
1
2
3
4
5
6
7
8
function createLeakyFunction() {
const largeData = new Array(10000000).fill('x');

return function() {
// This inner function captures largeData
console.log(largeData[0]);
};
}
  1. Detached DOM Elements:
1
2
3
4
5
6
7
8
9
10
11
12
function detachedDOM() {
const div = document.createElement('div');
div.innerHTML = 'A lot of content...';

// Store reference to the DOM element but never append it to the DOM
// or remove the reference when done
const detachedElements = [];
detachedElements.push(div);

// Later remove from DOM but keep in array
// detachedElements still holds a reference, preventing garbage collection
}

Performance Optimization

  1. Object Pooling:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Reuse objects instead of creating new ones
const objectPool = [];

function getObject() {
if (objectPool.length > 0) {
return objectPool.pop();
}
return { x: 0, y: 0 }; // Create new if pool is empty
}

function releaseObject(obj) {
obj.x = 0;
obj.y = 0;
objectPool.push(obj);
}
  1. Throttling and Debouncing:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// Throttle: Execute at most once every X ms
function throttle(func, limit) {
let lastFunc;
let lastRan;
return function(...args) {
if (!lastRan) {
func.apply(this, args);
lastRan = Date.now();
} else {
clearTimeout(lastFunc);
lastFunc = setTimeout(() => {
if (Date.now() - lastRan >= limit) {
func.apply(this, args);
lastRan = Date.now();
}
}, limit - (Date.now() - lastRan));
}
};
}

// Debounce: Execute only after X ms have passed without another call
function debounce(func, delay) {
let timeout;
return function(...args) {
clearTimeout(timeout);
timeout = setTimeout(() => {
func.apply(this, args);
}, delay);
};
}

// Example usage
window.addEventListener('resize', throttle(() => {
console.log('Resize event throttled');
}, 200));

document.getElementById('search').addEventListener('input', debounce((e) => {
console.log('Search query:', e.target.value);
}, 300));

Learning Resources

  • You Don’t Know JS (book series)
  • JavaScript: The Good Parts
  • Functional-Light JavaScript
  • Exploring ES6
  • JavaScript Allongé
  • MDN Web Docs - JavaScript
  • JavaScript.info
  • Frontend Masters - Advanced JavaScript courses
  • Web Development
  • JavaScript
  • Advanced

show all >>

JavaScript Basics

  2023-06-05
字数统计: 1.9k字   |   阅读时长: 11min

JavaScript Basics

JavaScript is a high-level, interpreted programming language that powers dynamic behavior on the web. It’s an essential skill for frontend development.

Introduction to JavaScript

JavaScript (JS) enables interactive web pages and is an essential part of web applications. It was created in 1995 by Brendan Eich and has since become one of the world’s most popular programming languages.

Where JavaScript Runs

  • Browser: Client-side JS runs in the user’s browser
  • Server: Node.js allows JS to run on servers
  • Mobile Apps: Frameworks like React Native use JS for mobile development
  • Desktop Apps: Electron enables JS-powered desktop applications

Setting Up JavaScript

In HTML

1
2
3
4
5
6
7
<!-- Internal JavaScript -->
<script>
console.log("Hello, World!");
</script>

<!-- External JavaScript -->
<script src="script.js"></script>

Console

You can also run JavaScript in the browser’s developer console (F12 or Right-click > Inspect > Console).

Variables and Data Types

Variable Declaration

There are three ways to declare variables:

1
2
3
4
5
6
7
8
9
10
// var - function-scoped (older way)
var age = 25;

// let - block-scoped, can be reassigned
let name = "John";
name = "Jane"; // Valid reassignment

// const - block-scoped, cannot be reassigned
const PI = 3.14159;
// PI = 3; // Error: Assignment to constant variable

Primitive Data Types

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// String - text
let greeting = "Hello, World!";
let phrase = 'JavaScript is fun';
let template = `My name is ${name}`; // Template literal

// Number - integers and floating points
let integer = 42;
let float = 3.14;
let scientific = 2.998e8; // Scientific notation

// Boolean - true or false
let isActive = true;
let isComplete = false;

// Undefined - variable declared but not assigned
let undefinedVar;
console.log(undefinedVar); // undefined

// Null - intentional absence of value
let emptyValue = null;

// Symbol - unique and immutable
const uniqueID = Symbol('id');

// BigInt - for integers larger than Number can handle
const bigNumber = 9007199254740991n;

Checking Types

1
2
3
4
5
6
7
typeof "Hello"; // "string"
typeof 42; // "number"
typeof true; // "boolean"
typeof undefined; // "undefined"
typeof null; // "object" (this is a known JavaScript quirk)
typeof Symbol('id'); // "symbol"
typeof 10n; // "bigint"

Complex Data Types

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// Object - collection of key-value pairs
let person = {
firstName: "John",
lastName: "Doe",
age: 30,
isEmployed: true,
greet: function() {
return `Hello, my name is ${this.firstName}`;
}
};

// Accessing object properties
console.log(person.firstName); // Dot notation
console.log(person["lastName"]); // Bracket notation
console.log(person.greet()); // Method call

// Array - ordered collection of values
let colors = ["red", "green", "blue"];
let mixed = [1, "two", true, { id: 4 }]; // Can hold different types

// Accessing array elements (zero-indexed)
console.log(colors[0]); // "red"
console.log(colors.length); // 3

// Function - reusable block of code
function add(a, b) {
return a + b;
}

// Arrow function
const subtract = (a, b) => a - b;

// Date
const today = new Date();

Operators

Arithmetic Operators

1
2
3
4
5
6
7
8
9
10
11
12
13
14
let a = 10;
let b = 3;

let sum = a + b; // Addition: 13
let difference = a - b; // Subtraction: 7
let product = a * b; // Multiplication: 30
let quotient = a / b; // Division: 3.33...
let remainder = a % b; // Modulus (remainder): 1
let exponent = a ** b; // Exponentiation: 1000

// Increment/Decrement
let counter = 5;
counter++; // Equivalent to: counter = counter + 1
counter--; // Equivalent to: counter = counter - 1

Assignment Operators

1
2
3
4
5
6
7
8
9
let x = 10; // Basic assignment

// Compound assignment
x += 5; // x = x + 5
x -= 3; // x = x - 3
x *= 2; // x = x * 2
x /= 4; // x = x / 4
x %= 3; // x = x % 3
x **= 2; // x = x ** 2

Comparison Operators

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
let a = 5;
let b = "5";

// Equal (value)
a == b; // true (converts types before comparison)

// Strict equal (value and type)
a === b; // false (different types)

// Not equal
a != b; // false

// Strict not equal
a !== b; // true

// Greater/less than
a > 4; // true
a < 10; // true
a >= 5; // true
a <= 5; // true

Logical Operators

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
let isAdult = true;
let hasPermission = false;

// AND - both must be true
isAdult && hasPermission; // false

// OR - at least one must be true
isAdult || hasPermission; // true

// NOT - inverts boolean value
!isAdult; // false
!hasPermission; // true

// Short-circuit evaluation
let name = null;
let displayName = name || "Anonymous"; // "Anonymous"

Control Flow

Conditional Statements

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// if statement
let age = 18;

if (age >= 18) {
console.log("You are an adult");
}

// if-else statement
if (age >= 18) {
console.log("You are an adult");
} else {
console.log("You are a minor");
}

// if-else if-else statement
if (age < 13) {
console.log("Child");
} else if (age < 18) {
console.log("Teenager");
} else {
console.log("Adult");
}

// Ternary operator - condition ? expressionIfTrue : expressionIfFalse
let status = age >= 18 ? "Adult" : "Minor";

// switch statement
let day = 3;
switch (day) {
case 1:
console.log("Monday");
break;
case 2:
console.log("Tuesday");
break;
case 3:
console.log("Wednesday");
break;
default:
console.log("Another day");
}

Loops

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// for loop
for (let i = 0; i < 5; i++) {
console.log(i); // 0, 1, 2, 3, 4
}

// while loop
let count = 0;
while (count < 5) {
console.log(count); // 0, 1, 2, 3, 4
count++;
}

// do-while loop (runs at least once)
let num = 0;
do {
console.log(num); // 0, 1, 2, 3, 4
num++;
} while (num < 5);

// for...of loop (for iterables like arrays)
let fruits = ["apple", "banana", "cherry"];
for (let fruit of fruits) {
console.log(fruit); // "apple", "banana", "cherry"
}

// for...in loop (for object properties)
let person = { name: "John", age: 30, job: "developer" };
for (let key in person) {
console.log(`${key}: ${person[key]}`);
}

// break and continue
for (let i = 0; i < 10; i++) {
if (i === 3) continue; // Skip this iteration
if (i === 7) break; // Exit the loop
console.log(i); // 0, 1, 2, 4, 5, 6
}

Functions

Function Declaration

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// Basic function declaration
function greet(name) {
return `Hello, ${name}!`;
}

console.log(greet("Alice")); // "Hello, Alice!"

// Function with default parameters
function greetWithDefault(name = "Guest") {
return `Hello, ${name}!`;
}

console.log(greetWithDefault()); // "Hello, Guest!"

// Function with multiple parameters
function calculateArea(length, width) {
return length * width;
}

console.log(calculateArea(5, 10)); // 50

Function Expressions

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// Function expression (anonymous)
const add = function(a, b) {
return a + b;
};

// Named function expression
const subtract = function sub(a, b) {
return a - b;
};

// Arrow functions
const multiply = (a, b) => a * b;

// Multi-line arrow function
const divide = (a, b) => {
if (b === 0) {
throw new Error("Cannot divide by zero");
}
return a / b;
};

Function Scope

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// Global scope
let globalVar = "I'm global";

function exampleFunction() {
// Function scope
let localVar = "I'm local";
console.log(globalVar); // Accessible
console.log(localVar); // Accessible
}

exampleFunction();
console.log(globalVar); // Accessible
// console.log(localVar); // Error: localVar is not defined

// Block scope with let/const
if (true) {
let blockVar = "I'm in a block";
const anotherBlockVar = "Me too";
var notBlockScoped = "I'll leak out"; // var is not block-scoped
}

// console.log(blockVar); // Error
// console.log(anotherBlockVar); // Error
console.log(notBlockScoped); // "I'll leak out" - accessible!

Higher-Order Functions

Functions that take other functions as arguments or return them:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Function as an argument
function executeOperation(operation, a, b) {
return operation(a, b);
}

const result = executeOperation(add, 5, 3); // 8

// Returning a function
function createMultiplier(factor) {
return function(number) {
return number * factor;
};
}

const double = createMultiplier(2);
console.log(double(10)); // 20

Working with Arrays

Array Methods

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
let fruits = ["apple", "banana", "cherry"];

// Basic array operations
fruits.push("orange"); // Add to end: ["apple", "banana", "cherry", "orange"]
fruits.pop(); // Remove from end: ["apple", "banana", "cherry"]
fruits.unshift("mango"); // Add to beginning: ["mango", "apple", "banana", "cherry"]
fruits.shift(); // Remove from beginning: ["apple", "banana", "cherry"]

// Finding elements
fruits.indexOf("banana"); // 1
fruits.includes("cherry"); // true

// Transforming arrays
let upperFruits = fruits.map(fruit => fruit.toUpperCase()); // ["APPLE", "BANANA", "CHERRY"]
let longFruits = fruits.filter(fruit => fruit.length > 5); // ["banana", "cherry"]
let allLengths = fruits.reduce((total, fruit) => total + fruit.length, 0); // Sum of all lengths

// Iterating
fruits.forEach(fruit => console.log(fruit));

// Sorting
fruits.sort(); // Alphabetical sort: ["apple", "banana", "cherry"]
fruits.reverse(); // Reverse: ["cherry", "banana", "apple"]

// Slicing and splicing
let citrus = fruits.slice(1, 3); // Shallow copy of portion: ["banana", "cherry"]
fruits.splice(1, 1, "blueberry", "blackberry"); // Remove and insert: ["apple", "blueberry", "blackberry", "cherry"]

Working with Objects

Object Methods

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
let person = {
firstName: "John",
lastName: "Doe",
age: 30
};

// Getting information
Object.keys(person); // ["firstName", "lastName", "age"]
Object.values(person); // ["John", "Doe", 30]
Object.entries(person); // [["firstName", "John"], ["lastName", "Doe"], ["age", 30]]

// Checking properties
person.hasOwnProperty("age"); // true

// Adding/modifying properties
person.email = "john@example.com";
Object.assign(person, { phone: "123-456-7890", age: 31 }); // Updates age, adds phone

// Preventing modifications
const frozen = Object.freeze({ x: 1, y: 2 }); // Cannot be modified
const sealed = Object.seal({ a: 1, b: 2 }); // Properties can't be added/removed but can be modified

Object Destructuring

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
const user = {
id: 1,
displayName: "jdoe",
fullName: {
firstName: "John",
lastName: "Doe"
}
};

// Basic destructuring
const { id, displayName } = user;
console.log(id); // 1
console.log(displayName); // "jdoe"

// Nested destructuring
const { fullName: { firstName, lastName } } = user;
console.log(firstName); // "John"

// Default values
const { admin = false } = user;
console.log(admin); // false

// Renaming
const { displayName: username } = user;
console.log(username); // "jdoe"

Error Handling

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// try...catch
try {
// Code that might throw an error
const result = nonExistentFunction();
} catch (error) {
console.error("An error occurred:", error.message);
} finally {
// Code that always runs
console.log("This always executes");
}

// Throwing custom errors
function divide(a, b) {
if (b === 0) {
throw new Error("Division by zero");
}
return a / b;
}

try {
console.log(divide(10, 0));
} catch (error) {
console.error(error.message); // "Division by zero"
}

JavaScript Best Practices

  1. Use strict mode to catch common coding errors:

    1
    2
    'use strict';
    // Your code here
  2. Prefer const/let over var for more predictable scoping

  3. Use meaningful variable and function names for better readability

  4. Add comments for complex logic, but make code self-documenting when possible

  5. Handle errors appropriately using try/catch

  6. Avoid global variables to prevent namespace pollution

  7. Use === and !== instead of == and != to avoid unexpected type coercion

  8. Use default parameters instead of checking for undefined

  9. Use template literals for string concatenation

  10. Use array and object destructuring for cleaner code

Learning Resources

  • MDN JavaScript Guide
  • JavaScript.info
  • Eloquent JavaScript
  • freeCodeCamp JavaScript Course
  • Web Development
  • JavaScript
  • Programming

show all >>

CSS Layout & Responsive Design

  2023-06-04
字数统计: 1.2k字   |   阅读时长: 7min

CSS Layout & Responsive Design

Modern web design requires layouts that adapt to different screen sizes and devices. This guide covers the essential CSS layout techniques and approaches to responsive design.

Traditional Layout Methods

1. Normal Flow

The default layout mode where elements are displayed one after another:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
.box {
/* Block elements occupy full width */
display: block;
width: 100%;
margin-bottom: 20px;
}

.inline-element {
/* Inline elements flow within text */
display: inline;
margin: 0 5px;
}

.inline-block {
/* Combines features of both */
display: inline-block;
width: 100px;
margin: 0 10px;
}

2. Floats

Originally designed for wrapping text around images, floats have been used for creating multi-column layouts:

1
2
3
4
5
6
7
8
9
10
11
12
.container::after {
content: "";
display: table;
clear: both; /* Clearfix to contain floats */
}

.column {
float: left;
width: 33.33%;
padding: 15px;
box-sizing: border-box;
}

3. Positioning

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
.static {
/* Default positioning */
position: static;
}

.relative {
/* Positioned relative to its normal position */
position: relative;
top: 10px;
left: 20px;
}

.absolute {
/* Positioned relative to the nearest positioned ancestor */
position: absolute;
top: 0;
right: 0;
}

.fixed {
/* Positioned relative to the viewport */
position: fixed;
bottom: 20px;
right: 20px;
}

.sticky {
/* Acts like relative, becomes fixed when scrolled */
position: sticky;
top: 0;
}

Modern Layout Methods

1. Flexbox

Flexbox is designed for one-dimensional layouts - either rows or columns:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
.container {
display: flex;
flex-direction: row; /* or column */
flex-wrap: wrap; /* or nowrap */
justify-content: space-between; /* horizontal alignment */
align-items: center; /* vertical alignment */
gap: 20px; /* spacing between items */
}

.item {
flex: 1; /* grow and shrink equally */
}

.important-item {
flex: 2; /* takes twice as much space */
}

.fixed-item {
flex: 0 0 200px; /* don't grow, don't shrink, stay at 200px */
}

Common flexbox properties:

For the container (parent):

  • display: flex
  • flex-direction: row | row-reverse | column | column-reverse
  • flex-wrap: nowrap | wrap | wrap-reverse
  • justify-content: flex-start | flex-end | center | space-between | space-around | space-evenly
  • align-items: stretch | flex-start | flex-end | center | baseline
  • align-content: flex-start | flex-end | center | space-between | space-around | stretch
  • gap: [row-gap] [column-gap]

For the items (children):

  • flex: [grow] [shrink] [basis]
  • align-self: auto | flex-start | flex-end | center | baseline | stretch
  • order: <integer> (default is 0)

2. CSS Grid

Grid is designed for two-dimensional layouts - rows and columns simultaneously:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
.grid-container {
display: grid;
grid-template-columns: repeat(3, 1fr); /* 3 equal columns */
grid-template-rows: 100px auto 100px; /* specific row heights */
gap: 20px;
}

.header {
grid-column: 1 / -1; /* spans all columns */
}

.sidebar {
grid-row: 2 / 3; /* specific row position */
grid-column: 1 / 2; /* specific column position */
}

.content {
grid-row: 2 / 3;
grid-column: 2 / -1; /* from column 2 to the end */
}

.footer {
grid-column: 1 / -1;
}

More complex grid example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
.advanced-grid {
display: grid;
grid-template-columns: [start] 1fr [content-start] 2fr [content-end] 1fr [end];
grid-template-rows: [top] auto [content-top] 1fr [content-bottom] auto [bottom];
grid-template-areas:
"header header header"
"sidebar content related"
"footer footer footer";
min-height: 100vh;
}

.header { grid-area: header; }
.sidebar { grid-area: sidebar; }
.content { grid-area: content; }
.related { grid-area: related; }
.footer { grid-area: footer; }

Responsive Design Principles

1. Viewport Meta Tag

1
<meta name="viewport" content="width=device-width, initial-scale=1.0">

2. Media Queries

Media queries allow applying different styles based on device characteristics:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/* Base styles for all screen sizes */
.container {
padding: 15px;
}

/* Styles for medium screens and up */
@media screen and (min-width: 768px) {
.container {
padding: 30px;
}
}

/* Styles for large screens */
@media screen and (min-width: 1024px) {
.container {
max-width: 1200px;
margin: 0 auto;
}
}

/* Print styles */
@media print {
.no-print {
display: none;
}
}

Common media query breakpoints:

  • Mobile: 320px - 480px
  • Tablets: 481px - 768px
  • Laptops: 769px - 1024px
  • Desktops: 1025px - 1200px
  • TV/Large screens: 1201px and above

3. Fluid Layouts

Rather than fixed pixel sizes, use percentage-based widths for flexible layouts:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
.container {
width: 100%;
max-width: 1200px;
margin: 0 auto;
}

.column {
width: 100%;
}

@media (min-width: 768px) {
.column {
width: 50%;
float: left;
}
}

@media (min-width: 1024px) {
.column {
width: 33.33%;
}
}

4. Responsive Units

Use relative units instead of fixed pixels:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
html {
font-size: 16px; /* Base font size */
}

.container {
width: 100%;
padding: 1rem; /* relative to root font size */
}

h1 {
font-size: 2.5rem; /* 40px if base is 16px */
}

.sidebar {
width: 30%; /* percentage of parent */
}

.full-height {
height: 100vh; /* viewport height */
}

.half-width {
width: 50vw; /* viewport width */
}

.flexible-padding {
padding: calc(1rem + 2vw); /* combines fixed and relative units */
}

Common responsive units:

  • %: Relative to parent element
  • em: Relative to the font-size of the element
  • rem: Relative to the font-size of the root element
  • vh: 1% of viewport height
  • vw: 1% of viewport width
  • vmin: 1% of the smaller dimension (height or width)
  • vmax: 1% of the larger dimension (height or width)

5. Responsive Images

1
2
3
4
img {
max-width: 100%;
height: auto;
}

HTML picture element for art direction:

1
2
3
4
5
<picture>
<source media="(min-width: 1024px)" srcset="large.jpg">
<source media="(min-width: 768px)" srcset="medium.jpg">
<img src="small.jpg" alt="Responsive image">
</picture>

6. Mobile-First Approach

Start with styles for mobile devices and progressively enhance for larger screens:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/* Base styles for mobile */
.navigation {
display: flex;
flex-direction: column;
}

.nav-item {
margin-bottom: 10px;
}

/* Enhanced styles for tablets and up */
@media (min-width: 768px) {
.navigation {
flex-direction: row;
justify-content: space-between;
}

.nav-item {
margin-bottom: 0;
margin-right: 20px;
}
}

CSS Layout Best Practices

  1. Choose the right layout technique for the job:

    • Flexbox for one-dimensional layouts (rows or columns)
    • Grid for two-dimensional layouts (rows and columns)
    • Positioning for specific placement needs
  2. Create a consistent grid system to maintain alignment and spacing

  3. Use a mobile-first approach to ensure good performance on all devices

  4. Test on real devices whenever possible, not just browser resizing

  5. Consider accessibility in your layout choices (e.g., logical tab order)

  6. Avoid mixing layout techniques unnecessarily which can lead to unexpected results

  7. Use CSS custom properties (variables) for consistent spacing and breakpoints:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
:root {
--spacing-sm: 0.5rem;
--spacing-md: 1rem;
--spacing-lg: 2rem;
--breakpoint-sm: 576px;
--breakpoint-md: 768px;
--breakpoint-lg: 992px;
--breakpoint-xl: 1200px;
}

.container {
padding: var(--spacing-md);
}

@media (min-width: var(--breakpoint-md)) {
.container {
padding: var(--spacing-lg);
}
}

Learning Resources

  • MDN - CSS Layout
  • CSS-Tricks Guide to Flexbox
  • CSS-Tricks Guide to Grid
  • Responsive Web Design Fundamentals
  • Every Layout - Sophisticated layouts using simple CSS
  • Smashing Magazine - Responsive Design Articles
  • Web Development
  • CSS
  • Layout
  • Responsive Design
  • Flexbox
  • Grid

show all >>

CSS Fundamentals

  2023-06-03
字数统计: 843字   |   阅读时长: 5min

CSS Fundamentals

CSS (Cascading Style Sheets) is a stylesheet language used to describe the presentation of a document written in HTML. CSS controls the layout, colors, fonts, and overall visual appearance of web pages.

CSS Syntax

CSS consists of selectors and declarations:

1
2
3
4
selector {
property: value;
another-property: another-value;
}

For example:

1
2
3
4
h1 {
color: blue;
font-size: 24px;
}

Ways to Include CSS

1. External CSS

1
2
3
<head>
<link rel="stylesheet" href="styles.css">
</head>

2. Internal CSS

1
2
3
4
5
6
7
<head>
<style>
body {
background-color: #f0f0f0;
}
</style>
</head>

3. Inline CSS

1
<p style="color: red; font-size: 16px;">This is a paragraph.</p>

CSS Selectors

Selectors determine which elements the styles will be applied to.

Basic Selectors

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/* Element selector */
p {
color: gray;
}

/* Class selector */
.highlight {
background-color: yellow;
}

/* ID selector */
#header {
font-size: 32px;
}

/* Universal selector */
* {
margin: 0;
padding: 0;
}

Combinators

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/* Descendant selector (elements inside other elements) */
article p {
font-style: italic;
}

/* Child selector (direct children only) */
ul > li {
list-style-type: square;
}

/* Adjacent sibling selector (element immediately after) */
h2 + p {
font-weight: bold;
}

/* General sibling selector (all siblings after) */
h2 ~ p {
color: purple;
}

Attribute Selectors

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/* Elements with a specific attribute */
a[target] {
color: red;
}

/* Elements with specific attribute value */
input[type="text"] {
border: 1px solid gray;
}

/* Elements with attribute value containing a word */
[class~="card"] {
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
}

Pseudo-classes and Pseudo-elements

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/* Pseudo-classes (special states) */
a:hover {
text-decoration: underline;
}

button:focus {
outline: 2px solid blue;
}

li:first-child {
font-weight: bold;
}

li:nth-child(odd) {
background-color: #f2f2f2;
}

/* Pseudo-elements (specific parts of elements) */
p::first-line {
font-variant: small-caps;
}

p::before {
content: "→ ";
}

p::after {
content: " ←";
}

The CSS Box Model

Every HTML element is a box consisting of:

  • Content: The actual content of the element
  • Padding: Space between the content and the border
  • Border: A line around the padding
  • Margin: Space outside the border
1
2
3
4
5
6
div {
width: 300px;
padding: 20px;
border: 5px solid black;
margin: 30px;
}

By default, width and height only set the content dimensions. The total width of an element is:
width + padding-left + padding-right + border-left + border-right + margin-left + margin-right

Box-sizing Property

1
2
3
4
/* Makes width/height include content, padding, and border */
* {
box-sizing: border-box;
}

Typography

1
2
3
4
5
6
7
8
9
10
11
12
p {
font-family: 'Arial', sans-serif;
font-size: 16px;
font-weight: 400;
line-height: 1.5;
text-align: justify;
text-decoration: underline;
text-transform: uppercase;
letter-spacing: 1px;
word-spacing: 3px;
color: #333;
}

Web Fonts

1
2
3
4
5
6
7
@font-face {
font-family: 'MyCustomFont';
src: url('fonts/mycustomfont.woff2') format('woff2'),
url('fonts/mycustomfont.woff') format('woff');
}

@import url('https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap');

Colors

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
.example {
/* Keyword */
color: red;

/* Hex */
background-color: #ff0000;

/* RGB */
border-color: rgb(255, 0, 0);

/* RGBA (with opacity) */
box-shadow: 0 0 10px rgba(255, 0, 0, 0.5);

/* HSL (hue, saturation, lightness) */
outline-color: hsl(0, 100%, 50%);

/* HSLA (with opacity) */
text-shadow: 1px 1px 3px hsla(0, 100%, 50%, 0.7);
}

CSS Cascade, Specificity, and Inheritance

Cascade

CSS rules cascade based on:

  1. Importance (!important)
  2. Specificity
  3. Source order (last defined rule wins)

Specificity

Specificity determines which rules take precedence:

  • Inline styles: 1000
  • IDs: 100
  • Classes, attributes, pseudo-classes: 10
  • Elements, pseudo-elements: 1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
/* Specificity: 1 */
p {
color: red;
}

/* Specificity: 11 (10 + 1) */
.content p {
color: blue;
}

/* Specificity: 101 (100 + 1) */
#main p {
color: green;
}

Inheritance

Some CSS properties are inherited from parent to child elements, like color, font-family, and text-align. Others, like margin and padding, are not.

1
2
3
4
5
6
7
8
body {
font-family: Arial, sans-serif; /* Inherited by all child elements */
color: #333; /* Inherited by all child elements */
}

h1 {
color: blue; /* Overrides inherited color */
}

CSS Variables (Custom Properties)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
:root {
--primary-color: #3498db;
--secondary-color: #2ecc71;
--padding-standard: 15px;
}

.button {
background-color: var(--primary-color);
padding: var(--padding-standard);
}

.alert {
border-color: var(--secondary-color);
padding: calc(var(--padding-standard) * 2);
}

Best Practices

  1. Use a CSS reset or normalize.css to ensure consistent styling across browsers
  2. Follow a naming convention like BEM (Block Element Modifier)
  3. Organize CSS logically by grouping related styles
  4. Keep selectors simple to improve performance and maintainability
  5. Minimize use of !important as it breaks the natural cascade
  6. Use shorthand properties when appropriate
  7. Comment your code for complex sections
  8. Avoid inline styles except for dynamic content

Learning Resources

  • MDN Web Docs - CSS
  • CSS-Tricks
  • W3Schools CSS Tutorial
  • freeCodeCamp CSS Course

Understanding CSS fundamentals is essential before moving on to more advanced layout techniques like Flexbox and Grid.

  • Web Development
  • CSS
  • Styling

show all >>

<< 上一页1…13141516下一页 >>

153 篇 | 133k
次 | 人
这里自动载入天数这里自动载入时分秒
2022-2025 loong loong | 新南威尔士龙龙号