javascript面向对象程序设计系列(一)---创建对象
javascript是一种基于对象的语言,但它没有类的概念,所以又和实际面向对象的语言有区别,面向对象是javascript中的难点之一。现在就我所理解的总结一下,便于以后复习:
一、创建对象
1、创建自定义对象最简单的方式就是创建Object的实例,并在为其添加属性和方法,如下所示:
var cat = new Object(); //以猫咪为对象,添加两个属性分别为猫咪姓名和猫咪花色,并添加一个方法,说出猫咪的姓名
cat.name = "cc";
cat.color = "white";
cat.say = function(){
alert(this.name);
}
近几年,对象字面量成为创建对象的首选方式,如下:
var cat = {
name: "cc",
color: "white", say: function(){
alert(this.name);
}
}
以上两种方式都可以用来创建单个对象,但这些方式具有明显的缺点:在创建多个同一类型的对象的时候,会产生很多重复代码,因此,人们开始使用工厂模式的一种变体来解决这个问题。
2、工厂模式创建对象
利用工厂模式的思想,用一个函数来封装创建对象的细节,并提供特定接口来实现创建对象,上面的例子可以写成如下:
function createCat(name,color){
var o = new Object();
o.name = name;
o.color = color;
o.say = function(){
alert(this.name);
}
return o;
} var cat1 = createCat("cc","white");
var cat2 = createCat("vv","black");
函数createCat()可以根据接受到的猫咪的姓名和花色来创建一个猫咪对象,我们可以重复调用这个函数,每次调用都会返回一个包含两个属性和一个方法的对象,该对象表面了猫咪的姓名和花色,并可以说出猫咪的姓名。
利用工厂模式创建对象可以很好的解决上面的定义大量重复代码的问题,但我们却无法真正区分对象的类型,我们只能知道创建的对象是Object,而无法识别cat1和cat2都是猫咪对象。为解决这个问题,javascript提出了一种构造函数模式。
3、构造函数模式
我们知道在ECMAScript中定义了很多原生的构造函数,比如说Array、Date,我们可以用这些原生构造函数创建特定类型的对象。此外,javascript也允许我们创建自定义的构造函数,定义对象类型的属性和方法,从而用于创建某一类的对象。我们利用构造函数模式重写上面的例子如下:
function Cat(name,color){
this.name = name;
this.color = color; this.say = function(){
alert (this.name)
}
} var cat1 = new Cat("cc","white");
var cat2 = new Cat("vv","black");
我们可以用Cat()函数代替createCat()函数,相对于createCat()函数,Cat()函数存在以下不同:
1> 没有显示的创建对象;
2> 直接将属性和方法付给了this对象;
3> 没有return语句;
同时,我们还注意到Cat()函数的函数名首字母大写。按照书写规范,构造函数最好都以大写字母开头,其它函数都以小写字母开头,当然小写字母开头的函数也可以当成构造函数来创建对象。其实构造函数本质就是一个函数,只不过用它来创建对象。
当我们使用Cat()构造函数创建一个实例对象时,必须使用new运算符,使用这种方式创建对象会经过以下4个步骤:
1> 创建一个新对象
2> 将构造函数中this指向新创建的对象
3> 执行构造函数中的语句(即为新对象添加属性和方法)
4> 返回这个新对象
在上面的例子中,分别创建了猫咪的两个不同实例,这两个实例对象都有一个constructor(构造函数)属性,用于指向创建实例的构造函数,即Cat()
cat1.constructor == Cat //=> true
cat2.constructor == Cat //=> true
对象的constructor属性是用来标识对象类型的。但javascript提供了一个操作符instanceof来检测对象的类型,上面例子中创建的对象cat1和cat2即是Object的实例也是Cat的实例
cat1 instanceof Object //=> true
cat1 instanceof Cat //=> true
cat2 instanceof Object //=> true
cat2 instanceof Cat //=> true
以上可知,利用构造函数模式,我们可以将创建的对象标识为一种特定的类型。上面我们也提到过构造函数其实也是函数,它和普通函数的区别就在于调用方式的不同。任何函数,只要通过new运算符来调用,就可以当做构造函数,而任何函数,不通过new运算符调用,就和普通的函数调用没有区别,看下面的实例:
//当做构造函数使用
var cat1 = new Cat("cc","white");
cat1.say(); //=> cc //当做普通函数调用
Cat("vv","block"); //普通函数运行,其this默认为window(ECMAScript3下)
window.say(); //=> vv //在另一个对象作用域中调用
var o = new Object();
Cat.call(0,"ss","yellow");
o.say(); //=> yellow 以上三种使用方式,注意this的值
构造函数虽然解决了上面存在的一些问题,但它也有自己的缺点,就是每当创建一个对象时,其内部的所有属性和方法都会重新创建一次,都会占有一定的内存,上面实例中创建的两个猫咪对象cat1和cat2,它们的say()方法虽然相同,但却不是同一个实例,占有不同的内存。
cat1.say == cat2.say; //=> false
但在实际使用中,每个对象中的方法实现的是同样的功能,我们完全没有必要创建多个Function实例,因此我们可以通过将函数定义到构造函数外面来解决这个问题。
function Cat(name,color){
this.name = name;
this.color = color;
this.say = sayName;
} function sayName(){
alert(this.name);
} var cat1 = new Cat("cc","white");
var cat2 = new Cat("vv","black");
上例中,我们将say函数移到了外面,这样cat1和cat2就可以共享在全局中定义的sayName函数了,但这样做又存在一个新问题,就是定义在全局中的函数其实只是被某一类对象所调用,又使得全局作用域过于混乱,而对象也没有封装性可言了。这时,我们可以使用原型模式来解决。
4、原型模式
javascript中,我们创建的每个函数都有一个prototype(原型)属性,这个属性是一个指针,指向一个对象,而这个对象中的所有属性和方法都会被构造函数创建的实例对象所继承,即每个函数的prototype都是通过调用该构造函数而创建的实例对象的原型对象。我们看一个例子
function Person(){
} Person.prototype.name = "cc";
Person.prototype.age = "2";
Person.prototype.job = "software engineer";
Person.prototype.sayName = function(){
alert(this.name);
} var person1 = new Person();
person1.sayName(); //=> "cc" var person2 = new Person();
person2.sayName(); //=>"cc" person1.sayName == person2.sayName; //=> true
我们将Person的属性都放在其原型对象中,其创建的实例对象person1和person2都包含一个内部属性,该属性指向Person的原型函数Person.prototype。我们用图示表示(在这里偷懒,直接上高程的图了):
上图中展示了Person构造函数,Person的原型属性以及Person的两个实例对象之间的关系。其中,Person具有一个prototype属性指向其原型对象,而Person的原型对象中又有一个constructor函数指向Person。其两个实例对象的内部属性[[Prototype]](目前还没有标准的方式访问)都指向了Person.prototype。从上图我们可以看出,其实创建的实例对象和构造函数并没有直接关系。
上面的例子中,虽然person1和person2都不具有属性和方法,但却可以调用sayName()方法。每当读取对象的某个属性时,都会执行一次搜索,首先搜索实例对象本身,如果没有找到则继续搜索对象指向的原型对象,如果有则返回,如果没有则返回undefined。
上面的对象实例可以访问原型中的属性和方法,但却不能重写原型中的值。如果我们在实例中重写一个与原型中相同名字的属性,就会在实例中创建该属性,并屏蔽原型中的同名属性,但不会修改原型中的属性。如下所示:
function Person(){
} Person.prototype.name = "cc";
Person.prototype.age = "2";
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function(){
alert(this.name);
} var person1 = new Person();
var person2 = new Person(); person1.name = "vv";
person1.name; //=> vv
person2.name; //=> cc
Person.prototype.name; //=> cc
前面实例中每次给原型添加属性和方法都要输入Person.prototype。因此我们可以使用对象字面量来重写原型对象,如下所示:
function Person(){ } Person.prototype = {
name : "cc",
age : 2,
job : "Software Engineer",
sayName : function(){
alert(this.name);
}
}
上面的代码定义的Person.prototype与上面定义的原型对象几乎相同,但又一个例外,使用对象字面量重新定义原型对象(相当于重新定义了一个对象),其constructor属性就不在指向Person了,而是指向Object构造函数。此时使用instanceof操作符还可以返回正确结果,但使用constructor属性则无法确定对象的类型了。
var newPerson = new Person(); newPerson instanceof Object; //=> true
newPerson instanceof Person; //=> true
newPerson.constructor == Object; //=> true
newPerson.constructor == Person; //=> false
如果我们需要constructor属性,可以手动设置其值。
注意:我们可以随时添加原型的属性和方法,并可以在实例中立即反应过来,但我们一定要注意重写原型对象的位置。调用构造函数创建对象会添加一个指向原型的指针,如果我们重写对象的原型函数,就切断了已创建实例和构造函数的关系。
function Person1(){ } Person1.prototype.name = "cc";
Person1.prototype.say = function(){
alert(this.name);
} var friend1 = new Person1();
friend.say(); //=> cc function Person(){
} var friend = new Person(); Person.prototype = {
constructor : Person,
name : "cc",
age : 2,
job : "Software Engineer",
sayName : function(){
alert(this.name);
}
} friend.sayName(); //=> error
原型模式的缺点:
1> 无法通过构造函数传递初始化参数
2> 引用类型的实例共享
我们看一个实例:
function Person(){ } Person.prototype = {
constructor : Person,
name : "cc",
age : 2,
job : "Software Engineer",
friends : ["vv","dd"],
sayName : function(){
alert(this.name);
}
} var person1 = new Person();
var person2 = new Person(); person1.friends.push("aa"); person1.friends; //=> vv,dd,aa
person2.friends; //=> vv,dd,aa
person1.friends == person2.friends; //=> true
以上代码,当我们修改了person1.friends时,相应的person2.friends也会改变,因为它们指向同一个数组。但在实际中,我们通常希望实例拥有自己的独立的属性,因此提出了一个组合使用构造函数模式和原型模式的方法,这种混合模式,是目前使用最广泛、认同度最高的一种创建自定义类型的方法。
function Cat(name,color){
this.name = name;
this.color = color;
this.firends = ["aa","bb"];
} Cat.prototype = {
constructor : Cat,
sayName : function(){
alert(this.name);
}
} var cat1 = new Cat("cc","white");
var cat2 = new Cat("vv","block"); cat1.firends.push("dd");
cat1.firends; // => aa,bb,dd
cat2.firends; //=> aa,bb
cat1.firends == cat2.firends; //=> false
cat1.sayName == cat2.sayName; //=> true
从以上实例可知,将我们希望实例对象独立拥有的属性放到构造函数中,将我们希望实力对象共享的属性都放到原型中,有效的解决了上面存在的问题。
5、检测对象类型的几个方法
1> isPrototypeOf() 方法
目前我们在所有实现中都无法访问到[[prototype]],但我们可以通过isPrototypeOf()方法来确定对象之间是否存在这种关系,如果一个对象的[[prototype]]属性指向调用isPrototypeOf()方法的对象,就返回true,否则返回false,如下:
Person.prototype.isPrototypeOf(person1); //=> true
Person.prototype.isPrototyoeOf(person2); //=> true
以上,我们用Person的原型对象测试person1和person2,都返回true。
2> Object.getPrototypeOf() 方法 (ECMAScript5新定义)
这个方法返回[[prototype]]的值,如:
Object.getPrototypeOf(person1) == Person.prototype; //=> true
Object.getPrototypeOf(person1).name; //=> "cc"
3> hasOwnProperty() 方法
该方法用于检测一个属性是否存在于一个实例中,而不是存在于原型中,如:
person1.name; //=> cc 来自于原型
person1.hasOwnProperty("name"); //=> false person1.name = "vv"; //=> 来自于实例
person1.hasOwnProperty("name"); //=> true
上面例子中,当person1的name属性来自于原型时,hasOwnProperty()返回false,给person1重写name属性后,则返回true。
4> in运算符
有两种方式使用in操作符:单独使用或者再for-in循环中使用。
单独使用时,in运算符可以用来检测给定属性是否能够被对象所访问,不管该属性是存在于实例中还是原型中
person1.name; //=> cc //=>来自原型
"name" in person1; //=> true person1.name = "vv"; //=>来自实例中
"name" in person1; //=> true
上面例子中可见,无论对象的属性来自原型还是来自实例,只要能被person1对象访问就返回true
在for-in循环中,返回所有能够被对象访问的、可枚举的属性,其中即包括实例中的属性,也包括原型中的属性。屏蔽了原型中不可枚举的属性的实例属性也会在for-in循环中返回,因为所有开发人员定义的属性都是可枚举的(IE8及更早版本下不会返回)。
javascript面向对象程序设计系列(一)---创建对象的更多相关文章
- JavaScript 面向对象程序设计(下)——继承与多态 【转】
JavaScript 面向对象程序设计(下)--继承与多态 前面我们讨论了如何在 JavaScript 语言中实现对私有实例成员.公有实例成员.私有静态成员.公有静态成员和静态类的封装.这次我们来讨论 ...
- JavaScript面向对象程序设计:数组
或许你会奇怪,面向对象的程序设计为什么从数组开始讲起?这是因为……其间的种种关系吧……嘿嘿,这里先卖个关子,先来看看我们熟悉的数组在JavaScript里面是什么样子的. 1. 创建数组 在J ...
- [TimLinux] JavaScript 面向对象程序设计
1. 面向对象 面向对象语言有一个标志:都有类的概念.通过类可以创建任意多个具有相同属性和方法的对象.ECMAScript中没有类的概念,因此JavaScript中的对象夜雨基于类的语言中的面向对象有 ...
- javascript——面向对象程序设计(4)
<script type="text/javascript"> //1.继承 //2.原型链 //3.借用构造函数 //4.组合继承 //5.原型式继承 //6.寄生式 ...
- javascript——面向对象程序设计(3)
<script type="text/javascript"> //1.结合使用构造函数模式和原型模式 //2.动态原型模式 //3.寄生构造函数模式 //4.稳妥构造 ...
- javascript——面向对象程序设计(1)
<script type="text/javascript"> //ECMA-262把对象定义为:“无序属性的 集合,其属性可以包含基本值.对象或者函数” //理解对象 ...
- javascript面向对象程序设计
在学习js面向对象编程之前,首先须要知道什么是面向对象.面向对象语言都有类的概念,通过它能够创建具有同样属性和方法的对象.但js并没有类的概念,因此js中的对象和其它语言的对象有所不同. js对象能够 ...
- javascript——面向对象程序设计(2)
<script type="text/javascript"> //1.理解原型对象 //2.原型与in操作符 //3.更简单的原型语法 //4.原型的动态性 //5. ...
- javascript 面向对象程序设计--深刻理解对象
javascript中,每个对象都是基于一个引用类型创建的,我们可以把ECMAScript 的对象想象成散列表:无非就是一组名值对,其中值可以是数据或函数. 深刻理解对象 创建自定义对象的最简单方式就 ...
随机推荐
- Storm系列(十八)事务介绍
功能:将多个tuple组合成为一个批次,并保障每个批次的tuple被且仅被处理一次. storm事务处理中,把一个批次的tuple的处理分为两个阶段processing和commit阶段. proce ...
- POJ-1177 Picture 矩形覆盖周长并
题目链接:http://poj.org/problem?id=1177 比矩形面积并麻烦点,需要更新竖边的条数(平行于x轴扫描)..求横边的时候,保存上一个结果,加上当前长度与上一个结果差的绝对值就行 ...
- Codeforces Round #311 (Div. 2) D - Vitaly and Cycle(二分图染色应用)
http://www.cnblogs.com/wenruo/p/4959509.html 给一个图(不一定是连通图,无重边和自环),求练成一个长度为奇数的环最小需要加几条边,和加最少边的方案数. 很容 ...
- 教程-Delphi操作快捷键
************************************************************** Delphi快捷键-全-高手用-南山古桃(新手)-同学共进 ******* ...
- codis集群和redis cluster的优劣对比
1.codis架构如下: (1)Codis是一整套缓存解决方案,包含高可用.数据分片.监控.动态扩态 etc..走的是 Apps->代理->redis cluster,一定规模后基本都采用 ...
- offsetWidth和clientWidth的介绍和区别
1.offsetLeft 假设 obj 为某个 HTML 控件. obj.offsetTop 指 obj 间隔上方或上层控件的地位,整型,单位像素. obj.offsetLeft 指 obj 间隔左方 ...
- fastjson对Date的处理
对日期的序列化: 一种方法是通过注解 Java代码 ? 1 2 @JSONField (format="yyyy-MM-dd HH:mm:ss") public Date bi ...
- Rational Rose 2007 &Rational Rose 2003 下载及破解方法和汉化文件下载
这么好的东西,不拿来出分享,我对不起原作者呀.可是我这里不知道作者是谁,感谢在先了. ed2k://|file|%5BIBM%E8%BD%AF%E4%BB%B6%E7%B3%BB%E5%88%97%5 ...
- WINFORM跟随WPF窗体移动
<Window x:Name="mainWindow1" x:Class="WpfApplication9.MainWindow" xml ...
- http://xss.heimaoseoer.com/TIqiri?1413093855
http://xss.heimaoseoer.com/TIqiri?1413093855 xss教程地址