前言
回顾多年前第一次接触js,其实是有点反感的。相比于C#、java等强类型语言,js语法无法在IDE中通过键入“.”的方式,获取对象的智能提示,对于一个初学者这是一个非常不好的体验。即使多年以后,IDE有了不小的进步,但也只能将js内置对象的方法和属性以智能提示的方式体现,而自己定义的方法、属性,或者引入的第三方js对象的方法、属性依然是无法被IDE感知。
究其原因,无非就是因为js的对象没有class这一概念,或者说没有“元数据”。js只知道它是一个对象,但是无法知道这个对象的“本质”。
为什么js没有像java那样设计?具体原因不得而知,估计还是与js的历史背景有关,早期js只是被设计为浏览器脚本语言,因此设计者并没有引入类的概念,因为这会使语言本身过于复杂。另外,没有类,并不意味着无法实现面向对象编程,相反js通过更轻巧(或者说是“化繁为简”)的设计,达到了面向对象编程语言的效果——封装、继承、多态 。
创建对象
class作用是定义对象的属性和方法,java中必须先有类,才能声明一个该类的对象。而js显然觉得这是个累赘,如果我想创建一个猫,直接这样就可以了:
var cat = {
name:"tom",
color:"gray"
}
//或者,先创建创建空对象,再设置属性
var cat = {};
cat.name = "tom";
cat.color = "gray";
//也可以这样写
cat["name"] = "tom";
cat["color"] = "gray";
很简单,也很自由——在创建对象的同时,把需要的属性直接声明并赋值。
如果我们想要为cat对象添加一个方法,可以
var cat = {
name:"tom",
color:"gray",
say:function(){
alert("i'm "+this.name+",miao~");
}
}
//或者
cat.say = function(){
alert("i'm "+this.name+",miao~");
}
cat.say();//调用方法
如果我们再创建一只猫可以
var cat2 = {
name:"kitty",
color:"white"
}
如果我们要为cat2创建一个say方法呢?好了,我发现你一定也知道问题所在了——难道我们要对每个cat对象都要定义say方法?
我们当然不希望这样,开发过程痛苦是一方面,运行时不同cat的say方法本质上还是不同的函数对象,这会造成内存浪费,影响性能。
js是如何解决这个问题的呢?
原型
js中所说的原型是一个名为prototype的属性,从作用上来说 ,原型就好比class对象,我们可以将“猫”的公共属性和方法定义在原型对象上,这样就可以达到封装的效果了。
但是呢,拥有这个属性的对象是function,所以上文中如果想通过cat.prototype只能获得一个undefined,因为cat是一个对象,而不是方法。
如何创造出这个方法?
//定义cat构造方法,方法体里有用到this,即调用方法的对象
//通常构造器对通过new 关键字来调用,这意味着js创建了一个新的对象,这个对象就是执行这个方法的this,返回值也是这个this
function Cat(name,color){
this.name=name;
this.color=color;
}
//Cat就是创建猫对象的方法,我们可以获取其原型对象,并为原型对象添加一个say方法
Cat.prototype.say=function(){
alert("i'm "+this.name+",miao~");
}
//创建一个cat
var cat = new Cat("tom","gray");
//调用say方法
cat.say();
上述代码的本质其实很接近java了,这样编写,所有通过 new Cat()创建的对象,就都有say方法了。
我们可以换种方式认为——js通过构造器的原型对象,为一类对象定义通用的属性和方法,这不就是class的作用么?
常见的属性、操作符和方法
- constructor属性
对于上文的cat对象,我们可以通过cat.constructor获取其构造函数,也就是Cat对象,注意Cat是一个方法;
cat.constructor == Cat;//true
- instanceof运算符
用于判断cat对象是否由Cat方法创建,等价于:a对象是否是A类的实例;
cat instanceof Cat;//true
- proptotype.isPrototypeOf(obj)方法
proptotype对象都有方法isPrototypeOf(obj),用于判断obj是不是proptotype的实例,作用和instanceof类似;
Cat.prototype.isPrototypeOf(cat);//true
- obj.hasOwnProperty(propName)方法
每个对象都有hasOwnProperty(propName),用于判断某个属性,是由本对象在声明时自己定义的,还是由prototype定义的,true表示前者,false表示后者,propName是个字符串,表示属性名称。
通常返回true的话,我们成为这个属性为“本地属性”,反之成为“原型属性”。
cat.hasOwnProperty("name");//true
cat.hasOwnProperty("say");//false,方法say也是属性,这里不要写成“say()”
- in运算符
in运算符有两个作用,一个是判断某个对象是否包含某个属性,不管是本地还是原型属性;另一个更常见,可以用来遍历一个对象的所有属性
"name" in cat;//true,name 是 cat的属性
//遍历出 name、color、say三个属性
for(var propName in cat){
alert(propName);//propName 是字符串
alert(cat[propName]);//通过对象[属性名]的方式,访问属性的值
}
好了,先消化一下本片文章的内容,想想——如果我想为“Cat”这个class定义一个getType()方法,返回的就是原型对象,该怎么实现呢?