为什么这么说?不知道各位有没有发现,虽然前端发展快,但一些有名的框架至少会火热很长时间,比如 Backbone、React、Ember 。如果有心要学,肯定有足够的时间把它学会,毕竟事实摆在面前,很多公司的上线产品就是用 React 来写的,比如 Teambition 的简聊,貌似它是从 Backbone 重构过来的。然而,很多同学在接手新项目时,常常会不知所措,不知道用什么技术去做,或者说,只依赖于擅长的技术,就算在一些场景中它可能并不是最适合 的。

因为这些同学平时不够努力吗?不是吧。他们可能会看书到很晚,浏览很多博客,就是为了去了解 CORS 的应用,或者是想知道为什么 Angular 中的 scope 在某些时候不能双向绑定了。对,时间是花了,但遇到问题还是一头雾水。可能前端就是这么一份工作吧,怂恿你去学游泳,蛙泳、自由泳、蝶泳,海啸来了照样被 冲走……

那这篇文章要说的是什么呢?就是假设你现在什么都没学,就靠基本功,去完成一个静态页面,当然也有业务逻辑,包括数据的 CRUD、动画,怎么做?有个关于 VanillaJS 的梗不知道大家看过没,你一定会会心一笑的。

没有 jQuery 了,没有 Bootstrap 了,扔掉所有你引以为傲的武器,但大恶魔 IE 6 还在。具体的需求不给了,反正给了你们也不会照着去实现,真有心要做的话,可以做一个 todo app 吧。

DOM 查询

在没有第三方框架可以用的时候,如果真的按照功能列表,从第一条实现到最后一条,每个模块用自执行匿名函数包起来,所有代码写在一个文件中,看上去十分合理,但真这么做的话,恐怕你会疯掉吧。哦,好处是你可以跟别人吹嘘今天写了三四百行代码,产量很高呢!

所以,不使用第三方框架,我们可以自己写,它的功能只要符合应用场景就可以了,不用去考虑各种不会发生的奇葩情况。

好,开始。我们最依赖的功能是通过 CSS 选择器获取相应的 DOM 元素,这里只使用兼容性最高的方式,就是 id 和元素名选择器。

  1. var idRegex         = /^#[\w\-]+/i,
  2. tagRegex        = /^[a-z]+/i;
  3. function query(selector, context) {
  4. context = context || document;
  5. if (idRegex.test(selector)) {
  6. return document.getElementById(selector.substring(1));
  7. } else if (tagRegex.test(selector)) {
  8. return context.getElementsByTagName(selector);
  9. }
  10. return null;
  11. }

对了,我把所有 DOM 操作放在了 F.DOM 命名空间下,所以是这样使用 query 方法的:

F.DOM.query('#id');

的确比 jQuery 的 $('#id') 方式麻烦很多,但“子不嫌母丑,狗不嫌家贫”,自己写的代码,再烂也要用下去。

另外一些必须的操作就不把代码贴出来了,比如说 addClass、removeClass、hasClass 等。

DOM 事件

如果有同学参加过面试的话,我想“怎么去监听一个 DOM 事件?请尽可能考虑浏览器兼容性”这个问题是经常会问到吧。这儿写一个可行方案吧。

  1. / 监听 DOM 事件
  2. function addEventListener(el, event, handler, useCapture) {
  3. if (el.addEventListener) {
  4. el.addEventListener(event, handler, useCapture);
  5. } else if (el.attachEvent) {
  6. el.attachEvent('on' + event, handler);
  7. } else {
  8. // not support
  9. }
  10. }
  11. // 取消 DOM 事件
  12. function removeEventListener(el, event, handler, useCapture) {
  13. if (el.removeEventListener) {
  14. el.removeEventListener(event, handler, useCapture);
  15. } else if (el.detachEvent) {
  16. el.detachEvent('on' + event, handler);
  17. } else {
  18. // not support
  19. }
  20. }

我知道大家可能有更好的,或者更完善的方案,但抱歉这里讨论的重点不是它。

关于 DOM 事件方面,还有一些有用的方法,比如 preventDefault 和 stopPropagation 也可以自己去封装一下。然后这儿想讨论一下 DOM 加载完成的事件。jQuery 中我们会这么用:

  1. $(function() {
  2. // ready
  3. });

