JAVASCRIPT高程笔记-------第六章 面向对象的程序设计

理解对象的概念  js中的对象与其他 编程语言中的类不一样  ECMAscript 没有类的概念      ECMA-262 把对象定义为 “无序属性的集合,其属性可以包含基本值,对象或者函数”   也就是键值对   可以理解成散列表  示例简单的创建一个对象

var person = new Object();
person.name = "zhangsan"
person.age = 20;
person.sayhi = function (){
	return "myName is "+this.name;
}
alert(person.sayhi());   // myName is zhangsan

  

6.1  属性类型  ---ECMAscript包含两种属性: 数据属性和访问器属性   

6.1.1数据属性  ---简单来理解就是 描述对象属性的属性  相当于java中的元数据信息      

    [[Configgurable]]  表示属性是否可以通过delete 删除属性 从而重新定义属性    或者能否将属性修改为访问器属性    示例中定义的person对象的name、age以及sayhi()方法 默认都是为true

    [[Enumerable]]  表示对象的该属性是否可以通过 for-in循环返回该属性    示例中  定义的person对象的name、age以及sayhi()方法 默认都是为true

    [[Waitable]] 表示能否修改 该对象的属性的值  相当于java中 final 关键字修饰的常量     示例中  定义的person对象的name、age以及sayhi()方法 默认都是为true

    [[Value]] 表示 该对象属性的实际值  例如示例中 person.name  的实际值就是等于"zhangsan"    这个特性的的值默认为undefined 

r如何修改对象属性默认的特性  则必须使用ECMAscript5的 Object.defineProperty()方法  此方法接收三个参数   第一个参数为对象  第二个参数为对象的属性,第三个参数为对象属性的描述 示例

var person ={};
Object.defineProperty(person,'name',{
    writable: false,       //设置person对象的name属性为只读
    configurable: true,  
    Enumerable: true,
    value: "zhangsan"
});
alert(person.name);   // ‘zhangsan’
person.name = 'lisi';        //因为person对象name属性已经变成只读 所以此处修改无效,并且在严格模式下的js解析将会报错
alert(person.name);  // ‘zhangsan’
Object.defineProperty(person,'name',{
    writable: true,
    configurable: true,
    Enumerable: true,
    value: "zhangsan"
});

person.name = 'lisi';
alert(person.name);  // lisi

 

当对象属性的  [[Configgurable]]  特性 被修改后 将 无法还原  因为再次修改时会报错   

6.1.2  访问器属性   -----相当于java中的属性私有化  提供get和set方法   示例

var person ={
    name : 'zhangsan',
    _age: 25
};
Object.defineProperty(person,'age',{
    get : function(){
        return this._age;
    },
    set : function(newValue){
        if(newValue>0){
            this._age = newValue;
        }
    }
});
alert(person.age); // 25
person.age = -18;   //由于设定了年龄不能为负数  所以此处的修改无效
alert(person.age); // 25

 

get和set 属性 也可以单独 设置   但若只设置了get  那么意味着该属性为只读 而不能写入   严格模式下  尝试写入操作则会报错

6.1.3   获取对象属性的特性描述  ECMAscript5中  可以设置对象属性的特性,亦可以通过 Object.getOwnPropertyDescriptor()方法来获取这些 特性信息  该方法返回的是一个对象  如果是访问器属性 则这个对象的属性为 configurable、enumerable、get和set  如果是 数据属性 则为 configurable、enumerable、waitable、value

 

6.2创建对象    简单理解成java中自定义对象

//工厂模式创建对象  
function createPerson(name,age){
    var o = new Object();
    o.name = name;
    o.age = age;
    o.sayhi = function(){
        alert('my name is ' + this.name);
    }
    return o;
}
var person1 = createPerson('zhangsan',27);
var person2 = createPerson('lisi',25);
//---------缺点 无法指定对象的类型------------------------

//构造函数模式  为了表示与普通函数的区别  函数首字母大写
function Person(name,age){
    this.name = name;
    this.age = age;
    this.sayhi = function(){
        alert('my name is ' + this.name);
    }
}
var person1 = new Person('zhangsan',27);
var person2 = new Person('lisi',25);

 

 

