在JavaScript中实现类的方式探讨

在 javascript 中有很多方式来创建对象,所以创建对象的方式使用起来非常灵活。那么,到底哪一种方式是最恰当的对象创建方式呢?构造模式,原型模式还是对象原意模式(Object literal)呢?

但这些模式具体是怎么回事呢?

在开始讲解之前,让我们先清楚地介绍一下关于 javascript 基本知识。

有没有可能在 javascript 中实现面向对象编程的方式呢?

答案是可能的,javascript 是可以创建对象的!这种对象可以包含数据及能够操作数据的方法,甚至可以包含其他对象。它没有类但拥有构造函数;它没有类继承机制,但是可以通过原型(prototype)实现继承。

现在看起来,我们已经了解了在 javascript 中创建对象及实现基于对象编程时所必须的组成部分。

我们都知道 javascript 拥有私有变量。一个通过“var”关键字定义的变量,只能在函数体中被访问,而不能在函数外被访问。那么,如果我们不通过使用“var”关键字来定义变量会怎样呢?我们现在不对这个问题进行深入探讨,可能是通过“this”进行访问的,我会在另外的时间来详细讲述这个问题。

现在回到之前的问题。到底哪一种方式是最恰当的对象创建方式呢?
让我们用已经知晓的知识,通过创建Person的对象是来试验一下。


代码如下:

var Person = {
firstName : 'John',
lastName : 'Cody',
fullName : '',
message : '',

createFullName : function () {
fullName = this.firstName + ' ' + this.lastName;
},

changeMessage : function (msg) {
this.message = msg;
},

getMessage : function () {
this.createFullName();
return this.message + ' ' + fullName;
}
}

Person.firstName = 'Eli';
Person.lastName = 'Flowers'
Person.changeMessage('welcome');
var message = Person.getMessage(); // welcome Eli Flowers
alert(message);

这是对象原意模式(literal pattern)。这非常接近我们常创建对象的方式。如果你不需要关心私有/包装的成员,并且你知道不将创建这个对象的实例。那么,这种方式将会很适合你。公有的成员可以做所有私有成员的事情,不是吗?但是,这不是一个类,而是一个对象而已,不能被创建实例并且不能被继承。

让我们尝试下其他的方面:


代码如下:

var Person = {
firstName : 'John',
lastName : 'Cody',
fullName : '',
message : '',

createFullName : function () {
fullName = this.firstName + ' ' + this.lastName;
},

changeMessage : function (msg) {
this.message = msg;
},

getMessage : function () {
this.createFullName();
return this.message + ' ' + fullName;
}
}

Person.firstName = 'Eli';
Person.lastName = 'Flowers'
Person.changeMessage('welcome');
var message = Person.getMessage(); // welcome Eli Flowers
alert(message);

这是一种构造模式的实例(Constructor Pattern)。那么,这是类还是对象呢?应该 两种都算是吧。我们能够在当请求时把它当做对象Person来使用。它毕竟也只是一个函数而已。然而,它可以通过使用“new”关键字来实现创建新的实例功能。

在使用这种方式时,我们需要时刻记住如下要点:

1. 无论什么时候这个函数被调用时,它拥有一个特别的变量叫做“this”并且可以在全局范围内使用。全局范围依赖于这个函数自身的作用范围。

2. 无论什么时候通过“new”关键字创建这个函数的实例,“this”变量指向这个函数本身,并且这个“new”操作将会影响到函数体中的代码被执行。这也正是构造模式。

3. 任何附加到“this”变量下的变量都会成为公有属性并且任何通过“var”关键字定义的变量都将是属于私有属性。

4. 一个附加到“this”下的函数叫做特权函数,它可以访问所有的私有变量以及被附加到“this”下的函数及变量。

5. 私有函数可以访问到其他私有变量及私有函数。

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 (msg) {
message = msg;
}

var setFirstName = function (fName) {
firstName = fName;
}

var setLastName = function (lName) {
lastName = lName;
}

var getMessage = function () {
createFullName();
return message + ' ' + fullName;
}

//functions exposed public
return {
setFirstName: setFirstName,
setLastName: setLastName,
setMessage: setMessage,
getMessage: getMessage
};

};

var person1 = new Person();
person1.setFirstName('Eli');
person1.setLastName('Flowers');
person1.setMessage('welcome');
var message = person1.getMessage(); // welcome Eli Flowers
alert(message);

这是一个显示模式(Revealing Pattern)。非常感谢 Christian Heilmann。使用这种模式的方式就是把请求的"getters" 和 "setters" 当作属性使用。我们很多都是从传统的Java编程中找到这样的身影并且很明显地知道实现它其实并不复杂。这同样是一种类似于当类继承自一个接口的情况。

