原文:http://www.cnblogs.com/hongru/archive/2010/11/08/1871359.html

  在说这个话题之前,我想先说几句题外话:最近偶然碰到有朋友问我“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在工厂函数外定义,然后通过属性指向该方法,可以避开这个问题:

代码

可惜的是,这种方式让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在对象实例化之前没来的及建起来,是根本无法访问的。所以第一个对象是无法访问原型方法的。同时这种方式在子类继承中也会有问题。
关于解决方案,我会在下一文中说明。

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

我所了解的关于JavaScript定义类和对象的几种方式的更多相关文章

  1. javaScript中定义类或对象的五种方式

    第一种方式: 工厂方法 能创建并返回特定类型的对象的工厂函数(factory function). function createCar(sColor){ var oTempCar = new Obj ...

  2. JavaScript定义类与对象的一些方法

    最近偶然碰到有朋友问我"hoisting"的问题.即在js里所有变量的声明都是置顶的,而赋值则是在之后发生的.可以看看这个例子: 1 var a = 'global'; 2 (fu ...

  3. javascript定义类或对象的方式

    本文介绍的几种定义类或对象的方式中,目前使用最广泛的是:混合的构造函数/原型方式.动态原型方式.不要单独使用经典的构造函数或原型方式. 工厂方式 构造器函数 原型方式 混合的构造函数/原型方式 动态原 ...

  4. Vue指令v-for之遍历输出JavaScript数组,json对象的几种方式

    定义数据: <script> new Vue({ el:"#test", data:{ message:"infor", list:["a ...

  5. Javascript创建类和对象

    现总结一下Javascript创建类和对象的几种方法: 1.原始的创建方法: <script type="text/javascript"> var person = ...

  6. Javascript定义类(class)的三种方法

    将近20年前,Javascript诞生的时候,只是一种简单的网页脚本语言.如果你忘了填写用户名,它就跳出一个警告. 如今,它变得几乎无所不能,从前端到后端,有着各种匪夷所思的用途.程序员用它完成越来越 ...

  7. [转]Javascript定义类的三种方法

    作者: 阮一峰 原文地址:http://www.ruanyifeng.com/blog/2012/07/three_ways_to_define_a_javascript_class.html 将近2 ...

  8. javascript 定义类(转载)

    Javascript本身并不支持面向对象,它没有访问控制符,它没有定义类的关键字class,它没有支持继承的extend或冒号,它也没有用来支持虚函数的virtual,不过,Javascript是一门 ...

  9. javascript定义类和类的实现

    首先说说类,在一个类里我们会有以下的几个特征: 1. 公有方法 2. 私有方法 3. 属性 4. 私有变量 5. 析构函数 我们直接看一个例子: /***定义类***/ var Class = fun ...

随机推荐

  1. "[Vue warn]: Failed to mount component: template or render function not defined"错误的解决

    VUE中动态路由出错: vue.esm.js?a026: [Vue warn]: Failed to mount component: template or render function not ...

  2. CentOS7.6下模拟iSCSI,Windows来连

    如题,在CentOS7上模拟一个iSCSI设备,然后在Windows Server 2008上连接这个iSCSI设备 第一步,CentOS7上的操作.CentOS7上安装iSCSI模拟器需要3个包,我 ...

  3. 思维题--code forces round# 551 div.2

    思维题--code forces round# 551 div.2 题目 D. Serval and Rooted Tree time limit per test 2 seconds memory ...

  4. centos 7 查看所有登录用户的操作历史

    2019-01-07 转自  https://www.cnblogs.com/kevingrace/p/7373146.html centos 7 查看所有登录用户的操作历史 在Linux系统的环境下 ...

  5. 开源:我的Android新闻客户端,速度快、体积小、支持离线阅读、操作简便、内容展现形式丰富多样、信息量大、功能全面 等(要代码的留下邮箱)

    分享:我的Android新闻客户端,速度快.体积小.支持离线阅读.操作简便.内容展现形式丰富多样.信息量大.功能全面 等(要代码的留下邮箱) 历时30天我为了开发这个新闻客户端APP,以下简称觅闻 h ...

  6. 本地jar包 安装到本地仓库中的命令

    maven 项目 本地jar包 安装到本地仓库中去: 首先进入到该文件所在文件夹内 若不在直接绝对路径就可以.注意命令中的空格 mvn install:install-file  -Dfile=文件名 ...

  7. Wrapper配置详解及高级应用(转)

    转自:http://286.iteye.com/blog/1921414 将一个简单的程度如HelloWorld 的应用包装秤Wrapper 服务并不复杂,甚至可以认为非常简单.但是实际项目应用过程中 ...

  8. Python爬虫学习:Python内置的爬虫模块urllib库

    urllib库 urllib库是Python中一个最基本的网络请求的库.它可以模拟浏览器的行为发送请求(都是这样),从而获取返回的数据 urllib.request 在Python3的urllib库当 ...

  9. Linux下Tomcat8.0.44配置使用Apr

    听说Apr可以提高tomcat很多的性能,配置具体如下 1.安装apr 1.5.2 [root@ecs-3c46 ]# cd /usr/local/src [root@ecs-3c46 src]# w ...

  10. try catch finall 结构里的 return

    public class ExceptionDemo1 { public static void main(String[] args) { try { ; /b; System.out.printl ...