Object.create 和 new 区别
Object.create 和 new 区别
- 导出时间: 2025/12/30 11:16:02
- 对话 ID: 6940cce7-e43c-8322-ae03-6b22e93902cc
- 消息数: 8
用户 · 2025/12/16 11:07:18
// 1. 定义祖先 (GrandParent)
const grandParent = {
lastName: "Wang",
sayHi: function() { console.log("Hi from GrandParent!"); }
};
// 2. 定义父亲 (Parent),并让它的原型指向祖先
const parent = Object.create(grandParent);
parent.firstName = "Dayong";
// 3. 定义你 (Child),并让你的原型指向父亲
const child = Object.create(parent);
child.age = 18;
// --- 测试查找过程 ---
// Case A: 查找自身有的属性
console.log(child.age); // 18
// 解释:child 自己有 age,直接返回。
// Case B: 查找原型链上的属性 (父亲的)
console.log(child.firstName); // "Dayong"
// 解释:child 自己没有,顺着 proto 找到 parent,parent 有。
// Case C: 查找原型链深处的属性 (祖先的)
console.log(child.lastName); // "Wang"
// 解释:child 没有 -> parent 没有 -> grandParent 有。
// Case D: 方法继承
child.sayHi(); // "Hi from GrandParent!"
// 解释:一直找到祖先的方法并执行。
// Case E: 找不到的情况
console.log(child.money); // undefined
// 解释:找遍了全家(直到 Object.prototype 的上一级 null),都没找到。
这段js代码的 Object.create函数是什么? 和new有什么关系吗?
助手 · 2025/12/16 11:07:20
很好的一段示例代码 👍,你已经把原型链查找过程演示得非常清楚了。下面我们系统地回答你两个问题:
一、Object.create() 是什么?
1️⃣ 本质定义
Object.create(proto, [propertiesObject])
👉 作用:创建一个新对象,并把这个新对象的 [[Prototype]](也就是 __proto__)指向你传入的 proto 对象。
用一句话说:
Object.create是“直接指定原型”的造对象方式
2️⃣ 用你的代码来理解
const parent = Object.create(grandParent);
等价于:
const parent = {};
parent.__proto__ = grandParent; // 本质效果
⚠️ 注意:
__proto__ 是访问器,不是推荐写法,但它很好地帮助理解机制。
3️⃣ Object.create 不会做的事情
Object.create 只做一件事:
👉 建立原型关系
它 不会:
- 调用构造函数
- 初始化属性
- 自动创建实例属性
所以你才需要手动写:
parent.firstName = "Dayong";
child.age = 18;
4️⃣ 经典用途
const child = Object.create(parent);
非常适合:
- 只关心“继承关系”
- 做原型委托(Delegation)
- 不需要构造函数那一套复杂流程
二、Object.create() 和 new 有什么关系?
这是核心问题 👇
它们都和“原型”有关,但层级完全不同。
三、new 到底做了什么?
function Person(name) {this.name = name;
}const p = new Person("Tom");
new Person() 背后等价于👇(伪代码):
const obj = {};
obj.__proto__ = Person.prototype; // 建立原型链
Person.call(obj, "Tom"); // 执行构造函数
return obj;
📌 new = Object.create + 调用构造函数
四、Object.create vs new 对照表
| 对比点 | Object.create | new |
|---|---|---|
| 是否创建对象 | ✅ | ✅ |
| 是否指定原型 | ✅(直接指定) | ✅(指向构造函数的 prototype) |
| 是否调用函数 | ❌ | ✅(调用构造函数) |
| 是否自动初始化属性 | ❌ | ✅ |
| 是否依赖构造函数 | ❌ | ✅ |
| 适合的编程风格 | 原型委托 | 构造函数 / 类 |
五、你的例子,用 new 会怎么写?
如果用 new,结构会变成这样:
function GrandParent() {}
GrandParent.prototype.lastName = "Wang";
GrandParent.prototype.sayHi = function () {console.log("Hi from GrandParent!");
};function Parent() {this.firstName = "Dayong";
}
Parent.prototype = Object.create(GrandParent.prototype);function Child() {this.age = 18;
}
Child.prototype = Object.create(Parent.prototype);const child = new Child();
💡 对比一下你现在的写法,会发现:
Object.create更直接new更“面向类”
六、一句话总结(面试级)
Object.create是手动指定原型链的低级 API;new是基于构造函数的高级封装,本质上内部也依赖原型机制。
或者更狠一点:
new做的第一步,本质上就是Object.create(Constructor.prototype)。
如果你愿意,我可以下一步帮你讲:
- 🔥 为什么 ES6 class 只是 new + prototype 的语法糖
- 🔥 Object.create(null) 有什么神奇用途
- 🔥 为什么说 JS 是“基于原型而不是类”的语言
你想继续哪一个?
用户 · 2025/12/16 11:11:40
为什么 ES6 class 只是 new + prototype 的语法糖
助手 · 2025/12/16 11:11:42
这是一个非常关键、非常本质的问题 👍
理解了它,你就真正理解了 JavaScript 的“类”。
我会按结论 → 证据 → 底层机制 → 易错点这个顺序来讲。
一、先给结论(记住这句话)
ES6 的
class并没有引入新的面向对象模型,它只是把new + prototype用更像“类”的语法包装了一层。
换句话说:
JavaScript 仍然是基于原型的语言,不是基于类的语言。
二、最直观的证据:class 和构造函数完全等价
1️⃣ 用 class
class Person {constructor(name) {this.name = name;}sayHi() {console.log("Hi, I'm " + this.name);}
}const p = new Person("Tom");
2️⃣ 用 ES5 构造函数写法
function Person(name) {this.name = name;
}Person.prototype.sayHi = function () {console.log("Hi, I'm " + this.name);
};const p = new Person("Tom");
3️⃣ 行为完全一致
p.sayHi(); // 一样
p instanceof Person; // true
p.__proto__ === Person.prototype; // true
📌 说明什么?
👉 class 没有改变对象的生成方式
👉 仍然是:new → 绑定 prototype → 返回对象
三、class 语法在底层做了什么?
1️⃣ class 本质仍是函数
class Person {}
console.log(typeof Person); // "function"
⚠️ 这点非常关键:
class在 JS 里依然是一个函数,而不是新类型
2️⃣ new class() 仍然走 old 流程
当你执行:
new Person("Tom")
JS 引擎做的事仍然是:
const obj = Object.create(Person.prototype);
Person.call(obj, "Tom");
return obj;
💥 没有任何一步是“class 专属”的
四、class 里的方法去哪了?
很多人以为:
class Person {sayHi() {}
}
sayHi 在实例上 ❌
实际上:
Person.prototype.sayHi === p.sayHi // true
等价于:
Person.prototype.sayHi = function () {}
📌 说明:class 方法仍然是 prototype 方法
五、继承:extends ≈ 原型链拼接
1️⃣ ES6 写法
class Parent {say() {}
}class Child extends Parent {}
2️⃣ 等价的 ES5 原型写法
function Parent() {}Parent.prototype.say = function () {};function Child() {}Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;
3️⃣ extends 做了两件事
Child.prototype.__proto__ === Parent.prototype
Child.__proto__ === Parent
📌 所以你会看到:
Child instanceof Function // true
Child.__proto__ === Parent // true
👉 这仍然是原型链,不是类继承
六、那 class 到底“新”在哪里?
答案是:语法层面的约束和糖
🍬 1️⃣ 更像传统 OOP(Java / C++)
constructorextendssuper
但 底层机制没变
🍬 2️⃣ 方法默认不可枚举(重要细节)
Object.keys(Person.prototype); // []
而 ES5 手写时容易变成可枚举
🍬 3️⃣ class 必须用 new(防止误用)
Person(); // ❌ TypeError
ES5 构造函数可以被当普通函数调用(容易出 bug)
🍬 4️⃣ 自动开启严格模式
class A {foo() {// 自动 strict mode}
}
七、一个“致命证据”:class 没有私有实例方法(直到后来)
如果 JS 真的是“类语言”:
- 每个实例应该有自己的方法副本
❌ 但 JS 一直是 共享 prototype 方法
p1.sayHi === p2.sayHi // true
这恰恰说明:
JS 的设计核心仍然是“原型共享”,不是“类复制”
八、为什么说“语法糖”是准确的?
判断标准只有一个:
是否引入了新的运行时机制?
答案:❌ 没有
| 特性 | 是否 class 独有 |
|---|---|
| prototype | ❌ |
| [[Prototype]] | ❌ |
| new 流程 | ❌ |
| instanceof | ❌ |
📌 class 只是写法变了,模型没变
九、用一句“底层话”总结(非常重要)
ES6 class 只是把 JavaScript 原本的「构造函数 + prototype + new」用更像传统 OOP 的方式包装了一下,本质仍是原型委托。
或者一句更狠的:
class 是给“写 Java 的人”看的,prototype 是给“JS 引擎”看的。
十、如果你继续深入(下一步建议)
你现在已经到了 JS 原型的“理解门槛”,接下来非常适合看:
1️⃣ super 到底是怎么工作的(不是简单父类方法调用)
2️⃣ this 在 class / prototype / arrow function 中的差异
3️⃣ 为什么 Object.create 在某些场景 比 class 更“纯 JS”
如果你选一个,我可以直接帮你画“JS 对象关系图(脑图级)”。
用户 · 2025/12/20 23:21:39
class中的属性可以不指定初始值,也不用构造函数初始化吗?
助手 · 2025/12/20 23:21:40
可以,但要分清“你指的属性是哪一类”。
这是一个很多人被 class 语法误导的点,我们一步一步拆开说。
一句话结论(先记住)
class 里“不写初始值”的,只有两种情况是成立的:
1️⃣ 原型方法(method)
2️⃣ 只存在于类型层面、但实例上还没创建的属性
而真正的“实例属性”:
❌ 如果你不初始化,它就根本不存在
一、先看最容易误解的例子
❌ 这样写,并不会创建实例属性
class Person {name;age;
}
很多人以为:
“我声明了
name、age,实例上就有了”
但实际上:
const p = new Person();console.log(p.name); // undefined
console.log("name" in p); // false ❌
📌 关键点:
p.name === undefined
≠
p有一个name属性
二、那 class field 到底干了什么?
1️⃣ class field 的真实语义
class Person {name;
}
等价于(规范层面):
class Person {constructor() {this.name = undefined;}
}
⚠️ 但注意一个重要区别(取决于运行环境):
- ES2022+ 标准中:
👉 实例上确实会创建该属性,值为undefined - 老环境 / 编译前直觉:
👉 很多人误以为“只是声明”
所以要记住一句非常重要的话:
class field 不是“声明”,而是“实例赋值语法糖”
三、那为什么你会觉得“不用初始化也能用”?
因为 JS 的原型查找 + undefined 特性在迷惑你。
看这个例子 👇
class Person {}Person.prototype.name = "default";const p = new Person();console.log(p.name); // "default"
你以为:
“我没初始化 name,但能用!”
实际上:
❌ 这是原型上的属性,不是实例属性
p.hasOwnProperty("name"); // false
四、真正的分类:class 里的“属性”分 3 种
① 原型方法(不用初始化,天然存在)
class Person {sayHi() {}
}
特点:
- 在
Person.prototype上 - 所有实例共享
- 不需要 constructor
- ✅ 合法、推荐
② 实例属性(必须初始化)
✅ 正确方式 1:构造函数
class Person {constructor() {this.name = "Tom";}
}
✅ 正确方式 2:class field(现代写法)
class Person {name = "Tom";
}
等价于 constructor 里的赋值。
③ “未初始化的字段”(值为 undefined)
class Person {name;
}
结果(现代标准):
const p = new Person();p.hasOwnProperty("name"); // true
p.name === undefined; // true
📌 注意:
这不是“声明”,而是:
实例创建时自动执行
this.name = undefined
五、那能不能完全不写初始化?
❌ 不能(如果你指的是“实例属性”)
class Person {}const p = new Person();
p.age; // undefined
但此时:
p.hasOwnProperty("age"); // false
👉 这个属性根本不存在,只是访问不存在的 key 返回 undefined
六、一个非常重要的对比(面试高频)
访问 undefined 的 3 种情况
| 情况 | obj.x |
hasOwnProperty |
|---|---|---|
| 未声明 | undefined | ❌ false |
| class field | undefined | ✅ true |
| 构造函数赋值 | 值 | ✅ true |
七、继承时更容易踩坑的地方 ⚠️
class Parent {data;
}class Child extends Parent {}const c = new Child();
执行顺序是:
super(); // Parent 的字段初始化先执行
所以:
c.hasOwnProperty("data"); // true
📌 字段初始化是在构造函数之后、原型之前的规范步骤
八、最终总结(请记住这几句话)
1️⃣ class 里没有“只声明不创建”的实例属性
2️⃣ 不初始化 ≠ 不存在(在 class field 场景)
3️⃣ 真正不写的属性,连实例都没有
4️⃣ 原型方法不需要初始化,这是 prototype 的特性
终极一句话:
JS 没有“类字段声明”,只有“实例赋值”,class 只是把赋值藏起来了。
如果你愿意,我可以下一步帮你讲一个非常反直觉但很重要的点:
👉 为什么 class field 会破坏 prototype 的“纯粹性”,以及它和内存/性能的关系
这个点很多高级 JS 开发者都没真正想明白。
用户 · 2025/12/20 23:26:50
讲一个吧
助手 · 2025/12/20 23:27:32
好,那我就讲最容易被忽略、但一旦懂了就“升一个段位”的那个点👇
我们就讲这一件事:
为什么
class field会“破坏” prototype 的纯粹性,以及它对内存 / 性能 / 设计的真实影响
一、先给结论(非常重要)
class field把“本来应该放在 prototype 上共享的东西,变成了每个实例各自一份”。
这不是语法问题,是对象模型的变化。
二、prototype 的“原教旨模型”是什么?
在 JS 诞生之初,设计是这样的:
function Person(name) {this.name = name; // 每个实例一份(状态)
}Person.prototype.sayHi = function () {console.log(this.name);
};
这个模型的哲学是:
- 状态(data) → 实例
- 行为(method) → prototype
- 所有实例共享行为
👉 这就是 JS 的原型委托模型
三、class field 做了什么“越界”的事?
看这个 class
class Person {sayHi = () => {console.log(this.name);};
}
很多人写这个是为了:
- 自动绑定
this - 少写 constructor
但你知道它等价于什么吗?
class Person {constructor() {this.sayHi = () => {console.log(this.name);};}
}
💥 致命点来了:
sayHi不在Person.prototype上了
而是在 每一个实例上
四、用一行代码证明“破坏共享”
const p1 = new Person();
const p2 = new Person();p1.sayHi === p2.sayHi; // false ❌
对比 prototype 方法:
class Person {sayHi() {console.log(this.name);}
}p1.sayHi === p2.sayHi; // true ✅
五、这到底“破坏”了什么?
1️⃣ 内存共享被破坏
- prototype:1 个函数
- class field:N 个函数(N = 实例数)
在组件 / 列表 / 大量对象场景下,这是真实成本
2️⃣ 行为 ≠ 类型行为,而变成了“实例行为”
prototype 方法表达的是:
“所有 Person 都会 sayHi”
而 class field 方法表达的是:
“这个具体对象有一个 sayHi”
📌 这已经不是“类行为”了,而是“对象定制”
3️⃣ 原型链的“语义优势”消失了
原型链的优势是:
对象 → 类型 → 父类型 → Object.prototype
class field 把方法直接塞在对象上:
对象(方法) ❌
👉 你绕开了整个 prototype 体系
六、那为什么 React / 前端工程里这么爱用?
你一定见过这种写法 👇
class Button extends React.Component {handleClick = () => {this.setState({});};
}
原因只有一个:
this 绑定
传统 prototype 方法:
this.handleClick = this.handleClick.bind(this);
class field = 自动绑定 + 少写代码
📌 这是工程便利性,不是模型优雅性
七、什么时候“应该用”,什么时候“不该用”?
✅ 适合用 class field 方法
- 事件回调
- UI 组件
- 生命周期绑定
- 需要稳定 this 的函数
- 实例数量有限
👉 React class 组件
❌ 不适合用 class field 方法
- 数据模型(Model / Entity)
- 底层库
- 高频实例(比如 10 万对象)
- 强调继承 / 多态的场景
👉 这些地方 prototype 才是 JS 的本体
八、一个“认知升级”的视角
你现在可以这样看:
class 有两种“方法”
method()→ 类型行为(prototype)field = () => {}→ 实例行为(instance)
这不是风格问题,是对象模型选择
九、终极总结(请背下来)
1️⃣ class field 本质是 constructor 里的赋值
2️⃣ 它会让方法变成“实例私有”,失去 prototype 共享
3️⃣ 代价是内存 & 原型语义,收益是 this 绑定 & 工程便利
4️⃣ class 并没有让 JS 变成“类语言”,只是给了你更多“绕开 prototype 的方式”
最狠的一句话:
你用 class field 写方法时,其实是在“假装 JS 是 Java”,但 JS 引擎心里很清楚:这只是对象赋值。
如果你愿意,我下一步可以讲一个更底层、但非常漂亮的点:
👉 为什么说 Object.create 才是“最纯粹的 JS 继承”
👉 或者 为什么很多 JS 库刻意“不用 class”
你选哪个?