普通视图

发现新文章,点击刷新页面。
今天 — 2026年4月6日首页

JS炼化 :(this+new+构造函数)面向对象秘法包——从抽象到落地“焚诀”来了

作者 忆往wu前
2026年4月6日 13:16

读完秘法包不会用?焚诀来了

上一篇我们用**剑(this)、剑鞘(new)、剑法(构造函数)**的比喻,理解了三者的本质关系。 本篇目标:把零散的“剑意”合成完整的“焚诀”,从概念落地到运行逻辑,吃透面向对象底层根基

先简单回顾一下秘法包

  • this 是剑:操作对象、访问实例属性的核心工具
  • new 是剑鞘:保证 this 正确指向实例,启动构造函数
  • 构造函数是剑法:定义对象模板,批量创建同类对象

详情看上一篇秘法包基础解析,下面开始修炼焚诀

焚诀导读(焚诀修炼结构)

1. 铸剑基础 • this 常见场景(先认识)

2. 剑之本意• this 面向对象核心(重点练)

3. 剑鞘认主 • new 到底做了什么(核心)

4. 剑法定式 • 构造函数完整用法(核心)

5. 焚诀合一 • 剑·鞘·法三者联动(本篇灵魂)

焚诀修炼开始

第一重 · 铸剑基础(this基础场景)

定位:了解即可 / 说明:this的使用场景较多,先梳理非面向对象的基础场景,建立基础认知即可,无需死抠细节,后续核心内容才是修炼重点。

1. 全局作用域中的this
  • 规则:浏览器环境指向 window ,Node.js环境指向 global ,严格模式下为 undefined 
console.log(this); // 浏览器:window / Node:global
'use strict';
console.log(this); // 严格模式:undefined
2. 普通函数调用中的this
  • 规则:非严格模式默认指向全局对象,严格模式下为 undefined 
function fn() {
  console.log(this);
}
fn(); // 非严格模式:window / 严格模式:undefined
3. 箭头函数中的this(箭头函数很神,但是面向对象不合适)
  • 规则:不绑定自身this,继承外层作用域的this指向,这是与普通函数的核心区别
const obj = {
  name: '张三',
  sayName: () => {
    console.log(this.name); // 继承外层全局this,浏览器下指向window
  }// obj 的 {} 是对象字面量,不产生作用域
};
obj.sayName(); // 输出:undefined
4. call / apply / bind显式绑定this
  • 规则:可手动强制修改this指向,优先级高于默认绑定,提升代码灵活性
function fn() {
  console.log(this.name);
}
const obj = { name: '李四' };
fn.call(obj);   // 手动绑定,输出:李四
fn.apply(obj);  // 效果同call,输出:李四
const boundFn = fn.bind(obj);
boundFn();      // 永久绑定,输出:李四

 

第二重 · 剑之本意(this面向对象核心)

定位:重点掌握 说明:抛开基础场景,面向对象中this的指向逻辑极其清晰,只有两个核心场景,也是后续写代码最常用的部分,必须吃透。

1. 对象方法调用中的this
  • 规则:this指向调用该方法的所属对象,谁调用方法,this就归属谁
const obj = {
  name: '前端学习者',
  sayName() {
    console.log(this.name); // this指向调用方法的obj对象
  }
};
obj.sayName(); // 输出:前端学习者
2. 构造函数 / new调用中的this
  • 规则:this指向new创建的新实例对象,也就是剑认主的核心逻辑,this通过new找到自己的归属
function Person(name) {
  this.name = name; // this指向new创建的新实例,剑找到新主人
}
const p = new Person("张三");
console.log(p.name); // 输出:张三

 

