3 使用工厂模式 和 构造函数 优化创建对象

1. 直接创建对象的弊端

我们已经学习了两种创建对象的方式

  1. new Object方式
  2. 字面量创建方法

我们可以看到一个很大的弊端:创建相似的对象,需要创建重复的写大量的代码

2. 工厂模式:工厂函数

js 复制代码
function createPerson(name, age, height) {
  var p = {};
  p.name = name;
  p.age = age;
  p.height = height;
  p.eating = function () {
    console.log(this.name + "在吃东西");
  };
  p.running = function () {
    console.log(this.name + "在跑步");
  };
  return p;
}
var p1 = createPerson("wei", 18, 1.88);
var p2 = createPerson("why", 19, 2.22);
var p3 = createPerson("gao", 22, 1.63);

工厂函数的弊端

1. ❌ 方法不能共享(内存浪费)

每次调用工厂函数都会创建一个新的函数对象,即使行为是一样的:

ini 复制代码
console.log(p1.sayHi === p2.sayHi); // false

方法没有被复用,占用更多内存,特别是创建大量对象时效率低。


2. ❌ 无法使用 instanceof 判断对象类型

因为工厂函数返回的是普通对象,不是构造函数的实例:

javascript 复制代码
console.log(p1 instanceof createPerson); // false

在一些需要类型判断的场景(如依赖注入、类型保护)不方便。


3. ❌ 不具备原型链功能

所有属性和方法都直接在对象本身,不能通过 prototype 实现继承或重用。


4. ❌ 不支持 new,也不会报错

arduino 复制代码
const p3 = new createPerson('Charlie', 22); // 语法上可以,但没意义

虽然写上 new 也不会出错,但没有使用构造函数的效果,容易产生歧义。

3. 构造函数

  1. 构造函数也称为构造器(construct),通常我们会在创建对象时候的函数
  2. 构造函数也是一个普通的函数,从表面上看和其他函数没有任何区别
  3. 当你使用 new 关键字来调用一个函数时,这个函数就充当了构造函数的角色。
js 复制代码
function foo(){
  console.log('foo!')
}
foo() //foo!
new foo() //foo!
new foo //foo!

new foo()new foo没有本质差异,带()为了方便传递参数

当使用new调用函数时会发生什么
1. 创建一个新的空对象:

首先,引擎在内存中创建一个全新的、普通的 JavaScript 空对象。你可以想象成 {}。

2. 设置新对象的原型([[Prototype]] 或 proto):
  • 这个新创建的空对象的内部 [[Prototype]] 属性(在很多 JavaScript 环境中可以通过非标准的 proto 属性访问,或者通过 Object.getPrototypeOf() 方法获取)会被设置为构造函数的 prototype 属性所指向的对象
  • 关键点: 这一步建立了新对象和构造函数原型之间的链接,使得新对象能够继承构造函数 prototype 对象上的属性和方法。例如,如果 Person.prototype 上有一个 sayHello 方法,那么通过 new Person() 创建的对象就能调用 sayHello。
3. 将构造函数的 this 绑定到新对象:
  • 构造函数内部的 this 关键字会被绑定(指向)到第一步创建的那个新对象上。
  • 关键点: 这意味着在构造函数内部,任何对 this 的操作(如 this.name = '...'; 或 this.doSomething = function(){...})实际上都是在修改这个新创建的对象。
4. 执行构造函数的代码体:
  • 引擎开始执行构造函数内部的代码。
  • 由于 this 已经指向了新对象(步骤 3),所以函数体内的代码通常会利用 this 来给新对象添加属性和方法,完成对象的初始化工作。
5. 判断返回值并返回:
  • 构造函数执行完毕后,引擎会检查构造函数的返回值:

    • 情况 A(最常见): 如果构造函数没有 显式地使用 return 语句,或者 return 了一个非对象类型 的值(如 string, number, boolean, null, undefined, symbol, bigint),那么 new 表达式的结果就是步骤 1 中创建并由 this 引用的那个新对象
    • 情况 B(较少见): 如果构造函数显式地 return 了一个对象 (包括普通对象、数组、函数等),那么 new 表达式的结果就是这个被显式返回的对象,而不是步骤 1 中创建的那个新对象。(在这种情况下,步骤 1 创建的对象可能会被丢弃,除非被返回的对象引用了它)。

一般情况下,我们确定这个函数要当构造函数。这个函数首字母大写,是一种规范也没有什么的相比其他函数特殊的

js 复制代码
function Person(name, age, height) {
  this.name = name;
  this.age = age;
  this.height = height;
  this.eating = function () {
    console.log(this.name + "在吃东西");
  };
  this.running = function () {
    console.log(this.name + "在跑步🏃‍➡️");
  };
}
var p1 = new Person('why', 18, 1.88)
var p2 = new Person('wei', 21, 1.88)
var p3 = new Person('gao', 22, 1.63)
console.log(p1)
构造函数的缺点
1. 占用空间大。每个实例都重新创建一份空间
2. 必须使用 new,容易忘记导致 bug
js 复制代码
function Person(name) {
  this.name = name;
}
const p = Person('Tom'); // 忘了 new,this 会指向全局对象或 undefined
console.log(p); // undefined
3. ❗没有私有属性(除非手动用闭包)构造函数没有原生语法支持私有变量,只能通过函数作用域"模拟":
js 复制代码
function Person(name) {
  let _name = name;

  this.getName = function () {
    return _name;
  };
}

构造函数优化创建对象

对象的原型

