现在框架太多了,各种大厂、各种流行、各种框架服务,都挑花了眼,学累了脑,如果是一个比较简单的业务,不要bootstrap,不要foundation,不要jq-mobile,不要vue,不要angular,不要React……,只用原生的html5,css,javascript等配合,后端就用个熟悉的语言,能不能做好一款一般需求的webapp?
可以的,看项目的大小,不能一言以蔽之的
个人感觉,各种技术的存在是为了简化编码的难度或者减轻工作量或者解决某些问题的,而不是为了难为大家的。 所以,当你的webapp简单到直接用原生的html、css、javascript能更简单更直接满足你的需求,那这些技术都可以不用。 不过我觉得,自己写写webapp玩玩用原生的还行,真用于生产环境的,单纯用原生的html5,css,javascript工作量可能会很大,麻烦也会不断。
命名空间+回调 这就够你喝一壶了
你看一下hbuilder,你可以用原生的js+css开发一个混合式的app。
非常同意 @youthfighter 的观点。
先回答你的问题:能。
首先,为什么要用框架? 比如我有一个滑块,和一张图片,我想让用户拖动滑块,然后图片会自动放大缩小,整个过程要十分流畅,那么我就要自己写js,监听滑块的滑动事件,然后再写js去控制图片的尺寸大小。这些用简单的js就能很方便实现,为了这个功能还去引入angular就是大炮打苍蝇。 好,现在加了一个新需求,让用户可以拖动图片的边框来放大缩小图片,同时滑块也要自动滑动;于是我就要自己将刚才那两段JS copy一份改一改,改成监听图片边框的拖动事件,然后再去改变滑块的位置。如果以后我要把滑块改成旋钮的话,我需要想着改两段几乎一模一样的代码,不过时间长工作忙很容易就忘了这一茬。不过这些都还好,毕竟功能很简单嘛。 新需求来了,用户每次做操作改变图片尺寸,都要自动发到服务器上;用原生JS也可以实现呀,在改变图片大小的JS中加一段JS代码发AJAX请求到服务器就好了。目前为止我敢说用原生JS写总共只需要10分钟不到100行代码。不需要用任何框架。 公司业务上升了,开始拓展业务,上述的功能需要在一个页面上重复做几十个,对应不同的业务需求,也就需要copy几十份代码进行修改,代码由几十个行成倍增长成了上千行。这时候维护起来忙得要死,改动的时候牵一发而动全身,而且经常有疏漏,造成BUG。 公司大规模拓展业务,类似上述的项目有十几个,都在一个网站上,用不同的页面来做,而且考虑到用户体验和服务器压力,需要每个页面在不刷新的情况下跳转,于是就要做前端路由,使用“#”来标明当天的页面;也可以用原生JS直接实现,写一段JS来监听URL的变化,然后不停地清空和填充<body>的内容,还需要写另一段JS来管理每一个页面需要如何以及怎样的填充<body>的内容,而且十分可能在页面跳转的时候要提前发AJAX请求来获取一些数据,根据服务器返回的信息来决定显示什么样的页面。 到目前为止,这些功能可以说是勉强能支撑公司的业务活动了。而目前为止都是自己用原生JS写的话,代码数量已经能达到上万行了。 其实好多东西都是重复的,copy代码的话不好维护,一类功能用10段代码实现的话,改这类功能就需要10个地方同时改,所以我们可以把重复的代码抽象成一些通用的function或者object,让每一个用到的地方调用同一个函数传不同的参数就可以了;发送AJAX的地方也抽象成一些通用的服务,想发请求的时候调用函数就好了;前端路由和页面渲染的部分对灵活性要求较高,需要设定很多参数,内部根据不同的参数组合选择不同的渲染方式。 亲,最后你会发现,你自己做了一套框架,而且扩展性、灵活性、开放性、可靠性、性能还不如人家大牛团队写的vue和angular要好。项目复杂性到了一定程度,用第三方成熟的框架只需要自己写两百行JS就能完成上万行代码的功能,何乐不为呢? 但若你只想做一个静态页面,鼠标移动字体变色这样的简单小效果,引入前端框架也没用不是吗? 所以有需要才会用框架,不需要没必要勉强用,那样只会徒增项目体积、消耗服务带宽、降低用户体验。
之后,如何选择前端框架? 我在做项目的时候从来都是先进行需求分析,知道要做啥,再根据需求进行技术选型,看哪些技术可以在满足需求的前提下将开发效率、架构合理性、性能等做到最好,有的项目我会用angular,有的只需要用jquery,有的用react比较适合。 没有完美的框架,但有最适合的框架,多学一学各种框架,能做到项目开始的时候准确选型,可以确保项目周期短、完成度高,那个时候你就是架构师了。
@libook 多谢指教。
用框架很难选择很累,恰恰说明了你对原生的html5/css/javascript不够熟悉。。。
如果有大量的使用原生js开发经验的话,就能很明显的感受到自己的需求,不需要很费心的挑选。
哪种快、方便,那种来。并不一定要限定在使用框架。只有适合自己的,没有更好的、。
@libook 分析的很到位,谢谢
因为绝大多数人真没这个水平,基础都是跳过去学的,框架成了最终和唯一目的,技能树的点法是力量敏捷智力点到刚好练大火球,然后一直用大火球,只会用大火球,结果呢?结果该拿的盾拿不起来,弓箭射速不够,想做刺客但避闪不够。
一辈子,就用个大火球,等到某天碰到火抗100%的精英怪,才发现不行,要上物理攻击,只可惜,可能已经打到后期了,晚了。
框架是填空法,是为了快速开发,而学习是不能这么快速的,学习应该重视基础,往后才有 resilency。
如果你坚持不用框架,那么最终你自己会写一个框架。最近对这句话深有体会。 自豪地采用 CNodeJS ionic
简单当然可以原生,但是你想下,用原生难免避免不了 dom 操作,你要是一直用 document.querySelector 名称长且重复出现,这时候你会想到封装一个 dom 选择器方法,如:function $(select){ return document.querySelector(select) } 和 function $$(select){ return document.querySelectorAll(select) } 然后又得扩展功能,最终你还是会得到一个库/框架 类似
class F {
constructor(select) {
return this.init(select)
}
hide() {
Array.from(this).forEach(el => {
el.style.display = 'none'
})
}
init(select) {
const nodeList = document.querySelectorAll(select)
this.length = nodeList.length
Array.from(nodeList).forEach((el, index) => {
this[index] = nodeList[index]
})
return this
}
}
const $ = select => new F(select)
$('div').hide()
-----分割线----- 下面摘抄 张鑫旭的 jQuery诞生记
gelElementById太长了 页面上有个按钮,还有个图片,我想点击按钮图片隐藏,如下HTML:
<button id=“button”>点击我</button> <img id=“image” src=“xxx.jpg”> 于是,我的脚本可能就这样:
var button = document.getElementById(“button”) , image = document.getElementById(“image”)
button.onclick = function() { image.style.display = “none”; }; 有何问题?人几乎都是天生的“懒惰者”,document.getElementById名称长且重复出现,好像到了公司发现卡没带又回家重新拿卡的感觉,我希望越简单越好。恩, 我很喜欢钱,$这个符号我很喜欢,我打算改造一番,简化我的工作:
var $ = function(id) { return document.getElementById(id); };
$(“button”).onclick = function() { $(“image”).style.display = “none”; }; 这里的$()就是最简单的包装器,只是返回的是原生的DOM对象。
- 我需要一个简洁的暗号,就像“芝麻开门” 后来页面复杂了,点击一个按钮,有2张图片要隐藏。
$(“button”).onclick = function() { $(“image1”).style.display = “none”; $(“image2”).style.display = “none”; }; 好像又看见长长的重复的东西,xxx.style.display = “none”, 为什么每次开门都要从包里找到钥匙、对准插口插进去、还要左扭扭右扭扭呢?一次还好,天天经常老是这样怎受得了。设想,要是有个芝麻开门的暗号就好了,“open开”,声音识别,门自动开了,多省心。
这里每次隐藏都要xxx.style.display = “none”, 比每天拿钥匙开门还要烦,我希望有一个快捷的方式,例如,“hide隐”,语句识别,元素自动隐藏,多省心。
就是要变成下面的效果:
$(“button”).onclick = function() { $(“image1”).hide(); $(“image2”).hide(); };
- 如何识别“芝麻开门”的暗号 $(“image1”)本质是个DOM元素,$(“image1”).hide()也就是在DOM元素上扩展一个hide方法,调用即隐藏。
哦,扩展,立马想到了JS中的prototype原型。//zxx: 老板,现在满大街什么菜最便宜。老板:原型啊,都泛滥了!
HTMLElement.prototype.hide = function() { this.style.display = “none”; }; 上面代码的demo地址应该不会被人看到吧……
虽然在身体上钻了个窟窿插进入了一个方法,毕竟浏览器有有效果啊,切肤之痛就不算什么了。但是,我们是在泱泱天朝,很多IE6~IE8老顽固,这些老东西不认识HTMLElement,对于HTMLElement自残扩展之法根本理解不了,而这些老家伙掌管了半壁江山。唉,面对现实,元素直接扩展是行不通了。
因此,由于兼容性,我们需要想其他扩展方法。
- 条条大路通罗马,此处不留爷,自有留爷处 虽IE6~IE8不识HTMLElement原型扩展,但是,Function的原型扩展其认识啊。管不管用,暂时不得而知,先随便搞个简单的试试呗~
var F = function() {}; F.prototype.hide = function() { this?.style.display = “none”; };
new F().hide(); // 这个实现隐藏? 本文至少还有一半的内容,但是,全文的最难点就在这里的,对new F()的认识和理解。
上面的代码,new F()您可以看做是this?.style这里的this. 您可能会跳起来抢答道:“那new F()的return值 = DOM元素不就完事OK啦!—— this.style.hide = new F().style.hide = DOM.style.hide”!
很傻很天真
只要new表达式之后的constructor返回(return)一个引用对象(数组,对象,函数等),都将覆盖new创建的匿名对象,如果返回(return)一个原始类型(无return时其实为return原始类型undefined),那么就返回new创建的匿名对象。
上面的引用来自这里。什么意思呢?说白了就是,new F()如果没有返回值(Undefined类型),或返回值是5种基本型(Undefined类型、Null类型、Boolean类型、Number类型、String类型)之一,则new F()我们可以看成是原型扩展方法中的this; 如果返回是是数组啊、对象啊什么的,则返回值就是这些对象本身,此时new F() ≠ this。
举例说明:
var F = function(id) { return document.getElementById(id); };
new F(“image1”) == document.getElementById(“image1”); // true 说明看上去返回DOM对象,实际确实就是DOM对象 var F = function(id) { return id; };
new F(“image1”) == “image1”; // false 说明看上去返回字符串值,实际并不是字符串 回到上面天真的想法。要想使用prototype.hide方法中的this, 偶们就不能让F函数有乱七八糟的返回值。
因此,new F()直接返回DOM是不可取的,但我们可以借助this间接调用。比方说:
var F = function(id) { this.element = document.getElementById(id); }; F.prototype.hide = function() { this.element.style.display = “none”; };
new F(“image”).hide(); // 看你还不隐藏 上面代码的demo地址应该不会被人看到吧……
- 暴露与重用元素获取方法 上面的方法,元素的获取直接在F方法中,但是,实际情况,考虑到兼容性实现,元素获取可能会相当复杂,同时方法私有,不能重利用。因此,可以把元素获取方法放在原型上,便于管理和重用。代码如下:
var F = function(id) { return this.getElementById(id); }; F.prototype.getElementById = function(id) { this.element = document.getElementById(id); return this; }; F.prototype.hide = function() { this.element.style.display = “none”; };
new F(“image”).hide(); // 看你还不隐藏 元素获取方法放在prototype上,通过F()执行。你可能会奇怪了,你刚明明说“new F()直接返回DOM是不可取的”,怎么现在又有return呢?大家务必擦亮眼睛,F.prototype.getElementById的返回值是this,也就是new F()的返回值是this. 形象点就是new F(“image”)出了一拳,又反弹到自己脸上了。
上面代码的demo地址应该不会被人看到吧……
- 我不喜欢new, 我喜欢$ new F(“image”)这种写法我好不喜欢,我喜欢$, 我就是喜欢$, 我要换掉。
好吧,把new什么什么藏在$方法中把~
var $ = function(id) { return new F(id); }; 于是,上面的图片隐藏的直接执行代码就是:
$(“image”).hide(); 上面代码的demo地址应该不会被人看到吧……
IE6浏览器也是支持的哦!是不是已经有些jQuery的样子啦!
- 你怎么就一种姿势啊,人家都腻了诶 循序渐进到现在,都是拿id来举例的,实际应用,我们可能要使用类名啊,标签名啊什么的,现在,为了接下来的继续,有必要支持多个“姿势”。
在IE8+浏览器中,我们有选择器API,document.querySelector与document.querySelectorAll,前者返回唯一Node,后者为NodeList集合。大统一起见,我们使用后者。于是,就有:
var F = function(selector, context) { return this.getNodeList(selector, context); }; F.prototype.getNodeList = function(selector, context) { context = context || document; this.element = context.querySelectorAll(selector); return this; };
var $ = function(selector, context) { return new F(selector, context); }; 此时,我们就可以使用各种选择器了,例如,$(“body #image”), this.element就是选择的元素们。
- IE6/IE7肿么办? IE6/IE7不认识querySelectorAll,咋办? 怎么办?
jQuery就使用了一个比较强大的选择器框架-Sizzle. 知道就好,重在演示原理,因此,下面还是使用原生的选择器API示意,故demo效果需要IE8+浏览器下查看。
- 遍历是个麻烦事 this.element此时类型是NodeList, 因此,直接this.element.style.xxx的做法一定是报错,看来有必要循环下:
F.prototype.hide = function() {
var i=0, length = this.element.length;
for (; i<length; i+=1) {
this.element[i].style.display = “none”;
}
};
于是乎:
$(“img”).hide(); // 页面所有图片都隐藏啦! 上面代码的demo地址应该不会被人看到吧……
单纯一个hide方法还可以应付,再来个show方法,岂不是还要循环遍历一次,岂不是要烦死~
因此,急需一个遍历包装器元素的方法,姑且叫做each吧~
于是有:
F.prototype.each = function(fn) { var i=0, length = this.element.length; for (; i<length; i+=1) { fn.call(this.element[i], i, this.element[i]); } return this; }; F.prototype.hide = function() { this.each(function() { this.style.display = “none”; }); };
$(“img”).hide(); // 页面所有图片都隐藏啦! 上面代码的demo地址应该不会被人看到吧……
- 我不喜欢this.element, 可以去掉吗? 现在包装器对象结构类似这样:
F.prototype = { element: [NodeList], each: function() {}, hide: function() {} } element看上去好碍眼,就不能去掉吗?可以啊,宝贝,NodeList是个类数组结构,我们把它以数值索引形式分配到对象中就好啦!一来去除冗余element属性,二来让原型对象成为类数组结构,可以有一些特殊的功能。
于是,F.prototype.getNodeList需要换一个名字了,比方说初始化init, 于是有:
F.prototype.init = function(selector, context) {
var nodeList = (context || document).querySelectorAll(selector);
this.length = nodeList.length;
for (var i=0; i<this.length; i+=1) {
this[i] = nodeList[i];
}
return this;
};
此时,each方法中,就没有烦人碍眼的this.element[i]出现了,而是直接的this[i].
F.prototype.each = function(fn) { var i=0, length = this.length; for (; i<length; i+=1) { fn.call(this[i], i, this[i]); } return this; }; 我们也可以直接使用索引访问包装器中的DOM元素。例如:$(“img”)[0]就是第一张图片啦!
上面代码的demo地址应该不会被人看到吧……
- 我是完美主义者,我特不喜欢F名称,可以换掉吗? F这个名称从头到尾出现,我好不喜欢的来,我要换成$, 我就是要换成$符号……
这个……$已经用了啊,再用冲突的吧。再说,你又不是狐后,耍无赖也没用啊……
狐后耍无赖
御姐发飙了
好吧,想想其他办法吧。一步一步来,那我把所有的F换成$.fn.
就有: 所有的F换成$.fn之后~
上图代码的demo地址应该不会被人看到吧……
显然,运行是OK的。似乎也非常有jQuery的模样了,但是,实际上,跟jQuery比还是有差别的,有个较大的差别。如果是上图代码所示的JS结构,则包装器对象要扩展新方法,每个都需要再写一个原型的。例如,扩展一个attr方法,则要写成:
$.fn.prototype.attr = function() { // … }; 又看到prototype了,高级的东西应该要隐藏住,否则会给人难以上手的感觉。那该怎么办呢?御姐不是好惹的。
脑子动一下就知道了,把F.prototype换成$.fn不久好了。这样,扩展新方法的时候,直接就是
$.fn.attr = function() { // … }; 至此,就使用上讲,与jQuery非常接近了。 但是,还有几个F怎么办呢,总不能就像下面这样放着吧: var $ = function(selector, context) { return new F(selector, context); }; var F = function(selector, context) { return this.init(selector, context); };
$.fn = F.prototype;
$.fn.init = function(selector, context) { // … return this; }; $.fn.each = function(fn) { // … }; $.fn.hide = function() { // … }; 数学中,我们都学过合并同类项。仔细观察上面的的代码: $()返回的是new F(),而new F()又是返回的对象的引用。擦,这返回来返回去的,参数又是一样的,我们是不是可以一次性返回,然后再做些手脚,让$.fn.init返回的this依然能够正确指向。
于是,一番调整有:
var $ = function(selector, context) { return new $.fn.init(selector, context); }; var F = function() { };
$.fn = F.prototype; $.fn.init = function(selector, context) { // … return this; };
// … 上面代码显然是有问题的,new的是$.fn.init, $.fn.init的返回值是this. 也就是$()的返回值是$.fn.init的原型对象,尼玛$.fn.init的prototype原型现在就是个光杆司令啊,哟,正好,$.fn对应的原型方法,除了init没用外,其他hide(), each()就是我们需要的。因此,我们需要加上这么一行:
$.fn.init.prototype = $.fn 于是,$()的返回值从$.fn.init.prototype一下子变成$.fn,正好就是我们一开始的扩展方法。
于是乎,大功告成。慢着…… 慢着……
上面明明还有残留的F呢!
哦,那个啊。F是任意函数,$本身就是函数,因此,直接使用$替换就可以了:
var $ = function(selector, context) { return new $.fn.init(selector, context); }; var F = function() { }; // 这个直接删除 $.fn = $.prototype; $.fn.init = function(selector, context) { // … return this; };
// … 上图代码的demo地址应该不会被人看到吧……
实际上,如果你不是非得一个$行便天下的话,到了上面进阶第9步就足够了。jQuery在第10步的处理是为了彰显其$用得如此的出神入化,代码完美,令人惊叹!
至此,jQuery大核心已经一步一步走完了,可以看到,所有的这些进阶都是根据需求、实际开发需要来的,慢慢完善,慢慢扩充的!
- 每个扩展方法都要$.fn.xxxx, 好闹心的来
$.fn.css = function() {} $.fn.attr = function() {} $.fn.data = function() {} // … 每个扩展前面都有个$.fn, 好讨厌的感觉,就不能合并吗?
于是,jQuery搞了个extend方法。
$.fn.extend({ css: function() {}, attr: function() {}, data: function() {}, // … });
- $()不仅可以是选择器字符串,还可以是DOM 在init方法中,判断第一个参数,如果是节点,直接this[0] = this_node. over!
以下13~?都是完善啊,补充啊,兼容性处理啊什么的,没有价值,到此为止!
三、排了很长队的结束语 网上也有其他一些介绍jQuery原理或机制的文章,可能当事人自己理解,而阅读者本来就不懂,说来说去,越说越绕,可能更不懂了。
jQuery是很优秀,好比身为灵长类的人类。但是,其诞生显然是从简单开始的。因此,要了解人类,可以通过追溯其起源。如果你是上帝,要让你造一个人,你会怎么造,是一口气出来?女娲造人还要捏泥人呢!不妨从单细胞生物开始,随着自然进化,淘汰,自然而然,就会出现人类,上帝他就是这么干的。
jQuery的诞生也大致如此,要想了解jQuery,可以试试踏着本文jQuery的成长足迹,一点一点逐步深入,您就会了解为何jQuery要这么设计,它是如何设计的等。
虽然,内容由浅及深,但是,其中涉及的原型以及new构造函数的一些特性,对于新人而言,还是有一些理解门槛的,希望我的描述与解释可以让你有一丝豁然开朗,那就再好不过了。
感谢您的阅读至此,欢迎指出文章可能书写不准确的地方,再次感谢!
月底在百姓网有个小分享,演示文档连个肉渣子还没准备呢。因此,未来一周休文。
原创文章,转载请注明来自张鑫旭-鑫空间-鑫生活[http://www.zhangxinxu.com]
@brickyang 非常赞成
当然可以,各种框架,库都是为了同一个目的,方便快捷开发,维护方便 当然也不一定要去学习所有的框架,但一定要理解这些框架诞生的来由以及演变趋势 可以着重学习一到两种不一样的,其它的都差不了多少的
最近我自己写了个项目,除了jQuery没有引入什么东西,写的很累,费时间,但是很轻巧 我认为长期用的项目好好写,赶时间的就各种框架怎么快怎么来
最近用Vue重构了之前一个纯手写的项目,有一点心得供你参考: Vue全家桶实践项目总结
@zbsccc 不敢苟同,既然说到了是用来做webapp,举例用户体验方面,单页应用显然更好,那做为长期项目,你就要自己在jquery的基础上,开发一个单页项目吗