美文网首页前端之美
新手看JS中的面向对象

新手看JS中的面向对象

作者: E微客 | 来源:发表于2018-08-29 17:24 被阅读104次

    面向对象

      面向对象是一种程序设计的思想,与面向过程不同,它引入了类的概念,将性质相似的一类物体抽象出来,作为设计图一般的存在,以实体的方式描述业务,重心放在了参与事务的对象身上,而不是逐步分离的步骤上。
      面向对象有三个特征:封装、继承、多态,关于继承,可以在读完本文后,去看看我的另一片文章,新手看JS的六张继承方式,这里暂且先不多做解释。

    与面向过程的区别

      这里借用一下百度知道上某位仁兄的解释:
      例如五子棋游戏,面向过程的设计思路就是首先分析问题的步骤:
      1、开始游戏,
      2、黑子先走,
      3、绘制画面,
      4、判断输赢,
      5、轮到白子,
      6、绘制画面,
      7、判断输赢,
      8、返回步骤2,
      9、输出最后结果。

      把上面每个步骤用分别的函数来实现,问题就解决了。

      而面向对象的设计则是从另外的思路来解决问题。整个五子棋可以分为 :
      1、黑白双方,这两方的行为是一模一样的,
      2、棋盘系统,负责绘制画面,
      3、规则系统,负责判定诸如犯规、输赢等。

      第一类对象(玩家对象)负责接受用户输入,并告知第二类对象(棋盘对象)棋子布局的变化,棋盘对象接收到了棋子的i变化就要负责在屏幕上面显示出这种变化,同时利用第三类对象(规则系统)来对棋局进行判定。
      以上,两种模式的区别,由此可见一斑。

    JS中的对象

      JS是解释性的脚本语言,对于类的概念并没有JAVA那般严谨和规范,且拥有自己的特性和方法。
      创建对象的过程,便是画一份设计图,JS一共提供了 7 种创建的方式(来自高程三),包括:
      1.工厂模式
      2.构造函数模式
      3.原型模式
      4.组合使用构造函数模式和原型模式
      5.动态原型模式
      6.寄生构造函数模式
      7.稳妥构造函数模式
      其中使用最广泛、认同度最高的方式是第四种:组合使用构造函数模式和原型模式,下面对每种方式进行粗略的描述。

    创建对象

    1.工厂模式

    function createPerson(name,age){
        var o = new Object();
        o.name = name;
        o.age = age;
        o.sayName = function(){
            alert(this.name)
        };
        return o;
    }
    var person = createPerson("亚当",99);
    

      接收两个参数,在函数内部创建一个对象,然后将参数绑定后再返回,可以实现封装一个类的功能,但缺点是所有的对象的都是Object,无法准确判断它们的类型,比如“人”类是Object,“动物”类也是Object。
      于是出现了构造函数模式。

    2.构造函数模式

    function Person(name,age){    //注意:首字母大写(惯例)
        this.name = name;
        this.age = age;
        this.sayName = function(){
            alert(this.name)
        };
    }
    var person = new Person("亚当",99);
    

      不用return对象,将属性和方法直接给了this对象,这样便可以用alert(person instanceof Person);//ture来检测对象的类型,这意味着将来可以将Person标识为一种特定的类型,更利于类的概念。
      有了“类”的模板,就可以照着模子捏人了,使用构造函数创建对象,必须使用到new操作符,若是当做普通函数来使用,就相当是为全局对象添加了属性,最后会出现window.sayName();//打印出传入的name变量,而使用new来调用构造函数会经历一下四个步骤:
      1.创建一个新对象
      2.将构造函数的作用域赋给新对象
      3.执行构造函数中的代码(为新对象添加属性)
      4.返回这个新对象
      构造函数模式同样有其缺陷,比如上面的例子中,如果创建了两个“人”,就有两个同样的sayName()方法,可以实现同样的功能(打印名字),一个两个还好,如果我们有成百上千个Person实例的话,name就有千百个satName()方法,这在内存中的开销无疑是极大的,既然是同样的功能,那么让它们共同使用一个函数就足够了,因此可以将这个函数摘出来,这样写:

    function Person(name,age){    //注意:首字母大写(惯例)
        this.name = name;
        this.age = age;
        this.sayName = sayName;
    }
    function sayName(){
        alert(this.name);
    }
    

      将内部引用外部命名的函数,而将函数体放在外面,这样指向的就是同一个方法了,只是如此一来sayName这个方法相当于是放在了全局作用域中,但方法本身却只想让Person的对象使用,大炮打蚊子,有点小尴尬,同时类的封装性也遭到了破坏,由此问题,便引出了第三种创建方法——原型模式。

    3.原型模式

      每个构造函数都有一个prototype属性,这个属性是一个指针,指向一个对象,而这个对象的用途,便是容纳同一类下所有实例公有的方法和属性,写法如下。

    function Person(){
    }
    Person.prototype.name = "亚当";
    Person.prototype.age = "99";
    Person.prototype.sayName= function(){
        alert(this.name)
    };
    var person = new Person();
    

      或者写的更简洁一些:

    Person.prototype = {
        name : "亚当",
        age : "99",
        sayName : function(){
            alert(this.name);
        }
    }
    

      好处很明显,同一类下所有对象可以共享属性和方法,当然,缺点一样明显,创建对象的时候无法传入自定义参数,除非设置如person1.name = "夏娃";才会覆盖掉原来的名字,更为严重的是,如果Person的原型中包含了一个数组(引用类型),如果一个对象修改了这个数组,其他对象的数组都会发生变化,因为引用类型的变量指向的是同一块内存地址,这样事情就变得很麻烦了。
      构造函数模式无法设置共享的属性,而原型模式无法自定义属性,那如果将两者优点结合起来,那不是天下无敌了吗!?
      所以,我们有了第四种方式——组合使用构造函数模式和原型模式。

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

      不多说,直接上代码:

    function Person(name,age){
        this.name = name;
        this.age = age;
    }
    Person.prototype = {
        constructor : Person,    //确保实例的构造函数指向Person
        sayName : function(){
            alert(this.name);
        }
    }
    var person = new Person("亚当",99);
    

      可以自定义的属性(包括引用类型)都放在构造函数里,随便修改都不会影响其他实例,而公共的方法则放在原型对象中,避免资源浪费。
      OJBK,万事大吉!这种模式也是目前在ECMAScript中使用最广泛、认同度最高的一种创建自定义的方法。
      至此,基本的几种已经介绍完了,后面三种会简单介绍一下,不想继续深入的小伙伴们可以在这里搬小板凳撤了


    5.动态原型模式

      当我们为对象定义一个方法时,有时可能存在冲突,必要的情况下,我们可以检查某个应该存在的方法是否有效,如果有效,看一眼走人,如果无效,我们再初始化原型。

    function Person(name,age){
        this.name = name;
        this.age = age;
    }
    //方法
    if(typeof this.sayName != "function"){    //如果sayName不是函数
        Person.prototype.sayName= function(){
            alert(this.name)
        }
    };
    

      如上述代码,仅当sayName方法不存在的情况下,才会在原型中添加此方法,而且只会在初次调用构造函数的时候才会执行这条语句,一旦定义后,由于是定义在原型上的方法,所有对象之后都可以直接调用了。
      这种方法的缺陷,同样是不能重写原型,否则会切断现有实例与心源性之间的联系。

    6.寄生构造函数模式

      唔...在前面几种模式都不适用的情况下(应该不会遇到吧...),可以使用寄生构造函数模式创建对象,基本思想是:创建一个函数,其作用仅仅只是封装创建对象的代码,然后再返回新创建的对象。

    function Person(name,age){
        var o = new Object();
        o.name = name;
        o.age = age;
        o.sayName = function(){
            alert(this.name)
        };
        return o;
    }
    var person = new Person("亚当",99);
    

      除了用new操作符以外,其余写法和工厂模式一模一样,一般会在特殊情况下使用它,例如要创建一个数组对象(Array),但在这个对象中要添加新的方法,直接修改Array的构造函数的话,程序里所有的数组都变了,GG,所以可以使用这个模式。代码如下:

    function specialArray(){
        var arr = new Array();
        arr.newFunction = function(){
            alert("我叫数组的新方法")
        }
        balabalabala...  //其他要添加的新方法或操作
        return arr;
    }
    var list = new specialArray();
    list.newFunction();  //我叫数组的新方法 
    

      要注意,返回的对象与构造函数之间没有关系,不能使用instanceof来确定对象类型,这一点与工厂模式相同,因此建议尽可能不要使用这种方法。

    7.稳妥构造函数模式

      稳妥对象,指的是没有公共属性,也不引用this对象,这种模式适合在禁止使用 this 和 new 的环境中,或者在防止数据被其他应用程序(如Mashup程序)改动时使用,除了不使用 this 和 new 以外,和寄生构造函数模式类似,代码如下:

    function Person(name,age){
        var o = new Object();
        //可以在这里定义私有变量和属性
        o.sayName = function(){
            alert(name)
        };
        return o;
    }
    var person = Person("亚当",99);
    person.sayName();    //亚当
    

      除了使用sayName() 方法外,没有其他办法访问 name 的值,方法中定义的私有变量和属性也无法影响传入的 name 值,安全性杠杠的!
      当然,与寄生构造函数模式、工厂模式相同,它也不能使用 instanceof 检测其类型。

    总结

      至此,JS面向对象与其中创建方法基本结束了,如文章有问题,欢迎指正!!!

    相关文章

      网友评论

      本文标题:新手看JS中的面向对象

      本文链接:https://www.haomeiwen.com/subject/brofwftx.html