js原型prototype,proto与function之间的关系图

原文地址:http://peihexian.iteye.com/blog/2147079
美工人员一般是用js写个function校验表单数据的合法性,例如

function checkAndSubmit(){
   if (document.getElementById('username').value=='') {
      alert('请输入登录用户名!');
      return false;
  }
  document.form[0].submit();
} 
<input type="button" value="登录" onclick="return checkAndSubmit();"/>

 

点击登录按钮后触发function的执行,写到这对美工来讲也就够用了,但是对程序员不行,程序员得封装、继承与多态不是?

 

如何像写java或者c#那样写复杂的js代码呢?

 

Question:如何在js中定义类?

Answer:定义function就是定义类了,例如:

function Person(){
}
var p=new Person();

 

上面定义的Person就是类,下面的p就是Person类的实例了,实例化类实例的时候是通过new 加类名称Person及构造函数()完成的。

如果类有构造函数参数,则类的定义就是下面这样:

function Person(name,age){
   this.name=name;
   this.age=age;
}

var p1=new Person('tom',11); //创建Person的类实例,传递构造函数参数。

 

除了和美工写function一样的写法,能不能显得专业一点呢?可以的,像下面这样:

        var Person=function(name,age){
            this.name=name;
            this.age=age;
        }

        var p1=new Person('tom',11);

 

 

这里面有个问题,见下面的代码:

 

var p2={name:'jake',age:12};

 

上面的p2同样是类实例,同样有name和age属性,这两种定义方法一样吗?看看chrome调试里面的输出:


js原型prototype,proto与function之间的关系图
  

通过chrome的调试工具可以看到,p1就是Person类型的实例,p2是Object类型的实例。

p2的另外一种等价写法类似于

 

        var p3=new Object();
        p3.name='mike';
        p3.age=2;

 

在chrome里面调试的输出结果如下:

 


js原型prototype,proto与function之间的关系图
 看到了吧?p2和p3都是Object类的实例,不是我们自定义类的类实例!

所以,定义自定义JS类的正确方式就是function 类名称(构造函数参数)这样去定义,而不是 var o={key:value} 或者var o=new Object();o.key=value;等方式。

 

Question:如何定义类属性和类方法及静态方法? 

Answer:

           function Person(name,age){

            this.name=name;
            this.age=age;
            this.say=function(){
                console.log(this.name);
            }
        }
        var p1=new Person('tom',11);
        p1.say();

 以上就是定义类属性name和age以及方法的示例,很简单。

在java或者c#里面都可以定义静态方法,js里面可以定义静态方法吗?答案是可以!定义静态方法的语法是类名.静态方法名称=function(){}的方式,例如:

        function Person(){}//定义类
        //定义Person类的静态方法test
        Person.test=function(){
            console.log("static method");
        }
        //调用静态方法
        Person.test();

 

除了在定义function的时候定义属性和方法,还可以在类定义以后追加属性和方法,例如:

 

        function Person(name,age){
            this.name=name;
            this.age=age;
            this.say=function(){
                console.log(this.name);
            }
        }

        var p1=new Person('tom',11);
        //利用prototype追加非静态方法,要是直接直接写Person.hello=function就是定义静态方法了
        Person.prototype.hello=function(){
            console.log(this.age);
        }

        p1.hello();

 这里面出现了一个prototype,这个玩意是个什么东西呢?是用来追加非静态属性或者方法的吗?千万不要这么理解,这个prototype叫做原型,是独立存在的对象,每一个类都有一个对应的prototype,即每一个类都一个对应的原型,有点类似于java里面每一个类被JVM加载以后都有一个对应的Class对象一样,见下面的图:

 

 


js原型prototype,proto与function之间的关系图
 

 

在上面的图中,每一种类类型都有对应的prototype原型对象,是不是很像JVM里面的Class对象一样?Object、Function、Cat都是类类型,都有对应的prototype对象,cat,o都是类实例而不是类,所以没有对应的prototype。

 为了让看上面的乱麻一样的关系图简单一点,先说点简单的,每一个类都有一个prototype属性,该属性指向对应的prototype对象,如Object有一个protytpe属性,指向与object唯一对应的原型对象,Cat类也有一个prototype属性指向了与Cat类唯一对应的prototype原型对象,上面的图中没有写出来,其实就是类名称右侧向右的那根黑色箭头线(说的是类的prototype属性)。

