看透JavaScript:原理、方法与实践
上QQ阅读APP看书,第一时间看更新

4.5 三种子类型

前面介绍过ES中的function共有三种用法:作为对象使用、处理业务以及创建object类型的实例对象。跟这三种用法相对应的有三种子类型,分别是对象的属性、变量(包括参数)和创建出来的object类型实例对象的属性。这三种子类型是相互独立的,而且也很容易区分。但是,很多新手往往会因为不了解它们之间的区别,经常将它们混淆,混淆之后就会带来不必要的错误,因此一定要将它们区分清楚。下面将分别对这三种类型进行介绍。

4.5.1 function作为对象来使用

这种情况下,function对象的子类型就是对象自己的属性,这时通过点操作符“.”(或者方括号操作符)使用,例如下面的例子。

    function book(){}
    book.price = 161.0;
    book.getPrice = function () {
        return this.price;
    }
    console.log(book.getPrice());    //161

在这种情况下,function是作为object类型的对象来使用的。上面的例子中首先定义了function类型的book对象,然后给它添加了price属性和getPrice方法,这时就可以直接使用点操作符来对其进行操作了。

4.5.2 function用于处理业务

这种情况下,function的子类型就是自己定义的局部变量(包括参数),这时的变量是在方法被调用时通过变量作用域链来管理的。变量作用域链的相关内容前面已经介绍过,这里就不再重述了。

4.5.3 function用于创建对象

这种情况下,对应的子类型是使用function创建的实例对象的属性,主要包括在function中通过this添加的属性,以及创建完成之后实例对象自己添加的属性。另外,还可以调用function的prototype属性对象所包含的属性,例如前面用过的Car的例子。

    function Car(color, displacement){
        this.color = color;
        this.displacement = displacement;
    }
    Car.prototype.logMessage = function(){
        console.log(this.color+", "+this.displacement);
    }
    var car = new Car("black", "2.4T");

这个例子中创建的car对象就包含有color和displacement两个属性,而且还可以调用Car.prototype的logMessage方法。当然,创建完之后还可以使用点操作符给创建的car对象添加或者修改属性,也可以使用delete删除其中的属性,例如下面的例子。

    function Car(color, displacement){
        this.color = color;
        this.displacement = displacement;
    }
    Car.prototype.logMessage = function(){
        console.log(this.color+", "+this.displacement);
    }
    var car = new Car("black", "2.4T");
    car.logColor = function () {
        console.log(this.color);
    }
    car.logColor();   //black
    car.color = "red";
    car.logColor();   //red
    delete car.color;
    car.logColor();   //undefined

这个例子中,在创建完car对象后又给它添加了logColor方法,可以打印出car的color属性。添加完logColor方法后直接调用就可以打印出car原来的color属性值(black)。然后,将其修改为red,再打印就打印出了red。最后,使用delete删除car的color属性,这时再调用logColor方法就会打印出undefined。

4.5.4 三种子类型的关系

function的三种子类型是相互独立的,它们只能在自己所对应的环境中使用而不能相互调用,例如下面的例子。

    function log(msg){
        console.log(msg);
    }


    function Bird(){
        var name = "kitty";
        this.type = "pigeon";
        this.getName = function () {
            return this.name;
        }
    }
    Bird.color="white";
    Bird.getType = function () {
        return this.type;
    }
    Bird.prototype.getColor = function () {
        return this.color;
    }
    var bird = new Bird();
    log(bird.getColor());    //undefined
    log(bird.getName());     //undefined
    log(Bird.getType());     //undefined

这个例子中的最后三条语句都会打印出undefined,下面分析其中的原因。

Bird作为对象时包含color和getType两个属性,作为处理业务的函数时包含一个名为name的局部变量,创建的实例对象bird具有type和getName两个属性,而且还可以调用Bird.prototype中的getColor属性,getColor也可以看作bird的属性,如表4-2所示。

表4-2 Bird的用法及子类型

每种用法中所定义的方法只能调用相应用法所对应的属性,而不能交叉调用,从表4-2中的对应关系可以看出,getName、getColor和getType三个方法都获取不到对应的值,所以它们都会输出undefined。

