一、定义
无序属性的集合。
说白了就是一个容器,可以容纳【基本值、对象或者函数】,这些东西都叫做属性。每个属性都有一个名字,每个名字都映射一个值(可以是基本类型的值,也可以是引用类型的值)。从以上描述上来看很像Java里面的Map,但形似神不似,它有它的特点。
二、对象的属性特点
1、 属性类型
a) 数据属性
数据属性包含一个数据值得位置,在这个位置可以读取和写入值,这类属性有四个描述符描述其特性
① 、Configurable:顾名思义是否可配置(通过delete删除属性,能否该变其为访问器属性),默认true。
② 、Enumerable:表示能否通过for-in循环返回属性,默认true
③ 、Writable:能否修改属性的值,默认true
④ 、Value:包含这个属性的数据值。读取属性的时候,从这个位置读,写入时把新值保存在这个位置。默认undefined。
以上可以理解为数据属性的数据结构,可以通过Object.defineProperty()方法来修改属性默认的特性,具体方法参照API。
b) 访问器属性
访问器属性不包含数据值,他们包含一对儿getter(读)和setter(写)函数(非必须)。特性也有4个,前两个跟数据属性一致,另外两个是
① 、Get:读取属性时调用的函数。默认undefined
② 、Set:写入属性时调用的函数。默认undefined
访问器属性只能通过Object.defineProperty()来定义,它长用于访问一些只能通过访问器属性访问的属性,通常这类属性以下划线’_’开头。
2、 Object.defineProperties()方法可以同时定义多个属性的特性。
3、 Object.getOwnPropertyDescriptor()方法,可以取得给定属性的描述符。
通过以上介绍,实现一个遍历对象属性以及属性特性描述符的功能就没什么问题了。
三、创建对象的方法
1、 简单方法
a) 构造函数定义后再追个追加属性var person = new Object();person.name=’xxx’;
b) 对象字面量定义 var person = {name:’xxxx’;};
优点:直观
缺点:如果我要创建很多相同的对象,会产生大量的重复代码。
2、 工厂模式
为解决1中的缺点产生的一种创建对象的方法
Function createPerson(name){
Var o = new Object();
o.name=name;
return o;
}
Var person1 = createPerson(‘xxxxx’);
Var person2 = createPerson(‘xxxxxxxxxx’);
这种模式解决了重复代码的问题,其实还有问题没有解决掉:实际应用中拿到变量很可能需要判断这个变量是哪一种变量的类型,然后再进行不同的处理逻辑。注意这里:变量的类型不要单纯的理解为ECMASript规定的6几种变量类型,要把它理解的更广义一点,向上面的代码其实就是想创建一种person的数据类型。
3、 构造函数模式
理解函数与构造函数的区别,不要想太多,直接从字面上去理解,构造函数也是一种函数,它本质上也是Object类型。当表达式中用【new 构造函数名(参数)】这种形式来使用这个函数时,他会返回一个新的对象,这个对象中的属性就是函数中定义的各种属性的镜像(除了prototype)。
Function Person(name){
This.name=name;
This.helloWold= function(){console.log(this.name)};
}
Var person1 = new Person(‘xxx’);
Var person2 = new Person(‘111’);
通常构造函数名首字母大写以区别于普通的函数。
Person1、person2分别保存着Person的一个不同的实例,那么问题来了person1与person2是怎么和Person关联的呢?其实person1,person2中有一个constructor属性,是一个引用类型指向Person。
缺点:通过构造函数创建的对象都是不同的对象,这些对象中包含了代码相同函数,也就是说在每一个对象中都定义了逻辑相同的方法。这似乎有点冗余的赶脚。
4、 原型模式
a) 原型对象的特点
每一个函数都有一个prototype(原型)属性,它是一个引用类型,指向一个对象,这个对象的的特点是:包含了一些属性和方法,这些属性和方法属于某个类型的所有并且可以为类型的所有实例共用。是不是很像Java中类的静态变量,静态方法的意思啊?
Function Person(){};
Person.prototype.name=’xxxx’;
Person.prototype.hellworld= function(){console.log(this.name)};
Var p1 = new Person();
Var p2 = new Person();
Alert(p1.name);alert(p2.name);
神奇的发现通过prototype所有Person的实例共享了prototype上的属性。
在默认情况下所有原型对象都会自动获得一个constructor属性,这个属性包含一个执行prototype属性所在函数的指针。当调用构造函数创建一个实例后,该实例内部也会有一个prototype属性,该属性指向构造函数的原型对象。机:Person.prototype==person1.prototype==person2.prototype。
可以通过isPrototypeOf()方法来确定对象之间是否存在这种关系。
ECMAScript5增加了一个新方法:Objec.getPrototypeOf()可以取得一个对象的原型。
HasOwnProperty()方法可以检测一个属性是存在于实例中还是存在于原型中,这个方法只在给定属性存在于实例中时,才会返回true。这个方法与in操作符有区别,in操作符是只要能够通过实例访问到给定的属性时都返回ture,无论属性实在实例中还是在原型中。
For in与Object.keys()都可以取得对象上所有可枚举的属性,前者包含了实例对象+原型对象上的属性,后者仅仅是实例对象属性。
Object.getOwnPropertyNames()方法可以得到对象的所有属性,无论属性是否可枚举。
b) 通过对象字面量设置对象的prototype。
通过对象字面量的方法设置prototype(本质上是重写默认的prototype对象)会切断prototype与对象之间的联系,即:constructor属性不再指向对象了。但是可以通过显示指定的方式建立这种连接(即在对象字面量中定义一个constructor属性指向对象)。
c) 缺点:
原型中所有的属性都是被很多实例共享的,这种共享对于函数比较合适,对于那些只包含基本值得属性也OK,然而对于包含引用类型(比如数组)的属性来说,某一个实例修改了这个原型属性,所有的实例都会受到影响。这恐怕在大多数应用中都是不想看到的,因为,实例一般都是要有属于自己的全部属性。
5、 组合使用构造函数和原型模式
这种模式解决了上面所说的问题,通过构造函数模式定义实例属性,而原型模式用于定义方法和共享属性。这样每个实例都会有自己的一份实例属性的副本,同时又共享着对方法的引用,最大限度地节省了内存。
6、 动态原型模式
这种方式只是写法上与5不同,它将prototype的定义写在了构造函数内部,完成了对prototype的内部封装。
7、 寄生构造函数模式
这其实是一种适配器模式,在一个构造函数内部再次封装了对象。
8、 稳妥构造函数模式
所谓稳妥构造指构造函数内部没有公共的属性,而且其方法也不引用this的对象。与寄生构造模式类型,使用稳妥构造函数模式创建的对象与构造函数之间也没有什么关系,使用instanceof操作符对这种对象也没有什么意义。
四、对象的继承
继承是OO程序设计中一个基本的概念,一般通过接口继承和实现继承,接口继承只继承方法签名,而实现继承则继承实际的方法。
ECMAScript中没有方法签名(没有返回值类型,参数统一用arguments表示),所以只支持实现继承,依靠原型链来实现。
1、 原型链
a) 概念:
构造函数与实例之间的关系是,每个实例都有一个属性prototype指向构造函数的prototype对象。
如果构造函数的prototype对象指向另一个类型的实例对象,此时会发生什么现象?
① 构造函数的prototype对象与构造函数之间切断联系;
② 构造函数的prototype对象包含了一个指向另一个类型的原型对象
通过这种prototype的指向我们可以一直延伸下去,这样就构成了一条原型链条,这就是原型链的概念。
b) 缺点:
① 、在通过原型来实现继承时,原型实际上会变成另一个类型的实例,于是,原先的实例属性也就变成了现在的原型属性了。这样新创建的所有实例可能会共享一些不应该共享的属性。与原型模式创建对象的问题一样。
② 在创建对象的实例时,没有办法再不影响所有对象实例的前提下向父对象的构造函数传递参数
2、 Constructor stealing
为解决上述问题,使用的一种技术手段,即在子对象的构造函数内部调用父对象的构造函数(通过使用apply()和call()方法,函数只不过是在特定环境中执行代码的对象,这个认识很强大)
Function SuperType(){this.colors=[‘aa’,’bb’,’cc’]};
Function SubType(){SuperType.call(this)};
Var instance1 = new SubType();
Instance1.colors.push(‘dd’);
Console.log(instance1.colors);// ‘aa’,’bb’,’cc’,’dd’
Var instance2 = new SubType();
Console.log(instance2.colors);// ‘aa’,’bb’,’cc’
但是如何解决构造函数带来的代码重复的问题?目前父对象原型中定义的方法对子对象是不可见的。
3、 构造函数+原型链模式(combination inheritance)
很自然的就想到通过这种方式来解决问题,通过原型链来实现对原型属性和方法的继承,通过借用构造函数来实现对实例属性的继承。
问题:子对象实例和原型上都存在父对象的实例属性,实例方法。
4、 寄生组合式继承
3中出现问题的原因是执行了两次父对象的构造函数,即:子对象构造函数中调用一次,子对象prototype赋值又一次,两次重复部分就是父对象实例属性与实例方法。
原因清除了,解决起来就不是很难了,在子对象原型赋值的时候赋一个父对象原型的实例就可以了。
Function inheritPrototype(subType, superType){
Var prototype = Object(superType.prototype);//创建父对象原型的实例
Prototype.constructor = subType;//构造器指向子对象
SubType.prototype=prototype;//制定子对象原型指向包装后的父对象原型
}
这段代码是不是很优雅啊~~~
本文属于个人读书总结,转载请注明出处