好了,知识点一:类的prototype属性指向与该类唯一对应的prototype对象。

 接着说简单的,死记硬背型的,原型对象(就是图里面的prototype)都有一个constructor属性,该属性反过来指向该类的构造函数(其实就是那个function本身啦),例如

function Person(){
}

Person.prototype.constructor==Person //true

 

 知道了这一点,看上面图中的原型对象的constructor都指向类本身就明白了,问题是知道有这么回事可以干啥?原型对象知道构造函数在哪里以后就可以实例化类实例的时候调用该构造函数了,从这一点来讲,和JVM里面知道类的Class信息以后可以通过反射去实例化类实例是一样的道理。

 

搞清了类(function),类原型对象(prototype对象,同时也是类的prototype属性所指向的对象),原型对象的constructor这几个死记硬背型的关系以后,下面就是_proto_这东东了。

 

javascript中一切都是对象,每个对象都是基于原型对象创建的,每个对象中都有__proto__属性,这个属性指向的就是它基于的原型对象

例如上面的o={},这里面的o的类型是Object类型的,所以o的_proto_属性指向了Object.prototype也就是说o._proto_==Object.prototype,但是这种关系没办法写js去验证,因为除非FF以外其他浏览器都不让程序员访问_proto_这个属性。

继续看简单的,上面的mycat=new Cat(),即mycat是Cat类的实例,所以mycat._proto_==Cat.prototype,即mycat的_proto_属性指向它对应类型的prototype属性或者叫对应的原型对象了。

上面的Cat是个类,也是一个function,JS里面所有的function都是Function的实例,注意后面的是大写F,所以Cat类的_proto_指向了Function类对应的原型对象。

原型对象也是对象吧(都叫原型对象了当然是对象的啦)?是对象就得有类型吧?所以上面图中的Cat类对应的原型对象和Function类对应的原型对象的_proto_指向了什么?指向了Object的prototype,任何的原型对象都是Object的实例。

 

好了,再往下就应该是研究Object和Function之间的纠缠关系了,但是和我要写的类的继承关系不大,先写类的继承问题。

 

Question:如何定义继承关系?

Answer:

 

        function Base(){
            console.log("base constructor");
            this.hello1=function(){
                console.log("hello1");
            }
        }

        function Child(){
            console.log("child constructor");
            this.hello2=function(){
                console.log("hello2");
            }
        }
        Child.prototype=new Base();
        var c=new Child();

        c.hello2();
        c.hello1();

 

  代码好写,理解后面的关系头疼的我出去溜达了好几圈,大爷的。

  最关键就是红色那一行,Child.prototype=new Base();  上面分析过了,类的prototype属性指向与本类唯一对应的原型对象上面,也就是说,没有这行代码的话Child.prototype指向的是与Child唯一对应的原型对象,后面的new Base()创建出来了一个Base类的实例,该实例肯定可以调用Base类的所有方法(是Base的实例嘛),然后让Child.prototype=Base类的实例发生了什么?这里面引出了原型链这个东西。

把那一行代码先拆成两行吧,好理解一点:

//Child.prototype=new Base()拆分成以下两行
var o=new Base();
Child.prototype=o;

 

 上面的o是实例对吧?具体来说是Base类的实例对吧? 实例没有prototype属性,但是有_proto_属性是吧?忘了的话看前面加黑加粗的文字,o._proto_==Base.prototype对吧?

 Child.prototype=o以后,Child.prototype._proto_==Base.prototype对吧?好了,到这就先打住,一般来讲还没乱呢。

 

var c=new Child(); 要是没有前面的红色那行Child.prototype=new Base();的话,c就是Child类的实例吧?c._proto_==Child.prototype吧?很简单,但是前面加红那一行让Child.prototype._proto_=Base.prototype了是吧?

那现在c._proto_._proto_=Base.prototype了吧?

c.hello2()调用的就是Child类里面自己的,这个肯定没问题的。

c.hello1()调用的时候Child类里面没有这个方法吧?没有为啥还能调用Base类的呢?JS调用方法或者读取类属性的时候从_proto_链中去找,一直找到Object.prototype为止!

c._proto_找不到hello1()方法,那就到c._proto_._proto_里面去找,上面已经推导出了,c._proto_._proto_是Base.prototype,所以就找到了hello1()方法,也就可以调用成功了。

 

但是按照正常人类的理解,Child.protoype=new Base();以后不是Child类原来指向的Child.prototype就没了吗?为啥尼玛的new Child()还是能有Child类里面的方法呢?这是个问题

 

这里面有一篇特别好的文章,看这里吧:http://weizhifeng.net/javascript-the-core.html