深入prototype源码之--Class

时间:2021-05-11 12:35:32

由于工作需要项目中要用prototype框架,所以这几天捣鼓了一下,研究了一下prototype 创建对象和类以及继承的一些源码,其实早在很久以前就接触prototype,然后直接看源码, 看着太蛋疼,里面的牵连太多太深, 绕来绕去,脑袋都绕晕了, 所以索性直接看prototype官方教程里面Class.create()这个函数的使用方式,了解了它的使用方式以后我们再来一步一步的反推它是怎么实现的,这里注意一下我不是直接讲的prototype源码实现这一块的内容,说穿了源码我看着也晕, 上面都说了东西太多牵连太深。好了, 废话就不多说了,我们直接进入主题

我们先来看一段基于prototype来创建对象以及继承的一段代码

//创建一个动物类
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在实现面向对象这一块还是很有优势的。

//下面我们来使用这个动物类
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()这个函数来创建类的,接受一个或者两个参数,这个函数重载了的

2.这个函数返回一个类,这个类又可以通过new的方式给为我们创建对象

3.每个类都有一个initialize方法,这个是一个构造器可以用于初始化构造对象

4.如果Class.create(base, child)方法重载了两个参数,那么base就是基类,child类是继承自base类的

上面说了prototype创建对象时的一些基本特点,下面我们就要来详细分析一下prototype创建到底是怎么来实现的,首先声明我不是看prototype框架源码来分析,我是根据这些写对象的一些特性来进行的,我们都知道如果声明一个函数,这个函数就是一个构造器,可以把它new出来的,看看下面的代码思路会更清晰

//这样也能创建一个类,至于它与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);
}
};