1 又是几个基本概念 为什么要说又呢? 在讨论继承时,我们已经列出了一些基本概念了,那些概念是跟封装密切相关的概念,今天我们要讨论的基本概念,主要是跟继承与多态相关的,但是它们跟封装也有一些联系。 1.1 定义和赋值 变量定义是指用 var a; 这种形式来声明变量。 函数定义是指用 function a(…) {…} 这种形式来声明函数。 var a = 1; 是两个过程。第一个过程是定义变量 a,第二个过程是给变量 a 赋值。 同样 var a = function(…) {}; 也是两个过程,第一个过程是定义变量 a 和一个匿名函数,第二个过程是把匿名函数赋值给变量 a。 变量定义和函数定义是在整个脚本执行之前完成的,而变量赋值是在执行阶段完成的。 变量定义的作用仅仅是给所声明的变量指明它的作用域,变量定义并不给变量初始值,任何没有定义的而直接使用的变量,或者定义但没有赋值的变量,他们的值都是 undefined。 函数定义除了声明函数所在的作用域外,同时还定义函数体结构。这个过程是递归的,也就是说,对函数体的定义包括了对函数体内的变量定义和函数定义。 通过下面这个例子我们可以更明确的理解这一点: Java代码 alert(a); alert(b); alert(c); var a = "a"; function a() {} function b() {} var b = "b"; var c = "c"; var c = function() {} […]
View DetailsJavaScript 是一种非常灵活的面向对象程序设计语言,它与传统的强类型的面向对象程序设计语言(如 C++,Java,C# 等)有很大不同,所以要实现如 C++、java、C# 当中的一些特性就需要换一种思考方式来解决。今天主要讨论如何在 JavaScript 脚本中实现数据的封装(encapsulation)。 数据封装说的简单点就是把不希望调用者看见的内容隐藏起来。它是面向对象程序设计的三要素之首,其它两个是继承和多态,关于它们的内容在后面再讨论。 关于数据封装的实现,在 C++、Java、C# 等语言中是通过 public、private、static 等关键字实现的。在 JavaScript 则采用了另外一种截然不同的形式。在讨论如何具体实现某种方式的数据封装前,我们先说几个简单的,大家所熟知却又容易忽略的 JavaScript 的概念。 1 几个基本概念 1.1 变量定义 在 JavaScript 语言中,是通过 var 关键字来定义变量的。 但是如果我们直接给一个没有使用 var 定义的变量赋值,那么这个变量就会成为全局变量。 一般情况下,我们应该避免使用没有用 var 定义的变量,主要原因是它会影响程序的执行效率,因为存取全局变量速度比局部变量要慢得多。 但是这种用法可以保证我们的变量一定是全局变量。 另外,为了保证速度,我们在使用全局变量时,可以通过 var 定义一个局部变量,然后将全局变量赋予之,由此可以得到一个全局变量的局部引用。 1.2 变量类型 没有定义的变量,类型为 undefined。 变量的值可以是函数。 函数在 JavaScript 中可以充当类的角色。 1.3 变量作用域 变量作用域是指变量生存周期的有效范围。 单纯用 { } 创建的块不能创建作用域。 with 将它包含的对象作用域添加到当前作用域链中,但 with 不创建新的作用域。with 块结束后,会将对象作用域从当前作用域链中删除。 try-catch 中,catch 的错误对象只在 catch 块中有效,但 catch 块中定义的变量属于当前作用域。 其它如 if、for、for-in、while、do-while、switch 等控制语句创建的块不能创建作用域。 用 function 创建的函数,会创建一个新的作用域添加到当前作用域中。 2 封装 下面我们就来讨论具体的封装。首先说一下大家最熟悉的几种封装:私有实例成员、公有实例成员和公有静态成员。最后会讨论一下大家所不熟悉的私有静态成员和静态类的封装办法。因为下面要讨论的是面向对象编程,所有当函数作为类来定义和使用时,我们暂且将其成为类。 2.1 私有实例成员 私有实例成员在 JavaScript 中实际上可以用函数内的局部变量来实现,它相当于类的私有实例成员。例如: class1 = function() { // private […]
View Details提起面向对象我们就能想到类,对象,封装,继承,多态。在《javaScript高级程序设计》(人民邮电出版社,曹力、张欣译。英文名字是:Professional JavaScript for Web Developers)这本书中描述的还算比较详细。我们看看JavaScript中定义类的各种方法。 1.工厂方式 javaScript中创建自己的类和对象,我们应该是必须掌握的,我们都知道javaScript中对象的属性可以在对象创建后动态定义,比如下面的代码: 复制代码代码如下: <script type="text/javascript"> //定义 var oCar = new Object(); oCar.color = "red"; oCar.doors = 4; oCar.showColor = function() { alert(this.color); } //调用 oCar.showColor(); </script> 我们很容易使用oCar对象,但是我们创就是想创建多个Car实例。我们可以使用一个函数来封装上面的代码来实现:<script type="text/javascript"> 复制代码代码如下: //定义 function createCar() { var oCar = new Object(); oCar.color = "red"; oCar.doors = 4; oCar.showColor = function() { alert(this.color); } return oCar; } //调用 var ocar1 = createCar(); var ocar2 = createCar(); ocar1.color = "black"; ocar1.showColor(); ocar2.showColor(); </script> 顺便说一下,javaScript对象默认成员属性都是public 的。这种方式我们称为工厂方式,我们创造了能创建并返回特定类型的对象的工厂。 这样做有点意思了,但是在面向对象中我们经常使用创建对象的方法是: Car car=new Car(); 使用new 关键字已经深入人心,因此我们使用上面的方法去定义总感觉别扭,并且每次调用时都去创建新的属性以及函数,功能上也不实际。下来我们看看构造函数的形式定义类。 2.构造函数 这种方式看起来有点象工厂函数。具体表现如下: 复制代码代码如下: <script type="text/javascript"> //定义 function Car(color, doors) { this.color = color; this.doors = doors; this.showColor = function() { alert(this.color); }; } //调用 var car1 = new Car("red", 4); var car2 = new Car("blue", 4); car1.showColor(); car2.showColor(); </script> 看起来效果很明显,有差别了吧。感觉有点意思了。在构造函数内部创造对象使用this 关键字,使用new 运算符创建对象感觉非常亲切。但是也有点问题:每次new 对象时都会创建所有的属性,包括函数的创建,也就是说多个对象完全独立,我们定义类的目的就是为了共享方法以及数据,但是car1对象与car2对象都是各自独立的属性与函数,最起码我们应该共享方法。这就是原形方式的优势所在。 3.原型方式 利用对象的prototype属性,可把它看出创建新对象所依赖的原型。方法如下: 复制代码代码如下: <script type="text/javascript"> //定义 function Car() { }; Car.prototype.color = "red"; Car.prototype.doors = 4; Car.prototype.drivers = new […]
View Details这个系列的第一部分介绍了"封装",第二部分介绍了使用构造函数实现"继承"。 今天是最后一个部分,介绍不使用构造函数实现"继承"。 一、什么是"非构造函数"的继承? 比如,现在有一个对象,叫做"中国人"。 var Chinese = {nation:’中国'}; 还有一个对象,叫做"医生"。 var Doctor ={career:’医生'} 请问怎样才能让"医生"去继承"中国人",也就是说,我怎样才能生成一个"中国医生"的对象? 这里要注意,这两个对象都是普通对象,不是构造函数,无法使用构造函数方法实现"继承"。 二、object()方法 json格式的发明人Douglas Crockford,提出了一个object()函数,可以做到这一点。 function object(o) { function F() {} F.prototype = o; return new F(); } 这个object()函数,其实只做一件事,就是把子对象的prototype属性,指向父对象,从而使得子对象与父对象连在一起。 使用的时候,第一步先在父对象的基础上,生成子对象: var Doctor = object(Chinese); 然后,再加上子对象本身的属性: Doctor.career = '医生'; 这时,子对象已经继承了父对象的属性了。 alert(Doctor.nation); //中国 三、浅拷贝 除了使用"prototype链"以外,还有另一种思路:把父对象的属性,全部拷贝给子对象,也能实现继承。 下面这个函数,就是在做拷贝: function extendCopy(p) { var c = {}; for (var i in p) { c[i] = p[i];} c.uber = p; return c;} 使用的时候,这样写: var Doctor = extendCopy(Chinese); Doctor.career = '医生'; alert(Doctor.nation); // 中国 但是,这样的拷贝有一个问题。那就是,如果父对象的属性等于数组或另一个对象,那么实际上,子对象获得的只是一个内存地址,而不是真正拷贝,因此存在父对象被篡改的可能。 请看,现在给Chinese添加一个"出生地"属性,它的值是一个数组。 Chinese.birthPlaces = ['北京',’上海',’香港']; 通过extendCopy()函数,Doctor继承了Chinese。 var Doctor = extendCopy(Chinese); 然后,我们为Doctor的"出生地"添加一个城市: […]
View Details这个系列的第一部分,主要介绍了如何"封装"数据和方法,以及如何从原型对象生成实例。 今天要介绍的是,如何生成一个"继承"多个对象的实例。 比如,现在有一个"动物"对象的构造函数, function Animal(){ this.species = "动物"; } 还有一个"猫"对象的构造函数, function Cat(name,color){ this.name = name; this.color = color; } 怎样才能使"猫"继承"动物"呢? 1. 构造函数绑定 最简单的方法,大概就是使用call或apply方法,将父对象的构造函数绑定在子对象上,也就是在子对象构造函数中加一行: function Cat(name,color){ Animal.apply(this, arguments); this.name = name; this.color = color; } var cat1 = new Cat("大毛","黄色"); alert(cat1.species); // 动物 2. prototype模式 更常见的做法,则是使用prototype属性。 如果"猫"的prototype对象,指向一个Animal的实例,那么所有"猫"的实例,就能继承Animal了。 Cat.prototype = new Animal(); Cat.prototype.constructor = Cat; var cat1 = new Cat("大毛","黄色"); alert(cat1.species); // 动物 代码的第一行,我们将Cat的prototype对象指向一个Animal的实例。 Cat.prototype = new Animal(); 它相当于完全删除了prototype 对象原先的值,然后赋予一个新值。但是,第二行又是什么意思呢? Cat.prototype.constructor = Cat; 原来,任何一个prototype对象都有一个constructor属性,指向它的构造函数。也就是说,Cat.prototype 这个对象的constructor属性,是指向Cat的。 我们在前一步已经删除了这个prototype对象原来的值,所以新的prototype对象没有constructor属性,所以我们必须手动加上去,否则后面的"继承链"会出问题。这就是第二行的意思。 总之,这是很重要的一点,编程时务必要遵守。下文都遵循这一点,即如果替换了prototype对象, o.prototype = {}; 那么,下一步必然是为新的prototype对象加上constructor属性,并将这个属性指回原来的构造函数。 o.prototype.constructor = o; 3. 直接继承prototype 由于Animal对象中,不变的属性都可以直接写入Animal.prototype。所以,我们也可以让Cat()跳过 Animal(),直接继承Animal.prototype。 现在,我们先将Animal对象改写: function […]
View Details学习Javascript,最难的地方是什么? 我觉得,Object(对象)最难。因为Javascript的Object模型很独特,和其他语言都不一样,初学者不容易掌握。 下面就是我的学习笔记,希望对大家学习这个部分有所帮助。我主要参考了以下两本书籍: 《面向对象的Javascript》(Object-Oriented JavaScript) 《Javascript高级程序设计(第二版)》(Professional JavaScript for Web Developers, 2nd Edition) 它们都是非常优秀的Javascript读物,推荐阅读。 笔记分成三部分。今天的第一部分是讨论"封装"(Encapsulation),后面的第二部分和第三部分讨论"继承"(Inheritance)。 ============================ Javascript 面向对象编程(一):封装 作者:阮一峰 Javascript是一种基于对象(object-based)的语言,你遇到的所有东西几乎都是对象。但是,它又不是一种真正的面向对象编程(OOP)语言,因为它的语法中没有class(类)。 那么,如果我们要把"属性"(property)和"方法"(method),封装成一个对象,甚至要从原型对象生成一个实例对象,我们应该怎么做呢? 1. 生成对象的原始模式 假定我们把猫看成一个对象,它有"名字"和"颜色"两个属性。 var Cat = { name : ", color : " } 现在,我们需要根据这个原型对象,生成两个实例对象。 var cat1 = {}; // 创建一个空对象 cat1.name = "大毛"; // 按照原型对象的属性赋值 cat1.color = "黄色"; var cat2 = {}; cat2.name = "二毛"; cat2.color = "黑色"; 好了,这就是最简单的封装了。但是,这样的写法有两个缺点,一是如果多生成几个实例,写起来就非常麻烦;二是实例与原型之间,没有任何办法,可以看出有什么联系。 2. 原始模式的改进 我们可以写一个函数,解决代码重复的问题。 function Cat(name,color){ return { name:name, color:color } } 然后生成实例对象,就等于是在调用函数: var cat1 = Cat("大毛","黄色"); var cat2 = Cat("二毛","黑色"); 这种方法的问题依然是,cat1和cat2之间没有内在的联系,不能反映出它们是同一个原型对象的实例。 3. 构造函数模式 为了解决从原型对象生成实例的问题,Javascript提供了一个构造函数(Constructor)模式。 所谓"构造函数",其实就是一个普通函数,但是内部使用了this变量。对构造函数使用new运算符,就能生成实例,并且this变量会绑定在实例对象上。 比如,猫的原型对象现在可以这样写, function Cat(name,color){ […]
View Details