65.9K
CodeProject 正在变化。 阅读更多。
Home

JavaScript 中的类及其创建模式。

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.88/5 (8投票s)

2015年4月2日

CPOL

4分钟阅读

viewsIcon

18126

本文讨论如何在 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 中,方法将被复制到你创建的每个实例中。 为了解决这个问题,我们将使用揭示原型模式,它结合了揭示模块模式的特性和原型特性。

揭示原型模式

在这个模块中,我们有两个部分,

  1. 构造函数部分。
  2. 原型部分。

您在构造函数中指定所有公共字段,在原型部分指定私有字段和方法。

让我们看一个实际的例子来获得上下文

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

感谢您的阅读 :)

© . All rights reserved.