构造函数与工厂模式创建对象的最大区别 就是 对象的类型鉴别

通过工厂模式创建的对象一定是Object类型      而通过构造函数创建的对象 既属于Object对象类型  也属于自定义对象类型     因为任何对象都是由Object对象继承而来的

构造函数模式创建对象带来的问题     由于每个函数都是一个特殊对象     因此person1和 person2的sayhi方法实际上引用了2个不同的对象  而只完成了相同的事情    就是告诉别人我叫啥    当 存在N个person对象的同时  也存在 N个sayh 对象  造成内存浪费

 

为解决这个问题    -------  原型模式

function PersonF(name,age){
    this.name = name;
    this.age = age;
    this.sayhi = function(){
        alert('my name is ' + this.name);
    };
}
var person1 = new PersonF('zhangsan',27);
var person2 = new PersonF('lisi',25);
alert(person1.sayhi === person2.sayhi);    // false  因为每创建一个新的person对象  然后也创建了一个新的sayhi函数对象  所以他们不相等 

//原型模式  
function PersonP(name,age){
    this.name = name;
    this.age = age;
    
}
PersonP.prototype.sayhi= function(){
    alert('my name is ' + this.name);
}
var person1 = new PersonP('zhangsan',27);
var person2 = new PersonP('lisi',25);
alert(person1.sayhi === person2.sayhi)  // true  原型模式下 所有被创建的person对象 将共享sayhi函数对象,所以这里相等

 

 无论任何时候创建一个新的函数  js解析器默认会创建这个函数的prototype对象 默认情况下 这个prototype只包含 函数的构造器constructor属性  而其他方法 都是从Object继承而来  或者 后期手动添加进去  例如示例中我们给PersonP()这个对象的prototype中添加了sayhi方法

当一个实例对象中添加了与原型中重名方法时   则实际使用的是实例对象中的方法  ------相当于java的子类重写父类方法  示例

//原型模式  
function PersonP(name,age){
    this.name = name;
    this.age = age;
}
PersonP.prototype.sayhi= function(){
    alert('my name is ' + this.name);
}
var person1 = new PersonP('zhangsan',27);
var person2 = new PersonP('lisi',25);
person1.sayhi();     //  my name is zhangsan   来自于原型中的sayhi方法
person1.sayhi = function(){           //修改当前实例对象的sayhi方法
    alert("my age = " + this.age);  
};
person1.sayhi();  // my age = 27     来自于当前实例中的sayhi方法

alert(person1.hasOwnProperty("sayhi"));   // true  此方法是person1对象实例的方法
alert(person2.hasOwnProperty("sayhi"));     // false  此方法来自PersonP 原型中

 

 hasOwnProperty() 检测对象的方法是由原型继承而来还是属于本身实例

原型与in操作符

in 操作符   用于判断   某个属性  是否 属于 指定 对象    示例  alert(('name'  in person1));  // 执行结果为true       in 操作符 不管对象的属性 是实例对象本身的属性  还是由原型中继承而来   都会返回true

for-in  循环  时 返回的都是能通过对象访问的、可以枚举的属性 ,也包括原型中存在的属性    首先遍历的是对象构造函数内的属性  然后再往原型中查找属性  

function PersonP(name,age){
    this.name = name;
    this.age = age;
    this.sayhi= function(){
        alert('my name is ' + this.name);
    }
}
PersonP.prototype.saybay= function(){
    alert('my name is ' + this.name);
}
var person2 = new PersonP('lisi',25);
person2.money = 50;
for(var property in person2){
    console.log(property);   // 结果  name age sayhi   money  saybay 依次出现
}
var propertys = Object.keys(person2);
console.log(propertys);    // 结果  ["name", "age", "sayhi", "money"]

 

 

    Object.keys()  方法 接收一个对象参数  返回该对象上所有可枚举的属性 

利用原型创建对象的简写方式

function PersonP(){
}
PersonP.prototype ={
    constructor : PersonP,  // 此属性表明构造器 为 PersonP 对象的构造器  否则PersonP构造器默认由Object对象继承而来  是属于Object对象的构造器    但是会导致对象的构造器的[[enumerable]]特性 设置为true  
    name : 'zhangsan',
    age : 18,
    sayhi : function(){
        alert('my name is ' + this.name);
    }
}

 

 在ECMAscript 5  js解析引擎下   可以重设 对象的对象构造器为不可枚举 

