知识点深化 · 前端 · JavaScript
闭包与原型链:作用域链、this、原型继承
闭包和原型链是 JS 两大灵魂:闭包让函数"记住"它出生时的作用域,原型链让对象沿 __proto__ 找属性。再加上 this 的动态绑定,构成了 JS 灵活又易错的本质。这一页把它们讲透。
① 小白第一课怎么学(4 步走,约 60 分钟)
别急着背代码,先按这四步建立直觉:
1看图建立直觉(10 分钟)
读②③:函数记住外面变量。
2记规则(15 分钟)
读④:作用域链/原型链/this。
3手推输出(20 分钟)
精读⑤。
4刷题纠错(15 分钟)
做⑦⑩,错题回⑥。
本课小目标学完你要能:① 说清闭包是什么、为什么能记变量;② 画出原型链查找;③ 判断 this 指向。
② 一图看懂:闭包与原型链全地图
读法:中心是 JS,左闭包右原型链,下是 this 与易错。
③ 本质直觉:闭包是函数带着它出生时的"记忆"
闭包:函数 A 里定义了函数 B,B 用到了 A 的变量。即使 A 已经执行完返回,B 仍然"记得"并能访问 A 的变量。这就是闭包——函数 + 它出生时的词法作用域。
原型链:每个对象有个 __proto__ 指向它的原型对象。访问 obj.x 时,obj 自身没有 x,就沿 __proto__ 往上找,直到 null。这就是原型继承。
this:this 不是定义时定的,是调用时定的。谁调用就指向谁:obj.fn() 里 this=obj;直接调 fn() 里 this=undefined(严格模式)/全局。
经典考题:var 循环for(var i=0;i<3;i++){ setTimeout(()=>console.log(i)) } 输出 3 个 3,因为三个闭包共享同一个 i。用 let 块级作用域就分别输出 0/1/2。
④ 完整体系与对比表
闭包/原型/this 对照
| 概念 | 本质 | 关键点 |
| 闭包 | 函数+词法作用域 | 外层跑完内层仍访问 |
| 原型链 | 对象 __proto__ 链 | 找属性沿链向上 |
| this | 调用时绑定 | 谁调用指谁 |
this 绑定规则(优先级从高到低)
① new 调用 → this 是新对象;② call/apply/bind → 显式指定;③ 对象方法调用 obj.fn() → this=obj;④ 单独调用 fn() → undefined/全局。箭头函数没有自己的 this,继承外层。
原型链终点obj.__proto__.__proto__... 最终指向 null;Object.prototype 是所有对象的原型终点
⑤ 用法场景与典型例题
例1(闭包计数)function counter(){let n=0; return ()=>++n;} 调三次结果?
闭包保持 n。
每次返回的箭头函数共享同一个 n:1、2、3。答案:1 2 3。n 被闭包保护,外部改不了——数据私有。
例2(原型链)obj={a:1},访问 obj.toString() 走哪?
自身没有 toString 沿原型链找。
obj 没有 toString → obj.__proto__(Object.prototype) 有 → 找到。答案:从 Object.prototype 取到。
例3(this)const obj={name:'A', fn(){console.log(this.name)}}; const f=obj.fn; f(); 输出?
f 单独调用 this 不指 obj。
f() 是单独调用,this 在严格模式下是 undefined,非严格下是全局。输出:undefined(严格模式),不是 A。
做题心法闭包题先问"函数出生在哪";原型链题画 __proto__;this 题看"怎么调用的"。
⑥ 高频错误诊断(4 条)
错误1:以为 this 在定义时定this 是调用时定的,同一函数不同调用方式 this 不同。
错误2:var 循环里 setTimeout 想得到 0/1/2var 没有块级作用域,三个回调共享同一 i。改 let。
错误3:闭包会导致内存泄漏闭包持有外层变量,不用时应置 null,避免不必要常驻。
错误4:以为箭头函数有自己的 this箭头函数没有 this,继承外层作用域的 this,不能当构造函数。
⑦ 考点真题演练(4 题)
考点分布
| 考法 | 出题形式 | 应对 |
| 闭包 | 外层跑完内层还访问 | 函数+作用域 |
| 原型链 | 找属性沿 __proto__ | 终点 null |
| this | 调用时绑定 | 谁调用指谁 |
| var 循环 | setTimeout 输出 | let 解决 |
真题基础1. 闭包的本质是?
真题中档2. 对象访问自身不存在的属性时会?
真题中档3. 关于 this,正确的是?
真题拔高4. for(var i=0;i<3;i++) setTimeout(()=>console.log(i)) 输出?
⑧ 必背知识点卡
闭包:函数记住外层变量 数据私有
作用域链:嵌套查找变量
原型链:__proto__ 向上找 终点 null
this:调用时定 谁调用指谁
箭头函数:无自己 this,继承外层 不能 new
var 循环:共享同一个 i 改 let
优先级:new>call/apply>对象调用>单独
⑨ 应用输出:用闭包实现一个私有计数器
场景:想做一个外部不能直接修改的计数器,只能通过 inc/get 操作。
① 用闭包:函数里 let count=0,返回 {inc, get} 两个方法。
② 封装:外部拿不到 count 变量,只能通过 inc() 加、get() 读。
③ 效果:const c=makeCounter(); c.inc(); c.inc(); c.get()=2。
④ 意义:这就是 JS 模块模式,实现数据私有(没 class 也能封装)。
⑤ 注意:闭包会持有 count 不释放,不用时删除引用。
口述思路合上书说:"闭包让函数带着外层变量,用来做私有状态和工厂函数。"
⑩ 分层练习(基础 + 中档 + 拔高)
▍基础 6 题
基础1闭包是什么?
函数+词法作用域,记住外层变量。
基础4箭头函数有自己的 this 吗?
没有,继承外层。
基础5对象方法 obj.fn() 中 this 指谁?
obj。
基础6var 循环 setTimeout 输出什么?
都输出最后一个值。
▍中档 6 题
中档7闭包能用来做什么?
数据私有、函数工厂、模块模式。
中档8obj.__proto__ 指向谁?
创建它的构造函数的 prototype。
中档9call/apply/bind 作用?
显式指定 this。
中档10为什么用 let 替代 var 循环?
let 有块级作用域,每次迭代独立绑定。
中档11Function.prototype 是谁的原型?
所有函数的原型。
中档12单独调用 fn() 严格模式 this?
undefined。
▍拔高 6 题
拔高13new 一个对象做了什么?
创建空对象→this 指向它→执行构造→返回。
拔高14原型链和 class 关系?
class 语法糖,底层仍是原型链继承。
拔高15闭包为什么可能内存泄漏?
闭包持有外层变量不释放,即使外层函数已返回。
拔高16如何打断闭包引用?
把闭包函数置 null,让 GC 回收。
拔高17Object.create 作用?
以指定对象为原型创建新对象。
拔高18作用域链和原型链区别?
作用域链找变量(词法),原型链找对象属性(运行)。
⑪ 记忆口诀 + 7 天复习计划
三句口诀① 闭包带记忆,原型链向上找。② this 看调用不看定义,箭头函数继承外层。③ var 循环共享 i,let 块级各管各。
| 天 | 任务 | 自检 |
| 第 1 天 | 读②③④,画闭包作用域 | 能说清 |
| 第 2 天 | 背规则 + 基础 1-6 | this 规则对 |
| 第 3 天 | 做中档 7-12,手推原型链 | 查找路径对 |
| 第 4 天 | 做拔高 13-18,讲 new 过程 | 能讲清 |
| 第 5 天 | 做⑦真题 4 题 | 限时每题 2 分钟 |
| 第 6-7 天 | 合上书默写 this 优先级 | 不看资料全默对 |
← 返回软件技术总览