这种模式大部分方面都实现得很好,仅仅只有一个很微小的问题。每一次当一个类的实例被创建时。这个新创建的对象获得了一份变量和函数的拷贝。现在,拷贝变量是没有问题的,我们希望对于每一个对象的数据都是属于对象自身的,那么,成员函数呢?他们仅仅是操作数据而已。那么,为什么需要拷贝他们呢?

这正是原型模式(Prototype)的优势所在。在所有实例中,所有东西都是被创建成一个原型,并且能够相互分享。我们仅仅需要做的就是依据原型创建共有函数。


代码如下:

var Person = function () {

//private
var welcomeMessage = 'welcome';
var fullName = '';
var firstName = '';
var lastName = "";
var createFullName = function () {
Person.prototype.setFirstName('asdsad');
fullName = firstName + ' ' + lastName;
};

//constructor
var Person = function () { }; //will be created evrytime

//public
Person.prototype = {
getFullName: function () {
createFullName();
return welcomeMessage + ' ' + fullName;
},
setFirstName: function (fName) {
firstName = fName;
},
setLastName: function (lName) {
lastName = lName;
},
ChangeMessage: function (mesg) {
welcomeMessage = mesg;
}
}

return new Person(); // Person; //new Person();
};

var person1 = new Person();
person1.setFirstName ('Eli');
person1.setLastName('Flowers');
person1.ChangeMessage('welcome');
var message = person1.getFullName(); // welcome asdsad Flowers
alert(message);

原型模式存在的一个问题是它不能访问私有变量及私有函数,正因为这个问题,我们才会介绍闭包以及始终组织好创建类中存在的代码以使得它在全局范围内不会变得很混乱。所有都是属于 Person 类的作用范围内。

另外一个问题是每一次实例被创建时,全部的代码都被执行一遍,包括原型的绑定。对于我们中的一部分人来说,这仅仅只是一个效率问题。处理好这个问题的一种方式是仅仅在期望共有函数不可用的情况下绑定这个原型。

这样将会使得绑定原型操作只会在第一个实例被创建时执行,并且在那之后所有其他的实例都将只会进行检查操作。不幸的是,这样还是不能解决我们在上面例子中提到的问题,因为我们只有重新再来一次创建的函数用于生成一个闭包来达到这个类的效果。这样的话,至少我们减少了一部分内存的使用。

等等,还有另外一个问题是私有函数不能直接访问原型函数。

为什么你们一定得需要私有函数和私有变量呢?我知道你一定是想实现类的封装性,想确保类中的属性或者内部的数据不会被突然地修改了或者被内部的其他程序所修改,或者任何其他的操作……

你应该记住你是不能将 javascript 代码编译成二进制的,对于这种情况,你在一定程度上很恼火吧,这样代码始终都是可用的。所以,如果任何人想搅乱代码的话,不管你真正实现私有或者没有实现私有,不管你将代码给团队中的其他成员或者卖出去,他们都可以搅乱代码。实现私有化可能有那么一点点帮助吧。

另一个其他编程者使用的技术是使用约定命名,使用下划线 “_”给所有你想设成私有任何的东西加上前缀以规定它成为私有。


代码如下:

