JavaScript欲速则不达——面向对象

javascript应用程序可能完全有函数和变量组成,但是如果你想要确保易于重用的、结构紧凑、高效率的代码,并且期望代码能够很好的使用其他库的话,需要考虑把代码封装到对象中。

javascript是基于原型继承的。基于原型的语言有一个特点,就是重用通过已有的对象的新实例来进行,而不是通过一个类的实例来进行。原型扩展通过以新的属性和方法来扩展一个已有的对象来进行,而不是通过类继承来扩展。

接下来我们就来看看javascript如何面向对象编程吧

工厂模式

工厂模式与创建对象模式类似,他比创建对象模式优点:能够根据设置的参数来无数次调用这个函数,而不会像单单的创建型模式那样,产生大量的重复代码。

通过一个可以包含了对象细节的函数来创建对象,然后返回这个对象。

function weber(designer,programmer,jscsser){
    var o = new Object();
    o.designer = designer;
    o.programmer = programmer;
    o.jscsser = jscsser;
    return o;
}

var team1 = new weber("xiaonan", "xiaoxi","xiaobei");
var team2 = new weber("xiaodong", "xiaozhong", "xiaobei");
alert(team1.jscsser);
工厂模式

这个模式十分有用,尤其是创建对象的流程赋值的时候,比如依赖于很多设置文件等。并且,你会经常在程序里看到工厂方法,用于让子类类定义需要创建的对象类型。

每次调用weber函数,都会通过该函数内部的对象o创建新的对象,然后返回,除此之外,这个为了创建新对象而存在的内部对象o没有其他的用途。另外,无法判断工厂模式创建的对象的类型。

工厂模式总结

什么时候使用工厂模式:

  • 对象的构建十分复杂
  • 需要依赖具体环境创建不同实例
  • 处理大量具有相同属性的小对象

什么时候不该用工厂模式

不滥用运用工厂模式,有时候仅仅只是给代码增加了不必要的复杂度,同时使得测试难以运行下去。

构造函数模式

构造函数用于创建特定类型的对象——不仅声明了使用的对象,构造函数还可以接受参数以便第一次创建对象的时候设置对象的成员值。你可以自定义自己的构造函数,然后在里面声明自定义类型对象的属性或方法。像Array和Object这样的原生构造函数。

function Weber(designer, programmer, jscsser) {
    this.designer = designer;
    this.programmer = programmer;
    this.jscsser = jscsser;
    this.sayName = function(){
        alert("设计师:"+this.designer+" 程序员:"+this.programmer+" 前端:"+this.jscsser)
    }
}

var team1 = new Weber("xiaonan", "xiaoxi","xiaobei");
var team2 = new Weber("xiaodong", "xiaozhong", "xiaobei");
team1.sayName()
构造函数模式

构造函数与其他函数的唯一区别,就在于调用它们的方式不同。因为构造函数也是函数,所以只要通过 new 调用那它就是构造函数,不是通过 new 调用的就不是构造函数。

构造函数总结

构造函数模式与工厂模式的不同之处

  • 没有显式地创建对象
  • 直接将属性和方法赋值给this对象
  • 没有return语句
  • 使用new操作符,创建 Weber 的新实例,如果不用new,直接在作为普通函数,则是被添加到了Window,因此要通过浏览器中的Window对象调用;或者也可以使用call(或者apply)在另一个通过new创建的实例中调用。
  • 函数名开头是大写,构造函数的惯例

另外,可以将构造函数的实例标识为一种特定的类型,这就解决了对象识别的问题(通过检查实例的constructor属性,或利用instanceof操作符检查该实例是否通过某个构造函数创建)。

构造函数的问题

Weber函数中,每定义了一个函数,也就是实例化了一个对象,从某中角度来讲,每个实例中都包含着不同的 function 实例,觉得这样没必要。因此,可以把方法都转移到构造函数外部来解决这个问题。

function Weber(designer, programmer, jscsser) {
    this.designer = designer;
    this.programmer = programmer;
    this.jscsser = jscsser;
    this.sayName = sayName;
}
function sayName(){
    alert("设计师:"+this.designer+" 程序员:"+this.programmer+" 前端:"+this.jscsser)
}
var team1 = new Weber("xiaonan", "xiaoxi","xiaobei");
var team2 = new Weber("xiaodong", "xiaozhong", "xiaobei");
team1.sayName()
构造函数模式

这样一来,由于 sayName 包含的是一个指向函数的指针,只是共享了在实例外部作用域中的一个方法,所以解决了每个方法都要在每个实例上重新创建一遍的问题,但是如果要对对象定义很多方法,那不是也要定义多个外部函数了么。于是这毫无封装性可言。

不过这些问题可以通过使用原型模式解决。接下来我们来介绍原型模式

原型模式

首先,得知我们创建的每个函数都有一个 prototype(原型)属性,这个属性是一个对象,它的用途就是通过调用构造函数而创建的那个对象的原型对象。

使用原型的好处就是可以让所有对象实例共享它所包含的属性和方法,直接将属性和方法定义到原型对象中,就可以让实例对象共享包含由特定类型的所有实例共享的属性和方法

