把 JavaScript 原型讲透:从 `[[Prototype]]`、`prototype` 到 `constructor` 的完整心智模型

目录

  • 引言:为什么原型是前端工程师绕不过去的一课
  • 一、先建立统一认知:对象原型到底是什么
  • 二、prototype[[Prototype]] 不是一回事
  • 三、从 new 和内存视角理解实例、构造函数与原型
  • 四、函数原型上的高频知识点:共享属性与 constructor
  • 五、重写原型对象时,为什么最容易踩坑
  • 六、创建对象的推荐姿势:实例数据放 this,共享方法放 prototype
  • 实战建议
  • 总结:关键结论与团队落地建议

引言:为什么原型是前端工程师绕不过去的一课

很多团队在日常开发里已经很少手写"构造函数 + 原型"这套模式了,更多时候我们写的是 class、对象字面量、组合式函数,甚至直接用框架帮我们屏蔽底层细节。于是原型这件事,常常只在面试里出现,看起来像"八股",但一旦线上排查问题,它又会突然变得非常真实:

  • 为什么两个实例的方法地址相同?
  • 为什么给对象赋值后没有覆盖到原型上的值?
  • 为什么重写 prototype 之后,constructor 看起来"不对了"?
  • 为什么控制台里 __proto__ 看起来什么都有,但代码里又不建议用它?
  • 为什么 class 最终仍然离不开原型链?

如果对这些问题没有统一心智模型,工程上就会出现两类常见问题:一类是"会用但讲不清",另一类是"改得动但不敢改"。而原型真正的价值,不在于背定义,而在于帮助我们理解 JavaScript 的对象系统、继承机制、方法共享、内存结构,以及很多框架设计背后的语言基础。

这篇文章的目标很明确:不是把概念堆给你,而是把"对象、函数、构造函数、实例、原型、构造器"这几者之间的关系,一次性串起来。读完之后,你至少应该能建立起一个稳定的判断标准:什么应该挂在实例上,什么应该挂在原型上,什么时候可以重写原型,重写后又要补什么。


一、先建立统一认知:对象原型到底是什么

在 JavaScript 中,几乎每个对象都带着一个隐藏的内部链接,这个内部链接在规范里叫 [[Prototype]]。它会指向另一个对象,而这个"被指向的对象",就是当前对象的原型对象。

你可以把它理解成:当前对象在找不到某个属性时,下一站该去哪里找。

1. 原型最核心的作用:兜底查找

当我们访问一个对象属性时,会触发内部的 [[Get]] 过程;当我们给对象设置属性时,会触发 [[Set]] 过程。

操作 触发时机 原型参与方式
[[Get]] 读取属性时 先查对象自身,找不到再沿原型向上查
[[Set]] 设置属性时 优先看当前对象及属性描述符,再决定是否在当前对象创建新属性

下面这个例子最能说明问题:

js 复制代码
function A() {}
A.prototype.x = 10

const obj = new A()

console.log(obj.x) // 10,obj 自身没有 x,沿原型找到 A.prototype.x

obj.x = 20
console.log(obj.x) // 20,此时 obj 自身已经有了 x

这里发生了两件事:

  1. 第一次读 obj.x,对象自身没有,沿着原型找到 A.prototype.x
  2. 第二次写 obj.x = 20,是在实例自身新增了一个同名属性,而不是改掉原型上的 x

这也是很多人第一次理解"共享"和"遮蔽(shadowing)"的关键入口。

2. 对象字面量创建出来的对象,也有原型

很多人以为只有通过构造函数创建出来的对象才有原型,这其实不对。只要是普通对象,通常都有 [[Prototype]]

js 复制代码
const obj = { name: 'XiaoWu' }
const foo = {}

console.log(obj.__proto__)
console.log(foo.__proto__)

图:隐式原型在浏览器与终端中的表现

控制台里你看到的结果,和真实运行时的内部结构并不完全等价。浏览器控制台为了方便调试,会把一些继承来的内容也展开给你看;Node 的输出则更接近"对象本身 + 原型关系"的表现。

从理解层面,可以先把它抽象成下面这样:

js 复制代码
const obj = { name: 'XiaoWu', __proto__: {} }
const foo = { __proto__: {} }

当然,真正的 [[Prototype]] 不是你字面量里真的写出来的这个字段,而是引擎内部维护的链接关系。