this使用注意事项

  • 对象方法作为回调函数时,this指向易改变→用箭头函数、bind绑定或缓存 const self = this 修正指向。(这个可能有些绕,但是还是this的核心机制:this  的指向 不是由「函数定义的位置」决定的,而是由「函数被调用的方式」决定的,也就是谁调用了这个函数, this  就指向谁(箭头函数除外,它是继承外层作用域的 this) 一句话就是:谁拿起this这把剑来用,剑就听谁的指挥,就为谁服务。你也可以用 call / apply / bind 提前给剑认主,不管谁拿,剑只听你指定的那个人的话。
  • 嵌套函数中this易混乱→借助箭头函数继承外层this,或显式绑定明确指向
  • 严格模式下this默认指向undefined→规范绑定对象,避免无绑定调用

 

第三重 · 剑鞘认主(new完整知识点)

定位:核心重点 说明:new是连接构造函数与this的关键,更是剑认主的核心仪式,没有new,剑法无法施展,剑也没有归属,彻底吃透它的执行逻辑,才算掌握面向对象的核心钥匙。

1. new操作符的核心作用

核心:new就是一场认主仪式,先造主人,再让剑(this)认主,最后按剑法武装出一个完整实例。

本质:是创建构造函数的实例对象,稳定绑定this,执行构造函数逻辑,最终返回可使用的完整实例。

2. new执行的4件事(比喻+实际作用一一对应)
2.1. 造一个新主人(创建空对象)

比喻:打造一个全新的、无属性的主人肉身

实际:创建一个空的JavaScript对象 {} ,这个空对象就是未来的实例载体

2.2. 给主人归入门派(关联构造函数原型)

比喻:给新主人打上剑法的门派标记,继承门派里的公共功法

实际:将空对象的  proto 指向构造函数的 prototype 属性,为后续继承公共方法打下基础,无需重复创建方法

(早期只用  this  和  new  创建对象时,每个实例都会重复携带方法,内存占用大、不好管理。 于是就有了原型(prototype),相当于一个门派,把共用方法统一存放。 实例归入门派,就能共享所有公共方法,节省内存、方便维护。 这一步就是把空对象关联到构造函数原型,更详细的原型机制我们后面再学。)

2.3. 剑认主:把this绑定给新主人(最核心一步)

比喻:剑鞘扣紧,将无主的剑(this)正式交给新主人,完成认主仪式

实际:构造函数里的this原本没有固定归属,new在这一步强制将this绑定给刚创建的空对象,从此this只归属这个实例,完美对应“this通过new认主”

2.4. 按剑法武装主人,再把主人交出来(执行构造、返回实例)

比喻:按照剑法招式,给主人装备专属属性、武器,打造完整的修炼者

实际:运行构造函数内的代码,通过this给实例添加名字、年龄等属性和方法,若构造函数无返回对象,就将这个武装完成的实例返回出去

3. new代码示例+手动模拟实现
// 构造函数(剑法)
function Person(name, age) {
  this.name = name;
  this.age = age;
}
// 门派公共功法(原型方法)
Person.prototype.sayHi = function() {
  console.log(`Hi, I'm ${this.name}`);
};

// 执行认主仪式(new调用)
const p = new Person('张三', 18);
p.sayHi(); // 输出:Hi, I'm 张三

// 手动模拟认主仪式(模拟new)
function myNew(constructor, ...args) {
  // 1. 造新主人肉身
  const obj = {};
  // 2. 归入门派
  obj.__proto__ = constructor.prototype;
  // 3. 剑认主,绑定this并执行剑法
  const result = constructor.call(obj, ...args);
  // 4. 交出完整主人
  return result instanceof Object ? result : obj;
}

// 测试模拟new
const p2 = myNew(Person, '李四', 20);
p2.sayHi(); // 输出:Hi, I'm 李四
 

new使用注意事项

  • 构造函数必须配合new使用,省略new会让构造函数变成普通函数,this指向全局,无法完成认主→构造函数首字母大写,养成配合new调用的习惯
  • 构造函数中手动返回对象,会覆盖new创建的默认实例,认主失效→无特殊需求,不手动返回数据,依靠new自动返回实例

 

第四重 · 剑法定式(构造函数完整知识点)

定位:核心重点 说明:构造函数是剑法的定式,是批量打造主人的模板,掌握规范写法,才能高效创建同类型对象。

1. 构造函数的本质与作用

本质:ES5中JavaScript实现面向对象编程的基础形式,专门用于创建对象

作用:定义对象的通用属性和方法,作为固定剑法模板,通过new调用批量创建实例

2. 构造函数的编写规范
  • 命名规范:首字母大写,与普通函数区分,行业通用约定
  • 赋值方式:通过this为实例添加属性和方法,借助剑认主的逻辑完成属性绑定
  • 返回值:无需手动return,new会自动返回完成认主的完整实例
3. 构造函数基础代码示例
// 基础剑法(构造函数)
function Person(name, age) {
  this.name = name;
  this.age = age;
  this.sayHi = function() {
    console.log(`Hi, ${this.name}`);
  };
}

// 执行认主仪式,创建两个主人
const p1 = new Person('张三', 18);
const p2 = new Person('李四', 20);

// 主人使用自身剑法
p1.sayHi(); // 输出:Hi, 张三
p2.sayHi(); // 输出:Hi, 李四
4. 构造函数与普通函数核心区别
  • 调用方式:构造函数必须用new调用,普通函数直接调用即可
  • this指向:构造函数中this指向new创建的实例,普通函数中this指向全局或undefined
  • 命名规范:构造函数首字母大写,普通函数小写开头
  • 主要作用:构造函数批量创建对象,普通函数执行单一逻辑、返回运算结果
  • 返回值:构造函数自动返回实例,普通函数需手动return结果
5. 抛出思考(引向下一篇)

当前基础写法能实现功能,但存在一个问题:每new一个实例,构造函数内的方法就会重新创建一次,p1和p2的sayHi功能完全一致,却占用多份内存,实例越多浪费越严重?

有没有办法让所有实例共用一套公共方法,不重复创建?这就是下一篇要探究的原型&原型链,以及ES6 class语法糖的优化逻辑。

 

第五重 · 焚诀合一(剑·鞘·法三者联动)

定位:本篇灵魂 说明:三者绝非独立存在,而是一套完整的焚诀功法,协同工作才能发挥最大作用,这是JS面向对象的核心逻辑。

1. 三者协同执行流程

一句话总结执剑鞘(new),运剑法(构造函数),剑(this)认主归位,最终炼成完整主人(实例)。 用new启动构造函数,先创建空实例,再将this绑定到该实例,执行构造函数为实例赋值,最终返回完整可用的实例。

2. 完整联动核心代码
// 剑法(构造函数):定好修炼定式
function Person(name) {
  // 剑(this):等待认主,为主人赋值
  this.name = name;
  this.sayName = function() {
    console.log(this.name);
  };
}

// 剑鞘(new):执行认主仪式,启动剑法
const p = new Person('张三');

// 主人(实例):使用自身的剑施展功法
p.sayName(); // 输出:张三

3. 联动核心总结

  • 构造函数(剑法):定下对象的基础规则,是功法核心
  • this(剑):负责实例数据传递,是操作对象的工具
  • new(剑鞘):完成认主仪式,保障this指向稳定,是功法启动器 三者缺一不可,配合使用才构成JS面向对象的基础,后续所有进阶知识,都围绕这一核心逻辑展开。

到这一步焚诀就大成了,只差修炼和优化了

学习复盘心得小小分享

其实不管学什么,刚开始的路都是又乱又零散。 回想刚学 JS 的那段时间,基础知识点,作用域、函数、闭包、this 一个个学过来, 知识点全都拼不起来,代码也看不懂,感觉知识学历和没有学一样,越学越迷茫,越学越煎熬。

直到今天回头复盘、把整条线梳理清楚才明白: 那些曾经看似无关又折磨的内容,并不是孤立的知识点, 最后拼装成了 JavaScript 最底层、最核心的运行框架。

虽然现在依然离精通很远,未来的学习也不会轻松, 但至少方向彻底清晰了,心里也有底了。 地基打牢,后面的路再难,也知道该怎么走,该往哪儿走。 踏踏实实继续往前走,就很好。

学习分享,如果有理解不对的地方,欢迎大家指正,一起学习进步

昨天 — 2026年4月5日首页

JS炼化:闭包——未识其名已善用, 深知反被概念拘

作者 忆往wu前
2026年4月5日 12:31

扯下“闭包”大衣,发现函数你好装

ZZZ复盘学习JS基础,重新认识知识,总结掌握知识,炼化知识,输出知识。这是一篇学习反思,用自己的逻辑把知识体系搭建,用自己的话总结输出。

首先看闭包定义 :闭包是指一个函数以及其捆绑的周边环境状态(词法环境)的引用的组合。

(定义中既有捆绑也有引用,感觉很复杂拗口。但是,如果我们从函数特性出发,再结合 JavaScript 发展过程中有意思的历史背景与设计演变,看清闭包的意义是如何一步步变化的,从而真正理解:我们今天学习闭包,到底在学什么、它又为何重要。)

一句话戳破闭包:没有那么玄乎,就是函数的特性延展

一、从函数来看闭包

1.1 了解函数概念及特点

在JS里,函数是一段可以被重复执行的代码块,也是实现闭包最基础的单元。想要看懂闭包,必须先搞懂函数这几个核心特点:

<>1. 函数可以独立定义,也可以嵌套定义 我们不仅可以在全局写函数,还可以在一个函数内部再定义另一个函数,形成内外层函数结构。

<>2. 函数有自己的作用域 函数内部声明的变量,外部无法直接访问,形成了一层独立的“变量空间”,保证了数据的独立性。

<>3. 内部函数可以访问外部函数的变量 内层函数能够“向外”读取外层函数的变量和参数,而外层函数不能访问内层函数的变量,这是一条单向可见的规则。

<>4. 函数可以作为值被返回和传递 函数可以被  return  出去、赋值给变量、当作参数传递,让它可以在定义它的作用域之外执行。

有了上面这四个函数特性,我们就初步理解了函数的基本能力。接下来我们试着实现一个简单功能,不用刻意去写闭包,却会在无形中把闭包搭建出来。

1.2 应用函数特长形成闭包

给你一个任务,写一段代码实现最简单的计数功能

// 定义一个用来生成计数功能的函数
function makeCounter() {
  // 在这里定义一个变量,用来记录次数
  let count = 0

  // 返回一个新的函数
  return function () {
    // 每次执行这个函数,count 就加 1
    count++
    // 把最新的 count 返回出去
    return count
  }
}

// 调用 makeCounter,得到里面返回的那个函数
const counter = makeCounter()

// 每调用一次 counter,就会操作之前的 count
console.log(counter()) // 1
console.log(counter()) // 2
console.log(counter()) // 3

通过上面的代码,我们实现了计数器功能。

回头看闭包的定义: 闭包是函数与其周边词法环境(状态)的引用所捆绑形成的组合。

对照这段代码就会发现:

  • 我们有一个内部函数
  • 这个函数引用并绑定了外部函数的  count  变量(也就是它的词法环境/状态)
  • 即使外部  makeCounter  执行完毕,内部函数依然保留着对  count  的引用

所以这段代码虽然我们没有刻意强调闭包,但它已经完全符合闭包的定义,天然形成了标准的闭包结构。通过这个例子,只是看到了我写了一个稍微复杂一点的逻辑,就有了一个闭包的形成,但是对于它的产生和定义还是有疑惑?继续看完后面闭包的优缺点以及历史演变,我想这些问题就会迎刃而解了。

二、为什么要学闭包:三大优点

2.1 优点一

持久保存状态,变量不会丢失

:内部函数会一直记住外部函数的变量,函数执行完后变量依然保留,不会被重置。

还看刚刚写的计数器

function makeCounter() {
  let count = 0
  return function () {
    count++
    return count
  }
}

const counter = makeCounter()
console.log(counter()) // 1
console.log(counter()) // 2

这个功能用到了函数的这几个特性:

  • 用到了 函数可以嵌套,在外面函数里又写了一个函数。
  • 用到了 内部函数可以访问外部函数变量,所以内部函数能拿到并修改  count 。
  • 用到了 函数可以被返回,把内部函数传出去,在外面还能调用。

正是这几个特性一起作用, count  才会被一直记住,实现了状态持久保存。

2.2 优点二

实现数据私有,外部无法随意修改

:外部函数里的变量,外部作用域无法直接访问和修改,只能通过闭包函数操作,保证了数据安全。

看一个简单私有数据

function createPerson() {
  let name = "小明" // 外部无法直接改

  return {
    getName: function () {
      return name
    }
  }
}

const person = createPerson()
console.log(person.getName()) // 小明
// 无法直接修改 person.name,保证了数据私有

这个功能同样是依靠函数特性:

  • 函数有 自己独立的作用域,外部本来就不能直接访问里面的  name 。
  • 再加上 函数嵌套 和 内部函数能访问外部变量,外部只能通过返回的方法去读  name 。
  • 最后把方法 返回出去,外部才能使用。

最终效果就是: 外面可以调用,但不能直接改,只能通过内部函数操作,实现了数据私有。

2.3 优点三

这里我先卖个关子,第三个优点我先不直接写出来。相信看完后面的例子和历史演变,自然就明白了,同时也是希望大家能看下去,虽然是个人的学习分享,但是想让大家一起思考一下技术学习的意义。

三、闭包的历史与作用域演变

3.1 闭包是不是JS 特有?还是早就被定义

虽然可能大家都知道,但是对于我来说确实一开始以为闭包是 JavaScript 才有的东西,了解后才发现其实不然。 闭包早在 1964 年就被计算机科学家 Peter Landin(彼得·兰丁) 正式定义,最初源于 λ 演算 和函数式编程,是一个非常古老的通用概念。

最早的官方定义(原文直译):

闭包是由 环境部分 + 控制部分 组成的整体,用于记录表达式的执行上下文。

简单大白话翻译(你文章直接用):

闭包就是 一个函数 + 它所绑定的外部变量环境 的组合,把“开放”的函数变成“闭合”的完整整体。

它从一开始就不是 JS 特有,Python、Java、Kotlin、Rust 等语言全都支持。 我们今天在 JS 里学闭包,本质是在学一门通用的编程基础能力,而不只是某一门语言的小技巧。

3.2 var 的问题与闭包“被迫”使用形成的第三个优点

前面我卖了个关子,说闭包还有第三个关键作用。 其实它和早期 JavaScript 里  var  的设计问题紧密相关——正是因为 var 没有块级作用域,才让我们不得不靠闭包来解决问题,这也成了闭包一个很重要的历史价值。

首先看例子1:循环绑定事件(var 的经典坑)

// 用 var 会出问题
for (var i = 0; i < 3; i++) {
  setTimeout(function () {
    console.log(i) // 输出:3 3 3
  }, 1000)
}

因为  var  只有函数作用域,没有块级作用域, 循环里的  i  是同一个变量,等到定时器执行时, i  已经变成 3 了。

“被迫”用闭包来解决

当年没有  let ,大家只能被迫用闭包来锁住每一次的  i :

  
for (var i = 0; i < 3; i++) {
  // 用立即执行函数制造一个函数作用域,形成闭包
  (function (currentI) {
    setTimeout(function () {
      console.log(currentI) // 输出:0 1 2
    }, 1000)
  })(i)
}

这里就是闭包的第三个优点:

  • 在没有块级作用域的年代,闭包帮我们模拟出独立的作用域
  • 让每个循环都能保存自己的变量,互不干扰

被迫这个词用的不是很恰当,只能说闭包很厉害能够成功解决这个痛点。但也正是因为这些痛点,后来 ES6 才推出了  let  /  const ,让我们不用再被迫写复杂的闭包。但也正因为这段历史,我们才更明白:闭包的意义,不只是技巧,更是语言发展过程中解决问题的关键方案。

3.3 let / const 带来的技术解放

后来 ES6 带来了  let  和  const ,可以说给前端开发者带来了一次技术解放。

它们拥有真正的块级作用域,在  for  循环、 if 、 {}  里都会生成独立的变量, 不再像  var  那样全局共用一个变量。

我们再看刚才的循环问题:直接用  let ,代码变简单了,再也不需要靠闭包来强行造作用域。

for (let i = 0; i < 3; i++) {
  setTimeout(function () {
    console.log(i) // 输出:0 1 2
  }, 1000)
}

这就意味着:

  • 以前闭包第三条优点是被迫使用,用来填  var  的坑
  • 现在  let  /  const  把坑填上了,闭包终于回归它本来的样子: 用来做状态保存、数据私有这些真正有价值的场景

所以我们回过头再看闭包,它的意义也变了: 不再是“不得不写的 workaround”, 而是主动选择、用来写出更优雅、更安全代码的强大特性。

3.4补充闭包使用带来的缺点

闭包虽然好用,但也有一个问题:它会让变量一直留在内存里,不容易被释放,用多了可能让程序变卡。

原因也很简单: 内部函数一直引用着外部函数的变量,所以就算外部函数执行完了,这些变量也不会被垃圾回收,会一直占着内存。如果不注意,就可能造成内存占用过高,甚至内存泄漏。

比如

function outer() {
  let data = new Array(10000).fill('我占内存')

  return function inner() {
    console.log(data.length)
  }
}

const fn = outer()
// 只要 fn 还在,data 就一直占内存

怎么解决?

不用闭包的时候,把引用设为 null,浏览器就会回收内存:

fn = null

四、总结:闭包是什么?我理解的闭包

4.1再看闭包是什么?

依我来看,闭包其实并不是 JavaScript 里刻意创造的复杂语法,而是我们用函数嵌套、实现功能时自然而然就会产生的一种形式。本质上,就是通过函数嵌套,让内部函数可以访问并使用外部函数的变量和参数,从而延长了这些变量的生命周期,同时让不同调用之间的数据相互独立、互不干扰,也实现了变量的私有化。

只要我们用到了「函数能访问外部作用域变量」这个特性,并且用它来实现对应的功能,闭包就已经在默默工作了。理解到这一层就会发现,闭包并没有那么神秘难懂,它只是 JavaScript 函数特性的自然延伸,是写好代码、做复杂逻辑时绕不开、也必然会用到的东西。

以上就是我对闭包的学习心路历程,逻辑梳理和炼化后的输出

如有理解不当,欢迎大家指正,一起学习进步

❌
❌