JS创建对象

273 阅读5分钟

JS创建对象

1.工厂模式

​ 恩。。。实话实说我也不晓得为什么叫工厂模式。咱们先来直接看例子吧

// 工厂模式
function createPerson(name, age,job) {
    let  o = new Object();
    o.name = name;
    o.age = age;
    o.job = job;
    o.sayName = function () {
        console.log(this.name)
    }
    return o;
}
let person1 =createPerson("Nicholas",29,"SoftwareEngineer");
let person2 =createPerson("Greg",27,"Doctor");
console.log(person1);
console.log(person2);

​ 这里,函数 createPerson()接收 3 个参数,根据这几个参数构建了一个包含 Person 信息的对象。 可以用不同的参数多次调用这个函数,每次都会返回包含 3 个属性和 1 个方法的对象。这种工厂模式虽 然可以解决创建多个类似对象的问题,但没有解决对象标识问题(即新创建的对象是什么类型)。

2.构造函数模式

​ ECMAScript中的构造函数是用于创建特定类型对象的。就比如 Object和Array这样的原生构造函数,运行时可以直接在执行环境中使用。因此,我们也可以自定义构造函数,以函数的形式为自己定义属性和方法。例如,我们可以把前面的例子用构造函数标识

eg:

function Person(name,age,job) {
    this.name = name;
    this.age = age;
    this.job=job;
    this.sayName = function () {
        console.log(this.name)
    }
};
let person1 = new Person("Nicholas",29,"SoftwareEngineer");
let person2 = new Person("Greg",27,"Doctor")
console.log(person1.name);
console.log(person2.sayName());

​ 这两者有啥子区别呢。

  1. 没有显示的创建对象
  2. 属性和方法直接赋值给了this
  3. 没有return

另外注意函数名 Person 的首字母大写了。按照惯例,构造函数的首字母都要大写的,非构造函数则以小写开头。

​ 构造函数不一定要写成函数声明的形式,赋值给变量的函数表达式也可以标识构造函数:

eg:

// 赋值给变量
let Person = function Person(name, age, job) {
    this.name = name;
    this.age = age;
    this.job = job;
    this.sayName = function () {
        console.log(this.name)
    }
};
let person1 = new Person("Nicholas", 29, "SoftwareEngineer");
let person2 = new Person("Greg", 27, "Doctor")
console.log(person1.name);
console.log(person2.sayName());

构造函数也是函数:构造函数与普通函数唯一的区别就是调用方式不同。除此之外,构造函数也是函数,并没有把某个函数定义为构造函数的特殊语法。任何函数只要使用了new 操作符调用就是构造函数,不使用new操作符调用的函数就普通函数。

构造函数的问题:构造函数的主要问题在于,其定义的每个方法会在每个实例上都创建一遍,对于前面的例子来说,person1 person2 都有sayName()方法,但这两个方法不是同一个Function实例。ECMASript中的函数时对象,因此每次定义函数时都会初始化一个对象,逻辑上来说构造函数实际上是这样的。

function Person(name, age, job){ 
 this.name = name; 
 this.age = age; 
 this.job = job; 
 this.sayName = new Function("console.log(this.name)"); // 逻辑等价
} 

​ 因为都是做一样的事情,没必要定义两个不同的Funtion实例,而且this对象可以把函数与对象的绑定延迟到运行时。

为解决这个问题,可以把函数定义转移到构造函数外部:

eg:

function Person(name, age, job){
    this.name = name;
    this.age = age;
    this.job = job;
    this.sayName = sayName;
}
function sayName() {
    console.log(this.name);
}
let person1 = new Person("Nicholas", 29, "Software Engineer");
let person2 = new Person("Greg", 27, "Doctor");
person1.sayName(); // Nicholas
person2.sayName(); // Greg

​ 在这里,sayName()被定义在了构造函数外部。在构造函数内部,sayName 属性等于全局 sayName() 函数。因为这一次 sayName 属性中包含的只是一个指向外部函数的指针,所以 person1 和 person2 共享了定义在全局作用域上的 sayName()函数。

​ 但是,这问题又来了。。。这样的话 全局作用域也因此被搞乱了,因为那个函数实际上只能在一个对象上调用。如果这个对象需要多个方法, 那么就要在全局作用域中定义多个函数。这会导致自定义类型引用的代码不能很好地聚集一起。这个新 问题可以通过原型模式来解决。

3.原型模式

​ 每一个函数都会创建一个 prototype 属性。prototype翻译过来就是——原型。这个属性是一个对象,这个对象就是通过调用构造函数来创建地下的原型。

使用原型对象的好处:在它上面定义的属性和方法可以被对象实例共享,原来在构造函数中直接赋值给对象的值,可以直接赋值给它们的原型,

eg:

// 原型模式
function Person(){}
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function (){
    console.log(this.name);
}
let person1 = new Person();
person1.sayName()
let person2 = new Person();
person2.sayName()
console.log(person1.sayName()==person2.sayName())

​ 这里,所有属性和 sayName()方法都直接添加到了 Person 的 prototype 属性上,构造函数体中 什么也没有。但这样定义之后,调用构造函数创建的新对象仍然拥有相应的属性和方法。与构造函数模 式不同,使用这种原型模式定义的属性和方法是由所有实例共享的。因此 person1 和 person2 访问的 都是相同的属性和相同的 sayName()函数。要理解这个过程,就必须理解 ECMAScript 中原型的本质。

4.理解原型

​ 无论何时,只要创建一个函数,就会创建一个prototype属性,指向原型对象。默认情况下,所有原型对象都又一个constructor的属性,指回与之关联的构造函数。

​ 在自定义构造函数时,原型对象默认只会获得 constructor 属性,其他所有的方法都继承自Object。每次调用构造函数创建一个新实例。关键在于——实例与构造函数原型之间有直接的联系,但实例与构造函数之间没有。我们通过代码来看一下;

eg:

// 正常的原型链都会终止于Object的原型对象
// Object原型的原型是null
console.log(Person.prototype.__proto__ === Object.prototype);
console.log(Person.prototype.__proto__.constructor === Object);
console.log(Person.prototype.__proto__.__proto__ === null);

/*
* 实例通过 __proto__链接到原型对象
* 构造函数通过 prototype 属性链接到原型对象
*实例与构造函数没有直接联系,与原型对象有直接联系
* */
console.log(person1.__proto__ === Person.prototype);
console.log(person1.__proto__.constructor === Person);
/*
 * 同一个构造函数创建的两个实例
 * 共享同一个原型对象:
 */
console.log(person1.__proto__ === person2.__proto__); // true