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