JavaScript 基础概念
你是否曾经遇到过这样的代码?在 setTimeout 回调里想访问外层的
this,却发现它变成了undefined。或者在处理数组时,写了一堆 for 循环,最后发现可以用一行map解决。这些问题,都指向 JavaScript 最基础,也是最核心的概念。
本文主要记录使用和学习 JavaScript 的过程中,我认为比较重要的概念:箭头函数、高阶函数、原型链、代码拆分和模板。这些概念,对于无论是刚入门的新手,还是想巩固基础的开发者,都至关重要。
1. 箭头函数:简洁语法的背后
ES6 引入的箭头函数,不仅仅是语法糖。它解决了 JavaScript 中 this 绑定的长期困扰。
1.1 基本语法
const add = (a, b) => {
return a + b;
};
// 隐式返回 - 省略花括号和 return
const multiply = (a, b) => a * b;
// 单参数 - 省略括号
const double = x => x * 2;
ps: 当函数体只有单个表达式时,省略花括号会让代码更简洁。
1.2 核心特性:词法 this 绑定
箭头函数最重要的特性:不绑定自己的 this。
// 箭头函数:this 指向定义时的上下文
function Counter() {
this.count = 0;
setInterval(() => {
this.count++; // 这里的 this 指向 Counter 实例
console.log(this.count);
}, 1000);
}
const counter = new Counter();
// 输出: 1, 2, 3, ...
对比普通函数:
// 普通函数:this 指向调用时的上下文
function Counter() {
this.count = 0;
setInterval(function() {
this.count++; // 这里的 this 指向 global 或 undefined
console.log(this.count);
}, 1000);
}
1.3 使用限制
箭头函数不是万能的。以下场景不适合使用:
- 对象方法:
this无法动态绑定 - 构造函数:不能使用
new调用 - 原型方法:
prototype属性不存在
// ❌ 错误用法
const obj = {
name: 'test',
getName: () => this.name // this 指向外层作用域,不是 obj
};
// ✅ 正确用法
const obj = {
name: 'test',
getName() { return this.name; } // 普通方法
};
2. 高阶函数:函数式编程的基石
既然说到了函数,就不得不提高阶函数——JavaScript 函数式编程的核心。
高阶函数是指:接收函数作为参数,或返回函数的函数。
2.1 数组处理的三板斧
const numbers = [1, 2, 3, 4, 5];
// map: 转换每个元素
const doubled = numbers.map(x => x * 2);
// [2, 4, 6, 8, 10]
// filter: 筛选符合条件的元素
const evens = numbers.filter(x => x % 2 === 0);
// [2, 4]
// reduce: 汇总为单个值
const sum = numbers.reduce((acc, cur) => acc + cur, 0);
// 15
graph LR
A["原始数组 1,2,3,4,5"] --> B["map"]
B --> C["新数组 2,4,6,8,10"]
A --> D["filter"]
D --> E["筛选结果 2,4"]
A --> F["reduce"]
F --> G["单一值 15"]
2.2 组合使用
高阶函数的真正威力在于组合:
const users = [
{ name: 'Alice', age: 25 },
{ name: 'Bob', age: 17 },
{ name: 'Charlie', age: 30 }
];
// 获取成年用户名字
const adultNames = users
.filter(user => user.age >= 18)
.map(user => user.name);
// ['Alice', 'Charlie']
性能提示: 避免在
map/filter/reduce中创建不必要的中间数组。链式调用虽然简洁,但数据量大时可能需要手动优化。
2.3 更多高阶函数
// find: 查找第一个匹配元素
const firstEven = numbers.find(x => x % 2 === 0);
// some: 是否有任意匹配
const hasNegative = numbers.some(x => x < 0);
// every: 是否全部匹配
const allPositive = numbers.every(x => x > 0);
// bind: 绑定 this 和参数
const log = console.log.bind(console);
3. 原型链:JavaScript 的继承
提到继承,Java 和 C++ 开发者可能会想到”类”。但 JavaScript 没有类 —— 它用的是原型。
3.1 原型链工作机制
每个 JavaScript 对象都有一个 __proto__ 属性,指向它的原型。当访问对象的属性时,如果对象本身没有这个属性,JavaScript 会沿着原型链向上查找。
function Person(name) {
this.name = name;
}
Person.prototype.greet = function() {
return `Hello, I'm ${this.name}`;
};
const alice = new Person('Alice');
console.log(alice.greet()); // "Hello, I'm Alice"
// 原型链: alice → Person.prototype → Object.prototype → null
graph TD
A["alice 实例"] -->|"__proto__"| B["Person.prototype"]
B -->|"__proto__"| C["Object.prototype"]
C -->|"__proto__"| D["null"]
3.2 原型继承
function Employee(name, jobTitle) {
Person.call(this, name); // 调用父构造函数
this.jobTitle = jobTitle;
}
// 原型继承
Employee.prototype = Object.create(Person.prototype);
Employee.prototype.constructor = Employee;
Employee.prototype.work = function() {
return `${this.name} is working as ${this.jobTitle}`;
};
const bob = new Employee('Bob', 'Developer');
console.log(bob.greet()); // "Hello, I'm Bob" (继承自 Person)
console.log(bob.work()); // "Bob is working as Developer"
3.3 ES6 Class 的本质
很多人以为 ES6 的
class是 JavaScript 新的继承方式。其实,它只是原型继承的语法糖。
// ES6 Class 写法
class Person {
constructor(name) {
this.name = name;
}
greet() {
return `Hello, I'm ${this.name}`;
}
}
// 等价于原型写法
function Person(name) {
this.name = name;
}
Person.prototype.greet = function() {
return `Hello, I'm ${this.name}`;
};
3.4 常见陷阱
// ❌ 错误:修改原型影响所有实例
Person.prototype.sayHi = () => console.log('Hi');
// 所有 Person 实例都受影响
// ✅ 正确:只在实例上添加
alice.sayHi = () => console.log('Hi');
// 只有 alice 实例有这个方法
4. 代码拆分:性能优化的关键
首屏加载太慢?用户流失严重。此时需要的可能就是代码拆分(Code Splitting)。
4.1 动态导入
ES6 的 import() 允许动态加载模块:
// 静态导入 - 立即加载
import { utils } from './utils.js';
// 动态导入 - 按需加载
button.addEventListener('click', () => {
import('./heavyModule.js')
.then(module => {
module.doSomething();
});
});
sequenceDiagram
participant User
participant Page
participant Network
participant Server
User->>Page: 点击按钮
Page->>Network: import('./heavyModule.js')
Network->>Server: 请求模块
Server-->>Network: 返回模块代码
Network-->>Page: 加载完成
Page->>Page: 执行模块代码
4.2 Webpack 拆分策略
// webpack.config.js
module.exports = {
optimization: {
splitChunks: {
chunks: 'all', // 分割所有类型的 chunk
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all',
},
},
},
},
};
4.3 拆分策略对比
| 策略 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| 路由级拆分 | SPA 应用 | 简单易实现 | 粒度粗 |
| 组件级拆分 | 大型组件 | 按需加载 | 配置复杂 |
| 库级拆分 | 依赖稳定 | 长期缓存 | 初始请求多 |
实战建议: 将 node_modules 单独打包。利用浏览器的长期缓存,减少重复下载。
5. 模板:动态渲染的艺术
如何安全地拼接动态数据到 HTML?模板技术是关键。
5.1 模板字符串
ES6 模板字符串极大缓解了拼接字符串时不断输入引号的折磨:
const user = { name: 'Alice', age: 25 };
// 模板字符串
const html = `
<div class="user-card">
<h2>${user.name}</h2>
<p>Age: ${user.age}</p>
</div>
`;
// 对比旧写法
const htmlOld = '<div class="user-card">' +
'<h2>' + user.name + '</h2>' +
'<p>Age: ' + user.age + '</p>' +
'</div>';
5.2 模板引擎
复杂场景下,使用模板引擎:
<!-- Handlebars 模板 -->
<script id="user-template" type="text/x-handlebars-template">
<div class="user">
<h2>{{name}}</h2>
{{#if age}}
<p>Age: {{age}}</p>
{{/if}}
<ul>
{{#each hobbies}}
<li>{{this}}</li>
{{/each}}
</ul>
</div>
</script>
// 编译并渲染
const template = Handlebars.compile(
document.getElementById('user-template').innerHTML
);
const html = template({
name: 'Alice',
age: 25,
hobbies: ['Reading', 'Coding', 'Gaming']
});
5.3 安全注意
XSS 防护: 永远不要直接将用户输入插入模板。使用模板引擎的自动转义功能。
// ❌ 危险
const html = `<div>${userInput}</div>`;
// ✅ 安全 - 自动转义
const html = template({ userInput });
6. 总结
| 核心要点 概念 | 应用场景 | |
|---|---|---|
| 箭头函数 | 词法 this 绑定 | 回调、数组方法 |
| 高阶函数 | 函数作为参数/返回值 | 数据处理、函数组合 |
| 原型链 | 基于原型的继承 | 对象创建、继承模式 |
| 代码拆分 | 按需加载 | 性能优化 |
| 模板 | 数据驱动渲染 | UI 动态更新 |
推荐
- 深入理解: MDN 原型文档
- 进阶主题:
- 异步编程 (Promise, async/await)
- 模块系统 (CommonJS, ES Modules)
- 装饰器与代理
学习是一个持续的过程,基础概念是进阶的前提,常看常新,共勉。