求知 文章 文库 Lib 视频 iPerson 课程 认证 咨询 工具 讲座 Modeler   Code  
会员   
 
  
 
 
     
   
分享到
javascript 创建对象—类,继承
 
作者 yangyang_cs博客,火龙果软件    发布于 2014-04-10
 

1.什么是对象?

javascript本身,是没有类的概念的,只有对象的概念,除了基本类型(string,number,boolean,null,undefined)外,其余均是对象,就连function也是对象.

那么,什么是对象?!javascript中的对象,类似于一组键值对的集合.你甚至可以以键值对的方式来操作javascript中的对象,就像这样:

var myDog= new Object();
myDog["name"] = "Odie";
myDog["color"] = "Yellow";
console.log(myDog ["name"] );
console.log(myDog ["color"] );

跟这种方式创建对象的效果是一样的:

var myDog = new Object();
myDog .name = "Odie";
myDog .color = "Yellow";
console.log(myDog.name );
console.log(myDog.color );

显然,第二种访问方式更加方便.通常只有在并不确定我们访问的对象的属性名字的时候(比如json数据),才会使用这种方式访问.当然,如果你想起一个类似"hello world"(有空格)这样奇葩的属性名字,那你就必须使用键值对的方式创建对象啦.

2.如何创建对象?

exampleA:

//字面量方式创建对象,简单方便,可以认为,这是exampleB方式的简写
var myDog= {name: "Odie", color: "Yellow"};

exampleB:

//使用new操作符创建对象,再追加属性
var myDog= new Object();
myDog.name = "Odie";
myDog.color = "Yellow";

在js中,如果你赋值操作的属性没有,就会创建一个,1话题中就已经用过这种方式了.exampleA中自然也可以这样继续追加属性,A和B最大的区别在于创建对象的方式(主要区别在第一行),而不在于追加属性上.值得注意是只有在赋值操作时,才会这样,你做一个访问的操作,它自然是不会创建的(记住这一点,在后面的话题中很重要).

简单验证一下:

var myDog= {name: "Odie", color: "Yellow"};
console.log(myDog.age);//undefined
for(var pro in myDog){console.log(pro);}//name,color,没有age

exampleC:

//一个很少使用的方式
var myDog= Object.create(new Object());
myDog.name = "Odie";
myDog.color = "Yellow";

这种方式着实很少用,它有什么用途在这里不好讲,留到后面吧.

其实A、B、C三种方式创建对象还是有差别的,在这里也不好说,也留在后面吧.

exampleD:

//如果是简单的对象,似乎exampleA是最方便的,
但当你要创建复杂的对象,亦或是大量类似的对象时,就应当考虑下面这个方法了.
function Dog(name, color) {
this.name = name;
this.color = color;
}
var myDog= new Dog("Odie", "Yellow");

等等,为什么new了一个function呢?!学过其他OO语言的人都会觉的怪怪的.之前说过,javascript根本就没有类这个概念,我们输出一下浏览器内置的Object和Date:

console.log(Object); 
console.log(Date);

在chrome中的结果:

function Object() { [native code] }
function Date() { [native code] }

真相了,Object和Date也不是所谓的类,而是function!在javascript中,就是使用"new function(参数)"这种方式创建对象的.我们称这种function为构造函数,exampleB和exampleD是同一种方式,只是在exampleD中,使用了自己定义的构造函数.

那么在 "new 构造函数(参数)" 的过程中,都发生了什么呢?!

1.创建一个新对象//对象的隐性引用"__proto__"指向构造函数的"prototype"(先忽略后面这半句);

2.将构造函数的 "this" 指向新对象;

3.执行构造函数(初始化,向新对象添加属性);

4.返回这个对象.

经过以上步骤,一个新的对象就创建出来了.

构造函数与其他函数有什么区别么?!

唯一区别是你在调用构造函数上需要使用new关键字,但在语法上并没有区别,所有的函数都有prototype和this引用,构造函数只是定义的时候通常首字母大写,调用的时候记得使用new关键字,但这都靠自觉,javascript本身没有语法强制规定什么样的函数才是构造函数.你不用new关键字调用构造函数(非strict mode)亦或你new一个普通的函数也不会报错,当然,结果自然不是你想要的(后面会讲会发生什么).

this是什么东西?!

this是function内部的一个引用,跟其他OO语言类似,它指向函数据以执行的对象.所有的函数都有这么一个引用.

什么叫据以执行的对象?!

不太好形容这个东西,大概可以理解为,这个函数运行在哪个对象之下(还是不大好理解),直接举例吧.

	//输出自己的this
