因为工做须要项目中要用prototype框架,因此这几天捣鼓了一下,研究了一下prototype 建立对象和类以及继承的一些源码,其实早在好久之前就接触prototype,而后直接看源码, 看着太蛋疼,里面的牵连太多太深, 绕来绕去,脑壳都绕晕了, 因此索性直接看prototype官方教程里面Class.create()这个函数的使用方式,了解了它的使用方式之后咱们再来一步一步的反推它是怎么实现的,这里注意一下我不是直接讲的prototype源码实现这一块的内容,说穿了源码我看着也晕, 上面都说了东西太多牵连太深。好了, 废话就很少说了,咱们直接进入主题javascript
咱们先来看一段基于prototype来建立对象以及继承的一段代码java
//建立一个动物类 var Animal = Class.create({ //初始化构造函数 initialize: function(name, type) { this.name = name; this.type = type; }, //动物类的方法 speak: function() { alert("my name is "+name); } });
上面这段代码描述一个基于prototype写的动物类, 不得不佩服在面向对象这一块prototype设计的很是整洁和优雅,既然建立了一个类,下面咱们来使用这个,若是有过java背景或者面向对象高级语言的同窗是否是以为调用方式有点类似,这样就体现出javascript在实现面向对象这一块仍是颇有优点的。json
//下面咱们来使用这个动物类 var animal = new Animal('小白', 'dog'); animal.speak();
可是如今问题来了,光能建立类就不能对刚刚建立的类进行扩展或者继承嘛, 答案是确定的,要否则怎么说是大名鼎鼎的prototype嘛,好, 下面咱们就来实现继承这个Animal类数组
//建立人类,继承自动物类 var Person = Class.create(Animal, { initialize: function($super, name, type) { $super(name, type); }, work: function() { alert(this.name+"今天工做了一成天,虽然有点累但很开心"); } });
好到这里面咱们已经看到prototype的强大之处了吧,那如今咱们来讲说这种方式建立类的一些特性,以便咱们在后面来实现这样写面向对象作一些铺垫,那到底有什么一些特性或者特色呢闭包
1.是经过Class.create()这个函数来建立类的,接受一个或者两个参数,这个函数重载了的app
2.这个函数返回一个类,这个类又能够经过new的方式给为咱们建立对象框架
3.每一个类都有一个initialize方法,这个是一个构造器能够用于初始化构造对象函数
4.若是Class.create(base, child)方法重载了两个参数,那么base就是基类,child类是继承自base类的this
上面说了prototype建立对象时的一些基本特色,下面咱们就要来详细分析一下prototype建立究竟是怎么来实现的,首先声明我不是看prototype框架源码来分析,我是根据这些写对象的一些特性来进行的,咱们都知道若是声明一个函数,这个函数就是一个构造器,能够把它new出来的,看看下面的代码思路会更清晰spa
//这样也能建立一个类,至于它与prototype的优劣只有本身体会了 function Animal(name, type) { this.name = name; this.type = type; this.speak = function() { alert("my name is"+this.name); }; } var animal = Animal("小黄", "dog"); animal.speak();
如今你们知道若是要采用new的方式确定要有构造器,说白了就是一个函数,那如今咱们知道Class.create()这个方法返回的是一个函数了吧,可是这究竟是个怎么样的函数呢,接着在进行挖掘,首先咱们来分析当传入一个参数也就是建立一个类没有继承的时候是这样一种状况
var Animal = Class.create({ //初始化构造函数 initialize: function(name, type) { this.name = name; this.type = type; }, //动物类的方法 speak: function() { alert("my name is "+name); } });
其实就是传入的了json参数,里面放了两个函数,一个构造函数和一个成员函数,刚刚咱们讲了这个Class.create()方法返回的是一个函数,传入的又是一个json, 那咱们确定要将json里面的函数复制到返回出来的这个函数里面,请看下面代码
var Class = { create: function(object) { var fn = function() {}; fn.prototype = object; return fn; } }; //好了, 如今咱们能够用上面的这个Class.create()方法来建立一个类 var Animal = Class.create({ initialize: function(name) { this.name = name; }, speak: function() { alert("my name is "+this.name); } }); //下面我来new一下建立这个动物类 var animal = new Animal(); animal.initialize("dog"); animal.speak();
上面这段代码是否是和prototype建立对象有点不同,细心的同窗会发现咱们在new Animal()这个动物类的时候没有参数, 构造参数是经过animal.initialize()来初始化的,这样就不对了, prototype不是经过构造函数的时候就直接调用initialize()函数来进行初始化的吗,没错,因此咱们要将上面的代码作一些小小的修改,上代码吧
var Class = { create: function(object) { var fn = function() { this.initialize.apply(this, arguments); }; fn.prototype = object; return fn; } }; var Animal = Class.create({ initialize: function(name) { this.name = name; }, speak: function() { alert("my name is "+this.name); } }); //下面我来new一下建立这个动物类 var animal = new Animal("dog"); animal.speak();
到这里你们应该能看清楚究竟是哪儿作了改动吧,this.initialize.apply(this, arguments); 就是在这儿作了手脚,fn这个函数在建立类的时候是会被返回的,因此在new这个被返回的函数里面能够作初始换的一些东西,就是调用咱们的initialize()函数来进行初始化了, 这里声明一些哈,若是没有对javascript, this, 闭包,做用域,原型链方面的知识理解就很是痛苦了,不过我仍是会大概说一下,this.initialize.apply(this, arguments);咱们对这句话进行分解, this 其实指的是{init:functdion(), speak:function() {}}这个传进去的对象,apply(this, arguments), apply是将当前函数的上下文改变也就是把this改变,arguments就是一个函数运行时的参数,这样说可能有点同窗不是很明白,若是实在看不明白javascript基础知识须要补补课了。如今咱们能实现向prototype的语法同样来建立一个了类, 但仅仅是建立一个类,若是须要继承怎么办呢, 就像这样Class.create(base, object); 这个object要继承base, 你们都知道若是在继承的话确定是要将base里面的成员和方法copy到object里面来,就这么简单吗, 咱们来仔细想一下吧,应该有如下几点
首先将基类的成员copy到子类里面
a. 若是子类的成员变量和积累的同名,会覆盖基类的
b.子类的成员方法和基类的同名会覆盖积累的
c.能不能子类和基类的成员方法进行重载呢,答案是确定的
d.手动控制调用基类的构造方法进行初始化会更灵活
以上这些就是prototype建立类的一些特性, 可能还不完善,欢迎你们讨论。 下面咱们就用代码具体的实现,我会尽可能用多的注释来标注方便你们理解和传播
var Class = { /* *功能:建立一个类 *参数: * 1.base 表示基类,但必定要是functdion类型的 * 2.object 表示要建立的子类,json数据格式类型的 */ create: function(base, object) { var fn = null; //这个判断主要是建立有没有继承的类 //通常状况其实就是检测create()方法是一个参数仍是两个参数 if(typeof base == "function") { //这个就是建立要返回的函数 fn = function() { var args = []; //这个判断是检测子类initialize($super, ...)方法里面参数 //若是有$super这个参数就是传了基类的构造函数能够调用,反之就没有 if(object.initialize.length > arguments.length) { //这里就是把基类的构造函数加入到args函数里面 //这里你们能够仔细体会一下这儿为何要用一个闭包,还有 //apply(fn.prototype, arguments)方法里面两个参数的意义 args.push(function() { base.prototype.initialize.apply(fn.prototype, arguments); }); } //将构造函数里面的参数加入到这个args数组 for(var i = 0 ; i < arguments.length ; i++) { args.push(arguments[i]); } //执行构造函数的初始化参数 this.initialize.apply(this, args); }; //先将积累里面的成员变量和方法copy到返回函数fn里面 for(var property in base.prototype) { if(property != "initialize") { fn.prototype[property] = base.prototype[property]; } } //在将子类里面成员变量和方法copy到返回函数fn里面 //注意这里为何要先拷贝基类在拷贝子类,缘由是 //基类和子类的成员变量和方法重名的话优先的是子类的 for(var property in object) { fn.prototype[property] = object[property]; } } //这个是没有继承的状况就很是简单了, 上面有讲到, //这里就再也不细细叙述了 else { fn = function() { this.initialize.apply(this, arguments); } fn.prototype = base; } return fn; } } //下面就能够像prototype语法同样写出优雅的代码, //这段是prototype官方Class.create()APi的源码实例 //建立一个动物类 var Animal = Class.create({ initialize: function(name, sound) { this.name = name; this.sound = sound; }, speak: function() { alert(this.name+"says:"+this.sound+"!"); } }); //实例化调用 var dog = new Animal('gogo', "wangwang"); dog.speak(); //建立一我的类,继承自动物类 var Snake = Class.create(Animal, { initialize: function($super, name) { //alert($super); $super(name, 'hisssssssssssssss'); }, say: function() { alert("my name is "+this.name); } }); //实例化调用 var ringneck = new Snake("Ringneck"); ringneck.speak(); ringneck.say();
说到这里prototype的 Class.create()方法的内核源码基本上就告一段落了,固然还有一些其余的方式,也各有千秋,你们能够看一下
var Class = { create: function() { return function() { this.initialize.apply(this, arguments); } } }; var Animal = Class.create(); Animal.prototype = { initialize: function(name) { this.name = name; }, say: function() { alert("my name is "+this.name); } };