Appearance
在JavaScript中,有多种创建对象的方式,每种方式都有其优缺点。主要包括字面量创建对象、工厂函数、以及构造函数。以下是对这几种方式的总结:
1. 字面量创建对象
利用字面量形式,手动创建对象。
javascript
const obj = {
name: 'Alice',
age: 25
};
优点:
- 简洁直接
弊端:
- 当需要创建多个相同结构的对象时,需要编写重复的代码。
2. 工厂函数
通过封装一个函数,批量创建对象。
javascript
function createPerson(name, age) {
const person = {};
person.name = name;
person.age = age;
return person;
}
const p1 = createPerson('w', 15);
const p2 = createPerson('ww', 155);
优点:
- 允许批量创建对象,减少代码重复。
弊端:
- 在打印对象时,对象的类型都是
Object
类型,不能明确区分出用哪个工厂函数创建的对象。
3. 构造函数
构造函数在JavaScript中扮演了其他语言中类的角色,通过new
关键字创建对象。
Java中的构造函数:
java
public class Person {
Person() { // 构造函数
System.out.println("无参的构造方法");
}
}
JavaScript中的构造函数和类:
- ES5方式:
javascript
function Person(name, age) { // 构造函数
this.name = name;
this.age = age;
}
const p1 = new Person('w', 15);
const p2 = new Person('ww', 155);
- ES6方式:
javascript
class Person {
constructor(name, age) { // 构造函数
this.name = name;
this.age = age;
}
}
const p1 = new Person('w', 15);
const p2 = new Person('ww', 155);
优点:
- 使用
new
关键字创建对象时,能够明确对象的类型。 - 支持面向对象编程的很多特性(如继承、方法定义等)。
手动实现一个 new 语法糖
利用工厂函数创建对象,因此可以发现下面手动实现一个new ,其中new 很像一个构造函数语法糖
js
function createPerson(name,age){
const per = {}
per.name = name
per.age =age
return per
}
const p1 = createPerson('w',15)
const p2 = createPerson('ww',155)
了解 Object.create
Object.create(proto,[propertiesObject])
静态方法以一个现有对象作为原型,创建一个新对象,这是一种更直接地基于原型链实现继承的方式。它允许我们创建一个新对象,并将其原型直接指定为传入的对象。这种方式绕过了构造函数,为原型继承提供了一种更为灵活和低级别的控制。
proto
:要作为新对象原型的对象,或 null。propertiesObject
(可选):包含一个或多个属性描述符的对象,这些属性描述符将被添加到新对象中。这些属性对应于 Object.defineProperties() 的第二个参数
通过 Object.create
实现原理其实可以看出来 Object.create(proto, [propertiesObject])
创建的新对象的内部 [[Prototype]]
其实链接的是传入参数 proto
js
// 模拟一个 Object.create
function simpleCreate(proto, props) {
// 创建一个空对象
const obj = {};
// 设置新对象的原型为传入的proto
if (typeof proto === 'object' || typeof proto === 'function') {
obj.__proto__ = proto;
} else {
throw new TypeError('Object prototype may only be an Object or null');
}
原型链挂在对象
当你使用 Object.create(proto)
时,所创建的新对象具有对 proto 的引用,它作为新对象的原型。在这个新对象上调用任何属性或方法时,如果该对象自身没有这个属性或方法,JavaScript 引擎会在它的原型(即 proto)中查找
dog 继承了 animal 可以用 animal 上的属性和方法
js
const animal = {
eats: true,
name:'123'
};
const dog = Object.create(animal);
dog.name = '456'
console.log(dog.name); // 456 指向自身的 并不会影响到原型链上的
console.log(animal.name); // 123 并不会改变原型链上的
console.log(dog.eats); // true, 因为在 dog 的原型链上有 eats 属性
console.log(dog.__proto__); // { eats: true }, 即 dog 的 __proto__ 是 animal 对象
console.log(Object.getPrototypeOf(dog) === animal); // true, 验证 dog 的原型是 animal
创建出来的dog 对象 dog.__proto__.animal.__proto__.Object
挂在到构造函数上
js
function Person( name ){
this.name = name;
};
Person.prototype.getName = function(){
return this.name;
};
// 这里让o2对象的原型链指向了Person,并且给o2对象自己添加了个属性p
/*
下面的写法等同于
const o2 = new Object()
o2.__proto__ = Person.prototype
o2.p = 42
所以o2只是继承了Person.prototype原型链但是没有基础他的name属性
因此o2是没有name属性的
*/
o2 = Object.create(Person.prototype, {
p: {
value: 42,
writable: true,
enumerable: true,
configurable: true
}
});
// 打印结果如图一
console.log(o2)
const per = new Person()
o3 = Object.create(per, {
p: {
value: 42,
writable: true,
enumerable: true,
configurable: true
}
});
// 打印结果如图一
console.log(o3)
创建空对象
有时候你可能需要创建一个没有任何原型链(尤其是没有默认的 Object 原型链)的对象,这可以通过传入 null 来实现。
js
const pureObject = Object.create(null);
pureObject.someProp = 'some value';
console.log(pureObject); // { someProp: 'some value' }
console.log(Object.getPrototypeOf(pureObject)); // Output: null
实现一个new
手动实现主要遵守的四个步骤
- 创建一个新对象;
- 将构造函数的作用域赋给新对象(因此 this 就指向了这个新对象);
- 执行构造函数中的代码(为这个新对象添加属性);
- 返回新对象。
设计时候要考虑到构造函数传参 new Person('wang')
因此对象设计 var a = objectFactory( Person, 'sven' )
js
function Person(name) {
// constructor.apply(obj, prams); 会将属性或方法添加到obj中
this.name = name;
}
Person.prototype.getName = function () {
return this.name;
};
function objectFactory(...args) {
// 获取构造函数和传入的参数
const [constructor, ...prams] = args;
// 将对象的原型指向构造函数的原型 创建一个对象
const obj = Object.create(constructor.prototype);
// 执行构造函数,将属性或方法添加到obj中
const result = constructor.apply(obj, prams);
return typeof result === "object" ? result : obj; // 确保构造器总是会返回一个对象
}
// var objectFactory = function(){
// // 创建一个 空对象
// var obj = new Object();
// // 获取构造函数
// var Constructor = [].shift.call( arguments )
// // 改变当前obj 空对象原型链的指向
// obj.__proto__ = Constructor.prototype
// // 改变构造函数指向
// var ret = Constructor.apply( obj, arguments );
// return typeof ret === 'object' ? ret : obj; // 确保构造器总是会返回一个对象
// }
// 这个写法等同 console.log(new Person('wang'))
var a = objectFactory(Person, "sven");
console.log(a);
console.log(a.name); // 输出:sven
console.log(a.getName()); // 输出:sven
console.log(Object.getPrototypeOf(a) === Person.prototype);
对这个做一个说明 typeof result === "object" ? result : obj;
- 当构造函数有返回值的时候, return 出来的是一个和 this 无关的对象时,new 命令会直接返回这个新对象,而不是通过 new 执行步骤生成的 this 对象
js
function Person(){
this.name = 'Jack';
return {age: 18}
}
var p = new Person();
console.log(p) // {age: 18}
console.log(p.name) // undefined
console.log(p.age) // 18
当返回时候是非对象,那么它还是会根据 new 关键词的执行逻辑,生成一个新的对象(绑定了最新 this),最后返回出来
js
function Person(){
this.name = 'Jack';
return 'tom';
}
var p = new Person();
console.log(p) // {name: 'Jack'}
console.log(p.name) // Jack