(function () {
var Person = function () {
this._fullName = '';
this.welcomeMessage = '';
this.firstName = '';
this.lastName = "";
_that = this;

this._createFullName = function () {
this.ChangeMessage('Namaste');
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 (mesg) {
this.welcomeMessage = mesg;
}
}

this.Person = Person;
})();

var person1 = new Person();
person1.firstName = 'Eli';
person1.lastName = 'Flowers';
person1.ChangeMessage('Welcome');
var message = person1.getFullName(); // Namaste Eli Flowers
alert(message);

我不是说你不应该考虑 “private” 或者类似的知识。你是代码的设计者,所以你将知道怎么来管理并且知道怎么做才是最好的。根据你的需求,你可以使用任何一种设计模式或者多个设计模式组合一起使用。

无论你决定采用哪种设计模式,始终记住做尽量少的事情,不要在全局作用范围内实现闭包,尽量减少内存泄露,以及优化代码,并且组织好代码。所以,尽量多了解些作用域,闭包以及 “this” 的表现行为。

最后,祝编程愉快!

译后感

经常使用 javascript,对于它的印象一直都是直接拷贝过来就可以用的。最近使用 extjs,它的类框架非常好用。从这样文章也明白在 javascript 中实现类的各种方式,以及在文章最后讨论了类中私有成员的实现情况。

(0)

相关推荐

  • JavaScript 实现类的多种方法实例

    构造方法 复制代码 代码如下: function coder(){    this.name = '现代魔法';    this.job = 'Web 开发者';    this.coding = function ()    { alert('我正在写代码'); }} var coder = new coder();alert(coder.name);coder.coding(); 工厂方法 复制代码 代码如下: function createCoderFactory(){    var ob

  • 在JavaScript中实现类的方式探讨

    在 javascript 中有很多方式来创建对象,所以创建对象的方式使用起来非常灵活.那么,到底哪一种方式是最恰当的对象创建方式呢?构造模式,原型模式还是对象原意模式(Object literal)呢? 但这些模式具体是怎么回事呢? 在开始讲解之前,让我们先清楚地介绍一下关于 javascript 基本知识. 有没有可能在 javascript 中实现面向对象编程的方式呢? 答案是可能的,javascript 是可以创建对象的!这种对象可以包含数据及能够操作数据的方法,甚至可以包含其他对象.它没

  • JavaScript中定义类的方式详解

    本文实例讲述了JavaScript中定义类的方式.分享给大家供大家参考,具体如下: Javascript本身并不支持面向对象,它没有访问控制符,它没有定义类的关键字class,它没有支持继承的extend或冒号,它也没有用来支持虚函数的virtual,不过,Javascript是一门灵活的语言,下面我们就看看没有关键字class的Javascript如何实现类定义,并创建对象. 一.定义类并创建类的实例对象 在Javascript中,我们用function来定义类,如下: function Sh

  • javaScript中定义类或对象的五种方式总结

    第一种方式: 工厂方法 能创建并返回特定类型的对象的工厂函数(factory function). function createCar(sColor){ var oTempCar = new Object; oTempCar.color = sColor; oTempCar.showColor = function (){ alert(this.color); }; return oTempCar; } var oCar1 = createCar(); var oCar2 = createCa

  • JavaScript中的类继承

    JavaScript Inheritance DouglasCrockfordwww.crockford.com And you think you're so clever and classless and free--John Lennon JavaScript一种没有类的,面向对象的语言,它使用原型继承来代替类继承.这个可能对受过传统的面向对象语言(如C++和Java)训练的程序员来说有点迷惑.JavaScript的原型继承比类继承有更强大的表现力,现在就让我们来看看. Java Jav

  • JavaScript中的类(Class)详细介绍

    在JavaScript中,可以使用类(Class)来实现面向对象编程(Object Oriented Programming).不过,JavaScript中的类与Java中的有所不同,其相应的定义和使用也不一样. JavaScript中类的定义 在JavaScript中,所有从同一个原型对象(prototype)处衍生出来的对象组成了一个类:也就是说,JavaScript中的类是一个对象集合的概念,如果两个对象它们的prototype相同,那么它们就属于同一个类:JavaScript中的类甚至都

  • Javascript 中的类和闭包

    有人说javascript也是面向对象的,只是它是prototype based,当然这只是概念上的区别,我不想讨论js是不是面向对象的,关键是想说明虽然javascript的类表现得很像其他语言中的类,但是内部的实现机理确不太一致,如果一味的把javascript中的类类比作其他语言中的类,有时候脑子会犯混. 先看一段简单的代码,一般教材上介绍如何新建一个类的时候都是这样的(当然还有更复杂的方法,不过本质上是一样的): 复制代码 代码如下: function MyClass(x) { this

  • javascript中定义类的方法汇总

    JS中定义类的方式有很多种: 1.工厂方式 复制代码 代码如下: function Car(){    var ocar = new Object;    ocar.color = "blue";    ocar.doors = 4;    ocar.showColor = function(){     document.write(this.color)    };    return ocar;   }   var car1 = Car();   var car2 = Car()

  • JavaScript中localStorage对象存储方式实例分析

    本文实例讲述了JavaScript中localStorage对象存储方式.分享给大家供大家参考,具体如下: [Local storage limitations]文章中提及JavaScript里的local storge的限制,例子中在localStorage里存储了一个bool型的数据,但是却没有像我们期待的一样进行存储. 当我们存储布尔型,数值型,字符串型时,localStorage对象会将我们存储的数据默认转为字符串字面量. localStorage[0] = false;// "fals

  • JavaScript中错误正确处理方式小结你用对了吗

    JavaScript的事件驱动范式增添了丰富的语言,也是让使用JavaScript编程变得更加多样化.如果将浏览器设想为JavaScript的事件驱动工具,那么当错误发生时,某个事件就会被抛出.理论上可以认为这些发生的错误只是JavaScript中的简单事件. 本文将会讨论客户端JavaScript中的错误处理.主要介绍JavaScript中的易犯错误.错误处理.异步代码编写等内容. 下面就让我们一起看看如何正确处理JavaScript中的错误. Demo演示 本文中使用的demo可以在GitH

  • ES6 javascript中class类的get与set用法实例分析

    本文实例讲述了ES6 javascript中class类的get与set用法.分享给大家供大家参考,具体如下: 与 ES5 一样, 在 Class 内部可以使用get和set关键字, 对某个属性设置存值函数和取值函数, 拦截该属性的存取行为. class MyClass { constructor() { // ... } get prop() { return 'getter'; } set prop(value) { console.log('setter: ' + value); } }

随机推荐