3. __proto__[[Prototype]]Object.getPrototypeOf 到底什么关系?

这是高频混淆点,必须一次说清:

  • [[Prototype]]:规范层面的内部槽,真实存在,但你不能直接写代码访问这个名字
  • __proto__:历史遗留的访问器属性,调试方便,但不推荐作为正式代码依赖
  • Object.getPrototypeOf(obj):标准 API,推荐在正式代码里使用
js 复制代码
const obj = { name: '小吴' }

console.log(Object.getPrototypeOf(obj))

调试场景里,obj.__proto__ 确实更顺手;工程代码里,优先使用 Object.getPrototypeOf(obj)。原因很简单:

  • 语义标准、跨环境更稳定
  • 可维护性更高
  • 降低"我在操作语言底层 hack 口子"的心智负担

顺手补一句:今天的引擎几乎都支持 __proto__,但"能用"不等于"应该作为主路径使用"。

本章小结

  • 每个对象的核心原型关系,体现在内部的 [[Prototype]]
  • 读取属性找不到时,会沿原型继续查找
  • 给实例赋值,不等于改原型;很多时候只是"在实例自身新增同名属性"
  • __proto__ 更适合调试,正式代码优先 Object.getPrototypeOf
  • 理解原型,本质是在理解 JavaScript 如何做"属性查找"和"能力复用"

二、prototype[[Prototype]] 不是一回事

聊原型最容易踩的第一个坑,就是把 prototype[[Prototype]] 混为一谈。它们名字很像,但角色完全不同。

1. prototype 是函数身上的属性,不是所有对象都有

先看例子:

js 复制代码
function foo() {}

const obj = {}

console.log(foo.prototype) // 普通函数默认有 prototype
console.log(obj.prototype) // undefined,普通对象没有 prototype

这里有一个非常重要的判断标准:

  • prototype 是函数对象上的一个属性,主要给"作为构造函数使用"时服务
  • [[Prototype]] 是对象内部的原型链接,普通对象、函数对象都可能有

也就是说:

  • 函数是对象,所以函数也有 [[Prototype]]
  • 但普通对象不是函数,所以普通对象没有 prototype

2. 这两个概念各自负责什么?

可以直接用一句最工程化的话来理解:

  • prototype定义将来由这个构造函数创建出来的实例,应该共享什么
  • [[Prototype]]当前这个对象,实际沿哪条链路去查找属性

它们的职责并不重复:

  1. 归属不同
    prototype 属于函数;[[Prototype]] 属于对象

  2. 作用不同
    prototype 用来定义共享能力;[[Prototype]] 用来参与查找路径

  3. 时机不同
    prototype 通常在定义阶段配置;[[Prototype]] 通常在对象创建时被确定

3. 纠正一个特别容易出现的误区

很多人在刚学到这里时,会误以为:

"函数自己的隐式原型会指向它自己的显式原型"

这是错误的。

准确关系应该是:

  • foo.prototype:给将来 new foo() 出来的实例用
  • Object.getPrototypeOf(foo):函数对象 foo 自己的原型,通常是 Function.prototype

也就是说:

js 复制代码
function foo() {}

console.log(Object.getPrototypeOf(foo) === Function.prototype) // true

而实例和构造函数之间的正确关系,是下一节的重点:

js 复制代码
const f1 = new foo()
console.log(Object.getPrototypeOf(f1) === foo.prototype) // true

4. new 到底做了什么?

理解原型,绕不开 new。把它拆开看,会清晰很多。

new Foo() 大致会做下面几步:

  1. 创建一个全新的空对象
  2. 把这个对象的 [[Prototype]] 指向 Foo.prototype
  3. 用这个新对象作为 this 执行构造函数
  4. 如果构造函数没有显式返回对象,就返回这个新对象

所以,实例为什么能访问构造函数原型上的方法?答案就在第 2 步。

js 复制代码
function Foo() {}

const f1 = new Foo()
const f2 = new Foo()

console.log(Object.getPrototypeOf(f1) === Foo.prototype) // true
console.log(Object.getPrototypeOf(f2) === Foo.prototype) // true

这就是为什么不同实例可以"共享一套方法定义",却又拥有各自不同的数据。

本章小结

  • prototype[[Prototype]] 名字相似,但职责完全不同
  • 普通对象没有 prototype,函数通常有
  • 实例的 [[Prototype]] 会在 new 时指向构造函数的 prototype
  • 函数对象自己的原型通常是 Function.prototype,不是它自己的 prototype
  • 只要把"定义共享能力"和"参与属性查找"分开理解,很多混乱都会消失

