这几天又好好读了JavaScript高级程序设计。其中在理解对象和创建对象这块有了很多新的见解和认知
JavaScript的对象
由于我最开始学习的是Java,所以很容易就会把JavaScript和Java联系到一起。不同于Java中,ES5还是没有类这个概念的。没办法通过类来实例化对象,更别说什么构造函数继承这些了。
ECMA-262把对象定义:无序属性的集合,其属性可以包含基本值,对象或者函数。
简单地创建对象
JavaScript创建对象很简单,最基本的创建方法:
var obj = new Object(); // 通过Object构造函数来创建对象
// 或者
var obj2 = {//....balabala} // 通过对象字面量来创建对象
创建一个对象后,我们来了解一下对象的属性类型:
对象的属性类型
数据属性:
数据属性说的简单点就是对象中具有具体数据值的属性。如下中,name,age均为person的数据属性。
var person = {
name: 'www',
age: 18
}
每一个数据属性呢,又包含四个属性值。显示在对象中的是其value属性。可以由Object.defineProperty来定义它的这四个属性。
四个属性:
1、configurable : 表示这个属性是否是可配置的。默认为true,如果修改为false,那么不可再修改这个属性的configurable属性和enumerable属性,也不可用delete来删除这个属性。
2、enumerabel : 表示这个属性是否是可枚举的。默认为true。在比如Array这种引用类型中,它的那些例如sort(),push()等等的方法的这个属性均为false,这样我们就没办法用for in 来枚举处其属性。
3、writable: 表示是否可修改。默认为true。如果改为false,那么就没办法再修改这个属性值。
4、value : 表示这个属性的属性值。上面的name的value属性就死 'www'。
Object.defineProperty方法的使用:
Object.defineProperty(person,name,{
// 这里我就取默认情况
configurable: true,
enumerable: true,
writable: true,
value: 'www'
})
访问器属性
访问器属性没有具体的数据值,它提供一个getter和setter方法。
这个就有点绕了。因为我们在定义对象的时候,像上面person这种,它的name属性我们是可以看得到的,很明显就容易理解数据属性。那么具体什么叫做访问器属性呢?
这里通过一个例子来说明一下。首先我们定义一个girl对象。女孩子的年龄都是保密的,所以我们给它设置一个_age属性。那我们可以通过一个ask访问器属性来获取到这个女孩的年龄。
var girl = {
_age:18, // 下划线_开头的属性是一种约定俗成,一般不作为公开属性, 只能通过对象方法访问。
mood: "happy"
}
// 定义一个ask访问器属性。
Object.defineProperty(girl,"ask",{
// 我们可以通过girl.ask 来获取到这个女孩18岁了。
get: function () {
return this._age;
}
// 要是我们给这个女孩设置一个 girl.ask = 20; 就像询问这个女孩,你是不是20了?女孩就会很生气。
她的mood属性就会变成 angry。
set: function(val) {
if(val > 18) {
this.mood = "angry";
}
}
})
这就是访问器属性。可以通过这个属性获取到其他属性,也可以在设置这个属性的时候,导致别的属性发生变化。同时,这个属性除了get和set两个属性,还有configurable和enumerable两个属性,类似于数据属性。
关于读取属性和设置多个属性等,这些都是基本知识的介绍,可以阅读《JavaScript高级程序设计》
创建对象
上面说了很简单通过Object构造函数和对象字面量来创建对象。接下来说几个逼格稍微高一点的。
工厂模式
JavaScript工厂模式创建对象就像是无限调用一个方法来创建Object对象。这个方法就像是一个工厂一样,如下代码所示:
function factory(name,age,sex) {
var o = new Object();
o.name = name;
o.age = age;
o.sex = sex;
o.say = function () {
console.log(o.name+'--'+o.age+'--'+o.sex);
}
return o;
}
// 创建实例对象
var p1 = factory("wyh",18,"man");
p1.say(); // wyh--18--man
缺点:缺点太明显了,我创造的对象全是Object的,没有标识度。实例化的p1,p2,p3也没有任何的联系。
所以,让我们来看构造函数模式。
构造函数模式
还是没办法摒弃Java的学习思想。所以这里我很容易就联想到Java的构造函数。JavaScript的构造函数模式创建对象还真的很像Java的构造函数。如下代码:
// 与Java的构造函数何其相似。函数内部没有显示的创建对象的动作。
function Animal(name,leg) {
this.name = name;
this.leg = leg;
this.sayName = function () {.....};
}
// 实例化两个对象:通过new 来创建。
var tiger = new Animal("tiger",4);
var lion = new Animal("lion",4);
console.log(tiger.constructor); // Function: Animal。
好像很不错。可是有严重的问题所在。每当实例化一个Animal对象,就会给sayName开辟一个内存空间,如果这个对象有很多函数属性,如果要实例化很多的Animal对象,就会很占用内存空间了。当然,我们可以把这个对象的函数属性放在全局变量中,但是同时也会污染了全局环境。
所以,来看原型模式:
原型模式
由于我们创建的每个函数都是有一个叫做prototype(原型)的属性,该属性作为一个指针指向一个对象。
所以我们可以利用这个prototype来进行创建对象。这里仅仅介绍简单的原型模式创建,更具体的可以阅读《JavaScript高级程序设计》。
代码:
function Person() {
}
Person.prototype.name = "wyh";
Person.prototype.age = 18;
Person.prototype.list = [],
Person.prototype.sayName = function () {
console.log(this.name);
}
var p1 = new Person();
var p2 = new Person();
p1.list.push("a");
console.log(p2.list) // ['a']
console.log(p1.constructor) // Person
console.log(p1.sayName == p2.sayName) // true;
解决了上面构造函数模式中的函数占用内存的问题了。因为所有实例化的对象的函数指向的地址是一样的。
缺点:没有办法自定义初值,不具变通能力。
如上面所示,我在p1中操作了数组list,p2的也发生改变,这是不想看到的。原型模式一个实例化对象更改引用类型,所有的都会改变。
组合使用构造函数模式和原型模式
既然构造函数和原型模式都各自有问题,而且还可以互补。那么我们就把这两种模式结合到一起使用。
代码如下所示:
function Person(name,age,sex) {
this.name = name;
this.age = age;
this.sex = sex;
this.shoplist = [];
}
Person.prototype = {
sayName: function () {
console.log(this.name);
},
constructor: Person
}
var p1 = new Person("wyh",18,"man");
var p2 = new Person("MJ",16,"wm");
p1.shoplist.push("apple");
p2.shoplist.push("shoes");
console.log(p1.sayName == p2.sayName); // true
console.log(p1.constructor) // Person
console.log(p2.shoplist) // ['shoes']
我们把公用的方法用原型模式构造,省去内存消耗。把不公用的引用类型用构造函数模式构造,避免混淆。
同时,也可以通过传入初始化数据来自定义我们需要的对象的样子。
这种方法(模式)是使用度最普遍的一种方法。
动态原型模式
把上面那种模式中,所有的信息都封装在构造函数中。但是又为了防止重复的初始化原型,于是就有了动态原型模式。话不多说,请看代码:
function Person(name,age) {
// 属性
this.name = name;
this.age = age;
// 动态地进行原型的构造方法
if(typeof this.sayName != "function") {
console.log('----1------');
Person.prototype.sayName = function () {
console.log(this.name);
}
// 其他的需要原型处理的属性
}
}
var p1 = new Person("wyh",18); // 1
p1.sayName(); // wyh
var p2 = new Person("mj",16); // 什么都没有
p2.sayName(); // mj
当我们第一次实例化Person的时候,会动态地创建原型属性,并且只创建这一次。
寄生构造函数模式
这个。。。我看不太出来和工厂模式的区别。要说实例化的时候用了new操作符的话,那我还是不明白这个模式的作用所在。
稳妥构造函数模式
同上面这个寄生构造函数模式,我暂时看不出来它的用处所在。