几种JavaScript定义类和对象的方法
http://www.nowamagic.net/javascript/js_MethodsToCreateClassObject.php
最近偶然碰到有朋友问我"hoisting"的问题。即在js里所有变量的声明都是置顶的,而赋值则是在之后发生的。可以看看这个例子:
view source
print?
1 var a = 'global';
2 (function () {
3 alert(a);
4 var a = 'local';
5 })();
大家第一眼看到这个例子觉得输出结果是什么?'global'?还是'local'?其实都不是,输出的是undefined,不用迷惑,我的题外话就是为了讲这个东西的。
其实很简单,看一看JavaScript运行机制就会明白。我们可以把这种现象看做"预声明"。但是如果稍微深究一下,会明白得更透彻。
这里其实涉及到对象属性绑定机制。因为所有JavaScript函数都是一个对象。在函数里声明的变量可以看做这个对象的"类似属性"。对象属性的绑定在语言里是有分"早绑定"和"晚绑定"之分的。
【早绑定】是指在实例化对象之前定义其属性和方法。解析程序时可以提前转换为机器代码。通常的强类型语言如C++,java等,都是早绑定机制的。而JavaScript不是强类型语言。它使用的是"晚绑定"机制。
【晚绑定】是指在程序运行前,无需检查对象类型,只要检查对象是否支持特性和方法即可。可以在绑定前对对象执行大量操作而不受任何惩罚。
上面代码出现的"预声明"现象,我们大可用"晚绑定"机制来解释。在函数的作用域中,所有变量都是"晚绑定"的。 即声明是顶级的。所以上面的代码和下面的一致:
view source
print?
1 var a = 'global';
2 (function () {
3 var a;
4 alert(a);
5 a = 'local';
6 })();
在alert(a)之前只对a作了声明而没有赋值。所以结果可想而知。
在JavaScript里,我所知道的几种定义类和对象的方式:
直接量方式
使用直接量构建对象是最基础的方式,但也有很多弊端。
view source
print?
1 var Obj = new Object;
2 Obj.name = 'sun';
3 Obj.showName = function() {
4 alert('this.name');
5 }
我们构建了一个对象Obj,它有一个属性name,一个方法showName。但是如果我们要再构建一个类似的对象呢?难道还要再重复一遍?NO!,我们可以用一个返回特定类型对象的工厂函数来实现。就像工厂一样,流水线的输出我们要的特定类型结果。
工厂方式
view source
print?
01 function createObj(name) {
02 var tempObj = new Object;
03 tempObj.name = name;
04 tempObj.showName = function () {
05 alert(this.name);
06 };
07 return tempObj;
08 }
09 var obj1 = createObj('obj_one');
10 var obj2 = createObj('obj_two');
这种工厂函数很多人是不把他当做构建对象的一种形式的。一部分原因是语义:即它并不像使用了运算符new来构建的那么正规。还有一个更大的原因,是因为这个工厂每次产出一个对象都会创建一个新函数showName(),即每个对象拥有不同的版本,但实际上他们共享的是同一个函数。
有些人把showName在工厂函数外定义,然后通过属性指向该方法,可以避开这个问题:
view source
print?
01 function showName () {
02 alert(this.name);
03 }
04 function createObj(name) {
05 var tempObj = new Object;
06 tempObj.name = name;
07 tempObj.showName = showName;
08 return tempObj;
09 }
10 var obj1 = createObj('obj_one');
11 var obj2 = createObj('obj_two');
可惜的是,这种方式让showName()这个函数看起来不像对象的一个方法。
构造函数方式
这种方式是为了解决上面工厂函数的第一个问题,即没有new运算符的问题。可是第二个问题它依然不能解决。我们来看看。
view source
print?
1 function Obj(name) {
2 this.name = name;
3 this.showName = function () {
4 alert(this.name);
5 }
6 }
7 var obj1 = new Obj('obj_one');
8 var obj2 = new Obj('obj_two');
它的好处是不用在构造函数内新建一个对象了,因为new运算符执行的时候会自动创建一个对象,并且只有通过this才能访问这个对象。所以我们可以直接通过this来对这个对象进行赋值。而且不用再return,因为this指向默认为构造函数的返回值。同时,用了new关键字来创建我们想要的对象是不是感觉更"正式"了。可惜,它仍然不能解决会重复生成方法函数的问题,这个情况和工厂函数一样。
原型方式
这种方式对比以上方式,有个很大的优势,就是它解决了方法函数会被生成多次的问题。它利用了对象的prototype属性。我们依赖原型可以重写对象实例。
view source
print?
1 var Obj = function () {}
2 Obj.prototype.name = 'me';
3 Obj.prototype.showName = function () {
4 alert(this.name);
5 }
6 var obj1 = new Obj();
7 var obj2 = new Obj();
我们依赖原型对构造函数进行重写,无论是属性还是方法都是通过原型引用的方式给新建的对象,因此都只会被创建一次。可惜的是,这种方式存在两个致命的问题:
1. 没办法在构建对象的时候就写入想要的属性,因为原型在构造函数作用域外边,没办法通过传递参数的方式在对象创建的时候就写入属性值。只能在对象创建完毕后对值进行重写。
2. 致命问题在于当属性指向对象时,这个对象会被多个实例所共享。考虑下面的代码:
view source
print?
01 var Obj = function () {}
02 Obj.prototype.name = 'me';
03 Obj.prototype.flag = new Array('A', 'B');
04 Obj.prototype.showName = function () {
05 alert(this.name);
06 }
07 var obj1 = new Obj();
08 var obj2 = new Obj();
09
10 obj1.flag.push('C');
11
12 alert(obj1.flag); // A,B,C
13 alert(obj2.flag); //A,B,C
是的,当flag属性指向对象时,那么实例obj1和obj2都共享它,哪怕我们仅仅改变了obj1的flag属性,但是它的改变在实例obj2中任然可见。面对这个问题,让我们不得不想是否应该把【构造函数方式】和【原型方式】结合起来,让他们互补。。。
构造函数和原型混合方式
我们让属性用构造函数方式创建,方法用原型方式创建即可:
view source
print?
01 var Obj = function (name) {
02 this.name = name;
03 this.flag = new Array('A', 'B');
04 }
05 Obj.prototype = {
06 showName : function () {
07 alert(this.name);
08 }
09 }
10 var obj1 = new Obj();
11 var obj2 = new Obj();
12
13 obj1.flag.push('C');
14
15 alert(obj1.flag); // A,B,C
16 alert(obj2.flag); //A,B
这种方式有效地结合了原型和构造函数的优势,是目前用的最多,也是副作用最少的方式。
不过,有些追求完美的家伙还不满足,因为在视觉上还没达到他们的要求,因为通过原型来创建方法的过程在视觉上还是会让人觉得它不太像实例的方法(尤其对于传统OOP语言的开发者来说。)所以,我们可以让原型活动起来,让他也加入到构造函数里面去,好让这个构造函数在视觉上更为统一。而这一系列的过程只需用一个判断即可完成。
view source
print?
01 var Obj = function (name) {
02 this.name = name;
03 this.flag = new Array('A', 'B');
04 if (typeof Obj._init == 'undefined') {
05 Obj.prototype = {
06 showName : function () {
07 alert(this.name);
08 }
09 };
10 Obj._init = true;
11 }
12 }
如上,用_init作为一个标志来判断是否已经给原型创建了方法。如果是那么就不再执行。这样其实在本质上是没有任何变化的,方法仍是通过原型创建,唯一的区别在于这个构造函数看起来"江山统一"了。
但是这种动态原型的方式是有问题的,《JavaScript高级程序设计》里并没有深究。创建第一个对象的时候会因为prototype在对象实例化之前没来的及建起来,是根本无法访问的。所以第一个对象是无法访问原型方法的。同时这种方式在子类继承中也会有问题。
分享到:
相关推荐
JavaScript定义类的几种方式包括工厂方式、构造函数和原型方式,每种方法都有其特点和适用场景,接下来我们将详细探讨。 工厂方式是最早期的面向对象编程在JavaScript中的实践之一。使用工厂方式定义类,本质上就是...
JavaScript中定义类或函数,主要有以下几种方式: 1. 工厂方式 工厂方式是JavaScript中创建对象的一种模式,它通过一个工厂函数来封装创建对象的代码。通过工厂函数,我们可以创建多个具有相同属性和方法的对象实例...
Javascript对象定义的几种方式
在讨论JavaScript中定义对象的几种方式之前,我们先理解一下JavaScript中的对象到底是什么。JavaScript是一种基于原型的编程语言,对象是其核心概念之一。对象可以被视为一个容器,存储各种键值对集合,键为属性名,...
本文旨在深入解析几种常见的实现类和对象的方法,并探讨它们之间的差异,以及在实际应用中如何选择合适的方法。 #### 方法一:使用 {} 和 new Object 定义实例 这种方法实际上并不定义一个类,而是直接创建一个...
在JavaScript中,定义对象的几种方式有直接量方式、工厂方式和原型方式。 直接量方式是最简单直接的定义对象的方法,即直接用花括号定义对象并填充属性和方法。例如: ```javascript var Obj = { name: 'sun', ...
本文将深入探讨JavaScript中创建对象的几种常见方式以及对象方法。 首先,我们来看“工厂模式”。工厂模式是一种创建对象的抽象方式,通过一个函数来返回一个新的对象。例如: ```javascript function getObj(name...
首先,手册的前几章会介绍JavaScript对象的基础知识,包括对象的创建、属性和方法的定义,以及对象与数据类型的关联。JavaScript中的对象是基于原型的,这意味着它们可以通过原型链共享属性和方法。此外,还会讲解...
以下是对标题和描述中提到的几种JavaScript面向对象编程模式的详细解释: 1. **构造函数与字面量结合模式** 在JavaScript中,我们可以通过构造函数和字面量语法来创建对象。构造函数用于定义对象的类型,而字面量...
通过在构造函数的原型上定义属性和方法,所有通过该构造函数创建的对象都可以访问这些属性和方法。然而,如果属性是可变的对象,如数组或对象,所有实例会共享同一份引用,导致可能的意外行为。 ```javascript ...
- **构造函数与原型**:构造函数用于创建对象实例,而原型则定义了所有实例共享的属性和方法。例如: ```javascript function Person(name) { this.name = name; } Person.prototype.greet = function() { ...
Prototype是JavaScript中的一个重要特性,允许对象共享属性和方法。我们可以通过设置构造函数的`prototype`属性来添加方法。这样,所有实例都可以访问共享的方法,减少了内存开销。但缺点是如果属性是对象,所有实例...
这里我们将深入探讨几种常见的创建对象的方法,并以标题和描述中提到的示例作为起点。 首先,我们看到的是字面量(Literal)或对象字面量的方式创建新对象。这种方式简单直接,直接在代码中定义了一个包含属性和...
这是最简单直接的创建对象的方式,通过大括号{}来定义一个对象,然后在内部用逗号分隔各个属性和方法。 ```javascript var obj = { name: 'Alice', age: 25, sayHello: function() { console.log('Hello!'); ...
在JavaScript中创建对象主要涉及以下几种模式: 1. 工厂模式 工厂模式是最早期的对象创建方式之一,它通过一个函数来创建对象,并返回新创建的对象。这种方法的核心是利用函数封装创建对象的细节,然后通过返回值来...
JavaScript中包含以下几种内置的数据类型: 1. **Undefined**:表示未定义或无法找到的值。在IE5及更早版本中,除了直接赋值或`typeof`操作外,对`undefined`的任何操作都会导致异常。在较新版本的浏览器中,`...