Skip to content

创建js对象

✍️ w 🕒 2024-06-17 14:53:00(5 days ago) 🔗 A.前端知识整理

在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

手动实现主要遵守的四个步骤

  1. 创建一个新对象;
  2. 将构造函数的作用域赋给新对象(因此 this 就指向了这个新对象);
  3. 执行构造函数中的代码(为这个新对象添加属性);
  4. 返回新对象。

设计时候要考虑到构造函数传参 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

Released under the MIT License.