三、从 new 和内存视角理解实例、构造函数与原型

如果只停留在语法层,原型会越学越抽象。真正把它看懂,最有效的方式是换成"引用关系"和"内存指向"的视角。

1. Person、实例对象和原型对象之间是什么关系?

先看一个最简单的例子:

js 复制代码
function Person() {}

console.log(Person.prototype)

很多人看到这里会困惑:Person 是函数,Person.prototype 是对象,那实例和它们之间是怎么连起来的?

关键结论只有一个:

同一个构造函数创建出来的实例,默认会共享同一个原型对象。

这也是后面方法复用的基础。

图:从控制台结果理解构造函数与原型对象的关系

这张图适合帮助我们建立第一个直觉:构造函数不是孤立存在的,它天然带着一个 prototype 对象。

2. 为什么 p1p2 可以访问同一套原型内容?

js 复制代码
function Person() {}

const p1 = new Person()
const p2 = new Person()

这里最值得记住的不是"创建了两个实例",而是"这两个实例的原型指向同一个地方"。

js 复制代码
console.log(Object.getPrototypeOf(p1) === Object.getPrototypeOf(p2)) // true
console.log(Object.getPrototypeOf(p1) === Person.prototype) // true
console.log(Object.getPrototypeOf(p2) === Person.prototype) // true

图:p1p2 实例对象共享同一个原型对象

这就解释了一个很重要的工程现象:

  • Person.prototype.xxx
  • 实际上影响的是所有还指向这个原型对象的实例
js 复制代码
function Person() {}

const p1 = new Person()
const p2 = new Person()

console.log(Object.getPrototypeOf(p1) === Person.prototype) // true
console.log(Object.getPrototypeOf(p2) === Person.prototype) // true

图:通过相等比较验证实例原型是否一致

3. 一个很适合面试和排错的思考题:p1.name 到底能从哪里拿到?

假设 p1 自身没有 name,那 p1.name 还能不能拿到值?

答案是能,而且方式不止一种。本质上,这些方式最终都在改同一个共享原型对象。

js 复制代码
function Person() {}

const p1 = new Person()
const p2 = new Person()

Object.getPrototypeOf(p1).name = '小吴'
console.log(p1.name) // 小吴

Person.prototype.name = 'XiaoWu'
console.log(p1.name) // XiaoWu

Object.getPrototypeOf(p2).name = 'why'
console.log(p1.name) // why

为什么第三种改 p2 的原型,也会影响 p1

因为:

js 复制代码
Object.getPrototypeOf(p1) === Object.getPrototypeOf(p2) === Person.prototype

它们最终都指向同一个共享对象。

把这个关系进一步抽象成"内存地址",就更容易理解了。你可以把上面的变化想成:

js 复制代码
// 假设共享原型对象就像一个地址 0x100
0x100.name = '小吴'
console.log(0x100.name) // 小吴

0x100.name = 'XiaoWu'
console.log(0x100.name) // XiaoWu

0x100.name = 'why'
console.log(0x100.name) // why

图:从"内存指向"视角理解实例、构造函数与原型的关系

这个视角非常关键,因为后面理解"共享方法""重写原型""原型链继承"时,本质都是在理解引用关系,而不是背结论。

本章小结

  • 同一个构造函数创建的实例,默认共享同一个原型对象
  • Object.getPrototypeOf(p1) === Person.prototype 是原型学习中的第一条黄金验证公式
  • 改共享原型,相当于影响所有还连接到它的实例
  • 原型问题一旦抽象成"引用地址",很多现象都会变得很好解释
  • 面试里问"为什么改 p2 的原型会影响 p1",本质在考你是否理解"共享引用"

四、函数原型上的高频知识点:共享属性与 constructor

前面讲的是"为什么原型存在",这一节讲"原型上通常放什么"。

1. 原型上放的是"共享能力"

在 JavaScript 中,函数的 prototype 对象,本质上就是给实例共享用的。

js 复制代码
function Person() {}

Person.prototype.name = 'why'
Person.prototype.age = 18

const p1 = new Person()
const p2 = new Person()

console.log(p1.name, p2.age) // why 18

