Javascript原型链

http://blog.csdn.net/zerohjw/article/details/5868215
Javascript原型链
分成4个部分阐述:
1.如何创建一个对象
2.使用原型链prototype实现对象的继承.
3.原型链上属性和方法的联系与规则
4.深入剖析原型链。

一、如何创建一个对象:
1.使用关键字new创建。
var obj=new Object();
或者
function c(){}
var obj=new c();

2.使用花括号创建。
var obj={};
我们知道,function是定义一个函数,里面的var都是局部变量,那么如何声明一个对象的属性和方法呢。秘密就在于this关键字。
在函数内部使用this关键字,就相当于是对象的属性和方法的声明这个变量是存在的。具体的例子如下:
function a(){
    this.a=1;
    this.get=function(){
    return this.a;
    }
}
那么只要使用用var obj =new a();即创建一个对象的实例,里面就包括function里面的this属性和方法的就会直接拷贝到对象变量obj上。直接使用a.a的方法即可调用相应的属性和方法。
但是注意,除非实例化function a(),否则无法调用里面的this关键字指向的属性以及方法。
因为此时this上定义的属性以及方法类似于C++/JAVA等面向对象声明的属性以及方法。
因此,这种定义函数的方法也被称为JS的构造函数。

二、使用原型链prototype实现对象的继承.
所有函数function都有一个默认的属性prototype,通过用typeof函数测试我们可以看到prototype是一个对象。
alert(typeof(a.prototype));

那么这个prototype的属性到底有什么用呢~?
既然是一个对象那么赋值的时候就当然是付给prototype一个实例了。事实上,只要 把一个实例赋给prototype这个属性,那么这个function就具有这个实例的属性和方法。相当于我们常说的继承。
请看下面的代码:
function a(){
    this.ia=1;
    this.get=function(){
    return this.ia;
    }
}

//b构造函数
function b(){
    this.ic=10;
    this.getfatherclass=function(){
    return this.ic;
    }
}

b.prototype=new a();//继承实例。
var obj =new b();//实例化函数b

alert(obj.get());
就是这么简单的逻辑,我们就是使用对象b实例化的obj,输出继承了父类a的方法get。输出父类a里面的属性ia。
大家可以看到,这样就轻松地实现了继承。

那如果a又继承另外一个对象x怎么办?
答案当然是b会继承a所继承的所有东西。而他们的关系就像用prototype属性连起来一样。
所以我们可以看成也是一个由prototype连接而成的链表,人称原型链

三、原型链上属性和方法的联系与规则
首先我们明确一个概念,原型链就是一串好像用属性prototype连接而成的,每个节点具有属性和方法甚至函数过程的链。
这种链式结构我们无疑可以发现一个很重要的问题----链上的节点之间发生属性或者方法重名怎么办?
下面我们从读和写两方面来解析这条原型链的属性和方法的使用过程。

读:
从上面的例子里,alert(obj.get())输出的结果是什么呢,如果运行过一次,我们可以发现输出的结果是1.
但是我们可以看到,构造函数b定义的时候是没有get的方法的,同时,get方法调用的属性ia也是b中不存在的,那么我们在拥有prototype继承的构造函数所实例化的对象是如何调用属性和方法的呢。

结论是:对于调用实例化某一构造函数的对象的方法或者属性的时候,js会在原型链最结尾的点,也就是C++/JAVA上所描述的子类,上述例子中的b函数上,查找是否有所需要的方法或者属性,如果找不到的话,就会根据赋值给prototype的父节点对象,父构造函数上查找对应的属性和方法,就这样按照这种规则查找出对应名称的属性或方法。如果链上节点都不存在的话,当然就会输出undefined。

如果子节点有该属性或者方法,同时父节点也有的时候,当然会优先考虑子节点的,这也类似于C++/JAVA上所描述的覆盖的概念,子类的属性或者方法覆盖父类的。
而在上述例子中,get方法调用了一个this.ia,那么查找此此元素的时候,也会遵循上诉的原则,先查找构造函数b上是否有属性ia,没有的话再查找a上的构造函数上的属性ia。最后输出结果1。如果我们在构造函数b上添加一个属性this.ia=2,那么结果会如上述结论所说输出2.

写:
我们都知道,new操作会建立一个和构造函数一模一样的副本,开辟一个空间存放对象。但是我们从上面的操作可以看到,对于函数链上的属性,都可以被各个实例所访问,那么,js在实例化有prototype继承的构造函数的时候,是把链上的节点都实例化一次,还是被所有实例所共享呢?下面我们看下面例子。
function a(){
    this.ia=1;
    this.get=function(){
        return this.ia;
    }
}

