`
jimichan
  • 浏览: 278222 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

详解javascript类继承机制的原理

阅读更多
 本文着重解析javascript类继承机制,让你从底层了解javascript是怎样实现“继承”这一概念的。
                                                                jimichan在javaeye博客发布,转载请说明。
   目前 javascript的实现继承方式并不是通过“extend”关键字来实现的,而是通过 constructor function和prototype属性来实现继承。首先我们创建一个animal
js 代码
 
  1.   var animal function (){   //这就是constructor function 了  
  2.    this .name = 'pipi';    
  3.    this .age = 10;      
  4.    this .height = 0;      
  5. }      
  6.  //建立一个动物的实例     
  7.  var  a1 =   new  animal ();   

构造函数与其他普通函数区别在于,1.构造函数里有 this关键字,2.调用构造函数是使用的new关键字。通过new运算符调用构造函数 animal 后,系统就会返回一个对象,这个对象就相当于
js 代码
  1. var  a1 = { name:'pipi' ,age:10,height:0 }  
  2.   
  3. //或者   
  4. var  a1 =  new  Object();  
  5. a1.name='pipi';  
  6. a1.age = 10;  
  7. a1.height = 0; 
等同这样的方式来产生js对象。

到这里我们知道如何在js中定义一个类了,接下来我们展示如何写一个cat

js 代码
 
  1. var  cat =  function (){    
  2. this .play =  function (){     
  3.  alert('cat play')    
  4.  }    
  5.  }    
  6.  cat .prototype = new  animal ();    
  7.  //prototype 属性指向一个对象 
  8. var c1 = new cat();

到这里,cat就继承了 animal 对象,类cat的一个实例对象c1拥有属性name,age,height,和方法play了。
那么
prototype起到了一个什么样的作用呢?
prototype就好比一个指针,它指向一个object,这个object就称为子类对象的原型。当cat的对象被创建的时候,由于cat的构造函数拥有prototype属性,那么cat的实例就会间接指向这个原型对象了(说成间接的是因为每个object都有一个 constructor 属性指向它的构造函数)。
那么问题来了,“当我们修改对象 c1 的name属性的时候,会不会修改它prototype的name属性值呢?”,答案是否定的。
接下来详细解析:
1.访问name属性: 首先当我们第一次访问c1.name的属性的时候,我们会得到值“pipi”,这个和我们预料中的一样。但是计算过程你未必知道。
它计算的过程是这样的:第一步:检查c1对象中是否有name属性,找到的话就返回值,没有就跳到第二步,显然没有找到,因为cat的构造函数中没有定义。第二步:当第一步没有找时,去间接访问
prototype对象所指向的object,如果在 prototype对象中 找到的name属性的话,就返回找到的属性值。如果还是没有找到的话,再去递归地寻找 prototype对象的 prototype对象(去找它的爷爷) ,一直到找到name属性或者没有prototype对象为止。如果到最后还是没有找到name属性的话就返回undefined。

2.设定name属性:当我们设定c1对象的name属性时,及调用 c1.name= ' new name';  这个过程就简单多了。首先检查是否对象已有该属性,若已存在则修改当前值,若不存在则为该对象新增一个属性并设定当前值。值得一提的是,在设定值的过程中没有去访问
prototype属性。

为了加深理解,我们再看一个 read-write-read 的过程,第一次read的时候,由于自己的对象没有name属性,那么就会返回的原型对象的name属性的值。第二步,写入name的值,同样没发现本身对象有name属性,那么就在本身对象上新建一个name属性,然后赋值。第三步,再次读取name属性,由于在第二步中已经新建了name属性,此时就返回在第二步中设定的值。值得一提的是,在这三步中没有改变原型对象的值。

好了,到此详细分析了 javascript对象是如果实现继承的,其实和其他的面向对象语言不一样的是,javascript的继承机制是对象的原型继承而不是类型继承。

呵呵,欢迎看完,有不对的地方欢迎大家讨论!



分享到:
评论
14 楼 userzx110 2012-12-31  
userzx110 写道
咖啡刀 写道
不错的帖子!!!
在你上面说的一个问题!!
"构造函数与其他普通函数区别在于,1.构造函数里有 this关键字,2.调用构造函数是使用的new关键字。"
我觉得这句话很值的商榷!!!
主要是构造函数里有 this关键字这句话,这个我看未必.一如果我选用空构造函数呢?
var a=function(){};
a.prototype.firstName="hello";
a.prototype.secondName="world";
var b=new a();
alert(b.firstName);

二如下形式构造函数:
var a=function(){
return {
firstName:"Holle",
secondName:"World"
}
}
var b=new a();
alert(b.firstName);
这个难道里面具有this关键字吗???

所以我觉得这里应该值得推敲!!


javascript 里的this比较乱,分清情况才能说明,不管是不是构造函数都有this
普通的函数里面的this 指向的是window ,而 构造函数的this是指向本身。(我正在理解)看资料好想this 有四种存在情况,现在我就继承的和普通的来说下

//js所处的环境就是window

      xxname = 'xxTest'; //也可以window.xxname='xxTest'; window可不写
        function xxPutong()
        {
            //这里的this 是指向window
            var xxname = 'xxPutongTest';
            return function ()
            {//传递this到这个匿名方法体内 this还是指向window
                alert(this.xxname);
            }
        }
        xxPutong()();
打印出来是:'xxTest'
但是当代码如下的时候:
  xxname = 'xxTest'; //也可以window.xxname='xxTest'; window可不写
        function xxPutong()
        {
            //这里的this 是指向window
            var xxname = 'xxPutongTest';
            return function ()
            {//传递this到这个匿名方法体内 this还是指向window
                alert(xxname);
            }
        }
        xxPutong()();
打印出来是:'xxPutongTest'
分析:当你直接写xxname的时候而不是this.xxname的时候,匿名function向外找,首先找到了xxPutong里面的xxname 所以就是'xxPutongTest'(我的理解)
xxname = 'xxTest';
        var xxJiCheng = function ()
        {//这里的this都是指向xxJiCheng
            this.xxname = 'xxJiCheng';
            this.getname = function ()
            {//传递this到这个匿名方法体内 this还是指向xxJiCheng
                alert(this.xxname);
            };

        };

        new xxJiCheng().getname();
这个时候得到的反而是内部:'xxJiCheng'
跟上面普通的funtion 同样是alert(this.xxname)但是相反的结果这个得到的是内部的
xxname = 'xxTest';
var xxJiCheng = function ()
        {//这里的this都是指向xxJiCheng
            this.xxname = 'xxJiCheng';
            this.getname = function ()
            {//传递this到这个匿名方法体内 this还是指向xxJiCheng
                alert(xxname);
            };

        };

这个时候得到的反而是外部:'xxTest',当没前缀,通过window.xxname里取得。
跟上面普通的funtion 同样是alert(this.xxname)但是相反的结果这个得到的是内部的 这里打快了应该是外部

13 楼 userzx110 2012-12-31  
咖啡刀 写道
不错的帖子!!!
在你上面说的一个问题!!
"构造函数与其他普通函数区别在于,1.构造函数里有 this关键字,2.调用构造函数是使用的new关键字。"
我觉得这句话很值的商榷!!!
主要是构造函数里有 this关键字这句话,这个我看未必.一如果我选用空构造函数呢?
var a=function(){};
a.prototype.firstName="hello";
a.prototype.secondName="world";
var b=new a();
alert(b.firstName);

二如下形式构造函数:
var a=function(){
return {
firstName:"Holle",
secondName:"World"
}
}
var b=new a();
alert(b.firstName);
这个难道里面具有this关键字吗???

所以我觉得这里应该值得推敲!!


javascript 里的this比较乱,分清情况才能说明,不管是不是构造函数都有this
普通的函数里面的this 指向的是window ,而 构造函数的this是指向本身。(我正在理解)看资料好想this 有四种存在情况,现在我就继承的和普通的来说下

//js所处的环境就是window

      xxname = 'xxTest'; //也可以window.xxname='xxTest'; window可不写
        function xxPutong()
        {
            //这里的this 是指向window
            var xxname = 'xxPutongTest';
            return function ()
            {//传递this到这个匿名方法体内 this还是指向window
                alert(this.xxname);
            }
        }
        xxPutong()();
打印出来是:'xxTest'
但是当代码如下的时候:
  xxname = 'xxTest'; //也可以window.xxname='xxTest'; window可不写
        function xxPutong()
        {
            //这里的this 是指向window
            var xxname = 'xxPutongTest';
            return function ()
            {//传递this到这个匿名方法体内 this还是指向window
                alert(xxname);
            }
        }
        xxPutong()();
打印出来是:'xxPutongTest'
分析:当你直接写xxname的时候而不是this.xxname的时候,匿名function向外找,首先找到了xxPutong里面的xxname 所以就是'xxPutongTest'(我的理解)
xxname = 'xxTest';
        var xxJiCheng = function ()
        {//这里的this都是指向xxJiCheng
            this.xxname = 'xxJiCheng';
            this.getname = function ()
            {//传递this到这个匿名方法体内 this还是指向xxJiCheng
                alert(this.xxname);
            };

        };

        new xxJiCheng().getname();
这个时候得到的反而是内部:'xxJiCheng'
跟上面普通的funtion 同样是alert(this.xxname)但是相反的结果这个得到的是内部的
xxname = 'xxTest';
var xxJiCheng = function ()
        {//这里的this都是指向xxJiCheng
            this.xxname = 'xxJiCheng';
            this.getname = function ()
            {//传递this到这个匿名方法体内 this还是指向xxJiCheng
                alert(xxname);
            };

        };

这个时候得到的反而是外部:'xxTest',当没前缀,通过window.xxname里取得。
跟上面普通的funtion 同样是alert(this.xxname)但是相反的结果这个得到的是内部的
12 楼 userzx110 2012-12-31  
kinggooo 写道
正好在看prototype所以搜到了楼主的代码。看了一下,自己也试验了。总结了一下也算稍微明白了其中的一些过程。补充几点东西把,经过试验,发现当你在New一个对象的时候,从这个操作的行为上来看,有2种可能的推断。第一种是他以你的构造函数也就是Cat对象为模版在内存里重新建立了一个新对象。并且将Cat中的属性注入到新的对象中,然后会将Cat的原型对象的construtor属性也注入到新对象中,因此你可以在新对象后面用“点”操作符点出construcor属性,它其实是指向Cat对象的。当你get新对象的属性时它首先在新对象内部寻找该属性,如果不存在,它会寻找constructor.prototype对象中是否有需要的属性,如果还是没有就继续递归直到根对象。而当你set的时候,因为c1.name被你动态的设进去了,所以再次get的时候自然是新对象本身的name属性,当然它是不会重写它原型对象中的name属性的。
第二种解释是,当你创造一个新对象时,它首先将prototype内的属性包括constructor属性copy到新对象里,然后对这个对象执行Cat里的this.name =xxx 这样新对象里的name属性自然被覆盖了,但是这种覆盖显然不会影响到原型的name,因为新对象本身只不过是原型的拷贝而已。其他的我想也没什么多说的了,如果有发现错误,可以在下面提出来,在讨论讨论。


看这个帖子已经很久没人回了:今天收到说明是第二种解释。
var animal = function ()  //var 定义 animalie会被即可的执行
        {   //这就是constructor function 了 
            this.name = 'pipi';
            this.age = 10;
            this.height = 0;
        }
        //alert(animal().name); //(错误语句)
        //建立一个动物的实例    
var a1 = new animal();
var cat = function ()
        {
            this.play = function ()
            {
                alert('cat play');
            };
            this.name = "xxPiPi";
        }
        cat.prototype = new animal();   //所以这里是覆盖
        var c1 = new cat();
        alert(c1.name);
这里打印出来的是 :pipi,说明
11 楼 liuxinsudi 2012-03-20  
题目挺大的,要努力才行啊
10 楼 lovelylife 2008-05-13  
我也学了点,自己尝试写了下,但是感觉不是很好
不知道我说的对不对, 现在流行的框架prototype和。。。
他们的继承做的还不错, 但是给我的理解是,copy, 并非像C++那样的继承, 写法上很复杂, 真正去用继承去做项目的人不多, 大批的人在谈论这个, 但是实际效果不是很好
9 楼 jimichan 2008-05-07  
to 咖啡刀 

感谢你的提醒。
那句话是表达的有问题。本意是指this关键字在构造函数里面会起到特定的作用。
构造函数不一定非要有this关键字。你可以试试在构造函数里面return一个非Function对象试试,js很有意思。
8 楼 kinggooo 2008-04-07  
正好在看prototype所以搜到了楼主的代码。看了一下,自己也试验了。总结了一下也算稍微明白了其中的一些过程。补充几点东西把,经过试验,发现当你在New一个对象的时候,从这个操作的行为上来看,有2种可能的推断。第一种是他以你的构造函数也就是Cat对象为模版在内存里重新建立了一个新对象。并且将Cat中的属性注入到新的对象中,然后会将Cat的原型对象的construtor属性也注入到新对象中,因此你可以在新对象后面用“点”操作符点出construcor属性,它其实是指向Cat对象的。当你get新对象的属性时它首先在新对象内部寻找该属性,如果不存在,它会寻找constructor.prototype对象中是否有需要的属性,如果还是没有就继续递归直到根对象。而当你set的时候,因为c1.name被你动态的设进去了,所以再次get的时候自然是新对象本身的name属性,当然它是不会重写它原型对象中的name属性的。
第二种解释是,当你创造一个新对象时,它首先将prototype内的属性包括constructor属性copy到新对象里,然后对这个对象执行Cat里的this.name =xxx 这样新对象里的name属性自然被覆盖了,但是这种覆盖显然不会影响到原型的name,因为新对象本身只不过是原型的拷贝而已。其他的我想也没什么多说的了,如果有发现错误,可以在下面提出来,在讨论讨论。
7 楼 咖啡刀 2008-04-02  
不错的帖子!!!
在你上面说的一个问题!!
"构造函数与其他普通函数区别在于,1.构造函数里有 this关键字,2.调用构造函数是使用的new关键字。"
我觉得这句话很值的商榷!!!
主要是构造函数里有 this关键字这句话,这个我看未必.一如果我选用空构造函数呢?
var a=function(){};
a.prototype.firstName="hello";
a.prototype.secondName="world";
var b=new a();
alert(b.firstName);

二如下形式构造函数:
var a=function(){
return {
firstName:"Holle",
secondName:"World"
}
}
var b=new a();
alert(b.firstName);
这个难道里面具有this关键字吗???

所以我觉得这里应该值得推敲!!
6 楼 kinggooo 2008-04-02  
正好在看prototype所以搜到了楼主的代码。看了一下,自己也试验了。总结了一下也算稍微明白了其中的一些过程。补充几点东西把,经过试验,发现当你在New一个对象的时候,从这个操作的行为上来看,有2种可能的推断。第一种是他以你的构造函数也就是Cat对象为模版在内存里重新建立了一个新对象。并且将Cat中的属性注入到新的对象中,然后会将Cat的原型对象的construtor属性也注入到新对象中,因此你可以在新对象后面用“点”操作符点出construcor属性,它其实是指向Cat对象的。当你get新对象的属性时它首先在新对象内部寻找该属性,如果不存在,它会寻找constructor.prototype对象中是否有需要的属性,如果还是没有就继续递归直到根对象。而当你set的时候,因为c1.name被你动态的设进去了,所以再次get的时候自然是新对象本身的name属性,当然它是不会重写它原型对象中的name属性的。
第二种解释是,当你创造一个新对象时,它首先将prototype内的属性包括constructor属性copy到新对象里,然后对这个对象执行Cat里的this.name =xxx 这样新对象里的name属性自然被覆盖了,但是这种覆盖显然不会影响到原型的name,因为新对象本身只不过是原型的拷贝而已。其他的我想也没什么多说的了,如果有发现错误,可以在下面提出来,在讨论讨论。
5 楼 jimichan 2007-09-04  
呵呵,是的,谢谢提醒,一会补上
4 楼 simbel 2007-09-04  
引用
var = function(){  //这就是constructor function 了

是否掉了animal?
3 楼 jimichan 2007-09-04  
引用
不错,引用一下,能否讲一下原型模式呢?

javascript的原型机制和设计模式中的原型模式不一样,原型模式中使用对象clone技术,它是完全拷贝一个备份,而js的原型机制则是在运行时动态访问上层(或多层之上)的原型对象。原型模式最多只能使用到上一层对象的拷贝值。
2 楼 goalbell 2007-09-04  
不错,引用一下,能否讲一下原型模式呢?
1 楼 jimichan 2007-09-03  

相关推荐

    javascript继承机制实例详解

    主要介绍了javascript继承机制,以实例形式详细分析了javascript继承的原理与基于原型链的继承实现方法,具有不错的参考借鉴价值,需要的朋友可以参考下

    基于JavaScript实现继承机制之构造函数方法对象冒充的使用详解

    这是因为 JavaScript 中的继承机制并不是明确规定的,而是通过模仿实现的。这意味着所有的继承细节并非完全由解释程序处理。作为开发者,你有权决定最适用的继承方式。最原始的继承实现方式就是对象冒充,下面着重...

    基于JavaScript实现继承机制之原型链(prototype chaining)的详解

    如果用原型方式重定义前面例子中的类,它们将变为下列形式: 代码如下:function ClassA() {} ClassA.prototype.color = “blue”;ClassA.prototype.sayColor = function () { alert(this.color);}; ...

    Javascript原型链及instanceof原理详解

    instanceof:用来判断实例是否是属于某个对象,这个判断依据是什么呢? 首先,了解一下javascript中的原型继承的基础知识: javascript中的对象都有一个__proto__属性,这...//继承机制 function A(){ } A.prototype.

    JS原型和原型链原理与用法实例详解

    Javascript语言的继承机制一直很难被人理解。 它没有”子类”和”父类”的概念,也没有”类”(class)和”实例”(instance)的区分,全靠一种很奇特的”原型链”(prototype chain)模式,来实现继承。 Brendan ...

    庖丁解牛:纵向切入ASP.NET 3.5控件和组件开发技术

    8.2 clientscriptmanager类功能详解 301 8.2.1 registerarraydeclaration方法 301 8.2.2 registerclientscriptblock方法 302 8.2.3 registerclientscriptinclude方法 303 8.2.4 registerclientscriptresource...

    php网络开发完全手册

    5.5 类的引用、扩展与继承 75 5.5.1 类的引用 75 5.5.2 类的扩展与继承 76 5.6 操作与调用 77 5.6.1 静态类的调用 77 5.6.2 实例类型的判断方法instanceof 78 5.6.3 对象的克隆 78 5.7 一些设计观念 80 5.7.1 策略...

    庖丁解牛 纵向切入ASP.NET 3.5控件和组件开发 part1

    8.2 clientscriptmanager类功能详解 301 8.2.1 registerarraydeclaration方法 301 8.2.2 registerclientscriptblock方法 302 8.2.3 registerclientscriptinclude方法 303 8.2.4 registerclientscriptresource...

    庖丁解牛 纵向切入ASP.NET 3.5控件和组件开发 part2

    8.2 clientscriptmanager类功能详解 301 8.2.1 registerarraydeclaration方法 301 8.2.2 registerclientscriptblock方法 302 8.2.3 registerclientscriptinclude方法 303 8.2.4 registerclientscriptresource...

    庖丁解牛纵向切入ASP.NET 3.5控件和组件开发技术.pdf

    8.2 clientscriptmanager类功能详解301 8.2.1 registerarraydeclaration方法301 8.2.2 registerclientscriptblock方法302 8.2.3 registerclientscriptinclude方法303 8.2.4 registerclientscriptresource方法304...

    史上最全韩顺平传智播客PHP就业班视频,10月份全集

    9-28 6 javascript类与对象 9-28 7 给对象指定成员函数 自定义工厂方法 9-30 1 课程回顾 9-30 2 javascript的闭包 js变量作用域 9-30 3 仿超级玛丽兄弟游戏制作 9-30 4 构造方法 对象的常用操作 9-30 5 面向对象的...

    史上最全传智播客PHP就业班视频课,8月份视频

    9-28 6 javascript类与对象 9-28 7 给对象指定成员函数 自定义工厂方法 9-30 1 课程回顾 9-30 2 javascript的闭包 js变量作用域 9-30 3 仿超级玛丽兄弟游戏制作 9-30 4 构造方法 对象的常用操作 9-30 5 面向对象的...

    史上最全韩顺平传智播客PHP就业班视频,9月份全集

    9-28 6 javascript类与对象 9-28 7 给对象指定成员函数 自定义工厂方法 9-30 1 课程回顾 9-30 2 javascript的闭包 js变量作用域 9-30 3 仿超级玛丽兄弟游戏制作 9-30 4 构造方法 对象的常用操作 9-30 5 面向对象的...

    (全)传智播客PHP就业班视频完整课程

    9-28 6 javascript类与对象 9-28 7 给对象指定成员函数 自定义工厂方法 9-30 1 课程回顾 9-30 2 javascript的闭包 js变量作用域 9-30 3 仿超级玛丽兄弟游戏制作 9-30 4 构造方法 对象的常用操作 9-30 5 面向对象的...

    韩顺平PHP JS JQUERY 所有视频下载种子 货真价实

    9-28 6 javascript类与对象 9-28 7 给对象指定成员函数 自定义工厂方法 9-30 1 课程回顾 9-30 2 javascript的闭包 js变量作用域 9-30 3 仿超级玛丽兄弟游戏制作 9-30 4 构造方法 对象的常用操作 9-30 5 面向对象的...

    blog-note:阳姐讲前端:一个野生程序媛的成长之路

    闭包原理详解 一文搞懂this 内存泄漏问题 原型和原型链 内置对象 对象和类 深拷贝和浅拷贝 原型和原型链 封装、多态、继承 多种继承方式 异步编程 宏任务和微任务 异步解决方案 手写Promise DOM BOM ajax 存储

    asp.net知识库

    运算表达式类的原理及其实现 #实现的18位身份证格式验证算法 身份证15To18 的算法(C#) 一组 正则表达式 静态构造函数 忽略大小写Replace效率瓶颈IndexOf 随机排列算法 理解C#中的委托[翻译] 利用委托机制处理.NET中...

    亮剑.NET深入体验与实战精要2

    13.6.1 优先使用(对象)组合,而非(类)继承 478 13.6.2 针对接口编程,而非(接口的)实现 481 13.6.3 开放-封闭法则(OCP) 482 13.6.4 Liskov替换法则(LSP) 485 13.6.5 单一职责原则(SRP) 486 13.6.6 依赖...

    亮剑.NET深入体验与实战精要3

    13.6.1 优先使用(对象)组合,而非(类)继承 478 13.6.2 针对接口编程,而非(接口的)实现 481 13.6.3 开放-封闭法则(OCP) 482 13.6.4 Liskov替换法则(LSP) 485 13.6.5 单一职责原则(SRP) 486 13.6.6 依赖...

    python入门到高级全栈工程师培训 第3期 附课件代码

    08 类的装饰器的基本原理 09 类的装饰器增强版 10 类的装饰器的应用 11 自定制property 12 自定制property流程分析 13 自定制property实现延迟计算功能 14 property补充 15 元类介绍 16 自定义元类 17 函数复习 18 ...

Global site tag (gtag.js) - Google Analytics