Object.definedProperty(PersonP.prototype,'constructor',{
    enumerable : false,
    value: PersonP
});

 

原型的动态性  ------js引擎 在原型中查找值的过程是一次搜索  因此 对对象原型的任何修改 都会立即在对象的实例上体现出来

当 对象创建以后  再重写其原型  则会出现 无法调用原有函数的现象   参考示例   原因在于 对象实例原有的原型对象已经被切断与对象联系    因此原型查找时无法获取对象的sayhi方法

function PersonP(){
}
var person1 = new PersonP();
PersonP.prototype = {
    constructor : PersonP,  // 此属性表明构造器 为 PersonP 对象的构造器  否则PersonP 由于是一个空对象  构造器默认由Object对象继承而来
    name : 'zhangsan',
    age : 18,
    sayhi : function(){
        alert('my name is ' + this.name);
    }
}
person1.sayhi();  // error

 6.2.4组合使用构造函数模式和原型模式

  将对象私有的属性放在构造函数中,而通用的方法放在原型中 例如  以person为例子   name、age、sex可能因人而已    但都会有打招呼  做自我介绍的方法sayhi方法 是通用的可以放在原型中

动态原型模式

function Person(name,age,sex){
    this.name = name;
    this.age = age;
    this.sex = sex;
    if(typeof this.sayhi != 'function'){
        Person.prototype.sayhi = function(){
            alert(this.name);
        }
    }
}

 

寄生构造函数模式----- 与工厂模式一致   在一个对象有多个实例的时候   而其中某一个实例需要特殊处理 ,在不影响其他实例属性数据的情况下可以使用此模式来完成操作    有点类似与java中的包装设计模式

function Person(name,age,sex){ 
    var o = new Object();
    o.name = name ;
    o.age = age;
    o.sex = sex;
    o.sayhi =function(){
        alert(this.name);
    }
    return o;
}

稳妥模式----     没有公共属性,而且其他方法也不引用this对象 ,稳妥对象适合在一些安全的环境中使用(这些环境会禁用this和new)   ,感觉像java中常量一样,一旦初始化就不可修改

function Person(name,age,sex){
    var o = new Object();
    o.sayhi =function(){
        alert(name);     //不使用this
    }
    return o;
}

var person = Person('zhangsan',18,'男');  //不使用new操作符创建对象
person.sayhi();   //除此方法 之外 没有任何其他方法可以访问 person对象的name属性

 

 

6.3 继承

一、原型链式继承    其本质 是重写原型对象, 

function SuperType(){
    this.property = true;
    
}
SuperType.prototype.getSuperValue = function(){
    return this.property;
}

function SubType(){
    this.subproperty = false;
}
SubType.prototype = new SuperType();

var sub = new SubType();
alert(sub.getSuperValue());   //true 通过原型继承而来的方法

原型继承注意的问题:  一、共享原型中的属性和方法   二、不能向超类型的构造函数中传递参数

 

   二、借用构造函数

 利用 apply()  和 call() 方法在新创建的对象上执行构造函数    此方式可以传递参数给超类的构造函数     问题点   一、不能继承超类原型的属性和方法,二、方法都在构造函数当中,无法实现函数复用

function SuperType(){
    this.colors = ['red','blue','green'];
}
function SubType(){
    SuperType.call(this);
}

var instance1 = new SubType();
instance1.colors.push('black');
alert(instance1.colors); //red,blue,green,black

 

 

三、组合式继承 ---又叫做伪经典继承 

 将原型链和借用构造函数组合到一块  利用原型链继承超类的原型属性和方法,而借用构造函数来实现对实例属性的继承

 

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.sayAge = function(){
    alert(this.age);
}

var instance1 = new SubType('zhangsan',28);
instance1.colors.push('black');
alert(instance1.colors); //red,blue,green,black  继承自超类的属性
instance1.sayName();  //zhangsan                传递给超类的参数  超类原型中的方法
instance1.sayAge();        //28                    自定义的方法

 

 

