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;

举个例子:

function Person(name,age) {  //定义方法  
    this.name = name;  
    this.age = age;  
}  
var o = new Object();   //空对象  
alert(o.name + "_" + o.age); //undefined_undefined  
  
Person.call(o,"sdcyst",18); //相当于调用:o.Person("sdcyst",18)  
alert(o.name + "_" + o.age); //sdcyst_18  
  
Person.apply(o,["name",89]);//apply方法作用同call,不同之处在于传递参数的形式是用数组来传递  
alert(o.name + "_" + o.age); //name_89

实例变量和实例方法都是通过实例对象加"."操作符然后跟上属性名或方法名来访问的,但是我们也可以为类来设置方法或变量,这样就可以直接用类名加"."操作符然后跟上属性名或方法名来访问。定义类属性和类方法很简单:

Person.counter = 0;   //定义类变量,创建的Person实例的个数  
function Person(name,age) {  
    this.name = name;  
    this.age = age;  
    Person.counter++; //没创建一个实例,类变量counter加1  
};  
  
Person.whoIsOlder = function(p1,p2) { //类方法,判断谁的年龄较大  
    if(p1.age > p2.age) {  
        return p1;  
    } else {  
        return p2;  
    }  
}  
  
var p1 = new Person("p1",18);  
var p2 = new Person("p2",22);  
  
alert("现在有 " + Person.counter + "个人");  //现在有2个人  
var p = Person.whoIsOlder(p1,p2);  
alert(p.name + "的年龄较大");   //p2的年龄较大

prototype属性的应用:

下面这个例子是根据原书改过来的。假设我们定义了一个Circle类,有一个radius属性和area方法,实现如下:

function Circle(radius) {  
    this.radius = radius;  
    this.area = function() {  
        return 3.14 * this.radius * this.radius;  
    }  
}  
var c = new Circle(1);  
alert(c.area());  //3.14

假设我们定义了100个Circle类的实例对象,那么每个实例对象都有一个radius属性和area方法,实际上,除了radius属性,每个Circle类的实例对象的area方法都是一样,这样的话,我们就可以把area方法抽出来定义在Circle类的prototype属性中,这样所有的实例对象就可以调用这个方法,从而节省空间。

function Circle(radius) {  
    this.radius = radius;  
}  
Circle.prototype.area = function() {  
        return 3.14 * this.radius * this.radius;  
    }  
var c = new Circle(1);  
alert(c.area());  //3.14

现在,让我们用prototype属性来模拟一下类的继承:首先定义一个Circle类作为父类,然后定义子类PositionCircle。

function Circle(radius) {  //定义父类Circle  
    this.radius = radius;  
}  
Circle.prototype.area = function() { //定义父类的方法area计算面积  
    return this.radius * this.radius * 3.14;  
}  
  
function PositionCircle(x,y,radius) { //定义类PositionCircle  
    this.x = x;                    //属性横坐标  
    this.y = y;                    //属性纵坐标  
    Circle.call(this,radius);      //调用父类的方法,相当于调用this.Circle(radius),设置PositionCircle类的  
                                   //radius属性  
}  
PositionCircle.prototype = new Circle(); //设置PositionCircle的父类为Circle类  
  
var pc = new PositionCircle(1,2,1);  
alert(pc.area());  //3.14  
                   //PositionCircle类的area方法继承自Circle类,而Circle类的  
                   //area方法又继承自它的prototype属性对应的prototype对象  
alert(pc.radius); //1  PositionCircle类的radius属性继承自Circle类  
  
/* 
注意:在前面我们设置PositionCircle类的prototype属性指向了一个Circle对象, 
因此pc的prototype属性继承了Circle对象的prototype属性,而Circle对象的constructor属 
性(即Circle对象对应的prototype对象的constructor属性)是指向Circle的,所以此处弹出 
的是Circle. 
*/  
alert(pc.constructor); //Circle      
  
/*为此,我们在设计好了类的继承关系后,还要设置子类的constructor属性,否则它会指向父类 
的constructor属性 
*/  
PositionCircle.prototype.constructor = PositionCircle  
alert(pc.constructor);  //PositionCircle

原文链接:https://hzjavaeyer-group.iteye.com/group/wiki/1283-javascript-object-oriented-technology-five


六、作用域、闭包、模拟私有属性
先来简单说一下变量作用域,这些东西我们都很熟悉了,所以也不详细介绍。

var sco = "global";  //全局变量  
function t() {   
    var sco = "local";  //函数内部的局部变量  
    alert(sco);         //local 优先调用局部变量  
}  
t();             //local  
alert(sco);      //global  不能使用函数内的局部变量

