JavaScript 中的类及其创建模式。






4.88/5 (8投票s)
本文讨论如何在 JavaScript 中模仿类似类的结构以及创建它们的模式。
引言
在本文中,我们将讨论如何模仿 class
类似的结构在 JavaScript
中,以便我们可以将相关的状态和行为封装成一个模块/类。
背景
在创建 JavaScript 类之前,我们需要知道什么是闭包以及它们如何在创建类时发挥作用。
关于闭包的著名说法 - "..... 嵌套函数可以访问父函数的变量和参数,即使在父函数执行/返回之后"
,现在这意味着什么 :)
让我们通过一个例子来深入了解一下。 假设我们有一个函数,它返回当前时间的毫秒数,当我们调用它时,它将返回,假设 345 毫秒,当您使用 setTimeOut 30 毫秒的延迟调用相同的函数时,输出将与之前的不同,因此状态不会在两次调用中保持。
非闭包演示
function time(){
var timeNow = new Date();
return timeNow.getMilliseconds();
};
现在,当您按如下方式延迟两次调用上述函数时
alert(time());
setTimeout(function(){
alert(time());
},220);
它将像预期的那样发出不同的毫秒数,因为每次调用该函数时,都会实例化一个新的 Date()
对象并给出不同的毫秒数。 如何解决这个问题? 闭包出现了。
通过返回一个嵌套函数,您可以创建一个闭包,这样即使在父函数返回之后,嵌套函数仍然可以访问父函数的
vars
,因此当您第一次调用该函数时,它将返回一个嵌套函数,该函数将存储毫秒的值,下次当你使用返回的嵌套函数来访问你保存的状态时,它会返回。
闭包演示
function timeClosure(){
var timeNow = new Date();
return function(){
return timeNow.getMilliseconds();
}
};
现在,当你调用
timeClosure()
并将其分配给一个变量时,timeClosure 函数会返回一个嵌套函数,该函数将可以访问保存的 timeNow 变量。 所以现在当你使用分配的变量两次,即使有延迟,你也会得到相同的毫秒数,因为返回的嵌套函数可以访问父函数的var
,并且它保存了状态,使用了两次,所以没有新的Date()
对象实例化,所以使用了相同的Date()
对象两次,因此我们将得到相同的毫秒数两次。
现在,当您按如下方式延迟两次调用上述函数时
var nestedFunc = timeClosure(); //This will return the nested func.
alert(nestedFunc());
setTimeout(function(){
alert(nestedFunc());
},220);
两者将弹出相同的毫秒数,如上所述。 现在这是否模仿类结构,实际上并没有,因为状态被保留了,但你不能将其新建为具有相同结构的许多实例。 所以这里有模式可以解决这个问题。
在本文中,我们将讨论两种模式
揭示模块模式
.揭示原型模式
.
揭示模块模式
在这种模式中,我们在一个函数中封装字段和成员,并返回一个对象字面量以公开这些成员。 在这里返回对象字面量会围绕这些成员创建一个闭包。
让我们看一个实际的例子来获得上下文
function Circle(radius){
var radius=radius;
var pi=Math.PI;
var area=function(){
return pi*(Math.pow(radius,2));
};
return{
area:area
}
}
现在这是类模拟,现在你可以按如下方式实例化上面的类
var circle1 = new Circle(10);
alert(circle1.area());
这将弹出半径为 10 的圆的面积,即 314.1592653589793
这种模式有一个缺点,我们创建的内部方法会被复制到 Circle 类的每个实例中,这与 C# 或 Java 世界略有不同,在 javascript 中,方法将被复制到你创建的每个实例中。 为了解决这个问题,我们将使用揭示原型模式,它结合了揭示模块模式的特性和原型特性。
揭示原型模式
在这个模块中,我们有两个部分,
- 构造函数部分。
- 原型部分。
您在构造函数中指定所有公共字段,在原型部分指定私有字段和方法。
让我们看一个实际的例子来获得上下文
function Circle(radius){
this.radius=radius;
}
Circle.prototype=function(){
var pi = Math.PI;
var area = function(){
return pi*(Math.pow(this.radius,2));
};
return {
area:area
}
}();
现在你可以按如下方式实例化和访问圆的面积
var circle1 = new Circle(10);
alert(circle1.area());
这将弹出半径为 10 的圆的面积,这将与上面相同 :)
在涉及到上面的代码时,我们创建了一个构造函数和一个原型,原型允许你在所有实例之间共享方法,而不是为类的每个实例单独复制。 并且通过使用自执行匿名函数,我们添加了 proptype 模式,以便能够封装私有字段并仅公开公共方法。 因此,原型模式中的返回和成员会围绕你的类创建一个闭包。
这种模式有一个缺点,它使用了 "this"
关键字,有时会很棘手,因为 this 的上下文会相应地发生变化。
让我们看一个实际的例子以及解决问题的方法
问题
function Triangle(a,b,c){
this.a=a;
this.b=b;
this.c=c;
}
Triangle.prototype=function(){
var perimeter=function(){
return this.a+this.b+this.c; //This here has different context so this won't work
}
var area = function(){
var s=(perimeter())/2;
return Math.sqrt(s*(s-this.a)*(s-this.b)*(s-this.c));
};
return {
area:area
}
}();
var triangle1=new Triangle(10,10,10);
alert(triangle1.area());
}
这将无法正常工作,因为您从 area 方法调用周长函数,因此 this 的上下文变为调用者函数 area,因此 area 没有 a、b、c,因此 perimeter 函数中的 this.a、this.b、this.c 未定义。 所以解决方法是将 this 传递给 perimter 函数并使用它。
解决方案
function Triangle(a,b,c){
this.a=a;
this.b=b;
this.c=c;
}
Triangle.prototype=function(){
var perimeter=function(thisob){
return thisob.a+thisob.b+thisob.c; //this will work as this here is the Triangle inst
}
var area = function(){
var s=(perimeter(this))/2; //passing this to perimeter
return Math.sqrt(s*(s-this.a)*(s-this.b)*(s-this.c));
};
return {
area:area
}
}();
var triangle1=new Triangle(10,10,10);
alert(triangle1.area());
}
这将弹出给定三角形三边的三角形面积,即 43.30127018922193
。
感谢您的阅读 :)