面向对象
面向对象是一种程序设计的思想,与面向过程不同,它引入了类的概念,将性质相似的一类物体抽象出来,作为设计图一般的存在,以实体的方式描述业务,重心放在了参与事务的对象身上,而不是逐步分离的步骤上。
面向对象有三个特征:封装、继承、多态,关于继承,可以在读完本文后,去看看我的另一片文章,新手看JS的六张继承方式,这里暂且先不多做解释。
与面向过程的区别
这里借用一下百度知道上某位仁兄的解释:
例如五子棋游戏,面向过程的设计思路就是首先分析问题的步骤:
1、开始游戏,
2、黑子先走,
3、绘制画面,
4、判断输赢,
5、轮到白子,
6、绘制画面,
7、判断输赢,
8、返回步骤2,
9、输出最后结果。
把上面每个步骤用分别的函数来实现,问题就解决了。
而面向对象的设计则是从另外的思路来解决问题。整个五子棋可以分为 :
1、黑白双方,这两方的行为是一模一样的,
2、棋盘系统,负责绘制画面,
3、规则系统,负责判定诸如犯规、输赢等。
第一类对象(玩家对象)负责接受用户输入,并告知第二类对象(棋盘对象)棋子布局的变化,棋盘对象接收到了棋子的i变化就要负责在屏幕上面显示出这种变化,同时利用第三类对象(规则系统)来对棋局进行判定。
以上,两种模式的区别,由此可见一斑。
JS中的对象
JS是解释性的脚本语言,对于类的概念并没有JAVA那般严谨和规范,且拥有自己的特性和方法。
创建对象的过程,便是画一份设计图,JS一共提供了 7 种创建的方式(来自高程三),包括:
1.工厂模式
2.构造函数模式
3.原型模式
4.组合使用构造函数模式和原型模式
5.动态原型模式
6.寄生构造函数模式
7.稳妥构造函数模式
其中使用最广泛、认同度最高的方式是第四种:组合使用构造函数模式和原型模式,下面对每种方式进行粗略的描述。
创建对象
1.工厂模式
function createPerson(name,age){
var o = new Object();
o.name = name;
o.age = age;
o.sayName = function(){
alert(this.name)
};
return o;
}
var person = createPerson("亚当",99);
接收两个参数,在函数内部创建一个对象,然后将参数绑定后再返回,可以实现封装一个类的功能,但缺点是所有的对象的都是Object,无法准确判断它们的类型,比如“人”类是Object,“动物”类也是Object。
于是出现了构造函数模式。
2.构造函数模式
function Person(name,age){ //注意:首字母大写(惯例)
this.name = name;
this.age = age;
this.sayName = function(){
alert(this.name)
};
}
var person = new Person("亚当",99);
不用return对象,将属性和方法直接给了this对象,这样便可以用alert(person instanceof Person);//ture
来检测对象的类型,这意味着将来可以将Person标识为一种特定的类型,更利于类的概念。
有了“类”的模板,就可以照着模子捏人了,使用构造函数创建对象,必须使用到new操作符,若是当做普通函数来使用,就相当是为全局对象添加了属性,最后会出现window.sayName();//打印出传入的name变量
,而使用new来调用构造函数会经历一下四个步骤:
1.创建一个新对象
2.将构造函数的作用域赋给新对象
3.执行构造函数中的代码(为新对象添加属性)
4.返回这个新对象
构造函数模式同样有其缺陷,比如上面的例子中,如果创建了两个“人”,就有两个同样的sayName()方法,可以实现同样的功能(打印名字),一个两个还好,如果我们有成百上千个Person实例的话,name就有千百个satName()方法,这在内存中的开销无疑是极大的,既然是同样的功能,那么让它们共同使用一个函数就足够了,因此可以将这个函数摘出来,这样写:
function Person(name,age){ //注意:首字母大写(惯例)
this.name = name;
this.age = age;
this.sayName = sayName;
}
function sayName(){
alert(this.name);
}
将内部引用外部命名的函数,而将函数体放在外面,这样指向的就是同一个方法了,只是如此一来sayName这个方法相当于是放在了全局作用域中,但方法本身却只想让Person的对象使用,大炮打蚊子,有点小尴尬,同时类的封装性也遭到了破坏,由此问题,便引出了第三种创建方法——原型模式。
3.原型模式
每个构造函数都有一个prototype属性,这个属性是一个指针,指向一个对象,而这个对象的用途,便是容纳同一类下所有实例公有的方法和属性,写法如下。
function Person(){
}
Person.prototype.name = "亚当";
Person.prototype.age = "99";
Person.prototype.sayName= function(){
alert(this.name)
};
var person = new Person();
或者写的更简洁一些:
Person.prototype = {
name : "亚当",
age : "99",
sayName : function(){
alert(this.name);
}
}
好处很明显,同一类下所有对象可以共享属性和方法,当然,缺点一样明显,创建对象的时候无法传入自定义参数,除非设置如person1.name = "夏娃";
才会覆盖掉原来的名字,更为严重的是,如果Person的原型中包含了一个数组(引用类型),如果一个对象修改了这个数组,其他对象的数组都会发生变化,因为引用类型的变量指向的是同一块内存地址,这样事情就变得很麻烦了。
构造函数模式无法设置共享的属性,而原型模式无法自定义属性,那如果将两者优点结合起来,那不是天下无敌了吗!?
所以,我们有了第四种方式——组合使用构造函数模式和原型模式。
4.组合使用构造函数模式和原型模式
不多说,直接上代码:
function Person(name,age){
this.name = name;
this.age = age;
}
Person.prototype = {
constructor : Person, //确保实例的构造函数指向Person
sayName : function(){
alert(this.name);
}
}
var person = new Person("亚当",99);
可以自定义的属性(包括引用类型)都放在构造函数里,随便修改都不会影响其他实例,而公共的方法则放在原型对象中,避免资源浪费。
OJBK,万事大吉!这种模式也是目前在ECMAScript中使用最广泛、认同度最高的一种创建自定义的方法。
至此,基本的几种已经介绍完了,后面三种会简单介绍一下,不想继续深入的小伙伴们可以在这里搬小板凳撤了
5.动态原型模式
当我们为对象定义一个方法时,有时可能存在冲突,必要的情况下,我们可以检查某个应该存在的方法是否有效,如果有效,看一眼走人,如果无效,我们再初始化原型。
function Person(name,age){
this.name = name;
this.age = age;
}
//方法
if(typeof this.sayName != "function"){ //如果sayName不是函数
Person.prototype.sayName= function(){
alert(this.name)
}
};
如上述代码,仅当sayName方法不存在的情况下,才会在原型中添加此方法,而且只会在初次调用构造函数的时候才会执行这条语句,一旦定义后,由于是定义在原型上的方法,所有对象之后都可以直接调用了。
这种方法的缺陷,同样是不能重写原型,否则会切断现有实例与心源性之间的联系。
6.寄生构造函数模式
唔...在前面几种模式都不适用的情况下(应该不会遇到吧...),可以使用寄生构造函数模式创建对象,基本思想是:创建一个函数,其作用仅仅只是封装创建对象的代码,然后再返回新创建的对象。
function Person(name,age){
var o = new Object();
o.name = name;
o.age = age;
o.sayName = function(){
alert(this.name)
};
return o;
}
var person = new Person("亚当",99);
除了用new操作符以外,其余写法和工厂模式一模一样,一般会在特殊情况下使用它,例如要创建一个数组对象(Array),但在这个对象中要添加新的方法,直接修改Array的构造函数的话,程序里所有的数组都变了,GG,所以可以使用这个模式。代码如下:
function specialArray(){
var arr = new Array();
arr.newFunction = function(){
alert("我叫数组的新方法")
}
balabalabala... //其他要添加的新方法或操作
return arr;
}
var list = new specialArray();
list.newFunction(); //我叫数组的新方法
要注意,返回的对象与构造函数之间没有关系,不能使用instanceof来确定对象类型,这一点与工厂模式相同,因此建议尽可能不要使用这种方法。
7.稳妥构造函数模式
稳妥对象,指的是没有公共属性,也不引用this对象,这种模式适合在禁止使用 this 和 new 的环境中,或者在防止数据被其他应用程序(如Mashup程序)改动时使用,除了不使用 this 和 new 以外,和寄生构造函数模式类似,代码如下:
function Person(name,age){
var o = new Object();
//可以在这里定义私有变量和属性
o.sayName = function(){
alert(name)
};
return o;
}
var person = Person("亚当",99);
person.sayName(); //亚当
除了使用sayName() 方法外,没有其他办法访问 name 的值,方法中定义的私有变量和属性也无法影响传入的 name 值,安全性杠杠的!
当然,与寄生构造函数模式、工厂模式相同,它也不能使用 instanceof 检测其类型。
总结
至此,JS面向对象与其中创建方法基本结束了,如文章有问题,欢迎指正!!!