澄清在先Java 和Javascript是雷锋和雷峰塔的关系Javascript原名Mocha当时还叫做LiveScript创造者是Brendan Eich现任Mozilla公司首席技术官
年历史上第一个比较成熟的网络浏览器——Navigator版诞生在网景公司(Netscape)极为轰动
但是Navigator只能用来浏览不具备与访问者交互的能力比如用户提交一个数据表单如果表单为空浏览器是无法判断的只能直接提交给服务器端再把空值的错误返回让用户重新填写这样显然是低效率和浪费资源的
这个时候对于正处于技术革新最前沿的 Netscape开发一种实用的客户端脚本语言来处理这些问题变得必要起来于是这个任务落到了工程师Brendan Eich身上他觉得吧木必要设计得很复杂只要能搞定一些简单操作就够了比如判断用户有没有填写表单
年正是面向对象编程(objectoriented programming)的兴盛时代C++是最流行的语言而Java语言的版即将于次年推出Brendan Eich难免受其影响 他想将Javascript里面所有的数据类型看做是对象(object)这一点与Java非常相似但是他马上就遇到了一个难题到底要不要设计”继承”机制呢?
采用new关键字生成实例
处理表单验证这样简单功能脚本语言显然是不需要”继承”机制的然而如果Javascript里面都是对象就需要有一种办法来把所有对象联系起来最后Brendan Eich还是设计了”继承”只是他并没有引入”类”(class)的概念因为一旦有了”类”Javascript就是一种完整的面向对象编程语言了
这好像有点太正式了与设计初衷也远了同时增加了初学者的入门难度
参照到C++和Java语言都使用new命令来生成实例
C++这样写
ClassName *object = new ClassName(param);
Java这样写
Foo foo = new Foo();
那么也可以把new命令引入了Javascript用来从原型对象生成一个实例对象但是Javascript中没有”类”的话怎样表示原型对象呢?
依然是参照C++和Java使用new命令时都会调用”类”的构造函数(constructor)Brendan Eich简化了设计在Javascript语言中new命令后面跟的是构造函数不再是类
我们举个例子来说现在有一个叫做WD构造函数表示前端开发(webdevelopper)对象的原型
function WD(skill){
thisskill = skill;
}
对这个构造函数使用new关键字就会生成一个前端开发对象的实例
var WD = new WD(’html’);
consolelog(WDskill); // html
在构造函数中的this关键字它其实代表的是新创建的实例对象
new 出来对象的缺陷
采用new关键字用构造函数生成实例对象无法共享属性和方法
比如在WD对象的构造函数中设置一个实例对象的共有属性skill
function WD(skill){
thisskill = skill;
thissex = ’男’;
}
然后生成两个实例对象
var WD = new WD(’html’);
var WD = new WD(’css’);
这两个对象的skill属性是独立的修改其中一个不会影响到另一个
WDskill= ’Javascript’;
consolelog(WDskill);//“css”不受WD的影响
每一个实例对象都有自己的属性和方法的副本这不仅无法做到数据共享也是极大的资源浪费
引入prototype属性
为了实现属性和方法的共享Brendan Eich决定为构造函数设置一个prototype属性
这个属性包含一个对象(以下简称”prototype对象”)所有实例对象需要共享的属性和方法都放在这个对象里面;那些不需要共享的属性和方法就放在构造函数里面
实例对象一旦创建将自动引用prototype对象的属性和方法也就是说实例对象的属性和方法分成两种一种是本地的另一种是引用的
还是以WD构造函数为例现在用prototype属性进行改写
function WD(skill){
thisskill = skill;
}
WDprototype = { sex : ’男’ };
var WD = new WD(’html’);
var WD = new WD(’css’);
consolelog(WDsex); // 男
consolelog(WDsex); // 男
现在sex属性放在prototype对象里是两个实例对象共享的只要修改了prototype对象就会同时影响到两个实例对象
WDprototypesex = ’女’;
consolelog(WDsex); //女
consolelog(WDsex); // 女
由于所有的实例对象共享同一个prototype对象那么从外界看起来prototype对象就好像是实例对象的原型而实例对象则好像”继承”了prototype对象一样这就是Javascript继承机制的设计思想
三构造函数如何实现继承
现在有一个”MED”对象的构造函数(MED:Marketing Experience Design营销体验设计)
function MED(){
thisaim = "营销体验设计";
}
依然是”WD”对象的构造函数
function WD(skillsex){
thisskill = skill;
thissex = sex;
}
怎样才能使”WD”继承”MED”呢?
apply绑定构造函数实现
最简单的方法大概就是使用call或apply方法将父对象的构造函数绑定在子对象上也就是在子对象构造函数中加一行
function WD(skillsex){
MEDapply(this arguments);
thisskill = skill;
thissex = sex;
}
var WD = new WD("Html""男");
consolelog(WDaim); // "营销体验设计"
prototype模式实现
我们通常的做法是使用prototype属性如果”WD”的prototype对象指向一个MED的实例那么所有”WD”的实例就能继承MED了
WDprototype = new MED();//我们将WD的prototype对象指向一个MED的实例
WDprototypeconstructor = WD;
var WD = new WD("Html""男");
consolelog(WDaim); // 营销体验设计
这句
WDprototype = new MED();
相当于完全删除了prototype 对象原先的值然后赋予一个新值那么第二行又是什么意思呢?
WDprototypeconstructor = WD;
原来任何一个prototype对象都有一个constructor属性指向它的构造函数也就是说WDprototype 这个对象的constructor属性是指向WD的
我们在前一步已经删除了这个prototype对象原来的值所以新的prototype对象没有constructor属性需要我们手动加上去否则后面的”继承链”会出问题这就是第二行的意思
注意这是很重要的一点编程时务必要遵守下文都遵循这一点即如果替换了prototype对象
oprototype = {};
那么下一步必然是为新的prototype对象加上constructor属性并将这个属性指回原来的构造函数
oprototypeconstructor = o;
从prototype直接继承实现
由于MED对象中不变的属性都可以直接写入MEDprototype所以我们也可以让WD()跳过 MED()直接继承MEDprototype
现在我们先将MED对象改写
function MED(){ }
MEDprototypeskill = "MED";
然后将WD的prototype对象指向MED的prototype对象这样就完成了继承
WDprototype = MEDprototype;
WDprototypeconstructor = WD;
var WD = new WD("Html""男");
consolelog(WDskill); // MED
与前一种方法相比这样做的优点是效率比较高(不用执行和建立MED的实例了)比较省内存缺点是 WDprototype和MEDprototype现在指向了同一个对象那么任何对WDprototype的修改都会反映到MEDprototype
所以上面这一段代码其实是有问题的请看第二行
WDprototypeconstructor = WD;
这一句实际上把MEDprototype对象的constructor属性也改掉了!
consolelog(MEDprototypeconstructor); // WD
利用一个空对象作为中介来实现
由于”直接继承prototype”存在上述的缺点所以可以利用一个空对象作为中介
var F = function(){};
Fprototype = MEDprototype;
WDprototype = new F();
WDprototypeconstructor = WD;
F是空对象所以几乎不占内存这时修改WD的prototype对象就不会影响到MED的prototype对象
consolelog(MEDprototypeconstructor); // MED
利用 prototype模式的封装函数
我们将上面的方法封装成一个函数便于使用
function extend(Child Parent) {
var F = function(){};
Fprototype = Parentprototype;
Childprototype = new F();
Childprototypeconstructor = Child;
}
使用的时候方法如下
extend(WDMED);
var WD = new WD("Html""男");
consolelog(WDaim); // 营销体验设计
这个extend函数就是YUI库如何实现继承的方法
拷贝继承实现
上面是采用prototype方式来实现继承其实既然子对象会拥有父对象的属性和方法我们直接采用”拷贝”方法也可以达到效果简单说如果把父对象的所有属性和方法拷贝进子对象不也能够实现继承吗?
首先还是把MED的所有不变属性都放到它的prototype对象上
function MED(){}
MEDprototypeaim = "营销体验设计";
然后再写一个函数实现属性拷贝的目的
function extendCopy(Child Parent) {
var p = Parentprototype;
var c = Childprototype;
for (var i in p) {
c[i] = p[i];
}
}
这个函数的作用就是将父对象的prototype对象中的属性一一拷贝给Child对象的prototype对象
使用的时候这样写
extendCopy(WD MED);
var WD = new WD("Html""男");
consolelog(WDaim); // 营销体验设计
四”非构造函数”的如何实现继承
比如现在有一个对象叫做”MED”–营销体验设计
var MED = {
aim:’营销体验设计’
}
还有一个对象叫做”前端开发”
var WD ={
skill:’html’
}
请问怎样才能让”前端开发”去继承”营销体验设计”就是说我怎样才能生成一个”营销体验设计的前端开发”对象?
这里要注意这两个对象都是普通对象不是构造函数无法使用构造函数方法实现”继承”
object()方法
Json格式的发明者Douglas Crockford提出了一个object()函数可以做到这一点
function object(o) {
function F() {}
Fprototype = o;
return new F();
}
这个object()函数其实只做一件事就是把子对象的prototype属性指向父对象从而使得子对象与父对象连在一起
使用的时候第一步先在父对象的基础上生成子对象
var WD = object(MED);
然后再加上子对象本身的属性
WDskill = ’html’;
这时子对象已经继承了父对象的属性了
consolelog(WDaim); //营销体验设计
浅拷贝
除了使用”prototype链”以外还有另一种思路把父对象的属性全部拷贝给子对象也能实现继承
下面这个函数就是在做拷贝
function LightCopy(p) {
var c = {};
for (var i in p) {
c[i] = p[i];
}
//cuber = p;
return c;
}
使用的时候这样写
var WD = LightCopy(MED);
WDaim = ’前端开发’;
但是这样的拷贝有一个问题那就是如果父对象的属性等于数组或另一个对象那么实际上子对象获得的只是一个内存地址而不是真正拷贝因此存在父对象被篡改的可能
请看现在给MED添加一个”技能”属性它的值是一个数组
MEDskills = [’‘html’’’css’’Javascript’];
通过LightCopy()函数WD继承了MED
var WD = LightCopy(MED);
然后我们为WD的”技能”添加一个属性
WDskillspush(’teamwork’);
发生了什么事?MED的”技能”也被篡改了!
consolelog(WDskills); //‘html’’Javascript’’css’’teamwork’
consolelog(MEDskills); //‘html’’Javascript’’css’’teamwork’
所以LightCopy()只是拷贝基本类型的数据我们把这种拷贝叫做”浅拷贝”这是早期jQuery实现继承的方式
深拷贝
所谓”深拷贝”就是能够实现真正意义上的数组和对象的拷贝它的实现并不难只要递归调用”浅拷贝”就行了
function deepCopy(p c) {
var c = c || {};
for (var i in p) {
if (typeof p[i] === ’object’) {
c[i] = (p[i]constructor === Array) ? [] : {};
deepCopy(p[i] c[i]);
} else {
c[i] = p[i];
}
}
return c;
}
使用的时候这样写
var WD = deepCopy(MED);
现在给父对象加一个属性值为数组然后在子对象上修改这个属性
MEDskills = [’‘html’’’css’’Javascript’];
WDskillspush(’teamwork’);
这时父对象就不会受到影响了
consolelog(WDskills); //‘html’’css’’Javascript’’teamwork’
consolelog(MEDskills); //‘html’’css’’Javascript’
目前jQuery库使用的就是这种继承方法