对象的创建:工厂模式/构造函数模式/原型模式 (笔记)

来源:互联网 发布:java编写游戏 编辑:程序博客网 时间:2024/05/21 06:52

使用Oject构造函数或者对象的字面量创建单个对象的时候会有大量的重复代码。

工厂模式

  • 是一种很常见的设计模式,就是将创建对象的过程进行了一定的抽象。在 JavaScript中无法创建类,所以我们可以用函数来封装创建对象的细节,只提供一个创建对象的接口即可。
 function createPerson(name, age, job){        var o = new Object();        o.name = name;        o.age = age;        o.job = job;        o.sayName = function(){            alert(this.name);        };return o; }var person1 = createPerson("Nicholas", 29, "Software Engineer");var person2 = createPerson("Greg", 27, "Doctor");
  • 在函数的内部创建了一个新的对象,将函数中的参数赋值给对象的属性,然后将对象返回。在外界调用createPerson()的时候,每调用一次就返回来一个Object类型的对象,这个对象的属性值就是调用函数的时候传递的参数值。
  • 优点:解决了创建多个相似对象的重复代码的问题
    缺点:但是不知道创建出来的对象的类型。

构造函数模式

  • 类似与Object和Array等原生的构造函数,我们可以定义自己的构造函数类创建特定类型的对象。
    function Person(name, age, job){        this.name = name;        this.age = age;        this.job = job;        this.sayName = function(){            alert(this.name);}; }    var person1 = new Person("Nicholas", 29, "Software Engineer");    var person2 = new Person("Greg", 27, "Doctor");
  • 构造函数一般首字母大写
  • 构造函数是用来创建对象的函数,本质仍是函数
    • 1.当做构造函数使用
var person = new Person("Nicholas", 29, "Software Engineer"); person.sayName(); //"Nicholas"
    • 2.当做普通函数使用
Person("Greg", 27, "Doctor"); //  加 window window.sayName(); //"Greg"
    • 3.在另一个对象的作用域中调用
var o = new Object();// 在o的作用域中调用Person函数Person.call(o, "Kristen", 25, "Nurse"); o.sayName(); //"Kristen"
  • 与工厂模式类似,但是不同之处在与:
    1.没有显式的创建对象,即没调new Object()
    2.工厂模式将参数赋值给新建的对象的属性值,这里将参数直接赋值给this
    3.工厂方法返回了一个object,构造函数没有返回语句
  • 使用构造函数创建对象的时候必须使用new关键字new Person,经历的过程为
    1.创建一个新对象
    2.将构造函数的作用域赋给新的对象,从而构造函数中的this指向新对象
    3.执行构造函数中的代码
    4.返回新的对象
  • person1和person2都是Person的实例,使用构造函数构造出来的实例都有一个constrictor(构造器)属性,指向的是person。
alert(person1.constructor == Person); //truealert(person2.constructor == Person); //true
  • 开始的时候constructor属性用来标识对象的类型,但是现在检测对象多用instanceOf()。person1和person2既是Object的实例,也是Person的实例。
    alert(person1 instanceof Object);  //true    alert(person1 instanceof Person);  //true    alert(person2 instanceof Object);  //true    alert(person2 instanceof Person);  //true
  • 优点:使用自定义的构造函数创建出来的实例具有特定的类型。person1和person2都是Person的实例(虽然也是继承自Object类型,但是他们有了更具体的类型),使用工厂模式创建出来person1和person2都是Object类型。
  • 缺点:每个方法都要在每个实例上创建一遍。例如person1和person2都是有sayName()方法,但是在堆中其实存在两份这个方法。alert(person1.sayName == person2.sayName); //false
  • 可以通过将函数定义带外面的方法
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");

sayName()定义到了构造函数外面,虽然实现了person1和person2共享了全局函数sayName(), 解决了两个函数做同一件任务的情况。但是 其实全局作用域中定义的函数只能被某个对象调用,而且如果在构造函数中需要定义多个函数,如果都拿到外面,则失去了构造函数的封装性

原型模式

  • 每个函数都有一个prototype属性(是个指针),指向一个原型对象。
  • 这个原型对象中有 某个特定的类型的 所有的 实例 共享的属性和方法。
    function Person(){    } // 构造了一个Person,将属性加到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"    alert(person1.sayName == person2.sayName);  //true
  • 无论什么时候,只要创建新的函数,函数内部都会有一个prototype属性,指向函数的原型对象。且原型对象的内部会有一个constructor属性,指向新建的函数(用于表明是哪个函函数的原型)。
  • 调用构造函数创建对象的时候,对象中也有一个prototype属性,也是指向构造函数的原型对象。
    构造函数/实例/原型
  • 有图可以看出实例和构造函数没有直接的联系。但是我们可以访问到Person的属性和方法。在访问某个给定名字的属性的时候,会先在实例中查找,如果在实例中找到这个属性,会将这个属性的值返回。如果在实例中没找到这个属性,会去实例的原型中去查找这个属性,找到之后再返回属性的值。这样多个同类型的实例可以共享一个原型中的属性和方法。
  • 如果实例中有与原型同名的属性,那么会覆盖原型中的属性,其实也不是覆盖,因为首先是在实例中查找属性,找到之后就不会再去原型中找了。所以无论实例中的同名属性为什么值,对原型中的同名属性都不会有影响,因为这根本就是两个对象中的属性。互不影响。
  • 如果实例中有了同名属性仍然想访问原型中的属性,只有用delete删除示例的属性,这样才不会阻止访问原型中的属性。