function log_this() {
console.log(this);
}
//初始化
window.onload = function() {

//example_one
log_division("example_one");
log_this();//window对象
//example_two假设你有一个btn按钮
document.getElementById("btn").onclick = log_this;//点击-->dom对象
//example_three
log_division("example_three");
var temp = {};//exampleA中创建对象的方法哦
temp.logThis = log_this; //function也是对象,可以这么给对象添加function属性,上面那个onclick监听回调函数的原理,跟这个类似
temp.logThis();//Object
}

这回好理解了吧,this指向哪里,是根据运行环境有关的,this指向它的运行环境对象,one例子当中,它就指向了window,two中就指向了dom对象,three中就指向了自定义对象.因此,如果你不用new调用构造函数,就会将属性添加到window上.//题外话,在strict mode下,这么做会报错的.

如何定义对象的函数呢?!

你可以这样:

	function Dog(name, color) {
this.name = name;
this.color = color;
this.sayName = function(){
console.log(this.name);
};
}

前文说过,function也是对象,这就相当于在执行构造函数的过程中,给对象增加了一个类型为function的sayName属性,但这样做有一个问题,就是每次执行构造函数的过程中,都会创建一个新的函数对象,显然,所有的Dog对象共享一个sayName函数就可以了.按照目前所讲的知识,你自然可以这样:

	function Dog(name, color) {
this.name = name;
this.color = color;
this.sayName = sayName;
}
function sayName() {
console.log(this.name);
}

在这里,定义了一个sayName的全局函数,这样,每次执行构造函数时,就不会重新创建一个新的sayName函数了.但这样做,想给对象定义多个函数,就要定义多个的全局函数.而当你想要定义多种对象时,全局函数的数量就会爆炸,变的让你无法掌控.

3.prototype!!!!!

幸好,可以使用函数的prototype这个对象,看下面的代码:

	function Dog(name, color) {
this.name = name;
this.color = color;
}
Dog.prototype.sayName = function() {
console.log(this.name);
};
var myDog = new Dog("Odie", "Yellow");
myDog.sayName();//Odie

再次强调,function也是对象(除了基本类型都是对象).在javascript中,每个function都有一个prototype对象.那么,"Dog.prototype.sayName = function(){...};"这种写法就是给Dog的ptototype增加一个名字为sayName的function对象.

咦?!,在上述的代码中,新对象myDog是如何指向了Dog的prototype的sayName函数(有点绕)呢?!

还记得"new 构造函数(参数)" 的过程中,都发生了什么么?!

1.创建一个新对象,对象的隐性引用"__proto__"指向构造函数的"prototype";

2.将构造函数的 "this" 指向新对象;

3.执行构造函数(初始化,向新对象添加属性)

4.返回这个对象.

下面对前面忽略的后半句进行解释.

每一个对象都有一个指向它的构造函数的prototype的隐性的引用(在构造的时候被赋值).

什么是隐性引用,就是你无法看到的,你无法使用myDog.prototype这样的方式获取prototype(在firefox和chrome中可以通过myDog.__proto__来访问).但是你却可以使用它的属性!所以你可以使用"myDog.sayName();"这种方式调用构造函数Dog的prototype的sayName函数.

如果对象的属性和对象的构造函数的prototype的属性重名了呢?!

自然是先访问对象本身的属性了.每当访问一个对象的属性时,先从对象自身查找,如果它自己没有,再查找它的__proto__指向的prototype对象有没有这个属性.

如果还没有呢?!

不会停止! 别忘了,prototype也是对象,它也有一个__proto__隐性引用,它会继续根据这个引用查找下去...查找下去...查找下去...查找下去,直到这个__proto__引用指向null为止.

在上面的例子中,很快就指向了null了.

var myDog = new Dog("Odie", "Yellow");
console.log(myDog.__proto__);
console.log(myDog.__proto__.__proto__);
console.log(myDog.__proto__.__proto__.__proto__);

在chrome中的结果:

Dog {say: function}//还有更详细的内容
Object {}
null

在firefox中的结果:

[object Object]
[object Object]
null

正是javascript这种链式查找的机制,使"继承"成为了可能(此处Dog继承Object).这个在后面讲.

