`

关于javascript定义类与对象

阅读更多

在说这个话题之前,我想先说几句题外话:最近偶然碰到有朋友问我“hoisting”的问题。即在js里所有变量的声明都是置顶的,而赋值则是在之后发生的。可以看看这个例子:

 

var a = 'global';
(function () {
    alert(a);
    var a = 'local';
})();

 大家第一眼看到这个例子觉得输出结果是什么?‘global’?还是‘local’?其实都不是,输出的是undefined,不用迷惑,我的题外话就是为了讲这个东西的。

其实很简单,看一看JavaScript运行机制就会明白。我们可以把这种现象看做“预声明”。但是如果稍微深究一下,会明白得更透彻。
  这里其实涉及到对象属性绑定机制。因为所有JavaScript函数都是一个对象。在函数里声明的变量可以看做这个对象的“类似属性”。对象属性的绑定在语言里是有分“早绑定”和“晚绑定”之分的。



【早绑定】
是指在实例化对象之前定义其属性和方法。解析程序时可以提前转换为机器代码。通常的强类型语言如C++,java等,都是早绑定机制的。而JavaScript不是强类型语言。它使用的是“晚绑定”机制。

【晚绑定】
是指在程序运行前,无需检查对象类型,只要检查对象是否支持特性和方法即可。可以在绑定前对对象执行大量操作而不受任何惩罚。
上面代码出现的“预声明”现象,我们大可用“晚绑定”机制来解释。在函数的作用域中,所有变量都是“晚绑定”的。 即声明是顶级的。所以上面的代码和下面的一致:

 

var a = 'global';
(function () {
    var a;
    alert(a);
    a = 'local';
})();

 在alert(a)之前只对a作了声明而没有赋值。所以结果可想而知。




<!-- 题外话到此结束 -->

  RT:本文要说的是,在JavaScript里,我所知道的几种定义类和对象的方式:<! -- 声明:以下内容大部分来自《JavaScript高级程序设计》,只是个人叙述方式不同而已 -- >

  【直接量方式】
  使用直接量构建对象是最基础的方式,但也有很多弊端。

 

var Obj = new Object();
Obj.name = 'sun';
Obj.showName = function() {
    alert(this.name);
}
 

 我们构建了一个对象Obj,它有一个属性name,一个方法showName。但是如果我们要再构建一个类似的对象呢?难道还要再重复一遍?

NO!,我们可以用一个返回特定类型对象的工厂函数来实现。就像工厂一样,流水线的输出我们要的特定类型结果。


【工厂方式】

 

function createObj(name) {
    var tempObj = new Object();
    tempObj.name = name;
    tempObj.showName = function () {
        alert(this.name);
    };
    return tempObj;
}
var obj1 = createObj('obj_one');
var obj2 = createObj('obj_two');
 

 

 这种工厂函数很多人是不把他当做构建对象的一种形式的。一部分原因是语义:即它并不像使用了运算符new来构建的那么正规。还有一个更大的原因,是因为这个工厂每次产出一个对象都会创建一个新函数showName(),即每个对象拥有不同的版本,但实际上他们共享的是同一个函数。


有些人把showName在工厂函数外定义,然后通过属性指向该方法,可以避开这个问题:

 

function showName () {
    alert(this.name);
}    
function createObj(name) {
    var tempObj = new Object();
    tempObj.name = name;
    tempObj.showName = showName;
    return tempObj;
}
var obj1 = createObj('obj_one');
var obj2 = createObj('obj_two');
 

 

 可惜的是,这种方式让showName()这个函数看起来不像对象的一个方法。


【构造函数方式】
这种方式是为了解决上面工厂函数的第一个问题,即没有new运算符的问题。可是第二个问题它依然不能解决。我们来看看。

 

function Obj(name) {
    this.name = name;
    this.showName = function () {
        alert(this.name);
    }
}
var obj1 = new Obj('obj_one');
var obj2 = new Obj('obj_two');
 

 

 它的好处是不用在构造函数内新建一个对象了,因为new运算符执行的时候会自动创建一个对象,并且只有通过this才能访问这个对象。所以我们可以直接通过this来对这个对象进行赋值。而且不用再return,因为this指向默认为构造函数的返回值。



同时,用了new关键字来创建我们想要的对象是不是感觉更“正式”了。
可惜,它仍然不能解决会重复生成方法函数的问题,这个情况和工厂函数一样。


【原型方式】
这种方式对比以上方式,有个很大的优势,就是它解决了方法函数会被生成多次的问题。它利用了对象的prototype属性。我们依赖原型可以重写对象实例。

var Obj = function () {};
Obj.prototype.name = 'me';
Obj.prototype.showName = function () {
    alert(this.name);
};
var obj1 = new Obj();
var obj2 = new Obj();

 我们依赖原型对构造函数进行重写,无论是属性还是方法都是通过原型引用的方式给新建的对象,因此都只会被创建一次。可惜的是,这种方式存在两个致命的问题:

1。没办法在构建对象的时候就写入想要的属性,因为原型在构造函数作用域外边,没办法通过传递参数的方式在对象创建的时候就写入属性值。只能在对象创建完毕后对值进行重写。

2。致命问题在于当属性指向对象时,这个对象会被多个实例所共享。考虑下面的代码:

 

var Obj = function () {};
Obj.prototype.name = 'me';
Obj.prototype.flag = new Array('A', 'B');
Obj.prototype.showName = function () {
    alert(this.name);
};
var obj1 = new Obj();
var obj2 = new Obj();

obj1.flag.push('C');

alert(obj1.flag); // A,B,C
alert(obj2.flag); //A,B,C

 

是的,当flag属性指向对象时,那么实例obj1和obj2都共享它,哪怕我们仅仅改变了obj1的flag属性,但是它的改变在实例obj2中任然可见。



面对这个问题,让我们不得不想是否应该把【构造函数方式】和【原型方式】结合起来,让他们互补。。。


【构造函数和原型混合方式】
我们让属性用构造函数方式创建,方法用原型方式创建即可:

var Obj = function (name) {
    this.name = name;
    this.flag = new Array('A', 'B');
};
Obj.prototype = {
    showName : function () {
        alert(this.name);
    }
};
var obj1 = new Obj();
var obj2 = new Obj();

obj1.flag.push('C');

alert(obj1.flag); // A,B,C
alert(obj2.flag); //A,B
  

 这种方式有效地结合了原型和构造函数的优势,是目前用的最多,也是副作用最少的方式。


不过,有些追求完美的家伙还不满足,因为在视觉上还没达到他们的要求,因为通过原型来创建方法的过程在视觉上还是会让人觉得它不太像实例的方法(尤其对于传统OOP语言的开发者来说。)
所以,我们可以让原型活动起来,让他也加入到构造函数里面去,好让这个构造函数在视觉上更为统一。而这一系列的过程只需用一个判断即可完成。

 

var Obj = function (name) {
    this.name = name;
    this.flag = new Array('A', 'B');
    if (typeof Obj._init == 'undefined') {
        Obj.prototype = {
            showName : function () {
                alert(this.name);
            }
        };
        Obj._init = true;
    }
};
 

 

如上,用_init作为一个标志来判断是否已经给原型创建了方法。如果是那么就不再执行。这样其实在本质上是没有任何变化的,方法仍是通过原型创建,唯一的区别在于这个构造函数看起来“江山统一”了。

但是这种动态原型的方式是有问题的,《JavaScript高级程序设计》里并没有深究。创建第一个对象的时候会因为prototype在对象实例化之前没来的及建起来,是根本无法访问的。所以第一个对象是无法访问原型方法的。同时这种方式在子类继承中也会有问题。
关于解决方案,我会在下一文中说明。 


其实就使用方便来说的话,个人觉得是没必要做这个判断的。。。呵呵 ^_^


 

转帖来自:http://www.cnblogs.com/hongru/archive/2010/11/08/1871359.html

 


 

分享到:
评论

相关推荐

    JS定义类或对象

    #### 一、JavaScript与对象概念 JavaScript是一种基于对象的脚本语言,这意味着在JavaScript中,对象扮演着非常重要的角色。在实际项目开发中,开发者经常通过`function fnname{…}`的方式直接声明并调用方法。这种...

    javascript定义对象

    下面将详细介绍JavaScript定义对象的四种主要方式:字面量语法、构造函数、对象解构和Proxy。 1. **字面量语法** 字面量语法是定义JavaScript对象最简单、最常见的方法。通过大括号`{}`来创建一个空对象,然后通过...

    JavaScript定义类和对象的方法

    举个例子,我们也可以通过Object类实例化的方式来定义对象: ```javascript var oClassA = new Object(); // 实例化Object类创建对象 oClassA.aaa = 'This is a class example!'; // 添加属性aaa oClassA.methodA = ...

    JavaScript定义类的几种方式总结

    JavaScript定义类的几种方式包括工厂方式、构造函数和原型方式,每种方法都有其特点和适用场景,接下来我们将详细探讨。 工厂方式是最早期的面向对象编程在JavaScript中的实践之一。使用工厂方式定义类,本质上就是...

    面向对象JavaScript精要(英文原版pdf)

    - **第三章:JavaScript中的类和对象**:详细介绍如何使用JavaScript创建类和对象。 - **第四章:继承**:探讨JavaScript中实现继承的不同方式。 - **第五章:封装和私有性**:讲解如何在JavaScript中实现封装以及...

    关于JavaScript定义类和对象的几种方式

    可以看看这个例子: 代码如下: var a = ‘global’; (function () { alert&#40;a&#41;; var a = ‘local’;...因为所有JavaScript函数都是一个对象。在函数里声明的变量可以看做这个对象的“类似属性”。对

    Javascript 面向对象的JavaScript进阶

    在面向对象的JavaScript中,类的成员可以根据它们与类和对象的关系分为“类成员”和“实例成员”。 ##### 8.2.1 实例属性和实例方法 **定义:** 实例成员包括实例属性和实例方法。每个对象实例都有自己的实例属性...

    JavaScript对象定义

    JavaScript对象定义是编程语言中的核心概念,它在Web开发中起着至关重要的作用。JavaScript是一种基于原型的对象导向语言,它的对象定义方式具有独特的灵活性。在本文中,我们将深入探讨JavaScript对象的定义、创建...

    js定义类 对象 构造函数,类的继承

    在提供的`ClassDefineAndExtends.js`文件中,可能包含了关于类定义和继承的示例代码。通过分析这个文件,我们可以看到实际应用中的类定义和继承实践,包括如何定义类,如何使用构造函数初始化对象,以及如何通过`...

    JavaScript定义类或函数的几种方式小结

    通过构造函数定义对象的属性,通过原型定义对象的方法。这样,每个对象的属性仍然是唯一的,而方法则可以通过原型链被所有对象共享。混合模式的代码示例如下: ```javascript function Car(color, doors) { this....

    javascript 经典面向对象设计

    - **类与对象**:JavaScript没有内置的类的概念,但在ES6中引入了`class`关键字来模拟类的行为。对象则是由类实例化出来的具体实体。 - **封装**:封装是指将数据和操作这些数据的方法绑定在一起,隐藏内部状态,只...

    第15章 javascript面向对象与原型

    然而,ECMAScript中并没有引入类的概念,这就使得JavaScript中的对象与传统面向对象语言中的对象存在差异。 接下来,我们将详细探讨在进行面向对象与原型的学习时所需的条件。首先,基础是必不可少的。JavaScript的...

    JavaScript的面向对象特性浅析与范例.pdf

    定义一个类的通用格式如下:function类名(参数列表){ //类的属性的定义 //类的方法的定义 } JavaScript定义类是通过定义函数直接实现的,每个函数可以直接看成class。 3. 定义类的数据成员——属性:在定义类的...

    Javascript面向对象编程

    本文将详细介绍JavaScript中的面向对象编程概念和技术,包括类定义、对象创建、继承、封装等内容。 #### 二、类定义和对象创建 ##### 2.1 类定义 在JavaScript中,“类”这一概念并不像在Java或C#这样的强类型...

    javascript 类, 对象 深入学习

    在这个主题中,我们将探讨JavaScript中的类与对象的实现方式,以及它们如何帮助我们构建复杂的软件系统。 首先,我们需要明白在JavaScript中,类是一种语法糖,实际上它是基于原型(prototype)的面向对象编程。ES6...

    javascript对象参考手册

    首先,手册的前几章会介绍JavaScript对象的基础知识,包括对象的创建、属性和方法的定义,以及对象与数据类型的关联。JavaScript中的对象是基于原型的,这意味着它们可以通过原型链共享属性和方法。此外,还会讲解...

    javascript面向对象

    本文旨在深入解析JavaScript面向对象编程的基础,包括对象的基本概念、类与对象的关系、对象的属性与方法、封装、继承与多态,以及JavaScript中对象的实现方式。 #### 对象的基本概念 面向对象编程的核心在于对...

Global site tag (gtag.js) - Google Analytics