feat(docs): add JavaScript introduction components and content

This commit is contained in:
sanbuphy
2026-02-15 16:23:15 +08:00
parent 4732d48777
commit 9c1a395962
9 changed files with 5243 additions and 2 deletions
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,766 @@
<template>
<div class="closure-demo">
<div class="demo-header">
<span class="icon">🎁</span>
<span class="title">函数与闭包</span>
<span class="subtitle">理解作用域链和闭包机制</span>
</div>
<div class="intro-text">
想象你有个<span class="highlight">背包</span>函数每次出门时都会把当时看到的
<span class="highlight">风景</span>外部变量装进去
<span class="highlight">闭包</span>就是这个背包即使离开了那个地方你依然能拿出当时装的风景
</div>
<div class="demo-tabs">
<button
v-for="tab in tabs"
:key="tab.id"
@click="activeTab = tab.id"
class="tab-btn"
:class="{ active: activeTab === tab.id }"
>
{{ tab.label }}
</button>
</div>
<!-- 函数基础 -->
<div v-if="activeTab === 'basic'" class="tab-content">
<div class="function-showcase">
<div class="code-panel">
<div class="code-title">函数声明方式</div>
<div class="code-block">
<div class="code-line comment">// 1. 函数声明</div>
<div class="code-line">function greet(name) {</div>
<div class="code-line indent">return "Hello " + name</div>
<div class="code-line">}</div>
<div class="code-line"></div>
<div class="code-line comment">// 2. 函数表达式</div>
<div class="code-line">const greet = function(name) {</div>
<div class="code-line indent">return "Hello " + name</div>
<div class="code-line">}</div>
<div class="code-line"></div>
<div class="code-line comment">// 3. 箭头函数 (ES6)</div>
<div class="code-line">const greet = (name) => {</div>
<div class="code-line indent">return "Hello " + name</div>
<div class="code-line">}</div>
<div class="code-line"></div>
<div class="code-line comment">// 简化版(单行可省略 return</div>
<div class="code-line">const greet = name => "Hello " + name</div>
</div>
</div>
<div class="playground">
<div class="playground-title">试试调用函数</div>
<div class="input-group">
<input v-model="functionName" placeholder="输入你的名字" />
<button @click="callFunction">调用</button>
</div>
<div class="output">
<span v-if="functionResult" class="result">{{ functionResult }}</span>
<span v-else class="placeholder">点击"调用"按钮看结果...</span>
</div>
</div>
</div>
</div>
<!-- 闭包演示 -->
<div v-else-if="activeTab === 'closure'" class="tab-content">
<div class="closure-visual">
<div class="scenario-selector">
<button @click="closureScenario = 'counter'" :class="{ active: closureScenario === 'counter' }">
计数器
</button>
<button @click="closureScenario = 'config'" :class="{ active: closureScenario === 'config' }">
配置器
</button>
</div>
<div v-if="closureScenario === 'counter'" class="counter-demo">
<div class="code-panel small">
<div class="code-line">function createCounter() {</div>
<div class="code-line indent">let count = 0 <span class="comment">// 私有变量</span></div>
<div class="code-line indent">return function() {</div>
<div class="code-line indent indent">count++</div>
<div class="code-line indent indent">return count</div>
<div class="code-line indent">}</div>
<div class="code-line">}</div>
<div class="code-line"></div>
<div class="code-line">const counter = createCounter()</div>
</div>
<div class="closure-animation">
<div class="closure-box">
<div class="box-title">闭包环境</div>
<div class="closure-var">
<span class="var-label">count = </span>
<span class="var-value">{{ counterValue }}</span>
</div>
</div>
<div class="controls-area">
<button @click="incrementCounter" class="action-btn primary">
调用 counter()
</button>
</div>
<div class="explanation">
<p><strong>发生了什么</strong></p>
<ul>
<li><code>createCounter()</code> 执行后局部变量 <code>count</code> 本该消失</li>
<li>但返回的函数"记住"了这个变量形成了闭包</li>
<li>每次调用 <code>counter()</code> 都在访问同一个 <code>count</code></li>
<li>外部无法直接访问 <code>count</code>实现了数据私有化</li>
</ul>
</div>
</div>
</div>
<div v-else class="config-demo">
<div class="code-panel small">
<div class="code-line">function makeMultiplier(times) {</div>
<div class="code-line indent">return function(n) {</div>
<div class="code-line indent indent">return n * times</div>
<div class="code-line indent">}</div>
<div class="code-line">}</div>
<div class="code-line"></div>
<div class="code-line">const double = makeMultiplier(2)</div>
<div class="code-line">const triple = makeMultiplier(3)</div>
</div>
<div class="multiplier-playground">
<div class="function-list">
<div class="func-item" @click="activeMultiplier = 'double'" :class="{ active: activeMultiplier === 'double' }">
<div class="func-name">double = makeMultiplier(2)</div>
<div class="func-desc">闭包捕获 times = 2</div>
</div>
<div class="func-item" @click="activeMultiplier = 'triple'" :class="{ active: activeMultiplier === 'triple' }">
<div class="func-name">triple = makeMultiplier(3)</div>
<div class="func-desc">闭包捕获 times = 3</div>
</div>
</div>
<div class="multiplier-input">
<input v-model.number="multiplyNumber" type="number" placeholder="输入数字" />
<button @click="doMultiply">计算</button>
</div>
<div v-if="multiplyResult" class="multiply-result">
<span class="result-equation">{{ multiplyResult }}</span>
</div>
</div>
</div>
</div>
</div>
<!-- 作用域链 -->
<div v-else class="tab-content">
<div class="scope-chain-demo">
<div class="nested-visual">
<div class="scope-level global">
<div class="level-title">全局作用域</div>
<div class="level-vars">
<span class="var-tag">globalVar = "全局"</span>
</div>
<div class="scope-level outer">
<div class="level-title">外层函数作用域</div>
<div class="level-vars">
<span class="var-tag">outerVar = "外层"</span>
</div>
<div class="scope-level inner">
<div class="level-title">内层函数作用域</div>
<div class="level-vars">
<span class="var-tag">innerVar = "内层"</span>
</div>
</div>
</div>
</div>
</div>
<div class="lookup-demo">
<div class="lookup-title">🔍 变量查找过程作用域链</div>
<div class="lookup-steps">
<div class="lookup-step" v-for="(step, i) in lookupSteps" :key="i">
<div class="step-num">{{ i + 1 }}</div>
<div class="step-content">{{ step }}</div>
</div>
</div>
<div class="lookup-rule">
<strong>查找规则</strong>
从当前作用域开始逐层向外查找直到全局作用域找不到则报错 ReferenceError
</div>
</div>
</div>
</div>
<div class="info-box">
<span class="icon">💡</span>
<strong>核心思想</strong>
<span v-if="activeTab === 'basic'">函数是 JavaScript 中的一等公民可以赋值给变量作为参数传递作为返回值箭头函数更简洁且不绑定自己的 this</span>
<span v-else-if="activeTab === 'closure'">闭包是函数和声明该函数的词法环境的组合它让函数可以访问外部作用域的变量即使外部函数已经执行完毕闭包常用于数据私有化函数工厂模块化等场景</span>
<span v-else>作用域链是 JavaScript 查找变量的机制当访问一个变量时引擎会先在当前作用域查找找不到就去外层作用域找直到全局作用域这种机制让内层函数可以访问外层变量形成了闭包的基础</span>
</div>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
const activeTab = ref('basic')
const functionName = ref('')
const functionResult = ref('')
const counterValue = ref(0)
const closureScenario = ref('counter')
const activeMultiplier = ref('double')
const multiplyNumber = ref(null)
const multiplyResult = ref('')
const tabs = [
{ id: 'basic', label: '函数基础' },
{ id: 'closure', label: '闭包' },
{ id: 'scope', label: '作用域链' }
]
const lookupSteps = ref([
'内层函数访问 innerVar → 在当前作用域找到 ✓',
'内层函数访问 outerVar → 当前找不到,向外层查找 ✓',
'内层函数访问 globalVar → 继续向外,在全局作用域找到 ✓',
'内层函数访问 unknownVar → 所有作用域都找不到 ✗ ReferenceError'
])
const callFunction = () => {
if (functionName.value.trim()) {
functionResult.value = `Hello ${functionName.value}`
}
}
const incrementCounter = () => {
counterValue.value++
}
const doMultiply = () => {
if (multiplyNumber.value !== null) {
const times = activeMultiplier.value === 'double' ? 2 : 3
multiplyResult.value = `${multiplyNumber.value} × ${times} = ${multiplyNumber.value * times}`
}
}
</script>
<style scoped>
.closure-demo {
border: 1px solid var(--vp-c-divider);
border-radius: 8px;
background: var(--vp-c-bg-soft);
padding: 1rem;
}
.demo-header {
display: flex;
align-items: center;
gap: 0.5rem;
margin-bottom: 0.75rem;
}
.demo-header .icon { font-size: 1.25rem; }
.demo-header .title { font-weight: bold; font-size: 1rem; }
.demo-header .subtitle { color: var(--vp-c-text-2); font-size: 0.85rem; margin-left: 0.5rem; }
.intro-text {
font-size: 0.9rem;
line-height: 1.6;
margin-bottom: 1rem;
color: var(--vp-c-text-1);
}
.highlight {
background: var(--vp-c-brand-soft);
color: var(--vp-c-brand);
padding: 0.1rem 0.3rem;
border-radius: 4px;
font-weight: 500;
}
.demo-tabs {
display: flex;
gap: 0.5rem;
margin-bottom: 1rem;
border-bottom: 1px solid var(--vp-c-divider);
padding-bottom: 0.5rem;
}
.tab-btn {
background: transparent;
border: none;
padding: 0.5rem 1rem;
cursor: pointer;
color: var(--vp-c-text-2);
font-size: 0.9rem;
border-radius: 6px;
transition: all 0.2s;
}
.tab-btn:hover {
background: var(--vp-c-bg-soft);
}
.tab-btn.active {
background: var(--vp-c-brand);
color: white;
}
.tab-content {
min-height: 380px;
}
.function-showcase {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1rem;
}
.code-panel {
background: #1e1e1e;
border-radius: 6px;
padding: 0.75rem;
font-family: 'Monaco', 'Menlo', monospace;
font-size: 0.8rem;
color: #d4d4d4;
}
.code-panel.small {
font-size: 0.75rem;
padding: 0.5rem;
}
.code-title {
color: #888;
font-size: 0.7rem;
margin-bottom: 0.5rem;
text-transform: uppercase;
}
.code-line {
padding: 0.1rem 0;
line-height: 1.4;
}
.code-line.indent {
padding-left: 1.5rem;
}
.code-line.indent.indent {
padding-left: 3rem;
}
.code-line .comment {
color: #6a9955;
}
.code-line :deep(code) {
background: #333;
padding: 0.1rem 0.3rem;
border-radius: 3px;
}
.playground {
background: var(--vp-c-bg);
border-radius: 6px;
padding: 1rem;
display: flex;
flex-direction: column;
gap: 0.75rem;
}
.playground-title {
font-weight: 600;
color: var(--vp-c-text-1);
font-size: 0.9rem;
}
.input-group {
display: flex;
gap: 0.5rem;
}
.input-group input {
flex: 1;
padding: 0.5rem;
border: 1px solid var(--vp-c-divider);
border-radius: 6px;
background: var(--vp-c-bg-soft);
color: var(--vp-c-text-1);
}
.input-group button {
background: var(--vp-c-brand);
color: white;
border: none;
padding: 0.5rem 1rem;
border-radius: 6px;
cursor: pointer;
}
.output {
background: var(--vp-c-bg-soft);
border-radius: 6px;
padding: 0.75rem;
min-height: 2.5rem;
display: flex;
align-items: center;
}
.output .result {
color: var(--vp-c-brand);
font-weight: 600;
font-size: 1.1rem;
}
.output .placeholder {
color: var(--vp-c-text-3);
font-size: 0.85rem;
}
.scenario-selector {
display: flex;
gap: 0.5rem;
margin-bottom: 1rem;
}
.scenario-selector button {
flex: 1;
padding: 0.5rem;
background: var(--vp-c-bg);
border: 1px solid var(--vp-c-divider);
border-radius: 6px;
cursor: pointer;
color: var(--vp-c-text-2);
transition: all 0.2s;
}
.scenario-selector button:hover {
border-color: var(--vp-c-brand);
}
.scenario-selector button.active {
background: var(--vp-c-brand);
color: white;
border-color: var(--vp-c-brand);
}
.closure-visual {
display: flex;
flex-direction: column;
gap: 1rem;
}
.counter-demo {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1rem;
}
.closure-animation {
display: flex;
flex-direction: column;
gap: 0.75rem;
}
.closure-box {
background: var(--vp-c-brand-soft);
border: 2px solid var(--vp-c-brand);
border-radius: 8px;
padding: 1rem;
text-align: center;
}
.box-title {
font-weight: 600;
color: var(--vp-c-brand);
margin-bottom: 0.5rem;
}
.closure-var {
font-size: 1.5rem;
font-weight: bold;
}
.var-label {
color: var(--vp-c-text-2);
}
.var-value {
color: var(--vp-c-brand);
font-size: 2rem;
}
.controls-area {
display: flex;
justify-content: center;
}
.action-btn {
background: var(--vp-c-brand);
color: white;
border: none;
padding: 0.75rem 1.5rem;
border-radius: 6px;
font-size: 1rem;
cursor: pointer;
transition: opacity 0.2s;
}
.action-btn:hover {
opacity: 0.9;
}
.explanation {
background: var(--vp-c-bg-alt);
border-radius: 6px;
padding: 0.75rem;
font-size: 0.85rem;
}
.explanation p {
margin: 0 0 0.5rem 0;
font-weight: 600;
color: var(--vp-c-text-1);
}
.explanation ul {
margin: 0;
padding-left: 1.2rem;
color: var(--vp-c-text-2);
line-height: 1.6;
}
.explanation code {
background: var(--vp-c-bg-soft);
padding: 0.1rem 0.3rem;
border-radius: 3px;
font-family: monospace;
}
.config-demo {
display: flex;
flex-direction: column;
gap: 1rem;
}
.multiplier-playground {
background: var(--vp-c-bg);
border-radius: 6px;
padding: 0.75rem;
}
.function-list {
display: flex;
gap: 0.5rem;
margin-bottom: 0.75rem;
}
.func-item {
flex: 1;
background: var(--vp-c-bg-soft);
border: 2px solid var(--vp-c-divider);
border-radius: 6px;
padding: 0.75rem;
text-align: center;
cursor: pointer;
transition: all 0.2s;
}
.func-item:hover {
border-color: var(--vp-c-brand);
}
.func-item.active {
border-color: var(--vp-c-brand);
background: var(--vp-c-brand-soft);
}
.func-name {
font-weight: 600;
font-family: monospace;
margin-bottom: 0.25rem;
color: var(--vp-c-text-1);
}
.func-desc {
font-size: 0.75rem;
color: var(--vp-c-text-3);
}
.multiplier-input {
display: flex;
gap: 0.5rem;
margin-bottom: 0.75rem;
}
.multiplier-input input {
flex: 1;
padding: 0.5rem;
border: 1px solid var(--vp-c-divider);
border-radius: 6px;
background: var(--vp-c-bg-soft);
color: var(--vp-c-text-1);
}
.multiplier-input button {
background: var(--vp-c-brand);
color: white;
border: none;
padding: 0.5rem 1rem;
border-radius: 6px;
cursor: pointer;
}
.multiply-result {
background: var(--vp-c-bg-soft);
border-radius: 6px;
padding: 1rem;
text-align: center;
}
.result-equation {
font-size: 1.2rem;
font-weight: bold;
color: var(--vp-c-brand);
}
.scope-chain-demo {
display: flex;
flex-direction: column;
gap: 1rem;
}
.nested-visual {
background: var(--vp-c-bg);
border-radius: 6px;
padding: 1rem;
}
.scope-level {
border: 2px solid var(--vp-c-divider);
border-radius: 6px;
padding: 0.75rem;
position: relative;
}
.scope-level.global {
background: #f5f5f5;
}
.scope-level.outer {
background: #e8f5e9;
margin: 0.5rem 0 0.5rem 0.5rem;
border-color: #c8e6c9;
}
.scope-level.inner {
background: #e3f2fd;
margin: 0.5rem 0 0 0.5rem;
border-color: #bbdefb;
}
.level-title {
font-weight: 600;
font-size: 0.85rem;
margin-bottom: 0.5rem;
text-transform: uppercase;
color: var(--vp-c-text-2);
}
.level-vars {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
}
.var-tag {
background: white;
padding: 0.25rem 0.5rem;
border-radius: 4px;
font-size: 0.8rem;
font-family: monospace;
border: 1px solid var(--vp-c-divider);
}
.lookup-demo {
background: var(--vp-c-bg-alt);
border-radius: 6px;
padding: 0.75rem;
}
.lookup-title {
font-weight: 600;
margin-bottom: 0.5rem;
color: var(--vp-c-text-1);
}
.lookup-steps {
display: flex;
flex-direction: column;
gap: 0.5rem;
margin-bottom: 0.75rem;
}
.lookup-step {
display: flex;
gap: 0.5rem;
align-items: flex-start;
}
.step-num {
background: var(--vp-c-brand);
color: white;
width: 1.5rem;
height: 1.5rem;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 0.75rem;
font-weight: 600;
flex-shrink: 0;
}
.step-content {
font-size: 0.85rem;
color: var(--vp-c-text-2);
line-height: 1.4;
}
.lookup-rule {
background: white;
padding: 0.5rem;
border-radius: 4px;
font-size: 0.85rem;
color: var(--vp-c-text-2);
}
.info-box {
background: var(--vp-c-bg-alt);
padding: 0.75rem;
border-radius: 6px;
font-size: 0.85rem;
color: var(--vp-c-text-2);
margin-top: 0.75rem;
display: flex;
gap: 0.25rem;
}
.info-box .icon { flex-shrink: 0; }
@media (max-width: 768px) {
.function-showcase {
grid-template-columns: 1fr;
}
.counter-demo {
grid-template-columns: 1fr;
}
}
</style>
@@ -0,0 +1,620 @@
<template>
<div class="data-type-demo">
<div class="demo-header">
<span class="icon">🏷</span>
<span class="title">JavaScript 数据类型</span>
<span class="subtitle">原始类型 vs 引用类型</span>
</div>
<div class="intro-text">
想象你在外面<span class="highlight">租了个储物柜</span>
<span class="highlight">原始类型</span>像是把东西直接拿回家复制一份
<span class="highlight">引用类型</span>像是只拿了张写着地址的小纸条共享同一个位置
</div>
<div class="type-tabs">
<button
v-for="tab in tabs"
:key="tab.id"
@click="activeTab = tab.id"
class="tab-btn"
:class="{ active: activeTab === tab.id }"
>
{{ tab.label }}
</button>
</div>
<div class="content-area">
<!-- 原始类型 -->
<div v-if="activeTab === 'primitive'" class="primitive-types">
<div class="type-grid">
<div
v-for="type in primitiveTypes"
:key="type.name"
class="type-card"
@click="selectedType = type"
:class="{ selected: selectedType?.name === type.name }"
>
<div class="type-icon">{{ type.icon }}</div>
<div class="type-name">{{ type.name }}</div>
<div class="type-example">{{ type.example }}</div>
</div>
</div>
<div v-if="selectedType" class="type-detail">
<div class="detail-title">📝 {{ selectedType.name }} 详细说明</div>
<div class="detail-desc">{{ selectedType.description }}</div>
<div class="detail-note">
<strong>💡 关键特性</strong>{{ selectedType.note }}
</div>
</div>
</div>
<!-- 引用类型 -->
<div v-else-if="activeTab === 'reference'" class="reference-types">
<div class="comparison-box">
<div class="compare-side">
<div class="side-title">原始类型赋值</div>
<div class="code-example">
<div class="code-line">let a = 10</div>
<div class="code-line">let b = a</div>
<div class="code-line">b = 20</div>
<div class="code-line result">// a = 10 (不变)</div>
</div>
<div class="visual-box">
<div class="value-box">a = 10</div>
<div class="arrow">复制</div>
<div class="value-box">b = 20</div>
</div>
</div>
<div class="compare-side">
<div class="side-title">引用类型赋值</div>
<div class="code-example">
<div class="code-line">let obj1 = {x: 10}</div>
<div class="code-line">let obj2 = obj1</div>
<div class="code-line">obj2.x = 20</div>
<div class="code-line result">// obj1.x = 20 (变了!)</div>
</div>
<div class="visual-box">
<div class="ref-box">
<div>obj1 </div>
<div class="memory-box">{x: 20}</div>
</div>
<div class="arrow">指向同一位置</div>
<div class="ref-box">
<div>obj2 </div>
</div>
</div>
</div>
</div>
<div class="ref-types-list">
<div class="ref-type-item" v-for="type in referenceTypes" :key="type.name">
<div class="ref-icon">{{ type.icon }}</div>
<div class="ref-info">
<div class="ref-name">{{ type.name }}</div>
<div class="ref-desc">{{ type.description }}</div>
</div>
</div>
</div>
</div>
<!-- 类型转换 -->
<div v-else class="type-conversion">
<div class="conversion-playground">
<div class="input-section">
<label>输入一个值</label>
<input v-model="inputValue" type="text" placeholder="试试输入 '123' 或 'hello'" @keyup.enter="convertType" />
<button @click="convertType" class="convert-btn">转换</button>
</div>
<div class="results-section">
<div class="result-row">
<span class="result-label">String():</span>
<span class="result-value">{{ conversionResults.string }}</span>
</div>
<div class="result-row">
<span class="result-label">Number():</span>
<span class="result-value" :class="{ error: conversionResults.number === 'NaN' }">
{{ conversionResults.number }}
</span>
</div>
<div class="result-row">
<span class="result-label">Boolean():</span>
<span class="result-value">{{ conversionResults.boolean }}</span>
</div>
</div>
<div class="falsy-values">
<div class="falsy-title"> 转成 false 的值falsy values</div>
<div class="falsy-list">
<span v-for="val in falsyValues" :key="val" class="falsy-item">{{ val }}</span>
</div>
<div class="falsy-note">其他所有值包括空数组 []空对象 {}都转成 true</div>
</div>
</div>
</div>
</div>
<div class="info-box">
<span class="icon">💡</span>
<strong>核心思想</strong>
<span v-if="activeTab === 'primitive'">原始类型存储实际的值赋值时复制值它们是不可变的修改后创建新值</span>
<span v-else-if="activeTab === 'reference'">引用类型存储的是内存地址的引用赋值时复制引用多个变量可以指向同一个对象修改其中一个会影响所有引用</span>
<span v-else>类型转换是 JS 中常见的 bug 来源理解 falsy values 和隐式转换规则能避免很多问题使用 === 而不是 == 来避免自动类型转换</span>
</div>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
const activeTab = ref('primitive')
const selectedType = ref(null)
const inputValue = ref('')
const conversionResults = ref({
string: '-',
number: '-',
boolean: '-'
})
const tabs = [
{ id: 'primitive', label: '原始类型' },
{ id: 'reference', label: '引用类型' },
{ id: 'conversion', label: '类型转换' }
]
const primitiveTypes = [
{
name: 'Number',
icon: '🔢',
example: '42, 3.14, NaN',
description: '数字类型,包括整数和小数。NaN 表示"不是数字"。',
note: '所有数字都是浮点数,没有整数类型。特殊值:Infinity、-Infinity、NaN'
},
{
name: 'String',
icon: '📝',
example: '"hello", \'你好\'',
description: '字符串类型,用单引号或双引号包裹的文本。',
note: '字符串是不可变的,任何操作都会返回新的字符串。'
},
{
name: 'Boolean',
icon: '✅',
example: 'true, false',
description: '布尔类型,只有两个值:真或假。',
note: '常用于条件判断和逻辑运算。'
},
{
name: 'Undefined',
icon: '❓',
example: 'let x; // x 是 undefined',
description: '变量已声明但未赋值时的默认值。',
note: '表示"缺少值"。主动赋值 undefined 没有意义。'
},
{
name: 'Null',
icon: '🕳️',
example: 'let x = null;',
description: '表示"空值"或"无对象"。',
note: 'typeof null === "object" 是 JS 的历史 bug。'
},
{
name: 'Symbol',
icon: '🔑',
example: 'Symbol("id")',
description: 'ES6 新增,表示独一无二的值。',
note: '常用于对象属性的键,防止属性名冲突。'
},
{
name: 'BigInt',
icon: '🔢',
example: '9007199254740991n',
description: 'ES2020 新增,表示任意大的整数。',
note: '数字后面加 n。用于处理超大整数。'
}
]
const referenceTypes = [
{
name: 'Object',
icon: '📦',
description: '键值对集合,最常用的引用类型。数组、函数也是对象。'
},
{
name: 'Array',
icon: '📚',
description: '有序的数据集合,实际上是特殊的对象。'
},
{
name: 'Function',
icon: '⚙️',
description: '可执行的代码块,也是对象,可以赋值给变量。'
},
{
name: 'Date',
icon: '📅',
description: '日期和时间对象。'
},
{
name: 'RegExp',
icon: '🔍',
description: '正则表达式对象,用于模式匹配。'
}
]
const falsyValues = ['false', '0', '""', 'null', 'undefined', 'NaN']
const convertType = () => {
const val = inputValue.value
conversionResults.value = {
string: String(val),
number: Number(val).toString(),
boolean: Boolean(val).toString()
}
}
</script>
<style scoped>
.data-type-demo {
border: 1px solid var(--vp-c-divider);
border-radius: 8px;
background: var(--vp-c-bg-soft);
padding: 1rem;
}
.demo-header {
display: flex;
align-items: center;
gap: 0.5rem;
margin-bottom: 0.75rem;
}
.demo-header .icon { font-size: 1.25rem; }
.demo-header .title { font-weight: bold; font-size: 1rem; }
.demo-header .subtitle { color: var(--vp-c-text-2); font-size: 0.85rem; margin-left: 0.5rem; }
.intro-text {
font-size: 0.9rem;
line-height: 1.6;
margin-bottom: 1rem;
color: var(--vp-c-text-1);
}
.highlight {
background: var(--vp-c-brand-soft);
color: var(--vp-c-brand);
padding: 0.1rem 0.3rem;
border-radius: 4px;
font-weight: 500;
}
.type-tabs {
display: flex;
gap: 0.5rem;
margin-bottom: 1rem;
border-bottom: 1px solid var(--vp-c-divider);
padding-bottom: 0.5rem;
}
.tab-btn {
background: transparent;
border: none;
padding: 0.5rem 1rem;
cursor: pointer;
color: var(--vp-c-text-2);
font-size: 0.9rem;
border-radius: 6px;
transition: all 0.2s;
}
.tab-btn:hover {
background: var(--vp-c-bg-soft);
}
.tab-btn.active {
background: var(--vp-c-brand);
color: white;
}
.content-area {
min-height: 350px;
}
.type-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
gap: 0.75rem;
margin-bottom: 1rem;
}
.type-card {
background: var(--vp-c-bg);
border: 2px solid var(--vp-c-divider);
border-radius: 8px;
padding: 0.75rem;
text-align: center;
cursor: pointer;
transition: all 0.2s;
}
.type-card:hover {
border-color: var(--vp-c-brand);
transform: translateY(-2px);
}
.type-card.selected {
border-color: var(--vp-c-brand);
background: var(--vp-c-brand-soft);
}
.type-icon {
font-size: 2rem;
margin-bottom: 0.5rem;
}
.type-name {
font-weight: 600;
margin-bottom: 0.25rem;
color: var(--vp-c-text-1);
}
.type-example {
font-size: 0.75rem;
color: var(--vp-c-text-3);
font-family: monospace;
}
.type-detail {
background: var(--vp-c-bg-alt);
border-radius: 6px;
padding: 0.75rem;
margin-top: 1rem;
}
.detail-title {
font-weight: 600;
margin-bottom: 0.5rem;
color: var(--vp-c-text-1);
}
.detail-desc {
font-size: 0.9rem;
color: var(--vp-c-text-2);
margin-bottom: 0.5rem;
line-height: 1.5;
}
.detail-note {
font-size: 0.85rem;
color: var(--vp-c-brand);
background: var(--vp-c-brand-soft);
padding: 0.5rem;
border-radius: 4px;
}
.comparison-box {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1rem;
margin-bottom: 1rem;
}
.compare-side {
background: var(--vp-c-bg);
border-radius: 6px;
padding: 0.75rem;
border: 1px solid var(--vp-c-divider);
}
.side-title {
font-weight: 600;
margin-bottom: 0.5rem;
color: var(--vp-c-text-1);
text-align: center;
}
.code-example {
background: #1e1e1e;
border-radius: 4px;
padding: 0.5rem;
font-family: monospace;
font-size: 0.8rem;
margin-bottom: 0.5rem;
}
.code-line {
padding: 0.15rem 0;
color: #d4d4d4;
}
.code-line.result {
color: #6a9955;
margin-top: 0.25rem;
}
.visual-box {
display: flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
font-size: 0.8rem;
}
.value-box {
background: var(--vp-c-bg-soft);
padding: 0.5rem;
border-radius: 4px;
border: 2px solid var(--vp-c-brand);
}
.ref-box {
display: flex;
align-items: center;
gap: 0.25rem;
}
.memory-box {
background: var(--vp-c-brand-soft);
padding: 0.5rem;
border-radius: 4px;
border: 2px solid var(--vp-c-brand);
}
.arrow {
color: var(--vp-c-text-3);
font-size: 0.75rem;
text-align: center;
}
.ref-types-list {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.ref-type-item {
display: flex;
gap: 0.75rem;
padding: 0.75rem;
background: var(--vp-c-bg);
border-radius: 6px;
align-items: center;
}
.ref-icon {
font-size: 1.5rem;
}
.ref-name {
font-weight: 600;
color: var(--vp-c-text-1);
}
.ref-desc {
font-size: 0.85rem;
color: var(--vp-c-text-2);
}
.conversion-playground {
display: flex;
flex-direction: column;
gap: 1rem;
}
.input-section {
display: flex;
gap: 0.5rem;
align-items: center;
}
.input-section label {
font-size: 0.9rem;
color: var(--vp-c-text-1);
white-space: nowrap;
}
.input-section input {
flex: 1;
padding: 0.5rem;
border: 1px solid var(--vp-c-divider);
border-radius: 6px;
background: var(--vp-c-bg);
color: var(--vp-c-text-1);
}
.convert-btn {
background: var(--vp-c-brand);
color: white;
border: none;
padding: 0.5rem 1rem;
border-radius: 6px;
cursor: pointer;
}
.results-section {
background: var(--vp-c-bg);
border-radius: 6px;
padding: 0.75rem;
}
.result-row {
display: flex;
justify-content: space-between;
padding: 0.4rem 0;
border-bottom: 1px solid var(--vp-c-divider);
}
.result-row:last-child {
border-bottom: none;
}
.result-label {
font-weight: 600;
color: var(--vp-c-text-2);
font-family: monospace;
}
.result-value {
font-family: monospace;
color: var(--vp-c-brand);
}
.result-value.error {
color: #f48771;
}
.falsy-values {
background: var(--vp-c-bg-alt);
border-radius: 6px;
padding: 0.75rem;
}
.falsy-title {
font-weight: 600;
margin-bottom: 0.5rem;
color: var(--vp-c-text-1);
}
.falsy-list {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
margin-bottom: 0.5rem;
}
.falsy-item {
background: var(--vp-c-bg);
padding: 0.25rem 0.5rem;
border-radius: 4px;
font-family: monospace;
font-size: 0.85rem;
color: var(--vp-c-brand);
}
.falsy-note {
font-size: 0.85rem;
color: var(--vp-c-text-2);
}
.info-box {
background: var(--vp-c-bg-alt);
padding: 0.75rem;
border-radius: 6px;
font-size: 0.85rem;
color: var(--vp-c-text-2);
margin-top: 0.75rem;
display: flex;
gap: 0.25rem;
}
.info-box .icon { flex-shrink: 0; }
@media (max-width: 768px) {
.comparison-box {
grid-template-columns: 1fr;
}
}
</style>
@@ -0,0 +1,827 @@
<template>
<div class="prototype-demo">
<div class="demo-header">
<span class="icon">🧬</span>
<span class="title">原型与继承</span>
<span class="subtitle">理解 JavaScript 的原型链机制</span>
</div>
<div class="intro-text">
想象你有本<span class="highlight">秘籍</span>上面记载了很多通用技能当你需要某个技能时
先翻翻自己的<span class="highlight">技能书</span>没有就去翻<span class="highlight">师傅的秘籍</span>
还没有就去翻<span class="highlight">师傅的师傅的秘籍</span>这条<span class="highlight">查找链</span>就是原型链
</div>
<div class="demo-tabs">
<button
v-for="tab in tabs"
:key="tab.id"
@click="activeTab = tab.id"
class="tab-btn"
:class="{ active: activeTab === tab.id }"
>
{{ tab.label }}
</button>
</div>
<!-- 原型基础 -->
<div v-if="activeTab === 'basic'" class="tab-content">
<div class="concept-explanation">
<div class="code-panel">
<div class="code-title">创建对象的方式</div>
<div class="code-block">
<div class="code-line comment">// 方式 1:对象字面量</div>
<div class="code-line">const obj1 = { name: "对象1" }</div>
<div class="code-line">obj1.__proto__ === Object.prototype <span class="comment">// true</span></div>
<div class="code-line"></div>
<div class="code-line comment">// 方式 2:构造函数</div>
<div class="code-line">function Person(name) {</div>
<div class="code-line indent">this.name = name</div>
<div class="code-line">}</div>
<div class="code-line">const p = new Person("张三")</div>
<div class="code-line">p.__proto__ === Person.prototype <span class="comment">// true</span></div>
</div>
</div>
<div class="prototype-visual">
<div class="prototype-chain">
<div class="chain-node" :class="{ active: chainLevel >= 0 }" @click="chainLevel = 0">
<div class="node-title">对象实例 (p)</div>
<div class="node-content">
<div class="property">name: "张三"</div>
<div class="proto-link">__proto__ </div>
</div>
</div>
<div class="chain-arrow" v-if="chainLevel >= 0"> 查找</div>
<div class="chain-node constructor" :class="{ active: chainLevel >= 1 }" @click="chainLevel = 1">
<div class="node-title">Person.prototype</div>
<div class="node-content">
<div class="method">constructor: Person</div>
<div class="proto-link">__proto__ </div>
</div>
</div>
<div class="chain-arrow" v-if="chainLevel >= 1"> 查找</div>
<div class="chain-node object" :class="{ active: chainLevel >= 2 }" @click="chainLevel = 2">
<div class="node-title">Object.prototype</div>
<div class="node-content">
<div class="method">toString()</div>
<div class="method">hasOwnProperty()</div>
<div class="proto-link">__proto__ null</div>
</div>
</div>
</div>
<div class="chain-explanation">
<div v-if="chainLevel === 0">
<strong>实例对象</strong>
<p>访问 p.name 在自己的属性中找到 返回 "张三"</p>
</div>
<div v-else-if="chainLevel === 1">
<strong>Person 原型</strong>
<p>访问 p.toString() 实例中没有 向上查找 Person.prototype 中没有 继续向上</p>
</div>
<div v-else>
<strong>Object 原型链的顶端</strong>
<p>找到了 toString() 方法这是所有对象的祖先提供的方法</p>
</div>
</div>
</div>
</div>
</div>
<!-- 原型继承 -->
<div v-else-if="activeTab === 'inheritance'" class="tab-content">
<div class="inheritance-demo">
<div class="inheritance-code">
<div class="code-title">原型继承示例</div>
<div class="code-block">
<div class="code-line comment">// 父类构造函数</div>
<div class="code-line">function Animal(name) {</div>
<div class="code-line indent">this.name = name</div>
<div class="code-line">}</div>
<div class="code-line"></div>
<div class="code-line">Animal.prototype.eat = function() {</div>
<div class="code-line indent">return this.name + " 在吃东西"</div>
<div class="code-line">}</div>
<div class="code-line"></div>
<div class="code-line comment">// 子类构造函数</div>
<div class="code-line">function Dog(name, breed) {</div>
<div class="code-line indent">Animal.call(this, name) <span class="comment">// 继承属性</span></div>
<div class="code-line indent">this.breed = breed</div>
<div class="code-line">}</div>
<div class="code-line"></div>
<div class="code-line comment">// 继承方法</div>
<div class="code-line">Dog.prototype = Object.create(Animal.prototype)</div>
<div class="code-line">Dog.prototype.constructor = Dog</div>
</div>
</div>
<div class="inheritance-visual">
<div class="class-diagram">
<div class="class-box parent">
<div class="class-title">Animal (父类)</div>
<div class="class-content">
<div class="class-section">
<div class="section-title">属性</div>
<div class="section-item">name: String</div>
</div>
<div class="class-section">
<div class="section-title">方法 (prototype)</div>
<div class="section-item">eat()</div>
</div>
</div>
</div>
<div class="inherit-arrow"> 继承</div>
<div class="class-box child">
<div class="class-title">Dog (子类)</div>
<div class="class-content">
<div class="class-section">
<div class="section-title">属性</div>
<div class="section-item">name: String</div>
<div class="section-item">breed: String</div>
</div>
<div class="class-section">
<div class="section-title">方法 (prototype)</div>
<div class="section-item">eat() <span class="inherited">[继承]</span></div>
<div class="section-item">bark() <span class="own">[新增]</span></div>
</div>
</div>
</div>
</div>
<div class="inheritance-playground">
<div class="playground-title">试试创建实例</div>
<div class="input-group">
<input v-model="dogName" placeholder="狗狗名字" />
<input v-model="dogBreed" placeholder="品种" />
<button @click="createDog">创建</button>
</div>
<div v-if="dogInstance" class="instance-result">
<div class="result-item">
<span class="label">名字</span>
<span class="value">{{ dogInstance.name }}</span>
</div>
<div class="result-item">
<span class="label">品种</span>
<span class="value">{{ dogInstance.breed }}</span>
</div>
<div class="result-item">
<span class="label">调用 eat()</span>
<button @click="callEat" class="action-btn">调用</button>
<span v-if="eatResult" class="method-result">{{ eatResult }}</span>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- class 语法 -->
<div v-else class="tab-content">
<div class="class-syntax-demo">
<div class="syntax-comparison">
<div class="syntax-panel old">
<div class="panel-title">ES5 构造函数</div>
<div class="code-block">
<div class="code-line">function Person(name) {</div>
<div class="code-line indent">this.name = name</div>
<div class="code-line">}</div>
<div class="code-line"></div>
<div class="code-line">Person.prototype.greet = function() {</div>
<div class="code-line indent">return "你好,我是" + this.name</div>
<div class="code-line">}</div>
<div class="code-line"></div>
<div class="code-line">const p = new Person("小明")</div>
</div>
</div>
<div class="syntax-panel new">
<div class="panel-title">ES6 class 语法</div>
<div class="code-block">
<div class="code-line">class Person {</div>
<div class="code-line indent">constructor(name) {</div>
<div class="code-line indent indent">this.name = name</div>
<div class="code-line indent">}</div>
<div class="code-line"></div>
<div class="code-line indent">greet() {</div>
<div class="code-line indent indent">return "你好,我是" + this.name</div>
<div class="code-line indent">}</div>
<div class="code-line">}</div>
<div class="code-line"></div>
<div class="code-line">const p = new Person("小明")</div>
</div>
</div>
</div>
<div class="class-features">
<div class="feature-card">
<div class="feature-icon">🎯</div>
<div class="feature-title">更清晰的语法</div>
<div class="feature-desc">class 语法让面向对象编程更直观但本质还是基于原型</div>
</div>
<div class="feature-card">
<div class="feature-icon">🔗</div>
<div class="feature-title">继承更简单</div>
<div class="feature-desc">使用 extends 关键字实现继承代码更简洁</div>
</div>
<div class="feature-card">
<div class="feature-icon"></div>
<div class="feature-title">注意</div>
<div class="feature-desc">class 只是语法糖底层仍然是原型链机制</div>
</div>
</div>
<div class="inheritance-example">
<div class="code-title">class 继承示例</div>
<div class="code-block">
<div class="code-line">class Animal {</div>
<div class="code-line indent">constructor(name) {</div>
<div class="code-line indent indent">this.name = name</div>
<div class="code-line indent">}</div>
<div class="code-line indent">eat() {</div>
<div class="code-line indent indent">return this.name + " 在吃东西"</div>
<div class="code-line indent">}</div>
<div class="code-line">}</div>
<div class="code-line"></div>
<div class="code-line">class Dog extends Animal {</div>
<div class="code-line indent">constructor(name, breed) {</div>
<div class="code-line indent indent">super(name) <span class="comment">// 调用父类构造函数</span></div>
<div class="code-line indent indent">this.breed = breed</div>
<div class="code-line indent">}</div>
<div class="code-line indent">bark() {</div>
<div class="code-line indent indent">return "汪汪!"</div>
<div class="code-line indent">}</div>
<div class="code-line">}</div>
</div>
</div>
</div>
</div>
<div class="key-points">
<div class="point-title">🎯 核心要点</div>
<ul class="point-list">
<li>每个对象都有 <code>__proto__</code> 属性指向其构造函数的 <code>prototype</code></li>
<li>访问对象属性时先在自身查找找不到就沿着原型链向上查找</li>
<li>原型链顶端是 <code>Object.prototype</code>它的 <code>__proto__</code> <code>null</code></li>
<li><code>class</code> 是语法糖本质仍然是原型继承</li>
</ul>
</div>
<div class="info-box">
<span class="icon">💡</span>
<strong>核心思想</strong>
<span v-if="activeTab === 'basic'">JavaScript 通过原型链实现继承而不是像其他语言那样使用类每个对象都有一个原型对象对象以其原型为模板从原型继承方法和属性这种"原型式继承"机制让 JavaScript 更加灵活</span>
<span v-else-if="activeTab === 'inheritance'">原型继承让对象可以共享方法节省内存子类通过原型链继承父类的方法同时可以添加自己的方法理解原型链是掌握 JavaScript 面向对象编程的关键</span>
<span v-else>ES6 class 语法让面向对象编程更加清晰易读但它只是语法糖底层仍然是原型链使用 class 可以让代码更接近传统面向对象语言的风格降低学习成本</span>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
const activeTab = ref('basic')
const chainLevel = ref(0)
const dogName = ref('')
const dogBreed = ref('')
const dogInstance = ref(null)
const eatResult = ref('')
const tabs = [
{ id: 'basic', label: '原型基础' },
{ id: 'inheritance', label: '原型继承' },
{ id: 'class', label: 'class 语法' }
]
const createDog = () => {
if (dogName.value && dogBreed.value) {
dogInstance.value = {
name: dogName.value,
breed: dogBreed.value
}
eatResult.value = ''
}
}
const callEat = () => {
if (dogInstance.value) {
eatResult.value = `${dogInstance.value.name} 在吃东西`
}
}
</script>
<style scoped>
.prototype-demo {
border: 1px solid var(--vp-c-divider);
border-radius: 8px;
background: var(--vp-c-bg-soft);
padding: 1rem;
}
.demo-header {
display: flex;
align-items: center;
gap: 0.5rem;
margin-bottom: 0.75rem;
}
.demo-header .icon { font-size: 1.25rem; }
.demo-header .title { font-weight: bold; font-size: 1rem; }
.demo-header .subtitle { color: var(--vp-c-text-2); font-size: 0.85rem; margin-left: 0.5rem; }
.intro-text {
font-size: 0.9rem;
line-height: 1.6;
margin-bottom: 1rem;
color: var(--vp-c-text-1);
}
.highlight {
background: var(--vp-c-brand-soft);
color: var(--vp-c-brand);
padding: 0.1rem 0.3rem;
border-radius: 4px;
font-weight: 500;
}
.demo-tabs {
display: flex;
gap: 0.5rem;
margin-bottom: 1rem;
border-bottom: 1px solid var(--vp-c-divider);
padding-bottom: 0.5rem;
}
.tab-btn {
background: transparent;
border: none;
padding: 0.5rem 1rem;
cursor: pointer;
color: var(--vp-c-text-2);
font-size: 0.9rem;
border-radius: 6px;
transition: all 0.2s;
}
.tab-btn:hover {
background: var(--vp-c-bg-soft);
}
.tab-btn.active {
background: var(--vp-c-brand);
color: white;
}
.tab-content {
min-height: 380px;
}
.concept-explanation {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1rem;
}
.code-panel {
background: #1e1e1e;
border-radius: 6px;
padding: 0.75rem;
font-family: 'Monaco', 'Menlo', monospace;
font-size: 0.75rem;
color: #d4d4d4;
}
.code-title {
color: #888;
font-size: 0.7rem;
margin-bottom: 0.5rem;
text-transform: uppercase;
}
.code-block {
line-height: 1.5;
}
.code-line {
padding: 0.1rem 0;
}
.code-line.indent {
padding-left: 1.5rem;
}
.code-line .comment {
color: #6a9955;
}
.prototype-visual {
display: flex;
flex-direction: column;
gap: 0.75rem;
}
.prototype-chain {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.chain-node {
background: var(--vp-c-bg);
border: 2px solid var(--vp-c-divider);
border-radius: 6px;
padding: 0.75rem;
cursor: pointer;
transition: all 0.2s;
}
.chain-node:hover {
border-color: var(--vp-c-brand);
}
.chain-node.active {
border-color: var(--vp-c-brand);
background: var(--vp-c-brand-soft);
}
.chain-node.constructor {
border-color: #c8e6c9;
}
.chain-node.constructor.active {
background: #e8f5e9;
}
.chain-node.object {
border-color: #bbdefb;
}
.chain-node.object.active {
background: #e3f2fd;
}
.node-title {
font-weight: 600;
color: var(--vp-c-text-1);
margin-bottom: 0.5rem;
font-size: 0.85rem;
}
.node-content {
display: flex;
flex-direction: column;
gap: 0.25rem;
}
.property {
background: var(--vp-c-bg-soft);
padding: 0.25rem 0.5rem;
border-radius: 4px;
font-family: monospace;
font-size: 0.75rem;
color: var(--vp-c-text-2);
}
.method {
background: #e3f2fd;
padding: 0.25rem 0.5rem;
border-radius: 4px;
font-family: monospace;
font-size: 0.75rem;
color: #1976d2;
}
.proto-link {
color: var(--vp-c-brand);
font-family: monospace;
font-size: 0.8rem;
margin-top: 0.25rem;
}
.chain-arrow {
text-align: center;
color: var(--vp-c-brand);
font-weight: 600;
font-size: 0.85rem;
}
.chain-explanation {
background: var(--vp-c-bg-alt);
border-radius: 6px;
padding: 0.75rem;
font-size: 0.85rem;
}
.chain-explanation strong {
color: var(--vp-c-text-1);
display: block;
margin-bottom: 0.5rem;
}
.chain-explanation p {
color: var(--vp-c-text-2);
line-height: 1.5;
margin: 0;
}
.inheritance-demo {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1rem;
}
.inheritance-code {
background: #1e1e1e;
border-radius: 6px;
padding: 0.75rem;
font-family: 'Monaco', 'Menlo', monospace;
font-size: 0.7rem;
color: #d4d4d4;
}
.inheritance-visual {
display: flex;
flex-direction: column;
gap: 0.75rem;
}
.class-diagram {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.class-box {
background: var(--vp-c-bg);
border: 2px solid var(--vp-c-divider);
border-radius: 8px;
padding: 0.75rem;
}
.class-box.parent {
border-color: #c8e6c9;
}
.class-box.child {
border-color: var(--vp-c-brand);
}
.class-title {
font-weight: 600;
color: var(--vp-c-text-1);
margin-bottom: 0.5rem;
font-size: 0.85rem;
}
.class-content {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.class-section {
background: var(--vp-c-bg-soft);
border-radius: 4px;
padding: 0.5rem;
}
.section-title {
font-weight: 600;
color: var(--vp-c-text-2);
margin-bottom: 0.25rem;
font-size: 0.75rem;
}
.section-item {
font-family: monospace;
font-size: 0.75rem;
padding: 0.15rem 0;
color: var(--vp-c-text-2);
}
.inherited {
color: #4caf50;
font-size: 0.7rem;
}
.own {
color: var(--vp-c-brand);
font-size: 0.7rem;
}
.inherit-arrow {
text-align: center;
color: var(--vp-c-brand);
font-weight: 600;
}
.inheritance-playground {
background: var(--vp-c-bg-alt);
border-radius: 6px;
padding: 0.75rem;
}
.playground-title {
font-weight: 600;
margin-bottom: 0.5rem;
font-size: 0.85rem;
color: var(--vp-c-text-1);
}
.input-group {
display: flex;
gap: 0.5rem;
margin-bottom: 0.5rem;
}
.input-group input {
flex: 1;
padding: 0.4rem;
border: 1px solid var(--vp-c-divider);
border-radius: 4px;
background: var(--vp-c-bg);
color: var(--vp-c-text-1);
font-size: 0.85rem;
}
.input-group button {
background: var(--vp-c-brand);
color: white;
border: none;
padding: 0.4rem 0.75rem;
border-radius: 4px;
cursor: pointer;
font-size: 0.85rem;
}
.instance-result {
background: var(--vp-c-bg);
border-radius: 4px;
padding: 0.5rem;
}
.result-item {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.3rem 0;
font-size: 0.85rem;
}
.result-item .label {
font-weight: 600;
color: var(--vp-c-text-2);
min-width: 4rem;
}
.result-item .value {
color: var(--vp-c-text-1);
}
.action-btn {
background: var(--vp-c-brand);
color: white;
border: none;
padding: 0.25rem 0.5rem;
border-radius: 4px;
cursor: pointer;
font-size: 0.75rem;
}
.method-result {
color: var(--vp-c-brand);
font-weight: 600;
}
.class-syntax-demo {
display: flex;
flex-direction: column;
gap: 1rem;
}
.syntax-comparison {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1rem;
}
.syntax-panel {
background: #1e1e1e;
border-radius: 6px;
padding: 0.75rem;
font-family: 'Monaco', 'Menlo', monospace;
font-size: 0.75rem;
color: #d4d4d4;
}
.syntax-panel.new {
border: 2px solid var(--vp-c-brand);
}
.panel-title {
color: #888;
font-size: 0.7rem;
margin-bottom: 0.5rem;
text-transform: uppercase;
}
.class-features {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 0.75rem;
}
.feature-card {
background: var(--vp-c-bg);
border: 1px solid var(--vp-c-divider);
border-radius: 6px;
padding: 0.75rem;
text-align: center;
}
.feature-icon {
font-size: 1.5rem;
margin-bottom: 0.5rem;
}
.feature-title {
font-weight: 600;
color: var(--vp-c-text-1);
margin-bottom: 0.25rem;
font-size: 0.85rem;
}
.feature-desc {
font-size: 0.8rem;
color: var(--vp-c-text-2);
line-height: 1.4;
}
.inheritance-example {
background: #1e1e1e;
border-radius: 6px;
padding: 0.75rem;
font-family: 'Monaco', 'Menlo', monospace;
font-size: 0.75rem;
color: #d4d4d4;
}
.key-points {
background: var(--vp-c-bg-alt);
border-radius: 6px;
padding: 0.75rem;
margin-top: 1rem;
}
.point-title {
font-weight: 600;
color: var(--vp-c-text-1);
margin-bottom: 0.5rem;
font-size: 0.9rem;
}
.point-list {
margin: 0;
padding-left: 1.2rem;
color: var(--vp-c-text-2);
font-size: 0.85rem;
line-height: 1.6;
}
.point-list code {
background: var(--vp-c-bg-soft);
padding: 0.1rem 0.3rem;
border-radius: 3px;
font-family: monospace;
}
.info-box {
background: var(--vp-c-bg-alt);
padding: 0.75rem;
border-radius: 6px;
font-size: 0.85rem;
color: var(--vp-c-text-2);
margin-top: 0.75rem;
display: flex;
gap: 0.25rem;
}
.info-box .icon { flex-shrink: 0; }
@media (max-width: 768px) {
.concept-explanation,
.inheritance-demo,
.syntax-comparison,
.class-features {
grid-template-columns: 1fr;
}
}
</style>
@@ -0,0 +1,899 @@
<template>
<div class="this-context-demo">
<div class="demo-header">
<span class="icon">🎯</span>
<span class="title">this 与执行上下文</span>
<span class="subtitle">理解 this 的指向规则</span>
</div>
<div class="intro-text">
想象<span class="highlight">this</span>就像一个<span class="highlight">指针</span>
指向"当前正在执行的主角"不同场景下主角会变化
有时是<span class="highlight">对象自己</span>有时是<span class="highlight">全局环境</span>还有时完全取决于<span class="highlight">谁在调用</span>
</div>
<div class="scenario-selector">
<button
v-for="scenario in scenarios"
:key="scenario.id"
@click="activeScenario = scenario.id"
class="scenario-btn"
:class="{ active: activeScenario === scenario.id }"
>
{{ scenario.label }}
</button>
</div>
<!-- 方法调用 -->
<div v-if="activeScenario === 'method'" class="scenario-content">
<div class="split-view">
<div class="code-panel">
<div class="code-title">对象方法调用</div>
<div class="code-block">
<div class="code-line">const person = {</div>
<div class="code-line indent">name: "张三",</div>
<div class="code-line indent">greet: function() {</div>
<div class="code-line indent indent">return "你好,我是" + this.name</div>
<div class="code-line indent">}</div>
<div class="code-line">}</div>
<div class="code-line"></div>
<div class="code-line">person.greet() <span class="comment">// this → person</span></div>
</div>
</div>
<div class="visual-panel">
<div class="object-visual">
<div class="object-box">
<div class="object-title">person 对象</div>
<div class="object-content">
<div class="property">name: "张三"</div>
<div class="method" @click="simulateMethodCall">
greet: function() { ... }
</div>
</div>
</div>
<div class="arrow-indicator">
<div class="this-pointer">this </div>
</div>
<div v-if="methodCallResult" class="result-box">
{{ methodCallResult }}
</div>
</div>
<div class="rule-box">
<div class="rule-title">规则对象方法</div>
<div class="rule-content">通过对象调用方法时<code>this</code> 指向该对象</div>
</div>
</div>
</div>
</div>
<!-- 普通函数 -->
<div v-else-if="activeScenario === 'function'" class="scenario-content">
<div class="split-view">
<div class="code-panel">
<div class="code-title">普通函数调用</div>
<div class="code-block">
<div class="code-line">function show() {</div>
<div class="code-line indent">return this === window</div>
<div class="code-line">}</div>
<div class="code-line"></div>
<div class="code-line">show() <span class="comment">// this → window (浏览器)</span></div>
<div class="code-line"></div>
<div class="code-line comment">// 严格模式下是 undefined</div>
</div>
</div>
<div class="visual-panel">
<div class="function-visual">
<div class="global-window">
<div class="window-title">window (全局对象)</div>
<div class="window-content">
<div class="global-item">show 函数在这里</div>
<div class="global-item">this window</div>
</div>
</div>
</div>
<div class="mode-toggle">
<button @click="strictMode = !strictMode" class="toggle-btn">
{{ strictMode ? '严格模式:开' : '严格模式:关' }}
</button>
<div class="mode-result">
this = {{ strictMode ? 'undefined' : 'window' }}
</div>
</div>
<div class="rule-box">
<div class="rule-title">规则普通函数</div>
<div class="rule-content">
非严格模式<code>this</code> 指向全局对象<br>
严格模式<code>this</code> <code>undefined</code>
</div>
</div>
</div>
</div>
</div>
<!-- 构造函数 -->
<div v-else-if="activeScenario === 'constructor'" class="scenario-content">
<div class="split-view">
<div class="code-panel">
<div class="code-title">构造函数调用</div>
<div class="code-block">
<div class="code-line">function Person(name) {</div>
<div class="code-line indent">this.name = name</div>
<div class="code-line">}</div>
<div class="code-line"></div>
<div class="code-line">const p1 = new Person("李四")</div>
<div class="code-line">const p2 = new Person("王五")</div>
<div class="code-line"></div>
<div class="code-line comment">// p1.name = "李四"</div>
<div class="code-line comment">// p2.name = "王五"</div>
</div>
</div>
<div class="visual-panel">
<div class="constructor-visual">
<div class="constructor-process">
<div class="process-step">
<span class="step-num">1</span>
<span>创建新对象</span>
</div>
<div class="process-arrow"></div>
<div class="process-step">
<span class="step-num">2</span>
<span>this 指向新对象</span>
</div>
<div class="process-arrow"></div>
<div class="process-step">
<span class="step-num">3</span>
<span>执行构造函数</span>
</div>
<div class="process-arrow"></div>
<div class="process-step">
<span class="step-num">4</span>
<span>返回新对象</span>
</div>
</div>
<div class="object-comparison">
<div class="obj-instance">
<div class="obj-title">p1</div>
<div class="obj-content">name: "李四"</div>
</div>
<div class="obj-instance">
<div class="obj-title">p2</div>
<div class="obj-content">name: "王五"</div>
</div>
</div>
</div>
<div class="rule-box">
<div class="rule-title">规则new 调用</div>
<div class="rule-content">
使用 <code>new</code> 调用函数时<code>this</code> 指向新创建的对象
</div>
</div>
</div>
</div>
</div>
<!-- call/apply/bind -->
<div v-else-if="activeScenario === 'explicit'" class="scenario-content">
<div class="split-view">
<div class="code-panel">
<div class="code-title">显式绑定 (call/apply/bind)</div>
<div class="code-block">
<div class="code-line">function greet() {</div>
<div class="code-line indent">return "我是" + this.name</div>
<div class="code-line">}</div>
<div class="code-line"></div>
<div class="code-line">const person = { name: "小明" }</div>
<div class="code-line"></div>
<div class="code-line">greet.call(person) <span class="comment">// 显式指定 this</span></div>
<div class="code-line">greet.apply(person)</div>
<div class="code-line">const bound = greet.bind(person)</div>
</div>
</div>
<div class="visual-panel">
<div class="binding-visual">
<div class="function-box">
<div class="box-title">greet 函数</div>
<div class="box-content">this.name</div>
</div>
<div class="binding-methods">
<div class="binding-item" @click="simulateCall" :class="{ active: bindingMethod === 'call' }">
<div class="method-name">call(person)</div>
<div class="method-desc">立即调用this person</div>
</div>
<div class="binding-item" @click="simulateApply" :class="{ active: bindingMethod === 'apply' }">
<div class="method-name">apply(person)</div>
<div class="method-desc"> call参数为数组</div>
</div>
<div class="binding-item" @click="simulateBind" :class="{ active: bindingMethod === 'bind' }">
<div class="method-name">bind(person)</div>
<div class="method-desc">返回新函数this 固定</div>
</div>
</div>
<div v-if="bindingResult" class="binding-result">
{{ bindingResult }}
</div>
</div>
<div class="rule-box">
<div class="rule-title">规则显式绑定</div>
<div class="rule-content">
<code>call/apply/bind</code> 可以显式指定 <code>this</code> 的指向
</div>
</div>
</div>
</div>
</div>
<!-- 箭头函数 -->
<div v-else class="scenario-content">
<div class="split-view">
<div class="code-panel">
<div class="code-title">箭头函数的 this</div>
<div class="code-block">
<div class="code-line">const person = {</div>
<div class="code-line indent">name: "小红",</div>
<div class="code-line indent">greet: function() {</div>
<div class="code-line indent indent">setTimeout(() => {</div>
<div class="code-line indent indent indent">console.log(this.name)</div>
<div class="code-line indent indent">}, 1000)</div>
<div class="code-line indent">}</div>
<div class="code-line">}</div>
<div class="code-line"></div>
<div class="code-line">person.greet() <span class="comment">// 输出 "小红"</span></div>
</div>
</div>
<div class="visual-panel">
<div class="arrow-function-visual">
<div class="outer-context">
<div class="context-title">外层作用域 (person)</div>
<div class="context-content">
<div class="context-item">this.name = "小红"</div>
</div>
</div>
<div class="arrow-capture">
<div class="capture-title">箭头函数捕获外层 this</div>
<div class="capture-arrow"> 继承 this</div>
</div>
<div class="inner-context">
<div class="context-title">箭头函数内部</div>
<div class="context-content">
<div class="context-item">this 外层的 this</div>
</div>
</div>
</div>
<div class="rule-box">
<div class="rule-title">规则箭头函数</div>
<div class="rule-content">
箭头函数没有自己的 <code>this</code>它继承外层作用域的 <code>this</code>
</div>
</div>
</div>
</div>
</div>
<div class="quick-reference">
<div class="reference-title">📋 this 指向速查表</div>
<div class="reference-table">
<div class="ref-row header">
<span>调用方式</span>
<span>this 指向</span>
</div>
<div class="ref-row">
<span>obj.method()</span>
<span>obj</span>
</div>
<div class="ref-row">
<span>func()</span>
<span>window / undefined</span>
</div>
<div class="ref-row">
<span>new Func()</span>
<span>新创建的对象</span>
</div>
<div class="ref-row">
<span>func.call(obj)</span>
<span>obj</span>
</div>
<div class="ref-row">
<span>箭头函数</span>
<span>外层作用域的 this</span>
</div>
</div>
</div>
<div class="info-box">
<span class="icon">💡</span>
<strong>核心思想</strong>
<span>this 的值是在函数调用时确定的不是定义时确定的关键要看"函数是如何被调用的"而不是"函数在哪里定义"箭头函数是例外它没有自己的 this从外层作用域继承</span>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
const activeScenario = ref('method')
const strictMode = ref(false)
const bindingMethod = ref('')
const methodCallResult = ref('')
const bindingResult = ref('')
const scenarios = [
{ id: 'method', label: '对象方法' },
{ id: 'function', label: '普通函数' },
{ id: 'constructor', label: '构造函数' },
{ id: 'explicit', label: 'call/apply/bind' },
{ id: 'arrow', label: '箭头函数' }
]
const simulateMethodCall = () => {
methodCallResult.value = '你好,我是张三'
setTimeout(() => {
methodCallResult.value = ''
}, 2000)
}
const simulateCall = () => {
bindingMethod.value = 'call'
bindingResult.value = '我是小明 (通过 call 绑定)'
}
const simulateApply = () => {
bindingMethod.value = 'apply'
bindingResult.value = '我是小明 (通过 apply 绑定)'
}
const simulateBind = () => {
bindingMethod.value = 'bind'
bindingResult.value = '我是小明 (通过 bind 绑定,返回新函数)'
}
</script>
<style scoped>
.this-context-demo {
border: 1px solid var(--vp-c-divider);
border-radius: 8px;
background: var(--vp-c-bg-soft);
padding: 1rem;
}
.demo-header {
display: flex;
align-items: center;
gap: 0.5rem;
margin-bottom: 0.75rem;
}
.demo-header .icon { font-size: 1.25rem; }
.demo-header .title { font-weight: bold; font-size: 1rem; }
.demo-header .subtitle { color: var(--vp-c-text-2); font-size: 0.85rem; margin-left: 0.5rem; }
.intro-text {
font-size: 0.9rem;
line-height: 1.6;
margin-bottom: 1rem;
color: var(--vp-c-text-1);
}
.highlight {
background: var(--vp-c-brand-soft);
color: var(--vp-c-brand);
padding: 0.1rem 0.3rem;
border-radius: 4px;
font-weight: 500;
}
.scenario-selector {
display: flex;
gap: 0.5rem;
margin-bottom: 1rem;
flex-wrap: wrap;
}
.scenario-btn {
background: var(--vp-c-bg);
border: 1px solid var(--vp-c-divider);
padding: 0.5rem 1rem;
border-radius: 6px;
cursor: pointer;
font-size: 0.85rem;
transition: all 0.2s;
}
.scenario-btn:hover {
border-color: var(--vp-c-brand);
}
.scenario-btn.active {
background: var(--vp-c-brand);
color: white;
border-color: var(--vp-c-brand);
}
.scenario-content {
min-height: 350px;
}
.split-view {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1rem;
}
.code-panel {
background: #1e1e1e;
border-radius: 6px;
padding: 0.75rem;
font-family: 'Monaco', 'Menlo', monospace;
font-size: 0.8rem;
color: #d4d4d4;
}
.code-title {
color: #888;
font-size: 0.7rem;
margin-bottom: 0.5rem;
text-transform: uppercase;
}
.code-block {
line-height: 1.5;
}
.code-line {
padding: 0.1rem 0;
}
.code-line.indent {
padding-left: 1.5rem;
}
.code-line.indent.indent {
padding-left: 3rem;
}
.code-line .comment {
color: #6a9955;
}
.code-line code {
background: #333;
padding: 0.1rem 0.3rem;
border-radius: 3px;
}
.visual-panel {
display: flex;
flex-direction: column;
gap: 0.75rem;
}
.object-visual {
display: flex;
flex-direction: column;
align-items: center;
gap: 0.75rem;
}
.object-box {
background: var(--vp-c-bg);
border: 2px solid var(--vp-c-brand);
border-radius: 8px;
padding: 0.75rem;
width: 100%;
}
.object-title {
font-weight: 600;
color: var(--vp-c-brand);
margin-bottom: 0.5rem;
font-size: 0.9rem;
}
.object-content {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.property, .method {
background: var(--vp-c-bg-soft);
padding: 0.4rem;
border-radius: 4px;
font-family: monospace;
font-size: 0.8rem;
}
.method {
cursor: pointer;
transition: background 0.2s;
}
.method:hover {
background: var(--vp-c-brand-soft);
}
.arrow-indicator {
text-align: center;
}
.this-pointer {
background: var(--vp-c-brand);
color: white;
padding: 0.3rem 0.6rem;
border-radius: 4px;
font-size: 0.85rem;
font-weight: 600;
}
.result-box {
background: var(--vp-c-brand-soft);
border: 2px solid var(--vp-c-brand);
border-radius: 6px;
padding: 0.75rem;
text-align: center;
color: var(--vp-c-brand);
font-weight: 600;
animation: fadeIn 0.3s;
}
@keyframes fadeIn {
from { opacity: 0; transform: scale(0.9); }
to { opacity: 1; transform: scale(1); }
}
.rule-box {
background: var(--vp-c-bg-alt);
border-radius: 6px;
padding: 0.75rem;
}
.rule-title {
font-weight: 600;
color: var(--vp-c-text-1);
margin-bottom: 0.5rem;
font-size: 0.85rem;
}
.rule-content {
font-size: 0.85rem;
color: var(--vp-c-text-2);
line-height: 1.5;
}
.rule-content code {
background: var(--vp-c-bg-soft);
padding: 0.1rem 0.3rem;
border-radius: 3px;
font-family: monospace;
}
.function-visual {
display: flex;
flex-direction: column;
align-items: center;
gap: 0.75rem;
}
.global-window {
background: #f5f5f5;
border: 2px solid #e0e0e0;
border-radius: 8px;
padding: 1rem;
width: 100%;
}
.window-title {
font-weight: 600;
color: #666;
margin-bottom: 0.5rem;
font-size: 0.9rem;
}
.window-content {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.global-item {
background: white;
padding: 0.4rem;
border-radius: 4px;
font-size: 0.8rem;
color: #666;
}
.mode-toggle {
display: flex;
flex-direction: column;
align-items: center;
gap: 0.5rem;
}
.toggle-btn {
background: var(--vp-c-brand);
color: white;
border: none;
padding: 0.5rem 1rem;
border-radius: 6px;
cursor: pointer;
font-size: 0.85rem;
}
.mode-result {
background: var(--vp-c-bg-soft);
padding: 0.5rem;
border-radius: 4px;
font-family: monospace;
color: var(--vp-c-brand);
font-weight: 600;
}
.constructor-process {
display: flex;
flex-direction: column;
align-items: center;
gap: 0.25rem;
}
.process-step {
display: flex;
align-items: center;
gap: 0.5rem;
font-size: 0.85rem;
color: var(--vp-c-text-2);
}
.step-num {
background: var(--vp-c-brand);
color: white;
width: 1.3rem;
height: 1.3rem;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 0.7rem;
font-weight: 600;
}
.process-arrow {
color: var(--vp-c-text-3);
font-size: 1rem;
}
.object-comparison {
display: flex;
gap: 1rem;
margin-top: 0.5rem;
}
.obj-instance {
flex: 1;
background: var(--vp-c-bg-soft);
border: 2px solid var(--vp-c-brand);
border-radius: 6px;
padding: 0.5rem;
text-align: center;
}
.obj-title {
font-weight: 600;
color: var(--vp-c-brand);
margin-bottom: 0.25rem;
font-size: 0.85rem;
}
.obj-content {
font-family: monospace;
font-size: 0.8rem;
color: var(--vp-c-text-2);
}
.binding-visual {
display: flex;
flex-direction: column;
align-items: center;
gap: 0.75rem;
}
.function-box {
background: var(--vp-c-bg-soft);
border: 2px solid var(--vp-c-divider);
border-radius: 6px;
padding: 0.5rem;
text-align: center;
}
.box-title {
font-weight: 600;
color: var(--vp-c-text-2);
font-size: 0.8rem;
margin-bottom: 0.25rem;
}
.box-content {
font-family: monospace;
font-size: 0.85rem;
color: var(--vp-c-brand);
}
.binding-methods {
display: flex;
gap: 0.5rem;
width: 100%;
}
.binding-item {
flex: 1;
background: var(--vp-c-bg);
border: 2px solid var(--vp-c-divider);
border-radius: 6px;
padding: 0.5rem;
text-align: center;
cursor: pointer;
transition: all 0.2s;
}
.binding-item:hover {
border-color: var(--vp-c-brand);
}
.binding-item.active {
border-color: var(--vp-c-brand);
background: var(--vp-c-brand-soft);
}
.method-name {
font-family: monospace;
font-weight: 600;
color: var(--vp-c-text-1);
margin-bottom: 0.25rem;
font-size: 0.8rem;
}
.method-desc {
font-size: 0.7rem;
color: var(--vp-c-text-3);
}
.binding-result {
background: var(--vp-c-brand-soft);
border: 2px solid var(--vp-c-brand);
border-radius: 6px;
padding: 0.75rem;
text-align: center;
color: var(--vp-c-brand);
font-weight: 600;
font-size: 0.9rem;
}
.arrow-function-visual {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.outer-context, .inner-context {
background: var(--vp-c-bg);
border: 2px solid var(--vp-c-brand);
border-radius: 6px;
padding: 0.75rem;
}
.context-title {
font-weight: 600;
color: var(--vp-c-brand);
margin-bottom: 0.5rem;
font-size: 0.85rem;
}
.context-content {
display: flex;
flex-direction: column;
gap: 0.25rem;
}
.context-item {
background: var(--vp-c-bg-soft);
padding: 0.4rem;
border-radius: 4px;
font-family: monospace;
font-size: 0.8rem;
color: var(--vp-c-text-2);
}
.arrow-capture {
text-align: center;
background: var(--vp-c-brand-soft);
padding: 0.5rem;
border-radius: 4px;
}
.capture-title {
font-size: 0.8rem;
color: var(--vp-c-brand);
font-weight: 600;
margin-bottom: 0.25rem;
}
.capture-arrow {
font-size: 0.85rem;
color: var(--vp-c-brand);
}
.quick-reference {
background: var(--vp-c-bg-alt);
border-radius: 6px;
padding: 0.75rem;
margin-top: 1rem;
}
.reference-title {
font-weight: 600;
margin-bottom: 0.5rem;
color: var(--vp-c-text-1);
font-size: 0.9rem;
}
.reference-table {
display: flex;
flex-direction: column;
gap: 0.25rem;
}
.ref-row {
display: flex;
justify-content: space-between;
padding: 0.4rem;
background: var(--vp-c-bg);
border-radius: 4px;
font-size: 0.85rem;
}
.ref-row.header {
background: var(--vp-c-brand-soft);
font-weight: 600;
color: var(--vp-c-brand);
}
.ref-row span:first-child {
font-family: monospace;
}
.info-box {
background: var(--vp-c-bg-alt);
padding: 0.75rem;
border-radius: 6px;
font-size: 0.85rem;
color: var(--vp-c-text-2);
margin-top: 0.75rem;
display: flex;
gap: 0.25rem;
}
.info-box .icon { flex-shrink: 0; }
@media (max-width: 768px) {
.split-view {
grid-template-columns: 1fr;
}
}
</style>
@@ -0,0 +1,366 @@
<template>
<div class="variable-scope-demo">
<div class="demo-header">
<span class="icon">📦</span>
<span class="title">变量与作用域</span>
<span class="subtitle">理解 letconstvar 的区别</span>
</div>
<div class="intro-text">
想象你在<span class="highlight">家里</span><span class="highlight">公司</span>放东西
<span class="highlight">var</span>像是把东西贴在脑门上哪都能看见
<span class="highlight">let</span>像是放在抽屉里当前房间能用
<span class="highlight">const</span>像是焊死在地上的柜子不能移动
</div>
<div class="code-display">
<div class="code-block">
<div class="code-line" v-for="(line, i) in codeLines" :key="i" :class="{ active: currentLine === i }">
<span class="line-num">{{ i + 1 }}</span>
<span class="line-code" v-html="highlightCode(line)"></span>
</div>
</div>
<div class="visualization">
<div class="scope-area global-scope">
<div class="scope-title">全局作用域房子外</div>
<div class="scope-vars">
<div v-if="step >= 1" class="var-item" :class="{ error: step === 4 }">
<span class="var-type">var</span>
<span class="var-name">globalVar</span>
<span class="var-value">= "外面"</span>
</div>
</div>
<div class="scope-area block-scope" v-if="step >= 2">
<div class="scope-title">块级作用域房间内</div>
<div class="scope-vars">
<div v-if="step >= 2" class="var-item" :class="{ error: step === 4 }">
<span class="var-type">var</span>
<span class="var-name">blockVar</span>
<span class="var-value">= "房间里"</span>
</div>
<div v-if="step >= 3" class="var-item let">
<span class="var-type">let</span>
<span class="var-name">blockLet</span>
<span class="var-value">= "只有房间内能用"</span>
</div>
</div>
</div>
</div>
<div class="console-output">
<div class="console-title">控制台输出</div>
<div class="console-lines">
<div v-for="(output, i) in consoleOutput" :key="i" class="console-line" :class="{ error: output.error }">
{{ output.text }}
</div>
</div>
</div>
</div>
</div>
<div class="controls">
<button @click="prevStep" :disabled="step === 0" class="control-btn"> 上一步</button>
<span class="step-indicator">{{ step + 1 }} / {{ maxSteps }}</span>
<button @click="nextStep" :disabled="step === maxSteps" class="control-btn">下一步 </button>
<button @click="reset" class="control-btn secondary">重置</button>
</div>
<div class="info-box">
<span class="icon">💡</span>
<strong>核心思想</strong>
<span v-if="step === 0">var 没有块级作用域"泄漏"到外部let const 有块级作用域只在声明的作用域内有效</span>
<span v-else-if="step === 1">var 声明的变量可以在全局作用域访问容易造成命名冲突</span>
<span v-else-if="step === 2">var 可以重复声明这在大型项目中容易导致难以排查的 bug</span>
<span v-else-if="step === 3">let const 有块级作用域 if 块外部无法访问更安全</span>
<span v-else>const 声明的变量不能重新赋值let 可以推荐优先使用 const需要重新赋值时用 let</span>
</div>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
const step = ref(0)
const maxSteps = 5
const codeLines = [
'var globalVar = "外面"',
'if (true) {',
' var blockVar = "房间里"',
' let blockLet = "只有房间内能用"',
'}',
'// 尝试访问这些变量'
]
const currentLine = computed(() => {
const lineMap = [0, 1, 2, 3, 1, 4]
return lineMap[step.value]
})
const consoleOutput = ref([])
const scenarios = {
0: { output: [] },
1: { output: [{ text: 'globalVar = "外面"', error: false }] },
2: { output: [{ text: 'globalVar = "外面"', error: false }, { text: 'blockVar = "房间里"', error: false }] },
3: { output: [{ text: 'globalVar = "外面"', error: false }, { text: 'blockVar = "房间里"', error: false }, { text: 'blockLet = "只有房间内能用"', error: false }] },
4: { output: [{ text: 'globalVar = "外面" ✓', error: false }, { text: 'blockVar = "房间里" ✓ (var 泄漏了!)', error: true }, { text: 'blockLet = 报错!let 不在块外部', error: true }] },
5: { output: [{ text: '推荐:const name = "值" (不能改)', error: false }, { text: '需要改:let count = 0 (可以改)', error: false }, { text: '避免:var old = "过时了"', error: true }] }
}
const nextStep = () => {
if (step.value < maxSteps) {
step.value++
consoleOutput.value = scenarios[step.value].output
}
}
const prevStep = () => {
if (step.value > 0) {
step.value--
consoleOutput.value = scenarios[step.value].output
}
}
const reset = () => {
step.value = 0
consoleOutput.value = []
}
const highlightCode = (line) => {
return line
.replace(/(var|let|const)/g, '<span class="keyword">$1</span>')
.replace(/(".+?")/g, '<span class="string">$1</span>')
.replace(/(\/\/.+)/g, '<span class="comment">$1</span>')
}
</script>
<style scoped>
.variable-scope-demo {
border: 1px solid var(--vp-c-divider);
border-radius: 8px;
background: var(--vp-c-bg-soft);
padding: 1rem;
}
.demo-header {
display: flex;
align-items: center;
gap: 0.5rem;
margin-bottom: 0.75rem;
}
.demo-header .icon { font-size: 1.25rem; }
.demo-header .title { font-weight: bold; font-size: 1rem; }
.demo-header .subtitle { color: var(--vp-c-text-2); font-size: 0.85rem; margin-left: 0.5rem; }
.intro-text {
font-size: 0.9rem;
line-height: 1.6;
margin-bottom: 1rem;
color: var(--vp-c-text-1);
}
.highlight {
background: var(--vp-c-brand-soft);
color: var(--vp-c-brand);
padding: 0.1rem 0.3rem;
border-radius: 4px;
font-weight: 500;
}
.code-display {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1rem;
margin-bottom: 1rem;
}
.code-block {
background: var(--vp-c-bg);
border: 1px solid var(--vp-c-divider);
border-radius: 6px;
padding: 0.75rem;
font-family: 'Monaco', 'Menlo', monospace;
font-size: 0.85rem;
}
.code-line {
display: flex;
gap: 0.5rem;
padding: 0.25rem 0;
border-radius: 4px;
}
.code-line.active {
background: var(--vp-c-brand-soft);
}
.line-num {
color: var(--vp-c-text-3);
min-width: 1.5rem;
text-align: right;
}
.line-code :deep(.keyword) { color: #c586c0; }
.line-code :deep(.string) { color: #ce9178; }
.line-code :deep(.comment) { color: #6a9955; }
.visualization {
display: flex;
flex-direction: column;
gap: 0.75rem;
}
.scope-area {
border: 1px solid var(--vp-c-divider);
border-radius: 6px;
padding: 0.75rem;
background: var(--vp-c-bg);
}
.scope-title {
font-size: 0.85rem;
font-weight: 600;
color: var(--vp-c-text-2);
margin-bottom: 0.5rem;
text-transform: uppercase;
letter-spacing: 0.05em;
}
.scope-vars {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.var-item {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.5rem;
background: var(--vp-c-bg-soft);
border-radius: 4px;
font-size: 0.85rem;
transition: all 0.3s;
}
.var-item.error {
background: #fee;
border: 1px solid #fcc;
}
.var-item.let {
background: #e8f5e9;
border: 1px solid #c8e6c9;
}
.var-type {
background: var(--vp-c-brand);
color: white;
padding: 0.15rem 0.4rem;
border-radius: 3px;
font-size: 0.75rem;
font-weight: 600;
}
.var-name {
font-weight: 600;
color: var(--vp-c-text-1);
}
.var-value {
color: var(--vp-c-text-2);
font-family: monospace;
}
.block-scope {
margin-left: 1rem;
border-left: 3px solid var(--vp-c-brand);
}
.console-output {
background: #1e1e1e;
border-radius: 6px;
padding: 0.75rem;
color: #d4d4d4;
font-family: monospace;
font-size: 0.85rem;
}
.console-title {
font-size: 0.75rem;
color: #888;
margin-bottom: 0.5rem;
text-transform: uppercase;
}
.console-line {
padding: 0.2rem 0;
}
.console-line.error {
color: #f48771;
}
.controls {
display: flex;
justify-content: center;
align-items: center;
gap: 1rem;
margin-top: 1rem;
}
.control-btn {
background: var(--vp-c-brand);
color: white;
border: none;
padding: 0.5rem 1rem;
border-radius: 6px;
cursor: pointer;
font-size: 0.9rem;
transition: opacity 0.2s;
}
.control-btn:hover:not(:disabled) {
opacity: 0.9;
}
.control-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.control-btn.secondary {
background: transparent;
border: 1px solid var(--vp-c-divider);
color: var(--vp-c-text-1);
}
.step-indicator {
font-size: 0.9rem;
color: var(--vp-c-text-2);
font-weight: 500;
}
.info-box {
background: var(--vp-c-bg-alt);
padding: 0.75rem;
border-radius: 6px;
font-size: 0.85rem;
color: var(--vp-c-text-2);
margin-top: 0.75rem;
display: flex;
gap: 0.25rem;
}
.info-box .icon { flex-shrink: 0; }
@media (max-width: 768px) {
.code-display {
grid-template-columns: 1fr;
}
}
</style>
+16
View File
@@ -479,6 +479,14 @@ import ResourceAnalogy from './components/appendix/api-design/ResourceAnalogy.vu
import RequestStructureDemo from './components/appendix/api-design/RequestStructureDemo.vue'
import ResponseStructureDemo from './components/appendix/api-design/ResponseStructureDemo.vue'
// JavaScript Intro Components
import VariableScopeDemo from './components/appendix/javascript-intro/VariableScopeDemo.vue'
import DataTypeDemo from './components/appendix/javascript-intro/DataTypeDemo.vue'
import ClosureDemo from './components/appendix/javascript-intro/ClosureDemo.vue'
import ThisContextDemo from './components/appendix/javascript-intro/ThisContextDemo.vue'
import PrototypeDemo from './components/appendix/javascript-intro/PrototypeDemo.vue'
import AsyncDemo from './components/appendix/javascript-intro/AsyncDemo.vue'
export default {
extends: DefaultTheme,
Layout,
@@ -965,6 +973,14 @@ export default {
app.component('ReliabilityDemo', ReliabilityDemo)
app.component('IdempotenceDemo', IdempotenceDemo)
app.component('MQComparisonDemo', MQComparisonDemo)
// JavaScript Intro Components Registration
app.component('VariableScopeDemo', VariableScopeDemo)
app.component('DataTypeDemo', DataTypeDemo)
app.component('ClosureDemo', ClosureDemo)
app.component('ThisContextDemo', ThisContextDemo)
app.component('PrototypeDemo', PrototypeDemo)
app.component('AsyncDemo', AsyncDemo)
},
setup() {
const route = useRoute()
@@ -304,6 +304,81 @@ DOM 树 + CSSOM 树 = **渲染树 (Render Tree)**。
---
## 5.5 网页是怎么"生成"的?静态网站 vs 动态网站
前面我们讲的都是浏览器如何"拆开包裹"——把服务器发来的 HTML/CSS/JS 渲染成页面。但你有没有想过一个问题:**服务器上那个 HTML 文件是怎么来的?**
答案是:**有两种方式**,这就是静态网站和动态网站的区别。
### 静态网站:提前做好、直接给你
想象你去超市买饼干。货架上的饼干都是工厂已经生产好的,你直接拿走就行,不需要等。
**静态网站**就是这样的"成品"——网页在服务器上已经准备好了,你访问时服务器直接把现成的 HTML 文件发给你,不做任何额外处理。
**特点:**
- ✅ 访问速度快(服务器直接发文件,不用计算)
- ✅ 制作简单(写好 HTML 就能用)
- ✅ 承载力强(可以用 CDN 分发,多少人访问都不怕)
- ❌ 内容难更新(想改内容就要重新生成文件)
**常见例子:** 公司介绍页、产品文档、帮助中心、个人博客
### 动态网站:现点现做、每次不同
这次想象你去餐厅点餐。厨师根据你的订单现做,你点宫保鸡丁不会给你上糖醋里脊。
**动态网站**就是你访问时才"现场制作"的页面——服务器收到你的请求后,去数据库查资料、计算数据,然后生成一个全新的 HTML 发给你。
**特点:**
- ✅ 内容实时(购物车显示最新库存、新闻随时更新)
- ✅ 因人而异(登录后看到你的个人信息)
- ✅ 功能强大(搜索、评论、推荐、支付都能实现)
- ❌ 访问速度慢(服务器需要时间计算)
- ❌ 服务器压力大(同时很多人访问要排队)
**常见例子:** 淘宝、微博、在线银行、在线文档
**需要服务器吗?** 动态网站确实需要某种"后端"来生成内容,但形式多样:
- **传统服务器**:自己买/租服务器(阿里云 ECS、AWS EC2)
- **Serverless**:不用管服务器,云厂商帮你运行代码(AWS Lambda、阿里云函数计算、Cloudflare Workers
- **调用第三方 API**:支付用 Stripe、天气用气象局 API,自己不写后端代码
::: tip 💡 静动态结合
现在很多网站是"混合"的:网页主体是静态的,但某些部分(比如评论区、搜索框)是动态加载的。JavaScript 可以在页面加载后调用 API 获取数据,实现"静态页面 + 动态功能"。
:::
### 📊 静态 vs 动态,一对比就清楚
| | 静态网站 | 动态网站 |
|---|---------|---------|
| **怎么来的** | 提前做好,存服务器上 | 访问时现做 |
| **像什么** | 超市货架上的商品 | 餐厅现点的菜 |
| **速度** | 快 | 慢(需要计算) |
| **能改内容吗** | 难(要重新生成) | 容易(后台直接改) |
| **适合做什么** | 展示型内容(介绍页、文档) | 交互型应用(购物、社交) |
| **典型例子** | 公司官网、帮助文档 | 淘宝、微信、在线银行 |
### 🤔 常见疑问
**Q: 静态网站是不是不能用 JavaScript?**
当然不是!轮播图、折叠菜单、表单验证这些交互功能,静态网站都能用 JavaScript 实现。我们说的"静态""动态",是指**页面内容是不是提前准备好的**,跟有没有交互功能是两回事。
**Q: 动态网站一定要自己买服务器吗?**
不一定。除了传统服务器,你还可以用 Serverless(云函数)、或者直接调用第三方 API。现在的趋势是"能不动服务器就不动"——用静态网站 + JavaScript 调用 API 的方式,既快又省成本。
::: tip 💡 重要提示
无论静态网站还是动态网站,**浏览器渲染的原理都是一样的**!服务器发来的是什么,浏览器就渲染什么。区别只在于:
- 静态网站:服务器发来的是"成品"
- 动态网站:服务器发来的是"现做的"
作为前端开发者,你主要关注的是浏览器如何处理收到的内容,而不是服务器怎么生成的。
:::
---
## 6. 总结:一次完整的"网购"之旅
让我们回顾整个旅程:
@@ -1,3 +1,631 @@
# JavaScript 语言深
# JavaScript 语言由浅入
::: tip 前言
**现代网页为什么可以像应用程序一样交互?** 淘宝可以筛选商品、地图可以缩放平移、文档可以实时保存——这些功能背后的"控制逻辑"是什么?HTML 定义了网页的结构,CSS 负责视觉呈现,而 JavaScript 则是让网页"动起来"的编程语言。本章将带你理解 JavaScript 的核心工作机制,掌握实现网页交互的关键技术。
:::
> 待实现
---
## 1. JavaScript 是什么?
### 1.1 从"只能看"到"可以用":网页的进化
早期的网页就像一本**电子杂志**——你只能看,不能改。内容是固定的,你点击什么都不会改变。
但现代网页完全不同了。它们更像**桌面软件**:
- 在线文档可以像 Word 一样编辑
- 地图网站可以像 GPS 一样导航
- 聊天应用可以像微信一样实时收发消息
**这种转变的核心技术就是 JavaScript**——它让网页从"展示信息"变成了"可以交互的工具"。
### 1.2 JavaScript 在网页中的角色
想象你在经营一家**网上商店**
| 层次 | 角色 | 做什么 |
|------|------|--------|
| **HTML** | 店铺的"货架" | 摆放商品、标注价格 |
| **CSS** | 店铺的"装修" | 美化布局、调整颜色 |
| **JavaScript** | 店铺的"收银系统" | 处理订单、计算总价、更新库存 |
**没有 JavaScript,网页就像一个没有收银员的商店**——顾客只能看商品,但无法完成购买。
::: tip 💡 三者如何配合
HTML 搭建结构,CSS 负责美观,JavaScript 处理交互。三者缺一不可,但 JavaScript 是让网页"能用"的关键。
打个比方:HTML 是房子的框架,CSS 是装修风格,JavaScript 是让房子"智能"的电器系统。没有电器,房子仍然可以住人,但不会有现代生活的便利。
:::
### 1.2 一个真实的成长故事
::: warning 从踩坑到顿悟
小王是一名前端工程师,刚入职时只会简单的 DOM 操作。
有一次,他遇到一个 bug:修改数组中的某个元素后,页面上显示的却是旧数据。他花了整整一天调试,最后才发现 —— 原来数组是引用类型,直接修改元素不会触发 Vue 的响应式更新。
**这个坑让他明白了一个道理:** 不理解 JavaScript 的核心概念(比如引用类型、响应式原理),写出的代码就像在雷区跳舞,随时可能踩中看不见的坑。
从那以后,他开始系统地学习 JavaScript 的底层原理。半年后,他不仅能快速定位问题,还能写出性能更好的代码,甚至能帮团队解决复杂的技术难题。
**深入理解一门语言,是成为高级工程师的必经之路。**
:::
::: info 💡 核心启示
JavaScript 看起来简单,实则精妙。它的设计哲学和实现机制,值得每个前端开发者深入理解。掌握了这些,你不仅能写出更好的代码,还能更快地学习新技术(因为很多框架都是基于 JavaScript 的特性构建的)。
:::
---
## 2. JavaScript 的核心概念全景
::: tip 🗺️ 学习路线图
JavaScript 的知识点很多,但我们不必一次全学完。按照下面这个顺序,逐步掌握核心概念:
1. **基础语法** → 变量、数据类型、操作符
2. **函数与作用域** → 函数声明、闭包、this
3. **对象与原型** → 对象创建、原型链、继承
4. **异步编程** → 回调、Promise、async/await
5. **ES6+ 新特性** → let/const、箭头函数、解构等
**本章节将聚焦于最核心、最容易出错的部分**,帮你建立坚实的知识体系。
:::
JavaScript 的核心概念可以分为以下几个层次:
| 层次 | 包含内容 | 学习重点 | 实际应用 |
|------|---------|---------|---------|
| **基础语法** | 变量、数据类型、操作符 | let/const/var、原始类型 vs 引用类型 | 避免常见的类型错误和变量泄漏 |
| **函数与作用域** | 函数声明、闭包、this | 作用域链、闭包机制、this 指向 | 理解函数的工作方式和数据私有化 |
| **对象与原型** | 对象创建、原型链、继承 | 原型链查找机制、继承方式 | 掌握面向对象编程和框架原理 |
| **异步编程** | 回调、Promise、async/await | 事件循环、Promise 链式调用 | 处理网络请求、动画等异步操作 |
| **ES6+ 新特性** | 箭头函数、解构、模块 | 新语法的使用场景 | 写出更简洁、更现代的代码 |
::: tip 💡 从表格中你能看到什么?
**基础语法** 是所有其他知识的基础,必须掌握。
**函数与作用域**、**对象与原型** 是 JavaScript 的核心机制,理解了它们,你就理解了这门语言的灵魂。
**异步编程** 是 JavaScript 的特色,因为它是单线程的,必须依赖异步来处理耗时操作。
**ES6+ 新特性** 让代码更简洁,但本质都是基于前面提到的核心机制。
**学习建议:** 先打好基础(变量、类型、函数),再深入理解机制(作用域、原型、异步),最后学习新特性(ES6+)。这样循序渐进,不会感到混乱。
:::
---
## 3. 变量与数据类型
### 3.1 变量声明:let、const、var 的区别
在 JavaScript 中,有三种声明变量的方式:`var``let``const`。它们的区别看似简单,实则影响深远。
::: details 🤔 为什么有三种方式?
这是 JavaScript 的历史遗留问题。
- **`var`** 是 ES5 时代的产物,有"变量提升"和"没有块级作用域"的问题
- **`let`** 和 **`const`** 是 ES6 新增的,解决了 `var` 的问题,更安全
现在推荐**始终使用 `const`**,需要重新赋值时才用 `let`,完全不要用 `var`
:::
**三个关键词的区别:**
| 特性 | var | let | const |
|------|-----|-----|-------|
| **作用域** | 函数作用域 | 块级作用域 | 块级作用域 |
| **重复声明** | ✅ 可以 | ❌ 不可以 | ❌ 不可以 |
| **重新赋值** | ✅ 可以 | ✅ 可以 | ❌ 不可以 |
| **变量提升** | ✅ 提升 | ✅ 提升(但不可访问) | ✅ 提升(但不可访问) |
| **全局对象属性** | ✅ 是 | ❌ 否 | ❌ 否 |
| **推荐使用** | ❌ 不推荐 | ✅ 需要重新赋值时 | ✅ 默认首选 |
::: tip 💡 如何选择?
记住这个简单规则:
- **默认用 `const`**:因为大多数变量不需要重新赋值,使用 `const` 更安全(防止意外修改)
- **需要重新赋值时用 `let`**:比如循环计数器、累加器
- **永远不要用 `var`**:除非你在维护老项目
**示例:**
```javascript
// ✅ 好的做法
const name = "张三" // 名字不会变
const age = 25 // 年龄不会变
let score = 0 // 分数会变化,用 let
score += 10 // ✓ 正确
// name = "李四" // ✗ 报错!const 不能重新赋值
// ❌ 不好的做法
var name = "张三" // 不要用 var
```
:::
👇 **动手试试看**
下面这个演示展示了 let、const、var 的区别:
<VariableScopeDemo />
### 3.2 数据类型:原始类型 vs 引用类型
JavaScript 有两种数据类型:**原始类型**Primitive)和**引用类型**Reference)。理解它们的区别,能帮你避免很多莫名其妙的 bug。
::: tip 🎯 什么是"类型"
简单来说,类型就是数据的"种类"。
- **原始类型**是最基本的数据,比如数字、字符串、布尔值
- **引用类型**是更复杂的数据结构,比如对象、数组、函数
它们的区别在于存储方式:原始类型存储"实际的值",引用类型存储"指向数据的地址"。
:::
**七种原始类型:**
| 类型 | 说明 | 示例 |
|------|------|------|
| **Number** | 数字(整数和小数) | `42`, `3.14`, `NaN` |
| **String** | 字符串(文本) | `"hello"`, `'你好'` |
| **Boolean** | 布尔值(真/假) | `true`, `false` |
| **Undefined** | 未定义 | `let x; // x 是 undefined` |
| **Null** | 空值 | `let x = null;` |
| **Symbol** | 独一无二的值(ES6 | `Symbol("id")` |
| **BigInt** | 大整数(ES2020 | `9007199254740991n` |
**引用类型:**
| 类型 | 说明 | 示例 |
|------|------|------|
| **Object** | 对象(键值对) | `{name: "张三", age: 25}` |
| **Array** | 数组(有序列表) | `[1, 2, 3]` |
| **Function** | 函数(可执行的代码) | `function() {}` |
| **Date** | 日期对象 | `new Date()` |
| **RegExp** | 正则表达式 | `/^test$/` |
::: details 🔍 原始类型 vs 引用类型的区别
这是最容易踩坑的地方!
**赋值时的区别:**
```javascript
// 原始类型:复制值
let a = 10
let b = a // b 得到 a 的副本
b = 20
console.log(a) // 10a 不受影响)
// 引用类型:复制引用(地址)
let obj1 = {x: 10}
let obj2 = obj1 // obj2 指向同一个对象
obj2.x = 20 // 修改 obj2 会影响 obj1
console.log(obj1.x) // 20obj1 也变了!)
```
**为什么引用类型会这样?**
因为引用类型存储的是"地址",而不是"实际的值"。当你把 `obj1` 赋值给 `obj2` 时,只是复制了地址,两个变量指向内存中的同一个对象。
**实际影响:**
- 函数参数传递时,引用类型可能会被修改
- 比较两个对象时,比较的是地址,不是内容
- 深拷贝 vs 浅拷贝的问题
**解决方法:**
- 如果需要复制对象,使用"深拷贝"(`JSON.parse(JSON.stringify(obj))``structuredClone(obj)`
- 如果只需要复制第一层,使用"浅拷贝"(`Object.assign({}, obj)``{...obj}`
:::
👇 **动手试试看**
下面这个演示展示了原始类型和引用类型的区别:
<DataTypeDemo />
---
## 4. 函数与闭包
### 4.1 函数是"一等公民"
在 JavaScript 中,函数是"一等公民"First-Class Citizen)。这意味着函数可以:
- 被赋值给变量
- 作为参数传递给其他函数
- 作为返回值从函数中返回
- 存储在数据结构中(如数组、对象)
::: tip 🤔 什么是"一等公民"
"一等公民"是编程语言的一个术语,意思是某种东西可以像其他数据一样被使用。
在 JavaScript 中,数字、字符串是"一等公民",函数也是。这让 JavaScript 非常灵活。
**在其他语言中(如 Java 8 之前),函数不是一等公民,你必须用对象或接口来包装它。**
:::
**函数的声明方式:**
| 方式 | 语法 | 特点 | 使用场景 |
|------|------|------|---------|
| **函数声明** | `function name() {}` | 会提升,可被提前调用 | 普通函数 |
| **函数表达式** | `const name = function() {}` | 不会提升 | 需要条件性创建函数 |
| **箭头函数** | `const name = () => {}` | 没有 `this`,更简洁 | 回调函数、简短函数 |
::: tip 💡 如何选择?
- **普通函数**:用函数声明或函数表达式
- **回调函数**:优先用箭头函数(更简洁)
- **需要 `this` 指向调用者**:不要用箭头函数(箭头函数没有自己的 `this`
**示例:**
```javascript
// 函数声明
function greet(name) {
return "Hello " + name
}
// 函数表达式
const greet = function(name) {
return "Hello " + name
}
// 箭头函数(最简洁)
const greet = name => "Hello " + name
```
:::
### 4.2 闭包:函数"记住"了它的出生环境
闭包(Closure)是 JavaScript 中最重要、也最容易被误解的概念之一。
::: tip 🎯 什么是闭包?
**简单来说:** 闭包是函数和它的词法环境的组合。
**更直白地说:** 内部函数可以访问外部函数的变量,即使外部函数已经执行完毕。
**打个比方:**
你出门时背了个背包,把当时看到的东西装进包里。即使你走了很远的路,依然可以从包里拿出当时装的东西 —— 这个背包就是"闭包"。
:::
**闭包的实际应用:**
1. **数据私有化**:模拟私有变量
```javascript
function createCounter() {
let count = 0 // 私有变量,外部无法直接访问
return function() {
count++
return count
}
}
const counter = createCounter()
console.log(counter()) // 1
console.log(counter()) // 2
// count 变量无法被外部直接修改,只能通过返回的函数操作
```
2. **函数工厂**:批量创建相似的函数
```javascript
function makeMultiplier(times) {
return function(n) {
return n * times
}
}
const double = makeMultiplier(2)
const triple = makeMultiplier(3)
console.log(double(5)) // 10
console.log(triple(5)) // 15
```
3. **模块化**:在 ES6 模块出现之前,常用闭包实现模块
::: warning 闭包的坑
闭包虽然强大,但使用不当会导致内存泄漏。
**问题示例:**
```javascript
function createHandlers() {
const handlers = []
for (var i = 0; i < 3; i++) {
handlers.push(function() {
console.log(i)
})
}
return handlers
}
const handlers = createHandlers()
handlers[0]() // 输出 3(不是 0!)
handlers[1]() // 输出 3(不是 1!)
handlers[2]() // 输出 3(不是 2!)
```
**原因:** `var` 没有块级作用域,所有闭包共享同一个 `i` 变量。
**解决方法:**
1.`let` 代替 `var`(推荐)
2. 用 IIFE(立即执行函数)创建独立作用域
```javascript
// 方法 1:用 let
for (let i = 0; i < 3; i++) { // ← 用 let
handlers.push(function() {
console.log(i)
})
}
// 方法 2:用 IIFE
for (var i = 0; i < 3; i++) {
(function(j) { // ← 用 IIFE 捕获当前值
handlers.push(function() {
console.log(j)
})
})(i)
}
```
:::
👇 **动手试试看**
下面这个演示展示了闭包的工作原理:
<ClosureDemo />
---
## 5. this 与执行上下文
### 5.1 this 是什么?
`this` 是 JavaScript 中最让人困惑的关键字之一。它的值取决于**函数如何被调用**,而不是**函数在哪里定义**。
::: tip 🎯 核心规则
**记住这句话:** `this` 的值是在函数调用时确定的,不是定义时确定的。
**判断 `this` 指向的四个规则:**
1. **默认绑定**`fn()``this` 指向全局对象(浏览器中是 `window`
2. **隐式绑定**`obj.fn()``this` 指向 `obj`
3. **显式绑定**`fn.call(obj)``this` 指向 `obj`
4. **new 绑定**`new Fn()``this` 指向新创建的对象
:::
**this 指向规则速查表:**
| 调用方式 | this 指向 | 示例 |
|---------|----------|------|
| **普通函数调用** | 全局对象(非严格模式)或 `undefined`(严格模式) | `fn()` |
| **对象方法调用** | 调用方法的对象 | `obj.method()` |
| **构造函数调用** | 新创建的对象 | `new Constructor()` |
| **call/apply/bind** | 显式指定的对象 | `fn.call(obj)` |
| **箭头函数** | 外层作用域的 `this` | `() => {}` |
::: tip 💡 常见误区
**误区 1** "箭头函数的 `this` 指向定义它的对象"
- ❌ 错误:箭头函数没有自己的 `this`
- ✅ 正确:箭头函数的 `this` 继承外层作用域
**误区 2** "`this` 总是指向函数本身"
- ❌ 错误:`this` 不是指向函数本身
- ✅ 正确:`this` 指向调用函数的对象
**误区 3** "嵌套函数的 `this` 不变"
- ❌ 错误:嵌套的普通函数有自己的 `this`
- ✅ 正确:用箭头函数可以继承外层 `this`
:::
👇 **动手试试看**
下面这个演示展示了不同场景下 `this` 的指向:
<ThisContextDemo />
---
## 6. 原型与继承
### 6.1 原型链:JavaScript 的继承机制
JavaScript 没有"类"ES6 之前),它通过"原型链"Prototype Chain)实现继承。
::: tip 🎯 什么是原型链?
每个对象都有一个"原型"(`__proto__`),当我们访问对象的属性时:
1. 先在对象自身查找
2. 找不到就去它的原型对象上查找
3. 还找不到就去原型的原型查找
4. 一直查到 `Object.prototype`(最顶层的原型)
5. 如果还找不到,返回 `undefined`
这条查找链条就是"原型链"。
:::
**原型链示例:**
```javascript
function Person(name) {
this.name = name
}
Person.prototype.greet = function() {
return "Hello, I'm " + this.name
}
const p = new Person("张三")
// 访问 p.greet() 时的查找过程:
// 1. 在 p 自身上查找 → 没有 greet 方法
// 2. 在 p.__proto__(即 Person.prototype)上查找 → 找到了!
// 3. 执行 greet 方法
console.log(p.greet()) // "Hello, I'm 张三"
```
**原型链的关系:**
```
实例对象 (p)
__proto__ → Person.prototype
__proto__ → Object.prototype
__proto__ → null
```
::: tip 💡 class 语法
ES6 引入了 `class` 语法,让面向对象编程更接近传统语言。但记住:**`class` 只是语法糖,底层仍然是原型链**。
**示例:**
```javascript
// ES6 class 语法
class Person {
constructor(name) {
this.name = name
}
greet() {
return "Hello, I'm " + this.name
}
}
// 等价于 ES5 的写法
function Person(name) {
this.name = name
}
Person.prototype.greet = function() {
return "Hello, I'm " + this.name
}
```
:::
👇 **动手试试看**
下面这个演示展示了原型链的工作原理:
<PrototypeDemo />
---
## 7. 异步编程
### 7.1 为什么需要异步?
JavaScript 是**单线程**的,这意味着它同一时间只能做一件事。如果所有操作都是同步的,那么耗时的操作(如网络请求)会阻塞整个程序,页面就会"卡死"。
::: tip 🎯 同步 vs 异步
**同步(Synchronous):** 按顺序执行,前一个任务完成后才开始下一个任务。
- 优点:简单直观
- 缺点:耗时操作会阻塞
**异步(Asynchronous):** 不等待耗时操作完成,先去做其他事,操作完成后再回来处理。
- 优点:不阻塞,性能更好
- 缺点:代码更复杂,需要处理回调
:::
**实际对比:**
```javascript
// 同步方式(会阻塞)
console.log("1")
console.log("2") // 等上面执行完
console.log("3")
// 输出:1, 2, 3
// 异步方式(不阻塞)
console.log("1")
setTimeout(() => console.log("2"), 1000) // 1秒后执行
console.log("3")
// 输出:1, 3, 2(注意顺序!)
```
### 7.2 异步编程的演进
JavaScript 的异步编程经历了三个阶段:
| 阶段 | 方式 | 优点 | 缺点 |
|------|------|------|------|
| **回调函数** | `callback` | 简单直接 | 回调地狱 |
| **Promise** | `then/catch` | 链式调用,可读性更好 | 仍然不够简洁 |
| **async/await** | `async/await` | 像同步代码一样写异步 | 需要 Promise 支持 |
**代码对比:**
```javascript
// 1. 回调函数(回调地狱)
getData(function(a) {
getMoreData(a, function(b) {
getMoreData(b, function(c) {
// 无限嵌套...
})
})
})
// 2. Promise 链式调用
getData()
.then(a => getMoreData(a))
.then(b => getMoreData(b))
.then(c => console.log(c))
.catch(err => console.error(err))
// 3. async/await(最优雅)
async function fetchData() {
try {
const a = await getData()
const b = await getMoreData(a)
const c = await getMoreData(b)
console.log(c)
} catch (err) {
console.error(err)
}
}
```
### 7.3 事件循环(Event Loop
JavaScript 如何实现异步?答案是**事件循环**。
::: tip 🎯 事件循环的工作原理
JavaScript 的执行机制:
1. **执行同步代码**(所有同步任务都在主线程执行)
2. **主线程为空时**,检查微任务队列(Microtask Queue
3. **执行所有微任务**Promise.then、MutationObserver
4. **执行一个宏任务**setTimeout、setInterval、I/O
5. **重复步骤 2-4**
**关键点:**
- 微任务优先级高于宏任务
- 每次执行完一个宏任务后,都会清空所有微任务
- setTimeout 是宏任务,Promise.then 是微任务
:::
**经典面试题:**
```javascript
console.log("1")
setTimeout(() => console.log("2"), 0) // 宏任务
Promise.resolve().then(() => console.log("3")) // 微任务
console.log("4")
// 输出顺序:1, 4, 3, 2
// 解析:
// 1. 执行同步代码:输出 1, 4
// 2. 执行微任务:输出 3
// 3. 执行宏任务:输出 2
```
👇 **动手试试看**
下面这个演示展示了异步编程和事件循环:
<AsyncDemo />
---
## 8. 总结
让我们用一张表格回顾 JavaScript 的核心概念:
| 概念 | 一句话总结 | 关键要点 | 常见坑 |
|------|-----------|---------|--------|
| **变量声明** | 优先用 `const`,其次 `let`,不用 `var` | 块级作用域、不可变性 | var 的变量提升、作用域泄漏 |
| **数据类型** | 原始类型存值,引用类型存地址 | 七种原始类型、引用类型 | 引用类型的赋值和比较 |
| **函数** | JavaScript 的"一等公民" | 函数声明、箭头函数 | 箭头函数没有自己的 `this` |
| **闭包** | 函数"记住"了外部变量 | 数据私有化、函数工厂 | 内存泄漏、循环中的闭包 |
| **this** | 取决于函数如何被调用 | 四种绑定规则 | 嵌套函数的 `this` 丢失 |
| **原型链** | JavaScript 的继承机制 | `__proto__`、原型链查找 | 属性查找的顺序 |
| **异步编程** | 用同步的方式写异步代码 | 事件循环、微任务/宏任务 | 回调地狱、执行顺序 |
::: info 写在最后
JavaScript 是一门看似简单、实则精妙的语言。它的核心概念——作用域、闭包、this、原型链、异步——构成了前端开发的基础。
**深入理解这些概念,你不仅能写出更好的代码,还能更快地学习新技术(因为 Vue、React 等框架都是基于这些特性构建的)。**
希望这篇文章能帮助你建立起对 JavaScript 的系统性认识。记住:**不必一次全学会,循序渐进、持续实践,你终将掌握这门语言的精髓。**
:::