聊一下对JavaScript中的伪类模式的个人理解

JavaScript中的伪类模式

JavaScript的原型中存在诸多矛盾。某些看起来有点像基于类的语言的复杂语法问题遮蔽了它的原型机制。它不让对象直接从其他对象继承,反而插入了一个多余的间接层,从而使构造函数产生对象。
当一个函数对相被创建时,Function构造器产生的函数对象会运行类似遮掩的一些代码:
this.prototype ={constructor:this};

新函数对象被赋予一个prototype苏花型,其值是包含一个constructor属性且属性值为新函数对象。该Prototype对象是存放继承特征的地方。因为JavaScript语言没有提供一种方法去确定哪个函数是打算用来作结构的,所以每个函数都会得到一个Prototype对象。
constructor属性没什么用。重要的是Prototype对象。当采用构造器调用模式,即使用new前缀去调用一个函数时,这将修改函数执行的方式。如果new运算符是一个方法而不是一个运算符,它可能会像这样执行:

聊一下对JavaScript中的伪类模式的个人理解_第1张图片

我们也可以定义一个构造器扩充它的原型:

聊一下对JavaScript中的伪类模式的个人理解_第2张图片

现在我们可以构造一个实例:
我们可以构造另外一个伪类来继承Mammal,这是通过定义它的constructor函数并替换它的prototype为一个Mammal的实例来实现的:

聊一下对JavaScript中的伪类模式的个人理解_第3张图片

伪类模式本意是想面向对象靠拢,但它看起来格格不入。我们可以隐藏一些不好的细节,这是通过使用method方法定义一个inherits方法来实现的:
我们的inherits和method方法都返回this,这将允许我们可以以级联的样式编程。可以只用一行语句构造我们的Cat:

聊一下对JavaScript中的伪类模式的个人理解_第4张图片

“伪类”形式可以给不熟悉JavaScript的程序员提供便利,但是也隐藏了该语言的真实本质,借鉴类的表示法可能误导程序员去编写国语深入与复杂的层次结构。许多复杂的类层次结构产生的原因就是静态类型检查的约束。JavaScript完全拜托了那些约束。在基于类的语言中,类的继承是代码重要的唯一方式。JavaScript悠着更多更好的选择。

用原型类的一个小例子来说明一下:

var Car = function(loc) {
var obj = Object.create(Car.prototype);
obj.loc = loc;
return obj;
};
Car.prototype.move = function(){
this.loc++;
};

现在我们把它重构成一种与之相似的模式-伪类模式。称它为伪类,是因为它仿照其他语言中的类系统,试图增添一些语法上的便利,如果我们要在程序中构造大量的类,那将会产生大量的重复代码,而语言本身可以通过某种方式自动实现一些步骤。
看上面这个列子,你认为哪些代码是每个类都需要的并且有可能通过语言实现自动化的呢?没错,就是这两行

var obj = Object.create(Car.methods);
return obj;

每个类都要创建一个对象,并且确保这个对象被委托到原型对象,并且返回这个对象。即这些行为会发生在每一个原型类中。
为了简化输入JavaScript提供了关键字new ,当我们在一个函数调用前使用关键字new,该函数便会以一种特殊的模式--构造模式来运行。在此模式中,JavaScript可以自动完成这些工作。所谓构造模式,及时指解释器在你的代码中嵌入几行操作代码,因为它知道无论何时实例化一个新对象,你都需要这些被自动完成。它会暂时性的使你的函数运行一些额外的代码,即使你从未输入这些代码。这些被插入的代码基本上与你之前再原型类里写的代码一样,那么因为我们在ben的创建的时候使用了关键字new,所以Car的调用会运行这些被插入在开头和结尾的额外操作。

聊一下对JavaScript中的伪类模式的个人理解_第5张图片

而对于amy的创建,我们并没有使用关键字new 在Car调用的前面,所以并不会插入前面的这些代码。所以此时在相同的程序里,这个类函数会以插入或者未被插入这些代码的方式运行。这些插入的代码不会在你的代码中显示。它们只有在调用前面使用关键字new时,作为这种特殊调用的结果才会被执行。但是即使这里展示了两行代码,一行有关键字new,一行没有关键字new,但是你在写程序时不应如此。你应该决定一个函数是使用new关键字,还是不使用new关键字。它的目的是让你跳过一些你已经做的工作。所以如果你决定使用new,就需要再对代码进行一些必要的重构。

this=Object.create(Car.prototype);
/*...*/
return this;

这行代码创建了一个将原型委托在Car.prototype中的对象,并且赋值给this,后面又返回this。这样即使你没有再程序中写出这行代码,解释器也可以把值赋给关键字this。这非常合理,并且与我们理解的关键字this的使用是一致的,关键字this的用途是作为一种便捷的方式去指代一些面向对象函数调用中的任何目标对象,而 var amy = new Car(1); 这个函数的调用就是面向对象的,其目的是构造一个新的对象。这就非常合理,无论新对象是什么,都能成为关键字this指代的目标。因此重构后的代码

var Car = function(loc) {
this = Object.create(Car.prototype);
this.loc = loc;
return this;
};
Car.prototype.move = function(){
this.loc++;
};

但是需要注意这两行代码只是并不存在的标注,用来告诉我们关键字new做的事情。所以去掉这些标注,我们的代码就变成

var Car = function(loc) {
this.loc = loc;
};
Car.prototype.move = function(){
this.loc++;
};
var amy = new Car(1);
amy.move();
var ben= new Car(9);
ben.move();

这就是构造一个伪类时所需要的代码。
此时在内存的情况

聊一下对JavaScript中的伪类模式的个人理解_第6张图片

这与之前的原型版本并没有本质的区别,因为伪类仅仅是在之前的原型模式之上,为了写代码方便加了薄薄一层语法糖。那么实际上,这两种模式之间的主要区别是JavaScript引擎实现性能优化的数量,而这些优化是伪类模式所独有的。
我们再来看一下这个代码,这段代码与JS中的其他类模式一样,有两个独立区分的部分,这存在之前的每一个伪类模式、原型模式以及函数共享模式之中。
在第2部分,是指定一个类中的所有实例应该相似的部分,在伪类模式中这些相似的部分一般会被存储为prototype对象的属性。在第1部分,你可以指定每个实例和其他实例之间的不同之处。如果大部分语言,这部分写在构造函数体内,它允许我们指定一个类的实例和另一个这个类的实例之间的不同。所有指定的arm和ben不同是的属性loc都是在构造函数内完成的。了解这两部分代码在每一个类中的存在,对于理解子类的概念非常重要。我下一篇会写伪类子类的概念。


聊一下对JavaScript中的伪类模式的个人理解_第7张图片

JavaScript中支持多种不同的类模式,那么应该使用哪一种模式或者说哪一种是最好的模式,这么问他并没有答案。作为一门语言,JavaScript并没有鲜明的立场,它更倾向于展现其本质的特征,以便程序员灵活运用。因此并不存在对与错,只是技术和选择上的不同。我们能做的是了解每一种类模式的优点和缺点,来帮我们判断在特定的情况下应该使用哪种模式。
最后,不知道各位程序员对伪类有什么看法,请多多指教。

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

你可能感兴趣的:(聊一下对JavaScript中的伪类模式的个人理解)