我们每个对象中都有一个[[prototype]]。这属性可以称为对象的原型(隐式原型)

有两种查看[[prototype]]原型的方法:

  1. 早期的方法 console.log(obj.__proto__)
  2. ES5以后提供的方法 console.log(Object.getPrototypeOf(obj))
js 复制代码
var obj = {name: 'why'}
var info = {}

console.log(obj.__proto__) //{}
console.log(info.__proto__) //{}
console.log(Object.getPrototypeOf(obj)) //{}
原型有什么用

当我们从对象中获取一个属性时,他会触发[[get]]操作:

  1. 我们去当前的对象中操作对应的属性,如果找到直接使用
  2. 如果没有找到,那会沿着原型去查找[[prototype]]
js 复制代码
var obj = {name: 'why'}
console.log(obj.age) //undefined

obj.age = 18
console.log(obj.age) //18

delete obj.age
console.log(obj.age) //undefined

obj.__proto__.age = 99
console.log(obj.age) //99
函数的原型

函数也可以看作一个对象。同时它也会有[[prototype]]属性

同时,函数也有不同与对象的点。函数有一个属性是:prototype。个 prototype 属性是一个对象,用来给"由这个函数创建的实例"提供共享的属性和方法

js 复制代码
function Person() {

}
console.log(Person.__proto__) //{}
console.log(Person.prototype) //{}

var f1 = new Person()
var f2 = new Person()
console.log(f1.__proto__ === Person.prototype) //true
console.log(f2.__proto__ === Person.prototype) //true

详细图

简单图

为什么f1和f2的__proto__会指向Person的函数的原型对象

因为。当使用new调用Person时候:

  1. 0x100和0x100其实是直接指向prototype
  2. 又是因为prototype指向Person的函数的原型对象。所以0x100和0x100间接的指向了Person的函数的原型对象
  3. 如果prototype的指向发生了改变。__proto__ 也会改变
js 复制代码
function Person() {

}
var p1 = new Person()
var p2 = new Person()

console.log(p1.name )//undefined
console.log(p2.age)//undefined
//因为
console.log(p1.__proto__ === Person.prototype) //true
console.log(p2.__proto__ === Person.prototype) //true
//所以
p1.__proto__.name = 'why'// 等于 Persn.prototype.name = 'why'
console.log(p1.name) //因为p1本身没有。但是它会沿着__proto__查找到 Person函数原型对象

p2.__proto__.age = 18 //等于 Persn.prototype.age = 18
console.log(p2.age) //因为p2本身没有。但是它会沿着__proto__查找到 Person函数原型对象

因此即使p1没有创建.__proto__.age = 18。p2没有.__proto__.name = 'why'但是当查找时p1还会通过__proto__属性找到 age,同理p2还会通过__proto__属性找到 name

因为都添加到构造函数的的原型对象上了,所以p1和p2对象还是空对象

js 复制代码
console.log(p1)//{}
console.log(p2)//{}
当我们要向构造函数的原型对象添加很多的属性,会很麻烦
js 复制代码
function foo() {
}
var f1 = new foo()
f1.__proto.name = 'why'
f1.__proto.age = 18
f1.__proto.height = 1.88
//还有很多。不一一列举了

更高效的做法

  1. 将function的 prototype 指向一个新的对象,来充当foo函数新的原型对象
  2. 新的对象是普通对象,没有原先自带的constructor。要自己手动添加
  3. 由foo创建的对象p1,p1的__proto__会先找的foo的prototype,p1的__proto__也会指向新的原型对象

我们手动模拟原型对象的上的constructor

js 复制代码
Object.defineProperty(foo.prototype, "constructor", {
  enumerable: false,
  configurable: true,
  writable: true,
  value: foo,
});
铺垫的知识讲完了
js 复制代码
function Person(name, age, height) {
  this.name = name
  this.age = age
  this.height = height
}
Person.prototype.eating = function() {
  console.log(this.name + '在吃东西😮')
}
Person.prototype.running = function() {
  console.log(this.name + '跑步🏃‍➡️')
}
var p1 = new Person('why', 18, 1.88, '广州')
var p2 = new Person('wei', 21, 1.88, '郑州')

为什么name,age,height不放在Person函数的原型对象上面呢?

p1放在原型对象的name,age,height的属性值都固定了。p2想放自己的name,age,height会把p1的值覆盖了

相关推荐
一只码代码的章鱼21 分钟前
Spring的 @Validate注解详细分析
前端·spring boot·算法
zimoyin42 分钟前
Kotlin 协程实战:实现异步值加载委托,对值进行异步懒初始化
java·前端·kotlin
恋猫de小郭1 小时前
如何查看项目是否支持最新 Android 16K Page Size 一文汇总
android·开发语言·javascript·kotlin
赵大仁1 小时前
React Native 与 Expo
javascript·react native·react.js
程序员与背包客_CoderZ2 小时前
Node.js异步编程——Callback回调函数实现
前端·javascript·node.js·web
非凡ghost3 小时前
Pale Moon:速度优化的Firefox定制浏览器
前端·firefox
清灵xmf3 小时前
从 Set、Map 到 WeakSet、WeakMap 的进阶之旅
前端·javascript·set·map·weakset·weakmap
11054654013 小时前
11、参数化三维产品设计组件 - /设计与仿真组件/parametric-3d-product-design
前端·3d
爱笑的林羽4 小时前
Mac M系列 安装 jadx-gui
前端·macos
运维@小兵4 小时前
vue使用路由技术实现登录成功后跳转到首页
前端·javascript·vue.js