一、属性类型
ECMAScript 中有两种属性:数据属性和访问器属性。
1 数据属性 包含一个数据值的位置。在这个位置可以读取和写入值。其特性有:configurable、enumerable、writable 和 value,修改数据属性的方法是Object.defineProperty()方法。这个方法 接收三个参数:属性所在的对象、属性的名字和一个描述符对象。
var person = {};
Object.defineProperty(person, "name", {
writable: false,
value: "Nicholas",
configurable: false
});
alert(person.name); //"Nicholas"
person.name = "Greg";
alert(person.name); //"Nicholas";
delete person.name;
alert(person.name); //"Nicholas"
把 configurable 设置为 false,表示不能从对象中删除属性。而且,一旦把属性定义为不可配置的, 就不能再把它变回可配置了。
2 访问器属性(IE9以上支持) 不包含数据值;它们包含一对儿 getter 和 setter 函数。访问器属性不能直接定义,必须使用 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
3 定义多个属性 Object.defineProperties()方法。利用这个方法可以通过描述符一次定义多个属性。
4 读取属性的特性 Object.getOwnPropertyDescriptor()方法,可以取得给定属性的描述符。
二、创建对象
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");
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");
注意:构造函数不用return,new的过程会在执行完构造函数后返回新对象。并将构造函数的作用域赋给新对象(因此this就指向新对象)。
构造函数产生的实例对象中的constructor(构造函数)属性回指向他的构造函数,即Person.
alert(person1.constructor == Person); //true
==> 检测对象类型:instanceof
构造函数模式比工厂模式好的地方:创建自定义的构造函数意味着将来可以将它的实例标识为一种特定的类型,即下面代码所示:
alert(person1 instanceof Object); //true
alert(person1 instanceof Person); //true
构造函数的缺点是,其中的每个方法都要在每个实例上重新创建一遍。如下代码所示:
alert(person1.sayName == person2.sayName); //false
为解决此问题,可以把里面的方法提成公共方法,放到构造函数的外部。
3).原型模式
创建一个构造函数,给这个构造函数的原型添加属性和方法。和 2) 构造函数模式的区别是他的所有方法是共享的原型里的方法。
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"
创建函数(尤其是构造函数)时,会按照某些规则为该函数创造一个prototype属性,该属性指向这个函数的原型对象。同时原型会有一个constructor属性指向该函数。当创建实例时,实例中的[[Prototype]]指针就指向他的原型,具体写法是_ _ proto_ _ 。而且实例会遗传原型中的constructor属性,也指向构造函数。
- 判断原型的两个方法:isPrototypeOf() 和 Object.getPrototypeOf(),后者是ECMA5里新加的,所以后者必须在IE9以上才支持。
- 判断属性是在原型上还是在实例上可以用in方法和hasOwnProperty方法。因为只要原型或者实例上有该属性,in方法就会返回true,而只有实例上有该属性,hasOwnProperty才会返回true,否则都是false。
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();
var person2 = new Person();
alert(person1.hasOwnProperty("name")); //false
alert("name" in person1); //true
- 要取得对象上所有可枚举的实例属性,可以使用 ECMAScript 5 的 Object.keys()方法。这个方法接收一个对象作为参数,返回一个包含所有可枚举属性的字符串数组。(IE8以上)
function Person(){
}
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function(){
alert(this.name);
};
var keys = Object.keys(Person.prototype);
alert(keys); //"name,age,job,sayName"
var p1 = new Person();
p1.name = "Rob";
p1.age = 31;
var p1keys = Object.keys(p1);
alert(p1keys); //"name,age"
- 如果你想要得到所有实例属性,无论它是否可枚举,都可以使用 Object.getOwnPropertyNames() 方法。(IE8以上)
var keys = Object.getOwnPropertyNames(Person.prototype);
alert(keys); //"constructor,name,age,job,sayName"
- 当重写构造函数的原型时,需要在原型对象里手动将constructor属性指向该构造函数。但是此时constructor的[[Enumerable]]特性被设置为 true。默认 情况下,原生的 constructor 属性是不可枚举的。不过可以在兼容ECMA5的浏览器里用Object.defineProperty将这个属性设置成不可枚举的,如下所示:
//重设构造函数,只适用于 ECMAScript 5 兼容的浏览器
Object.defineProperty(Person.prototype, "constructor", {
enumerable: false,
value: Person
});
- 原型模式的最大问题: 原型模式的最大问题是由其共享的本性所导致的。在一个实例中对某个原型中的属性做了修改,其他实例中的这个属性也会更改。
4).组合使用构造函数模式和原型模式
即构造函数模式用于定义实例属性,原型模式用于定义方法和共享的属性。这样,每个实例都会有自己的一份实例属性的副本, 但同时又共享着对方法的引用,最大限度地节省了内存。该模式用的最多
function Person(name, age, job){
this.name = name; 3 this.age = age;
this.job = job;
this.friends = ["Shelby", "Court"];
2
}
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
5).动态原型模式
它把所有信息都封装在了构造函数中,而通过在构造函数 中初始化原型(仅在必要的情况下),又保持了同时使用构造函数和原型的优点。
function Person(name, age, job){
//属性
this.name = name;
this.age = age;
this.job = job;
//方法
if (typeof this.sayName != "function"){
Person.prototype.sayName = function(){
alert(this.name);
};
}
}
var friend = new Person("Nicholas", 29, "Software Engineer");
friend.sayName();
对于采用这种模式创建的对象,还可以使 用 instanceof 操作符确定它的类型。
6).寄生构造函数模式
这种模式 的基本思想是创建一个函数,该函数的作用仅仅是封装创建对象的代码,然后再返回新创建的对象。
function Person(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 friend = new Person("Nicholas", 29, "Software Engineer");
friend.sayName(); //"Nicholas"
除了使用 new 操作符并把使用的包装函数叫做构造函数之外,这个模式跟工厂模式其实 是一模一样的。构造函数在不返回值的情况下,默认会返回新对象实例。而通过在构造函数的末尾添加一个 return 语句,可以重写调用构造函数时返回的值。一般不建议使用这种模式。
7).稳妥构造函数模式
所谓稳妥对象,指的是没有公共属性,而且其方法也不引用 this 的对象。稳妥对象最适合在 一些安全的环境中(这些环境中会禁止使用 this 和 new),或者在防止数据被其他应用程序(如 Mashup 程序)改动时使用。
function Person(name, age, job){
//创建要返回的对象
var o = new Object();
//可以在这里定义私有变量和函数
//添加方法
o.sayName = function(){
alert(name);
};
//返回对象
return o;
}
三、继承
1).原型链
- 将一个构造函数的原型设置成另一个原型的实例,那么这个构造函数的实例将继承另一个原型上相应的方法。本质就是重新定义原型对象,代之以一个新类型的实例。
- 构造函数里的方法是存在实例中的方法和属性,原型中的方法是存在原型中的原型方法。
- 所有引用类型默认都继承了 Object,而 这个继承也是通过原型链实现的,所有函数的默认原型都是 Object 的实例,因此默认原 型都会包含一个内部指针,指向 Object.prototype。
- 只要是原型链中的构造函数,使用instanceof,isPrototypeOf方法测试返回的都会是true。
- 原型链的问题:原来的实例属性变成了下一级的原型属性,会被共享,一旦在子实例中修改了该方法,修改的是原型的,不好,所以不建议单独使用。
2).借用构造函数(经典继承)
就是在子类型构造函数的内部用call调用超类型构造函数。这样每个属性都是互相独立的,不会共享。还可以设置在子类型构造函数中向超类型构造函数中传递参数。
但是在超类型的原型中定义的方法,不能复用,对子类型而言也是不可见的,所以用的较少。
function SuperType(){
this.colors = ["red", "blue", "green"];
}
function SubType(){
//继承了 SuperType
SuperType.call(this);
}
var instance1 = new SubType();
instance1.colors.push("black");
alert(instance1.colors); //"red,blue,green,black"
var instance2 = new SubType();
alert(instance2.colors); //"red,blue,green"
2).组合继承
组合继承(combination inheritance),有时候也叫做伪经典继承,指的是使用原型链实现对原型属性和方法的继承(主要是方法),而通过借用构造函数来实现对实例属性的继承。
function SuperType(name){
this.name = name;
this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function(){
alert(this.name);
};
function SubType(name, age){
//继承属性
SuperType.call(this, name);
this.age = age;
}
//继承方法
SubType.prototype = new SuperType();
SubType.prototype.constructor = SubType;
组合继承是用的最多的继承方法,属性通过借用构造函数可以相互独立,方法通过原型链继承可以共享,减少内存消耗。
3).原型式继承
在 object()函数内部,先创建了一个临时性的构造函数,然后将传入的对象作为这个构造函数的 原型,最后返回了这个临时类型的一个新实例。
function object(o){
function F(){}
F.prototype = o;
return new F();
}
ECMA5里新增一个object.create()方法和上述继承原理类似,如下:
var person = {
name: "Nicholas",
friends: ["Shelby", "Court", "Van"]
};
var anotherPerson = Object.create(person, {
name: {
value: "Greg"
}
});
alert(anotherPerson.name); //"Greg"
4).寄生式继承
创建一个仅用于封装继承过程的函数,该 函数在内部以某种方式来增强对象,最后再像真地是它做了所有工作一样返回对象。
function createAnother(original){
var clone=object(original); //通过调用函数创建一个新对象
clone.sayHi = function(){ //以某种方式来增强这个对象
alert("hi");
};
return clone; //返回这个对象
}
5).寄生组合式继承
开发人员普遍认为寄生组合式继承是引用类型最理想的继承范式。