一个简单的、面向对象的javascript基础框架

  如果以后公司再能让我独立做一套新的完整系统,那么我肯定会为这个系统再写一个前端框架,那么我到底该如何写这个框架呢?

  在我以前的博客里我给大家展示了一个我自己写的框架,由于当时时间很紧张,做之前几乎没有完整的思考过我到底该如何去写这个框架,所以事后对于这个框架我有很多遗憾之处,当我重构过一次代码后我就没再做过任何重构操作的工作,因为我根本不想再去给它修修补补了,之所以有这个想法,就是我对我写的那个框架的基础架构不满意。

  为什么不满意这个基础架构了?我们先来看看我当时封装框架的方式:

(function(window,document){
    var o1 ={},o2={};
    var outerObj = {
        o1:o1,
        o2:o2
    }
    window.outerObj = outerObj;
})(window,document,undefined)

  这段代码是我参考jQuery的编写方式,现在回头看看,这个编写方式无非就是用匿名函数把内部代码保护起来,除此之外真的没有用到什么javascript语言其他特性。不过当时这么写还是有我自己的考虑的,主要是从下面这三个角度思考:

  1. 要提高网站性能就得尽量减少页面加载时候的请求个数,因此外部的javascript文件越少越好;
  2. 单个请求的大小也要尽量最少,因此页面里javascript代码要尽量精简;
  3. 要向jQuery学习,在页面里编写的代码应该有固定的套路,只要是两个页面都会使用javascript代码都应该迁移到自己编写的javascript库里,其次,页面的javascript开发里容易出错的代码都应该由javascript库来完成

  因此我开发javascript库的时候将大量代码都迁移到一个外部文件里,这些代码有的是基础性的代码,例如一些工具类,grid的构建代码,遮罩功能等,还有些代码是业务代码例如:加密解密等,这些都集中在了一个外部文件,由于自己原框架的结构只是将这些代码通过匿名函数包装起来,而没有让基础性代码和业务代码解耦的方式,所以当时编写的javascript库就是一个大杂烩,很多东西交织在一起,这使得自己维护代码的时候不是很科学了,经常变成硬编码。

  总而言之,我之前的javascript库的基础结构没有很好的扩展性和伸缩性,它没有将不同类型代码隔离出来的能力,所以我需要一个新的javascript基础框架,这样我以后再去开发一个javascript库,这个基础架构使得库更加的健壮。

  我最近做了一些前端的项目,这个项目里单个页面都是非常复杂的,功能非常多,单个页面的javascript业务代码少则几百行,多则上千行,因此我们前端应用里有一个很大的公共库,但是到了每个页面又得单独写一个外部javascript文件做相应的业务处理,下面是这个项目javascript代码书写的基本结构:

  var opts = {
        version:"1.0.0",
        name:"sharpxiajun"
    };
    (function(opts){
        function Clazz(){
            return this.init(arguments);
        }
        Clazz.fn = Clazz.prototype = {
            init:function(opts){
                this.settings = opts;
                return this;
            },
            testInit:function(){
                // 直接打印对象
                console.log(this.settings);
                // 遍历对象输出
                for (var o in this.settings){
                    console.log(this.settings[o]);
                }
                return this;
            }
        }
        window.$ = new Clazz(opts)
    })(opts);
    // 测试
    $.testInit();

  

  运行结果如下所示:

  不管我们使用外部公共类库,还是每个页面对应的javascript外部文件,都采用这个结构,仔细分析下这个代码,它和我之前写的javascript库的结构并没有高明之处,只不过我之前的javascript库是直接使用javascript的对象,而这个结构无非换成了面向对象的写法,而这个面向对象其实是不好使用继承的面向对象,是个孤立的对象,因此实际开发里我们要区别不同的功能模块,只得新建多个不同的功能对象,最后整个应用里会有N多个相互独立的功能对象,这其实和我原来库的写法有着同样的不易扩展的问题。而且这个代码有个让我开发时候很不习惯的问题,就是在具体页面开发里,我必须先要构建一个参数对象,并把对象传到外部文件的接口里,如果你没有提前构造参数对象,那么外部javascript代码就会出错。

  不过这两种写法的差异让我对编写javascript库有了新的想法,这个想法具体内容如下:

  一个web前端应用里,排除一些公用的库例如我必用的jQuery,可能还有时间控件的库(我以后做web前端估计都会尽量让我的外部库最少,像jQuery,requireJs,seajs这样的库我基本是毫无保留的使用,像什么eaysui,jqgrid,extjs这样的库我会想尽办法舍弃),其他的javascript代码都是程序员要自己编写的,程序员自己写的代码不管它是业务代码还是通用代码都应该是一个整体,这个整体的表现就是它们都可以用一个对象进行输出,看看我上面讲的两种写库的基础结构,它们的共同问题要么就是通用代码和业务代码交织,耦合,要么就是相互独立,关系僵硬。

  此外,生产上能把javascript合并成少量文件也是非常重要的,上面的第二种库的写法(把页面的业务代码抽取到外部文件)目的之一就是让文件合并比较容易,但是这种堆砌式的合并文件总让我感觉有点不是很舒服的味道。我觉得对于复杂页面单独一个javascript外部文件有很多好处,这个做法还是不能舍弃的,但是这个外部文件最好和超大的公共库有一个逻辑关系,这个关系最好像jQuery原始库和它的插件之间的关系,如果有这样的关系我们再合并文件,这个做法感觉就会好多了。

  最后,我自己写库的做法没有使用面向对象编程,使用的是javascript对象本身的特点,这个如果换到面向对象编程里就是类的静态变量方案,而另外一种写法则是实例化对象的实现方案,我觉得这两种写法都有可取之处,也有不足地方,最好我们设计的库应该兼容这两个机制。

  下面就是我根据上面思考写的新的基础javascript基础框架模型,代码如下:

(function(window,document){
    function Clazz(){
        return this.init(arguments);
    }
    Clazz.fn = Clazz.prototype = {
        init:function(opts){
            this.settings = opts;
            return this;
        },
        testInit:function(){
            // 直接打印对象
            console.log(this.settings);
            // 遍历对象输出
            for (var o in this.settings){
                console.log(this.settings[o]);
            }
            return this;
        }
    };
    Clazz.addStaticMethod = function(nmSpace,obj,ftn){
        if (!Clazz[nmSpace]){Clazz[nmSpace] = {}}
        for (var i in obj){
            Clazz[nmSpace][i] = obj[i];
        }
        if (ftn) {ftn()}
    }
    Clazz.addObjectMethod =  function(nmSpace,obj,ftn){
        if (!Clazz.fn[nmSpace]){Clazz.fn[nmSpace] = {}}
        for (var i in obj){
            Clazz.fn[nmSpace][i] = obj[i];
        }
        if (ftn) {ftn()}
    }
    window.Clazz = Clazz;
})(window,document,undefined)
var opts = {
    version:"1.0.0",
    name:"sharpxiajun"
};
Clazz.addStaticMethod("myStatic",{
    sClz:"static",
    staticFtn:function(){
        console.log(Clazz["myStatic"].sClz);
    }
},function(){
    console.log("Add Static Method End!!!!!!!");
})
Clazz.addObjectMethod("myFirst",{
    sParam:"sharp",
    ftn01:function(s){
        this.sParam = s;
        return this;
    },
    ftn02:function(){
        console.log("sParam:" + this.sParam);
        return this;
    }
},function(){
    console.log("Add Object Method End!!!!!!!");
})
var $ = new Clazz(opts);
// 测试一
$.testInit();
// 测试二
console.log($.myFirst.sParam);
$.myFirst.ftn01("My God!!!").ftn02();
// 测试三
console.log(Clazz.myStatic.sClz);
Clazz.myStatic.staticFtn();

  页面运行结果如下所示:

  这个代码里我在匿名函数里返回的是类(javascript里其实没有类的概念,但是有构造函数,所以javascript里的构造函数承担了类的作用,所以这里提javascript里的类应该是没问题的),而不是已经实例化好的对象。这样返回的东西既可以有静态的方法和属性又有属于对象的方法和属性了。

  该结构里有一个Clazz.addStaticMethod方法,它的作用是给定义的类添加静态的方法,这个方法我设计了三个参数,第一个参数是个字符串,业务函数就是静态方法的作用域,看下面实例代码:

  console.log(Clazz.myStatic.sClz);

  Clazz.myStatic.staticFtn();

  这样就等于给静态变量一个保护,有人会问如果我们不传第一个参数怎么办?认真的童鞋注意,现在我给出的代码只是想表达我的想法,到了生产实现时候该方法会更加丰满点,那时如果用户不传作用域字段,那么添加的静态方法和属性就是直接属于类本身的。

  第二个参数就是具体要添加的静态属性和静态方法,这里我用的是对象类型。

  第三个参数是个回调函数,当静态方式添加成功后调用,当然用户也可以不传,加个回调函数参数,是我觉得在设计javascript方法时候都应该给一个回调函数,这就是在运用事件驱动编程的思想,这其实为自己留有余地,这么做常常会在你意想不到的时候发挥重要作用。

  方法Clazz.addObjectMethod是给对象添加方法和属性的,这些方法和属性石赋予给原型对象的,参数类型和addStaticMethod相同,这里就不在累述了。

  用户在使用addObjectMethod方法时候要注意this指针的运用,在每个要添加到对象的方法最后加上一个return this,这么做好处就是每个对象的返回值都是对象本身,这样我们就可以让我们的库拥有和jQuery一样的连缀写法,例如下面的代码:

  console.log($.myFirst.sParam);

  $.myFirst.ftn01("My God!!!").ftn02();

  在生产开发里,我们可以把公共的javascript代码直接写到库里,页面里的业务代码则直接通过扩展返回类的静态方法和属性以及对象方法和属性进行扩充。

  上面这个结构基本达到我的需要了,如果我以后用javascriptMVC思想开发前端我估计这个基本结构已经够用,当然还要做些代码健壮性的处理,如果是传统前端页面开发估计还会有一定修改,传统网站的页面开发都是服务端和html混搭的,例如jsp,velocity文件,因此有很多重要数据都是服务端变量生成的,我们时常要把这些变量作为参数传给外部javascript文件,每个页面里的参数是不一样的,所以必须有个对象专门接收这个对象。这个好做,因此这里就不累述了。

  上面这个结构使用了面向对象继承机制,原始库是父对象,而业务javascript文件则是子类了,不过这个子类是用命名空间来区分的。

  不过有时候我们可能想冒险替换整个父对象的内容,这个需求我想在平时开发里并不常见,不过我还是写了一个这样的方法,下面是我改进的代码,具体如下:

