30-面向对象编程

第三十章.面向对象编程

1.创建对象

  • 创建单个对象

    var afei = {
        name : "皮卡",
        age : 18,
        university : "外经贸",
        sayName : function(){
            alert(this.name);
        }
    }
    
  • 工厂模式

    假设我们要创建多个统一类别的对象,比如:

    var pika = {
        name : "皮卡",
        age : 18,
        university : "外经贸",
        sayName : function(){
            alert(this.name);
        }
    };
    var ayuan = {
        name : "阿远",
        age : 19,
        university : "外经贸",
        sayName : function(){
            alert(this.name);
        }
    };
    var weige = {
        name : "伟哥",
        age : 20,
        university : "外经贸",
       sayName : function(){
            alert(this.name);
        }
    };
    

    此时一个一个定义有点麻烦,我们可以封装一下:

    /*
    通过这个方法我们可以创建多个具有相同属性的对象,由于这个函数内部类似于 原料->加工->产出 的形式,所以我们称之为工厂模式。
    */
    function createPerson(name,age){
        var obj = {};
        
        obj.name = name;
        obj.age = age;
        obj.university = "外经贸";
        obj.sayName = function(){
            alert(this.name);
        }
        
        return obj;
    }
    
    var afei = createPerson("皮卡",18);
    var yanxin = createPerson("阿远",19);
    var yinshi = createPerson("伟哥",20);
    
  • 构造函数模式

    在我们执行函数的时候,如果在函数前加上关键词new,那么会对函数产生以下影响:

    函数内部默认生成一个空对象

    函数默认返回这个对象

    函数内部 this 指向这个对象

    从而我们可以将上述代码写为:

    /*
    此时这个函数单独执行没有太大意义(相当于给window加属性),只有在new执行的时候才能发挥其作用,我们称这个函数为 构造函数。ES5里面没有 类 的概念,我可以将构造函数认为是 类。
    按照惯例,构造函数第一个字母大写(但不是必须),以便于区分。
    */
    function Person(name,age){
        this.name = name;
        this.age = age;
        this.university = "外经贸";
        this.sayName = function(){
            alert(this.name);
        }
    }
    
    /*
    通过new得到对象的过程,我们称之为 实例化 。
    而得到的每个对象我们称之为实例,比如:对象afei是构造函数Person的一个实例。
    */
    var afei = new Person("皮卡",18);
    var yanxin = new Person("阿远",19);
    var yinshi = new Person("伟哥",20);
    
  • 原型模式

    每个函数都拥有 prototype (原型)属性,该属性是一个对象,里面所存储的各种属性和方法是可以直接被该函数的实例使用的。

    比如上述例子中,所有对象都拥有university和sayName属性,而这两个属性所有实例都是相同的,所以没有必要每个实例都创建一份。(检测 pika.sayName === ayuan.sayName)

    我们可以将它们放置到 原型 中,让所有实例都使用同一个方法,这样就可以节省资源。

    //实例所需的不同属性放置于构造函数
    function Person(name,age){
        this.name = name;
        this.age = age;
    }
    //实例所需的相同属性放置于 prototype
    Person.prototype.company = "潭州教育";
    Person.prototype.sayName = function(){
        alert(this.name);
    };
    
    var afei = new Person("皮卡",18);
    var yanxin = new Person("阿远",19);
    var yinshi = new Person("伟哥",20);
    

    (再次检测 pika.sayName === ayuan.sayName)

    (标准浏览器为每个对象添加了 __proto__ 属性,该属性指向其对应的原型)

2.包装对象

基础数据类型是具有 . 操作的(比如 字符串.charAt()),很显然,基础数据类型本身不可能拥有属性,所以他们使用的都是其原型上的方法。而原型是针对于 object 类型的数据而言的,基础数据类型是如果使用到原型上的各种方法的呢?答案是 包装对象