这意味着:

  • nameage 不在 p1p2 自身上
  • 它们来自共享原型
  • 所有实例都能访问,但并不各自拷贝一份

图:往原型上添加共享属性后的结构示意

这里顺便给一个工程建议:

如果一个值会因实例不同而不同,就不要放原型上;如果一段行为对所有实例都一致,就优先考虑放原型上。

2. constructor 是什么?为什么平时看不见?

默认情况下,函数的原型对象上会有一个 constructor 属性,它指回构造函数本身。

js 复制代码
function Foo() {}

console.log(Foo.prototype.constructor === Foo) // true

但很多同学在控制台直接打印 Foo.prototype 时,看见的是个空对象,于是误以为它什么都没有。其实不是没有,而是:

constructor 默认是不可枚举的。

所以直接打印、遍历时看不明显,但你可以通过属性描述符把它"看见"。

js 复制代码
function Foo() {}

console.log(Foo.prototype) // 看起来像 {}
console.log(Object.getOwnPropertyDescriptors(Foo.prototype))

图:在 Node 中查看 constructor 的真实属性描述符

3. constructor 存在的意义是什么?

constructor 的工程意义,不是"让你炫技",而是帮我们保留一条从原型对象追溯回构造函数的路径。

js 复制代码
function Foo() {}

console.log(Foo.prototype.constructor.name) // Foo

这相当于让原型系统形成了一个闭环:

  • 实例通过 [[Prototype]] 指向原型对象
  • 原型对象通过 constructor 指回构造函数

这条关系能帮助我们做理解、调试和某些类型判断。但也要注意一点:

constructor 可以被改写,所以它不是绝对可靠的类型判断依据。

在工程里,如果你想做类型判断:

  • 优先考虑 instanceof
  • 或者基于更稳定的品牌判断方式
  • 不要把 constructor 当成唯一真理

4. 一个有意思但不建议滥用的闭环验证

js 复制代码
function Foo() {}

console.log(
  Foo.prototype.constructor.prototype.constructor.prototype.constructor.name
) // Foo

这段代码能跑通,不是因为 JavaScript 神秘,而是因为这条引用关系本来就存在。

不过知道就好,别把它写进业务代码里。

本章小结

  • 原型对象最适合承载共享属性和共享方法
  • constructor 默认存在于函数原型对象上,只是不可枚举
  • Foo.prototype.constructor === Foo 是默认成立的
  • constructor 适合理解原型结构,但不适合作为唯一类型判断依据
  • 共享逻辑放原型,是 JavaScript 节省内存、复用能力的关键设计

五、重写原型对象时,为什么最容易踩坑

前面讲的是"给现有原型追加内容",这一节讲的是另一种更激进的操作:直接重写整个原型对象。

1. 什么叫"重写原型对象"?

不是这样:

js 复制代码
Person.prototype.name = '小吴'
Person.prototype.age = 20

而是这样:

js 复制代码
function Person() {}

Person.prototype = {
  name: '小吴',
  age: 20,
  learn() {
    console.log(this.name + '在学习')
  }
}

这种写法在属性比较多时很常见,结构也更集中。

先看原始的"构造函数与原型相互关联"视角:

图:默认原型对象与构造函数之间的关联

当你执行 Person.prototype = { ... } 时,本质上是让 Person.prototype 指向了一个全新的对象

图:重写原型后,构造函数指向了新的原型对象

继续把内容填进去之后,新的结构才完整:

图:新的原型对象被填充内容后的状态

2. 这里最容易掉的坑:constructor 丢了

看下面的代码:

js 复制代码
function Person() {}

Person.prototype = {
  name: '小吴',
  age: 18,
  height: 1.88
}

const f1 = new Person()
console.log(f1.name + '今年' + f1.age) // 小吴今年18

功能看起来没问题,但有一个隐藏变化:

js 复制代码
console.log(Person.prototype.constructor === Person) // false
console.log(Person.prototype.constructor === Object) // true

原因并不复杂:

  • 默认创建函数时,引擎会为它生成一个带 constructor 的原型对象
  • 但你手动赋值的新对象只是一个普通对象字面量
  • 它自己的 constructor 并不是 Person
  • 查找时会沿着这个新对象的原型往上找到 Object.prototype.constructor

图:重写原型后,实例仍能访问属性,但 constructor 关系已发生变化

3. 正确做法:手动把 constructor 补回去

最常见的补法如下:

js 复制代码
function Foo() {}

