几种JavaScript定义类和对象的方法
转自:http://www.nowamagic.net/javascript/js_MethodsToCreateClassObject.php
最近偶然碰到有朋友问我"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作了声明而没有赋值。所以结果可想而知。
在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();
我们依赖原型对构造函数进行重写,无论是属性还是方法都是通过原型引用的方式给新建的对象,因此都只会被创建一次。可惜的是,这种方式存在两个致命的问题:
- 没办法在构建对象的时候就写入想要的属性,因为原型在构造函数作用域外边,没办法通过传递参数的方式在对象创建的时候就写入属性值。只能在对象创建完毕后对值进行重写。
- 致命问题在于当属性指向对象时,这个对象会被多个实例所共享。考虑下面的代码:
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在对象实例化之前没来的及建起来,是根本无法访问的。所以第一个对象是无法访问原型方法的。同时这种方式在子类继承中也会有问题。
相关推荐
Javascript对象定义的几种方式
JAVASCRIPT中定义对象的几种方式.pdf
可以看看这个例子: 代码如下: var a = ‘global’; (function () { alert(a); var a = ‘local’;...因为所有JavaScript函数都是一个对象。在函数里声明的变量可以看做这个对象的“类似属性”。对
提起面向对象我们就能...javaScript中创建自己的类和对象,我们应该是必须掌握的,我们都知道javaScript中对象的属性可以在对象创建后动态定义,比如下面的代码: 代码如下:[removed] //定义 var oCar = new Objec
详解js创建对象的几种方式和对象方法 这篇文章是看js红宝书第8章,记的关于对象的笔记(第二篇)。 创建对象的几种模式: 工厂是函数的意思。工厂模式核心是定义一个返回全新对象的函数。 function getObj...
工厂方式 javaScript中创建自己的类和对象,我们应该是必须掌握的,我们都知道javaScript中对象的属性可以在对象创建后动态定义,比如下面的代码: 代码如下: [removed] //定义 var oCar = new Object();
前言类与实例:类的声明生成实例类与继承:如何实现继承:继承的本质就是原型链继承的几种方式类的定义、实例化类的定义/类的声明方式一:用构造函数模拟类(传统写法)/
前言类与实例:类的声明生成实例类与继承:如何实现继承:继承的本质就是原型链继承的几种方式类的定义、实例化类的定义/类的声明方式一:用构造函数模拟类(传统写法)/
本文实例讲述了JS定义函数的几种常用方法。分享给大家供大家参考,具体如下: 在 JavaScript 语言里,函数是一种对象,所以可以说函数是 JavaScript 里的一等公民(first-class citizens)。 之前我们这样定义过一个...
一、javaScript 的几种数据类型 1、数字类型 2、字符串 3、布尔型 4、数组 5、null 和 undefined 6、对象类型 二、 类型转换 1、字符串转整数 2、字符串转小数 3、数字转字符串 4、布尔型与其他类型的相互转换 5、...
1.JavaScript的数值处理对象学习 .txt 2.JavaScript的系统函数学习 .txt 3.js中用于对象的语句——with和for...in语句...定义JavaScript对象的几种格式.txt 8.正则表达式.txt 9.55种网页常用小技巧 .txt
在JavaScript中,有时可以重用其它对象的函数或方法,而不一定非得是对象本身或原型上定义的。通过 call()、apply() 和 bind() 方法,我们可轻易地借用其它对象的方法,而无须继承这些对象。这是专业 JavaScript ...
我们知道,在js中,是没有类的概念的。类的所有实例对象都从同一个原型对象上继承属性,因此,原型对象是类的核心。...* Person类:定义一个人,有name属性和getName方法 */ [removed] function Person(na
上几节讲了 JavaScript 面向对象之命名空间 、 javascript 面向对象的JavaScript类 、 JavaScript 面向对象的之私有成员和公开成员 与 Javascript 面向对象之重载,大家可以先看上面的再继续往下看。在JavaScript...
详解JavaScript中的链式调用 ...链式调用通常有以下几种实现方式,但是本质上相似,都是通过返回对象供之后进行调用。 this的作用域链,jQuery的实现方式,通常链式调用都是采用这种方式。 返回对象本身, 同t
相信大家对javascript中的面向对象写法都不陌生,那还记得有几种创建对象的写法吗?相信大家除了自己常写的都有点模糊了,那接下来就由我来帮大家回忆回忆吧! 1. 构造函数模式 通过创建自定义的构造函数,来定义...
JavaScript 的语法和命令,按照它们的用法和功能分为几种类型。 对象是JavaScript 的构造块,它们被用来返回并修改表格、页面、浏览器和程序员定义的变量等的状况。理解对象的一种比较容易的方法是把它们看成一个...
该死的 Javascript 总是会让我们死去很多的脑细胞,这篇文章主要说明下声明 Javascript 类的几种方法。 工厂模式 工厂模式可能是很多开发人员使用的一种模式,简单的说这种方法先定义“地基”,然后在往上面扔(绑定...