function b(){
    this.ic=10;
    this.getfatherclass=function(){
    return this.ic;
    }
}

b.prototype=new a();//继承实例。
var obj1 =new b();//实例化函数b
var obj2 =new b();//实例化函数b
alert(obj1.ia);//输出1,因为他是父类a中的ia
alert(obj2.ia);//输出1,因为他是父类a中的ia
obj1.ia=2;
alert(obj1.ia);//输出2,因为写操作会在实例对象建立一个副本ia并且赋值2,无修改其原型链上的父节点。
alert(obj2.ia);//输出还是1,因为他是父类a中的ia
b.prototype.ia=3;//修改原型链上的属性ia。
alert(obj1.ia);//输出2,因为已经在实例上建立了副本,所以优先读取副本。
//输出3,因为子类b中不存在,所以还是查找父节点(prototype对象上的对象)上的ia,由于父节点上的ia已经被修改成3,所以输出3
alert(obj2.ia);
alert(new a().ia);//输出1

上面的例子说明了2件事:
1. 写操作并不是直接查找原型链上的属性,然后更改他, 而是直接在实例上创建在一个同名对象。由于实例上已经存在这个属性,所以下次读取的时候优先读取这个已存在的,相当于原型链上的原属性被覆盖(但是还是存在的)。

2. Prototype链上的节点(父类)都是被子类实例化后的对象所共享,也就是只有唯一的一个对象prototype,因此当我们修改b.prototype.ia=3;/后,obj2输出的也是3,说明他是被其他没有创建ia属性副本的实例所共享的。

这样我们就清晰了,原型链只有一条,并且不会因为子类实例化和把链上的节点,属性与方法都实例化,他将被所有子类的实例化对象所共享。

而这个原型链上的父类是准读不准写(通过子类实例化对象改变链上节点的属性与方法)。一旦你的实例化对象进行和父类属性与方法同名的写操作时,就会在实例化对象上建立对应的副本,同时进行写操作,此时这个实例也覆盖了原型链上的这个属性和方法。这种方法很好的保护了原型链上节点的共享性,不用担心不小心被其他实例所修改。这样大大地节省了内存,可以在频繁的对象创建中,有效地减少了内存空间的使用。

当然可以通过函数b上的prototype对象来修改链上的属性与方法,这样就可以做到一改全改,让子类共享的属性发生改变。如上面的 b.prototype.ia=3;操作。

这种做法也衍生出一种叫做延时绑定的思想,在使用的时侯才进行属性和方法的绑定,这种行为是可以通过原型链、prototype这个属性实现的。这样可以很方便地应用到你的编程中,但是注意,这种方法还是弊大于利的,因为可以在任何地方进行重绑定。所以程序者也许不知道在何时进行了绑定,使得代码很混乱,同时在大代码量的情况下,照成不可预料的结果。因此,延时绑定必须慎用。

四、深入剖析原型链
上面说到,原型链是由prototype连在一起的,事实上这种说法是不严谨,甚至可以说是错误的,因为prototype是一个对象,并且实例是没有prototype这个属性的,但是,有比prototype更好的,用来表现原型链的东西,这是对象实例后的一个私有属性,ie下是不可访问的,但是用firefox是可以查看的,它才是真正的作为实例在原型链上查找属性与方法的一个类似指针的属性。
可以这么说,prototype是函数所有的,而__proto__是实例化的对象存在的属性。
我们常把这个__proto__指向的原型成为父原型。因为prototype是一个对象的实例,所以原型也有父原型。
下面给出一段例子来解释这个现象。
function a(){
    this.ia=1;
    this.get=function(){
    return this.ia;
    }
}

function b(){
    this.ic=10;
    this.getfatherclass=function(){
    return this.ic;
    }
}

b.prototype=new a();//继承实例。
alert(a instanceof Function);//a是Function的实例;   
alert(a.__proto__ === Function.prototype);//a的父原型指向到Function的原型;   
alert(b.__proto__ === Function.prototype);//a的父原型指向到Function的原型
var obj =new b();//实例化函数b
alert(typeof(obj.__proto__));//输出obejct,说明是一个对象
alert(obj instanceof Object);//ture.obj是其中一个对象
alert(obj.__proto__===b.prototype);//ture。因为__prototype指向父原型
alert(obj.__proto__.__proto__===a.prototype);//ture。因为__prototype指向父原型
alert(obj.__proto__.__proto__.__proto__===Object.prototype);//ture。因为__prototype指向父原型
alert(Object.prototype.__proto__);//null。因为Object的原型是所有父原型的顶端,它不再具有父原型;  
alert(Function instanceof Object);//Function是Object的实例;   
alert(Function.__proto__ === Function.prototype);//Function的父原型指向到Function的原型;   
alert(Function.prototype.__proto__ === Object.prototype);//Function的原型的父原型指向到Object的原型   

