1.框架概览
序
就个人开发而言,当前的前端开发环境是一个比较混乱的状态。
用户对产品要求的提高,技术为了满足用户的需求不断的进步,引入了很多专业性的知识,然而这点却不是阻碍web开发的门槛。每个入门开发者选择web开发,是因为它具备更多专业性,更多可能性,更多包容性。
较高的门槛。学会并掌握了html,css,js,却不能做出主流的东西出来,个人觉得原因在于webapp的流行。各种框架的兴起,框架层互相竞争,引入专属于自己的知识,从而提高了门槛。
各种框架自立门派,写法和思想理念互不兼容,与刚入门时学习的前端知识分歧比较大,导致无法很好的直观的去理解,等到正常业务开发使用的时候,就会显得力不从心。
较快的项目迭代速度,需要开发人员快速的开发出产品,逐渐的大家都采用了统一的组件,但各种组件库互不兼容,捆绑于不同框架。如果切换一个框架,就会导致之前的组件使用经验要从头积累,很难积累更本质的知识和经验,最后沦为只会使用别人组件,然后对着组件文档开发,修改细节的时候苦思冥想。到头来,无法系统的提高自身全面水平。
前端的延伸,nodejs带来的前端革命。每个框架带来自己的脚手架,这个东西有利有弊,虽然提高了工作效率,但脚手架配置的门槛让人叹为观止。而当完全理解了脚手架配置和里面的思想理念后,是否会对个人前端开发技术来个飞跃性的调高呢?
当前各种前端框架都称自己先进的理念,能够弥补当前web开发的各种不足。如果抛离这些框架,拾起最原始的开发, 能否做到和使用前端框架的web一样优秀,甚至更好呢?个人觉得这是肯定的。
需求
为了可以非常方便的修改并且应用到别的框架中,或者能把其它框架的代码修改成适合当前使用的,并同时也能使用以前的各种优秀的功能代码,直接把它集成到该项目中并且使用。这里只需要尽量浅封装,保留原始web开发过程、保留最初的css特性、html特性以及不修改js原有的用法。在这基础上还能挖掘基本特性,引申出更多扩展。
为了兼容更多的浏览器,应该采用统一的api,开发人员使用经过封装后的一致的功能。封装后,对不同版本的浏览器进行兼容,保持整体一致的开发思想,每次更新迭代的时候不会影响以前的代码。
以内容为先,任何一个框架, 都应该为用户更好的开发内容而服务的,不应该去影响用户的开发,并设立不同的障碍,或者只能通过hack手段来实现。
整体思想
市场上的mvvm框架盛行,大吹自身的便利性, 然而在处理精细细节的时候,要定义特别的控制变量,明明可以通过dom几行代码搞定。再加上好多人有时在谈论:“我做什么项目都没有操作到dom”,顿时让人觉得dom是非常难以理解的东西。在浏览器实现高度一致的现今,dom规范已经非常的一致,而且个人觉得dom的api是非常合理实用的,合理的使用可以让产品性能更加好。因此,这可以算是以dom操作为核心的框架,包括后续引入的组件以及图表,都是为了能更好的在使用上贴近dom使用。因为可视化的内容都是通过dom来展现的,dom的理解以及灵活使用会影响到使用者最终的呈现。
作为一个单页面框架,采用合理灵活的页面结构来应对不同的场景是特别重要的。思考以往的页面架构,通常有一部分是固定不变(变动很少), 另一部分频繁更改。 当我们把不变的抽出来作为公用的时候,在某个特定的场景,需要对不变的部分进行大动,最后导致整体结构大改,打破了初始的概念。无论是不变的部分,还是经常变动的部分,在框架中,我统一用Page对象来定义。 一个webapp可以有很多个Page对象,不变的部分假定只有一个,经常变动的部分可以有很多,并随着业务更改而更改。通过用一个App对象来管理Page对象,处理路由,处理全局与dom无关的一切事务。因此一个webapp,至少由一个App对象和两个Page对象组成,如下图
事件机制:对整体webapp做了分层管理后,不同对象的交互不能直接调用。比如不变的Page不知道经常变动的Page是什么。因此引入了事件处理机制,基本原理与dom事件基本思想基本一致,类似于:你对某个元素监听了点击事件,用户点击(特殊场景)触发了该事件,执行了某些行为。如果你没有监听,那么什么也不会发生。事件的引入让不同的对象间的关系耦合度变低,提高了维护性和移植性。
优点
js部分是由es5开发完成,因此可以不做任何处理,就能直接在现有的浏览器中运行,如果单独要使用es6或更高版本的js,请确认运行浏览器的支持。
采用了基本的css,html,js分离开发方式,也最初的学习前端的方式,以及使用方式,门槛极低,一般对css,js,html有基本的认识就可以直接上手,对于前端初学者最难的还是在于本地服务器的搭建(单页面的路由机制,内存管理)
占用内存极低,基本是静态的html的使用率持平,因为是以dom为核心,不需要太多的辅助对象。
底层代码量很少且易于理解(去除注释大概仅有3500行左右),更适合阅读且理解最深处的原理,后续博客更新主要是为了分析原理并举出相关的例子
简单案例
为了更好的理解单App对象和至少两个Page对象,特地的举了下面的案例帮助理解,这里尽量详细的分析部分代码细节。如果需要源代码以及本地node服务器的搭建,进qq群或微信讨论下载,更多效果可以通过我们的网站查看。
整体项目文件
--- index.html
--- public--- component 放置组件对象 后续更新 --- img 放置图片 --- pages 放置Page对象代码 --- home --- home.html 页面html片段 --- home.js 页面逻辑js代码 --- popup 放置PopUp对象代码,弹窗对象。 后续更新 --- script 放置全局js文件 --- services 放置可引用服务对象,后续更新 --- style 放置样式文件 --- app.css 放置所有的样式,如果多人协作,可以分开多个 --- index.css 放置整体全局样式 --- ui 引入的ui框架 后续更新
创建一个index.html
<!DOCTYPE html> <html lang="zh-cn"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>hello world</title> <!-- 引入整体样式 --> <link rel="stylesheet" href="/public/style/index.css"> <!-- 引入单独样式 --> <link rel="stylesheet" href="/public/style/app.css"> <!-- 引入框架js代码 --> <script src="/public/script/str.js"></script> </head> <body> <!-- 初始化App,以及Page对象注册 初始化页面 --> <script src="/public/script/index.js"></script> </body> </html>
创建App对象
在public/script/index.js文件中
(function() { // 新建一个App对象,布局页为static, 首页为home, // 版本号为1, 名称为helloWorld var app = App.create("helloWorld", "static", "home", 1); // 配置页面信息,以后切换页面可以通过Page的name来进行切换。 app.setPageConfig([{ name: "static", // 对象上面的static js: "/public/pages/static/static.js", // 路径 title: "布局", // 标题 url: "/static" // path }, { name: "home", js: "/public/pages/home/home.js", title: "首页", url: "/home" }, { name: "second", js: "/public/pages/second/second.js", title: "次页", url: "/second" } ]); // 将页面嵌入dom中。 app.initialize(document.body); })();
创建Page对象
})();
- 创建名为second的Page对象 html部分在/public/pages/second/second.html ```html <div>欢迎阅读及使用</div> <button>返回</button>
js部分在/public/pages/second/second.js
(function () { var app = App.getCurrent(); app.definePage("second", { render: function (next) { this.fetch("./second.html", next); }, getDomObj: function () { this.attachDom("button", "btn") .attachEvent("btn", "click", this.clickHandler, false); }, init: function () { app.staticPage.header = "次页"; app.staticPage.footer = "从首页跳转到次页"; }, clickHandler: function (ev) { // 返回上一页 history.back(); } }) })();
创建名为static的Page对象
html部分在/public/pages/static/static.html
<header>页面名称</header> <!-- 放置其它Page对象的div --> <main class="page-container"></main> <footer></footer>
js部分在/public/pages/static/static.js
(function () { var app = App.getCurrent(); // 获取当前app对象 // 定义命名为static的Page对象 app.definePage("static", { // 获取静态文件放入页面 render: function (next) { this.fetch("./static.html", function (text) { next(text); }); }, // 缓存dom并绑定事件 getDomObj: function () { this.attachDom(".page-container", "pageContainer") .attachDom("header", "header") .attachDom("footer", "footer"); }, // 方法或属性 set header(value) { this.domList.header.textContent = value; }, set footer(value) { this.domList.footer.textContent = value; } }) })();
创建名为home的Page对象
html部分在/public/pages/home/home.html
<div>hello world</div> <button>跳转</button>
js部分在/public/pages/home/home.js
(function () { var app = App.getCurrent(); app.definePage("home", { render: function (next) { this.fetch("./home.html", next); }, getDomObj: function () { this.attachDom("button", "btn") // 绑定dom事件 .attachEvent("btn", "click", this.clickHandler, false); }, // 页面加载完后初始化页面 init: function () { app.staticPage.header = "首页"; if (!app.data.home) app.staticPage.footer = "初始化到了首页"; else app.staticPage.footer = "次页返回到首页"; app.data.home = true; }, clickHandler: function (ev) { // 切换second页面 app.render("second"); } })
查看效果(主要针对移动端,可以通过手机端查看或者用浏览器模拟移动端)