zoukankan      html  css  js  c++  java
  • JavaScript创建类的方式

    一些写类工具函数或框架的写类方式本质上都是 构造函数+原型。只有理解这一点才能真正明白如何用JavaScript写出面向对象的代码,或者说组织代码的方式使用面向对象方式。当然用JS也可写出函数式的代码,它是多泛型的。

    为了讨论的单一性,暂不考虑类的继承,(私有,受保护)属性或方法。EMCAScript中实际没有类(class)的概念,但可以把它理解为更广义的概念。

    1、构造函数方式

    /**
    * Person类:定义一个人,有个属性name,和一个getName方法
    * @param {String} name
    */
    function Person(name) {
    this.name = name;
    this.getName = function() {
    return this.name;
    }
    }
    这种风格让写过Java的有点亲切在于构造一个对象需要配置一些参数,参数要赋值给类里面this。但与Java的区别是JS用function来代替class,参数也无需定义类型。

    类写好了,我们造几个对象:

    var p1 = new Person("Jack");
    var p2 = new Person("Tom");
    console.log(p1 instanceof Person);//true
    console.log(p2 instanceof Person);//true
    控制台输出也证明了p1,p2的确是类Person的对象实例。

    这种方式的优点是:可以根据参数来构造不同的对象实例 ,缺点是构造时每个实例对象都会生成getName方法版本,造成了内存的浪费 。

    经验丰富的程序员用一个外部函数来代替类方法,达到了每个对象共享同一个方法。改写后的类如下


    //外部函数
    function getName() {
    return this.name;
    }

    function Person(name) {
    this.name = name;
    this.getName = getName;//注意这里
    }
    有人可能觉得代码风格有点差强人意,怎么看也没有Java那么紧凑。但的确可以减少内存的消耗。


    2、原型方式

    /**
    * Person类:定义一个人,有个属性name,和一个getName方法
    */
    function Person(){}
    Person.prototype.name = "jack";
    Person.prototype.getName = function() { return this.name;}
    把类的属性(字段),方法都挂在prototype上。造几个对象测试下


    var p1 = new Person();
    var p2 = new Person();
    console.log(p1.getName());//jack
    console.log(p2.getName());//jack
    可以看出输出的都是jack,原型方式的缺点就是不能通过参数来构造对象实例 (一般每个对象的属性是不相同的) ,优点是所有对象实例都共享getName方法(相对于构造函数方式),没有造成内存浪费 。

    3、构造函数+原型

    取前面两种的优点:
    a、用构造函数来定义类属性(字段)。
    b、用原型方式来定义类的方法。


    /**
    * Person类:定义一个人,有个属性name,和一个getName方法
    * @param {String} name
    */
    function Person(name) {
    this.name = name;
    }
    Person.prototype.getName = function() {
    return this.name;
    }
    这样,即可通过构造函数构造不同name的人,对象实例也都共享getName方法,不会造成内存浪费。
    但似乎这样的代码风格似乎仍然没有Java的类那么紧凑,把属性,构造方法(函数),方法都包在大括号内。

    public class Person {
    //属性(字段)
    String name;
    //构造方法(函数)
    Person(String name) {
    this.name = name;
    }
    //方法
    String getName() {
    return this.name;
    }
    }

    为了让JS代码风格更紧凑,把挂在prototype的方法代码移到function Person的大括号内。


    function Person(name) {
    this.name = name;
    Person.prototype.getName = function() {
    return this.name;
    }
    }
    似乎很神奇,还能这么写啊!验证一下


    var p1 = new Person("Jack");
    var p2 = new Person("Tom");
    console.log(p1.getName());//Jack
    console.log(p2.getName());//Tom
    没有报错,控制台也正确输出了。说明可以这么写,似乎很完美。
    a 、可以通过传参构造对象实例
    b 、对象实例都共享同一份方法不造成内存浪费
    c 、代码风格也比较紧凑

    但每次new一个对象的时候都会执行


    Person.prototype.getName = function() {
    return this.name;
    }
    造成了不必要的重复的运算。因为getName方法挂在prototype上只需执行一次即可。只需稍微改造下


    function Person(name) {
    this.name = name;

    if(Person._init==undefined) {
    alert("我只执行一次!");
    Person.prototype.getName = function() {
    return this.name;
    }
    Person._init = 1;
    }
    }
    new两个对象,

    var p1 = new Person("Andy");//第一次new会弹出'我只执行一次!'
    var p2 = new Person("Lily");//以后new的对象不会再执行了

    构造函数 + 原型 直接组装一个类;同一构造函数将组装出同一类型


    /**
    * $class 写类工具函数之一
    * @param {Function} constructor
    * @param {Object} prototype
    */
    function $class(constructor,prototype) {
    var c = constructor || function(){};
    var p = prototype || {};
    c.prototype = p;
    return c;
    }
    用构造函数来生成类实例的属性(字段),原型对象用来生成类实例的方法。


    //构造函数
    function Person(name) {
    this.name = name;
    }
    //原型对象
    var proto = {
    getName : function(){return this.name},
    setName : function(name){this.name = name;}
    }
    //组装
    var Man = $class(Person,proto);
    var Woman = $class(Person,proto);
    这时候已经得到了两个类Man,Woman。并且是同一个类型的。测试如下:


    console.log(Man == Woman); //true
    console.log(Man.prototype == Woman.prototype); //true
    创建对象看看


    var man = new Man("Andy");
    var woman = new Woman("Lily");

    console.log(man instanceof Man); //true
    console.log(woman instanceof Woman); //true
    console.log(man instanceof Person); //true
    console.log(woman instanceof Person); //true
    ok,一切如我们所期望。但是有个问题,下面代码的结果输出false

    console.log(man.constructor == Person); //false
    这让人不悦:从以上的代码看出man的确是通过Man类new出来的 var man = new Man("Andy"),那么对象实例man的构造器应该指向Man,但为何事与愿违呢?

    原因就在于$class中重写了Person的原型:c.prototype = p;
    好了,我们把$class稍微改写下,将方法都挂在构造器的原型上(而不是重写构造器的原型),如下:


    function $class(constructor,prototype) {
    var c = constructor || function(){};
    var p = prototype || {};
    // c.prototype = p;
    for(var atr in p){
    c.prototype[atr] = p[atr];
    }
    return c;
    }


    构造函数+原型 组装一个类;同一构造函数可以定义出多个类型


    /**
    * $class 写类工具函数之二
    * @param {Function} constructor
    * @param {Object} prototype
    */
    function $class(constructor,prototype) {
    var c = constructor || function(){};
    var p = prototype || {};
    return function() {
    for(var atr in p) {
    arguments.callee.prototype[atr] = p[atr];
    }
    c.apply(this,arguments);
    }
    }
    与上一篇方式类似,仍然用构造函数,原型对象,定义两个类。

    //构造函数
    function Person(name) {
    this.name = name;
    }
    //原型对象
    var proto = {
    getName : function(){return this.name},
    setName : function(name){this.name = name;}
    }
    //写两个类
    var Man = $class(Person,proto);
    var Woman = $class(Person,proto);
    与上一篇不同的是,虽然Man和Woman都是用Person,proto组装的。但Man却不等于Woman。即同一个构造函数(Person)可以定义出不同的类。

    // 同一个构造函数(Person)定义不同的类
    console.log(Man == Woman); //false


    工具函数如下

    /**
    * $class 写类工具函数之三
    * @param {String} className
    * @param {Function} superClass
    * @param {Function} classImp
    */
    function $class(className, superClass, classImp){
    if (superClass === "") superClass = Object;
    function clazz(){
    if(typeof this.init == "function"){
    this.init.apply(this, arguments);
    }
    }
    var p = clazz.prototype = new superClass();
    var _super = superClass.prototype;
    window[className] = clazz;
    classImp.apply(p, [_super]);
    }
    定义一个Person类

    $class('Person', '', function(){
    // 构造函数
    this.init = function(name){
    this.name = name;
    };
    // 方法体
    this.getName = function(){
    return this.name;
    };
    this.setName = function(name){
    this.name = name;
    };
    });
    创建对象看看

    var p = new Person('Jack');
    console.log(p);
    console.log(p.constructor == Person); // false
    使用该工具函数写类需注意,this.init方法必不可少。使用过Prototype库的同学会知道Class.create后的initialize方法也是必不可少的。

    因为没考虑继承,第二个参数superClass使用空字符串,即默认继承于Object。

    从输出为false可看到,这个工具类没有去维护constructor属性。 设计的每一种写类方式都是有取舍的,这完全取决于设计者的意图。

    我们看看各个JS库的写类方式

    1,Prototype的写类方式

    Prototype中使用Class.create方法,如下


    //类名Person
    var Person = Class.create();

    //通过原型重写来定义Person
    Person.prototype = {
    initialize : function(name) {
    this.name = name;
    },
    getName : function() {
    return this.name;
    },
    setName : function(name) {
    this.name = name;
    }
    }

    //创建对象
    var p = new Person("jack");
    console.log(p.constructor == Person);//false
    initialize完成对象的初始化(相当于构造函数,必不可少),方法依次往下写即可。

    有个问题,但是p.constructor == Person却为false,这正是Prototype库一个小缺陷。原因是重写了Person的原型。为了使constructor能指向正确的构造器,只需在原型重写时维护好constructor属性即可。


    Person.prototype = {
    constructor : Person, //注意这句将修复constructor属性
    initialize : function(name) {
    this.name = name;
    },
    getName : function() {
    return this.name;
    },
    setName : function(name) {
    this.name = name;
    }
    }


    2,Dojo的写类方式

    dojo中用dojo.declare方法来定义一个类。dojo.declare有三个参数
    参数1:类名className
    参数2:继承的类superclass
    参数3:构造器,方法props

    单纯的定义一个类实际只需传第一,三两个参数。因为这里只讨论如何定义一个类,不讨论继承。


    //定义类名
    var className = "Person";
    //定义构造器及方法
    var proto = {
    constructor : function(name){this.name=name;},
    getName : function(){ return this.name;},
    setName : function(name){ this.name = name;}
    }

    //定义类Person
    dojo.declare(className,null,proto);

    //创建一个对象
    var p = new Person("tom");
    console.log(p.getName());//tom
    p.setName("jack");
    console.log(p.getName());//jack

    //测试instanceof及p.constructor是否正确指向了Person
    console.log(p instanceof Person);//true
    console.log(p.constructor === Person);//true

    3,Ext的写类方式

    Ext中用Ext.extend来定义一个类(当然它更多用来扩展一个类),Ext整个框架各种控件如Panel,MessageBox等都是用Ext.extend方法来扩展。这里仅仅用它来定义一个最简单的类。

    这里只需传两个参数即可,第一个参数是根类Object,第二个是原型。比较特殊的是,如果单纯的定义一个类,那么第一个参数永远传Object即可。

    /**
    * Person类
    * @param {Object} name
    */
    var Person = Ext.extend(Object,{
    constructor : function(name) {this.name = name;},
    setName : function(name) {this.name = name;},
    getName : function() {return this.name;}
    });

    //创建一个对象
    var p = new Person("Lily");
    console.log(p.getName());//Lily
    p.setName("Andy");
    console.log(p.getName());//Andy

    //测试instanceof及p.constructor是否正确指向了Person
    console.log(p instanceof Person);//true
    console.log(p.constructor == Person);//true

    4,YUI的写类方式

    //定义包名
    YAHOO.namespace("test");

    //定义类
    YAHOO.test.Person = function(name) {
    this.name = name;
    }
    YAHOO.test.Person.prototype.setName = function(name){ this.name = name;}
    YAHOO.test.Person.prototype.getName = function(){ return this.name;}


    //创建一个对象
    var p = new YAHOO.test.Person("jack");

    console.log(p.getName());//jack
    p.setName('tom');
    console.log(p.getName());//tom

    //测试instanceof及p.constructor是否正确指向了YAHOO.test.Person
    console.log(p instanceof YAHOO.test.Person);
    console.log(p.constructor == YAHOO.test.Person);
    可以看出除了多了包名,与原始的 构造函数+原型 方式 并无区别。

    5,Mootools的写类方式

    mootools是被设计成非常紧凑的,模块化的,面向对象的JS库。mootools中写类用Class类。导入后我们写类时只需要用Class就行了。

    /**
    * Person类
    * @param {Object} name
    */
    var Person = new Class({
    initialize: function(name){
    this.name = name;
    },
    setName : function(name) {
    this.name = name;
    },
    getName : function() {
    return this.name;
    }
    })

    //new一个对象
    var p = new Person("jack");

    //测试set,get方法
    console.log(p.getName());//jac
    p.setName('andy');
    console.log(p.getName());//andy

    //测试instanceof及p.constructor是否正确指向了Person
    console.log(p instanceof Person); //true
    console.log(p.constructor == Person); //true

  • 相关阅读:
    数据压缩算法---LZ77算法 的分析与实现
    数据压缩算法---霍夫曼编码的分析与实现
    数据压缩的重要组成部分---位操作
    排序算法的C语言实现(上 比较类排序:插入排序、快速排序与归并排序)
    广度优先(bfs)和深度优先搜索(dfs)的应用实例
    数据结构 图的定义和搜索方法(清晰图解)
    数据结构-堆 接口定义与实现分析(详细注释与图解)
    数据结构-堆的定义描述
    数据结构 链式哈希表(Hash Table)的接口定义与实现分析(完整代码)
    SQLServer常用快捷键汇总
  • 原文地址:https://www.cnblogs.com/axl234/p/3765337.html
Copyright © 2011-2022 走看看