JavaScript面向对象编程(一)原型与继承

原型(prototype)

JavaScript是通过原型(prototype)进行对象之间的继承。当一个对象A继承自另外一个对象B后,A就拥有了B中定义的属性,而B就成为了A的原型。JavaScript中定义了Object.create()方法。该方法创建一个对象,将方法的第一个参数作为所创建对象的原型(这个方法支持可选的第二个参数,用于对对象的属性进行描述,详细可以参考《JavaScript:The Definitive Guide》)。如下面代码所示:

1 var obj1 = {name:"obj"};
2 var obj2 = Object.create(obj1);
3 obj2.id = 1;//{id: 1, name: "obj"}
4 var obj3 = Object.create(obj2);
5 obj3.type = "type"; //{type: "type", id: 1, name: "obj"}

其中obj1是obj2的原型。所以obj2就可访问obj1中定义的name属性。在代码的第三行给obj2添加了一个自己的属性id并且赋值为1。这样obj2的值就是{id: 1, name: "obj"}。同理,obj2又作为obj3的原型,这样obj3就可访问obj2中定义的属性id以及obj2从obj1中继承的属性name。代码的第五行给obj3添加了自己的属性type并赋值为"type"。所以obj3的值就是{type: "type", id: 1, name: "obj"}。

原型链(prototype chain)

原型链是访问对象属性的机制。当我们访问一个对象的属性时,先从对象本身定义的属性找,如果找不到,再从对象的原型找,如果再找不到就从原型的原型找,依次类推,直到在Object。prototype都找不到的话,就返回undefined。这就是一个原型链。仍旧以上面的代码为例,

1 console.log(obj3.name);//obj
2 console.log(obj3.othername);//undefinded

当要访问obj3的name属性时,先从obj3自己定义的属性中寻找。而obj3只定义了type属性,那么就从原型链的下一个节点,即obj2中寻找。而obj2只定义了id属性,所以再向下一个节点,从obj2的原型寻找,即obj1。obj1中有定义了name属性,那么就返回obj1的name属性值,即"obj"。这个搜索的原型链就是obj3->obj2->obj1->Object.prototype。而整个原型链都没有定义othername这个属性,所以访问它的结果就是undefined。

必须注意的是,这个name属性的值是保存在obj1中的,因此当obj1的name值改变之后,从所有继承于它的对象中name的值都要改变(前提是这些对象没有自己定义name属性)。而且这个是动态的,在对象生成之后改变其原型的属性值也会产生效果。原因是name属性是从其原型中访问的。看下面代码,我们更新了obj1.name的值,然后发现从继承于obj1的对象访问name值的结果都发生了变化。

1 obj1.name="new name";
2 console.log(obj2.name);//new name
3 console.log(obj3.name);//new name

对象可以通过同名自有属性覆盖所继承的非只读属性。(属性的只读性在此文中暂不讨论,会在后续文章中讨论)。看下面代码:

1 obj3.name ="obj3";//override the name
2 console.log(obj3.name);//obj3
3 obj1.name = "another name";
4 console.log(obj3.name);//obj3
5 console.log(obj2.name);//another name

因为obj3自己定义了name属性,那么从obj3访问name属性时,就不必再从原型链的后续节点中寻找。所以obj1的name值改变对obj3.name没有影响。反观obj2,因为它没有自定义name属性,所以访问obj2.name时会继续搜寻原型链。当搜寻到obj1时找到了name属性,所以就访问到了obj1的name属性的值。

hasOwnProperty()函数

hasOwnProperty()可以判断对象是否定义了某个属性,而不是从其原型继承过来或者其原型链根本没有这一属性。该函数的参数是属性名,返回true代表定义了某个属性。下面的代码对三个对象检测了三个属性。

 1 obj1.hasOwnProperty("name");//true
 2 obj1.hasOwnProperty("id");//false
 3 obj1.hasOwnProperty("type");//false
 4 
 5 obj2.hasOwnProperty("name");//false
 6 obj2.hasOwnProperty("id");//true
 7 obj2.hasOwnProperty("type");//false
 8 
 9 obj3.hasOwnProperty("name");//true
10 obj3.hasOwnProperty("id");//false
11 obj3.hasOwnProperty("type");//true

isPrototypeOf() 函数

通过isPrototypeOf() 函数可以判断一个对象是否处于另外一个对象的原型链之中。需要注意的是,并不一定是直接继承的,在原型链中即可,看代码的第2行。

1 obj1.isPrototypeOf(obj2)//true
2 obj1.isPrototypeOf(obj3)//true
3 obj2.isPrototypeOf(obj3)//true
4 obj3.isPrototypeOf(obj3)//false

Object.prototype

前文有提到,在搜索原型链的过程中,如果在Object.prototype都搜索不到的话,那么就会返回undefined。因为所有的对象都从Object.prototype中继承属性和方法(当然可以用自定义的同名属性或方法覆盖)。所以,给Object.prototype添加属性或方法时,可从所有的对象中访问。

1 Object.prototype.myProperty = "myProperty";
2 var x = {};
3 console.log(x.myProperty);//myProperty
4 console.log(obj3.myProperty);//myProperty

代码中新定义了一个对象直接量x,它不定义任何自有的属性。但是可以从x访问myProperty原因是Object.prototype中有定义。从MDN中摘录一句话”All objects in JavaScript are descended from Object Object; all objects inherit methods and properties from Object.prototypeObject.prototype”。Object是构造函数。使用new Object()构造的对象都以Object.prototype为原型。

new关键字的作用初步总结下来就是:new Func()生成一个对象,用跟在new后面的函数Func对所生成的对象进行初始化,并且将Func.prototype作为所生成对象的原型。所以,所有的对象不论是用构造函数 new Object()构造的,或者是使用直接量初始化的,都从Object.prototype中继承属性和方法。同理,数组以Array.prototype为原型。函数以Function.prototype为原型。日期对象以Date.prototype为原型。字符串以String.prototype为原型。其中,Array、Function、Date和String都是构造函数。

 1 Date.prototype.showToday = "it is today";
 2 Array.prototype.showLength = "my length is...";
 3 Function.prototype.showParameters = "parameters...";
 4 String.prototype.showContext = "context";
 5 
 6 var a = new Date();
 7 console.log(a.showToday);//it is today
 8 
 9 var b = [];
10 console.log(b.showLength);//my length is...
11 
12 var c = function(){};
13 console.log(c.showParameters);//parameters...
14 
15 var d = "";
16 console.log(d.showContext);//context

在Object、Array、Function、Date和String中可以添加方法和属性,以达到扩展功能的效果。比如我们给字符串添加一个sayHello方法。

1 String.prototype.sayHello = function(){
2     alert("Hello, I am a string!");
3 }
4 var s = "";
5 s.sayHello();

后续的文章JavaScript面向对象编程(二)构造函数和类会对new 关键字和构造函数继续讨论。

参考文章:

1)《JavaScript:The Definitive Guide》第六章

2)MDN https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/prototype

原文地址:https://www.cnblogs.com/zhenchaoni/p/JavaScript_prototype.html