另外,getName和getColor是bird的属性方法,getType是Bird的属性方法,如果用Bird对象调用getName或getColor方法或者使用bird对象调用getType方法都会抛出找不到方法的错误。

除了三种子类型不可以相互调用之外,还有一种情况也非常容易被误解,那就是对象的属性并没有继承的关系,例如下面的例子。

    function obj(){}
    obj.v = 1;
    obj.func = {
        logV : function(){
            console.log(this.v);
        }
    };
    obj.func.logV();

这个例子中的obj是作为对象使用的,obj有一个属性v和一个对象属性func, func对象中又有一个logV方法,logV方法用于打印对象的v属性。这里需要特别注意,logV方法打印的是func对象的v属性,但是func对象并没有v属性,所以最后会打印出undefined。在这个例子中,虽然obj对象中包含v属性,但是由于属性不可以继承,所以obj的func属性对象中的方法不可以使用obj中的属性v。这一点各位读者一定要记住,并且不要和prototype的继承以及变量作用域链相混淆。

多知道点

JS中的“公有属性”“私有属性”和“静态属性”

在有些资料中,可能会看到类似“公有属性”“私有属性”以及“静态属性”等名称,其实这些是基于类的语言(例如Java、C++等)中的一些概念,由于JS并不是基于类的而是基于对象的语言,因此JS本身并没有这些概念。所谓的“公有属性”,一般指使用function对象创建出来的object实例对象所拥有的属性,“私有属性”一般指function的内部变量,“静态属性”一般指function对象自己的属性。这跟基于类的语言的公有属性、私有属性的含义并不相同,而且这种叫法很容易让人产生误解,其实这是三种不同用法分别对应的三种不同的子类型。

当然,学习JS的目的是为了使用它来实现用户需要的功能,而不是为了做理论上的研究,因此,如果大家习惯这种叫法也无所谓,关键是要理解清楚其本质的含义而不要混淆。

4.5.5 关联三种子类型

ES中的三种子类型本来是相互独立、各有各的使用环境的,但是,在一些情况下需要操作不属于自己所对应环境的子类型,这时就需要使用一些技巧来实现了。

为了描述方便,本书将function作为对象使用时记作O(Object),作为函数使用时记作F(Function),创建出来的对象实例记作I(Instance),它们所对应的子类型分别记作op(object property)、v(variables)和ip(instance property),它们之间的调用方法如表4-3所示。

表4-3 function对象的用法及子类型

表4-3的纵向表头表示function对象不同的用法,横向表头表示三种子类型,表格的主体表示在function相应用法中调用各种子类型的方法。因为function创建的实例对象在创建之前还不存在,所以function作为方法(F)和作为对象(O)使用时无法调用function创建的实例对象的属性(ip)。调用参数可以在函数中将变量关联到相应属性,调用function作为对象(O)时的属性可以直接使用function对象来调用,我们来看下面的例子。

    function log(msg){
        console.log(msg);
    }
    function Bird(){
        var name = "kitty";
        var type = "pigeon";
        //将局部变量name关联到新创建的对象的getName、setName属性方法
        this.getName = function () {
            return name;
        }
        this.setName = function (n) {
              name = n;
        }
        //将局部变量type关联到Bird对象的getType属性方法
        Bird.getType = function () {
            return type;
        }
        //在业务处理中调用Bird对象的color属性
        log(Bird.color);     //white, F调用op
    }
    Bird.color="white";
    //在创建出的实例对象中调用Bird对象的color属性
    Bird.prototype.getColor = function () {
        return Bird.color;
    }
    var bird = new Bird();
    log(bird.getColor());    //white, I调用op
    log(bird.getName());     //kitty, I调用v
    log(Bird.getType());     //pigeon, O调用v
    bird.setName("petter");  //I调用v
    log(bird.getName());     //petter, I调用v

上述代码中已经添加了详细的注释,我们就不再多加解释了。通过这个例子,你应该清楚三种子类型在不同环境(用法)中交叉调用的方式了。