众所周知,工厂模式在软件工程领域的运用极其广泛,以下举例工厂模式创建一个对象。
1 function createCar(name,num){ 2 //创建一个空对象 3 let obj = {}; 4 //为空对象添加属性 5 obj.name = name; 6 obj.num = num; 7 return obj 8 } 9 const myCar = createCar('benz',123456) 10 console.log (myCar); //{name: 'benz', num: 123456}
上述案例中,createCar()方法接收两个参数,一个name,一个num,通过参数传入,然后返回新对象,最后调用函数即可快速创建多个属性不同的对象。
这样的好处是能够快速创建大量属性不同的对象,但是新创建的对象没有明确的标识,即无法得知新创建的对象是什么类型,使用instanceOf无法准确查明。通过构造函数可以解决标识问题并也能批量创建对象。为此,我们引出构造函数的概念。
ES中的构造函数是用于创建特定类型对象的,如Object和Array这样的原生构造函数,运行时就可以直接使用。但是我们也可以使用构造函数来定义属性和方法。
以下为使用构造函数模式创建的对象的例子:
1 const Person = function(name,age){ 2 this.name=name; 3 this.age=age; 4 } 5 const p1 = new Person('Billy',21); 6 console.log(p1);//{name:Billy,age:21}
上述例子同样也可以通过多次new来创建大量属性不同的对象 ,所创建的对象与工厂模式创建的对象类似,但是仍有以下区别:
这样看似创建的对象天衣无缝,又能批量创建、又有标识,但是如果使用构造函数创建对象需要在对象上捆绑方法时,就会存在极大缺陷。代码如下:
1 const Person = function(name,age){ 2 this.name=name; 3 this.age=age; 4 this.sayName(){ 5 console.log(this.name) 6 } 7 } 8 const p1 = new Person('Billy',21);
构造函数的主要问题就存在于此,构造函数定义的方法会在每个实例上都创建一遍,即每次定义函数时都会初始化一个对象。即可以看似为:
1 const Person = function(name,age){ 2 this.name=name; 3 this.age=age; 4 this.sayName = new Function(){ 5 "console.log(this.name)" 6 }; 7 } 8 const p1 = new Person('Billy',21);
因此,以这种方法创建函数会带来不同的作用域链和标识符解析,不同实例上函数虽然同名但是却不相等。因为都是一样的效能,所以没有必要定义两个不同的函数。
要解决这个问题,可以把函数定义在全局,即从外部插入函数。
1 function sayName(){ 2 console.log(this.name); 3 } 4 const Person = function(name,age){ 5 this.name=name; 6 this.age=age; 7 this.sayName = sayName; 8 } 9 const p1 = new Person('Billy',21);
这样便解决了相同逻辑的函数重复定义的问题,但是这样会污染全局命名空间,若这个对象需要多个方法,则需要在全局作用域定义大量函数,这样会严重污染全局作用域。因此使用原型模式便成为了唯一方法。
使用原型对象的好处是在上面定义的属性和方法可以被对象实例共享。代码如下:
1 function Person3(){ 2 Person3.prototype.name='Billy'; 3 Person3.prototype.age=21; 4 } 5 const p7 = new Person3; 6 const p8 = new Person3; 7 console.log(p7.name); //Billy 8 console.log(p7.name); //Billy
绑定在构造函数上的属性实例都可以取到。这样便实现了属性共享,在构造函数中绑定函数也可以共享,这便解决了之前的所有问题。由此可见原型的重要性 :