持续不断的学习---面向对象的程序设计
面向对象的程序设计
属性类型
数据类型
-
Configurable:能否通过
delete
删除属性从而重新定义属性 默认值为true
-
Enumerable:能否通过
for-in
循环返回属性 默认值为true
-
Writable:能否修改属性的值 默认为
true
-
Value:数据值 读取属性值,即读取value,修改也是修改value 默认值为
undefined
var person = {}; Object.defineProperty(person,'name',{ writable:false, //不可修改 value:'AAAA' //值为'AAAA' }) alert(person.name) // 'AAAA' person.name = 'BBBB' alert(person.name) // 'AAAA' // 创建了 name 的属性,但值是只读的,不可修改,非严格模式下,指定新值会忽略,严格模式下会报错 var person = {}; Object.defineProperty(person,'name',{ connfigurable:false, // 是否可以先删除属性 在重新定义 value:"AAAA" // 值为’AAAA‘ }) alert(person.name) // 'AAAA' delete(person.name) //不可删除 alert(person.name) // 'AAAA' // 把configurable设置为false,意味着不能从对象中删除属性,非严格模式下,删除属性不会报错,严格模式下会报错。如果设置为false变为不可配置,则不可能在变为可配置的。 // 如果使用defineProperty创建新的属性时 不指定这三个数据类型 configuration enumerable writable 那么这三个特性的默认值都为false 一旦把configurable设置为false 那么其他两个值都不可在修改。
访问器属性
-
configuration:同上
-
enumerable:同上
-
get:读取属性时调用的函数
-
set:写入属性时调用的函数
var book = { _year:2004, edition:1 }; Object.defineProperty(book,'year',{ get:function(){ return this._year }, set:function(newVal){ if(newVal > 2004){ this._year = newVal; this.edition +=newVal - 2004 } } }) book.year = 2005; console.log(book.edition); // 2 // 两者并非一定要同时存在 如果只存在get,那么只能读,不能写入,如果只存在set,那么只能写入,不能读
读取属性的特性
- 访问器属性:configuratble/enumerable/set/get
- 数据属性:configuratble/enumerable/writable/value
创建对象
工厂模式
解决了创建多个相似对象的问题,但却没有解决对象识别的问题(即怎样知道一个对象的类型)
function createPerson(name,age){
var o = new Object();
o.name = name;
o.age = age;
o.sayName = function(){
alert(this.name)
}
return o
}
var person1 = createPerson('蛋蛋',18)
构造函数模式
new 操作符这种方式调用构造函数经历4个步骤
-
创建一个新对象
-
将构造函数的作用域赋给新对象(因此this就指向了这个新对象)
-
执行构造函数中的代码(为这个新对象添加属性)
-
返回新对象
function Person(name,age){
this.name = name;
this.age = age;
this.sayName = function(){
alert(this.name)
}
}
var person1 = new Person('蛋蛋',18);
var person2 = new Person('廖魔鬼',88);
// 与工厂模式的不同
/*
* 没有显示的创建对象
* 直接将属性和方法赋给了this对象
* 没有return语句
*/
在上面的例子里,
person1
和person2
分别保存着Person
的一个不同的实例。这两对象都有一个constructor
(构造函数)属性,该属性指向Person
alert(person1 instanceof Object) // true
alert(person1 instanceof Person) // true
alert(person2 instanceof Object) // true
alert(person2 instanceof Person) // true
构造函数的问题(见书P147)
全局作用域中定义的函数实际上只能被某个对象调用,这让全局作用域有点名不副实
如果对象需要定义很多方法,就需要定义很多全局函数
原型模式
创建的每个函数都有一个prototype属性,这个属性是一个指针,指向一个对象,这个对象的用途是包含可以由特定类型的所有实力共享的属性和方法。即使用原型对象的好处是可以让所有的对象实例共享他所包含的属性和方法。
function Person(){}
Person.prototype.name = 'Nice';
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function(){
alert(this.name);
}
var person1 = new Person(); // 'Nice'
person1.sayName();
var person2 = new Person(); // 'Nice'
person2.sayName();
alert(person1.sayName == person2.sayName); // true
理解原型对象
创建新函数,会为该函数创建一个prototype属性,这个属性指向函数的原型对象,所有的原型对象都会获得一个constructor(构造函数)属性,这个属性是一个指向prototype属性所在函数的指针。
console.log(Person.prototype); // {name:"Nice",---------}
console.log(Person.prototype.constructor); // f Person() Person函数

对象实例可以访问保存在原型中的值,但不能通过对象实例重写原型中的值,如果在对象实例中添加一个属性,且属性与实实例原型(Object.getPrototypeOf(person1))的属性同名,那么会在实例中创建该属性,屏蔽原型中的那个属性。
function Person(){}; // 构造函数
Person.prototype.name = 'ABC'; // 原型对象
var person1 = new Person(); // 实例
person1.name = 'DEF';
var person2 = new Person(): // 实例
console.log(person1.name); //DEF
consolle.log(person2.name); // ABC
//***************
如果删除了实例中的属性 那么会去读原型中的对应的属性
delete(person1.name);
console.log(person1.name); // ABC
更简单的原型语法
instanceof
运算符用来检测constructor.prototype
是否存在于参数object
的原型链上。
与上面的原型语法相比,该语法减少了不必要的输出,用一个包含所有属性和方法的对象字面量重写整个原型对象。
但该语法的
constructor
不再指向Person
,而是Object
function Person(){}
Person.prototype = {
name:"Nice",
age:20
}
var friend = new Person();
console.log(friend innstanceof Object); // true
console.log(friend innstanceof Person); // true
console.log(friend.constructor == Object); // true
console.log(friend.constructor == Person); // false
原型对象的问题
实例一般都是要有属于自己的全部的属性,引用指针相同
下面的就是最简单的例子 person1新交了一个朋友,结果导致person2就一定会认识这个朋友,这个结果不一定是person1想要的。
function Person(){};
Person.prototype = {
constructor:Person, // 指定构造函数为Person 该方法造成Enumeratble为true
friend:['a','b','c']
}
var person1 = new Person();
person1.friend.push('d');
var person2 = new Person();
console.log(person1.friend) // a,b,c,d
console.log(person2.friend) // a,b,c,d
console.log(person1.friend == person2.friend) //true
组合使用构造函数模式和原型模式
构造函数模式:用于定义实例属性
原型模式:用于定义方法和共享的属性
优点:每个实例都会有自己的一份实力属性的副本,但是同时又共享着对方法的引用,最大限度的节省了内存,这种模式还支持想构造函数传递参数
function Person(name,age){
this.name = name;
this.age = age;
this.friends = ['a','b','c']
}
Person.prototype = {
constructor:Person,
sayName:function(){
console.log(this.name);
}
}
var person1 = new Person('蛋蛋',18);
var person2 = new Person('xx',28);
person1.friends.push('d');
console.log(person1.friends); // ['a','b','c','d']
console.log(person2.friends); // ['a','b','c']
console.log(person1.friends == person2.friends); // false
console.log(person1.sayName == person2.sayName); // true
该例子中,实例属性都在构造函数中定义,所有共享的属性
constructor
和方法(sayName
)则是在原型中定义的。而单独修改person1.friends,不会影响到person2.friends,因为它们分别引用了不同的数组。
动态原型模式
所有信息封装在构造函数中,通过在构造函数中初始化原型,又保持了同时使用构造函数和原型的优点。
也就是说可以通过检查某个应该存在的方法是否有效,来决定是否初始化原型
function Person(name,age){
this.name = name;
this.age = age;
this.friends = ['a','b','c'];
// 方法
if(typeof this.sayName != 'function'){
Person.prototype.sayName = function(){
console.log(this.name)
}
}
}
构造函数中 if语句中,只在sayName不存在的情况下,才会添加到原型中,这段代码只会在初次调用构造函数时才会执行。此后,原型已经完成初始化。
寄生构造函数模式
function Person(name,age){
var o = new Object();
o.name = name;
o.age = age;
o.friend = [1,2]
o.sayName = function(){
console.log(this.name)
}
return o;
}
var person1 = new Person('蛋蛋',18);
var person2 = new Person('蛋蛋',18);
person1.friend.push(3)
console.log(person1.friend) // [1,2,3]
console.log(person2.friend) // [1,2]
person2.sayName();
返回的对象与构造函数或者与构造函数的原型属性之间没有任何关系,也就是说构造函数返回的对象与在构造函数外部创建的对象没有什么不同
稳妥构造函数
function Person(name,age){
var o = new Object();
o.sayName = function(){
console.log(name)
}
return o;
}
var friend = new Person('蛋蛋',18);
friend.sayName(); // '蛋蛋'
这种模式创建的对象中,除了使用
sayName
方法外,没有其他任何方法可以访问name的值,即使有其他代码会给这个对象添加方法或者属性,但也不可能有别的方法访问传入到构造函数中的原始数据(sayName
除外)
继承
原型链
基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法
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();
console.log(instance.getSuperValue()); // 继承自SuperType
console.log(instance.getSubValue()); // 自己的方法

所有的引用类型默认都继承了
Object
,这个继承也是通过原型链实现的
确定原型和实例的关系
使用
instanceof
操作符,测试实例与原型链中出现过的狗在函数,结果返回true
console.log(instance instanceof Object) // true
console.log(instance instanceof SubType) // true
console.log(instance instanceof SuperType) // true
使用
isPrototypeOf()
方法。只要是原型链中出现过的原型,都可以说是该原型链所派生的实例的原型,因此结果也返回true
console.log(Object.prototype.isPrototypeOf(instance)) // true
console.log(SubType.prototype.isPrototypeOf(instance)) // true
console.log(SuperType.prototype.isPrototypeOf(instance)) // true
谨慎的定义方法
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
}
////// 重写原来继承的原型中(超类型)的方法
SubType.prototype.getSuperValue = function(){
return false
}
var instance = new SubType();
console.log(instance.getSuperValue()); // 继承自SuperType
不能使用字面量创建原型方法,会重写原型,切断原来原型链中的关系
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();
console.log(instance.getSuperValue()) /////////// 报错
由于SubType先继承SuperType,所以在试用字面量定义原型,导致继承失效,字面量定义原型重写了原型,切断了原来的关系。
function SuperType(){
this.property = true;
}
SuperType.prototype.getSuperValue = function(){
return this.property;
}
function SubType(){
this.subproperty = false;
}
// 使用字面量定义原型
SubType.prototype = {
getSubValue:function(){
return this.subproperty;
}
}
// 继承SuperType,上面的字面量定义原型失效
SubType.prototype = new SuperType();
var instance = new SubType();
console.log(instance.getSubValue()) /////////// 报错
同样的,若是先使用字面量定义原型,在定义SubType继承SuperType,会导致字面量定义圆心失效
使用组合继承解决借用构造函数的问题和原型链的问题
将原型链和借用构造函数的技术组合到一块儿。思路是使用原型链实现对原型属性和方法的继承,通过借用构造函数来实现对实例属性的继承。既可以在原型上定义方法实现函数复用,又可以保证每个实例都有各自的属性,不会相互影响。
function SuperType(name){
this.name = name;
this.colors = ['red','blue'];
}
SuperType.prototype.sayName = function(){
console.log(this.name)
}
function SubType(name,age){
// 继承属性 使用call 直接使用SuperType的 this.name = name ;然后改变this的指向
SuperType.call(this,name);
console.log('SuperType.call(this,name)')
console.log(SuperType.call(this,name))
this.age = age;
}
// 继承方法
SubType.prototype = new SuperType();
// 指定SubType的构造函数为SubType
SubType.prototype.constructor = SubType;
// 给SubType原型添加方法
SubType.prototype.sayAge = function(){
console.log(this.age)
}
var instance1 = new SubType('AAAA',18);
instance1.colors.push('black');
console.log(instance1.colors); // ['red','blue','black']
instance1.sayName(); // 'AAAA'
instance1.sayAge(); // 18
var instance2 = new SubType('BBBBB',38);
instance2.colors.push('green');
console.log(instance2.colors); // ['red','blue','green']
instance2.sayName(); // 'BBBBB'
instance2.sayAge(); // 38