如果我们也想封装一个类似的方法,可能会这么写:

addEventListener('window', 'load', callback);

可是 load 事件是在什么情况下触发的呢?当页面上的所有资源,包括图片,加载完之后才触发!也就是说,如果图片很多,网速很慢,那触发 load 要花很长时间。在本地调试时不会有这种延迟的问题,所以往往会被忽略。

那怎么改正呢?第一,可以把 <script> 放到 <body> 中所有元素的下方,就不需要监听任何“加载完成”的事件了。第二,监听 DOMContentLoaded 事件,IE 9+ 支持。至于如何兼容低版本浏览器,可以看这篇文章 (addDOMLoadEvent)。

组件式开发

“组件”这个词其实来源于很多框架,比如 Backbone 中的 View,React 就更不用说了,它为了组件化专门规定了 JSX(当然它有更宏伟的 goal) 。我们这里讨论的组件也是差不多的意思,就是按照功能,把页面上分成一个个独立的模块,模块之间通过消息(事件)进行沟通。关于模块耦合,JSX 是通过类似于 HTML 标签嵌套的方式来表现的,而我们自然没这么高级,就直接把依赖的模块注入到其他模块中,比如:

  1. /**
  2. * 应用顶层,构造一些页面中用到的组件
  3. */
  4. function App() {
  5. F.Component.call(this);
  6. }
  7. App.prototype = new F.Component();
  8. F.extend(App.prototype, {
  9. constructor: App,
  10. init: function() {
  11. this._blogPost = new BlogPost('#blog-post');
  12. this._blogList = new BlogList('#blog-list', this);
  13. this._newsList = new NewsList('#news-wrapper');
  14. }
  15. });
  16. new App();

其中,BlogPost 是发布日志的组件,BlogList 是日志列表。发布日志后必然会显示到列表中,所以在构造日志列表时,会把 BlogPost 注入到 BlogList 中。

每个组件可以提供一个 id 选择器,表示该组件需要绘制在哪个元素内。

消息传播机制

关于 BlogPost 和 BlogList,大家可以想象微博的主页,它上面是一个发布框,下面是微博列表,就是这样一个界面。

当微博发布之后,列表中需要增加新发布的内容,这个过程是谁给谁发消息?按照面向对象的思想,应该是类似于这样:

// 在 发布框组件 中调用 列表组件 的方法
blogList.add(item);

显然是 BlogPost 依赖于 BlogList 对吗?但貌似我们上面的代码不是这个逻辑,而是反过来。那么实际情况就成了这样:

// 发布框:BlogPost 中触发事件
this.emit('add', item);

// 列表:BlogList 中监听事件
this.listenTo(blogPost, 'add', handler);

// 由 handler 处理发布事件

嗯,代码变多了,看来得强行圆回来。

为什么我强烈建议使用后者?假设过了一段时间,某个充满创意的策划突然告诉你,当发布微博之后,可以显示到朋友圈(假设有这么个东西)。那么前者的方式会怎么做?是不是首先给这个发布框多注入一个依赖,即朋友圈,然后调用朋友圈的某个方法?

如果再过段时间,又有新创意了,是不是又得给发布框加依赖了?最后搞得发布框依赖于微博列表、依赖于朋友圈、依赖于其他 8 个组件,真不想用水性杨花来形容它。

这个问题很常见吧?如果用消息机制的方式就会好很多,只需要在新增加的组件中监听发布框的 'add' 事件就可以了。