更简单的原型语法

function Person(){}Person.prototype = {constructor : Person,          alert(this.name);}};var friend = new Person();alert(friend instanceof Object); //truealert(friend instanceof Person); //truealert(friend.constructor == Person); //falsealert(friend.constructor == Object); //true
  • 本质是完全重写了默认的prototype对象。每次创建一个函数,都会同时创建函数的prototype对象,prototype对象也会自动获得指向这个函数的constructor属性。但是重写了默认的prototype对象,prototype对象的constructor属性指向了Object构造函数,不在指向自定义的构造函数。

原型的动态性

function Person(){}var friend = new Person();Person.prototype = {    constructor: Person,// 手动设置构造器指向Person    name : "Nicholas",    age : 29,    job : "Software Engineer",    sayName : function () {        alert(this.name);    }};friend.sayName();   //error

这里写图片描述

  • 有图可见,重写原型对象切断了现有原型和已经存在的实例之间的联系,在重写原型之前创建的对象,仍是指向最初的原型。
  • 原型对象的优缺点
    • 优点:省去了为构造函数传递参数的过程。
    • js中的原生对象也使用了原型的构造方式,可以想修改自定义对象的原型一样修改原生对象的原型,但是不推荐修改原生对象的原型。
    • 缺点:本质是由于实例的共享特性造成的。如果原型中包含引用类型的值的时候,会出现问题。
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");alert(person1.friends);    //"Shelby,Court,Van"alert(person2.friends);    //"Shelby,Court,Van"alert(person1.friends === person2.friends);  //true

当一个实例修改了原型中的应用型值的时候,另一个实例访问的时候访问到的是修改之后的值。所以我们一般不会单独使用原型模式。

组合使用构造函数模式和原型模式

  • 这是最常用的自定义类型的方式,构造函数用于定义属性,原型模式用于定义方法和共享的属性。这样每个实例都会用一份属性的副本,且可以共享方法,最大限度的节省了内存。
function Person(name, age, job){this.name = name;this.age = age;this.job = job;this.friends = ["Shelby", "Court"];}Person.prototype = {    constructor : Person,    sayName : function(){        alert(this.name);    }}ar person1 = new Person("Nicholas", 29, "Software Engineer");ar person2 = new Person("Greg", 27, "Doctor");erson1.friends.push("Van");lert(person1.friends);    //"Shelby,Count,Van"lert(person2.friends);    //"Shelby,Count"lert(person1.friends === person2.friends);lert(person1.sayName === person2.sayName);//false/true

这里写图片描述

修改person1中的friends对person2中的friends没有影响,因为这根本就是两份内存中数据。

动态原型模式

function Person(name, age, job){//  属性this.name = name; this.age = age; this.job = job;}    var friend = new Person("Nicholas", 29, "Software Engineer");    friend.sayName();//  方法if (typeof this.sayName != "function"){    Person.prototype.sayName = function(){        alert(this.name);}; }

在初次调用构造函数的时候,判断如果sayName()方法不存在的情况下,才会在构造函数的原型中添加这个方法。

寄生构造函数模式

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

就是使用了new操作符包装的的函数,其余的和工厂模式一样。构造函数在不返回值的情况下,默认会返回新的对象实例。但是如果在构造函数的末尾添加return语句,这样可以重写调用构造函数是返回的值。一般用来为对象创建构造函数。
创建一个具有额外方法的特殊数组,但是不能直接修改Array的构造函数

function SpecialArray(){    var values = new Array(); // 创建数组    values.push.apply(values, arguments); // 用构造函数接收到的值初始化values数组    values.toPipedString = function(){ // 添加方法        return this.join("|");    };    return values; // 返回数组,这个数组具有toPipedString方法}var colors = new SpecialArray("red", "blue", "green");alert(colors.toPipedString()); //"red|blue|green"

寄生的意思是一般用于对已有的对象在添加某些东西,就像计生在原有的对象之上。注意:和工厂模式类似,不能使用instance()来确定对象的类型,因为返回的对象和构造函数或者与构造函数的原型之间没有关系。其实使用寄生构造函数与在外部创建对象没有什么不同。

稳妥构造函数模式

这个方法感觉和工厂一个样,有问题
- 没有公共属性,方法不应用this对象。
- 可以防止数据被其他应用程序改动。

function Person(name, age, job){var o = new Object();o.sayName = function(){    alert(name);}; return o; }

除了使用sayName()方法以为没有办法访问name的值。

0 0