Foo.prototype = {
  name: '小吴',
  age: 18,
  height: 1.88
}

Object.defineProperty(Foo.prototype, 'constructor', {
  enumerable: false,
  writable: true,
  configurable: true,
  value: Foo
})

const f1 = new Foo()
console.log(f1.name + '今年' + f1.age)

为什么不用下面这种简单写法?

js 复制代码
Foo.prototype = {
  constructor: Foo,
  name: '小吴'
}

因为这样写出来的 constructor 默认是可枚举的,而原生默认行为里,这个属性应该是不可枚举的。

如果你想尽量保持和原生行为一致,Object.defineProperty 更合适。

图:补回 constructor 后,构造函数与新原型对象重新闭合

4. 再补一个容易忽略的边界条件

很多人以为"重写原型后,旧原型会立即消失",这其实不严谨。

更准确的说法是:

  • 如果旧原型对象已经没有任何可达引用,后续才可能被垃圾回收
  • 如果已有实例还指向旧原型,那旧原型仍然活着

例如:

js 复制代码
function Person() {}

const oldP = new Person()

Person.prototype = {
  sayHello() {
    console.log('hello')
  }
}

const newP = new Person()

console.log(Object.getPrototypeOf(oldP) === Object.getPrototypeOf(newP)) // false

这在排查"为什么新老实例行为不一致"时非常关键。

本章小结

  • prototype 追加属性,和直接重写整个 prototype,是两种不同操作
  • 重写原型后,默认的 constructor 关联会丢失
  • 推荐用 Object.definePropertyconstructor 补回去
  • 重写原型不会自动"更新"旧实例的原型指向
  • 原型对象是否回收,取决于是否还有引用,而不是"看起来不用了"

六、创建对象的推荐姿势:实例数据放 this,共享方法放 prototype

这是原型章节里最重要的工程落点。

1. 一个典型错误:把实例数据塞进共享原型

下面这段代码看似"想省事",实则会制造共享数据污染:

js 复制代码
function Person(name, age, sex, address) {
  Person.prototype.name = name
  Person.prototype.age = age
  Person.prototype.sex = sex
  Person.prototype.address = address
}

const p1 = new Person('小吴', 18, '男', '福建')
console.log(p1.name) // 小吴

const p2 = new Person('why', 35, '男', '广州')
console.log(p1.name) // why

为什么 p1.name 最后变成了 why

因为你不是把数据放进 p1p2 自身,而是放进了它们共享的 Person.prototype

这等于让所有实例共用一份可变数据,自然后创建的实例会覆盖前一个实例的结果。

这类问题在工程里很致命,因为它会造成一种非常糟糕的现象:对象看起来是独立的,实际状态却是串联的。

2. 正确做法:实例数据归实例,共享方法归原型

js 复制代码
function Person(name, age, sex, address) {
  this.name = name
  this.age = age
  this.sex = sex
  this.address = address
}

Person.prototype.eating = function () {
  console.log(this.name + '今天吃烤地瓜了')
}

Person.prototype.running = function () {
  console.log(this.name + '今天跑了五公里')
}

const p1 = new Person('小吴', 18, '男', '福建')
const p2 = new Person('why', 35, '男', '广州')

console.log(p1.name) // 小吴
console.log(p2.name) // why
console.log(p1.eating === p2.eating) // true

这套写法有三个直接收益:

  1. 实例数据隔离

    每个对象维护自己的状态,不会相互覆盖

  2. 方法共享

    所有实例共用同一个方法引用,减少重复创建

  3. 结构清晰

    一眼能分清"对象自己的数据"和"对象共享的行为"

3. 为什么不要把原型方法写进构造函数内部?

有些代码会这么写:

js 复制代码
function Person(name) {
  this.name = name
  this.eating = function () {
    console.log(this.name + '在吃东西')
  }
}

它不是不能运行,而是有明显代价:每次 new Person() 都会重新创建一个新的函数对象。

如果实例特别多,这就是实打实的重复内存占用和不必要的函数分配。

更合理的方式还是:

js 复制代码
function Person(name) {
  this.name = name
}

Person.prototype.eating = function () {
  console.log(this.name + '在吃东西')
}

4. 这套模式和 class 有什么关系?

如果你已经在写 class,那更应该理解这部分。因为:

js 复制代码
class Person {
  constructor(name) {
    this.name = name
  }