(function(window,document){
    function Clazz(){
        return this._init(arguments);
    }
    Clazz.prototype = {
        _init:function(opts){
            this.settings = opts;
            return this;
        },
        testInit:function(){
            // 直接打印对象
            console.log(this.settings);
            // 遍历对象输出
            for (var o in this.settings){
                console.log(this.settings[o]);
            }
            return this;
        }
    };
    Clazz.addStaticMethod = function(nmSpace,obj,ftn){
        if (!Clazz[nmSpace]){Clazz[nmSpace] = {}}
        if (Object.prototype.toString.apply(obj) == "[object Object]"){
            for (var i in obj){
                Clazz[nmSpace][i] = obj[i];
            }
            window.Clazz = Clazz;
        }
        if (ftn) {ftn()}
    }
    Clazz.addObjectMethod =  function(nmSpace,obj,ftn){
        if (!Clazz.prototype[nmSpace]){Clazz.prototype[nmSpace] = {}}
        if (Object.prototype.toString.apply(obj) == "[object Object]"){
            for (var i in obj){
                Clazz.prototype[nmSpace][i] = obj[i];
            }
            window.Clazz = Clazz;
        }
        if (ftn) {ftn()}
    }
    /*Clazz.newStaticMethod(){//todo..........}*/
    Clazz.newObjectMethod = function(obj,ftn){
        if (Object.prototype.toString.apply(obj) == "[object Object]"){
            var tmpInit = Clazz.prototype._init;
            Clazz.prototype = obj;
            Clazz.prototype._init = tmpInit;
            window.Clazz = Clazz;
        }
        if (ftn) {ftn()}
    }
    window.Clazz = Clazz;
})(window,document,undefined)
var opts = {
    version:"1.0.0",
    name:"sharpxiajun"
};
Clazz.addStaticMethod("myStatic",{
    sClz:"static",
    staticFtn:function(){
        console.log(Clazz["myStatic"].sClz);
    }
},function(){
    console.log("Add Static Method End!!!!!!!");
})
Clazz.myStatic.staticFtn();
Clazz.newObjectMethod({
    newver:"1.0.3",
    testNewFtn:function(){
        console.log(this.newver);
        return this;
    }
},function(){
    console.log("New Create Prototype Object End !!!!!!");
});
Clazz.addObjectMethod("myFirst",{
    sParam:"sharp",
    ftn01:function(s){
        this.sParam = s;
        return this;
    },
    ftn02:function(){
        console.log("sParam:" + this.sParam);
        return this;
    }
},function(){
    console.log("Add Object Method End!!!!!!!");
})
var $ = new Clazz();
console.log("================================");
console.log($.newver);
$.testNewFtn().myFirst.ftn01("XXXX").ftn02();
console.log("================================");

  运行结果如下所示:

  上面的代码做了一定优化,代码里我只给出了替换原型的方法,没有提供替换静态的方法,这是因为替换原型还是有点意义的,替换静态变量其实就是替换整个类了,如果这么干,我这个结构还有啥意义哦。

  文章写毕,今年写文章,文章里代码都很少,很多童鞋不习惯,今天补上一篇代码比较多的文章了。

时间: 2024-10-13 20:37:42

一个简单的、面向对象的javascript基础框架的相关文章

JavaScript基础--面向对象三大特性(八):继承封装多态

一.构造函数基本用法:function 类名(参数列表){属性=参数值} 1 function Person(name,age){ 2 this.name = name; 3 this.age = age; 4 } 5 6 //创建Person对象的时候,可以直接给名字和年龄 7 var p1 = new Person('abc',80); 8 window.alert(p1.name); 9 var p2 = new Person('hello',9); 10 window.alert(p2.