注意一点,在javascript中没有块级别的作用域,也就是说在java或c/c++中我们可以用"{}"来包围一个块,从而在其中定义块内的局部变量,在"{}"块外部,这些变量不再起作用,同时,也可以在for循环等控制语句中定义局部的变量,但在javascript中没有此项特性:

function f(props) {  
    for(var i=0; i<10; i++) {}  
    alert(i);         //10  虽然i定义在for循环的控制语句中,但在函数  
                      //的其他位置仍旧可以访问该变量.  
    if(props == "local") {  
        var sco = "local";  
    alert(sco);   
    }  
    alert(sco);       //同样,函数仍可引用if语句内定义的变量  
}  
f("local");      //10  local   local

在函数内部定义局部变量时要格外小心:

var sco = "global";  
function print1() {  
    alert(sco);   //global  
}  
function print2() {  
    var sco = "local";  
    alert(sco);   //local  
}  
function print3() {  
    alert(sco);   //undefined  
    var sco = "local";   
    alert(sco);   local  
}  
  
print1();  //global  
print2();  //local  
print3();  //undefined  local

前面两个函数都很容易理解,关键是第三个:第一个alert语句并没有把全局变量"global"显示出来,而是undefined,这是因为在print3函数中,我们定义了sco局部变量(不管位置在何处),那么全局的sco属性在函数内部将不起作用,所以第一个alert中sco其实是局部sco变量,相当于:

function print3() {  
    var sco;  
    alert(sco);  
    sco = "local";  
    alert(sco);  
}

从这个例子我们得出,在函数内部定义局部变量时,最好是在开头就把所需的变量定义好,以免出错。
函数的作用域在定义函数的时候已经确定了,例如:

var scope = "global"   //定义全局变量  
function print() {  
    alert(scope);  
}  
function change() {  
    var scope = "local";  //定义局部变量  
    print();              //虽然是在change函数的作用域内调用print函数,  
                          //但是print函数执行时仍旧按照它定义时的作用域起作用  
}  
change();    //golbal

闭包:
闭包是拥有变量、代码和作用域的表达式。在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的,没有思索,马上下笔:

var i = 0;  
function getNext() {  
    i++;  
    return i;  
}  
alert(getNext()); //1  
alert(getNext()); //2  
alert(getNext()); //3

一直用getNext函数得到下一个整数,而后不小心或者故意的将全局变量i的值设为0,然后再次调用getNext,你会发现又从1开始了........这时你会想到,要是把i设置成一个私有变量该多好,这样只有在方法内部才可能改变它,在函数之外就没有办法修改了。下面的代码就是按照这个要求来做得,后面我们详细讨论。为了解释方便,我们就把下面的代码称为demo1。

function temp() {  
    var i = 0;  
    function b() {  
        return ++i;  
    }  
    return b;  
}  
var getNext = temp();  
alert(getNext());    //1  
alert(getNext());    //2  
alert(getNext());    //3  
alert(getNext());    //4

因为我们平时所说的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()函数执行时传递过来的参数。此时,整个的作用域链如下图所示:

JavaScript面向对象技术基础(下)

同理可以得出函数b()执行时的整个作用域链:

JavaScript面向对象技术基础(下)

注意在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当然可以了)。

来看一个利用闭包来模拟私有属性的例子:

function Person(name, age) {    
    this.getName = function() { return name; };    
    this.setName = function(newName) { name = newName; };    
    this.getAge = function() { return age; };    
    this.setAge = function(newAge) { age = newAge; };    
}    
    
var p1 = new Person("sdcyst",3);    
alert(p1.getName());  //sdcyst    
alert(p1.name);       //undefined   因为Person('类')没有name属性    
p1.name = "mypara"    //显示的给p1添加name属性    
alert(p1.getName());  //sdcyst     但是并不会改变getName方法的返回值    
alert(p1.name);       //mypara     显示出p1对象的name属性    
p1.setName("sss");    //改变私有的"name"属性  
alert(p1.getName());  //sss    
alert(p1.name);       //仍旧为mypara

定义了一个Person类,有两个私有属性name,age,分别定义对应的get/set方法。虽然可以显示的设置p1的name、age属性,但是这种显示的设置,并不会改变我们最初设计时模拟出来的"name/age"私有属性。

原文链接:https://hzjavaeyer-group.iteye.com/group/wiki/1317-javascript-object-oriented-technology-6


参考:

anzhihe 安志合个人博客,版权所有 丨 如未注明,均为原创 丨 转载请注明转自:https://chegva.com/5144.html | ☆★★每天进步一点点,加油!★★☆ | 

您可能还感兴趣的文章!

发表评论

电子邮件地址不会被公开。 必填项已用*标注