理解 JS 的对象
在 JS 中,对象被定义为——“无序属性的集合,其属性可以包含基本值、对象或函数。“ 我们可以将其想象成为一个散列表,无非就是一组名值对,其中可以是数据或函数。所以,可以采用如下字面量来创建对象:
var person = {
name: "xiaobai",
age: 22,
sayName: function(){
alert(this.name);
}
}
属性类型
在 JS 中有两种属性:数据属性和访问器属性。JS在定义描述属性各种特征的特性,这些特性是为了是想 JS 引擎用的,因此在 JS 中不能直接访问它们。表示时,一般将其放在两对方括号中,例如 [[Enumerable]];
1.数据属性
数据属性包含一个数据值的位置。在这个位置能读取和写入值。有如下4个特性。
- [[Configurable]]: 表示表示能否通过 delete 删除属性,从而定义新的属性,能否修改属性的特性,或者能否吧属性修改为访问器属性。默认值为 true。
- [[Enumberable]]: 表示能够通过 for-in 循环访问属性。默认值为 true。
- [[Writable]]: 表示能否修改属性的值。默认值为 true。
- [[Value]]: 包含这个属性的数据值。默认值为 undefined。
有修改属性默认的特性,必须使用 Object.defineProperty() 方法。其接受三个参数,属性所在的对象、属性的名字和一个描述符对象。其中描述符对象的属性必须是: configurable、enumerable、writable 和 value。设置其中一个或多个值,可以修改其相应的特性。如下:
var person = {};
Object.defineProperty(person, "name" {
writeable: false,
value: "Xiaobai"
});
alert(person.name); // "Xiaobai"
person.name = "xiaohei";
alert(person.name); // "Xiaobai"
如果以上代码在非严格模式下会被忽略,严格模式下则会抛出错误。值得一提的是,如果一旦将 configurable 设置为 false 后,就不能再次改变其特性,再次调用 Object.defineProperty(), 则会跑出错误。另外,在调用该方法时,如果不指定,configurable、enumerable和writable 特性的默认值都是 false。
2. 访问器属性
访问器属性不包含数值,但是包含一对儿 getter 和 setter 函数(都不是必须的)。在读取访问器属性时,会调用 getter 函数。在写入访问器属性时,则会调用 setter 函数。其有如下4个特性。
- [[Configurable]]: 表示表示能否通过 delete 删除属性,从而定义新的属性,能否修改属性的特性,或者能否吧属性修改为数据属性。默认值为 true。
- [[Enumberable]]: 表示能够通过 for-in 循环访问属性。默认值为 true。
访问器属性不能直接定义。必须使用 Object.defineProperty() 来定义。如下:
var book = {
_year: 2004,
edition: 1
};
Object.defineProperty(book, "year", {
get: function(){
return this._year;
},
set: function(newValue){
if(newValue > 2004){
this._year = newValue;
this.edition += newValue - 2004;
}
}
});
book.year = 2005;
alert(book.edition); // 2
定义多个属性
Object.defineProperties() 的方法可以通过描述符一下添加多个属性。例如:
var book = {};
Object.defineProperties(book, {
_year: {
value: 2004
},
edition: {
value: 1
},
year: {
get: function(){
return this._year;
},
set: function(newValue){
if(newValue > 2004){
this._year = newValue;
this.edition += newValue - 2004;
}
}
}
});
以上代码与上一节创建的对象相同。唯一区别是,它们是在同一时间创建的。
读取属性的特性
Object.getOwnPropertyDescriptor() 方法,客气鱼的给属性的描述符。接受两个参数: 属性所在对象和要读取器描述符的属性名称。返回是一个对象,如果是属性访问器,则对象属性包含 configurable、enumerable、get 和 set。如果是数据属性,则这个对象属性包含 configurable、enumerable、writable 和 value。例如:
var book = {};
Object.defineProperties(book, {
_year: {
value: 2004
},
edition: {
value: 1
},
year: {
get: function(){
return this._year;
},
set: function(newValue){
if(newValue > 2004){
this._year = newValue;
this.edition += newValue - 2004;
}
}
}
});
var descriptor = Object.getOwnPropertyDescriptor(book, "_year");
alert(descriptor.value); //2004
alert(descriptor.configurable); // false
创建对象
工厂模式
这是一种广为人知的模式,如下所示:
function createPerson(name, age){
var o = new Object();
o.name = name;
o.age = age;
o.sayname = function(){
alert(this.name);
};
return o;
}
var person = createPerson("xiaobai", 23);
工厂模式最大的问题在于没有解决对象的识别问题,即知道一个对象的类型。
构造函数
我们也可以自定义构造函数,如下所示:
function Person(name, age){
this.name = name;
this.age = age;
this.sayName = function(){
alert(this.name);
};
}
var person = new Person("xiaobai", 23);
在这里我们使用了 new 操作符,以这种方式调用构造函数会经历以下4个步骤:
- 创建一个新对象;
- 将构造函数的作用域赋值给这个对象(因此 this 就指向了这个新对象);
- 执行构造函数的代码(为这个对象添加新的属性);
- 返回新的对象。
其中,每一个对象有一个 constructor 属性,指向其构造函数。
alert(person.constructor == Person); // true
我们还可以通过 instanceof 操作符检测对象是不是某个类型的实例。
alert(person instanceof Object); //true
alert(person instanceof Person); //true
构造函数的缺点在于
hasOwnProperty() 方法可以检测一个属性是否存在于实例中还是在原型中。而 in 操作符在对象能够访问属性时返回 true;
function Person(){
}
Person.prototype.name = "xiaobao";
Person.prototype.age = 22;
person = new Person();
alert(person.hasOwnProperty("name")); //false 来自原型
alert("name" in person); //true
person.name = "xiaohei";
alert(person.hasOwnProperty("name")); //true 来自实例
alert("name" in person);
在使用 for-in 循环时能够返回该对象能够访问的所有的属性,包括不可枚举属性(即将[[Enumerable]]标记的属性)。
Object.keys() 方法接受一个对象作为参数,返回该对象上一个包含所有可枚举属性的字符串数组。
Person.prototype.name = "xiaobao";
Person.prototype.age = 22;
person = new Person();
var keys = Object.keys(Person.prototype);
alert(keys); //"name, age"
keys = Object.keys(person);
alert(keys); //""
要想去的某个实例的所有属性,无论是否可枚举,都可以使用 Object.getOwnPropertyNames();
function Person(){
}
Person.prototype.name = "xiaobao";
Person.prototype.age = 22;
person = new Person();
var keys = Object.getOwnPropertyNames(Person.prototype);
alert(keys); //"constructor,name, age"
keys = Object.getOwnPropertyNames(person);
alert(keys); //""
2. 更简单的原型语法
为了更好的封装原型功能,一个更常见的功能是用一个包含所有对象和方法的字面量来重写整个原型对象。如下所示:
function Person(){
}
Person.prototype = {
name: "xiaobai",
age: 22,
sayName: function(){
alert(this.name);
}
}
该方法将 Person.prototype 设置为一个字面量创建的新的对象。最终的结果就是实例的 constructor 不在指向Person。而当我们创建这个对象时,它也自动获得了一个 constructor 属性。我们重写了默认 Prototype 对象,因此 constructor 也比啊我觉得新对象的 constructor 属性(指向 Object 构造函数)。
var person = new Person();
alert(person instanceof Object); //true
alert(person instanceof Person); //true
alert(person.constructor == Person); //false
alert(person.constructor == Object); //true
这个解决的方法如下:
function Person(){
}
Person.prototype = {
constructor: Person,
name: "xiaobai",
age: 22,
sayName: function(){
alert(this.name);
}
}
组合使用构造函数和原型模式
因为原型主要是实现共享的方法与属性,所以我们还需要构造函数来定义实例属性。如下所示:
function Person(name, age){
this.name = name;
this.age = age;
}
Person.prototype = {
constructor: Person,
sayName: function(){
alert(this.name);
}
}
继承
原型链
JS 中原型链是实现继承的主要方法。其做法是让一个原型对象等于另一个类型的实例对象。
function Person(name, age){
this.name = name;
this.age = age;
}
Person.prototype.sayName = function(){
alert("My name is " + this.name);
};
function Student(major){
this.major = major;
};
Student.prototype = new Person("xiaobai", 22);
Student.prototype.sayMajor = function(){
alert("My major is " + this.major);
};
student = new Student("Computer");
student.sayName(); //"My name is xiaobai"
student.sayMajor(); //"My major is computer"
原型链主要问题在于,引用类型的属性会被所有实例共享,这很明显不是我们希望看到的。我们可以借用构造函数来解决这个问题,如下所示:
function Person(name, age){
this.name = name;
this.age = age;
}
Person.prototype.sayName = function(){
alert("My name is " + this.name);
};
function Student(name, age, major){
Person.call(this, name, age);
this.major = major;
};
Student.prototype = new Person();
Student.prototype.sayMajor = function(){
alert("My major is " + this.major);
};
student = new Student("xiaobai", 22, "Computer");
student.sayName();
student.sayMajor();
这种方式被成为组合继承,有时也被叫做伪经典继承,将原型链和借用构造函数组合到一起。它唯一一个小缺点时,原型中还保存与实例继承下来的同名属性。