JavaScript 面向对象开发知识基础总结

JavaScript 面向对象开发知识基础总结 最近看了两本书,书中有些内容对自己还是很新的,有些内容是之前自己理解不够深的,所以拿出来总结一下,这两本书的名字如下: JavaScript 面向对象精要 JavaScript 启示录 如果对于 JavaScript 面向对象编程理解不够深的话,第一本书还是强烈推荐的.第二本书比较适合初中级的开发者阅读.对各种知识点都有代码示例.内容中规中矩. 1.JavaScript 中的变量类型和类型检测 C#和Java等编程语言用栈存储原始类型,用堆存储引用

[python] 理解metaclass并实现一个简单ORM框架

metaclass 除了使用type()动态创建类以外,要控制类的创建行为,还可以使用metaclass. metaclass,直译为元类,简单的解释就是: 当我们定义了类以后,就可以根据这个类创建出实例,所以:先定义类,然后创建实例. 但是如果我们想创建出类呢?那就必须根据metaclass创建出类,所以:先定义metaclass,然后创建类. 连接起来就是:先定义metaclass,就可以创建类,最后创建实例. 所以,metaclass允许你创建类或者修改类.换句话说,你可以把类看成是met

JavaScript 基础入门11 - 运动框架的封装

目录 JavaScript 运动原理 运动基础 简单运动的封装 淡入淡出 不同属性的设置 多属性值同时运动 运动回调,链式运动 缓冲运动 加入缓冲的运动框架 JavaScript 运动原理 运动基础 在JavaScript中,如何让一个页面元素动起来? 首先,我们需要了解的是,在JavaScript中如何让一个页面元素动起来. 我们先来实现一个简单的功能,当我们点击按钮之后,让一个元素动起来.并且到达500的边界之后立刻停止下来. <!DOCTYPE html> <html> &l

node.js基于express框架搭建一个简单的注册登录Web功能

这个小应用使用到了node.js  bootstrap  express  以及数据库的操作 :使用mongoose对象模型来操作 mongodb 如果没了解过的可以先去基本了解一下相关概念~ 首先注明一下版本,因为express因为版本的不同使用的方式也不同,我这算是目前最新的了吧 还没有装express的可以移步到这里 看看express框架的获取安装 1.简单地项目初始化 进入你的nodejs安装路径下边,如图,然后执行命令  express -e test  (这里把项目名设置为test

《Entity Framework 6 Recipes》翻译系列 (3) -----第二章 实体数据建模基础之创建一个简单的模型 (转)

第二章 实体数据建模基础 很有可能,你才开始探索实体框架,你可能会问“我们怎么开始?”,如果你真是这样的话,那么本章就是一个很好的开始.如果不是,你已经建模,并在实体分裂和继承方面感觉良好,那么你可以跳过本章. 本章将带你漫游使用实体框架建模的基本实例,建模是实体框架的核心特性,同时也是区别实体框架和微软早期的数据访问平台的特性.一旦建好模,你就可以面向模型编写代码,而不用面向关系数据库中的行和列. 本章以创建一个简单概念模型的实例开始,然后让实体框架创建底层的数据库,剩下的实例,将向你展示,如

从零构建一个简单的 Python Web框架

为什么你想要自己构建一个 web 框架呢?我想,原因有以下几点: 你有一个新奇的想法,觉得将会取代其他的框架 你想要获得一些名气 你遇到的问题很独特,以至于现有的框架不太合适 你对 web 框架是如何工作的很感兴趣,因为你想要成为一位更好的 web 开发者. 接下来的笔墨将着重于最后一点.这篇文章旨在通过对设计和实现过程一步一步的阐述告诉读者,我在完成一个小型的服务器和框架之后学到了什么.你可以在这个代码仓库中找到这个项目的完整代码. 我希望这篇文章可以鼓励更多的人来尝试,因为这确实很有趣.它让

使用mybatis框架的一个简单的用户商品的增删改查例子

---恢复内容开始--- 这个例子的实现过程:用户登录----->servlet验证用户是否存在----->显示商品信息----->可以进行商品的删除.修改.添加功能(商品信息是分页显示的) 例子很简单,但是基本的mybatis框架一个也差不多是这样的 数据库表格如下 项目的结构 1.src目录下的mybatis文件和外部源文件 jdbc.properties jdbc.driver=oracle.jdbc.driver.OracleDriver jdbc.url=jdbc:oracle

一个简单且丑陋的js切换背景图片基础示例

不多说,直接上代码,非常基础的一个原生js切换元素背景图片范例 <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title>原生JS范例</title> <script type="text/javascript"> function changeBg()