澳门新葡萄京娱乐场在JavaScript中实现类的方式探讨_javascript技巧_脚本之家

本文由码农网 –
小峰原创翻译,转载请看清文末的转载要求,欢迎参与我们的付费投稿计划!

在 javascript
中有很多方式来创建对象,所以创建对象的方式使用起来非常灵活。那么,到底哪一种方式是最恰当的对象创建方式呢?构造模式,原型模式还是对象原意模式呢?
但这些模式具体是怎么回事呢?
在开始讲解之前,让我们先清楚地介绍一下关于 javascript 基本知识。
有没有可能在 javascript 中实现面向对象编程的方式呢?
答案是可能的,javascript
是可以创建对象的!这种对象可以包含数据及能够操作数据的方法,甚至可以包含其他对象。它没有类但拥有构造函数;它没有类继承机制,但是可以通过原型实现继承。
现在看起来,我们已经了解了在 javascript
中创建对象及实现基于对象编程时所必须的组成部分。 我们都知道 javascript
拥有私有变量。一个通过“var”关键字定义的变量,只能在函数体中被访问,而不能在函数外被访问。那么,如果我们不通过使用“var”关键字来定义变量会怎样呢?我们现在不对这个问题进行深入探讨,可能是通过“this”进行访问的,我会在另外的时间来详细讲述这个问题。
现在回到之前的问题。到底哪一种方式是最恰当的对象创建方式呢?
让我们用已经知晓的知识,通过创建Person的对象是来试验一下。 复制代码 代码如下: var Person = { firstName :
‘John’, lastName : ‘Cody’, fullName : ”, message : ”, createFullName :
function () { fullName = this.firstName + ‘ ‘ + this.lastName; },
changeMessage : function { this.message = msg; }, getMessage : function
() { this.createFullName(); return this.message + ‘ ‘ + fullName; } }
Person.firstName = ‘Eli’; Person.lastName = ‘Flowers’
Person.changeMessage; var message = Person.getMessage(); // welcome Eli
Flowers alert;
这是对象原意模式。这非常接近我们常创建对象的方式。如果你不需要关心私有/包装的成员,并且你知道不将创建这个对象的实例。那么,这种方式将会很适合你。公有的成员可以做所有私有成员的事情,不是吗?但是,这不是一个类,而是一个对象而已,不能被创建实例并且不能被继承。
让我们尝试下其他的方面: 复制代码
代码如下: var Person = { firstName : ‘John’, lastName : ‘Cody’, fullName
: ”, message : ”, createFullName : function () { fullName =
this.firstName + ‘ ‘ + this.lastName; }, changeMessage : function {
this.message = msg; }, getMessage : function () { this.createFullName();
return this.message + ‘ ‘ + fullName; } } Person.firstName = ‘Eli’;
Person.lastName = ‘Flowers’ Person.changeMessage; var message =
Person.getMessage(); // welcome Eli Flowers alert;
这是一种构造模式的实例。那么,这是类还是对象呢?应该
两种都算是吧。我们能够在当请求时把它当做对象Person来使用。它毕竟也只是一个函数而已。然而,它可以通过使用“new”关键字来实现创建新的实例功能。
在使用这种方式时,我们需要时刻记住如下要点: 1.
无论什么时候这个函数被调用时,它拥有一个特别的变量叫做“this”并且可以在全局范围内使用。全局范围依赖于这个函数自身的作用范围。
2.
无论什么时候通过“new”关键字创建这个函数的实例,“this”变量指向这个函数本身,并且这个“new”操作将会影响到函数体中的代码被执行。这也正是构造模式。
3.
任何附加到“this”变量下的变量都会成为公有属性并且任何通过“var”关键字定义的变量都将是属于私有属性。
4.
一个附加到“this”下的函数叫做特权函数,它可以访问所有的私有变量以及被附加到“this”下的函数及变量。

