写在前面的:需要大量时间反复理解;
《高程》中这么描述:
面向对象(Object-Oriented,OO)的语言有一个标志,那就是它们都有类的概念,而通过类可以创建任意多个具有相同属性和方法的对象。前面提到过,ECMAScript中没有类的概念,因此它的对象也与基于类的语言中的对象有所不同。 ECMA-262把对象定义为:无序属性的集合,其属性可以包含基本值、对象或者函数。”严格来讲,这就相当于说对象是一组没有特定顺序的值。对象的每个属性或方法都有一个名字,而每个名字都映射到一个值。正因为这样,我们可以把ECMAScript的对象想象成散列表:无非就是一组名值对,其中值可以是数据或是函数。
1. 理解对象
1.1 属性类型 ECMAScript中有两种属性:数据数据和访问器属性
- 数据属性
描述数据属性行为的特性 | 功能 |
---|---|
[[Configurable]] | 能否通过delete 删除,能否修改属性特性,能否把属性修改为访问器属性 |
[[Enumerable]] | 能否通过 for-in 循环返回属性 |
[[Writable]] | 能否修改属性值 |
[[Value]] | 属性的数据值 |
修改属性默认的特性,必须使用 Object.defineProperty(),该方法接受三个参数:属性所在的对象,属性的名字,描述对象(只能为configurable,enumerable,writable , value)。
var person = {};
Object.defineProperty(person, 'name', { writable: false, value: 'Maning'});
console.log(person.name); // Maning
person.name = 'Other';
console.log(person.name); // Maning
该例创建一个名为name属性,值为只读,这个属性的值是不可修改的,重新赋值后,并未成功。
var person = {};
Object.defineProperty(person, 'name', { configurable: false, value: 'Maning'});
console.log(person.name,1); // Maning
person.name = 'Other';
console.log(person.name,2); // Maning
delete person.name;
console.log(person.name, 2); // Maning
设置configurable为false后,定义属性为不可配置的,主要表现有以下:
不能从对象中删除属性;
不能去修改属性值;
其余三个属性默认值均为false。
更简洁的说,除了能再次修改writable的属性,其余属性均无法修改。
- 访问器属性
描述访问器属性的特性 | 功能 |
---|---|
[[Configurable]] | 能否通过delete 删除,能否修改属性特性,能否把属性修改为访问器属性 |
[[Enumerable]] | 能否通过 for-in 循环返回属性 |
[[Get]] | 读取属性调用的函数 |
[[Set]] | 写入属性调用的函数 |
2. 创建对象
2.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");
每个实例对象虽然通过同一个方法出来,但是全部是通过Object实例化出来,即使传入的参数完全相同的情况,其实两者也是不等的。
2.2 构造函数模式
function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
this.sayName = function(){
alert(this.name);
}; }
var person1 = new Person("Nicholas", 29, "Software Engineer");
var person2 = new Person("Greg", 27, "Doctor");
该方式创建对象,我们能够看到以下几点不同:
1.没有显示创建对象,
2.直接将属性和方法赋给了this对象,
3.没有return语句
4.函数名的首字母是大写(这应该是一种规范,区分与其他函数)
- 构造函数当作函数使用
// 当作构造函数使用
var person = new Person("Nicholas", 29, "Software Engineer");
person.sayName(); //"Nicholas"
// 普通函数
// 在全局作用域中调用函数,this对象指向Global对象(在浏览器指向window)
Person("Greg", 27, "Doctor");
window.sayName(); //"Greg"
// 另一个对象的作用域中调用
var o = new Object();
Person.call(o, "Kristen", 25, "Nurse"); o.sayName(); //"Kristen"
- 构造函数问题
每一个方法都要在每一个实例上重新创建,这就意味着每一个实例的同名方法其实是不等价的,因为它们都是不同的实例。
解决方法:可以让构造函数中的方法指向同一个全局函数,但这又带来另外的问题,如果多个方法则会暴露出多个全局函数。
2.3 原型模式
请理解《高程》中的这么一句话:我们创建的每个函数都有一个prototype(原型)属性,这个属性是一个指针,指向一个对象,而这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法。
function Person(){
}
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function(){
alert(this.name);
};
var person1 = new Person();
person1.sayName(); //"Nicholas"
var person2 = new Person();
person2.sayName(); //"Nicholas"
alert(person1.sayName == person2.sayName); //true
- 理解原型
1.只要创建一个新函数,该函数会有一个
prototype(原型)
属性,去指向一个原型对象,包含所有共享属性与方法。
2.所有原型对象会有一个constructer(构造函数)
属性,指向构造函数。
3.原型对象方法isPrototypeOf()
来确定对象之间的关系:
Person.prototype.isPrototypeOf(person1) // true
4.ES5方法Object.getPrototypeOf()
返回原型对象值;
Object.getPrototypeOf(person1) // { ... }
5.如果实例中添加一个与原型同名的属性时,原型的属性不会被修改,但只能访问到实例的属性;只有使用delete
删除实例属性,才能重新访问原型属性。
6.hasOwnProperty()
检测一个属性是否存在于实例中;
person1.hasOwnProperty( "name")
2.4 组合模式构造函数模式和原型模式
顾名思义,采用构造函数模式定义实例属性,原型模式定义方法和共享的属性。这样子每个实例有各自的实例属性副本,又共享对方法的引用,最大限度节省了内存。
function Person(){
}
Person.prototype = {
constructor: Person,
name : "Nicholas",
age : 29,
job : "Software Engineer",
friends : ["Shelby", "Court"],
sayName : function () {
alert(this.name);
} };
var person1 = new Person();
var person2 = new Person();
person1.friends.push("Van");
alert(person1.friends); //"Shelby,Court,Van"
alert(person2.friends); //"Shelby,Court,Van"
alert(person1.friends === person2.friends); //true
⚠️以下几种不做过多描述,想要了解,可以参考《高程》第6章第6.2节
2.5 动态原型模式
根据需要判断是否要初始化原型;
2.6寄生构造函数模式
在构造函数中返回一个实例化的对象;
2.7妥协构造函数模式
主要提供安全性;