JavaScript 创建对象的8种方式?
1. Object 构造函数
创建自定义对象可以创建 Object
的一个新实例,再添加属性和方法。
let person = new Object();
// 添加属性
person.name = "lili";
// 添加方法
person.sayName = function() {
console.log(this.name)
}
2. 对象字面量
使用{}
来创建对象
let person = {
name: "lili",
sayName() {
console.log(this.name)
}
}
3. Object.create()
Object.create()方法创建一个新的对象,使用现有的对象作为新创建对象的原型.
let person = {
name: "lili",
sayName() {
console.log(this.name)
}
}
let person1 = Object.create(person);
console.log(person1.name); // "lili"
4. 类(ES)
类用于创建对象的模版,它建立在原型上。
使用类声明来创建一个对象:
class Person {
constructor(name) {
this.name = name;
}
// 定义方法
sayName() {
console.log(this.name)
}
}
const person1 = new Person("lili");
person1.sayName(); // "lili"
使用类表达式来创建一个对象:
let Person = class {
constructor(name) {
this.name = name;
}
// 定义方法
sayName() {
console.log(this.name)
}
}
const person1 = new Person("lili");
person1.sayName(); // "lili"
注意:函数声明和类声明的一个重要区别是:函数声明会提升,类声明不会。
🔆为什么函数声明会有变量提升?
因为 JavaScript 的执行分为了编译阶段和执行阶段,在编译阶段,js 引擎 执行任何代码之前创建一个 执行上下文,并为作用域内的所有变量分配内存空间.会先扫描整个作用域(即函数或全局作用域),并将所有通过 var 声明和函数声明的变量提升到顶部。
5. 工厂模式
用于抽象创建特定对象,在内部使用Object 构造函数来创建对象。
可以用不同的参数多次调用这个函数,每次都会返回包含相同属性和方法的对象,但缺点是没有给出新创建的对象的类型。
function createPerson(name) {
let o = new Object();
o.name = name;
o.sayName = function() {
console.log(this.name);
};
return o;
}
let person1 = createPerson("lili");
let person2 = createPerson("Joe");
6. 构造函数
构造函数是用于创建特定类型对象的
function Person(name) {
this.name = name;
this.sayName = function() {
console.log(this.name)
}
}
let person1 = new Person("lili");
let person2 = new Person("Joe");
person1.sayName(); // "lili"
person2.sayName(); // "Joe"
用 new 操作符调用构造函数会执行如下操作:
- 在内存中创建一个新对象
- 这个新对象内部的
[[Prototype]]
特性被赋值为构造函数的prototype
属性 - 构造函数内部的
this
被赋值为这个新对象(即 this 指向新对象) - 执行构造函数内部的代码
- 如果构造函数返回非空对象,则返回该对象,否则,返回刚创建的新对象。
function newOperator(Constructor, ...args) {
let thisValue = Object.create(Constructor.prototype); // 对应上文操作步骤: 1、2
let result = Constructor.apply(thisValue, args); // 对应上文操作步骤: 3、4
return typeof result === 'object' && result !== null ? restult : thisValue; // 对应上文操作步骤: 5
}
// 测试代码
function Person(name) {
this.name = name;
this.sayName = function() {
console.log(this.name)
}
}
let person1 = newOperator(Person, "lili");
let person2 = newOperator(Person, "Joe");
person1.sayName(); // "lili"
person2.sayName(); // "Joe"
上文中创建的 person1 和 person2 都分别保存着 Person 的不同实例。这两个对象都有一个 constructor 属性指向 Person:
console.log(person1.constructor === Person) // true
console.log(person2.constructor === Person) // true
constructor 是用于标识对象类型的,instanceOf 操作符也可以确定对象类型。如下所示:
console.log(person1 instanceof Object); // true
console.log(person1 instanceof Person); // true
优点:可以确保实例被标识为特定类型,相比于工厂函数,这是一个很大的好处。
缺点:定义的方法在每个实例上都创建一遍。person1 和 person2 的都有名为 sayName() 的方法,但这两个方法不是同一个 Function 实例。ECMAScript 中的函数是对象,因此每次定义函数时,都会初始化一个对象。
7. 原型模式
每个函数都会创建一个 prototype
属性,这个属性是一个对象,包含应该由特定引用类型的实例共享的属性和方法。实际上,这个对象就是通过调用构造函数创建的对象的原型。使用原型对象的好处是,在它上面定义的属性和方法可以被对象实例共享。
function Person() {};
// 将属性和方法添加到 prototype 属性上
Person.prototype.name = "Lucy";
Person.prototype.sayName = function() {
console.log(this.name)
}
let person1 = new Person();
person1.sayName(); // "Lucy"
let person2 = new Person();
person2.sayName(); // "Lucy"
8. 组合模式
组合模式是原型模式和构造函数模式的结合。
function Person(name) {
this.name = name;
}
Person.prototype = {
constructor: Person,
sayName: function() {
console.log(this.name)
}
};
let person1 = new Person("Lucy");
let person2 = new Person("Joe");
person1.sayName(); //"Lucy"
person1.constructor === Person; // true
person2.sayName(); //"Joe"
person2.constructor === Person; // true