如果你能接受这个方式,可能想知道怎么去简单地实现它。

  1. var Event = F.Event = function Event() {
  2. // 该组件相关的所有的事件都保存在 _events 对象中
  3. // 格式 - {'eventName': [{handler, context}*]}
  4. this._events = {};
  5. };
  6. F.extend(Event.prototype, {
  7. // 监听事件
  8. on: function(event, handler, context) {
  9. if (!this._events[event]) {
  10. this._events[event] = [];
  11. }
  12. this._events[event].push({
  13. handler: handler,
  14. context: context || this
  15. });
  16. },
  17. // 触发事件
  18. emit: function(event) {
  19. var events    = this._events[event] || [],
  20. args      = [];
  21. // 第一个参数为事件名,后面的参数需要传给处理该事件的方法,记录到 args 中
  22. if (arguments.length > 1) {
  23. args = slice.call(arguments, 1);
  24. }
  25. // 回调时需要传入参数
  26. events.forEach(function(v) {
  27. v.handler.apply(v.context, args);
  28. });
  29. }
  30. }

把重点部分贴了一下。第一,这个 Event 是所有组件的基类,所以每个组件都有 on 和 emit 方法。第二,F.extend 的作用就是把后面对象的方法和属性直接赋值给第一个,extend 的意思是“扩展”而不是“继承”,这点别混淆了。第三,通过改变上下文(就是 this),当一个组件的事件触发时,由另一个组件处理。

由于上面省略了很多代码,一般还要考虑的情况有,怎么取消监听,怎么实现例子中的 listenTo 等。

组件继承

关于继承,这篇文章略有提到(4.2 通过 prototype 实现继承)。

这里就写个 F.extend 技巧好了。一般来说,会在继承之后修改 prototype 的 constructor 属性,并在它上面定义很多方法,就变成了:

  1. A.prototype = new B();
  2. A.prototype.constructor = A;
  3. A.prototype.f1 = function() {};
  4. A.prototype.f2 = function() {};

大家不妨去实现一个 extend 方法,让代码变成:

  1. A.prototype = new B();
  2. extend(A.prototype, { /* 原型上的方法和属性 */ });

DOM 事件代理

一个组件往往会对应一个页面区域,那在这个区域上会有单击按钮等一些 DOM 事件。由于在初始化组件时,这些元素还没有追加到 DOM 上去,所以就不能使用 addEventListener 这个方法来监听单击事件。那要怎么监听呢?

两种方法。一,在生成 HTML 片段时,设置元素的 onclick 属性,比如:

container.innerHTML = '<a href="#" οnclick="delegate(' + id + ')">click</a>';

技巧在于,这个 delegate 方法是全局的,并且它能通过组件的 id 来找到对应的组件对象,再调用该组件的回调函数。

二,在子元素添加到 DOM 之前,父容器是存在了的,所以可以对父容器监听 click 事件,然后对 event.target 判断。

addEventListener(container, 'click', delegate)

无论是哪种方法,具体实现时肯定会碰到问题,这些都是预期范围内的,所以不用沮丧。

封装 AJAX

同样地,面试官极有可能问你“请用原生 JS 封装 AJAX 的 GET 请求”。你应该已经熟稔于心,或者至少有笔记记录了怎么写。

现在要讨论的是,如何利用“消息机制”去避免回调。jQuery 中的 ajax 方法需要一个 success 的回调,加上配置 url 等信息,导致完成一次请求所用到的代码非常复杂,很难阅读。ES 6 推出了 Promise,使得我们可以用同步的语法去做异步的事,阅读性得到了提升。

由于我们不能用 Promise,所以就发消息吧,也很优雅。

  1. F.extend(Request.prototype, {
  2. constructor: Request,
  3. get: function() {
  4. var xhr   = createXHR(),
  5. self  = this;
  6. xhr.open('GET', this._api, true);
  7. xhr.onreadystatechange = function() {
  8. if (xhr.readyState === 4 && xhr.status === 200) {
  9. self.emit('success', xhr.responseText);
  10. }
  11. };
  12. xhr.send();
  13. }
  14. });

代码并不全,说明一下,Request 继承自 Event,构造时需要传入一个 url,表示请求的地址。用法类似于:

// 在某个组件中,this 指向该对象的实例
var r = new Request('http://www.example.com/blogs');

r.get();
r.on('success', callback, this);

貌似有点像 Angular 中 new Resource(url); 的用法。

功能性兼容 (Polyfill)

这部分主要是为了兼容比如说 IE 6 不支持 HTML5 元素的样式、数组中的高级用法(forEach 和 map 等)、字符串的高级用法(trim)、Function 的 bind 等。

因为是临时编写的框架,所以业务逻辑的代码中需要什么,就补什么。

兼容 HTML5 元素你可以这么做,很简单:

document.createElement('header');

把所有用到的元素都 createElement 一遍就行了,这段代码必须放在 <head> 中。

至于兼容 forEach、map、bind 这一些,网上应该有一大堆吧,这儿只是为了提醒各位去考虑这些方面。然后,网上的兼容策略可能很复杂,没必要,大家完全可以尝试自己去写,“过早的优化是万恶之源”(这是个人最喜欢的名言了)。

浅谈模板语言

这个虽然不是必须的,并且在我目前写的代码中也没有考虑到,但经过一位高人提醒,就觉得,咦,很多听上去高大上的技术,从原理来讲都是柴米油盐这些基础知识。

如果各位之前对 underscore 中的 _.template 方法并不了解,看完这节应该会帮助你一些。

假设要生成一个用户名的链接,用模板可以这么写:

<a href="#">{{ name }}</a>

而用现在的方式是这么做的:

var html = '<a href="#">' + model.name + '</a>';

那么怎么通过模板的方式去做,不用费劲地拼接字符串呢?答案是正则。

  1. function parse(template, model) {
  2. return template.replace(/\{\{\s*(.+?)\s*\}\}/g, function(match, p1) {
  3. return model[p1] || match;
  4. });
  5. }

替换时,match 表示由正则匹配到的字符串,这里是 '{{ name }}',p1 表示匹配到的字符串中第一个组的值,这里是 'name',问号 ? 是阻止贪婪匹配,最后由返回值替换 match,这里是 model.name 。

小结

文章中的代码可能只展示了一小部分,因为我主要是想说明一些值得考虑的点,并不是教程,至少大家可以用这些作为草稿去开始。

绕来绕去,JS 中的语法也屈指可数,那为什么在学习新技术的时候会很焦灼呢?基础是一个原因,没有基础就造不了任何建筑;知识面是另一个,解决问题时最怕的是不知道有某 个答案存在,使用 API 时最讨厌的就是不知道它已经提供这个功能了。所以平时应该多看一些文章,有想法就记下来,无论是笔记的方式还是博客的方式都行,写博客可以强迫你把想法表 达出来,这跟“看懂”是不一样的。

至于学习的性价比,我只能说,不要停!

你可能觉得,唉,React 是很好,但眼下又用不到,就算学了也没用,还不如把时间花在绩效上。自己写代码永远是个封闭的空间,包括因为遇到什么问题被动地去 google 也好,如果不是主动去看新鲜事物,能力的增长是十分缓慢的。为什么会不断有新技术产生?这个事情本身就在告诉我们,需要用新的角度去解决新的问题(或者旧 的)。举两个例子,IE 在 Windows 系统上是不会自动更新的,现在它死了(Windows 10 Edge);Adobe Flash 适应不了移动平台,而安全漏洞又频出,现在它马上要死了(Adobe 的高层表示并不 care,因为 Flash 只占很少一部分营收)。

互联网它不跟你讲人情的,适者生存。

来源:51CTO

一个 JS 框架需要做什么相关推荐

  1. [JavaScript 随笔] 一个 JS 框架需要做什么

    学习再多,也是杯水车薪. 为什么这么说?不知道各位有没有发现,虽然前端发展快,但一些有名的框架至少会火热很长时间,比如 Backbone.React.Ember .如果有心要学,肯定有足够的时间把它学 ...

  2. 一个JS框架D3.js

    最近有点不务正业,好吧,诚恳一点面对现实.许多时候我想等我把某些问题,完全想明白,完全理解透,再写点东西:事实往往相反,发现等我真正接近,把某些东西理解好,我自己再也不屑写点东西分享了,没有精神气也好 ...

  3. 简述JQuery,Extjs,YUI,Prototype,Dojo等JS框架的区别和应用场景

    随着web2.0的彪悍发展,以及浏览器端所承载的工作越来越大(在不是很影响性能的情况下,开发者都习惯把能用浏览器做的事儿都让浏览器做,以减轻服务器的压力和带宽费用等).所以Javascript已经成为 ...

  4. 主题:大胆预测下JS框架的走势(ZT)

    MooTools将在接下来的几年内像jQuery一样迅速走红 而jQuery则会慢慢销声匿迹 YUI将不尴不尬的活着,YUI 3.x将成为小部分人的玩物,大部分人的忽视物 Ext将在web app应用 ...

  5. 【Vue3 造轮子项目 ------ kaite-ui】基于vue3.0 + vite + TypeScript 实现一个UI框架 - kaiteUI

    基于vue3.0 + vite + TypeScript 实现一个UI框架 - kaiteUI 前言 前段时间笔者一直忙于学习Vue3方面新知识,比如如何从vue2.0版本过渡到vue3.0,如何理解 ...

  6. 自己的JS框架--Amy框架。

    这是我根据司徒正美<JavaScript框架设计>一书然后百度了很多东西之后自己写的一个JS框架,满足了司徒正美文中的种子模块部分,包含了命名空间.对象扩展.数组化.类型判断.选择器.多库 ...

  7. 做一个laravel框架下的系统日志

    做一个laravel框架下的系统日志(php) 简要说明 第一步:新建目录下系统日志 新建路由 转到控制器 转到第一个首页视图方法对应view 此时打开本地localhost对应视图,即可看见这样的界 ...

  8. h5+js调取相机做取景框_使用Vue.js开发微信小程序:开源框架mpvue解析

    戳蓝字"CSDN云计算"关注我们哦! 作者 | 成全 责编 | 阿秃 转自 | 美团技术团队企业博客 前言 mpvue是一款使用Vue.js开发微信小程序的前端框架.使用此框架,开 ...

  9. 用SVG和Vanilla JS框架创建一个“星形变心形”的动画效果

    在我写的这篇文章中, 讲述了如何用Vanilla JavaScript使动画顺滑的从一种状态过渡到另一种.最好先看下那篇文章,因为在这篇文章中我们要用到一些那篇文章中讲过的内容.例如例子的演示.各种时 ...

  10. 2020使用html、js、正则表达式做一个前端注册表单信息验证

    使用html.js.正则表达式做一个前端注册表单信息验证 小小前端练手项目,主要运用正则表达式对用户名.密码.确认密码.电子邮箱.手机号码.生日.身份证号码的验证 1.用户名不正确(需由由英文字母和数 ...

最新文章

  1. VB6 通过winsock控件数组实现客户端和服务器多对一通信
  2. 如何处理SQL Server事务复制中的大事务操作
  3. 五子棋c语言代码ppt答辩,C语言案例:控制台版本的五子棋游戏【代码】
  4. 《高性能MySQL》の复制
  5. 【opencv】5.cv::findContours和cv::drawContours()
  6. 图像降噪算法——高斯低通滤波
  7. 数据结构学习笔记(一):链表(linked list)
  8. 详解WinCE下USB Host驱动开发(2)
  9. ORACLE SQL总结六:管理方案对象
  10. 在FIREBUG控制台中输入 jQuery() 返回 []
  11. MLP多层感知机(人工神经网络)原理及代码实现
  12. Redis教程:数据对象分析(二)
  13. Transformer在图像复原领域的降维打击!ETH提出SwinIR:各项任务全面领先
  14. Php与Mysql关系揭秘
  15. PSP3000高破解率傻瓜包!
  16. BSC(币安智能链)主网链部署
  17. 太阳辐射最少的地区_我国太阳辐射总量最少的是哪一个地区?
  18. dell r620 升级idrac_秋明 | dell 720/720xd服务器centos7下idrac固件升级[ipmi,vnc等相关操作]...
  19. 2023年西安交通大学管理学院MPAcc提前批面试网报通知
  20. Java Web之Servlet的三大常用作用域对象及其使用方法

热门文章

  1. 使用TortoiseSVN碰到的几个问题(2)-冲突解决, 图标重载
  2. 【转】3个普通IO识别22个按键试验
  3. 微信公众平台开发(41)一键关注微信公众平台账号
  4. IOS6 编程:Core Data持久化数据存储(5)-使用Core Data模板创建EntLibCart项目
  5. I2C总线简介(很经典)
  6. Java基础学习,一些零散的笔记之抽象类与接口
  7. json数据格式在javascript的读取与c#后台的赋值格式
  8. 配置nginx负载均衡
  9. unity3d 射击游戏BOSS行为代码
  10. 【Oracle 集群】ORACLE DATABASE 11G RAC 知识图文详细教程之RAC 工作原理和相关组件(三)...