乐趣区

关于javascript:javascript对象创建模式

创立模式

在 javascript 中,次要有以下几种创立模式:

  • 工厂模式
  • 构造函数模式
  • 原型模式
  • 组合模式
  • 动静原型模式
  • 寄生构造函数模式
  • 稳当结构模式

工厂模式

工厂模式是软件工程畛域一种广为人知的设计模式。javascript 实现形式:

    function createPerson(name, obj, job) {var o = new Object();
        o.name = name;
        o.obj = obj;
        o.job = job;
        o.sayName = function() {alert(this.name);
        }
        return o;
    }
    
    var person1 = createPerson("Nicholas", 29, "software Enginner");
    var person2 = createPerson("Greg", 27, "Doctor");

工厂模式尽管解决了创立多个类似对象的问题,但却没有解决对象辨认问题

构造函数模式

    function Person(name, age, job) {
        this.name = name;
        this.age = name;
        this.job = name;
        this.sayName = function () {alert(this.name);
        }
    }
    
    var person1 = new Person("Nicholas", 29, "Software Engineer");
    var person2 = new Person("Greg", 27, "Doctor");
    
    person1 instanceof Person; // true
    person1 instanceof Object; // true
    
    person2 instanceof Person; // true
    person2 instanceof Object; // true

new 操作符实现原理请查看文章附录

不同于工厂模式,构造函数模式
没有显示创建对象
间接将属性和办法赋值给了 this 对象
没有 return 语句
解决了对象辨认问题

然而构造函数模式同样存在问题,就是每个办法都要在每个实例上从新申明一遍。person1 和 person2 都有一个名为 sayName() 的办法,但那两个办法不是同一个 Function 实例。(在 javascript 中,函数本质上也是对象,因而每定义一个函数,也就是实例化一个对象。)
通过吧函数定义转移到构造函数内部能够解决这个问题:

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

但这种形式又带来了一个新的问题,咱们在全局创立了一个全局函数。

须要留神一点,依照常规,构造函数始终应该以一个大写字母结尾,而非构造函数应该以一个小写字母结尾。这次要用于区别构造函数和非构造函数,因为构造函数自身也是函数。

原型模式

咱们创立的每个函数都有一个 prototype(原型) 属性,这个属性是一个指针,指向一个对象,而这个对象的用处能够由特定类型的所有实例共享的属性和办法。

函数原型对象请查看附录

    function Person() {}
    
    Person.prototype.name = "Nicholas";
    Person.prototype.age = 29;
    Person.prototype.job = "Software Engineer";
    Person.prototype.sayName = function() {alert(this.name);
    }
    
    var person1 = new Person();
    person1.sayName(); // "Nicholas"
    
    var person2 = new Person();
    person2.sayName(); // "Nicholas"
    
    person1.sayName === person2.sayName // true

通过原型模式创建对象,咱们不用在构造函数中定义对象实例的信息,同时实例化多个对象,每个对象不会再申明一个新的函数。
能够看到,person1.sayNameperson2.sayName 都指向了同一个函数。

然而原型模式的毛病也是不言而喻的。
首先原型模式省略了构造函数模式传递参数这一环节,后果导致所有实例的初始值在默认状况下都是雷同的属性值。
更重要的是,因为将属性和办法都搁置在原型对象上,本质上原型上的属性是 被所有实例所共享的。对于蕴含根本值的属性还体现失常,扭转属性值,只是在实例上增加一个同名属性。但对于援用类型值的属性来说,这可能是个劫难。

    function Person() {}
    
    Person.prototype = {
        constructor: Person,
        name: "Nicholas",
        age: 29,
        job: "Software Engineer",
        friends: ["shelby", "Court"],
        sayName: function() {alert(this.name);
        }
    };
    
    var person1 = new Person();
    var person2 = new Person();
    
    person1.friends.push("Van");
    
    person1.friends; // ["shelby", "Court", "Van"]
    person2.friends; // ["shelby", "Court", "Van"]

组合模式

创立自定义类型最常见的形式,就是组合应用构造函数模式和原型模式。结构模式用于定义实例属性,而原型模式用于定义方法和共享属性。
这样,每个实例都会有本人的一份实例属性正本,但同时又共享方法的援用。

    function Person(name, age, job) {
        this.name = name;
        this.age = age;
        this.job = job;
        this.friends = ["Shelby", "Court"];
    }
    
    Person.prototype.sayName = function() {alert(this.name);
    }
    
    var person1 = new Person("Nicholas", 29, "Software Enginner");
    var person2 = new Person("Greg", 27, "Doctor");
    
    person1.friends.push("Van");
    
    person1.firends; // ["Shelby", "Court", "Van"];
    person2.firends; // ["Shelby", "Court"]
    
    person1.firends === person2.firends; // false
    person1.sayName === person2.sayName; // true

动静原型模式

    function Person(name, age, job) {
        this.name = name;
        this.age = age;
        this.job = job;
        
        if (typeof this.sayName != "function") {Person.prototype.sayName = function() {alert(this.name);
            }
        }
    }