每个基础数据类型在执行 . 操作的时候,js会产生一个其对应的包装对象以供使用,比如 "123".charAt(1),代码运行时,内部其实是 new String("123").charAt(1),这里创建的 new String("123")就是"123"的包装对象。.charAt()这个方法,其实是定义在该包装对象对应的原型上的,也就是定义在String.prototype上的,所以我们能对基础数据类型进行 . 操作。

需要注意的是,每次 . 操作都会产生一个新的包装对象,也就是说每次 . 操作产生的包装对应并不是同一个指向,这也就是为什么基础数据类型能存值但是没法取到值的原因。

3.原型链

实例可以使用原型上的各种属性,而原型本身也是一个对象,它是Object构造函数的实例,所以在寻找属性的时候,如果自身没找到,会去原型里面找,而如果原型里面没找到,会继续去Object.prototype里面找,这构成了一个最基础的原型链。

如果将实例的原型指向另外一个构造函数的实例,那么就可以将上述的链型结构变长。从而导致:当使用某个对象的属性的时候,会先从对象自身查找,如果没有找到,会去该对象对应的原型对象上查找,如果还没有找到,会继续去原型对象对应的原型对象查找,直到Object.prototype为止,这就构成了一个实例与原型之间的链条,我们称之为原型链。

(代码演示、画图演示……)

4.继承

在一个类的基础上,想要扩展一些新功能,但是又不影响之前的实例,就需要用到继承。也就是,子类拥有父类的所有属性和方法,子类的扩展不会影响父类。

  • 构造函数内部的继承

    构造函数内部定义了实例的私有属性,子类需要拥有父类的这些定义,并且还能扩展新的定义:

    //父类 Person
    function Person(name,age){
        this.name = name;
        this.age = age;
    }
    
    //子类 Teacher
    function Teacher(name,age,id){
        //继承父类的所有私有属性定义
        Person.call(this,name,age);
        //添加子类新的私有属性定义
        this.id = id;
    }
    
    //子类 Student
    function Student(name,age,regTime){
        //继承父类的所有私有属性定义
        Person.call(this,name,age);
        //添加子类新的私有属性定义
        this.regTime = regTime;
    }
    
    var p = new Person("张三",30);
    var t = new Teacher("Jam",18,"201801624");
    var s = new Student("皮卡",20,new Date(2020,6));
    

    我们可以看到在父类 Person 的基础上,继承出了两个新的子类 Teacher 和 Student,新子类拥有各自独特的私有属性定义,并且也保留了父级的私有属性定义,同时,子类的新增不会影响父类。

  • 原型的继承

    子类需要继承父类原型上的所有定义,同样的,可以新增定义,但是不会影响父级。

    //用于继承原型的辅助函数
    function extend(CLASS){
        function Fn(){}
        Fn.prototype = CLASS.prototype;
        return new Fn();
    }
    
    
    //父类 Person
    function Person(name,age){
        this.name = name;
        this.age = age;
    }
    Person.prototype.showInfo = function(){
        console.log("姓名:"+this.name+",年龄"+this.age);
    };
    
    //子类 Teacher
    function Teacher(name,age,id){
        Person.call(this,name,age);
        this.id = id;
    }
    //继承父类的原型属性
    Teacher.prototype = extend(Person);
    //新增自己的原型属性
    Teacher.prototype.showID = function(){
        console.log("ID:"+this.id);
    }
    
    //其他子类同上
    

结合这两种方式,我们就可以将一个父类从私有属性和原型属性两个方便进行继承,达到我们的目的。需要注意的是,每个类的原型上有一个默认属性 constructor,它的值默认是指向构造函数本身,而在我们继承的时候,这个属性的指向会被我们改变,所以在继承之后,最好要将该属性的指向恢复一下 构造函数.prototype.constuctor = 构造函数

以上这些是面向对象编程的基础知识,我们可以将以前的面向过程的思维转换为面向对象的思维,也就说将各个功能抽象为对象,然后再在其基础上定义各种方法,将我们关注的侧重点放到一个一个的类上,而不是一步一步的流程。这样的方式在解决某些问题的时候能节省大量的代码,并且更便于维护。这也就是JavaScript中面向对象编程的两个重大特点 封装性继承性

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值