如你所知,getter和setter已经成为了JavaScript的一部分。它们广泛支持所有的主流浏览器,甚至是IE8。

  1. 私有函数可以访问到其他私有变量及私有函数。 6.
    私有函数不能直接访问被附加到“this”变量和函数。我们可以通过创建一个私有变量“_that”并且将它赋值为“this”的方式实现。
    7.
    任何私有变量及函数对于其他私有函数及其他被附加到“this”的函数是可用的。这完全是可能的再javascript的作用范围下。
    8.
    一个变量:不是通过“var”关键字,也不是附加到“this”变量上以获得全局作用范围的。例如,对于一个自定义函数的作用范围。需要再一次地了解作用域及集群的知识。
    这已经实现了我们想要的大部分要求了,但是,有时候“this”和“that”这两个入口变量很容易造成给人们带来疑惑。尤其对于那些一直坚持要求纯粹私有的人来说,更容易迷惑。
    让我们再稍微修改下试试吧。 复制代码
    代码如下: var Person = function () { //private var firstName = ‘John’;
    var lastName = ‘Cody’; var fullName = ”; var message = ”; var
    createFullName = function () { fullName = firstName + ‘ ‘ + lastName; }
    //public setters var setMessage = function { message = msg; } var
    setFirstName = function { firstName = fName; } var setLastName =
    function { lastName = lName; } var getMessage = function ; return
    message + ‘ ‘ + fullName; } //functions exposed public return {
    setFirstName: setFirstName, setLastName: setLastName, setMessage:
    setMessage, getMessage: getMessage }; }; var person1 = new Person();
    person1.setFirstName; person1.setLastName; person1.setMessage; var
    message = person1.getMessage(); // welcome Eli Flowers alert;
    这是一个显示模式。非常感谢 Christian
    Heilmann。使用这种模式的方式就是把请求的”getters” 和 “setters”
    当作属性使用。我们很多都是从传统的Java编程中找到这样的身影并且很明显地知道实现它其实并不复杂。这同样是一种类似于当类继承自一个接口的情况。
    这种模式大部分方面都实现得很好,仅仅只有一个很微小的问题。每一次当一个类的实例被创建时。这个新创建的对象获得了一份变量和函数的拷贝。现在,拷贝变量是没有问题的,我们希望对于每一个对象的数据都是属于对象自身的,那么,成员函数呢?他们仅仅是操作数据而已。那么,为什么需要拷贝他们呢?
    这正是原型模式的优势所在。在所有实例中,所有东西都是被创建成一个原型,并且能够相互分享。我们仅仅需要做的就是依据原型创建共有函数。
    复制代码 代码如下: var Person = function
    () { //private var welcomeMessage = ‘welcome’; var fullName = ”; var
    firstName = ”; var lastName = “”; var createFullName = function () {
    Person.prototype.setFirstName; fullName = firstName + ‘ ‘ + lastName; };
    //constructor var Person = function () { }; //will be created evrytime
    //public Person.prototype = { getFullName: function ; return
    welcomeMessage + ‘ ‘ + fullName; }, setFirstName: function { firstName =
    fName; }, setLastName: function { lastName = lName; }, ChangeMessage:
    function { welcomeMessage = mesg; } } return new Person(); // Person;
    //new Person(); }; var person1 = new Person(); person1.setFirstName ;
    person1.setLastName; person1.ChangeMessage; var message =
    person1.getFullName(); // welcome asdsad Flowers alert;
    原型模式存在的一个问题是它不能访问私有变量及私有函数,正因为这个问题,我们才会介绍闭包以及始终组织好创建类中存在的代码以使得它在全局范围内不会变得很混乱。所有都是属于
    Person 类的作用范围内。
    另外一个问题是每一次实例被创建时,全部的代码都被执行一遍,包括原型的绑定。对于我们中的一部分人来说,这仅仅只是一个效率问题。处理好这个问题的一种方式是仅仅在期望共有函数不可用的情况下绑定这个原型。
    这样将会使得绑定原型操作只会在第一个实例被创建时执行,并且在那之后所有其他的实例都将只会进行检查操作。不幸的是,这样还是不能解决我们在上面例子中提到的问题,因为我们只有重新再来一次创建的函数用于生成一个闭包来达到这个类的效果。这样的话,至少我们减少了一部分内存的使用。
    等等,还有另外一个问题是私有函数不能直接访问原型函数。
    为什么你们一定得需要私有函数和私有变量呢?我知道你一定是想实现类的封装性,想确保类中的属性或者内部的数据不会被突然地修改了或者被内部的其他程序所修改,或者任何其他的操作……
    你应该记住你是不能将 javascript
    代码编译成二进制的,对于这种情况,你在一定程度上很恼火吧,这样代码始终都是可用的。所以,如果任何人想搅乱代码的话,不管你真正实现私有或者没有实现私有,不管你将代码给团队中的其他成员或者卖出去,他们都可以搅乱代码。实现私有化可能有那么一点点帮助吧。
    另一个其他编程者使用的技术是使用约定命名,使用下划线
    “_”给所有你想设成私有任何的东西加上前缀以规定它成为私有。 复制代码 代码如下: { var Person = function () {
    this._fullName = ”; this.welcomeMessage = ”; this.firstName = ”;
    this.lastName = “”; _that = this; this._createFullName = function () {
    this.ChangeMessage; this._fullName = this.firstName + ‘ ‘ +
    this.lastName; }; } //Shared Functions for Code optimization
    Person.prototype = { constructor: Person, getFullName: function () {
    this._createFullName(); return this.welcomeMessage + ‘ ‘ +
    this._fullName; }, ChangeMessage: function { this.welcomeMessage =
    mesg; } } this.Person = Person; })(); var person1 = new Person();
    person1.firstName = ‘Eli’; person1.lastName = ‘Flowers’;
    person1.ChangeMessage; var message = person1.getFullName(); // Namaste
    Eli Flowers alert; 我不是说你不应该考虑 “private”
    或者类似的知识。你是代码的设计者,所以你将知道怎么来管理并且知道怎么做才是最好的。根据你的需求,你可以使用任何一种设计模式或者多个设计模式组合一起使用。
    无论你决定采用哪种设计模式,始终记住做尽量少的事情,不要在全局作用范围内实现闭包,尽量减少内存泄露,以及优化代码,并且组织好代码。所以,尽量多了解些作用域,闭包以及
    “this” 的表现行为。 最后,祝编程愉快! 译后感 经常使用
    javascript,对于它的印象一直都是直接拷贝过来就可以用的。最近使用
    extjs,它的类框架非常好用。从这样文章也明白在 javascript
    中实现类的各种方式,以及在文章最后讨论了类中私有成员的实现情况。