javascript中的prototype实现了对象的共享机制,由同一个构造函数创造出来的对象,都有一个指向构造函数的prototype的隐性指针,再通过javascript访问属性的查找机制,就实现了共享.这类似于其他OO语言中的类的静态变量和静态函数.既然通过prototype共享function,自然也可以共享属性.

	function Dog(name, color) {
this.name = name;
this.color = color;
}
Dog.prototype.sayName = function() {
console.log(this.name);
};
Dog.prototype.kind = "Dog";
var myDog = new Dog("Odie", "Yellow");
var youDog = new Dog("Oalive", "Black");
//通过对象读取
console.log(myDog.kind);//Dog
console.log(youDog.kind);//Dog
//但不能通过对象修改prototype属性
myDog.kind = "cat";
console.log(myDog.kind);//cat
console.log(youDog.kind);//dog

上面这段代码,定义了一个prototype的kind属性,可以看到,可以直接通过对象访问该属性,却不可以通过对象进行修改.

为什么呢?!

前面讲过,每个对象都有一个指向prototype的隐性引用,这个引用是你摸不到的,"__proto__"这种写法只是chrome和firefox的浏览器支持,它并不是javascript标准(退一步说,即使是标准,你修改的方式也该是myDog1.__proto__.kind = "dog").你之所以能共通过对象访问到全局属性,是由于javascript查找机制决定的,在你访问一个属性时,在自身找不到,会继续向对象隐性引用指向的prototype继续找.

在最初讲对象的时候,特意强调了一下,访问一个对象的属性时,如果没有,并不会给对象追加一个属性,当时没有说后半句,后半句就是它会继续向它所指向prototype对象查找.这是javascript对访问属性时的处理方式.

而赋值操作呢,不会有这个查找的过程,如果没有,直接追加一个属性!所以myDog1.kind = "dog";这是给myDog1追加了一个kind属性,而对myDog1访问kind属性时,在自身就找到了这个属性,自然就不会继续查找了.

那怎么修改prototype的属性呢?

谁有prototype"正常"的引用呢——function——也就是构造函数嘛.

Dog.prototype.kind = "dog";

也可以是这样,但__proto__不是标准,即使是,也不建议这么写:

myDog1.__proto__.kind = "dog";

A.B.C三种创建对象方式的不同之处

exampleA:

//字面量方式创建对象,简单方便,可以认为,这是exampleB方式的简写
var myDog = {name: "Odie", color: "Yellow"};

exampleB:

//使用new操作符创建对象,再追加属性
var myDog = new Object();
myDog.name = "Odie";
myDog.color = "Yellow";

exampleC:

//一个很少使用的方式
var myDog = Object.create(new Object());
myDog.name = "Odie";
myDog.color = "Yellow";

A相当于B的简写方式,唯一的区别就是,通常,在浏览器中,A这种方式创建对象是不调用Object的构造函数的.

C呢,之前没有解释,这个方式跟A.B两种区别很大.

首先要说明Object.create(prototype,descriptors)这个函数,它可以创建一个指定原型的对象,参数prototype就是想要给创建出来的对象添加的指定原型//忽略后面的参数(它是可选的).

"var myDog = Object.create(new Object());"就是创建了一个以Object的实例为原型的对象,就是创建出来的对象的隐性指针指向了Object的实例.

过程类似于:

function(proto){
fucntion F(){};
F.prototype = proto;
return new F();
}

输出一下myDog的__proto__;

console.log(myDog.__proto__);//Object {}
console.log(myDog.__proto__.__proto__);//Object {}
console.log(myDog.__proto__.__proto__.__proto__);//null

为什么前两个都是"Object{}"呢,因为这是以Object的实例做为prototype的,myDog的__proto__指向Object的实例,而实例才真正指向Object的prototype.

如果这么写:

var myDog = Object.create(Object.prototype);

就和

var myDog = new Object();

是一样的了.

原型链图

CF --> constructor function -->构造函数
CFp -->constructor function prototype -->构造函数的原型
cf1--cf5 --> CF构造的实例
虚线 --> 隐性引用
实线 --> 正常引用

cf1--cf5这些对象共享CF的prototype;

CFp本身也是对象也有一个虚线的隐性引用;

CF也是对象,也有一个虚线的隐性引用,至于是什么,这要看浏览器是怎么实现的了.

prototype也有一个指向它的构造函数的引用

上面的图没有标明,其实prototype也有一个指向构造函数的引用,叫做constructor.这么说不太准确,应该说每个prototype也有一个指向相应的function的引用(prototype并不是构造函数独有的).

因此,是可以通过对象(经过prototype)访问constructor的.

var myDog = new Dog("Odie", "Yellow");
console.log(myDog.constructor);

怎么证明constructor是prototype的属性而不是对象myDog1的呢?!

Object有一个hasOwnProperty(name)函数来判断:

