1.27 - 作用域、闭包、this指向
JavaScript 深入理解:作用域、闭包、this指向
目录
作用域
一、什么是作用域? (Scope)
作用域是变量和函数的可访问范围,它决定了代码中变量的可见性和生命周期。
二、作用域类型
1. 全局作用域 (Global Scope)
var globalVar = "我是全局变量";
let globalLet = "我也是全局变量";
function test() {
console.log(globalVar); // 可以访问
console.log(globalLet); // 可以访问
}
2. 函数作用域 (Function Scope)
function outer() {
var functionVar = "函数作用域变量";
console.log(functionVar); // ✅ 可以访问
}
console.log(functionVar); // ❌ ReferenceError: functionVar is not defined
关键特性:
- 用
var声明的变量具有函数作用域 - 函数内部可以访问外部作用域的变量
- 函数外部无法访问函数内部的变量
3. 块级作用域 (Block Scope)
if (true) {
let blockLet = "块级作用域";
const blockConst = "也是块级作用域";
var notBlock = "不是块级作用域";
}
console.log(notBlock); // ✅ "不是块级作用域"
console.log(blockLet); // ❌ ReferenceError
console.log(blockConst); // ❌ ReferenceError
let/const vs var 的区别:
// var 没有块级作用域
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100);
}
// 输出: 3, 3, 3 (因为 i 是函数作用域,循环结束后 i = 3)
// let 有块级作用域
for (let j = 0; j < 3; j++) {
setTimeout(() => console.log(j), 100);
}
// 输出: 0, 1, 2 (每次循环 j 都是新的变量)
三、词法作用域 (Lexical Scope)
JavaScript 使用词法作用域(也叫静态作用域),即函数的作用域在函数定义时就确定了,而不是在函数调用时确定。
var value = 1;
function foo() {
console.log(value);
}
function bar() {
var value = 2;
foo(); // 输出什么?
}
bar(); // 输出: 1
原理解析:
foo函数在全局作用域中定义- 根据词法作用域,
foo函数内部的value会沿着定义时的作用域链查找 - 查找顺序:foo 函数内部 → 全局作用域
- 所以输出的是全局的
value = 1,而不是bar中的value = 2
四、作用域链 (Scope Chain)
当查找变量时,JavaScript 引擎会按照以下顺序查找:
当前作用域 → 外层作用域 → 外外层作用域 → ... → 全局作用域
var a = 1;
function outer() {
var b = 2;
function inner() {
var c = 3;
console.log(a); // 1 (从全局作用域找到)
console.log(b); // 2 (从 outer 作用域找到)
console.log(c); // 3 (从当前作用域找到)
}
inner();
}
outer();
作用域链的形成:
inner 的作用域链: [inner 作用域] → [outer 作用域] → [全局作用域]
outer 的作用域链: [outer 作用域] → [全局作用域]
五、变量提升 (Hoisting)
var 的提升
console.log(a); // undefined (不是报错)
var a = 1;
// 等价于:
var a;
console.log(a); // undefined
a = 1;
函数声明的提升
foo(); // "Hello" (可以在声明前调用)
function foo() {
console.log("Hello");
}
let/const 的暂时性死区 (TDZ)
console.log(b); // ❌ ReferenceError: Cannot access 'b' before initialization
let b = 2;
// const 同理
console.log(c); // ❌ ReferenceError
const c = 3;
原理:let/const 也会提升,但在声明之前的区域是”暂时性死区”,无法访问。
六、面试常考题
题目1:循环中的闭包
// 问题代码
for (var i = 0; i < 5; i++) {
setTimeout(function() {
console.log(i);
}, 1000);
}
// 输出: 5, 5, 5, 5, 5
// 解决方案1: 使用 let
for (let i = 0; i < 5; i++) {
setTimeout(function() {
console.log(i);
}, 1000);
}
// 输出: 0, 1, 2, 3, 4
// 解决方案2: 使用 IIFE
for (var i = 0; i < 5; i++) {
(function(j) {
setTimeout(function() {
console.log(j);
}, 1000);
})(i);
}
// 输出: 0, 1, 2, 3, 4
// 解决方案3: 使用 setTimeout 的第三个参数
for (var i = 0; i < 5; i++) {
setTimeout(function(j) {
console.log(j);
}, 1000, i);
}
// 输出: 0, 1, 2, 3, 4
闭包
一、什么是闭包? (Closure)
闭包是指有权访问另一个函数作用域中变量的函数。
更准确的定义:
- 闭包 = 函数 + 函数能够访问的自由变量
function outer() {
var count = 0; // 自由变量
function inner() {
count++;
console.log(count);
}
return inner;
}
var counter = outer();
counter(); // 1
counter(); // 2
counter(); // 3
二、闭包的原理
执行上下文和变量对象
当函数执行时,JavaScript 引擎会创建一个执行上下文,包含:
- 变量对象 (VO): 存储变量、函数声明、参数
- 作用域链 (Scope Chain): 保存所有父级作用域的变量对象
- this 值
function createCounter() {
var count = 0; // 存储在 createCounter 的变量对象中
return function() {
count++;
return count;
};
}
var counter1 = createCounter();
var counter2 = createCounter();
console.log(counter1()); // 1
console.log(counter1()); // 2
console.log(counter2()); // 1 (独立的闭包)
内存模型:
全局执行上下文
├─ counter1 → 匿名函数
│ └─ [[Scope]] → createCounter 的 AO (count = 2)
└─ counter2 → 匿名函数
└─ [[Scope]] → createCounter 的 AO (count = 1)
每次调用 createCounter(),都会创建一个新的执行上下文和变量对象,形成独立的闭包。
三、闭包的应用场景
1. 模块化模式 (Module Pattern)
var Module = (function() {
// 私有变量
var privateVar = "我是私有的";
var privateCount = 0;
// 私有方法
function privateMethod() {
console.log("私有方法");
}
// 返回公共接口
return {
publicMethod: function() {
privateCount++;
console.log("调用次数:", privateCount);
return privateVar;
},
getCount: function() {
return privateCount;
}
};
})();
console.log(Module.publicMethod()); // "调用次数: 1", "我是私有的"
console.log(Module.publicMethod()); // "调用次数: 2", "我是私有的"
console.log(Module.getCount()); // 2
console.log(Module.privateVar); // undefined (无法访问私有变量)
2. 函数工厂
function makeMultiplier(multiplier) {
return function(number) {
return number * multiplier;
};
}
var double = makeMultiplier(2);
var triple = makeMultiplier(3);
console.log(double(5)); // 10
console.log(triple(5)); // 15
3. 防抖和节流
// 防抖 (Debounce)
function debounce(fn, delay) {
var timer = null; // 闭包变量
return function() {
var context = this;
var args = arguments;
clearTimeout(timer);
timer = setTimeout(function() {
fn.apply(context, args);
}, delay);
};
}
// 使用示例
var searchInput = document.getElementById('search');
searchInput.addEventListener('input', debounce(function(e) {
console.log('搜索:', e.target.value);
}, 500));
// 节流 (Throttle)
function throttle(fn, delay) {
var lastTime = 0;
return function() {
var now = Date.now();
if (now - lastTime >= delay) {
fn.apply(this, arguments);
lastTime = now;
}
};
}
4. 缓存 (Memoization)
function memoize(fn) {
var cache = {}; // 闭包保存缓存
return function() {
var key = JSON.stringify(arguments);
if (cache[key]) {
console.log('从缓存读取');
return cache[key];
}
var result = fn.apply(this, arguments);
cache[key] = result;
return result;
};
}
// 使用示例:计算斐波那契数列
var fibonacci = memoize(function(n) {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
});
console.log(fibonacci(10)); // 计算
console.log(fibonacci(10)); // 从缓存读取
5. 私有变量和方法
function Person(name) {
// 私有变量
var age = 0;
// 公共方法(通过闭包访问私有变量)
this.getName = function() {
return name;
};
this.setAge = function(newAge) {
if (newAge > 0 && newAge < 150) {
age = newAge;
}
};
this.getAge = function() {
return age;
};
}
var person = new Person("张三");
person.setAge(25);
console.log(person.getAge()); // 25
console.log(person.age); // undefined (私有变量无法直接访问)
四、闭包的陷阱和注意事项
1. 内存泄漏
// ❌ 不好的例子:循环引用导致内存泄漏
function leakyFunction() {
var element = document.getElementById('myDiv');
var data = new Array(10000).fill('x');
element.onclick = function() {
console.log(data[0]); // 闭包引用了 data 和 element
};
// element 和闭包相互引用,可能导致内存泄漏
}
// ✅ 好的例子:及时清理引用
function betterFunction() {
var element = document.getElementById('myDiv');
var id = element.id;
element.onclick = function() {
console.log(id); // 只保存必要的数据
};
element = null; // 解除引用
}
2. 在循环中创建闭包
// ❌ 常见错误
function createFunctions() {
var result = [];
for (var i = 0; i < 3; i++) {
result[i] = function() {
return i;
};
}
return result;
}
var funcs = createFunctions();
console.log(funcs[0]()); // 3
console.log(funcs[1]()); // 3
console.log(funcs[2]()); // 3
// ✅ 解决方案1:使用 let
function createFunctionsFixed1() {
var result = [];
for (let i = 0; i < 3; i++) {
result[i] = function() {
return i;
};
}
return result;
}
// ✅ 解决方案2:使用 IIFE
function createFunctionsFixed2() {
var result = [];
for (var i = 0; i < 3; i++) {
result[i] = (function(num) {
return function() {
return num;
};
})(i);
}
return result;
}
五、面试高频题
题目1:实现一个 once 函数
function once(fn) {
var called = false;
var result;
return function() {
if (!called) {
called = true;
result = fn.apply(this, arguments);
}
return result;
};
}
// 使用示例
var initialize = once(function() {
console.log("初始化");
return "初始化完成";
});
console.log(initialize()); // "初始化", "初始化完成"
console.log(initialize()); // "初始化完成" (不再打印"初始化")
题目2:实现 bind 方法
Function.prototype.myBind = function(context) {
var self = this;
var args = Array.prototype.slice.call(arguments, 1);
return function() {
var bindArgs = Array.prototype.slice.call(arguments);
return self.apply(context, args.concat(bindArgs));
};
};
// 测试
var obj = { value: 42 };
function fn(a, b) {
console.log(this.value, a, b);
}
var boundFn = fn.myBind(obj, 1);
boundFn(2); // 42, 1, 2
题目3:实现私有计数器
var Counter = (function() {
var count = 0; // 私有变量
return {
increment: function() {
return ++count;
},
decrement: function() {
return --count;
},
getCount: function() {
return count;
},
reset: function() {
count = 0;
return count;
}
};
})();
console.log(Counter.increment()); // 1
console.log(Counter.increment()); // 2
console.log(Counter.getCount()); // 2
console.log(Counter.reset()); // 0
this指向
一、什么是 this?
this 是函数运行时的上下文对象,它的值取决于函数的调用方式,而不是定义方式。
二、this 的绑定规则
1. 默认绑定 (Default Binding)
独立函数调用时,this 指向全局对象(浏览器中是 window,Node.js 中是 global)。
function foo() {
console.log(this); // window (非严格模式) 或 undefined (严格模式)
}
foo();
// 严格模式
'use strict';
function bar() {
console.log(this); // undefined
}
bar();
2. 隐式绑定 (Implicit Binding)
通过对象调用函数时,this 指向该对象。
var obj = {
name: "张三",
sayName: function() {
console.log(this.name);
}
};
obj.sayName(); // "张三" (this 指向 obj)
// 多层对象
var obj2 = {
name: "李四",
child: {
name: "王五",
sayName: function() {
console.log(this.name);
}
}
};
obj2.child.sayName(); // "王五" (this 指向 child)
隐式绑定丢失:
var obj = {
name: "张三",
sayName: function() {
console.log(this.name);
}
};
var name = "全局";
var fn = obj.sayName; // 赋值给变量
fn(); // "全局" (this 指向 window,隐式绑定丢失)
// 作为回调函数
setTimeout(obj.sayName, 100); // "全局" (隐式绑定丢失)
3. 显式绑定 (Explicit Binding)
使用 call、apply、bind 显式指定 this。
function greet(greeting, punctuation) {
console.log(greeting + ', ' + this.name + punctuation);
}
var person = { name: "张三" };
// call: 参数逐个传递
greet.call(person, "你好", "!"); // "你好, 张三!"
// apply: 参数以数组形式传递
greet.apply(person, ["你好", "!"]); // "你好, 张三!"
// bind: 返回新函数,永久绑定 this
var boundGreet = greet.bind(person, "你好");
boundGreet("!"); // "你好, 张三!"
bind 的实现原理:
Function.prototype.myBind = function(context) {
var self = this;
var args = Array.prototype.slice.call(arguments, 1);
var fBound = function() {
var bindArgs = Array.prototype.slice.call(arguments);
// 如果作为构造函数调用,this 指向实例
return self.apply(
this instanceof fBound ? this : context,
args.concat(bindArgs)
);
};
// 维护原型链
fBound.prototype = Object.create(this.prototype);
return fBound;
};
4. new 绑定 (New Binding)
使用 new 调用构造函数时,this 指向新创建的对象。
function Person(name) {
this.name = name;
this.sayName = function() {
console.log(this.name);
};
}
var person1 = new Person("张三");
person1.sayName(); // "张三" (this 指向 person1)
new 的执行过程:
function myNew(Constructor, ...args) {
// 1. 创建一个新对象
var obj = {};
// 2. 将新对象的 __proto__ 指向构造函数的 prototype
obj.__proto__ = Constructor.prototype;
// 3. 将构造函数的 this 绑定到新对象,并执行构造函数
var result = Constructor.apply(obj, args);
// 4. 如果构造函数返回对象,则返回该对象;否则返回新对象
return typeof result === 'object' && result !== null ? result : obj;
}
// 测试
var person2 = myNew(Person, "李四");
person2.sayName(); // "李四"
三、绑定优先级
new 绑定 > 显式绑定 > 隐式绑定 > 默认绑定
function foo() {
console.log(this.name);
}
var obj1 = { name: "obj1", foo: foo };
var obj2 = { name: "obj2" };
// 隐式绑定 vs 显式绑定
obj1.foo.call(obj2); // "obj2" (显式绑定优先)
// 显式绑定 vs new 绑定
function Bar(name) {
this.name = name;
}
var bar = Bar.bind(obj1);
var instance = new bar("new绑定");
console.log(instance.name); // "new绑定" (new 绑定优先)
四、箭头函数的 this
箭头函数没有自己的 this,它的 this 继承自外层作用域(词法作用域)。
var obj = {
name: "张三",
regularFunc: function() {
console.log(this.name); // "张三"
},
arrowFunc: () => {
console.log(this.name); // undefined (this 指向全局)
},
nestedFunc: function() {
var arrow = () => {
console.log(this.name); // "张三" (继承自 nestedFunc 的 this)
};
arrow();
}
};
obj.regularFunc(); // "张三"
obj.arrowFunc(); // undefined
obj.nestedFunc(); // "张三"
箭头函数的特性:
- 没有自己的 this,继承外层作用域的 this
- 不能使用 call/apply/bind 改变 this
- 不能作为构造函数使用(不能使用 new)
- 没有 arguments 对象
- 没有 prototype 属性
// 箭头函数解决回调中的 this 问题
function Timer() {
this.seconds = 0;
// 传统方法:需要保存 this
setInterval(function() {
this.seconds++; // ❌ this 指向 window
}, 1000);
// 使用 that/self
var that = this;
setInterval(function() {
that.seconds++; // ✅ 通过闭包访问外层 this
}, 1000);
// 使用箭头函数
setInterval(() => {
this.seconds++; // ✅ 箭头函数继承外层 this
}, 1000);
}
五、常见面试题
题目1:输出结果
var name = "全局";
var obj = {
name: "obj",
foo1: function() {
console.log(this.name);
},
foo2: () => {
console.log(this.name);
},
foo3: function() {
return function() {
console.log(this.name);
};
},
foo4: function() {
return () => {
console.log(this.name);
};
}
};
obj.foo1(); // "obj" (隐式绑定)
obj.foo2(); // "全局" (箭头函数,this 指向全局)
var fn3 = obj.foo3();
fn3(); // "全局" (默认绑定)
var fn4 = obj.foo4();
fn4(); // "obj" (箭头函数,继承 foo4 的 this)
题目2:实现一个柯里化函数
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));
};
}
};
}
// 测试
function add(a, b, c) {
return a + b + c;
}
var curriedAdd = curry(add);
console.log(curriedAdd(1)(2)(3)); // 6
console.log(curriedAdd(1, 2)(3)); // 6
console.log(curriedAdd(1)(2, 3)); // 6
题目3:手写 call、apply、bind
// call 实现
Function.prototype.myCall = function(context, ...args) {
context = context || window;
var fn = Symbol('fn'); // 使用 Symbol 避免属性冲突
context[fn] = this;
var result = context[fn](...args);
delete context[fn];
return result;
};
// apply 实现
Function.prototype.myApply = function(context, args) {
context = context || window;
var fn = Symbol('fn');
context[fn] = this;
var result = args ? context[fn](...args) : context[fn]();
delete context[fn];
return result;
};
// bind 实现
Function.prototype.myBind = function(context, ...args) {
var self = this;
var fBound = function(...args2) {
return self.apply(
this instanceof fBound ? this : context,
args.concat(args2)
);
};
fBound.prototype = Object.create(this.prototype);
return fBound;
};
// 测试
var obj = { value: 42 };
function test(a, b) {
console.log(this.value, a, b);
}
test.myCall(obj, 1, 2); // 42, 1, 2
test.myApply(obj, [1, 2]); // 42, 1, 2
var bound = test.myBind(obj, 1);
bound(2); // 42, 1, 2
题目4:setTimeout 中的 this
var obj = {
name: "obj",
foo: function() {
console.log(this.name);
}
};
setTimeout(obj.foo, 1000); // undefined (this 指向 window)
// 解决方案1:箭头函数
setTimeout(() => obj.foo(), 1000); // "obj"
// 解决方案2:bind
setTimeout(obj.foo.bind(obj), 1000); // "obj"
// 解决方案3:传递函数
setTimeout(function() {
obj.foo();
}, 1000); // "obj"
综合实战
案例1:实现一个事件发布订阅系统
class EventEmitter {
constructor() {
this.events = {}; // 使用对象存储事件
}
// 订阅事件
on(eventName, callback) {
if (!this.events[eventName]) {
this.events[eventName] = [];
}
this.events[eventName].push(callback);
// 返回取消订阅的函数(闭包)
return () => {
this.off(eventName, callback);
};
}
// 只订阅一次
once(eventName, callback) {
var wrapper = (...args) => {
callback.apply(this, args);
this.off(eventName, wrapper);
};
this.on(eventName, wrapper);
}
// 发布事件
emit(eventName, ...args) {
var callbacks = this.events[eventName];
if (!callbacks) return;
callbacks.forEach(callback => {
callback.apply(this, args);
});
}
// 取消订阅
off(eventName, callback) {
var callbacks = this.events[eventName];
if (!callbacks) return;
if (!callback) {
delete this.events[eventName];
} else {
this.events[eventName] = callbacks.filter(cb => cb !== callback);
}
}
}
// 使用示例
var emitter = new EventEmitter();
var unsubscribe = emitter.on('login', (user) => {
console.log(`用户 ${user} 登录了`);
});
emitter.once('logout', (user) => {
console.log(`用户 ${user} 登出了`);
});
emitter.emit('login', '张三'); // "用户 张三 登录了"
emitter.emit('logout', '张三'); // "用户 张三 登出了"
emitter.emit('logout', '李四'); // 无输出(once 只触发一次)
unsubscribe(); // 取消订阅
emitter.emit('login', '王五'); // 无输出
案例2:实现 Promise.all
Promise.myAll = function(promises) {
return new Promise((resolve, reject) => {
if (!Array.isArray(promises)) {
return reject(new TypeError('参数必须是数组'));
}
var results = [];
var completedCount = 0;
var total = promises.length;
if (total === 0) {
return resolve(results);
}
promises.forEach((promise, index) => {
Promise.resolve(promise).then(
value => {
results[index] = value;
completedCount++;
if (completedCount === total) {
resolve(results);
}
},
reason => {
reject(reason);
}
);
});
});
};
// 测试
var p1 = Promise.resolve(1);
var p2 = new Promise(resolve => setTimeout(() => resolve(2), 100));
var p3 = Promise.resolve(3);
Promise.myAll([p1, p2, p3]).then(results => {
console.log(results); // [1, 2, 3]
});
案例3:实现深拷贝(处理循环引用)
function deepClone(obj, hash = new WeakMap()) {
// 处理 null 和基本类型
if (obj === null) return obj;
if (typeof obj !== 'object') return obj;
// 处理日期
if (obj instanceof Date) return new Date(obj);
// 处理正则
if (obj instanceof RegExp) return new RegExp(obj);
// 处理循环引用
if (hash.has(obj)) return hash.get(obj);
// 根据原对象的原型创建新对象
var cloneObj = new obj.constructor();
hash.set(obj, cloneObj);
// 遍历对象
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
cloneObj[key] = deepClone(obj[key], hash);
}
}
return cloneObj;
}
// 测试
var obj1 = {
name: '张三',
info: { age: 25 },
hobbies: ['篮球', '足球']
};
obj1.self = obj1; // 循环引用
var obj2 = deepClone(obj1);
console.log(obj2.self === obj2); // true
console.log(obj1 === obj2); // false
案例4:实现一个简单的虚拟 DOM diff
function createElement(type, props, ...children) {
return {
type,
props: props || {},
children: children.flat()
};
}
function diff(oldNode, newNode) {
// 节点类型不同,直接替换
if (typeof oldNode !== typeof newNode) {
return { type: 'REPLACE', newNode };
}
// 文本节点
if (typeof oldNode === 'string') {
if (oldNode !== newNode) {
return { type: 'TEXT', newNode };
}
return null;
}
// 标签不同,替换
if (oldNode.type !== newNode.type) {
return { type: 'REPLACE', newNode };
}
// 比较属性
var propsPatches = diffProps(oldNode.props, newNode.props);
// 比较子节点
var childrenPatches = [];
var maxLength = Math.max(
oldNode.children.length,
newNode.children.length
);
for (var i = 0; i < maxLength; i++) {
childrenPatches[i] = diff(
oldNode.children[i],
newNode.children[i]
);
}
return {
type: 'UPDATE',
props: propsPatches,
children: childrenPatches
};
}
function diffProps(oldProps, newProps) {
var patches = {};
// 检查修改和新增的属性
for (var key in newProps) {
if (oldProps[key] !== newProps[key]) {
patches[key] = newProps[key];
}
}
// 检查删除的属性
for (var key in oldProps) {
if (!(key in newProps)) {
patches[key] = undefined;
}
}
return patches;
}
// 使用示例
var oldVNode = createElement('div', { id: 'container' },
createElement('span', {}, 'Hello')
);
var newVNode = createElement('div', { id: 'container', class: 'active' },
createElement('span', {}, 'World')
);
var patches = diff(oldVNode, newVNode);
console.log(JSON.stringify(patches, null, 2));
总结
作用域核心要点
- JavaScript 使用词法作用域(静态作用域)
- 作用域链在函数定义时确定
- let/const 有块级作用域,var 没有
- 变量提升:var 和函数声明会提升,let/const 有暂时性死区
闭包核心要点
- 闭包 = 函数 + 能访问的自由变量
- 闭包可以访问外部函数的变量,即使外部函数已经返回
- 每次函数调用都会创建新的执行上下文和闭包
- 注意内存泄漏:及时清理不需要的引用
this 核心要点
- this 的值取决于函数调用方式,不是定义方式
- 绑定优先级:new > 显式绑定 > 隐式绑定 > 默认绑定
- 箭头函数没有自己的 this,继承外层作用域的 this
- 严格模式下,独立调用函数的 this 是 undefined
面试技巧
- 理解原理,不要死记硬背
- 能手写实现核心方法(bind、call、apply)
- 熟悉常见应用场景(模块化、防抖节流、事件系统)
- 注意边界情况(严格模式、箭头函数、循环引用)
- 结合实际项目经验,说明如何在工作中应用
推荐学习资源
-
书籍
- 《你不知道的JavaScript(上卷)》
- 《JavaScript高级程序设计》
- 《JavaScript权威指南》
-
在线资源
- MDN Web Docs
- JavaScript.info
- 阮一峰的ES6教程
-
实践建议
- 在浏览器控制台中实际运行代码
- 使用 debugger 和断点调试
- 查看调用栈和作用域链
- 手写实现核心方法
记住:理解比记忆重要,实践比理论重要!