来聊聊JavaScript核心之"伪类子类"

前面的文章已经介绍过类的三种模式-函数类,原型类和伪类。
一起来聊聊JavaScript核心中的"函数类""
来聊聊JavaScript核心之"原型类
来聊聊JavaScript核心之"伪类"
这篇文章谈论稍微高级一点的代码共享技术子类以及如何使用伪类模式实现相同的目标。
首先看一下为什么要引入父类和子类。
一个类可以工作的很好,当你想制造一批相似的对象。但是假如你想要第二类对象它只是和Car类依稀类似会怎么样?还看这个列子

var Car = function(loc) { 
var obj.loc = {loc: loc
}; 
obj.move = function() { 
obj.loc++; 
}; 
return obj;
};
var amy = Car(1);
amy.move();
var ben = Car(1);
ben.move();

假如你需要创建一辆警车,它有一个呼叫功能call();我们不会把这个方法写进函数类里,因为非警车的普通汽车不会拥有这个方法,那我们实现这个相同类的幼稚的一个方法就是从Car类中拷贝所有代码到第二个类中,然后重构这个类

var Van = function(loc) { 
var obj.loc = {loc: loc}; 
obj.move = function() {
 obj.loc++; }; 
obj.grab = function{ /*...*/}; 
return obj;
};
var Cop = function(loc) { var obj.loc = {loc: loc}; 
obj.move = function() { obj.loc++; };
 obj.call = function{ /*...*/}; return obj;};

我们为这两个类取不同的名字以区别他们。这里的grab方法是仅有敌人车才会用到的方法。而call方法仅仅对警车是有用的。看一下内存中的情况

这种场景下,我们有两个不同的构造函数,都可以生产出基本相同的对象,但是都有各自的新方法grab或者call.这种方法可以解决问题,但是存在大量重复代码,需要优化。
假设我们在这之前构造一个函数做大部分的工作,之后让Van和Cop类做剩下少量的定制,使得他们区别与普通汽车,我们把这个新的函数成为父类。重构后代码如下

var Car = function(loc) { 
var obj.loc = {loc: loc}; 
obj.move = function() { obj.loc++; }; 
return obj;
};
var Van = function(loc) { 
var obj = Car(loc); 
obj.grab = function{ /*...*/}; 
return obj;
};
var Cop = function(loc) { var obj = Car(loc); 
obj.call = function{ /*...*/}; return obj;};

此时我们有了两个完整的子类函数和一个父类函数。
此时内存里情况


实现了相同的功能,但是用了更少的代码。
当然,我们使用函数类来创建子类很容易。那么现在可以想一下如果用伪类模式应该怎么做。应该怎样写伪类模式的子类呢?我们从头开始分析,用一个新的例子

还是Van是Car的子类,zed是Car的实例对象,amy是Van的实例对象。到此,父类Car我们已经编写完毕,下面继续编写子类。我们先为子类写一个构造函数,内存里应该构建了一个新的子类,并且这个子类应该从父类中继承很多属性

那么这个子类的构造函数怎么写呢?
var Van = function(loc){ /.../};

当然可以写成和父类一样,即:this.loc=loc;

var Van = function(loc){ this.loc = loc;};

但是这样做的话没什么意义,一旦Car有什么修改,Van也得跟着修改。所以这样不好。或者我们也可以在Van中调用Car函数,即这样

var Van = function(loc){ new Car(loc);};

但是这样仍然有问题,首先在内存里,它将产生一个新的实例对象(但不会作为该函数返回值),而代码 Var amy = new Van(9);将会创建新的对象作为调用new Van()的结果。


这里想一下使用关键字new运行函数时,函数里会自动运行额外的代码

其中比较特殊的是,它将会设置关键字this等于一个全新创建的对象。正是我们因为使用了new创建Van(),它将创建另一个新的对象,但是并不意味着这个Car函数会运行这个新对象作为this的值。事实上两个不同的object.create语句被插入到两个不同的函数里,会有两次使用关键字new的结果。如果这么写,不管你的Van有没有用new关键字来创建实例,在内存里都会创建一个全新的委托到Car类上的对象。如果你用new关键字调用Van,那么你已经创建了一个新对象,并且你并不需要另一个对象。或许有人会想直接将这个使用Car构造器构造的对象赋值给关键字this,比如这样