alert(Object.__proto__ === Function.prototype);//Object的父原型指向到Function的原型; 


彻底理解原型链
在ECMAScript中,每个由构造器创建的对象拥有一个指向构造器prototype属性值的隐式引用,这个引用称之为 原型(prototype)。进一步,每个原型可以拥有指向自己原型的 隐式引用(即该原型的原型),如此下去,这就是所谓的原型链。在具体的语言实现中,每个对象都有一个 __proto__ 属性来实现对原型的隐式引用。
function Person( name ) {
    this.name = name;
}
var p = new Person();
// 对象的隐式引用指向了构造器的 prototype 属性,所以此处打印 true
console.log( p.__proto__ === Person.prototype );

// 原型本身是一个 Object 对象,所以他的隐式引用指向了
// Object 构造器的 prototype 属性 , 故而打印 true
console.log( Person.prototype.__proto__ === Object.prototype );

// 构造器 Person 本身是一个函数对象,所以此处打印 true
console.log( Person.__proto__ === Function.prototype );
有了 原型链,便可以定义一种所谓的 属性隐藏机制,并通过这种机制实现继承。

ECMAScript 规定,当要给某个对象的属性赋值时,解释器会查找该对象原型链中第一个含有该属性的对象(注:原型本身就是一个对象,那么原型链即为一组对象的链。对象的原型链中的第一个对象是该对象本身)进行赋值。反之,如果要获取某个对象属性的值,解释器自然是返回该对象原型链中首先具有该属性的对象属性值
利用原型链 Horse->Mammal->Animal 实现继承
// 声明 Animal 对象构造器
function Animal() {
}
// 将 Animal 的 prototype 属性指向一个对象,
// 亦可直接理解为指定 Animal 对象的原型
Animal.prototype = {
    name: animal",
    weight: 0,
    eat: function() {
    alert( "Animal is eating!" );
}
}
// 声明 Mammal 对象构造器
function Mammal() {
    this.name = "mammal";
}
// 指定 Mammal 对象的原型为一个 Animal 对象。
// 实际上此处便是在创建 Mammal 对象和 Animal 对象之间的原型链
Mammal.prototype = new Animal();

// 声明 Horse 对象构造器
function Horse( height, weight ) {
    this.name = "horse";
    this.height = height;
    this.weight = weight;
}
// 将 Horse 对象的原型指定为一个 Mamal 对象,继续构建 Horse 与 Mammal 之间的原型链
Horse.prototype = new Mammal();

// 重新指定 eat 方法 , 此方法将覆盖从 Animal 原型继承过来的 eat 方法
Horse.prototype.eat = function() {
alert( "Horse is eating grass!" );
}
// 验证并理解原型链
var horse = new Horse( 100, 300 );
console.log( horse.__proto__ === Horse.prototype );
console.log( Horse.prototype.__proto__ === Mammal.prototype );
console.log( Mammal.prototype.__proto__ === Animal.prototype );

以上基于原型的继承方式,虽然实现了代码复用,但其行文松散且不够流畅,可阅读性差,不利于实现扩展和对源代码进行有效地组织管理。不得不承认,类式继承方式在语言实现上更具健壮性,且在构建可复用代码和组织架构程序方面具有明显的优势。这使得程序员们希望寻找到一种能够在 JavaScript 中以类式继承风格进行编码的方法途径。从抽象的角度来讲,既然类式继承和原型继承都是为实现面向对象而设计的,并且他们各自实现的载体语言在计算能力上是等价的

目前一些主流的JS框架都提供了这种转换机制,也即类式声明方法,比如 Dojo.declare()、Ext.entend() 等等。用户使用这些框架,可以轻易而友好地组织自己的 JS 代码。

JavaScript 私有成员实现
function User( pwd ) {
    // 定义私有属性
    var password = pwd;
    // 定义私有方法
    function getPassword() {
        // 返回了闭包中的 password
        return password;
    }
    // 特权函数声明,用于该对象其他公有方法能通过该特权方法访问到私有成员
    this.passwordService = function() {
        return getPassword();
    }
 }
 // 公有成员声明
 User.prototype.checkPassword = function( pwd ) {
    return this.passwordService() === pwd;
 };
 // 验证隐藏性
 var u = new User( "123456" );
 // 打印 true
 console.log( u.checkPassword( "123456" ) );
 // 打印 undefined
 console.log( u.password );
 // 打印 true
 console.log( typeof u.gePassword === "undefined" );


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值