JavaScript面向对象开发技术基础:类变量、类方法、实例变量、实例方法、作用域、闭包、模拟私有属性
五、类变量、类方法、实例变量、实例方法
先补充一下以前写过的方法:
在javascript中,所有的方法都有一个call方法和apply方法。这两个方法可以模拟对象调用方法。它的第一个参数是对象,后面的参数表示对象调用这个方法时的参数。(ECMAScript specifies two methods that are defined for all functions, call() and apply(). These methods allow you to invoke a function as if it were a method of some other object. The first argument to both call() and apply() is the object on which the function is to be invoked; this argument becomes the value of the this keyword within the body of the function. Any remaining arguments to call() are the values that are passed to the function that is invoked)。比如我们定义了一个方法f(),然后调用下面的语句:
f.call(o,1,2);
作用就相当于
o.m = f;
o.m(1,2);
delete o.m;
举个例子:
实例变量和实例方法都是通过实例对象加"."操作符然后跟上属性名或方法名来访问的,但是我们也可以为类来设置方法或变量,这样就可以直接用类名加"."操作符然后跟上属性名或方法名来访问。定义类属性和类方法很简单:
prototype属性的应用:
下面这个例子是根据原书改过来的。假设我们定义了一个Circle类,有一个radius属性和area方法,实现如下:
假设我们定义了100个Circle类的实例对象,那么每个实例对象都有一个radius属性和area方法,实际上,除了radius属性,每个Circle类的实例对象的area方法都是一样,这样的话,我们就可以把area方法抽出来定义在Circle类的prototype属性中,这样所有的实例对象就可以调用这个方法,从而节省空间。
现在,让我们用prototype属性来模拟一下类的继承:首先定义一个Circle类作为父类,然后定义子类PositionCircle。
原文链接:https://hzjavaeyer-group.iteye.com/group/wiki/1283-javascript-object-oriented-technology-five
六、作用域、闭包、模拟私有属性
先来简单说一下变量作用域,这些东西我们都很熟悉了,所以也不详细介绍。
注意一点,在javascript中没有块级别的作用域,也就是说在java或c/c++中我们可以用"{}"来包围一个块,从而在其中定义块内的局部变量,在"{}"块外部,这些变量不再起作用,同时,也可以在for循环等控制语句中定义局部的变量,但在javascript中没有此项特性:
在函数内部定义局部变量时要格外小心:
前面两个函数都很容易理解,关键是第三个:第一个alert语句并没有把全局变量"global"显示出来,而是undefined,这是因为在print3函数中,我们定义了sco局部变量(不管位置在何处),那么全局的sco属性在函数内部将不起作用,所以第一个alert中sco其实是局部sco变量,相当于:
从这个例子我们得出,在函数内部定义局部变量时,最好是在开头就把所需的变量定义好,以免出错。
函数的作用域在定义函数的时候已经确定了,例如:
闭包:
闭包是拥有变量、代码和作用域的表达式。在javascript中,函数就是变量、代码和函数的作用域的组合体,因此所有的函数都是闭包(JavaScript functions are a combination of code to be executed and the scope in which to execute them. This combination of code and scope is known as a closure in the computer science literature。 All JavaScript functions are closures)。好像挺简单,但是闭包到底有什么作用呢?看一个例子,我们想写一个方法,每次都得到一个整数,这个整数是每次加1的,没有思索,马上下笔:
一直用getNext函数得到下一个整数,而后不小心或者故意的将全局变量i的值设为0,然后再次调用getNext,你会发现又从1开始了........这时你会想到,要是把i设置成一个私有变量该多好,这样只有在方法内部才可能改变它,在函数之外就没有办法修改了。下面的代码就是按照这个要求来做得,后面我们详细讨论。为了解释方便,我们就把下面的代码称为demo1。
因为我们平时所说的javascript绝大多数都是指的在客户端(浏览器)下,所以这里也不例外。在javascript解释器启动时,会首先创建一个全局的对象(global object),也就是"window"所引用的对象。然后我们定义的所有全局属性和方法等都会成为这个对象的属性。
不同的函数和变量的作用域是不同的,因而构成了一个作用域链(scope chain)。很显然,在javascript解释器启动时,这个作用域链只有一个对象:window(Window Object,即global object)。在demo1中,temp函数是一个全局函数,因此temp()函数的作用域(scopr)对应的作用域链就是js解释器启动时的作用域链,只有一个window对象。当temp执行时,首先创建一个call对象(活动对象),然后把这个call对象添加到temp函数对应的作用域链的最前头,这时,temp()函数对应的作用域链就包含了两个对象:window对象和temp函数对应的call object(活动对象)。然后呢,因为我们在temp函数里定义了变量i,定义了函数b(),这些都会成为call object的属性。当然,在这之前会首先给call object对象添加arguments属性,保存了temp()函数执行时传递过来的参数。此时,整个的作用域链如下图所示:
同理可以得出函数b()执行时的整个作用域链:
注意在b()的作用域链中,b()函数对应的call object只有一个arguemnts属性,并没有i属性,这是因为在b()的定义中,并没有用var关键字来声明i属性,只有用var 关键字声明的属性才会添加到对应的call object上.在函数执行时,首先查找对应的call object有没有需要的属性,如果没有,再往上一级查找,直到找到为止,如果找不到,那就是undefined了。
这样我们再来看demo1的执行情况。我们用getNext引用了temp函数,而temp函数返回了函数b,这样getNext函数其实就是b函数的引用。
执行一次getNext,就执行一次b()函数。因为函数b()的作用域依赖于函数temp,因此temp函数在内存中会一直存在。函数b执行时,首先查找i,在b对应的call object中没有,于是往上一级找,在temp函数对应的call object中找到了,于是将其值加1,然后返回这个值。这样,只要getNext函数有效,那么b()函数就一直有效,同时,b()函数依赖的temp函数也不会消失,变量i也不会消失,而且这个变量在temp函数外部根本就访问不到,只能在temp()函数内部访问(b当然可以了)。
来看一个利用闭包来模拟私有属性的例子:
定义了一个Person类,有两个私有属性name,age,分别定义对应的get/set方法。虽然可以显示的设置p1的name、age属性,但是这种显示的设置,并不会改变我们最初设计时模拟出来的"name/age"私有属性。
原文链接:https://hzjavaeyer-group.iteye.com/group/wiki/1317-javascript-object-oriented-technology-6