var Van = function(loc){ new Car(loc);};

但是在代码给关键字this赋值是不允许的,即使解释器允许做这样的事情。因此它仍旧解决不了创建了两个不同对象的问题。那么或许也可以去掉关键字new,直接调用Car函数呢

var Van = function(loc){ Car(loc);};

这样做看似行,那么仔细想想Car函数中的this会指向什么呢?答案是Car函数将会在全局作用域的执行环境中去运行,this指向global。因为这个Car函数被作为一个自由函数调用。如果这样写调用var amy = new Van(9);实际上将会绑定一个指向数字9的全局变量loc


你的Van实例将完全不会被Car函数里的代码影响。
那么如果我们调用Car函数,我们只需要确定它运行在正确的执行环境中,我们有方法可以指定函数被调用时的执行环境,.call方法允许我们在调用函数时指定一个特定的执行环境。这意味着Car函数里参数this表现得更像一个传入函数的参数。这样写

Car.call(this,loc);

就可以使Car函数的执行环境与Van函数相同。那么这就是基于目前分析的解决方案

Var Car = function(loc){ 
this.loc = loc;};
Car.prototype.move = function(){ this.loc++;};
Var Van = function(loc){ Car.call(this,loc);};

此时我们运行

console.log(amy.loc);

是可以成功输出"9"的。但是此时amy.move()并不能运行,当执行到这一句时,代码会终止。因为amy对象没有move方法,根据原型链原理,它会在它的构造函数的原型上去查找,即Van.prototype,但是Van.prototype里面也没有,Van.prototype的原型是Object.prototype,但是也没有move方法。
到此,我们已经实现让子类的特定代码调用父类的特定代码,但由于事实上我们使用原型链实现继承的效果,我们需要将子类原型链到父类原型,是父类中相似的代码能被继承。amy确实委托到Van.prototype,但是它和Car.prototype并没有一毛钱的关系,因此我们需要将Van.prototype委托到Car.prototype。理情况下,我们可以修改Van.prototype,以便它能直接委托到Car.prototype

var.prototype._proto_ = Car.prototype;

内存中情况是这样,我们希望他这样工作,Van.prototype对象一直向上委托到Car.prototype。这样它就会和Car实例变得很类似,因为它们都能把字段查询委托给Car的prototype属性。

但是官方版本的语言里这种写法是不允许的,取而代之我们将不得不用我们的新创建的对象重写这个原型对象。
人们常常期望Van.prototype只是简单的等于Car.prototype

Van.prototype = Car.prototype;

但是注意当你把一个变量或者属性赋值给另一个时JavaScript不会做任何拷贝操作,因为它们是同一个对象。


如果是这样的话,加在Car原型上的方法,一定在Van原型中存在,反之亦然。因此如果我们加了一个Van.prototype。grab方法,那么会使Car.prototype也拥有一个.grab方法,简而言之,我们不希望Van.prototype和Car.prototype是同一个对象,所以我们还有找一种替代方法来替代Van.prototype和Car.prototype相等。
这种替代方法就是Object.create方法,如果我们想把Van.prototype委托给Car.prototype,我们只需要把它传递给Object.create生成一个委托该对象的新对象就好。

Van.prototype = Object.create(Car.prototype)

现在如果我们需要amy有一个新的方法grab,那么我们之间将次方法添加到构造函数的的原型对象上就行了

Van.prototype.grab = function(){/*...*/};

到此,我们似乎完成了整个功能,但是本属于Van.prototype的一个属性丢失了,就是.constructor。


因此需要添加这行代码
Van.prototype.contructor = Van;

这就是伪类模式下的子类,完整代码

Var Car = function(loc){ this.loc = loc;};Car.prototype.move = function(){ this.loc++;};Var Van = function(loc){ Car.call(this,loc);};Van.prototype = Object.create(Car.prototype);Van.prototype.contructor = Van;Van.prototype.grab = function(){/*...*/};

到此,如果你从我的前几篇文章一路读来,那么你关于不同代码的复用模式已经了解很多,包括函数模式、修饰器模式和JavaScript原生提供的所有主要的类模式以及它们相应的子类技术。
完。

作者:长梦未央
图片来源:优达学城付费课程
个别文字摘自教学视频老师的讲解

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容