四、原型式继承  

先建立一个临时性的构造函数 然后将传入的对象当做这个构造函数的原型,最后返回这个临时类型的新实例   从本质上看 是object对传入对象的一次浅复制

function object(o){
    function F(){}
    F.prototype = o;
    return new F();
}
var  person = {
    name : 'zhangsan',
    friends : ['lisi','wangwu']
}
var anotherPerson = object(person);
anotherPerson.name = 'zhaotiezhu';
anotherPerson.friends.push('zhangxiaohua');  //找了个女朋友赵小花
alert(person.friends);    //lisi,wangwu,zhangxiaohua

ECMA5.0  中 新增了 Object.create() 方法 规范了原型式继承   此方法接收两个参数,第一个参数为创建新对象的原型  另外一个用于修饰新对象中元数据的描述(可选)如下面代码

var  person = {
    name : 'zhangsan',
    friends : ['lisi','wangwu']
}
var person1  = Object.create(person);
person1.name = 'lisi';
alert(person1.name);
var person2  = Object.create(person,{
    name :{
        //writable : false, 
        value : 'wangwu'
    }
});  //第二个参数为新对象的元数据修饰参数  此处由于未设置 person2对象的name属性 为可写的    所以导致 在修改 person2的name属性不成功
alert(person2.name);     //wangwu
person2.name = 'zhaoliu';
alert(person2.name);    //wangwu

 寄生式继承  ----接收一个对象参数     在函数内部对此对象进行增强   然后返回这个函数--------------------------感觉像java中的实现接口????

function createAnother(original){
    var clone = object(original);
    clone.sayhi = function(){    //  
        alert("hi");
    }
    return clone;
}
var  person ={
    name:'zhangsan',
    friends:['lisi','wangwu']
}
var  anotherPerson = createAnother(person);
anotherPerson.sayhi();   //hi

 

 

6.3.6 寄生组合式继承  

    组合式继承的缺陷 ---无论任何条件下都需要调用两次超类型的构造函数   第一次为指定子类原型  第二次为子类构造函数中 借用超类的构造函数      第一次调用已经拥有了超类的属性  第二次调用 则是在子类上重新创建了与原型中相同属性  从而屏蔽了原型的属性

寄生组合式继承  通过借用构造函数来继承超类属性,通过原型链的混成形式来继承方法---------此方式体现在只调用一次超类的构造函数  避免了subtype.prototype上面创建多余的属性  ,原型链还能保持不变,因此能够正常使用 instanceof和isPrototypeOf()方法去确定类型

 

 

function inheritPrototype(subType,superType){
    var prototype = Object(superType.prototype);     //创建对象
    prototype.constructor = subType;                //增强对象
    subType.prototype = prototype;                    //指定对象
}

function SuperType(name){
    this.name = name;
    this.friends = ['xiaohua','xiaoming']
}

SuperType .prototype.sayhi = 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);
}

var person = new  SubType('zhangsan',28);
person.friends.push('xiaohong');  //找了个女朋友 小红
person.sayAge();            //28
person.sayhi();                //zhangsan
alert(person.friends);        //xiaohua,xiaoming,xiaohong

 

 

 小结 : ECMAscript支持面向对象编程,但没有类和接口的概念 。对象可以在代码执行过程中创建和增强,因此具有动态性而非严格的定义的实体对象,

 创建对象   1.工厂模式   定义一个函数 接收指定参数  函数内部创建Object对象  添加传入的参数作为属性,然后返回对象

     2.构造函数模式   可以创建自定义引用类型  可以向内置对象一样使用new 操作符 缺点 每个成功都无法得到复用 包括方法

     3.原型模式,使用构造函数的prototype属性来指定共有的属性和方法   

  对象继承   ECMAscript 主要通过原型链实现继承      原型链的构造是将一个实例赋值给另一个构造函数 ,这样通过构造函数 new处理的对象的原型指向超类的实例    实现 属性和方法复用

     通常使用  组合式寄生继承    避免了多次调用超类的构造函数   以及属性的唯一性

 

 

 

posted @ 2017-11-03 00:16  迷~途  阅读(154)  评论(0编辑  收藏  举报