console.log(myDog.hasOwnProperty("constructor"));//false
console.log(myDog.hasOwnProperty("hasOwnProperty"));//false,hasOwnProperty是Object的prototype的函数哦.
console.log(myDog.hasOwnProperty("sayName"));//false,这是Dog的prototype的函数
console.log(myDog.hasOwnProperty("name"));//这个才是自己的属性

javascript的对象可以分为两部分,自己的属性和从prototype共享的属性.

那么,想要在javascript中实现继承,就需要:

1.子构造函数在执行过程中,也构造父构造函数构造出的属性;

2.继承父构造函数的共享属性——将子构造函数的prototype对象的隐性引用__proto__指向父构造函数的prototype,这样根据javascript的查找机制,就可以共享父类的prototype了;

javascript没有类,只有对象,非要向其他OO语言靠拢的话,可以认为把上述的父构造函数替换为父类.

构造父构造函数构造的属性

如果能在子构造函数构造的过程中,把它的this直接传递给父构造函数,在跑一遍父构造函数就好了.

javascript确实提供了这样一个方式.就是function对象call函数.

call函数的用法

还记得function的this引用吧——就是函数据以执行的对象的引用(可以叫做函数的运行环境引用,亦或者叫函数的上下文引用),使用call函数,就可以指定函数运行时this引用指向的对象.

call(thisObj,arg0,arg1....);

thisObj这个参数就是你要给函数运行时指定的对象.

arg0,arg1....是可选的,是函数运行时真正的参数.

用call函数继承父构造函数的属性:

<span style="white-space:pre">	</span>//父构造函数
function SuperType(name) {
this.name = name;
}
//子构造函数
function SubType(name, age) {
SuperType.call(this, name);//调用父构造函数
this.age = age;
} var sub = new SubType("js", 20);
console.log(sub.name);//js
console.log(sub.age);//20
console.log(sub.hasOwnProperty("name"));//true
console.log(sub.hasOwnProperty("age"));//true

前半部分就这么实现了,so easy是吧.

继承父构造函数的共享属性

还记得那个create函数么——创建一个指定原型的对象——可以指定被创建出来的对象的隐性引用(__proto__)指向那个对象.
那创建一个隐性引用指向父构造函数的prototype的对象:

var obj = Object.create(SuperType.prototype);

这是什么?!

这不就是我们想要的子构造函数的prototype对象么!!

<span style="white-space:pre">	</span>//父构造函数
function SuperType(name) {
this.name = name;
}
SuperType.prototype.sayName = function() {
console.log(this.name);
}
//子构造函数
function SubType(name, age) {
SuperType.call(this, name);//调用父构造函数
this.age = age;
}
SubType.prototype = Object.create(SuperType.prototype);
SubType.prototype.sayAge = function() {
console.log(this.age);
}
var sub = new SubType("js", 20);
console.log(sub.name);//js
console.log(sub.age);//20
sub.sayName();//js
sub.sayAge();//20
<span style="color:#ff0000;">console.log(sub.constructor);//function SuperType{...}</span>

啊呀,sub的prototypetype怎么指向SuperType了?!

从头找,sub本身没有constructor这个属性,向他的原型找;

它的原型被修改了——"Object.create(SuperType.prototype)",这个对象也没有constructor属相啊,继续向它的原型找;
那就是SuperType.prototype了,它的constructor指向了SuperType.

没关系,只要小小的改动一下就好了.

SubType.prototype = Object.create(SuperType.prototype);
SubType.prototype.constructor = SubType;
console.log(sub.constructor);//function subType{...}

只要给SuperType.prototype加一个constructor属性就可以了.

继承就这样完成了~

相关文章 相关文档 相关课程



深度解析:清理烂代码
如何编写出拥抱变化的代码
重构-使代码更简洁优美
团队项目开发"编码规范"系列文章
重构-改善既有代码的设计
软件重构v2
代码整洁之道
高质量编程规范
基于HTML5客户端、Web端的应用开发
HTML 5+CSS 开发
嵌入式C高质量编程
C++高级编程
 
分享到
 
 



Android手机开发(一)
理解Javascript
非典型ajax实践
彻底的Ajax
javascript 使用Cookies
使用 jQuery 简化 Ajax 开发
更多...   


Struts+Spring+Hibernate
基于J2EE的Web 2.0应用开发
J2EE设计模式和性能调优
Java EE 5企业级架构设计
Java单元测试方法与技术
Java编程方法与技术


某航空公司IT部 JavaScript实践
某电视软件 HTML5和JavaScript
中航信 JavaScript高级应用开发
大庆油田 web界面Ajax开发技术
和利时 使用AJAX进行WEB应用开发
更多...