我不认为这个点子通常是错误的,但我认为它不是非常适合JavaScript。可能看起来getter和setter可以简化代码和节省时间,但其实它们会带来隐藏错误,并且这些错误第一眼看并不明显。

getter和setter如何工作?

首先小小地总结一下这些是什么东西:

有时候,我们希望能允许访问一个会返回动态计算值的属性,或者你可能想要反映内部变量的状态,而不使用显式的方法调用。

为了说明它们是如何工作的,让我们来看一个有着两个属性的person对象,这两个属性为:firstName和lastName,以及一个计算值:fullName。

var obj = {
  firstName: "Maks",
  lastName: "Nemisj"
}

计算值fullName会返回firstName和lastName两者的串联。

Object.defineProperty(person, 'fullName', {
  get: function () {
    return this.firstName + ' ' + this.lastName;
  }
});

为了得到fullName的计算值,不需要像person.fullName()带可怕的括号,只需要使用简单的var
fullName = person.fullName。

这同样适用于setter,你可以通过使用函数设置值:

Object.defineProperty(person, 'fullName', {
  set: function (value) {
    var names = value.split(' ');
    this.firstName = names[0];
    this.lastName = names[1];
  }
});

使用就和getter一样简单:person.fullName = ‘Boris
Gorbachev’。这将调用上面定义的函数,并分离Boris
Gorbachev成firstName和lastName。

问题在哪里?

你也许在想:“嘿,我喜欢getter和setter方法,它们感觉更自然,就像JSON一样。”你说得对,它们的确是这样的,但是我们先退一步来看一看fullName在getter和setter之前是如何工作的。

为得到值,我们将使用类似于getFullName()的一些东西,以及为了设置值,我们将使用person.setFullName(‘Maks
Nemisj’)。

如果拼错函数名,person.getFullName()写成person.getFulName()会发生什么呢?

JavaScript会给出一个错误:

person.getFulName();
       ^
TypeError: undefined is not a function

这个错误会在适当的时候适当的地方被触发。访问函数不存在的对象将触发错误——这是好的。

现在,让我们来看看当用错误的名称来使用setter的时候会发生什么?

person.fulName = 'Boris Gorbachev';

什么也没有。对象是可扩展的,可以动态分配键和值,因此不会有错误在运行时被抛出。

这样的行为意味着错误可能显示在用户界面上的某个地方,或者,当某些操作被执行在错误的值上时,而并非是打字错误的时刻。

跟踪应该发生在过去但却显示在将来的代码流上的错误是如此有意思。

seal行不行

这个问题可以通过sealAPI来部分解决。只要对象是密封的,它就不能突变,也就是意味着fulName将试图分配一个新键到person对象,并且它会失败。

