对象:无需属性的集合,其属性包含基本值、对象或函数。
(一)理解对象的属性类型
- 数据属性
内部特性:是为了实现 JavaScript 引擎用的,不可直接访问。
- [[configurable]]: 表示是否能通过 delete 删除属性从而重新定义属性。默认为 true
- [[enumerable]]: 表示是否能通过 for-in 循环返回属性。默认为 true
- [[writable]]:表示是否修改属性的值。默认为 true
- [[value]]:属性的数据值。默认 undefined
要修改属性默认的特性,博旭使用 object.defineProperty() 方法。
var person = {}
Object.defineProperty(person, 'name', {
configurable: false,
value: ''nico
})
注意:configurable 设置为 false 的操作是不可逆的,也就是说一旦把属性定义为不可配置的,就不能再把它变回可配置的了。
2.访问器属性
访问器属性包含一对 getter 和 setter 函数。
- getter:获取属性值
- setter:设置属性值,这个函数决定如何处理数据
访问器属性有4个特性:
- configurable
- enumerable
- get
- set
访问器属性不能直接定义,必须使用 Object.defineProperty() 来定义。
var book = {
__year:2004,
edition:1
}
Object.defineProperty(book, 'year', {
get(){
return this.__year
},
set(val){
if(val>2004){
this.__year == val
this.edition += val - 2004
}
}
})
tips: __year 前面的下划线是一种常用的记号,表示只能通过对象方法访问的属性。
(二)定义多个属性的方法
很多开发场景中会用到为对象定义多个属性,因此 ECMAscript5 又定义了一个 Object.definedProperties() 方法。
var book = {}
book.defineProperties(book, {
__year: {
value: 2004
},
edition: {
value: 1
},
year: {
get(){},
set(){}
}
})
(三)读取属性的特性
Object.getOwnPropertyDescriptor() 获取给定属性的描述符。
- 参数:属性所在的对象和要读取其描述符的属性名称
- 返回值:返回值是一个对象,如果
是访问器属性,这个对象的属性有 configurable、 enumerable、 get 和 set;如果是数据属性,这
个对象的属性有 configurable、 enumerable、 writable 和 value。
(四)创建对象的方式
虽然 Object 构造函数或对象字面量都可以用来创建单个对象,但是使用通过一个接口创建很多对象,会产生大量的重复代码,因此我们诞生了工厂模式。
1.工厂模式
这是一种广为人知的设计模式,抽象了创建具体对象的过程。即用函数来封装以特定接口创建对象的细节:
function createPerson(name, age, job){
var o = new Object();
o.name = name;
o.age = age;
o.job = job;
o.sayName = function(){
alert(this.name);
};
return o;
}
var person1 = createPerson("Nicholas", 29, "Software Engineer");
var person2 = createPerson("Greg", 27, "Doctor");
工厂模式虽然解决了创建多个相似对象的问题,但却没有解决对象识别的问题。
随着 JavaScript 的发展,又一个新模式出现了。
2.构造函数模式
通过了解 object 和 Array 这样的原生构造函数,我们知道在运行时会自动出现在执行环境中。因此我们可以创建自定义的构造函数,自定义对象类型的属性和方法:
function Person(name, age, job){
this.name = name
this.age = age
this.job = job
this.sayName = function(){
alert(this.name)
}
var person1 = new Person('nico', 29, 'ss')
var person2 = new Person('greg', 27, 'Doctor')
alert(person1.constructor == Person); //true
alert(person2.constructor == Person); //true
}
这个例子就是用 Person() 函数取代了 createPerson() 函数。要创建 Person 的新势力,必须使用 new 操作符。使用了 new 操作符会经历 4 个步骤:
(1)创建一个新对象
(2)this 指向这个新对象
(3)为这个新对象添加属性
(4)返回新对象
tips:构造函数的命名一般使用 首字母大写,主要是为了区别于 其他函数。
使用构造函数需要注意的地方:
1.构造函数与普通函数唯一的区别:调用方式不同
任何函数只要通过 new 操作符来调用,都可以作为构造函数
2.构造函数的问题
每个方法都要在每个实例上重新创建一遍
3.原型模式
我们创建的每一个函数都会有一个 prototype 属性,这是一个指针,指向一个对象,即原型对象,而这个对象就是包含实例共享的属性和方法。
默认情况下,所有原型对象都会自动获得一个 constructor(构造函数)属性,指向构造函数,通过构造函数我们可以继续为原型对象添加其他属性和方法。
Person 构造函数、 Person 的原型属性以及 Person 现有的两个实例之间的关系。
在此, Person.prototype 指向了原型对象,而 Person.prototype.constructor 又指回了 Person。
原型对象中除了包含 constructor 属性之外,还包括后来添加的其他属性。 Person 的每个实例——
person1 和 person2 都包含一个内部属性,该属性仅仅指向了 Person.prototype;换句话说,它们
与构造函数没有直接的关系。此外,要格外注意的是,虽然这两个实例都不包含属性和方法,但我们却可以调用 person1.sayName()。这是通过查找对象属性的过程来实现的。
hasOwnProperty() 可以检测一个属性是存在于实例中,还是存在于原型中,当只有该属性存在于对象实例时,才会返回 true。
4 组合使用构造函数和原型模式
这种方式的优点是实现每个实例都有属于自己的一份实例属性副本,同时又共享方法的引用,最大限度的节省内存。
function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
this.friends = ["Shelby", "Court"];
}
Person.prototype = {
constructor : Person,
sayName : function(){
alert(this.name);
}
}
var person1 = new Person("Nicholas", 29, "Software Engineer");
var person2 = new Person("Greg", 27, "Doctor");
person1.friends.push("Van");
alert(person1.friends); //"Shelby,Count,Van"
alert(person2.friends); //"Shelby,Count"
alert(person1.friends === person2.friends); //false
alert(person1.sayName === person2.sayName); //true
这种模式也是目前使用最广泛、认同度最高的一种。