菜鸟也能懂的前端开发入门教程
这篇文章很长,但的确是一篇非常干的干货,讲诉了 HTML、JavaScript、CSS、jQuery使用的一些规范与建议,前端的同学可以认真阅读此文,并比较自己平时的一些习惯,看是否有改进的地方……
HTML
咋地了, DOCTYPE?
不定义DOCTYPE是一种可以被判死刑的罪行。 以前你可能用的是下面的DOCTYPE,不过你要知道现在已经有更简洁清晰的代码取而代之了。
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
理想的状况是用HTML5 DOCTYPE,所有现代的浏览器都支持它,即使是不支持HTML5的浏览器,例如IE6,IE7,也会由此转入标准模式。 参见来源。
<!DOCTYPE html>
编写合法且语义清晰的标记
用整洁、语义清晰的HTML编写网站代码是我们一直孜孜以求的。有时我们会发现前人配置页面的方式限制了我们,或者有时我们编写的是HTML格式的email模板。但永远不要偏离HTML的规范,即使是为了解决特定浏览器兼容性的bug。
所有的标题应该从<h2>开始分层级创建,文字段落应该总是放在<p>标签里,诸如此类。如果你编写的HTML的语义清晰,产生的页面会更整洁、简练,而且易于被搜索引擎爬虫解析。这是你能做到的最简单的SEO修补方式。
来看看下面的段落,你觉得哪个更整洁?是这个?
<span class="sectionHeading">A Heading</span> <br /> <br /> Lorem ipsum dolor sit amet. ... <br /> <br />
还是这个?
<h2>A Heading</h2> <p> Lorem ipsum dolor sit amet. ... </p>
鼠标中键点击的应变方式
现代web应用最令人郁闷的可用性缺陷之一是超链接功能的变种。 一些看起来像是超链接的元素可能是通过Javascript映射的单击功能,这就破坏了鼠标中键点击(在新的tab中打开链接页面)的功能。即使它们能在新的标签页打开,它们只带有一个 # 的href又会把你带回到同样的页面。
深刻诠释了该问题的一个现代热门网站的例子就是Twitter。在它的整个应用里,鼠标中键点击用户名或头像会得到完全不同的结果。
<!-- 旧的方式,破坏网页语义 --> <a href="#"></a> <!-- 如果鼠标点击不能产生一个页面,那就不是超链接 --> <span class="link" role="link"></span>
另一个替代方案是使用 # 引导的路径,它会把普通的url映射为 # 引导的链接,然后通过AJAX来获取页面片段。提供此功能的库应该能够在鼠标中键点击的时候正常显示页面,或者在左键点击时把该页面内容加载到指定的区域。不过这样也要慎重行事,有很多人都认为 #链接正在破坏web应用。
用Microformats格式表示联系人信息
Microformat是一种便于机器读取联系人信息的方式。hCard类(不是vCard)用来定义元素里包含的内容类型。这些内容会被浏览器提取并突出显示。
<span class="tel"> <span class="type">home</span>: <span class="value">+1.415.555.1212</span> </span>
如果你曾经浏览采用此格式的网页,你会注意到类似skype的程序可以轻松检测到网页上的哪些数字是电话号码。在iOS设备上的Safari浏览器也可以做到类似的事情。
有关Microformat的更多信息请参阅http://microformats.org/wiki/hcard
图片需要设 'Alt' 文本
<img> 标签需要 alt 文本,以便检查并满足可读性的要求。 在 alt 属性中的文本必须能够说明图片显示的内容或要达到的效果,除非该图片不重要。
如果图片只是一个列表中的着重号或者其他无关紧要的图标,最好是给 alt 属性一个空字符串,但还是留着它。这样屏幕阅读器会忽略它,而不是把"着重号"连读20次。
<img src="dog.gif" alt="Fido and I at the park!" /> <!-- 很好,描述清晰 --> <img src="bullet.gif" alt="bullet" /> <!-- 不好,显得多余 --> <img src="bullet.gif" alt="" /> <!-- 好 -->
只对表格数据用table标签
table标签永远只应该用在表格数据的展示上。唯一的例外是当编写HTML格式的邮件时,这种情况下可能table是某些坑爹的邮件客户端唯一支持的样式了。
为了可读性,表格头永远要使用 <th> 元素。同时切记要设置cellpadding, cellspacing 和 border 的值为 0 , 因为这些样式由CSS来控制更容易确保一致性。
<table cellpadding="0" cellspacing="0" border="0"> <thead> <tr> <th> Cell Header </th> </tr> </thead> <tbody> <tr> <td> Cell Item </td> </tr> </tbody> </table>
使用 jQuery 和 jQuery UI Widgets
jQuery 和 jQuery UI 被做成尽可能在不同浏览器上表现出几乎相同的外观和功能。 jQuery UI 被设计为符合 WAI WCAG 2.0 及 WAI ARIA, 因此采用该框架可以避免在你的站点上运行的插件或脚本的所有不确定性。
JavaScript
代码留空和格式
任何关于代码格式、留空和大括号位置的讨论都会引起激烈辩论。对此,我想最简单的规则就是,除非你愿意把整个代码文件重新格式化,不然还是尊重并保持已有代码文件的格式。这意味着如果你看到一个JS文件里的大括号没有换行写,那你的代码也要继续保持大括号不换行。如果你的代码没有和代码文件里的其他部分保持一致,那么你的代码就不应该通过代码审查流程。
一致的代码格式让代码更加易读,同时也意味着代码容易用查找/替换命令进行修改。谢天谢地,我们自己形成的编程习惯和jQuery正式推荐的方式非常相似。细微的差异也是有的,不过,那些是个人问题或者我们觉得没法维护的一些东西。 参阅jQuery核心样式指南
字符间距
// 不好 if(blah==="foo"){ foo("bar"); } // 好 :) if (blah === "foo") { foo("bar"); }
大括号不换行
// 不好 if (foo) { bar(); } // 好 :) if (foo) { bar(); }
总是用大括号
// 不好 if (foo) bar(); // 好 :) if (foo) { bar(); }
字符串处理
引用字符串永远要用双引号。 有些人非常喜欢用C语言风格的字符串(单引号),但这种习惯会导致脚本内部的风格冲突。C语言风格的字符串处理要求空字符串和单字符包在单引号里,而短语和单词必须包在双引号内。
注释
往代码里玩命加注释的需求是由各种经理、主管及其他很少接触代码的人们引领的。这种需求无非是雇员们考核指标中的一个勾选栏,花在这上面的时间基本没有带来什么回报。 如果那些从善如流的开发者能遵循本文档中提出的建议,他们的代码会变得相当易于阅读,一目了然,以至于再用注释描述这些代码会多余得令人尴尬。来看下面的例子。在这里,布尔变量被作为问题提出,而函数也有直观的命名。
if (user.hasPermission) { editPage(); }
至少在这个场景中,注释是完全没有必要的。
注释重要的情况
一个项目里,永远会有某些部分难以查阅和理解。比如一个复杂的正则表达式,或者一个对角度进行计算或在度和弧度单位之间切换的数学函数。没有上面的注释,初级或中级的读者将对脚本的含义毫无头绪。
// 校验美国电话号码的正则表达式,号码格式是 (XXX) XXX-XXXX (减号、空格和括号都是可选的,可以有也可以没有) var phoneRegEx = /^\(?(\d{3})\)?[- ]?(\d{3})[- ]?(\d{4})$/;
总是使用 === 比较符
使用 == 比较符可以让令人郁闷的bug消失于无形。它允许在 JavaScript花园 中有清楚解释的弱类型。使用严格的 === 比较符不会执行类型强制转换,从而能够严格地评估两个对象之间的差别。再说一遍,更多详细信息请参见 JavaScript花园。
var zeroAsAString = "0"; if (zeroAsAString == 0) { // 这样也能判断为true,呵呵... } if (zeroAsAString === 0) { // 判断为false }
例外
在和null进行比较的时候,允许使用 == 比较符,因为它会检测null和undefined两个属性。如果你不完全理解这个原理,那我还是建议你用 === 比较符为好。
var foo = null; // foo 是 null, 但 bar 是 undefined ,因为它尚未被声明 if (foo == null && bar == null) { // 上面的判断还是成立的 }
使用 .parseInt() 的时候,总是指定第二个 'radix' 参数
把字符串解析为整数的时候,有个好习惯是指定第二个基数参数 -- 它会确定该字符串被转换成几进制。当字符串以 0 开头的时候,默认情况下会触发 16 进制作为基数。大部分初级和中级用户只会用到 10 这个基数。 感谢 João Moreno 记录的这个 勘误。
alert( parseInt("08") ); // alerts: 2 alert( parseInt("08", 10) ); // alerts: 8
避免比较 true 和 false
直接比较 true 和 false 的值是没有必要的。有时候也许明确一下有好处,但它还是额外的代码。
if (foo === true) { // 用了 === 倒是不错,可这是多余的 } if (foo) { // 赞! } if (!bar) { // 反过来也赞 }
避免污染全局命名空间
过分依赖全局变量是我们组所有人 -- 特别是我自己 -- 特别有负罪感的一件事。关于为啥全局变量不好的讨论是相当直接的:这增加了脚本和变量冲突的概率,而且源文件和命名空间本身都会充斥着数不清的命名模糊的变量。
Douglas Crockford 坚信一个Javascript应用的代码质量可以用其中使用的全局变量数来评价,越少越好。由于并不是什么都可以定义成local的(不过要诚实,其实你现在考虑的那个是可以的,别偷懒),你需要想办法整理你的变量以避免冲突,并把命名空间的膨胀减到最小。最简单的方法就是采用单变量或者把用到这些全局变量的模块尽可能减少。 Crockford提到YUI只用了一个全局变量,YAHOO。他在他的博文 "全局统治" 中讨论了更多的细节问题。
考虑这种情况:对于小型web应用,全局变量通常用于保存应用级的设置,可以用你的项目名或者settings作为命名去定义一个对象,这样总的来说会更好。
// 被污染的全局命名空间 var settingA = true; var settingB = false; var settingC = "test"; // 用 settings 作为对象命名 var settings = { settingA: true, settingB: false, settingC: "test" }
不过,如果我们可以通过避免使用全局变量来减少冲突概率,但是把命名空间标准化成一样的,岂不是会增加各个应用之间产生冲突的概率么?呃,这个担忧确实有道理。所以,建议你用自己特定的应用名作为全局变量的命名空间,或者用和jQuery采取的 $.noConflict() 模式相同的方法重新分配你的命名空间.
var myAppName = { settings: { settingA: true } } //访问全局变量 myAppName.settings.settingA; // true
驼峰法变量命名
JavaScript变量的驼峰法命名在大部分编程环境中都是作为标准的。有读者在评论中提出了唯一的例外,就是要用大写字母加下划线来指代常量。
var X_Position = obj.scrollLeft; var xPosition = obj.scrollLeft; // 更好,更简洁 SCENE_GRAVITY = 1; // 常量
循环的性能 - 缓存数组长度
循环估计是Javascript性能调优最重要的部分了。在一个循环内部节省个一两毫秒的,说不定总体就省出好几秒来了。这里有一招就是把数组的长度缓存,这样在循环里就无需每次迭代的时候都计算一遍了。
var toLoop = new Array(1000); for (var i = 0; i < toLoop.length; i++) { // 败家玩意 - 长度会反复算 1000 次你知道不? } for (var i = 0, len = toLoop.length; i < len; i++) { // 会过日子 - 长度只计算一次,然后缓存了 }
例外
如果你对一个数组做循环来查找并删除某个元素,这就会改变数组长度。任何时候你只要会在循环内部增加或删除元素来改变数组的长度,你就给自己带来了麻烦。这种情况下,你要么每次改变后重新设置数组长度,要么就别缓存它了。
循环的性能 - 使用 'break;' 和 'continue;'
跳过和跳出循环的能力对于避免开销很大的循环周期是非常有用的。
如果你是在循环内部查找,查找成功以后你会做什么?比如1000个元素的循环执行到一半,你就找到了你要的东西。你不管三七二十一,即使知道后面的if语句不会再有符合的机会,还是让循环继续把剩下的500个元素迭代完么?不!你应该跳出循环,必须的!
var bigArray = new Array(1000); for (var i = 0, len = bigArray.length; i < len; i++) { if (i === 500) { break; } console.log(i); // 这样只会输出 0 - 499 }
另一个问题是跳过某个特定的迭代,然后继续循环。虽然说类似于奇偶数这样的条件可以通过把 i++ 替换成 i + 2 的办法来管理,有些条件还是需要具体检测,然后触发跳过操作。任何能够避免执行完整的迭代过程的东西都是很有用的。
var bigArray = new Array(1000); for (var i = 0, len = bigArray.length; i < len; i++) { if (condition) { continue; } doCostlyStuff(); }
函数调用不要传输太多的参数
对于可读性而不是其他因素来说,下面这种方式真是糟透了:
function greet(name, language, age, gender, hairColour, eyeColour) { alert(name); }
下面的例子预先构建了一个对象作为参数,或者把内联对象传递过去,这样就好多了。
function greet(user) { alert(user.name); } greet({ name: "Bob", gender: "male" });
把 'this' 映射为 'self'
在编写面向对象(OO)Javascript代码的时候, 必须了解 this 的作用范围. 不管你用来构建伪类的设计模式是什么,对 this 的引用是指向一个实例的最简单办法。当你开始把jQuery的helper方法和你的伪类集成的时候,你就会注意到 this 的作用范围变化。
Bob.findFriend("Barry"); Person.prototype.findFriend = function(toFind) { // this = Bob $(this.friends).each(function() { // this = Bob.friends[i] if (this.name === toFind) { // this = Barry return this; } }); }
在上面的例子里, this 经历了从对 Bob 的引用,变成对他的朋友 Barry 的引用的过程。 了解 this 的取值在一段时间发生的变化是很重要的。在原型函数内部, this 指向所在伪类的当前实例(这里是 Bob )。而一旦我们进入 $.each() 循环, this 就会重新映射为被解析数组的第 i 个元素。
解决办法是把 this 的值重新映射为 self 或者 _self。虽然 self (不带下划线)并不是 保留字, 但它 确实是 window 对象的一个属性。虽然我上面用到 self 的例子是从jQuery源代码中挑的,但他们也认识到了这个错误,正打算 修正目前的状况 ,也就是改用 _self。我个人还是喜欢用 self ,不为别的,就因为它的简洁 -- 不过它可能会冒出一些非常令人困惑的bug。总之,用 self 有风险,使用需谨慎。
在下面的例子中,我会更好地利用在 $.each() helper 中提供的参数,同时重新映射 this 的值。
Bob.findFriend("Barry"); Person.prototype.findFriend = function(toFind) { // 就这一次用到了 "this" var _self = this; $(_self.friends).each(function(i,item) { if (item.name === toFind) { return item; } }); }
我能用 Boolean 吗?
布尔变量必须能够很容易通过命名来识别。可以用类似于 is, can 或者 has 的前缀来形成一个问句。
isEditing = true; obj.canEdit = true; user.hasPermission = true;
尽量减少重新绘制和重新布局
重新绘制和重新布局与重新渲染DOM的过程关联,这个过程会在特定属性或元素被改变时发生。重新绘制是在某个元素的外观被改变但没有对布局进行调整的情况下触发的。 Nicole Sullivan 在一篇全面的 博文 中把这些改变描述为诸如是否可见或背景色变化之类的样式改变。重新布局则是开销更大的操作,由调整页面布局的一些改变引发。例如增加或删除元素,改变某个元素的宽度或高度,甚至是改变浏览器窗口的大小。最糟糕的情况是重新布局导致先辈、兄弟和孩子节点元素也需要重新布局的多米诺骨牌效应。
毫无疑问,重新绘制和重新布局应该尽量避免,但是如何做到呢?
重新布局的例子
其实也不是说下面的代码就很糟糕啦。不过我们先假定数组 arr 有10个元素
var myList = document.getElementById("myList"); for (var i = 0, len = arr.length; i < len; i++) { myList.innerHTML += "<li>" + arr[i].title + "</li>"; //重新布局 -- 增加到元素 }
在上面的 for 循环里,每次迭代会触发一次重新布局。10次迭代就是10次重新布局。
现在考虑下面的代码:
var constructedHTML = ""; for (var i = 0, len = arr.length; i < len; i++) { constructedHTML += "<li>" + arr[i].title + "</li>"; //没有重新布局 - 增加到字符串 } document.getElementById("myList").innerHTML = constructedHTML; //在这里重新布局
在这个场景里,需要增加的元素是在一个字符串里构建的。循环里边没有产生任何重新布局,因为DOM并没有变化。只有当数组被完全循环完毕,构建的字符串被应用到某个对象的 innerHTML ,这才产生函数里唯一的一次重新布局。
有无数种重新布局和重新绘制是可以避免的,希望你幸运地了解了那些诀窍。这方面的阅读材料汗牛充栋,不过大部分的材料都会引用到 Nicole Sullivan的这篇 博文 ,这是一个完美的起点。除了这里的经验,在涉及到"web 3.0"和HTML5时代的多种技术术语的时候,还有其他重要的经验教训值得汲取。上面的分析直接适用于写jQuery代码。在捣腾 canvas 的时候这些原则也很重要,另外尽量保持帧频在30-60的范围内。
不要用微秒来产生唯一的ID
自打web开发早期开始,就流行一种产生唯一ID的方法。具体做法是把从1970年1月1日开始计算的微秒数加到你的静态ID后面,如下所示:
var myID = "static" + new Date().getTime();
这本来是相当万无一失的方法,因为即便两段这样的代码连续执行,在它们执行的间隙也会有几毫秒。可是现在新的浏览器带着新的Javascript引擎,伴随着一直在提升的主频。到现在,上面的代码产生相同的毫秒数的可能性会比产生间隙的可能性更大。
这会导致传统方法难以debug的bug。因为你的DOM是在运行中创建的,对页面源代码进行传统的测试无法确定多个重复ID的错误。Javascript和jQuery的错误处理机制会把第一个匹配的作为ID并忽略其他的重复ID。所以它甚至都不会抛出JS错误!
这样不行,唯一真正的方法是逐行设断点和log,但是如果断点的位置不对,你的毫秒数又不会冲突了!
好消息是有很多产生唯一ID的替代方法。学究气一点的说法是,计算机的随机数函数其实并不是真正随机的,因为它还是来源于系统时间,虽然这一点值得注意,但是随机数冲突的可能性是微乎其微的。
var myID = "static" + Math.round(Math.random() * 10000);
我个人更偏爱人工产生GUID方法。从技术角度说,GUID是根据你的硬件创建的,不过下面的Javascript函数做得相当棒。这是我从 stack overflow 的一个帖子 里偷来的,相当顺手的一个函数。
function S4() { return (((1+Math.random())*0x10000)|0).toString(16).substring(1); } function guid() { return (S4()+S4()+"-"+S4()+"-"+S4()+"-"+S4()+"-"+S4()+S4()+S4()); }
var myID = "static" + guid();
检测特性,而不是检测浏览器类型
用户的浏览器是否支持地理信息?用户的浏览器是否支持web works?HTML5 视频?HTML5 音频?答案曾经是这样的:
if ($.browser.msie) { // 哦,是IE啊,那肯定不支持 }
但是世界在快速变化。最新版的IE几乎能算是现代浏览器了,但它依旧给前端开发带来痛苦。更早版本的IE基本上和它之前的版本一样烂,这就让偷懒的Javascript程序员习惯于检测 if (ie) 然后执行一些微软专用的破语法。现在IE9已经废弃了这些专用函数,那些原来的 if (ie) 老古董就反而会坏事了。
那么,如果能检测每个特性而不用检测(既不可靠又能伪装的)user-agent,你觉得咋样?
如果你的回答是 "那相当靠谱", 那你就说对了。
用 Modernizr 吧,这是行业梦幻级大师Paul Irish参与开发的一个Javascript库。该库集广泛应用、轻量级和海量文档三大优势于一身,实施无需动脑,实为居家旅行、杀人灭口必备。它会产生一个 Modernizr 对象,其中包含了它所有检测测试的结果,这样检测某个特性的支持与否就和下面的例子一样简单:
// 检测浏览器是否支持canvas的老办法 if (!!document.createElement('canvas').getContext) { ... } // 用 Modernizr 检测 if (Modernizr.canvas) { ... }
使用可读的毫秒数
毫秒数的一种方便的写法是写成可读的。对于初学者这很棒,但是大部分情况下其实只是一个噱头。
// 这是3秒,30秒还是300秒啊? var timeout = 30000; // 增加了额外的计算开销,但是读和修改会更容易 var timeout = 30 * 1000;
关于jQuery代码
像疯狗一样串接
jQuery最好的特性之一就是它的函数串接。你可能已经用过一点,也许把一些简单的调用一个接一个串起来...但是你是否曾经像头疯狗一样在DOM里上蹿下跳地遍历呢?还是花点时间来熟悉一下 .end() 函数。等你从起始选择器开始在DOM里上蹿下跳的时候,这个函数会很关键。
$(".quote") .hide() .find("a").text("Click here").bind("click",doStuff).end() .parent().removeClass().addClass("testimonial").draggable().end() .fadeIn("slow");
上例中,每次我们完成对某个DOM对象的操作,要反向遍历DOM返回我们引用的原始对象的时候,就需要使用 .end() 函数。然后我们就顺藤摸瓜扎回原来DOM里的位置了。
使用 data-* 属性
你们当中那些已经写了很长时间Javascript(原生的,不是jQuery)代码的同学,很可能都熟悉各种属性吧。你们想办法设置它们,获取它们,或者滥用 rel 和 title ...
别说HTML5 或者 jQuery 没帮上忙哦。新的描述中允许在HTML元素中使用 data- 前缀来指明包含数据的属性,jQuery会把指定的字符串转换成正确的Javascript数据类型,这活干的非常漂亮。我们来创建一个带有某些数据属性的 DIV 。
<div id="test" data-is-bool="true" data-some-number="123"></div>
现在,即使我们的值被包装在引号里面,它们也不会被当做字符串处理:
typeof $("#test").data("isBool"); // boolean typeof $("#test").data("someNumber"); // number
特殊的大小写
要注意,要让这些代码片段正常工作,(HTML里的属性定义)必须使用小写字母,这很重要。不过如果你是一位很强的前端开发者,你还是会想用驼峰法来命名你的数据变量。正如在Javascript里很多地方出现的,前置的连接符意味着下一个字母会适用于驼峰法大写。不过,下面的例子里在HTML属性定义中使用驼峰法是 不行的 ,会让上面的Javascript代码返回 undefined。
不好使 :(
<div id="test" data-isBool="true" data-someNumber="123"></div>
好使 :)
<div id="test" data-is-bool="true" data-some-number="123"></div>
'.stop()' 停止协作和监听
把jQuery动画效果和鼠标事件绑定是基于web的现代用户交互方式中的关键部分,可是这方面即便某些最有名的网站也做得很蹩脚。这篇文章 提供了一个实现动画的直接例子并且演示了这些动画放在一起在视觉上会产生多么不和谐的效果。 好在这个问题可以利用一个函数前缀或在 $.animate 调用中加入一个参数来轻松解决。
在使用 $.animate 的时候, 可以在参数中加入 queue: false 来避免串接。诸如 $.fadeIn 或 $.slideDown 这样的动画快捷方式不接受 queue 设置,你必须用 $.stop 这个方法预先停止这些动画.。 在特定的场景下,需要某个动画直接停下,或跳转到变换的最终状态。推荐你先熟悉有关 clearQueue 和 jumpToEnd 这两个参数的相关 文档 ,因为老天在上,我没有其他办法帮你。
$("selector").stop(true,true).fadeOut(); $("selector").animate({ property: value }, { duration: 1000, queue: false }
优化你的选择器
jQuery 很高冷。它几乎无所不能,不过它目前还没法给你冲咖啡,我听说在2.0版的路线图里有才这个特性。你需要当心的一件事是别滥用它的 sizzleJS 选择器引擎的能力。想避免这种问题可以有两个策略:缓存选择器结果 以及 使用高效率的选择器。
缓存选择器结果
是每次你要修改一点东西的时候都先进行开销巨大的DOM查询,还是保存一份元素的索引?选择一目了然。
// before $(".quote a").bind("click", doStuff); // DOM查询 // now $(".quote a").addClass("quoteLink"); // DOM查询!! // later $(".quote a").fadeIn("slow"); // 又一次DOM查询!!!
忽略串接,这样做更好:
// before var $quoteLinks = $(".quote a"); // 只需一次DOM查询 $quoteLinks.bind("click", doStuff); // now $quoteLinks.addClass("quoteLink"); // later $quoteLinks.fadeIn("slow");
使用高效率的选择器
好了,jQuery/sizzleJS 可以轻松使用CSS3选择器,但是真正的开销是什么? 在这种场景下浏览器有可能会使用 document.querySelector(), 但是它也有可能分拆你的选择器字符串,然后手工去查询DOM。
// ID搜索是最快的查询方式,然后它获取孩子节点的列表,匹配其中class为'quotes'的元素 $("#quoteList").children(".quotes"); // 只在预先确定的bar元素下查找'foo'class $(".foo",bar);
'for' 循环总是比 'each()' 循环快
不管未来几年在浏览器开发领域会发生什么,本地的 for 循环永远会比jQuery的 $.each() 循环快。 当你思考jQuery到底是什么(把本地JS函数包装起来的一个库)这种高大上问题的时候,你就会开始认识到本地原生Javascript代码永远会更快。用库还是用原生,这是一个运行速度和编程速度之间的权衡。
很重要的一点是,对那些可能每秒调用数百次的性能关键的函数,总是要使用 for 循环。例如:
- 鼠标移动
- 时间间隔
- 循环内部的循环
CSS
理解盒子模型是关键
"盒子模型"对于理解浏览器如何渲染页面是关键性决定性的因素。对其复杂性的全面理解有助于奇迹般地简化你的工作。盒子模型描述了对HTML元素的物理维度进行计算的方式。如果一个块元素具有固定宽度,比如说100px,那么应该如何确定它的 padding, border 和 margin 呢?
很多网站都有深入的描述,但咱们简单点说:在遵循标准的浏览器中,border和padding是被放在指定宽度之外的。最好是用图形来解释。比如下列代码:
.foo { width: 150px; height: 150px; padding: 25px; border: 25px solid; margin: 20px; }
你可能估计的情况(Quirks 模式)
padding 和 border 都是往里算的,结果保持高度和宽度都是150px。
你看到的情况(遵循标准模式)
可是,实际上出来的宽度和高度都是250px。 也就是150px + (2 * 25) + (2 * 25)。
如果你觉得这个结果很奇怪,那你不是一个人(呃,你是人,只是说还有其他人也会这么想)。 现在手头有个修复办法,需要引入一个CSS属性叫 box-sizing,这个属性对于 IE8 及以上版本 都适用。它允许你选择计算元素维度的确切方式,这样就能救你于危难之中。具体支持的参数因浏览器而异,另外需要用到浏览器厂商的前缀,具体细节请参阅 caniuse.com 。
/* 旧方法 (178 + 20 + 2 = 200) */ .foo { width: 178px; padding: 10px; border: 1px; } /* 更好的方法 */ .foo { width: 200px; padding: 10px; border: 1px; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; }
虽然说你也总是可以对宽度进行心算,在各个像素数中减来减去(就像第一个方法做的那样),但在涉及到不同的宽度单位时(比如百分比或者EM),就没人能搞清楚到底该怎么做了。目前,除了把元素包在父元素中,以确保宽度和 padding/margin/borders 可以全部分开之外,也没有别的解决办法。
知道什么时候用float,什么时候用position
用table进行布局的时代过去了。现在要承认我们可以集中精力去理解float和position的工作原理。这里需要掌握一套特别的思维模型,我相信这件事最好是通过动手练习来进行。
用float从DOM中提取元素并强制它们靠到左边或右边,那是相当靠谱。它们已成为前端开发的后table布局时代的万金油,这可能是因为以前浏览器对于 display: inline 和 inline-block 的支持不力,还有对position的支持中冒出的 z-index bug。可现在就真的没有借口了。 inline-block 已经支持得很好了,简单的一点修正就能让它在 IE7 里应用。
谢天谢地,以前那些阻挠用CSS对元素进行绝对定位的争论都消亡了。理论上,定位属性可以让你在页面上以X和Y坐标放置元素,这种方式简单直接,Flash开发者们都应该很熟悉。
理解 Position
用CSS定位元素的时候,理解一个事实非常重要:定位的位置总是相对于离它最近的有定位属性的父元素而言的。人们刚开始用CSS的时候会有一个常见的误解,认为 position: absolute; 是相对页面的root元素定位的。 我觉得这种误解来源于某些情况下,元素没有任何父元素具备position样式 -- 在这种情况下,他们的结论是对的。这样向上遍历DOM树没有找到任何有定位样式的元素,就会定位到页面的根元素上。
那么,如果 position: absolute; 是把元素从他们所在的流中抽取出来,那你如何相对一个元素的父元素对它进行定位呢? 方法很直接。父元素需要定义 position: relative; 样式,然后所有的孩子元素就会按上、右、下、左的顺序依次摆放。利用这个知识,你会如何实现下面很直观的布局呢?
使用 float,你会需要把这些元素包在一个父元素中, 然后把.one float靠左,然后改动 .two 和 .three 的 float 和 margin 。最后你应该写出类似下面的东西:
.parent { /* ghetto clearfix */ width: 310px; overflow: auto; } .one { width: 200px; height: 210px; float: left; } .two { width: 100px; height: 100px; float: right; margin-bottom: 10px; } .three { width: 100px; height: 100px; float: right; }
正如我们前面所说,使用 position 让我们可以用很明确的方式,按照 X 和 Y 坐标把元素显示在屏幕上。 上面用float的方式会把页面上的长文字隔开,下面的方法则可以确保所有元素处于正常位置,无论页面上有什么内容。
.parent { position: relative; width: 310px; height: 210px; } .one, .two, .three { position: absolute; } .one { top: 0; left: 0; width: 200px; height: 210px; } .two { top: 0; right: 0; width: 100px; height: 100px; } .three { bottom: 0; right: 0; width: 100px; height: 100px; }
如前文所述,有些 z-index 的问题需要考虑。虽然上面的例子可能显得有点过分,不过一旦你开始思考定位,它会打开一个各种可能性的新世界.
留空
如果我们在单行和多行CSS参数的格式之间变来变去,CSS里的留空也会不一样。我不打算对这个说太细。
合适的空白
/* 不好 */ .selector {display:none;background:#ff0000;color:#000000;} /* 好 -- 单行 */ .selector { display: none; background: #ff0000; color: #000000; } /* 好 -- 多行 */ .selector { display: none; background: #ff0000; color: #000000; }
大括号不换行
.selector { display: none; background: #ff0000; color: #000000; }
子元素缩进
这个用不用就见仁见智了,我个人只会在单行定义的CSS文档中用这种格式。
.selector { display: none; background: #ff0000; color: #000000; } .selector a { text-decoration: none; } .selector span { font-weight: bold; }
组合并缩进浏览器厂商前缀属性
.selector { background: #FFF; border: 1px solid #000; color: #EAEAEA; -webkit-border-radius: 3px; -moz-border-radius: 3px; border-radius: 3px; }
CSS 速记格式
属性分组
把属性分组到一起是大大减少CSS文件大小的最有效方法。这里很重要的一点是要理解属性是如何排序的(顺时针 -- 上,右,下,左),以及如何进一步缩短它们(上和下,左和右)。
/* 逐个定义,太长了 */ padding-top: 1px; padding-right: 2px; padding-bottom: 1px; padding-left: 2px; /* 上,右,下,左,好很多 */ padding: 1px 2px 1px 2px; /* 上和下,左和右,最优 */ padding: 1px 2px;
从 0px 到英雄
给值为 0 的属性分配一个单位类型是多余的。一个元素是距离左边 0px 还是 0 elephants 根本不重要,只要知道它是贴着左边就行了。
/* 不好 */ padding: 0px 10px;
/* 好 */ padding: 0 10px;
注释块
对于在一个样式表里维护多个样式区域的任务,给大段CSS加上注释是很好的办法。显然这和单行CSS风格配合使用效果更佳,不过这个效果在多行CSS风格里也不是完全没用。注释里用破折号、等号还是下划线起强调作用就见仁见智了,不过下面是我喜欢的方式:
/* === HORIZONTAL NAV === */ #horizNav { width: 100%; display: block; } #horizNav li { display: block; float: left; position: relative; } #horizNav li a { display: block; height: 30px; text-decoration: none; } #horizNav li ul { display: none; position: absolute; top: 30; left: 0; } /* === HOME PAGE - CAROUSEL === */ #carousel { width: 960px; height: 150px; position: relative; } #carousel img { display: none; } #carousel .buttons { position: absolute; right: 10px; bottom: 10px; }
清除浮动
清除一个 <div> 过去意味着额外的DOM,因为这会涉及到增加一个额外的清除元素。更好的办法是给父元素设置明确的宽度('auto'并不是在所有浏览器和场景中有效)以及把overflow属性设为'auto'或者'hidden'。'hidden'显然兼容性更好,但在某些兼容IE的版本里'auto'的效果好一些。
HTML:
<div class="parentElement"> <div class="childElement"> I'm floated left! </div> I'm normal text that wraps around the float </div>
CSS:
.parentElement { width: 100%; overflow: hidden; } .childElement { float: left; }
有本项目的贡献者提醒我注意最新的clearfix。 micro clear-fix 被认为相当稳定且跨浏览器兼容,足以列入最新的HTML5 boilerplate发布了。 我 强烈 建议你去看看。虽然我不是浏览器特定CSS和 :after 这种伪元素的*粉丝,不过这个micro clearfix的确更健壮。它还能避免顶层margin缩回的问题。
垂直和水平居中
水平居中元素实际上不是什么高精尖的科技,我敢肯定你们大部分人都熟悉下面的代码片段:
.class { width: 960px; margin: 0 auto; }
前端开发者们使用这种代码很长时间了,也没搞明白为什么这种方式对垂直居中不起作用。从我的理解说,很重要的一点是记住父元素一般会有个 height: auto; 样式, 也没有垂直居中元素所需的100%高度。而应用 position: absolute; 能有效地把元素转移到定位模式,然后被设为auto的margin会自动帮助它调整位置,达到居中效果。
.exactMiddle { width: 100px; height: 100px; position: absolute; top: 0; right: 0; bottom: 0; left: 0; margin: auto; }
这种方法的不足之处包括在 IE6 和 IE7 中缺乏支持,以及当浏览器被缩小到比居中对象还小时不出现滚动条。 在 这个网页 里列出了更多的方法(现在这个是第4个),不过现在这个目前是最优方法。
在一个元素里垂直居中文字也是很直接的。如果文字是单行的,例如一个水平导航元素,你可以设置 line-height 为该元素的物理高度。
#horizNav li { height: 32px; line-height: 32px; }
检测特性,而不是检测浏览器类型
在前面关于Javascript特性检测的讨论中,检测到浏览器是 任何版本 的 IE 然后就运用某些属性的做法越来越成问题了。铁人 Paul Irish 引领了使用 IE 版本检测 方法来解决这些问题的大潮,但是 Modernizr 从那时起拯救了我们。 Modernizr 在 root <html> 元素里放入一些class,描述某些特性是否得到支持. 然后前沿的样式就可以很容易从这些class级联出来或者删除掉。
.my_elem { -webkit-box-shadow: 0 1px 2px rgba(0,0,0,0.25); -moz-box-shadow: 0 1px 2px rgba(0,0,0,0.25); box-shadow: 0 1px 2px rgba(0,0,0,0.25); } /* 如果 box shadow 不支持, 就应用 borders 属性 */ .no-boxshadow .my_elem { border: 1px solid #666; border-bottom-width: 2px; }
!important 不要乱用
依赖于 !important 标签是个危险的现象。非用它不可的情况屈指可数,而且是特殊情况。这些情况大抵是需要覆盖另外一套样式表,而你没法或者没权限编辑它。另一个场景是对元素的样式硬编码以防止Javascript在线产生的样式有更大优先级。而实际情况是 !important 往往被用做偷懒的快捷方式,让某个样式优先于其他的样式,这样做将来会产生很多问题。
对 !important 标签的大部分使用是可以避免的,只要更好地理解CSS选择器优先级以及如何更准确地定位元素。选择器越具体,被接受为适用样式的可能性就越大。下面来自 vanseodesign 的例子展示了具体化起作用的情况。
p { font-size: 12px; } p.bio { font-size: 14px; }
关于样式优先级, 他们的文章 在解释继承性方面比我能写出来的文章都好,所以请给它点个赞吧。
进取性向下兼容
值得注意的是,这段是我的个人观点,只适用于特定情况。在依赖老版本浏览器的大型商业项目或企业级解决方案中,进取性向下兼容的立场将不容易被接受。
进取性向下兼容的意思是如果某个特定的(老版本)浏览器无法渲染某个特定效果,则应直接忽略它。CSS3 按钮就是一个好例子。诸如 border-radius, box-shadow, text-shadow 和 gradients 这些效果会在先进的浏览器里显示出来。对于版本稍微老一点的浏览器,可以用一个 .PNG 图片作为无伤大雅的补救办法,而所有解决办法中最优雅的应该是针对IE6提供一个PNG-Fix,或者用filter 参数来代替 gradients 和 shadows等属性。 不过,在这种情况下,进取性向下兼容方式会让你忽略老版本浏览器,而在其中展示一个平面的还过得去的对象。
简单地说,进取性向下兼容说白了就是:如果你的浏览器渲染不了渐变色或盒子阴影,那是你运气不好。
虽然这不是对所有情况都理想,这种方法能确保项目按时交付,且核心产品是可用的,而不需依赖于对浏览器的破解办法。
CSS3及HTML5
这个话题我想我已经说的够多了。用 Modernizr 来检测特定的 HTML5 和 CSS3 特性是否可用。
@font-face的使用和滥用
在你考虑嵌入一套定制的字体之前,很重要的一点是你要查看 EULA 并检查是否允许web嵌入。 字体库厂商自然是不愿意让设计师和开发者有能力把字体库文件直接存放在服务器上,然后被熟练的终端用户拷贝走。某些厂商也禁止嵌入特定的文件类型,例如 .TTF 和 .OTF。
如果,经过慎重考虑,你相信想要的字体是可嵌入的,那就去看一下Font Squirrel的 @font-face 生成器。 它利用了 Fontspring的 防弹 @font-face 结构 并能自动生成所有需要的文件格式。
向下兼容
谢天谢地,浏览器对于它不支持的HTML5 和 CSS3 特性的处理已经达到了优雅的本色。加到 <input /> 标签的新类型例如 "email", "search" 等等在浏览器本地不支持的情况下一般会向下兼容为正常的 <input type="text" />。 类似的,不支持的CSS3 特性就不会出现,由高度和宽度媒体查询控制的响应式布局也不会被应用。
精巧的CSS3效果应该被应用为对使用现代浏览器的用户的一种奖励。
在下面的"资源"小节里包括了一些有助于让HTML5和CSS3功能在一批老版本浏览器中保持正常的库。
资源
下列的资源对于代码标准化和现代web页面的互动是很重要的。它们确保了CSS3 和 HTML5 特性在以前缺乏支持能力的一批浏览器中能够使用。
- jQuery JavaScript助手库
- jQuery UI 为用户交互/界面做着jQuery为JavaScript做的同样的事
- Modernizr 检测特性,不要检测浏览器!
- RespondJS 为老版本浏览器带来响应式布局
- @font-face Generator 全面的字体内嵌
- RaphaelJS 简单易用的跨浏览器向量图
- HTML5 Boilerplate 对于任何项目都是一个好的起点。不过即使是它的"精简"版也还是有点臃肿
- Twitter Bootstrap 让你能给简单的web应用迅速产生原型和样式
本文由 Tait Brown 编写 (@taitems),此中文版由 coderLMN 翻译。
上一篇: 总浮动时间和*浮动时间
推荐阅读
-
小白也能懂!微信小程序开发入门必知的基础知识
-
小白也能懂!揭秘Web前端开发中的JS事件委托(事件代理)是什么意思?
-
菜鸟也能懂的Apache JMeter压力测试工具安装与使用指南
-
SSM三大框架基础面试题-一、Spring篇 什么是Spring框架? Spring是一种轻量级框架,提高开发人员的开发效率以及系统的可维护性。 我们一般说的Spring框架就是Spring Framework,它是很多模块的集合,使用这些模块可以很方便地协助我们进行开发。这些模块是核心容器、数据访问/集成、Web、AOP(面向切面编程)、工具、消息和测试模块。比如Core Container中的Core组件是Spring所有组件的核心,Beans组件和Context组件是实现IOC和DI的基础,AOP组件用来实现面向切面编程。 Spring的6个特征: 核心技术:依赖注入(DI),AOP,事件(Events),资源,i18n,验证,数据绑定,类型转换,SpEL。 测试:模拟对象,TestContext框架,Spring MVC测试,WebTestClient。 数据访问:事务,DAO支持,JDBC,ORM,编组XML。 Web支持:Spring MVC和Spring WebFlux Web框架。 集成:远程处理,JMS,JCA,JMX,电子邮件,任务,调度,缓存。 语言:Kotlin,Groovy,动态语言。 列举一些重要的Spring模块? Spring Core:核心,可以说Spring其他所有的功能都依赖于该类库。主要提供IOC和DI功能。 Spring Aspects:该模块为与AspectJ的集成提供支持。 Spring AOP:提供面向切面的编程实现。 Spring JDBC:Java数据库连接。 Spring JMS:Java消息服务。 Spring ORM:用于支持Hibernate等ORM工具。 Spring Web:为创建Web应用程序提供支持。 Spring Test:提供了对JUnit和TestNG测试的支持。 谈谈自己对于Spring IOC和AOP的理解 IOC(Inversion Of Controll,控制反转)是一种设计思想: 在程序中手动创建对象的控制权,交由给Spring框架来管理。IOC在其他语言中也有应用,并非Spring特有。IOC容器实际上就是一个Map(key, value),Map中存放的是各种对象。 将对象之间的相互依赖关系交给IOC容器来管理,并由IOC容器完成对象的注入。这样可以很大程度上简化应用的开发,把应用从复杂的依赖关系中解放出来。IOC容器就像是一个工厂一样,当我们需要创建一个对象的时候,只需要配置好配置文件/注解即可,完全不用考虑对象是如何被创建出来的。在实际项目中一个Service类可能由几百甚至上千个类作为它的底层,假如我们需要实例化这个Service,可能要每次都搞清楚这个Service所有底层类的构造函数,这可能会把人逼疯。如果利用IOC的话,你只需要配置好,然后在需要的地方引用就行了,大大增加了项目的可维护性且降低了开发难度。 Spring中的bean的作用域有哪些? 1.singleton:该bean实例为单例 2.prototype:每次请求都会创建一个新的bean实例(多例)。 3.request:每一次HTTP请求都会产生一个新的bean,该bean仅在当前HTTP request内有效。 4.session:每一次HTTP请求都会产生一个新的bean,该bean仅在当前HTTP session内有效。 5.global-session:全局session作用域,仅仅在基于Portlet的Web应用中才有意义,Spring5中已经没有了。Portlet是能够生成语义代码(例如HTML)片段的小型Java Web插件。它们基于Portlet容器,可以像Servlet一样处理HTTP请求。但是与Servlet不同,每个Portlet都有不同的会话。 Spring中的单例bean的线程安全问题了解吗? 概念用于理解:大部分时候我们并没有在系统中使用多线程,所以很少有人会关注这个问题。单例bean存在线程问题,主要是因为当多个线程操作同一个对象的时候,对这个对象的非静态成员变量的写操作会存在线程安全问题。 有两种常见的解决方案(用于回答的点): 1.在bean对象中尽量避免定义可变的成员变量(不太现实)。 2.在类中定义一个ThreadLocal成员变量,将需要的可变成员变量保存在ThreadLocal(线程本地化对象)中(推荐的一种方式)。 ThreadLocal解决多线程变量共享问题(参考博客):https://segmentfault.com/a/1190000009236777 Spring中Bean的生命周期: 1.Bean容器找到配置文件中Spring Bean的定义。 2.Bean容器利用Java Reflection API创建一个Bean的实例。 3.如果涉及到一些属性值,利用set方法设置一些属性值。 4.如果Bean实现了BeanNameAware接口,调用setBeanName方法,传入Bean的名字。 5.如果Bean实现了BeanClassLoaderAware接口,调用setBeanClassLoader方法,传入ClassLoader对象的实例。 6.如果Bean实现了BeanFactoryAware接口,调用setBeanClassFacotory方法,传入ClassLoader对象的实例。 7.与上面的类似,如果实现了其他*Aware接口,就调用相应的方法。 8.如果有和加载这个Bean的Spring容器相关的BeanPostProcessor对象,执postProcessBeforeInitialization方法。 9.如果Bean实现了InitializingBean接口,执行afeterPropertiesSet方法。 10.如果Bean在配置文件中的定义包含init-method属性,执行指定的方法。 11.如果有和加载这个Bean的Spring容器相关的BeanPostProcess对象,执行postProcessAfterInitialization方法。 12.当要销毁Bean的时候,如果Bean实现了DisposableBean接口,执行destroy方法。 13.当要销毁Bean的时候,如果Bean在配置文件中的定义包含destroy-method属性,执行指定的方法。 Spring框架中用到了哪些设计模式? 1.工厂设计模式:Spring使用工厂模式通过BeanFactory和ApplicationContext创建bean对象。 2.代理设计模式:Spring AOP功能的实现。 3.单例设计模式:Spring中的bean默认都是单例的。 4.模板方法模式:Spring中的jdbcTemplate、hibernateTemplate等以Template结尾的对数据库操作的类,它们就使用到了模板模式。 5.包装器设计模式:我们的项目需要连接多个数据库,而且不同的客户在每次访问中根据需要会去访问不同的数据库。这种模式让我们可以根据客户的需求能够动态切换不同的数据源。 6.观察者模式:Spring事件驱动模型就是观察者模式很经典的一个应用。 7.适配器模式:Spring AOP的增强或通知(Advice)使用到了适配器模式、Spring MVC中也是用到了适配器模式适配Controller。 还有很多。。。。。。。 @Component和@Bean的区别是什么 1.作用对象不同。@Component注解作用于类,而@Bean注解作用于方法。 2.@Component注解通常是通过类路径扫描来自动侦测以及自动装配到Spring容器中(我们可以使用@ComponentScan注解定义要扫描的路径)。@Bean注解通常是在标有该注解的方法中定义产生这个bean,告诉Spring这是某个类的实例,当我需要用它的时候还给我。 3.@Bean注解比@Component注解的自定义性更强,而且很多地方只能通过@Bean注解来注册bean。比如当引用第三方库的类需要装配到Spring容器的时候,就只能通过@Bean注解来实现。 @Configuration public class AppConfig { @Bean public TransferService transferService { return new TransferServiceImpl; } } <beans> <bean id="transferService" class="com.kk.TransferServiceImpl"/> </beans> @Bean public OneService getService(status) { case (status) { when 1: return new serviceImpl1; when 2: return new serviceImpl2; when 3: return new serviceImpl3; } } 将一个类声明为Spring的bean的注解有哪些? 声明bean的注解: @Component 组件,没有明确的角色 @Service 在业务逻辑层使用(service层) @Repository 在数据访问层使用(dao层) @Controller 在展现层使用,控制器的声明 注入bean的注解: @Autowired:由Spring提供 @Inject:由JSR-330提供 @Resource:由JSR-250提供 *扩:JSR 是 java 规范标准 Spring事务管理的方式有几种? 1.编程式事务:在代码中硬编码(不推荐使用)。 2.声明式事务:在配置文件中配置(推荐使用),分为基于XML的声明式事务和基于注解的声明式事务。 Spring事务中的隔离级别有哪几种? 在TransactionDefinition接口中定义了五个表示隔离级别的常量:ISOLATION_DEFAULT:使用后端数据库默认的隔离级别,Mysql默认采用的REPEATABLE_READ隔离级别;Oracle默认采用的READ_COMMITTED隔离级别。ISOLATION_READ_UNCOMMITTED:最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读。ISOLATION_READ_COMMITTED:允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生ISOLATION_REPEATABLE_READ:对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。ISOLATION_SERIALIZABLE:最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。 Spring事务中有哪几种事务传播行为? 在TransactionDefinition接口中定义了八个表示事务传播行为的常量。 支持当前事务的情况:PROPAGATION_REQUIRED:如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。PROPAGATION_SUPPORTS: 如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。PROPAGATION_MANDATORY: 如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。(mandatory:强制性)。 不支持当前事务的情况:PROPAGATION_REQUIRES_NEW: 创建一个新的事务,如果当前存在事务,则把当前事务挂起。PROPAGATION_NOT_SUPPORTED: 以非事务方式运行,如果当前存在事务,则把当前事务挂起。PROPAGATION_NEVER: 以非事务方式运行,如果当前存在事务,则抛出异常。 其他情况:PROPAGATION_NESTED: 如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于PROPAGATION_REQUIRED。 二、SpringMVC篇 什么是Spring MVC ?简单介绍下你对springMVC的理解? Spring MVC是一个基于Java的实现了MVC设计模式的请求驱动类型的轻量级Web框架,通过把Model,View,Controller分离,将web层进行职责解耦,把复杂的web应用分成逻辑清晰的几部分,简化开发,减少出错,方便组内开发人员之间的配合。 Spring MVC的工作原理了解嘛? image.png Springmvc的优点: (1)可以支持各种视图技术,而不仅仅局限于JSP; (2)与Spring框架集成(如IoC容器、AOP等); (3)清晰的角色分配:前端控制器(dispatcherServlet) , 请求到处理器映射(handlerMapping), 处理器适配器(HandlerAdapter), 视图解析器(ViewResolver)。 (4) 支持各种请求资源的映射策略。 Spring MVC的主要组件? (1)前端控制器 DispatcherServlet(不需要程序员开发) 作用:接收请求、响应结果,相当于转发器,有了DispatcherServlet 就减少了其它组件之间的耦合度。 (2)处理器映射器HandlerMapping(不需要程序员开发) 作用:根据请求的URL来查找Handler (3)处理器适配器HandlerAdapter 注意:在编写Handler的时候要按照HandlerAdapter要求的规则去编写,这样适配器HandlerAdapter才可以正确的去执行Handler。 (4)处理器Handler(需要程序员开发) (5)视图解析器 ViewResolver(不需要程序员开发) 作用:进行视图的解析,根据视图逻辑名解析成真正的视图(view) (6)视图View(需要程序员开发jsp) View是一个接口, 它的实现类支持不同的视图类型(jsp,freemarker,pdf等等) springMVC和struts2的区别有哪些? (1)springmvc的入口是一个servlet即前端控制器(DispatchServlet),而struts2入口是一个filter过虑器(StrutsPrepareAndExecuteFilter)。 (2)springmvc是基于方法开发(一个url对应一个方法),请求参数传递到方法的形参,可以设计为单例或多例(建议单例),struts2是基于类开发,传递参数是通过类的属性,只能设计为多例。 (3)Struts采用值栈存储请求和响应的数据,通过OGNL存取数据,springmvc通过参数解析器是将request请求内容解析,并给方法形参赋值,将数据和视图封装成ModelAndView对象,最后又将ModelAndView中的模型数据通过reques域传输到页面。Jsp视图解析器默认使用jstl。 SpringMVC怎么样设定重定向和转发的? (1)转发:在返回值前面加"forward:",譬如"forward:user.do?name=method4" (2)重定向:在返回值前面加"redirect:",譬如"redirect:http://www.baidu.com" SpringMvc怎么和AJAX相互调用的? 通过Jackson框架就可以把Java里面的对象直接转化成Js可以识别的Json对象。具体步骤如下 : (1)加入Jackson.jar (2)在配置文件中配置json的映射 (3)在接受Ajax方法里面可以直接返回Object,List等,但方法前面要加上@ResponseBody注解。 如何解决POST请求中文乱码问题,GET的又如何处理呢? (1)解决post请求乱码问题: 在web.xml中配置一个CharacterEncodingFilter过滤器,设置成utf-8; <filter> <filter-name>CharacterEncodingFilter</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>utf-8</param-value> </init-param> </filter> <filter-mapping> <filter-name>CharacterEncodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> (2)get请求中文参数出现乱码解决方法有两个: ①修改tomcat配置文件添加编码与工程编码一致,如下: <ConnectorURIEncoding="utf-8" connectionTimeout="20000" port="8080" protocol="HTTP/1.1" redirectPort="8443"/> ②另外一种方法对参数进行重新编码: String userName = new String(request.getParamter("userName").getBytes("ISO8859-1"),"utf-8") ISO8859-1是tomcat默认编码,需要将tomcat编码后的内容按utf-8编码。 Spring MVC的异常处理 ? 统一异常处理: Spring MVC处理异常有3种方式: (1)使用Spring MVC提供的简单异常处理器SimpleMappingExceptionResolver; (2)实现Spring的异常处理接口HandlerExceptionResolver 自定义自己的异常处理器; (3)使用@ExceptionHandler注解实现异常处理; 统一异常处理的博客:https://blog.csdn.net/ctwy291314/article/details/81983103 SpringMVC的控制器是不是单例模式,如果是,有什么问题,怎么解决? 是单例模式,所以在多线程访问的时候有线程安全问题,不要用同步,会影响性能的,解决方案是在控制器里面不能写成员变量。(此题目类似于上面Spring 中 第5题 有两种解决方案) SpringMVC常用的注解有哪些? @RequestMapping:用于处理请求 url 映射的注解,可用于类或方法上。用于类上,则表示类中的所有响应请求的方法都是以该地址作为父路径。 @RequestBody:注解实现接收http请求的json数据,将json转换为java对象。 @ResponseBody:注解实现将conreoller方法返回对象转化为json对象响应给客户。 SpingMvc中的控制器的注解一般用那个,有没有别的注解可以替代? 一般用@Controller注解,也可以使用@RestController,@RestController注解相当于@ResponseBody + @Controller,表示是表现层,除此之外,一般不用别的注解代替。 如果在拦截请求中,我想拦截get方式提交的方法,怎么配置? 可以在@RequestMapping注解里面加上method=RequestMethod.GET。 怎样在方法里面得到Request,或者Session? 直接在方法的形参中声明request,SpringMVC就自动把request对象传入。 如果想在拦截的方法里面得到从前台传入的参数,怎么得到? 直接在形参里面声明这个参数就可以,但必须名字和传过来的参数一样。 如果前台有很多个参数传入,并且这些参数都是一个对象的,那么怎么样快速得到这个对象? 直接在方法中声明这个对象,SpringMVC就自动会把属性赋值到这个对象里面。 SpringMVC中函数的返回值是什么? 返回值可以有很多类型,有String, ModelAndView。ModelAndView类把视图和数据都合并的一起的。 SpringMVC用什么对象从后台向前台传递数据的? 通过ModelMap对象,可以在这个对象里面调用put方法,把对象加到里面,前台就可以拿到数据。 怎么样把ModelMap里面的数据放入Session里面? 可以在类上面加上@SessionAttributes注解,里面包含的字符串就是要放入session里面的key。 SpringMvc里面拦截器是怎么写的: 有两种写法,一种是实现HandlerInterceptor接口,另外一种是继承适配器类,接着在接口方法当中,实现处理逻辑;然后在SpringMvc的配置文件中配置拦截器即可: <!-- 配置SpringMvc的拦截器 --> <mvc:interceptors> <!-- 配置一个拦截器的Bean就可以了 默认是对所有请求都拦截 --> <bean id="myInterceptor" class="com.zwp.action.MyHandlerInterceptor"></bean> <!-- 只针对部分请求拦截 --> <mvc:interceptor> <mvc:mapping path="/modelMap.do" /> <bean class="com.zwp.action.MyHandlerInterceptorAdapter" /> </mvc:interceptor> </mvc:interceptors> 注解原理: 注解本质是一个继承了Annotation的特殊接口,其具体实现类是Java运行时生成的动态代理类。我们通过反射获取注解时,返回的是Java运行时生成的动态代理对象。通过代理对象调用自定义注解的方法,会最终调用AnnotationInvocationHandler的invoke方法。该方法会从memberValues这个Map中索引出对应的值。而memberValues的来源是Java常量池 三、Mybatis篇 什么是MyBatis? MyBatis是一个可以自定义SQL、存储过程和高级映射的持久层框架。 讲下MyBatis的缓存 MyBatis的缓存分为一级缓存和二级缓存,一级缓存放在session里面,默认就有, 二级缓存放在它的命名空间里,默认是不打开的,使用二级缓存属性类需要实现Serializable序列化接口, 可在它的映射文件中配置<cache/> Mybatis是如何进行分页的?分页插件的原理是什么? 1)Mybatis使用RowBounds对象进行分页,也可以直接编写sql实现分页,也可以使用Mybatis的分页插件。 2)分页插件的原理:实现Mybatis提供的接口,实现自定义插件,在插件的拦截方法内拦截待执行的sql,然后重写sql。 举例:select * from student,拦截sql后重写为:select t.* from (select * from student)t limit 0,10 简述Mybatis的插件运行原理,以及如何编写一个插件? 1)Mybatis仅可以编写针对ParameterHandler、ResultSetHandler、StatementHandler、 Executor这4种接口的插件,Mybatis通过动态代理, 为需要拦截的接口生成代理对象以实现接口方法拦截功能, 每当执行这4种接口对象的方法时,就会进入拦截方法, 具体就是InvocationHandler的invoke方法,当然, 只会拦截那些你指定需要拦截的方法。 2)实现Mybatis的Interceptor接口并复写intercept方法, 然后在给插件编写注解,指定要拦截哪一个接口的哪些方法即可, 记住,别忘了在配置文件中配置你编写的插件。 Mybatis动态sql是做什么的?都有哪些动态sql?能简述一下动态sql的执行原理不? 1)Mybatis动态sql可以让我们在Xml映射文件内, 以标签的形式编写动态sql,完成逻辑判断和动态拼接sql的功能。 2)Mybatis提供了9种动态sql标签:trim|where|set|foreach|if|choose|when|otherwise|bind。 3)其执行原理为,使用OGNL从sql参数对象中计算表达式的值, 根据表达式的值动态拼接sql,以此来完成动态sql的功能。 #{}和${}的区别是什么? 1)#{}是预编译处理,${}是字符串替换。 2)Mybatis在处理#{}时,会将sql中的#{}替换为?号,调用PreparedStatement的set方法来赋值(有效的防止SQL注入); 3)Mybatis在处理${}时,就是把${}替换成变量的值。 为什么说Mybatis是半自动ORM映射工具?它与全自动的区别在哪里? Hibernate属于全自动ORM映射工具, 使用Hibernate查询关联对象或者关联集合对象时, 可以根据对象关系模型直接获取,所以它是全自动的。 而Mybatis在查询关联对象或关联集合对象时, 需要手动编写sql来完成,所以,称之为半自动ORM映射工具。 Mybatis是否支持延迟加载?如果支持,它的实现原理是什么? 1)Mybatis仅支持association关联对象和collection关联集合对象的延迟加载, association指的就是一对一,collection指的就是一对多查询。 在Mybatis配置文件中, 可以配置是否启用延迟加载lazyLoadingEnabled=true|false。 2)它的原理是,使用CGLIB创建目标对象的代理对象, 当调用目标方法时,进入拦截器方法, 比如调用a.getB.getName, 拦截器invoke方法发现a.getB是null值, 那么就会单独发送事先保存好的查询关联B对象的sql, 把B查询上来,然后调用a.setB(b), 于是a的对象b属性就有值了, 接着完成a.getB.getName方法的调用。 这就是延迟加载的基本原理。 MyBatis与Hibernate有哪些不同? 1)Mybatis和hibernate不同,它不完全是一个ORM框架, 因为MyBatis需要程序员自己编写Sql语句, 不过mybatis可以通过XML或注解方式灵活配置要运行的sql语句, 并将java对象和sql语句映射生成最终执行的sql, 最后将sql执行的结果再映射生成java对象。 2)Mybatis学习门槛低,简单易学,程序员直接编写原生态sql, 可严格控制sql执行性能,灵活度高,非常适合对关系数据模型要求不高的软件开发, 例如互联网软件、企业运营类软件等,因为这类软件需求变化频繁, 一但需求变化要求成果输出迅速。但是灵活的前提是mybatis无法做到数据库无关性, 如果需要实现支持多种数据库的软件则需要自定义多套sql映射文件,工作量大。 3)Hibernate对象/关系映射能力强,数据库无关性好, 对于关系模型要求高的软件(例如需求固定的定制化软件) 如果用hibernate开发可以节省很多代码,提高效率。 但是Hibernate的缺点是学习门槛高,要精通门槛更高, 而且怎么设计O/R映射,在性能和对象模型之间如何权衡, 以及怎样用好Hibernate需要具有很强的经验和能力才行。 总之,按照用户的需求在有限的资源环境下只要能做出维护性、 扩展性良好的软件架构都是好架构,所以框架只有适合才是最好。 MyBatis的好处是什么? 1)MyBatis把sql语句从Java源程序中独立出来,放在单独的XML文件中编写, 给程序的维护带来了很大便利。 2)MyBatis封装了底层JDBC API的调用细节,并能自动将结果集转换成Java Bean对象, 大大简化了Java数据库编程的重复工作。 3)因为MyBatis需要程序员自己去编写sql语句, 程序员可以结合数据库自身的特点灵活控制sql语句, 因此能够实现比Hibernate等全自动orm框架更高的查询效率,能够完成复杂查询。 简述Mybatis的Xml映射文件和Mybatis内部数据结构之间的映射关系? Mybatis将所有Xml配置信息都封装到All-In-One重量级对象Configuration内部。 在Xml映射文件中,<parameterMap>标签会被解析为ParameterMap对象, 其每个子元素会被解析为ParameterMapping对象。 <resultMap>标签会被解析为ResultMap对象, 其每个子元素会被解析为ResultMapping对象。 每一个<select>、<insert>、<update>、<delete> 标签均会被解析为MappedStatement对象, 标签内的sql会被解析为BoundSql对象。 什么是MyBatis的接口绑定,有什么好处? 接口映射就是在MyBatis中任意定义接口,然后把接口里面的方法和SQL语句绑定, 我们直接调用接口方法就可以,这样比起原来了SqlSession提供的方法我们可以有更加灵活的选择和设置. 接口绑定有几种实现方式,分别是怎么实现的? 接口绑定有两种实现方式,一种是通过注解绑定,就是在接口的方法上面加 上@Select@Update等注解里面包含Sql语句来绑定, 另外一种就是通过xml里面写SQL来绑定,在这种情况下, 要指定xml映射文件里面的namespace必须为接口的全路径名. 什么情况下用注解绑定,什么情况下用xml绑定? 当Sql语句比较简单时候,用注解绑定;当SQL语句比较复杂时候,用xml绑定,一般用xml绑定的比较多 MyBatis实现一对一有几种方式?具体怎么操作的? 有联合查询和嵌套查询,联合查询是几个表联合查询,只查询一次, 通过在resultMap里面配置association节点配置一对一的类就可以完成; 嵌套查询是先查一个表,根据这个表里面的结果的外键id, 去再另外一个表里面查询数据,也是通过association配置, 但另外一个表的查询通过select属性配置。 Mybatis能执行一对一、一对多的关联查询吗?都有哪些实现方式,以及它们之间的区别? 能,Mybatis不仅可以执行一对一、一对多的关联查询, 还可以执行多对一,多对多的关联查询,多对一查询, 其实就是一对一查询,只需要把selectOne修改为selectList即可; 多对多查询,其实就是一对多查询,只需要把selectOne修改为selectList即可。 关联对象查询,有两种实现方式,一种是单独发送一个sql去查询关联对象, 赋给主对象,然后返回主对象。另一种是使用嵌套查询,嵌套查询的含义为使用join查询, 一部分列是A对象的属性值,另外一部分列是关联对象B的属性值, 好处是只发一个sql查询,就可以把主对象和其关联对象查出来。 MyBatis里面的动态Sql是怎么设定的?用什么语法? MyBatis里面的动态Sql一般是通过if节点来实现,通过OGNL语法来实现, 但是如果要写的完整,必须配合where,trim节点,where节点是判断包含节点有 内容就插入where,否则不插入,trim节点是用来判断如果动态语句是以and 或or 开始,那么会自动把这个and或者or取掉。 Mybatis是如何将sql执行结果封装为目标对象并返回的?都有哪些映射形式? 第一种是使用<resultMap>标签,逐一定义列名和对象属性名之间的映射关系。 第二种是使用sql列的别名功能,将列别名书写为对象属性名, 比如T_NAME AS NAME,对象属性名一般是name,小写, 但是列名不区分大小写,Mybatis会忽略列名大小写,
-
菜鸟也能懂!用Putty轻松连接阿里云服务器的方法
-
菜鸟也能懂的Logistic回归分析入门指南
-
菜鸟也能懂的前端开发入门教程
-
菜鸟也能懂!从入门到放弃的CTF二进制分析(之二)-彻底搞懂gdb的使用方法
-
菜鸟也能懂!阿里云服务器部署宝塔Linux面板的详细步骤教程
-
菜鸟也能懂的IDEA入门指南