出于某种原因,当我在Node.js
V4.0测试这个的时候,它没有按照我期待的那样工作。所以,我不能确保这个解决方案。

而更令人沮丧的是,对于setter一点也没有解决方法。正如我前面提到的,对象是可扩展和可故障保护的,这意味着访问一个不存在的键不会导致任何错误。

如果这种情况只适用于对象的文字的话,我不会多此一举地写这篇文章,但在ECMAScript
2015(ES6)和用类定义getter和setter能力的兴起之后,我决定写下关于潜在陷阱的博客。

类的到来

我知道当前类在一些JavaScript社区不是非常受欢迎。人们对在函数式/基于原型的语言,例如JavaScript中是否需要它们,争执不休。然而,事实是,类就在ECMAScript
2015(ES6)规范说明中,并且将存在于此一段时间。

对我来说,类是指定在类的外部世界(消费者)和应用程序的内部世界之间的定义良好的API的一种方式。这就是白纸黑字放入规则的抽象,并且我们假定这些规则不会很快改变。

改进person对象,做一个它的real类。person定义了接口用于获取和设置fullName。

class Person {
  constructor(firstName, lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
  }
  getFullName() {
    return this.firstName + ' ' + this.lastName;
  }
  setFullName(value) {
    var names = value.split(' ');
    this.firstName = names[0];
    this.lastName = names[1];
  }
}

类定义了一个严格的接口描述,但getter和setter方法使其变得不太严格。我们已经习惯了臃肿的错误,当工作于对象文字和JSON时的键中出现拼写错误的时候。我希望至少类能够更严格,并且在这个意义上,提供更好的反馈给开发人员。

虽然这种情况在定义getter和setter在一个类上的时候没有任何不同。但它不会阻止任何人拼错。

class Person {
  constructor(firstName, lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
  }
  get fullName() {
    return this.firstName + ' ' + this.lastName;
  }
  set fullName(value) {
    var names = value.split(' ');
    this.firstName = names[0];
    this.lastName = names[1];
  }
}

有拼写错误的执行不会给出任何错误:

var person = new Person('Maks', 'Nemisj');
console.log(person.fulName);

同样不严格,不冗长,不可追踪的行为导致可能会出错。

在我发现这一点后,我有一个问题:在使用getter和setter的时候,有没有什么可以做的,以便于使得类更严格?我发现:有是肯定有,但是这值得吗?增加额外层次的复杂性到代码就只是为了使用数量更少的括号?对于API定义,也可以不使用getter和setter,而这样一来就能解决这个问题。除非你是一个铁杆开发人员,并愿意继续进行,不然还有另一种解决方案,如下所述。

proxy来帮助?

除了getter和setter方法,ECMAScript
2015(ES6)还自带proxy对象。proxy可以帮助你确定委托方法,这些委托方法可以在实际访问键执行之前,用来执行各种操作。事实上,它看起来像动态getter
/ setter方法。

proxy对象可以用来捕捉任何到类的实例的访问,并且如果在类中没有找到预先定义的getter或setter就会抛出错误。

为了做到这一点,必须执行下面两个操作:

  • 创建基于Person原型的getter和setter清单。
  • 创建将测试这些清单的Proxy对象。

让我们来实现它。

首先,为了找出什么样的getter和setter方法可以用在类Person上,可以使用getOwnPropertyNames和getOwnPropertyDescriptor:

var names = Object.getOwnPropertyNames(Person.prototype);
var getters = names.filter((name) => {
  var result =  Object.getOwnPropertyDescriptor(Person.prototype, name);
  return !!result.get;
});
var setters = names.filter((name) => {
  var result =  Object.getOwnPropertyDescriptor(Person.prototype, name);
  return !!result.set;
});

在此之后,创建一个Proxy对象:

var handler = {
  get(target, name) {
    if (getters.indexOf(name) != -1) {
      return target[name];
    }
    throw new Error('Getter "' + name + '" not found in "Person"');
  },
  set(target, name) {
    if (setters.indexOf(name) != -1) {
      return target[name];
    }
    throw new Error('Setter "' + name + '" not found in "Person"');
  }
};
person = new Proxy(person, handler);

现在,只要你尝试访问person.fulName,就会显示Error: Getter “fulName” not
found in “Person”的消息。

希望这篇文章可以帮助你全面了解getter和setter方法,以及它们将会带到代码中的危险。

发表评论

电子邮件地址不会被公开。 必填项已用*标注