new到底在做什么
面向对象
面向对象的思想存在于大多数编程语言中,比较典型的是Java。与之相对应的是面向过程,比如站在程序猿鄙视链上端的C语言。在Java等面向对象的编程语言风靡的现在,“面向对象”似乎已经是一个程序员必备的知识点了。而鉴于“对象”这个名词的多义性,又进一步提高了这个词的出现频率。但是,真的有人能说的清楚什么是面向对象吗?
有人说,万事万物皆对象。
有人说,面向对象就是“你办事,我放心”。
还有人说,所谓对象,就是抽象化的数据本身。
那你可能要问我了,说这么多,你能说清楚什么是面向对象吗?
对不起,我也不能。不过我会试图让你明白什么是面向对象。
面向对象,英文是Object Oriented,简称OO,一般加个中划线。面向对象程序设计,Object-oriented Programing,简称OOP。
面向对象的三个基本特征——封装、继承、多态。
封装,拿汽车做比方,就是把一堆零件用汽车的壳子包起来,用户——也就是你,只能看见方向盘、油门刹车等。这样做的好处是什么?使用这辆汽车的人完全可以不用去关心发动机是怎样带动轮胎转动的,不用去关心方向盘是怎么控制转向的,更不用知道化学能是怎么转化成机械能的。他只需要拧一拧钥匙,踩离合,挂挡,就可以使用这辆汽车了。这,就是封装。封装会把你不需要知道的东西隐藏起来,让更多的人能够更方便地使用你的东西。
大家都知道jQuery,jQuery就是对原生js的封装。有一些用原生js实现起来很复杂但是又很常用的功能,jQuery帮你实现了,并暴露出一个接口,让你可以很方便地去使用它。这就是封装。再比如,我们知道js数组有很多自带的方法,比如indexOf,filter等等,这些同样是一种封装,内部实现隐藏掉,你只需要知道这些东西有什么用,怎么用就好了。
继承,还是拿汽车做比方。一般说来,汽车有四个轮子一个壳还有个备胎。但是同样是汽车,价格却相差甚远。凯迪拉克是汽车,大众是汽车,玛莎拉蒂是汽车,宝马也是汽车,换句话说,这些都是继承自汽车,但是它们各自除了汽车的一般功能外,还有各自特殊的地方,比如不同的安全系数等。或者你可以把范围再扩大一下——车。卡车是车,货车是车,汽车是车,拖拉机是车,挖土机也是车。这些都是继承自车这个大类。有着车子的功能,可以作为代步工具。但与此同时,它们还有着自己各自的功能,比如运货,载人,挖土等等。这就是继承。继承让你拥有一些公共的基础属性,你自己要是想要实现一些特殊功能的话那就自己再给自己加一点东西。
多态,一种事物的多种形态?
多态分为两种,第一种我们拿汽车举例。一辆汽车,有个外壳,你拿个石头砸过去,外壳会凹陷进去一块。这是汽车对拿石头砸这件事情做出的反应。同样的一辆汽车,你加固了外壳,当然,它本质上还是一辆汽车,不过是一辆新型的汽车,我们姑且称它为汽车的儿子。同样有外壳,你拿个石头砸过去,外壳不凹陷了。这是新一代汽车对拿石头砸这件事情做出的反应。用上继承的概念,新型汽车继承自汽车,但是新型汽车面对别人拿石头砸的时候不会凹陷,汽车会。对同一件事情做出不同的反应,这是一种多态。
第二种,嗯,第二种放过汽车吧!换一个东西讲,水。我们都喝过水吧!水里面可以加入很多东西,你加冰糖,就是甜甜的糖水,加入柠檬,就是酸酸的柠檬水。或者冰糖柠檬一起加,就成了酸酸甜甜的冰糖柠檬水。加进去的东西不同,就会有不同的表现形式。这就是我要说的第二种多态。用伪代码粗略表示如下:
水 = {
加入(酸的东西){ return 酸酸的水}
加入(甜的东西){ return 甜甜的水}
加入(甜的东西, 酸的东西){ return 酸酸甜甜的水}
}
我们可以看见,同样是“加入”这个方法,你传入的东西类型、个数不同,那么得到的结果也会不同,这是多态的第二种形式。
拿C++来说,子类继承自父类,如果子类重写了父类的某个同名方法,那么子类实例化出来的对象与父类实例化出来的对象在调用这个方法并且传入相同的参数的时候,得到的结果也不一定相同。这是我们说的第一种多态。第二种多态的实现方法是方法的重载,封装好了的类会暴露出一些方法,但是事实上,你看见的一个方法,可能并不是同一个方法。我知道这个说法可能有点绕。或者用java的方法签名来讲会好理解一些。方法签名由方法名和形参列表构成,比如一个walk(int a, int b)和walk(int a, string b)是两个不同的方法。在封装好了的类的内部,实际上写了两个函数,一个是walk(int a, int b){},另一个是walk(int a, string b){},只是外部调用的时候同样是使用walk来调用而已。所以第二种多态的话,看上去调用的是同一个方法,实际却不是。
讲了这么些,什么是对象呢?
让我们回到汽车,汽车就是一个对象。汽车有大小颜色等属性,提供了踩油门、踩刹车、转方向盘等方法,我们可以使用它,并且不需要知道它的工作原理。电脑也是一个对象,有很多的属性,同样的被封装起来,并且对外提供了一些方法,我们可以使用它并且不需要知道它的工作原理。所以为什么说万事万物皆对象?因为万事万物都可以被看做是对象。为什么说你办事我放心?因为不放心你也没辙。为什么说对象就是抽象化的数据本身?因为对象是这些东西按照一定的逻辑组合而成的。
放在编程语言中,对象就是属性与函数封装而成的一个东西,为了达到某个目的而存在。
那么,什么是面向对象编程呢?
你要造一辆汽车,你会去买轮胎,买螺丝钉,买发动机,你不知道轮胎是怎么造出来的,不知道螺丝钉是怎么造出来的,不知道发动机是什么造出来的,但是你可以使用它们造汽车。这就是面向对象编程。轮胎、发动机甚至螺丝钉都是封装好了的对象,你使用它们去完成你的工作——造汽车。把一个个小小的功能模块拆分成一个个的对象,调用这些对象来完成其它的功能,谓之面向对象编程也。
js的面向对象
js是一门基于原型的语言,我们说过,js之父Brendan Eich擅长的是Scheme语言,主要方向是函数式编程。由此js的类依然是一个函数,而不是像Java或者C++那样指定关键字class。
function Person(){} //创建了一个Person类
var person1 = new Person() //实例化Person类的对象
让我们给Person加一些属性,和方法。
//代码1
function Person(name){
this.name = name
}
Person.prototype.sayHello = function(){
alert('Hello,'+this.name)
}
var person1 = new Person('wcy')
var person2 = new Person('whw')
这样子的话你可以使用person1或者是person2来调用sayHello这个方法。
如果没有new呢?先创建一个Person函数,函数返回一个对象,这样调用这个函数的时候就可以得到一个对象了。公共的函数呢?放到另一个对象publicFn里边。使用js自带的__proto__
属性来建立公共函数和Person之间的关系。代码如下:
//代码2
function Person(name){
return {
name: name,
__proto__: publicFn
}
}
var publicFn = {
sayHello: function(){
alert('Hello,'+this.name)
}
}
var person1 = Person('wcy')
var person2 = Person('whw')
让我们把代码稍加修饰一下,让它看起来和new更像。其中公共属性publicFn使用了固定的名字prototype,并且为了避免与其它的公共函数重复将它挂载到了Person下。
//代码3
function Person(name){
var temp = {}
temp.name = name
temp.__proto__ = Person.prototype
return temp
}
Person.prototype = {
sayHello: function(){
alert('Hello,'+this.name)
}
}
var person1 = Person('wcy')
var person2 = Person('whw')
所以new做了什么呢?
从代码3到代码1简化的部分,都是它做的了。
new帮我们创建了一个临时对象并返回它,帮我们固定了person1.__proto__ = Person.prototype
。这是我们可见的。事实上,除此之外,new还固定给prototype赋了一个属性constructor,用来记录它代表的是哪个对象的共有属性。因此,对prototype的赋值最好是使用下边的这种代码,这样不会覆盖掉原有的属性。或者在你知道它原有属性是什么的情况下对原有属性也进行再一次的重新赋值。(ps: 不过我试了试,没试出来这个属性有什么实质性的影响。不过最好还是遵从规则办事,这样能少些麻烦。)
Person.prototype.sayHello = function(){
alert('Hello,'+this.name)
}
此时你所看见的person1,person2都是封装好了的Person类实例化出来的对象。