javascript中的继承[一] 原型链介绍

废话,可以跳过:好懒,好久没写东西了,总结了好久的正则表达式也没有出炉。最近在干啥,倒叙:git github怎么用,计算机网络,http协议,javascript面向对象,javascriptECMA标准,正则表达式,RESTful原则,php,css inline & block 等等,表示 inline和block还是一塌糊涂,等待研究。需要学的东西好多,亚历山大~~~

 

前言:可能会写三到四篇关于js链式继承以及面向对象的东西,大都是不同书籍的笔记、总结以及自己的理解。

涉及的书:

1.Object-Oriented JavaScript

2.Professional JavaScript for Web Developers[红书,javascript高级程序设计]

3.JavaScript Web Applications

 

正文

(一)javascript中"类"的继承是基于原型链的。什么是原型[prototype]链?

先举个例子

[想这个例子好纠结,纠结于js中“类”和Php、C#、java中类的含义其实是不一样的,我纠结于是否有必要阐述下两者的不同,想想还是算了。也许后文慢慢会说明白。如果你对其他语言中类的含义有所理解,希望在理解原型的时候,更多的是比较有何不同,着重理解原型链的本质,不要急于想用原型怎么达到类的效果。]

[我们可以用原型链做很多有趣事情,不用较真js如何构造一些诸如private public static 等关键字的实现]

类可以理解为种类,是一些特性的集合;对象是类的实现,是具体特性的实现。

动物,马,人,车都可以看做是类,是概念;具体到他家的狗,他妹,他家的车都是实物,是对象。

面向对象的程序设计,这种说法不一定确切,但是可以这么粗糙的理解。

继续举例子

原型链 更像是“我爸是李刚”,我儿子有事,先找我,我如果有事解决不了,就找李刚。

js中的每个对象,都包含有一个指针,指向它的原型。当我们调用一个js对象obj的属性或者方法时,obj先在直接属于自己的属性、方法中找,如果找不到,就在它的原型里找,如果找不到,再在原型的原型里找。

我是一个人,一个对象,我爹也是个人,一个对象。我儿子办成什么事情,外人不一定清楚到底是谁的本事。

objA是一个对象,它的原型objB也是一个对象,objC也是一个对象,每个对象都有他们自己的属性和方法。当objA.fun()执行时,如果objA不含有fun这个方法,解释器会在objA的原型对象objB查找,一直向上找。我自己认为,这种原型链的思想,不一定理解成父子的关系,也不一定要理解成继承关系。就像社会关系,不一定只是父子,也可以是师生、朋友。

这是原型链的一个概貌。和普遍意义上类的继承有很大差距,其他语言中如果按照“链”的方式去理解继承关系,更多是在类(Class)层面上,鸟有飞的方法,马和牛都是哺乳动物,他们都是生物。javascript是没有类(Class)这一说法的。

 

(二)原型如何实现

构造对象一般会见到两种最基本的形式:字面量和构造函数

/*字面量*/
var jim = {};
jim.name = "Jim";
jim.sayHello = function(){alert(this.name);};

var hanmeimei = {
    name:"hanmeimei",
    sayHello:function(){alert(this.name);}
};

/*构造函数*/

function Person(name){
    this.name = name;
    this.sayHello = function(){alert(this.name);};

}
var zhangsan = new Person("zhangsan");
var lisi = new Person("lisi");
lisi.toString();

上面的代码中,原型在哪里??(不知道能扯多远,本来第一篇文章是讲o-o javascript那本书的笔记的,哎,就变成js对象基础介绍了)

javascript中函数也是一种对象,我们定义的Person 是Function的实例。

原型是函数实例的一个属性。

原型本身是一个对象,他是在函数定义的时候创建的。

Person定义的过程中,Person会得到一个prototype的属性,指向一个内部生成的对象(就是一个普通的Object)。执行 如lisi.toString();的时候,这个方法其实是这个原型对象的。

lisi又是如何和Person的原型对象扯上关系的呢?

var lisi = new Person("lisi");

这简单的一句话,华丽丽地做好好多你不知道的事情,比如说一下这些(有很多,具体可以参看ECMA的标准)

1.实例化一个Object对象,Person函数中的this指向这个对象

2.给这个对象加上一个内部私有属性(如__proto__,不通的浏览器宿主实现不一样),指向Person的原型(函数的原型对象是在函数定义的时候产生的,现在是实例化一个对象,这是两个过程)

3.执行函数Person中的代码

4.如果函数Person的返回值不是对象,或者没有返回值,返回步骤1中生成的对象;如果有返回值,返回这个对象。

 

小结:

1.原型(prototype)是函数的一个属性,原型对象在函数定义时产生。

2.通过构造函数产生对象的时候,这个对象会有一个内部属性(__proto__),指向函数的原型对象。

3.一个对象没有实现某方法、属性时,会向它内部指向的原型去查找。

 

(三)我们如何通过上面的小结,用原型链实现继承??

我们尝试地基于Person"类",实现一个Coder"类",继承Person的属性和方法。

function Person(name){
    this.name = name;
    this.sayHello = function(){alert(this.name);};
}
function Coder(language){
   this.language = language;
    this.code = function(){alert("i am a "+this.language+" coder");};
}

Coder.prototype = new Person("无名");

var c1 = new Coder("js");
c1.sayHello();
c1.code();

var c2 = new Coder("js");

c2.sayHello();
c2.name = "jim";
c2.sayHello();
c2.code();
 

 

你能理解吗?运行下看看~~~

 

 

 

 

 

 

 

 

//这是Person构造函数
//函数定义的时候,会产生一个原型对象,Person.prototype指向这个对象
function Person(name){
    this.name = name;
    this.sayHello = function(){alert(this.name);};
}

//这是Coder构造函数
//函数定义的时候,会产生一个原型对象,Coder.prototype指向这个对象
function Coder(language){
   this.language = language;
    this.code = function(){alert("i am a "+this.language+" coder");};
}

//这里是关键,我们修改了原生的原型对象,而是把prototype指向一个Person的实例,后面还会再次详细解释。
Coder.prototype = new Person("无名");
var c1 = new Coder("js");
c1.sayHello();
c1.code();


//执行构造函数,对象c有个内部属性(__proto__)指向Coder的原型对象
 var c2 = new Coder("js"); 

//c2此时没有name属性,会查找原型对象中的name属性
alert(c2.name);

//给c2增加name属性,此处再做解释
c2.name = "jim";

//同样,c2没有sayHello方法,执行的是原型对象的方法
c2.sayHello();

//c2自己的方法
c2.code();

 

我们人工的修改了Coder的prototype属性,指向一个Person的实例。

c2调用sayHello方法的时候,自己没有这个方法,就会通过__proto__指针查找Coder原型对象(即我们Person("未命名")产生的实例)的属性,找到sayHello执行即可。

特别需要注意的是,c2.name = "jim" 并不是修改了 原型对象的name属性,而是给c增加了name属性,并赋值”jim“。因为,Coder的实例只能通过__proto__获取原型对象的属性、方法,并无权修改。原型对象的属性、方法只能通过如 Coder.prototype.name = "XXX" 这种方式去修改。我写上面代码注释,起初写的是错的,只是犹豫不觉,最后和js群里尼奥交流了下,才意识到错了。

 

现在你能看出点端倪来了吗?Coder原型中的方法、属性,是实例共享的,实例通过__proto__指针可以访问原型中的属性方法,但是不能修改。这是基于原型继承的基本原理。

上面不是js继承最好的实现,只是个入门的介绍。不用纠结new Coder() 构造函数不可以传姓名,以及Coder.prototype = new Person("未命名")这种蹩脚的写法。这篇文字还停留在介绍原型基本原理,并顺便说了几句面向对象的基本概念。后面的章节会介绍很多js继承的实践,会按照书中的逻辑,一本一本的介绍(其实是写读书笔记)。

本章描述的原型链的概念,几乎可以在所有介绍js的主流书中找到,很多博客中也有详细说明。最后用一幅图作为结束:

 你看懂了吗?下篇解释~~晚安

posted @ 2013-03-13 21:48  acjialiren  阅读(1162)  评论(0编辑  收藏  举报