JavaScript中的对象就是键值对的集合,区别于其他OO语言中对象的概念(类的实例化)。面向对象编程就是将数据和对数据的行为进行封装,封装的结果被称作类。在JavaScript中,我们可以将这些封装在对象中,这里的对象即相当于OO里面的类也相当于OO里面的对象。JavaScript创建对象有两种方式,一种是使用构造函数,一种是使用字面量,现在基本上都推荐使用字面量的方式创建对象。
对象属性
在使用JavaScript的过程中,我们经常做的一件操作就是修改对象里面的键值对,但是我们有时需要创建一个不可变的对象,在别人修改对象时不生效或者出错,防止别人的错误修改。或者我们不希望这个对象能通过for-in方法迭代出来。我们需要能描述的定义对象的这种特性,如能不能被迭代,能不能被修改。数据属性和访问器属性都能描述对象的这种特征。
- 数据属性
数据属性一共有四种可配置项:
- [[Configurable]] 能否通过delete删除,能否修改属性,默认true,如果为false则除了[[Writable]]之外的其他三个属性都不可以在修改。
- [[Enumerable]] 能否通过for-in返回属性,默认true。
- [[Writable]] 能否修改键值对中的值,默认true。
- [[Value]] 键值对中值的默认值,默认为undefined。
var person = {
name : "haha"
};
上面的代码中,name的前三个属性都为true,第四个属性([[Value]])为haha。 下面我们主要介绍[[Configurable]]。在介绍之前,我们先了解一下如何使用Object.defineProperty
修改键值对的默认属性。
var person = {};
Object.defineProperty(person,"name",{
configurable:true,
writable:false,
value:"haha"
});
console.log(person.name); //haha
person.name = "nohaha";
console.log(person.name); //haha, 不可写
如果将configurable设置为false,那么就不能再用Object.defineProperty
方法修改enumerable、value、configurable属性了。至于writable,如果它为true,则可以通过Object.defineProperty
修改为false,但一旦修改为false或本来就是false则不能再调用Object.defineProperty
,不然也会报错,当然你可以让writable从true到true,但没有意义。
var person = {};
Object.defineProperty(person,"name",{
configurable: false,
writable:true,
enumerable:true,
value:"haha"
});
console.log(person.name); //haah
person.name = "nohaha";
console.log(person.name); //nohaha
Object.defineProperty(person,"name",{
enumerable: true
});
Object.defineProperty(person,"name",{
writable: false
});
person.name ="haha";
console.log(person.name); //nohaha
-
访问器属性 访问器属性的和数据属性一样有四个配置项:
-
[[Configurable]]
-
[[Enumerable]]
-
[[Get]]
-
[[Set]]
访问器属性和数据属性在[[Configurable]]和[[Enumerable]]上是相同的,因为没有[[Value]],所以在使用defineProperty时无法初始化值,只有在定义好对象属性后才能赋值。[[Get]]代表着读权限,没有的话意味着不能读取,[[Set]]则意味着写权限。
var book = {
_year : 2016,
edition :1
};
Object.defineProperty(book,"year",{
get: function(){
return this._year;
},
set: function(newValue){
if(newValue>2016){
this._year = newValue;
this.edition += newValue -2016;
}
}
});
book.year = 2020;
console.log(book.edition); //5
上面的是JavaScript高级编程书中的例子,本人暂时没有理解year和_year的区别,算一个变量还是两个,大家如有知道可以告知一下。
上面的方法只能一次定义对象的一个键值对属性,我们可以使用Object.defineProperties
一次定义多个属性。
var book = {};
Object.defineProperties(book,{
_year : {
writable:true,
enumerable:true,
value:2016
},
edition:{
writable:true,
value:1
},
year:{
get:function(){
return this._year;
},
set: function(newValue){
if(newValue>2016){
this._year = newValue;
this.edition += newValue -2016
}
}
}
});
//读取对象属性的特性
var descriptor = Object.getOwnPropertyDescriptor(book,"_year");
console.log(descriptor.value); //2016
console.log(descriptor.configurable); //false
console.log(typeof descriptor.get); //undefined
var descriptor = Object.getOwnPropertyDescriptor(book,"year");
console.log(descriptor.value); //undefined
console.log(typeof descriptor.get); //"function"
创建对象
在对象的使用过程中,另一个更常见的问题是如何创建对象。也许你会很奇怪,创建对象不就是使用Object或者使用字面量创建对象吗?但问题在于这种方式在于创建很多类似对象时会产生大量的重复代码。
1. 工厂模式
function createPerson(name,age) {
var o = new Object();
o.name = name;
o.age = age;
o.sayName = function(){
console.log(this.name);
}
return o;
}
var person1 = createPerson("zhangsan",10);
var person2 = createPerson("lisi",20);
这种方式的问题是返回的是Object类型,丢失了类型的信息。
2. 构造函数
function Person(name,age){
this.name=name;
this.age=age;
this.sayName = function(){
console.log(this.name);
}
}
var person1 = new Person("zhangsan",10);
var person2 = new Person("lisi",20);
console.log(person1 instanceof Person); //true
console.log(person2.constructor == Person); //true
console.log(person1.sayName === person2.sayName); //false
构造函数的问题在于每次创建对象时,Function每次也会实例化一个,它们并不相同,当然我们也可以将函数提取出来,如下:
function Person(name,age){
this.name=name;
this.age=age;
this.sayName = sayName;
}
function sayName(){
console.log(this.name);
}
var person1 = new Person("zhangsan",10);
person1.sayName();
这样的问题在于封装性,不能将相关的内容放在一起,尤其在函数很多的情况下。
3. 原型模式
我们可以将函数设置到对象的原型上面去,这样所有对象的实例都会共享一个函数,但是原型的问题在于如果一个对象修改了原型中的引用类型,如数组,会导致其他对象上的值发生了变化。
function Person(name,age){
Person.prototype.name=name;
Person.prototype.age=age;
Person.prototype.friends=["zhangsan","lisi"];
Person.prototype.sayName = function sayName(){
console.log(this.name);
}
}
var person1 = new Person("zhangsan",10);
var person2 = new Person("lisi",20);
console.log(person1.sayName == person2.sayName) //true
person1.friends.push("wangwu");
console.log(person2.friends); //[ 'zhangsan', 'lisi', 'wangwu' ]
4. 原型模式+构造函数模式
我们将变量放到构造函数中,方法放到原型中较好的解决了上述问题。
function Person(name,age){
this.name=name;
this.age=age;
this.friends=["zhangsan","lisi"];
Person.prototype.sayName = function sayName(){
console.log(this.name);
}
}