思考一下如果需要在开发中创建一系列相似的对象,我们应该如何操作呢?
普通创建
使用字面量的方式多次创建对象
var person1 = {
name: 'obj1',
age: 18,
height: 188,
}
var person2 = {
name: 'obj2',
age: 28,
height: 180,
}
var person3 = {
name: 'obj3',
age: 38,
height: 170,
}
这种方式有一个很大的弊端:创建同样的对象时,需要编写重复的代码,那怎么解决?继续往下看 ↓↓↓
工厂函数创建
我们可以封装一个函数,这个函数用于帮助我们创建一个对象,我们只需要重复调用这个函数即可,这就是工厂函数,工厂模式其实是一种常见的设计模式
function createPerson(name, age, height) {
var obj = {}
obj.name = name
obj.age = age
obj.height = height
obj.running: function () {
console.log(name + "在running");
},
obj.jumping: function () {
console.log(name + "在jumping");
},
return obj
}
const person1 = createPerson('Alice', 25, 170);
person1.running(); // 输出: Alice在running
person1.jumping(); // 输出: Alice在jumping
const person2 = createPerson('Bob', 30, 180);
person2.running(); // 输出: Bob在running
person2.jumping(); // 输出: Bob在jumping
这种方式是有效的,但它还有一个缺点:每次调用 createPerson
时,都会为每个新对象创建新的 running
和 jumping
方法实例,如果创建大量对象可能会浪费内存。那怎么解决?继续往下看 ↓↓↓
构造函数创建
在JavaScript
是为了支持面向对象编程,引入了new
操作符,使得开发者能够通过构造函数创建并初始化对象
- 构造函数也称为构造器(
constructor
),通常是我们在创建对象时会调用的函数 - 构造函数是一个普通的函数,命名以大写字母开头,这个函数被
new
操作符来调用了,那么这个函数就称为是一个构造函数
function Person(name, age, height) {
this.name = name;
this.age = age;
this.height = height;
this.running = function() {
console.log(name + "在running");
};
this.jumping = function() {
console.log(this.name + "在jumping");
};
}
const person1 = new Person('Alice', 25, 170);
person1.running(); // 输出: Alice在running
person1.jumping(); // 输出: Alice在jumping
const person2 = new Person('Bob', 30, 180);
person2.running(); // 输出: Bob在running
person2.jumping(); // 输出: Bob在jumping
通过工厂函数和构造函数代码对比可以看出:
new
操作符肯定创建了一个空对象new
操作符肯定return
了一个对象- 把
this
绑定到了新对象上,
但构造函数和new
还是没有解决工厂函数每次都会创建新的 running
和 jumping
方法实例的问题,那么继续往下看 ↓↓↓
构造函数和原型
原型和原型链构成了 JavaScript
的继承机制,使得对象能够共享属性和方法,继承具体学习这篇文章:https://juejin.cn/post/7399986979735781391
function Person(name, age, height) {
this.name = name;
this.age = age;
this.height = height;
}
Person.prototype.running = function() {
console.log(this.name + "在running");
};
Person.prototype.jumping = function() {
console.log(this.name + "在jumping");
};
const person1 = new Person('Alice', 25, 170);
person1.running(); // 输出: Alice在running
person1.jumping(); // 输出: Alice在jumping
const person2 = new Person('Bob', 30, 180);
person2.running(); // 输出: Bob在running
person2.jumping(); // 输出: Bob在jumping
这时我们会思考为什么定义在构造函数Person.prototype
原型的方法,通过new
创建出来的person1
和person2
对象可以访问到?
可想而知这肯定是new
操作符内部做了操作:
它将新对象的 __proto__
属性设置为构造函数的 prototype
属性
person1.__proto__ = Person.prototype
图示如下:
总结
- 创建一个空对象:使用 new 关键字时,首先会创建一个空对象
const obj = { }
- 设置原型:新对象的原型(
__proto__
)会被设置为构造函数的prototype
属性obj.__proto__ = Constructor.prototype
- 绑定
this
:构造函数中的this
会绑定到新创建的对象上const result = Constructor.apply(obj, args)
- 执行构造函数:执行构造函数中的代码,
this
指向新创建的对象 - 返回对象:如果构造函数没有显式返回对象,
new
表达式会自动返回新创建的对象return typeof result === 'object' && result !== null ? result : obj
手写new
function myNew(constructor, ...args) {
// 1. 创建一个新的空对象
const obj = {};
// 2. 将新对象的 __proto__ 属性设置为构造函数的 prototype 属性
obj.__proto__ = constructor.prototype;
// 3. 调用构造函数,并将 this 绑定到新对象
const result = constructor.apply(obj, args);
// 4. 如果构造函数返回的是对象,则返回该对象,否则返回新对象
return typeof result === 'object' && result !== null ? result : obj;
}
function Person(name, age) {
this.name = name;
this.age = age;
}
const john = myNew(Person, 'John', 30);
console.log(john.name); // 输出: John
console.log(john.age); // 输出: 30
扩展ES6
在ES6
中,引入了 class
关键字,我们可以使用它来创建一系列的对象,这让面向对象编程变得更加直观和易于理解
class Person {
constructor(name, age, height) {
this.name = name;
this.age = age;
this.height = height;
}
running() {
console.log(this.name + "在running");
}
jumping() {
console.log(this.name + "在jumping");
}
}
const person1 = new Person('Alice', 25, 170);
person1.running(); // 输出: Alice在running
person1.jumping(); // 输出: Alice在jumping
const person2 = new Person('Bob', 30, 180);
person2.running(); // 输出: Bob在running
person2.jumping(); // 输出: Bob在jumping