function Person(){}
Person.prototype.name = "xiaobei";
Person.prototype.job = "jscsser";
Person.prototype.getName = function() { return this.name;}
var p1 = new Person();
var p2 = new Person();
console.log(p1.getName());//xiaobei
console.log(p2.getName());//xiaobei
原型方式

把类的属性(字段),方法都挂在prototype上,输出都是同一个 name ,从而看出原型模式能让是实例对象贡献他全部的属性和方法。

但是原型模式每添加一个属性或者方法,都得要敲一遍 Person.prototype 。为了减少不必要的输入,也为了视觉上更好的封装原型的功能,一般采用包含所有属性和方法的对象字面量来重写整个原型对象,如下:

function Person(){};
Person.prototype = {
    name:"xiaobei",
    job :"jscsser"
}
var p1 = new Person();
var p2 = new Person();
console.log(p1.name);//xiaobei
console.log(p2.name);//xiaobei
更简单的原型语法

上诉代码中,我们将Person.prototype设置为等于一个以对象字面量形式创建的新对象。但是这里要注意的是:没创建一个函数,就会同时创建它的 prototype 对象,这个对象也会自动获得 construction 属性。而通过对象字面量形式创建的新对象本质上完全重写了Person 对象,因此construction 属性也就变成了新对象的 construction 属性,不再指向 Person 函数。

如果construction 确实很重要,我们可以在对象字面量中增加以下一句代码 就可以了

construction : Person

以上代码特意包含一个 construction 属性,并将它设置为 Person ,从而确保通过该属性能够访问到适当的值。

原型模式总结

原型模式优点是所有对象实例都共享其方法(相对于构造函数方式),没有造成内存浪费,而其缺点也是致命的

原型方式的缺点:

  1. 省略了为构造函数传递初始化参数这一环节,结果所有实例在默认情况下都将取得相同的属性值,
  2. 一般每个对象的属性是不相同的,但是原型中所有属性都是被很多实例共享的。特别是包含引用类型值的属性,问题尤为突出

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

取构造模式和原型模式的特点:

  • 用构造函数来定义实例属性
  • 用原型模式来定义方法和共享的属性

如下:

function Person(name,age,job){
    this.name = name;
    this.age = age;
    this.job = job;
    this.friends = ["xiaodong","xiaoxi"]
};
Person.prototype = {
    constructor:Person,
    sayName:function(){
        alert(this.name);
    }
}
var person1 = new Person("xiaobei",22,"jscsser");
var person2 = new Person("xiaonan",24,"jscsser");
person1.friends.push("xiaonan");
console.log(person1.friends);//["xiaodong","xiaoxi","xiaonan" ]
console.log(person2.friends);//["xiaodong","xiaoxi" ]
console.log(person1.friends === person2.friends);//false
console.log(person1.sayName === person2.sayName);//true
组合使用构造函数模式和原型模式实例代码

上例中实例属性都是在构造函数中定义的,而由所有实例共享的属性 construction 和 方法 sayname 只是在原型中定义的。从而修改 person1.friends 并不会影响到 person2.friends,因为它们引用不同的数组。

可能会觉得代码风格不紧凑,把挂在prototype的方法代码移到function Person的大括号内。

function Person(name,age,job){
    this.name = name;
    this.age = age;
    this.job = job;
    this.friends = ["xiaodong","xiaoxi"]
    Person.prototype = {
    constructor:Person,
        sayName:function(){
            alert(this.name);
        }
    }
};
console.log(person1.friends);//["xiaodong","xiaoxi","xiaonan" ]
console.log(person2.friends);//["xiaodong","xiaoxi" ]
console.log(person1.friends === person2.friends);//false
console.log(person1.sayName === person2.sayName);//true

似乎也能正常运行,但是每次new一个对象的时候都会执行

Person.prototype = {
    constructor:Person,
    sayName:function(){
        alert(this.name);
    }
}

造成了不必要的重复的计算。所以我们只需要在 prototype 改造一下

动态原型模式

function Person(name,age,job){
    this.name = name;
    this.age = age;
    this.job = job;
    this.friends = ["xiaodong","xiaoxi"]
    if(typeof this.sayName != 'function') {
        Person.prototype.sayName = function() {
            console.log(this.name);
        };
        Person.prototype.sayFriends = function() {
            console.log(this.friends);
        };
    };
};

var person1 = new Person("xiaobei",22,"jscsser");
var person2 = new Person("xiaonan",24,"jscsser");
person1.friends.push("xiaonan");
person1.sayName();//xiaobei
person1.sayFriends();//["xiaodong","xiaoxi","xiaonan" ]
person2.sayFriends();["xiaodong","xiaoxi"]
动态原型模式

原型动态模式将需要的所有信息都封装到构造函数中,通过if语句判断原型中的某个属性,其中一个是否存在就行,若不存在,执行if语句内部的原型初始化代码。

不能使用对象字面量语法填写原型对象,如果使用对象字面量则会重写原型,那么通过构造函数创建的第一实例就会切断其他现有实例与新原型之间的联系。

各模式都有自己的应用场景,无所谓优劣。

本文源链接:http://www.html5jscss.com/js-object-oriented.html

转载请注明《JavaScript欲速则不达——面向对象》| html5jscss

评论关闭了.