寄生构造函数模式

寄生模式的基本概念就是创立一个函数,该函数的作用仅仅是封装创建对象的代码,而后再返回新创建的对象。

    function Person(name, age, job) {var o = new Object();
        o.name = name;
        o.age = age;
        o.job = job.
        
        o.sayName = function() {alert(this.name);
        }
    }
    
    var person1 = new Person("Nicholas", 29, "Software Engineer");
    person1.sayName(); // "Nicholas"

看起来,除了应用 new 操作符之外,这个模式和工厂模式其实是截然不同的。
这个模式能够在非凡的状况下用来作为对象创立构造函数。
假如咱们须要一个具备额定办法的非凡数组类型。因为不能间接批改 Array 构造函数,因而能够应用这个模式。

    function SpecialArray() {var values = [];
        
        values.push.push(values, arguments);
        values.toPipedString = function() {return this.join("|");
        }
        
        return values;
    }
    
    var colors = new SpecialArray("red", "blue", "green");
    colors.toPipedString(); // "red|blue|green"

该模式次要毛病:
返回的对象和构造函数或构造函数的原型属性间没有关系,不能依赖 instanceof 来确定对象类型。
在其余模式可能应用的状况下,尽量不要应用这种模式。

稳当构造函数模式

    function Person(name, age, job) {var o = new Object();
        var name = name;
        var age = age;
        var job = jbo;
        
        o.sayName = function() {alert(name);
        }
    }
    
    var person1 = Person("Nicholas", 29, "Software Enginner");
    firend.sayName(); // "Nicholas"

附录

new 操作符

new 操作符实际上会经验 4 个步骤:

  1. 创立一个空的简略 JavaScript 对象(即**{}**);
  2. 链接该对象(设置该对象的constructor)到另一个对象;
  3. 将步骤 1 新创建的对象作为 **this** 的上下文;
  4. 如果该函数没有返回对象,则返回**this**
    function new(func) {var o = {};
        o.__proto__ = func.prototype;
        var result = func.apply(o, arguments);
        return typeof result === "object" ? object : o;
    }

函数原型对象

了解原型对象

无论什么时候,只有创立一个新函数,就会依据一组特定的规定为该函数创立一个 prototype 属性,这个属性指向函数的原型对象。在默认状况下,所有原型对象都会主动取得一个 construtor(构造函数) 属性,这个属性蕴含一个指向 prototype 属性所在函数的指针。

在创立了一个自定义的构造函数之后,其原型对象只会获得 construtoe 属性,至于其余属性,则都是从 Object 继承而来。当调用构造函数创立一个新实例时,该实例的外部将蕴含一个指针(外部属性),指向构造函数的原型对象,这个指针叫 [[Prototype]]。在少数浏览器中,每个对象都反对一个属性__proto__ 来调用[[Prototype]]。

尽管所有实现都无奈间接拜访 [[Prototype]], 但能够通过isPrototype 办法来确定对象之间是否存在关系。

    Person.prototype.isPrototypeOf(person1); // true;
    Person.prototype.isPrototypeOf(person2); // true;

咱们测试了 person1 和 person2,都返回了 true。因为他们外部都有一个指向Person.prototype 的指针。

Object.getPrototype()能够返回对象的原型对象。

每当代码读取某个对象的属性时,都会执行一次搜寻,指标是具备给定名字的属性。搜寻首先会从对象自身开始,如果在实例中找到了对应的属性,则返回该属性的值。如果没找到,则持续搜寻指针指向的原型对象。这也是为什么咱们在 person1person2两个实例中,并没有定义 sayName 这个属性,但仍可能失常应用。
咱们在调用 person1.sayName() 是,会执行两次搜寻。首先,解析器会问:“实例 person1 有 sayName 属性吗?”,答:“没有”。而后他持续搜寻,再问:“person1 的原型有 sayName 属性吗?”,答:“有”。于是,它就读取保留在原型中的函数。

尽管咱们可能通过实例拜访原型的属性,但却不能从新原型的属性。
如果咱们在实例上增加属性名,而这个属性名又与原型中的属性名雷同,即咱们心愿在实例中重写属性。

    function Person() {}
    Person.prototype.name = 'Nicholas';
    
    var person1 = new Person();
    var person2 = new Person();
    
    person1.name === person2.name; // true
    person1.name = 'Greg';
    
    person1.name === person2.name; // false
    person1.name; // 'Greg'
    person2.name; // 'Nicholas'
    
    person1.__proto__.name; // 'Nicholas'

事实上,当咱们重写原型属性时,只是在实例上增加了一个新属性。当咱们把实例上的属性删除后,又会暴露出原型属性。

    delete person1.name;
    person1.name; // 'Nicholas'

应用 hasOwnProperty() 函数能判断属性是否在实例上。

    person1.hasOwnProperty('name'); // false
    person1.name = 'Greg';
    person1.hasOwnProperty('name'); // true
退出移动版