JavaScript即学即用教程[8]-原型及面向对象
类以及实例
在js中,一个函数在通过new来调用时,该函数就相当于其他语言中的类型了。new调用后,函数会自动创建一个新实例,自动return出来。
我们在函数体中,可以对this进行操作,相当于初始化新创建的实例。 return其他东西的话,会覆盖掉默认的return this, 所以如果要创建类型实例,尽量不要自己return东西。
JavsScript与其他语言比较不同的地方在于它是有点函数式的,所以并没有那么的 面向对象
, 另外他的面向对象是通过原型实现的。这就导致他实现 面向对象中的继承就有很多种方式。
原型继承
1 | var Person = function () { |
当p被new出来的时候,p对象的内部原型指针已经指向了Person.prototype, 因此p继承了Person.prototype对象上的所有内容。
所谓的继承,指的是当通过p访问某个属性或方法时,如果p自身没有,则会去寻找p原型链上的Person.prototype对象上有没有。
从表面上看,我们可以说p继承自Person。
当我们给p对象添加一个自有属性时,p对象自身拥有该属性,则访问时就不会去寻找原型链上的该属性,遵循了就近原则。
Note: 实例化对象之后,不要轻易重写类的原型对象,因为重写后,实例依然指向最初的那个原型对象。
1 | var p = new Person() |
上面这个例子,实例化p之后,又重写了Person.protypte, 其实p还在指向最初的Person.prototype, 所以重写变得没有意义。
父类子类的实例属性继承
在其他语言中,子类的构造函数构造时,一般要去调用父类的构造函数完成基类要求的相关属性的构造。在JavaScript进行类型继承时,也应该做好这样的操作,方能把父类的实例属性继承下来,从而让创建出来的实例既完成了父类的初始化任务,又完成了子类的初始化任务。
其最佳实践便是,在构造函数中调用父类构造函数。至于原型继承的东西,就通过原型链的继承来完成即可。
举例: Dog继承自Animal,Animal有年龄、颜色,狗有名字和住址,这些都是实例属性。 而动物会吃饭,狗会叫,这是他们分别具有的公共方法。 OK,实现吧:
1 | function Animal (age, color) { |
为什么要用nodejs内置的的inherit来让子类继承父类,而不是直接 Dog.prototype = new Animal() 呢。
这里涉及到一个小问题,那就是如果我们这样简单的实现Dog继承Animal,会导致Dog.prototype上面拥有了Animal的实例属性,其实这不符合所谓继承的目标。
我们仅仅希望Dog.prototype继承Animal原型上的东西,而Animal构造函数里的东西,我们希望通过Dog构造函数内部调用的方式来创建为Dog的实例的自有属性。 所以inherit这个事情还是个小学问,那么问题来了:
utils/inherit是怎么实现的呢?
1 | exports.inherits = function(ctor, superCtor) { |
原来他利用了Object.create,来避免了执行Animal父类的构造函数,却达到了继承Animal.prototype的目的。而创建出来的Dog.prototype对象,虽然原型链指向了Animal.prototype, 但是缺少一个constructor属性,只有配置好这个属性,才能让所有的Dog实例知道自己是被Dog构建new出来的,所以inherit函数在Object.create的过程中,顺便设置了Dog.prototype的constructor属性。
同时,inherit函数还给子类类型添加了一个静态属性super_,来标示他的父类是谁。
这样,一个完整的继承函数就实现了。
原型判断
判断一个原型是不是某个对象的: Person.prototype.isPrototypeOf(xx)
获取某个对象的原型: Object.getPrototypeOf(xx)
相关方法
判断属性类型
obj.hasOwnProperty(‘att’)
可以判断一个对象是否拥有每个自有属性。换句话说,是判断att是不是对象obj的实例属性。
判断属性有无
in操作符,可以判断一个对象在整个原型链上是否拥有某个属性att, 如 att in obj, 如果obj对象上有att,或者obj对象的原型链上有att,则返回true,否则返回false。
遍历对象
For in 可以遍历一个对象自身及其原型链上的所有可枚举属性。 其遍历的目标是对象里所有 可枚举
的属性。 是否可枚举是属性的一种基本特征,一般情况下,开发人员通过简单方式定义的所有属性都是可枚举的。 js中固有的那些toString,toLocaleString, ValueOf, hasOwnProperty,都是不可枚举的。
1 | for (var k in obj) { |
如果希望只遍历对象自身属性,则需要遍历时通过obj.hasOwnProperty(k)来判断。 另外,ES5提供了Object.keys(obj)来获得对象上所有自有可枚举属性,可以满足这个需求。
甚至,我们还可以玩的更high,去获取一个对象上所有的自有属性(包括不可枚举),使用: Object.getOwnPropertyNames(),
例如,获取Object.prototype上的所有属性:1
2
3
4
5
6
7
8
9
10
11
12
13[ '__defineGetter__',
'__defineSetter__',
'hasOwnProperty',
'__lookupGetter__',
'__lookupSetter__',
'propertyIsEnumerable',
'__proto__',
'constructor',
'toString',
'toLocaleString',
'valueOf',
'isPrototypeOf',
]
应用
判断一个属性是不是一个对象的原型属性
1 | function hasPrototypeProperty (obj, name) { |
自己实现一个Object.create函数,在不调用父类构造函数的情况下来继承父类
1 | if (!Object.create) { |