工厂函数和构造函数

4,671 阅读3分钟

这是我参与8月更文挑战的第4天,活动详情查看:8月更文挑战

工厂函数

工厂模式是一种设计模式,说白了就是一种简单的函数,这个函数可以创建对象,为它添加属性和方法,然后返回这个对象。就像一个工厂一样,可以批量制作某种类型的对象。这种设计模式是就是为了降低代码冗余。

简单写一个小例子

  function createPerson(name, age) {
        let o = new Object();
        o.name = name;
        o.age = age;
        o.sayName = function () {
            console.log(this.name);
        };
        return o;
    }
    let person1 = createPerson("jackson", 20);
    let person2 = createPerson("bear", 22);
    person1.sayName(); //jackson
    person2.sayName(); //bear

这里函数 createPerson()接收俩个参数,根据这几个参数构建了一个包含person信息的对象。这样写主要是为了解决需要创建大量有属性重叠的对象,如果每个都new一下,然后逐一添加属性。这也是个累人的活。通过上面的代码中,我们声明了一个createPerson方法,此方法可批量制造。但是还有缺点,它没有解决对象标识问题(就是创建的对象是什么类型)。

构造函数

构造函数模式可以自定义引用类型,可以使用new关键字创建内置类型实例应用创建自定义类型实例,我们常用的 new Vue(),其本质上就是传递 options参数,实例化一个Vue对象。

上面的工厂函数我们可以用构造函数来改进一下

    function Person(name, age) {
        this.name = name;
        this.age = age;
        this.sayName = function () {
            console.log(this.name);
        };
    }
    let person1 = new Person("jackson", 20);
    let person2 = new Person("bear", 22);
    person1.sayName(); // jackson
    person2.sayName(); // bear

在这个例子中,我们可以发现构造函数可以替代工厂函数,在实际开发中,我们用构造函数的频率一般会大于用工厂函数的频率。

注意:按照惯例,构造函数名称的首字母要大写

要创建Person的新实例,必须使用new操作符。以这种方式调用构造函数,实际上会有以下5个步骤。

(1) 在内存中创建一个新对象。

(2) 这个新对象内部的Prototype特性被赋值为构造函数的 prototype 属性。

(3) 构造函数内部的 this 被赋值为这个新对象(即 this 指向新对象)。

(4) 执行构造函数内部的代码(给新对象添加属性)。

(5) 如果构造函数返回非空对象,则返回该对象;否则,返回刚创建的新对象。

构造函数虽然好用,但是也有一些问题,我们分析一下逻辑

上面的例子,person1和person2都有一个sayName()方法,但这俩个方法却不是同一个function实例,相当于这里定义的方法sayName()会在每个实例上都创建一遍,虽然我们自己省事了,但机器可不省事。

        this.sayName = function () {console.log(this.name);};
        //逻辑等价与下面的
        this.sayName = new Function("console.log(this.name)"); 

优化一下

因为做的事情是一样的,所以没必要定义俩个不同的 Function 实例,我们可以把函数定义转移到构造函数外部。

    function Person(name, age) {
        this.name = name;
        this.age = age;
        this.sayName = sayName;
    }

    function sayName() {
        console.log(this.name);
    }
    let person1 = new Person("jackson", 20);
    let person2 = new Person("bear", 22);
    person1.sayName(); // jackson
    person2.sayName(); // bear

在这里,sayName()被定义在了构造函数外部。在构造函数内部,sayName 属性等于全局 sayName() 函数。因为这一次 sayName 属性中包含的只是一个指向外部函数的指针,所以 person1 和 person2共享了定义在全局作用域上的 sayName()函数。这样虽然解决了相同逻辑的函数重复定义的问题,但全局作用域也因此被搞乱了,因为那个函数实际上只能在一个对象上调用。如果这个对象需要多个方法,那么就要在全局作用域中定义多个函数。这会导致自定义类型引用的代码不能很好地聚集一起。这个新问题可以通过原型模式来解决。 我们下一次讲解原型(可以mark一下我的文章,谢谢)