• 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处理的对象的原型指向超类的实例    实现 属性和方法复用

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

     

  • 相关阅读:
    case when then 中判断null的方法
    在SELECT的时候,加入一列固定值
    拿到iframe页面里面的变量及元素的方法
    datatables 多一列报错Cannot read property 'sWidth' of undefined(…)/少一列报错Cannot read property 'style' of undefined(…)
    MySQL 显示表字段及注释等信息
    MYSQL escape用法--转义
    MyBatis insert操作返回主键
    Java关键字final、static使用总结
    数据库往表中插入数据报错
    洛谷 题解 P1287 【盒子与球】
  • 原文地址:https://www.cnblogs.com/shenwenbo/p/7690007.html
Copyright © 2020-2023  润新知