  eating() {
    console.log(this.name + '在吃东西')
  }
}

本质上仍然是:

  • constructor 里放实例数据
  • 方法定义在原型上

class 改变的是写法,不是底层原理。

本章小结

  • 实例间不同的数据,放 this
  • 所有实例共享的行为,放 prototype
  • 不要把可变实例数据放到共享原型上
  • 不要在构造函数里重复创建所有实例都相同的方法
  • 理解这条原则后,再看 class 会非常顺手

实战建议

1. 代码评审时重点看这几件事

  • 是否把实例级数据错误地挂到了原型上
  • 是否把共享方法错误地定义在构造函数内部
  • 是否在重写 prototype 后忘了补 constructor
  • 是否在正式代码里依赖 __proto__ 而不是标准 API
  • 是否出现"旧实例"和"新实例"指向不同原型的潜在风险

2. 调试原型问题时,建议这样验证

js 复制代码
console.log(Object.getPrototypeOf(obj))
console.log(Object.getPrototypeOf(obj) === Foo.prototype)
console.log(obj.hasOwnProperty('xxx'))
console.log('xxx' in obj)
console.log(Object.getOwnPropertyDescriptors(Foo.prototype))

这一组排查动作,足够覆盖大多数原型相关问题:

  • 属性是自己的,还是继承来的
  • 当前实例到底连到哪个原型对象
  • 原型对象上的属性描述符是否符合预期
  • constructor 是否被改坏了

3. 团队内可以落地的约束

  • 约定:实例状态一律放 this / 类字段
  • 约定:共享方法统一放原型 / 类方法
  • 约定:禁止在业务代码里直接依赖 __proto__
  • 约定:重写 prototype 必须同步恢复 constructor
  • 约定:在 Code Review Checklist 中加入"原型污染"和"共享引用"检查项

4. 性能与可维护性的权衡

  • 小量对象场景下,差异可能不明显
  • 大量实例场景下,方法是否共享会带来真实内存差异
  • 动态改原型虽然灵活,但会明显增加维护成本
  • 原型越"魔法化",后续新人接手成本越高

总结:关键结论与团队落地建议

JavaScript 的原型并不神秘,它本质上解决的是两个问题:

  1. 对象找不到属性时,去哪里继续找
  2. 多个实例如何共享同一套行为定义

把这两件事想清楚,原型就不再是零散知识点,而是一套完整的对象模型。

最后用几条结论收尾:

  • [[Prototype]] 是对象的查找链路,prototype 是构造函数为实例准备的共享模板
  • new 的关键一步,是把实例的 [[Prototype]] 指向构造函数的 prototype
  • constructor 默认存在于原型对象上,只是不可枚举
  • 重写 prototype 会改变后续实例的继承来源,同时可能破坏 constructor
  • 最稳妥的工程实践是:实例数据放 this,共享方法放 prototype

如果要在团队内部继续往下沉淀,建议下一步把下面几个主题串起来学习:

  • 原型链完整查找过程
  • instanceof 的底层判断逻辑
  • Object.create 与显式指定原型
  • 组合继承、寄生组合继承
  • class extends 背后的原型链本质

当你把这些知识连起来之后,JavaScript 的对象系统就不再是"记忆题",而会变成你分析框架、阅读源码、设计抽象时的一套底层能力。

相关推荐
问道飞鱼2 小时前
【前端知识】React 组件生命周期:从底层原理到实践场景
前端·react.js·前端框架·生命周期
Dxy12393102162 小时前
JS发送请求的方法详解
开发语言·javascript·ecmascript
CHU7290352 小时前
定制专属美丽时刻:美容预约商城小程序的贴心设计
前端·小程序
浩~~3 小时前
反射型XSS注入
前端·xss
AwesomeDevin3 小时前
AI时代,我们的任务不应沉溺于与 AI 聊天,🤔 从“对话式编程”迈向“数字软件工厂”
前端·后端·架构
harrain3 小时前
antvG2折线图和区间range标记同时绘制
前端·javascript·vue.js·antv·g2
德育处主任Pro3 小时前
从重复搭建到高效生产,RollCode的H5开发新范式
前端
蜡台3 小时前
SPA(Single Page Application) Web 应用(即单页应用)架构模式 更新
前端·架构·vue·react·spa·spa更新
网络点点滴4 小时前
组件通信-作用域插槽
前端·javascript·vue.js