面向对象
对象的两种属性:数据属性和访问器属性
数据属性: //对于直接在对象上定义属性,前三个默认都为 true value默认为undefined
[[Configurable]]:能否通过delete删除属性,能否修改属性,或者能否把属性修改为访问器属性。
[[Enumerable]]: 能否通过for-in循环返回属性,可枚举属性,
[[Writable]]: 能否修改属性
[[Value]]: 包含这个属性的数据值,读取属性值从这个位置读取;写入时保存也在位置
ES5的object.defineProperty()修改属性默认值,修改了Configurablefalse后,再调用这个方法就会出错。
访问器属性: //对于直接在对象上定义属性,前两个默认都为 true,后两个默认为undefined
[[Configurable]]:能否通过delete删除属性,能否修改属性,或者能否把属性修改为访问器属性。
[[Enumerable]]: 能否通过for-in循环返回属性,可枚举属性,
[[Get]]: 读取属性时调用的函数。
[[Set]]: 写入属性时调用的函数。
访问器属性不能直接定义,必须使用object.defineProperty()定义。定义多个属性用object.defineProperties(),形式key-object
/*_year的下划线是一种常用记号,用于只能通过对象方法访问的属性*/
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
/*支持ES5的defineProperty方法的浏览器要IE9+(IE8部分支持),在这个方法之前,要创建访问器属性,一般都是用两个非标准的方法:__defineGetter__()和__defineSetter__();
不支持defineProperty()的浏览器中不能修改[[Configurable]]、[[Enumerable]]*/
读取属性特性
使用object.getOwnPropertyDescriptor()取得给定属性的描述符,及访问器属性和数据属性,返回值为对象。接受两个参数:属性所在对象和要读取其描述符的属性名称。
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
alert(typeof descriptor.get); //"undefined"
var descriptor = Object.getOwnPropertyDescriptor(book,"year");
alert(descriptor.value); //undefined
alert(descriptor.enumerable); //false
alert(typeof descriptor.get); //"function"
创建对象
工厂模式
function creatPerson(name){
var o = new Object();
o.name = name;
o.sayName = function(){
alert(this.name);
};
return o;
}
var person1 = creatPerson("Nick");
var person2 = creatPerson("Greg");
解决了创建多个相似对象的问题,但没有解决对象识别的问题(怎么知道一个对象的类型);
构造函数
function Person(name){
this.name = name;
this.sayName = function(){
alert(this.name);
};
}
var person1 = new Person("Nick");
var person2 = new Person("Greg");
alert(person1.sayName == person2.sayName) //false
//不同实例上的同名函数是不相等的
构造函数可以自定义对象类型的属性和方法。
构造函数实际上经历一下4个步骤:
1. 创建新对象;
2. 将构造函数的作用域赋给新对象(因此this指向了这个新对象)
3. 执行构造函数中的代码(为新对象添加属性)
4. 返回新对象
构造函数也是函数,全局使用时this指向global对象(浏览器中为window对象)
不同之处:
- 没有显式创建对象
- 直接将属性和方法赋给this对象
- 没有return
- 函数名首字母为大写
构造函数的主要问题,每个方法都需要在每个实例上从新创建。
创建两个相同的实例函数没有很必要,何况还有this对象在执行代码钱将函数绑定到特定对象上面。因此可以将函数转移为全局函数来解决这个问题。
function Person(name){
this.name = name;
this.sayName = sayName;
}
function sayName(){
alert(this.name);
};
var person1 = new Person("Nick");
var person2 = new Person("Greg");
但如果对象需要定义多个函数的时候,需要定义多个全局函数,这样自定义的引用类型就丝毫没有封装性可言,所以通过使用原型模式来解决。
原型模式 Prototype(指针)
理解原型对象
现在所有实现都无法访问到[[Prototype]],但可以通过isPrototypeOf()确定对象之间是否存在这种关系。
alert(Person.prototype.isPrototypeOf(person1)); //true
ES5新方法 返回 [[Prototype]]的值
alert(Object.getPrototypeOf(person1) == Person.prototype); //true
当代码读取某个对象属性、方法时,先从对象实例本身开始,如果找到了则返回属性或执行方法,没有的话继续搜索指针指向的原型对象,找到则返回属性或者执行方法。删除或者修改属性,只会删除或者修改这个实例对象本身的属性,不会影响原型对象属性。
function Person(){
}
Person.prototype.name = "Nicholas";
var person1 = new Person();
var person2 = new Person();
alert(person1.hasOwnProperty("name")); //false
person1.name = "Greg";
alert(person1.name); //"Greg" ——来自实例
alert(person1.hasOwnProperty("name")); //true
alert(person2.name); //"Nicholas" ——来自原型
alert(person2.hasOwnProperty("name")); //false
delete person1.name;
alert(person1.name); //"Nicholas" ——来自原型
alert(person1.hasOwnProperty("name")); //false
原型与in操作符
使用in操作符的两种方式:
单独使用和在for-in循环使用。在单独使用时,in 操作符会在通
过对象能够访问给定属性时返回 true,无论该属性存在于实例中还是原型中。
function Person(){
}
Person.prototype.name = "Nicholas";
};
var person1 = new Person();
var person2 = new Person();
alert(person1.hasOwnProperty("name")); //false
alert("name" in person1); //true
person1.name = "Greg";
alert(person1.name); //"Greg" ——来自实例
alert(person1.hasOwnProperty("name")); //true
alert("name" in person1); //true
alert(person2.name); //"Nicholas" ——来自原型
alert(person2.hasOwnProperty("name")); //false
alert("name" in person2); //true
delete person1.name;
alert(person1.name); //"Nicholas" ——来自原型
alert(person1.hasOwnProperty("name")); //false
alert("name" in person1); //true
判断一个实例中是否有指定属性,而不是在原型中的函数:
function hasPrototypeProperty(object, name){
return !object.hasOwnProperty(name) && (name in object);
}
实例中定义了 name 属性,原型中的 name 属性将用不到了。
function Person(){
}
Person.prototype.name = "Nicholas";
var person = new Person();
alert(hasPrototypeProperty(person, "name")); //true
person.name = "Greg";
alert(hasPrototypeProperty(person, "name")); //false
for-in循环时,返回的是所有能够通过对象访问的、可枚举属性,其中包括存在于实例中的属性,也包括存在于原型中的属性。屏蔽了原型中不可枚举属性的实例属性也会返回。IE早期版本的BUG,屏蔽不可枚举属性的实例属性不会出现在for-in循环中。
ES5 的 Object.keys() 方法。这个方法
接收一个对象作为参数,返回一个包含所有可枚举属性的字符串数组
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()
方法。
var keys = Object.getOwnPropertyNames(Person.prototype);
alert(keys); //"constructor,name,age,job,sayName"
//包含了不可枚举的 constructor 属性
Object.keys() 和 Object.getOwnPropertyNames() 方法都可以用来替代 for-in 循环
更简单的原型语法
使用对象字面量重写整个原型对象
function Person(){
}
Person.prototype = {
name : "Nicholas",
age : 29,
job: "Software Engineer",
sayName : function () {
alert(this.name);
}
};
//导致的结果 constructor无法确定对象的类型
var friend = new Person();
alert(friend instanceof Object); //true
alert(friend instanceof Person); //true
alert(friend.constructor == Person); //false
alert(friend.constructor == Object); //true
//为解决上面的问题,直接绑定指向对象,如下:
Person.prototype = {
constructor : Person,
name : "Nicholas",
age : 29,
job: "Software Engineer",
sayName : function () {
alert(this.name);
}
};
但这种重置constructor属性的方法会导致它的[[Enumerable]]特性被设置为true,使的constructor为可枚举的,因此为解决这个问题只有重置构造函数的属性,如下:
function Person(){
}
Person.prototype = {
name : "Nicholas",
age : 29,
job : "Software Engineer",
sayName : function () {
alert(this.name);
}
};
//重设构造函数,只适用于 ECMAScript 5 兼容的浏览器
Object.defineProperty(Person.prototype, "constructor", {
enumerable: false,
value: Person
});
但是如果重写了整个原型对象,那就切断了构造函数和最初原型之间的联系。实例中的指针仅指向原型,而不指向构造函数。
var friend = new Person();
friend.sayName(); //error
//friend指向的原型中不包含以该名字命名的属性。
原生对象的原型
所有原生引用类型( Object、 Array、 String,等等)都在其构造函数的原型上定义了方法,(sort/substring…)。可以再定义方法或者修改原生对象的原型,但是不推荐修改的,这样会影响到其他使用到这个原生对象的方法。
原型对象的问题在于属性方法的共享
function Person(){
}
Person.prototype = {
constructor: Person,
friends : ["Shelby", "Court"],
};
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
结合情况,组合使用构造函数模式和原型模式
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
继承
面向对象的语言都支持两种继承方式:接口继承和实现继承。接口继承只继承方法签名,而实现继承则继承实际的方法。js只支持实现继承,而且实现继承依靠原型链继承来实现。
函数的签名,包括参数列表,函数名;这些都需要固定。用来实现类型检查、函数重载、接口(interface)等等。但是js不具备
基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法
function SuperType(){
this.property = true;
}
SuperType.prototype.getSuperValue = function(){
return this.property;
};
function SubType(){
this.subproperty = false;
}
//继承了 SuperType
SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function (){
return this.subproperty;
};
var instance = new SubType();
alert(instance.getSuperValue()); //true
确定原型和实例的关系
方法1:instanceof 测出现过的构造函数
方法2:isPrototypeOf 测出现过的原型
alert(instance instanceof Object); //true
alert(instance instanceof SuperType); //true
alert(instance instanceof SubType); //true
alert(Object.prototype.isPrototypeOf(instance)); //true
alert(SuperType.prototype.isPrototypeOf(instance)); //true
alert(SubType.prototype.isPrototypeOf(instance)); //true
谨慎定义方法
- 子类型有时候需要重写超类型中的某个方法,或者需要添加超类型中不存在的某个方法。但不管怎样,给原型添加方法的代码一定要放在替换原型的语句之后
- 在通过原型链实现继承时,不能使用对象字面量创建原型方法。因为这样做就会重写原型链。
借用构造函数
运用call()/apply()
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"
缺点:
1. 无法复用
2. 超类型的原型中定义的方法,对子类型而言是不可见的。
组合继承
原型链和借用构造函数的技术组合到一块,从而发挥二者之长的一种继承模式。
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;
SubType.prototype.sayAge = function(){
alert(this.age);
};
var instance1 = new SubType("Nicholas", 29);
instance1.colors.push("black");
alert(instance1.colors); //"red,blue,green,black"
instance1.sayName(); //"Nicholas";
instance1.sayAge(); //29
var instance2 = new SubType("Greg", 27);
alert(instance2.colors); //"red,blue,green"
instance2.sayName(); //"Greg";
instance2.sayAge(); //27
使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。
原型式继承
借助原型可以基于已有的对象创建新对象,同时还不必因此创建自定义类型
//实质是对传入的对象进行一次浅复制
function object(o){
function F(){}
F.prototype = o;
return new F();
}
var person = {
name: "Nicholas",
friends: ["Shelby", "Court", "Van"]
};
var anotherPerson = object(person);
anotherPerson.name = "Greg";
anotherPerson.friends.push("Rob");
var yetAnotherPerson = object(person);
yetAnotherPerson.name = "Linda";
yetAnotherPerson.friends.push("Barbie");
alert(person.friends); //"Shelby,Court,Van,Rob,Barbie"
ES5新增 Object.create() 方法规范化了原型式继承。
var person = {
name: "Nicholas",
friends: ["Shelby", "Court", "Van"]
};
var anotherPerson = Object.create(person);
anotherPerson.name = "Greg";
anotherPerson.friends.push("Rob");
var yetAnotherPerson = Object.create(person);
yetAnotherPerson.name = "Linda";
yetAnotherPerson.friends.push("Barbie");
alert(person.friends); //"Shelby,Court,Van,Rob,Barbie"
Object.create() 方法的第二个参数与 Object.defineProperties() 方法的第二个参数格式相同:每个属性都是通过自己的描述符定义的。以这种方式指定的任何属性都会覆盖原型对象上的同名属性。例如:
var person = {
name: "Nicholas",
friends: ["Shelby", "Court", "Van"]
};
缺点:共享属性导致不同实例引用会出错
寄生式继承
和原型式继承相似
function createAnother(original){
var clone = object(original); //通过调用函数创建一个新对象
clone.sayHi = function(){ //以某种方式来增强这个对象
alert("hi");
};
return clone; //返回这个对象
}
缺点:无法复用和构造函数模式类似
寄生组合式继承
寄生组合式继承,就是通过借用构造函数继承属性,通过原型链的混成形式来继承方法。
基本模板函数:
function inheritPrototype(subType,superType){
var prototype = Object(superType.prototype);//创建对象
prototype.constructor = subType;//增强对象
subType.prototype= prototype;//制定对象
}
应用:
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;
}
inheritPrototype(SubType, SuperType);
SubType.prototype.sayAge = function(){
alert(this.age);
};
总结:
一、创建对象:
1. 工厂模式:函数内创建对象,添加属性和方法,返回对象。(被构造函数取代)
2. 构造函数模式:创建自定义引用类型,使用new操作符创建引用类型实例。(无法复用)
3. 原型模式:使用prototype属性指定需要共享的属性和方法。(不可使用字面量创建对象属性)。
最佳的方法:结合构造函数和原型模式,即构造函数定义实例属性,原型定义共享的属性和方法。
二、继承
通过原型链实现继承。即将一个类型的实例赋值给另一个构造函数的原型实现的。
最佳的方法:原型链继承共享的属性和方法,通过借用构造函数(call/apply)继承实例属性
其他继承模式:
1. 原型继承:执行对给定的对象进行浅复制。
2. 寄生式继承:创建对象,增强对象,返回对象。
3. 寄生组合式继承:集寄生式继承和组合继承的优点于一身,是实现基于类型继承的最有效的方式。