JS编程建议——48:慎用正则表达式修剪字符串
建议48:慎用正则表达式修剪字符串(1)使用两个子表达式修剪字符串去除字符串首尾的空格是一个简单而常见的任务,但到目前为止JavaScript 还没有实现它。正则表达式允许用很少的代码实现一个修剪函数,最好的全面解决方案可能是使用两个子表达式:一个用于去除头部空格,另一个用于去除尾部空格。这样处理简单而快速,特别是处理长字符串时。if(!String.prototype.trim) {
String.prototype.trim = function() {
return this.replace(/^\s+/, "").replace(/\s+$/, "");
}
}var str = " tn test string ".trim();alert(str == "test string"); // alerts "true"使用if语句进行检测,如果已经存在trim原生函数,则不要覆盖trim原生函数,因为原生函数进行了优化后通常远远快于自定义函数。使用上面代码在Firefox浏览器中大约有35%的性能提升(或多或少依赖于目标字符串的长度和内容)。将/s+$/(第二个正则表达式)替换成/ss*$/。虽然这两个正则表达式的功能完全相同,但是Firefox浏览器却为那些以非量词字元开头的正则表达式提供额外的优化。在其他浏览器上,差异不显著,或者优化完全不同。然而,改变正则表达式,在字符串开头匹配/^ss*/不会产生明显差异,因为^锚需要“照顾”那些快速作废的非匹配位置(避免一个轻微的性能差异,因为在一个长字符串中可能产生上千次匹配尝试)。(2)使用一个正则表达式修剪字符串事实上,除这里列出的方法外还有许多其他方法,可以写一个正则表达式来修剪字符串,但在处理长字符串时,这种方法执行速度总比用两个简单的表达式要慢。String.prototype.trim = function() {
return this.replace(/^\s+|\s+$/g, "");
}这可能是最通常的解决方案。它通过分支功能合并了两个简单的正则表达式,并使用/g(全局)标记替换所有匹配,而不只是第一个匹配(当目标字符串首尾都有空格时将匹配两次)。这并不是一个“可怕”的方法,但在对长字符串操作时,它比使用两个简单的子表达式要慢,因为两个分支选项都要测试每个字符位置。String.prototype.trim = function() {
return this.replace(/^\s*([\s\S]*?)\s*$/, "$1");
}这个正则表达式的工作原理是匹配整个字符串,捕获从第一个到最后一个非空格字符之间的序列,记入后向引用1。然后使用后向引用1 替代整个字符串,就留下了这个字符串的修剪版本。这个方法概念简单,但捕获组中的“懒惰”量词使正则表达式进行了许多额外操作(如回溯),因此在操作长目标字符串时很慢。在进入正则表达式捕获组时,[sS]类的“懒惰”量词?要求捕获组尽可能地减少重复次数。因此,这个正则表达式每匹配一个字符,都要停下来尝试匹配余下的s$模板。如果由于字符串当前位置之后存在非空格字符而导致匹配失败,正则表达式将匹配一个或多个字符,更新后向引用,然后再次尝试匹配模板的剩余部分。String.prototype.trim = function() {
return this.replace(/^\s*([\s\S]*\S)?\s*$/, "$1");
}这个表达式与上一个很像,但出于性能原因以“贪婪”量词取代了“懒惰”量词。为确保捕获组只匹配到最后一个非空格字符,必须尾随一个S。然而,由于正则表达式必须匹配全部由空格组成的字符串,整个捕获组通过尾随一个?量词而成为可选组。在此,[sS]中的“贪婪”量词“”表示重复方括号中的任意字符模板直至字符串结束。然后,正则表达式每次回溯一个字符,直到它能够匹配后面的S,或者直到回溯到第一个字符而匹配整个组(之后它跳过这个组)。如果尾部空格不比其他字符串更多,通过一个表达修剪的方案通常比前面那些使用“懒惰”量词的方案更快。事实上,这个方案在IE、Safari、Chrome和Opera浏览器上执行速度如此之快,甚至超过使用两个子表达式的方案,是因为这些浏览器包含特殊优化,专门服务于为字符类匹配任意字符的“贪婪”重复操作,正则表达式引擎直接跳到字符串末尾而不检查中间的字符(尽管回溯点必须被记下来),然后适当回溯。不幸的是,这种方法在Firefox 和Opera 9 浏览器上执行得非常慢,所以到目前为止,使用两个子表达式仍然是更好的跨浏览器方案。String.prototype.trim = function() {
return this.replace(/^\s*(\S*(\s+\S+)*)\s*$/, "$1");
}这是一个相当普遍的方法,但没有很好的理由使用它,因为它在所有浏览器上都是这里所列出的所有方法中执行得最慢的一个。这类似于最后两个正则表达式,它匹配整个字符串然后用打算保留的部分替换这个字符串,因为内部组每次只匹配一个单词,正则表达式必须执行大量的离散步骤。修剪短字符串时性能冲击并不明显,但处理包含多个词的长字符串时,这个正则表达式可以成为影响性能的一个问题。将内部组修改为一个非捕获组,例如,将(s+S+)修改为(?:s+S+),在Opera、IE和Chrome 浏览器上缩减了大约20%~45%的处理时间,在Safari 和Firefox 浏览器上也有轻微改善。尽管如此,一个非捕获组不能完全代换这个实现。注意,外部组不能转换为非捕获组,因为它在被替换的字符串中被引用了。虽然正则表达式的执行速度很快,但是没有它们帮助时修剪字符串的性能还是值得考虑的。例如:String.prototype.trim = function() {
var start = 0,
end = this.length - 1,
ws = " \n\r\t\f\x0b\xa0\u1680\u180e\u2000\u2001\u2002\u2003
\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u200b\u2028\u2029\u202f
\u205f\u3000\ufeff";
while (ws.indexOf(this.charAt(start)) > -1) {
start++;
}
while (end > start && ws.indexOf(this.charAt(end)) > -1) {
end--;
}
return this.slice(start, end + 1);
}在上面代码中,ws变量包括在ECMAScript v5中定义的所有空白字符。出于效率方面的考虑,在得到修剪区的起始和终止位置之前避免复制字符串的任何部分。当字符串末尾只有少量空格时,这种情况使正则表达式处于无序状态。原因是,尽管正则表达式很好地去除了字符串头部的空格,却不能同样快速地修剪长字符串的尾部。一个正则表达式不能跳到字符串的末尾而不考虑沿途字符。正因如此,在第二个while循环中从字符串末尾向前查找一个非空格字符。虽然上面代码不受字符串总长度影响,但是它有自己的弱点—长的头尾空格,因为循环检查字符是不是空格在效率上不如正则表达式所使用的优化过的搜索代码。(3)正则表达式与非正则表达式结合起来修剪字符串最后一个办法是将正则表达式与非正则表达式两者结合起来,用正则表达式修剪头部空格,用非正则表达式方法修剪尾部字符。String.prototype.trim = function() {
var str = this.replace(/^\s+/, ""),
end = str.length - 1,
ws = /\s/;
while (ws.test(str.charAt(end))) {
end--;
}
return str.slice(0, end + 1);
}当只修剪一个空格时,此混合方法非常快,同时去除了性能上的风险,如以长空格开头的字符串,完全由空格组成的字符串(尽管它在处理尾部长空格的字符串时仍具有弱点)。注意:此方案在循环中使用正则表达式检测字符串尾部的字符是否为空格,虽然使用正则表达式增加了一点性能负担,但是它允许根据浏览器定义空格字符列表,以保持简短和兼容性。所有修剪方法总的趋势:在基于正则表达式的方案中,字符串总长比修剪掉的字符数量更影响性能;而非正则表达式方案从字符串末尾反向查找,不受字符串总长的影响,但明显受到修剪空格数量的影响。简单地使用两个子正则表达式在所有浏览器上处理不同内容和长度的字符串时,均表现出稳定的性能,因此可以说这种方案是最全面的解决方案。混合解决方案在处理长字符串时特别快,其代价是代码稍长,在某些浏览器上处理尾部长空格时存在弱点。
JavaScript性能优化 DOM编程
最近在研读《高性能JavaScript》,在此做些简单记录。示例代码可在此处查看到。
一、DOM
1)DOM和JavaScript
文档对象模型(DOM)是一个独立于语言的,用于操作XML和HTML文档的程序接口(API)。
浏览器通常会把DOM和JavaScript独立实现。例如Chrome中使用Webkit的WebCore库渲染页面,用V8作为JavaScript引擎。
访问DOM天生就慢,将DOM和JavaScript比喻为两个岛屿,两处同行要收过桥费,ECMAScript访问DOM的次数越多,过桥费越贵,因此推荐的做法是尽可能减少过桥的次数。
2)性能测试
下图是对两段代码的性能测试,可在此处查看在线性能测试。
1. 第一段每一次循环都直接用DOM赋值
2. 第二段是先将内容缓存到局部变量中,最后使用一次DOM赋值。
测试结果以每秒钟执行测试代码的次数(Ops/sec)显示,这个数值越大越好。
除了这个结果外,同时会显示测试过程中的统计误差,以及相对最好的慢了多少(%),统计误差也是非常重要的指标。
二、选择器API
有时候为了得到需要的元素列表,需要组合调用getElementById等并遍历返回的节点,但这种繁密的过程效率低下。
使用CSS选择器是一种定位节点的便利途径,querySelectorAll就是DOM的原生方法。
//DOM组合API
var elements = document.getElementById('menu').getElementsByTagName('a');
//替换为简便的CSS选择器
var elements = document.querySelectorAll('#menu a');
上面的例子不能体现效率的区别, 后面又改写了一个。在codepen中书写了一个例子,可以在线查看。
//从列表中选出CSS类是one的节点
var elements = document.querySelectorAll('#menu a.one');
var elements = document.getElementById('menu').getElementsByTagName('a'), list=[];
for(var i=0, len=elements.length; i<len; i++) {
if(elements[i].className == 'one') {
list.push(elements[i]);
}
}
三、重绘与重排
在《CSS动画与JavaScript动画》中层提到过页面渲染的一般过程为JavaScript > 计算样式 > 布局 > 绘制 > 渲染层合并。
Layout(重排)和Paint(重绘)是整个环节中最为耗时的两环,所以我们尽量避免着这两个环节。
当DOM的变化影响了元素的几何属性(宽和高)将会发生重排(reflow);
完成重排后,浏览器会重新绘制受影响的部分到屏幕中,此过程为重绘(repaint)。
1)重排何时发生
1. 添加或删除可见的DOM元素
2. 元素位置改变
3. 元素尺寸改变(包括外边距、内边距、边框宽度、宽、高等属性)
4. 内容改变,例如文本改变或图片被不同尺寸的替换掉。
5. 页面渲染器初始化。
6. 浏览器窗口尺寸改变。
2)批量执行重排
下面代码看上去会重排3次,但其实只会重排1次,大多数浏览器通过队列化修改和批量显示优化重排版过程。
//渲染树变化的排队和刷新
var ele = document.getElementById('myDiv');
ele.style.borderLeft = '1px';
ele.style.borderRight = '2px';
ele.style.padding = '5px';
但下列操作将会强迫队列刷新并要求所有计划改变的部分立刻应用:
offsetTop, offsetLeft, offsetWidth, offsetHeight
scrollTop, scrollLeft, scrollWidth, scrollHeight
clientTop, clientLeft, clientWidth, clientHeight
getComputedStyle() (currentStyle in IE)(在 IE 中此函数称为 currentStyle)
像offsetHeight属性需要返回最新的布局信息,因此浏览器不得不执行渲染队列中的“待处理变化”并触发重排以返回正确的值。
对于尺寸坐标相关的信息可以参考《JavaScript中尺寸、坐标》。
3)最小化重绘和重排
1. cssText和class
cssText可以一次设置多个CSS属性。class也可以一次性设置,并且更清晰,更易于维护,但有前提条件,就是不依赖于运行逻辑和计算的情况。
// cssText
ele.style.cssText = 'border-left: 1px; border-right: 2px; padding: 5px;';
// class
ele.className = 'active';
在《JavaScript特性(attribute)、属性(property)和样式(style)》详细介绍了CSS相关的JS操作。
2. 批量修改DOM
2.1 隐藏元素display:none,应用修改,重新显示display:block。
2.2 使用文档片段fragment,在片段上操作节点,再拷贝回文档。
//文档片段(fragment)
var fragment = document.createDocumentFragment();
var li = document.createElement('li');
li.innerHTML = 'banana';
fragment.appendChild(li);
document.getElementById('fruit').appendChild(fragment);
2.3 将原始元素拷贝到一个脱离文档的节点中(例如position:absolute),修改副本,完成后再替换原始元素。
四、其它章节性能介绍
1)第一章 加载和执行
将<script>标签放到页面底部,也就是</body>闭合标签之前。
多种无阻塞下载JavaScript的方法:
1. 使用<script>标签的defer属性。页面解析到<script>标签时开始下载,但并不会执行,直到DOM加载完(onload事件触发前)。
2. 动态创建<script>元素来下载并执行代码。无论在何时启动下载,文件的下载和执行过程不会阻塞页面其它进程,但返回的代码通常会立刻执行。
3. 使用XHR对象下载JavaScript代码并注入页面中。优点是下载后不会自动执行,所有主流浏览器都支持,但不能跨域下载。
2)第二章 数据存取
1. 每遇到一个变量,就会经历一次标识符解析的过程,以决定在哪里获取和存储数据。在执行环境的作用域中,标识符所在的位置越深,读写速度也就越慢。因此函数中读写局部变量是最快的,读写全局变量是最慢的。
2. 由于对象成员可能包含其它成员,例如window.location.href。每次遇到点操作符,嵌套成员会导致JavaScript引擎搜索全部对象成员。对象成员嵌套越深,读取速度越慢。location.href就比window.location.href快。
3)第五章 字符串与正则表达式
str = str + "one";//性能高
str = "one" + str;//性能差
1. 除IE外,浏览器会简单的将第二个字符串拷贝到第一个的后面,如果变量str很大的话,就会出现性能损耗(内存占用)就会很高。
2. 正则优化包括:减少分支数量,缩小分支范围;使用非捕获数组;只捕获感兴趣的文本以减少后期处理;使用合适的量词;化繁为简,分解复杂的正则;
4)第六章 快速响应的用户界面
使用定时器让出时间片段,分割大型任务,在文章《JavaScript定时器分析》中有具体分析。
5)第七章 Ajax
Mutipart XHR允许客户端只用一个HTTP请求就可以从服务器向客户端传送多个资源。
将资源文件(CSS、HTML、JavaScript、Base64编码图片)打包成一个由双方约定的字符串分隔符,发送到客户端。
然后用JavaScript代码处理这个字符串,根据“mime-type”类型和传入的头信息解析每个资源。
6)第九章 构建并部署高性能JavaScript应用
1. 合并JavaScript文件以减少HTTP请求数。
2. 压缩JavaScript文件。
3. 在服务器端压缩JavaScript文件(Gzip编码)。
4. 正确设置HTTP响应头来缓存JavaScript文件,通过向文件名增加时间戳避免缓存问题。
5. 使用CDN(Content Delivery Network)提供JavaScript文件。
第八章 编程实践内容比较多,单独令出来作为一节。
五、第八章 编程实践
1)使用Object/Array直接量
代码例下:
var myObject = {
name: "pwstrick",
age: 29
};
var myArr = ["pwstrick", 29];
2)避免重复工作
也就是惰性模式。减少每次代码执行时的重复性分支判断,通过对对象重定义来屏蔽原对象中的分支判断。
惰性模式分为两种:第一种文件加载后立即执行对象方法来重定义,第二种是当第一次使用方法对象时来重定义。可参考在线demo代码。
在文章《JavaScript设计模式》中有更多的设计模式介绍。
3)位运算
1. 用位运算取代纯数学操作,比如对2取模digit%2可以判断偶数与奇数。
2. 位掩码技术,使用单个数字的每一位来判断选项是否成立。掩码中每个选项的值都是2的幂。例如:
var OPTION_A = 1, OPTION_B = 2, OPTION_C = 4, OPTION_D = 8, OPTION_E = 16;
//用按位或运算创建一个数字来包含多个设置选项
var options = OPTION_A | OPTION_C | OPTION_D;
//接下来可以用按位与操作来判断给定的选项是否可用
//选项A是否在列表中
if(options & OPTION_A) {
//...
}
3. 用按位左移(<<)做乘法,用按位右移做除法(>>),例如digit*2可以替换成digit<<2。
4)原生方法
无论你的代码如何优化,都比不上JavaScript引擎提供的原生方法快。
1. 数学运算用内置的Math对象中提供的方法。
2. 用原生的CSS选择器查找DOM节点,querySelector或querySelectorAll。
参考资料:
高性能JavaScript
利用jsPerf优化Web应用的性能
高性能JavaScript DOM编程
读《高性能javascript》笔记(一)
高性能JavaScript 重排与重绘
分类: JavaScript,前端性能
本文转自 咖啡机(K.F.J) 博客园博客,原文链接:http://www.cnblogs.com/strick/p/6769634.html ,如需转载请自行联系原作者
JavaScript性能优化 DOM编程
最近在研读《高性能JavaScript》,在此做些简单记录。示例代码可在此处查看到。
一、DOM
1)DOM和JavaScript
文档对象模型(DOM)是一个独立于语言的,用于操作XML和HTML文档的程序接口(API)。
浏览器通常会把DOM和JavaScript独立实现。例如Chrome中使用Webkit的WebCore库渲染页面,用V8作为JavaScript引擎。
访问DOM天生就慢,将DOM和JavaScript比喻为两个岛屿,两处同行要收过桥费,ECMAScript访问DOM的次数越多,过桥费越贵,因此推荐的做法是尽可能减少过桥的次数。
2)性能测试
下图是对两段代码的性能测试,可在此处查看在线性能测试。
1. 第一段每一次循环都直接用DOM赋值
2. 第二段是先将内容缓存到局部变量中,最后使用一次DOM赋值。
测试结果以每秒钟执行测试代码的次数(Ops/sec)显示,这个数值越大越好。
除了这个结果外,同时会显示测试过程中的统计误差,以及相对最好的慢了多少(%),统计误差也是非常重要的指标。
二、选择器API
有时候为了得到需要的元素列表,需要组合调用getElementById等并遍历返回的节点,但这种繁密的过程效率低下。
使用CSS选择器是一种定位节点的便利途径,querySelectorAll就是DOM的原生方法。
//DOM组合API
var elements = document.getElementById('menu').getElementsByTagName('a');
//替换为简便的CSS选择器
var elements = document.querySelectorAll('#menu a');
上面的例子不能体现效率的区别, 后面又改写了一个。在codepen中书写了一个例子,可以在线查看。
//从列表中选出CSS类是one的节点
var elements = document.querySelectorAll('#menu a.one');
var elements = document.getElementById('menu').getElementsByTagName('a'), list=[];
for(var i=0, len=elements.length; i<len; i++) {
if(elements[i].className == 'one') {
list.push(elements[i]);
}
}
三、重绘与重排
在《CSS动画与JavaScript动画》中层提到过页面渲染的一般过程为JavaScript > 计算样式 > 布局 > 绘制 > 渲染层合并。
Layout(重排)和Paint(重绘)是整个环节中最为耗时的两环,所以我们尽量避免着这两个环节。
当DOM的变化影响了元素的几何属性(宽和高)将会发生重排(reflow);
完成重排后,浏览器会重新绘制受影响的部分到屏幕中,此过程为重绘(repaint)。
1)重排何时发生
1. 添加或删除可见的DOM元素
2. 元素位置改变
3. 元素尺寸改变(包括外边距、内边距、边框宽度、宽、高等属性)
4. 内容改变,例如文本改变或图片被不同尺寸的替换掉。
5. 页面渲染器初始化。
6. 浏览器窗口尺寸改变。
2)批量执行重排
下面代码看上去会重排3次,但其实只会重排1次,大多数浏览器通过队列化修改和批量显示优化重排版过程。
//渲染树变化的排队和刷新
var ele = document.getElementById('myDiv');
ele.style.borderLeft = '1px';
ele.style.borderRight = '2px';
ele.style.padding = '5px';
但下列操作将会强迫队列刷新并要求所有计划改变的部分立刻应用:
offsetTop, offsetLeft, offsetWidth, offsetHeight
scrollTop, scrollLeft, scrollWidth, scrollHeight
clientTop, clientLeft, clientWidth, clientHeight
getComputedStyle() (currentStyle in IE)(在 IE 中此函数称为 currentStyle)
像offsetHeight属性需要返回最新的布局信息,因此浏览器不得不执行渲染队列中的“待处理变化”并触发重排以返回正确的值。
对于尺寸坐标相关的信息可以参考《JavaScript中尺寸、坐标》。
3)最小化重绘和重排
1. cssText和class
cssText可以一次设置多个CSS属性。class也可以一次性设置,并且更清晰,更易于维护,但有前提条件,就是不依赖于运行逻辑和计算的情况。
// cssText
ele.style.cssText = 'border-left: 1px; border-right: 2px; padding: 5px;';
// class
ele.className = 'active';
在《JavaScript特性(attribute)、属性(property)和样式(style)》详细介绍了CSS相关的JS操作。
2. 批量修改DOM
2.1 隐藏元素display:none,应用修改,重新显示display:block。
2.2 使用文档片段fragment,在片段上操作节点,再拷贝回文档。
//文档片段(fragment)
var fragment = document.createDocumentFragment();
var li = document.createElement('li');
li.innerHTML = 'banana';
fragment.appendChild(li);
document.getElementById('fruit').appendChild(fragment);
2.3 将原始元素拷贝到一个脱离文档的节点中(例如position:absolute),修改副本,完成后再替换原始元素。
四、其它章节性能介绍
1)第一章 加载和执行
将<script>标签放到页面底部,也就是</body>闭合标签之前。
多种无阻塞下载JavaScript的方法:
1. 使用<script>标签的defer属性。页面解析到<script>标签时开始下载,但并不会执行,直到DOM加载完(onload事件触发前)。
2. 动态创建<script>元素来下载并执行代码。无论在何时启动下载,文件的下载和执行过程不会阻塞页面其它进程,但返回的代码通常会立刻执行。
3. 使用XHR对象下载JavaScript代码并注入页面中。优点是下载后不会自动执行,所有主流浏览器都支持,但不能跨域下载。
2)第二章 数据存取
1. 每遇到一个变量,就会经历一次标识符解析的过程,以决定在哪里获取和存储数据。在执行环境的作用域中,标识符所在的位置越深,读写速度也就越慢。因此函数中读写局部变量是最快的,读写全局变量是最慢的。
2. 由于对象成员可能包含其它成员,例如window.location.href。每次遇到点操作符,嵌套成员会导致JavaScript引擎搜索全部对象成员。对象成员嵌套越深,读取速度越慢。location.href就比window.location.href快。
3)第五章 字符串与正则表达式
str = str + "one";//性能高
str = "one" + str;//性能差
1. 除IE外,浏览器会简单的将第二个字符串拷贝到第一个的后面,如果变量str很大的话,就会出现性能损耗(内存占用)就会很高。
2. 正则优化包括:减少分支数量,缩小分支范围;使用非捕获数组;只捕获感兴趣的文本以减少后期处理;使用合适的量词;化繁为简,分解复杂的正则;
4)第六章 快速响应的用户界面
使用定时器让出时间片段,分割大型任务,在文章《JavaScript定时器分析》中有具体分析。
5)第七章 Ajax
Mutipart XHR允许客户端只用一个HTTP请求就可以从服务器向客户端传送多个资源。
将资源文件(CSS、HTML、JavaScript、Base64编码图片)打包成一个由双方约定的字符串分隔符,发送到客户端。
然后用JavaScript代码处理这个字符串,根据“mime-type”类型和传入的头信息解析每个资源。
6)第九章 构建并部署高性能JavaScript应用
1. 合并JavaScript文件以减少HTTP请求数。
2. 压缩JavaScript文件。
3. 在服务器端压缩JavaScript文件(Gzip编码)。
4. 正确设置HTTP响应头来缓存JavaScript文件,通过向文件名增加时间戳避免缓存问题。
5. 使用CDN(Content Delivery Network)提供JavaScript文件。
第八章 编程实践内容比较多,单独令出来作为一节。
五、第八章 编程实践
1)使用Object/Array直接量
代码例下:
var myObject = {
name: "pwstrick",
age: 29
};
var myArr = ["pwstrick", 29];
2)避免重复工作
也就是惰性模式。减少每次代码执行时的重复性分支判断,通过对对象重定义来屏蔽原对象中的分支判断。
惰性模式分为两种:第一种文件加载后立即执行对象方法来重定义,第二种是当第一次使用方法对象时来重定义。可参考在线demo代码。
在文章《JavaScript设计模式》中有更多的设计模式介绍。
3)位运算
1. 用位运算取代纯数学操作,比如对2取模digit%2可以判断偶数与奇数。
2. 位掩码技术,使用单个数字的每一位来判断选项是否成立。掩码中每个选项的值都是2的幂。例如:
var OPTION_A = 1, OPTION_B = 2, OPTION_C = 4, OPTION_D = 8, OPTION_E = 16;
//用按位或运算创建一个数字来包含多个设置选项
var options = OPTION_A | OPTION_C | OPTION_D;
//接下来可以用按位与操作来判断给定的选项是否可用
//选项A是否在列表中
if(options & OPTION_A) {
//...
}
3. 用按位左移(<<)做乘法,用按位右移做除法(>>),例如digit*2可以替换成digit<<2。
4)原生方法
无论你的代码如何优化,都比不上JavaScript引擎提供的原生方法快。
1. 数学运算用内置的Math对象中提供的方法。
2. 用原生的CSS选择器查找DOM节点,querySelector或querySelectorAll。
本文转自 咖啡机(K.F.J) 博客园博客,原文链接:http://www.cnblogs.com/strick/p/6769634.html,如需转载请自行联系原作者
JavaScript性能优化 DOM编程
一、DOM
1)DOM和JavaScript
文档对象模型(DOM)是一个独立于语言的,用于操作XML和HTML文档的程序接口(API)。
浏览器通常会把DOM和JavaScript独立实现。例如Chrome中使用Webkit的WebCore库渲染页面,用V8作为JavaScript引擎。
访问DOM天生就慢,将DOM和JavaScript比喻为两个岛屿,两处同行要收过桥费,ECMAScript访问DOM的次数越多,过桥费越贵,因此推荐的做法是尽可能减少过桥的次数。
2)性能测试
下图是对两段代码的性能测试,可在此处查看在线性能测试。
1. 第一段每一次循环都直接用DOM赋值
2. 第二段是先将内容缓存到局部变量中,最后使用一次DOM赋值。
测试结果以每秒钟执行测试代码的次数(Ops/sec)显示,这个数值越大越好。
除了这个结果外,同时会显示测试过程中的统计误差,以及相对最好的慢了多少(%),统计误差也是非常重要的指标。
二、选择器API
有时候为了得到需要的元素列表,需要组合调用getElementById等并遍历返回的节点,但这种繁密的过程效率低下。
使用CSS选择器是一种定位节点的便利途径,querySelectorAll就是DOM的原生方法。
//DOM组合APIvar elements = document.getElementById('menu').getElementsByTagName('a');//替换为简便的CSS选择器var elements = document.querySelectorAll('#menu a');
上面的例子不能体现效率的区别, 后面又改写了一个。在codepen中书写了一个例子,可以在线查看。
//从列表中选出CSS类是one的节点var elements = document.querySelectorAll('#menu a.one');var elements = document.getElementById('menu').getElementsByTagName('a'), list=[];for(var i=0, len=elements.length; i<len; i++) { if(elements[i].className == 'one') {
list.push(elements[i]);
}
}
三、重绘与重排
在《CSS动画与JavaScript动画》中层提到过页面渲染的一般过程为JavaScript > 计算样式 > 布局 > 绘制 > 渲染层合并。
Layout(重排)和Paint(重绘)是整个环节中最为耗时的两环,所以我们尽量避免着这两个环节。
当DOM的变化影响了元素的几何属性(宽和高)将会发生重排(reflow);
完成重排后,浏览器会重新绘制受影响的部分到屏幕中,此过程为重绘(repaint)。
1)重排何时发生
1. 添加或删除可见的DOM元素
2. 元素位置改变
3. 元素尺寸改变(包括外边距、内边距、边框宽度、宽、高等属性)
4. 内容改变,例如文本改变或图片被不同尺寸的替换掉。
5. 页面渲染器初始化。
6. 浏览器窗口尺寸改变。
2)批量执行重排
下面代码看上去会重排3次,但其实只会重排1次,大多数浏览器通过队列化修改和批量显示优化重排版过程。
//渲染树变化的排队和刷新var ele = document.getElementById('myDiv');
ele.style.borderLeft = '1px';
ele.style.borderRight = '2px';
ele.style.padding = '5px';
但下列操作将会强迫队列刷新并要求所有计划改变的部分立刻应用:
offsetTop, offsetLeft, offsetWidth, offsetHeight
scrollTop, scrollLeft, scrollWidth, scrollHeight
clientTop, clientLeft, clientWidth, clientHeight
getComputedStyle() (currentStyle in IE)(在 IE 中此函数称为 currentStyle)
像offsetHeight属性需要返回最新的布局信息,因此浏览器不得不执行渲染队列中的“待处理变化”并触发重排以返回正确的值。
对于尺寸坐标相关的信息可以参考《JavaScript中尺寸、坐标》。
3)最小化重绘和重排
1. cssText和class
cssText可以一次设置多个CSS属性。class也可以一次性设置,并且更清晰,更易于维护,但有前提条件,就是不依赖于运行逻辑和计算的情况。
// cssTextele.style.cssText = 'border-left: 1px; border-right: 2px; padding: 5px;';// classele.className = 'active';
在《JavaScript特性(attribute)、属性(property)和样式(style)》详细介绍了CSS相关的JS操作。
2. 批量修改DOM
2.1 隐藏元素display:none,应用修改,重新显示display:block。
2.2 使用文档片段fragment,在片段上操作节点,再拷贝回文档。
//文档片段(fragment)var fragment = document.createDocumentFragment();var li = document.createElement('li');
li.innerHTML = 'banana';
fragment.appendChild(li);
document.getElementById('fruit').appendChild(fragment);
2.3 将原始元素拷贝到一个脱离文档的节点中(例如position:absolute),修改副本,完成后再替换原始元素。
四、其它章节性能介绍
1)第一章 加载和执行
将<script>标签放到页面底部,也就是</body>闭合标签之前。
多种无阻塞下载JavaScript的方法:
1. 使用<script>标签的defer属性。页面解析到<script>标签时开始下载,但并不会执行,直到DOM加载完(onload事件触发前)。
2. 动态创建<script>元素来下载并执行代码。无论在何时启动下载,文件的下载和执行过程不会阻塞页面其它进程,但返回的代码通常会立刻执行。
3. 使用XHR对象下载JavaScript代码并注入页面中。优点是下载后不会自动执行,所有主流浏览器都支持,但不能跨域下载。
2)第二章 数据存取
1. 每遇到一个变量,就会经历一次标识符解析的过程,以决定在哪里获取和存储数据。在执行环境的作用域中,标识符所在的位置越深,读写速度也就越慢。因此函数中读写局部变量是最快的,读写全局变量是最慢的。
2. 由于对象成员可能包含其它成员,例如window.location.href。每次遇到点操作符,嵌套成员会导致JavaScript引擎搜索全部对象成员。对象成员嵌套越深,读取速度越慢。location.href就比window.location.href快。
3)第五章 字符串与正则表达式
str = str + "one";//性能高str = "one" + str;//性能差
1. 除IE外,浏览器会简单的将第二个字符串拷贝到第一个的后面,如果变量str很大的话,就会出现性能损耗(内存占用)就会很高。
2. 正则优化包括:减少分支数量,缩小分支范围;使用非捕获数组;只捕获感兴趣的文本以减少后期处理;使用合适的量词;化繁为简,分解复杂的正则;
4)第六章 快速响应的用户界面
使用定时器让出时间片段,分割大型任务,在文章《JavaScript定时器分析》中有具体分析。
5)第七章 Ajax
Mutipart XHR允许客户端只用一个HTTP请求就可以从服务器向客户端传送多个资源。
将资源文件(CSS、HTML、JavaScript、Base64编码图片)打包成一个由双方约定的字符串分隔符,发送到客户端。
然后用JavaScript代码处理这个字符串,根据“mime-type”类型和传入的头信息解析每个资源。
6)第九章 构建并部署高性能JavaScript应用
1. 合并JavaScript文件以减少HTTP请求数。
2. 压缩JavaScript文件。
3. 在服务器端压缩JavaScript文件(Gzip编码)。
4. 正确设置HTTP响应头来缓存JavaScript文件,通过向文件名增加时间戳避免缓存问题。
5. 使用CDN(Content Delivery Network)提供JavaScript文件。
第八章 编程实践内容比较多,单独令出来作为一节。
五、第八章 编程实践
1)使用Object/Array直接量
代码例下:
var myObject = {
name: "pwstrick",
age: 29 };var myArr = ["pwstrick", 29];
2)避免重复工作
也就是惰性模式。减少每次代码执行时的重复性分支判断,通过对对象重定义来屏蔽原对象中的分支判断。
惰性模式分为两种:第一种文件加载后立即执行对象方法来重定义,第二种是当第一次使用方法对象时来重定义。可参考在线demo代码。
在文章《JavaScript设计模式》中有更多的设计模式介绍。
3)位运算
1. 用位运算取代纯数学操作,比如对2取模digit%2可以判断偶数与奇数。
2. 位掩码技术,使用单个数字的每一位来判断选项是否成立。掩码中每个选项的值都是2的幂。例如:
var OPTION_A = 1, OPTION_B = 2, OPTION_C = 4, OPTION_D = 8, OPTION_E = 16;//用按位或运算创建一个数字来包含多个设置选项var options = OPTION_A | OPTION_C | OPTION_D;//接下来可以用按位与操作来判断给定的选项是否可用//选项A是否在列表中if(options & OPTION_A) { //...}
3. 用按位左移(<<)做乘法,用按位右移做除法(>>),例如digit*2可以替换成digit<<2。
4)原生方法
无论你的代码如何优化,都比不上JavaScript引擎提供的原生方法快。
1. 数学运算用内置的Math对象中提供的方法。
2. 用原生的CSS选择器查找DOM节点,querySelector或querySelectorAll。
本文转自zsdnr 51CTO博客,原文链接:http://blog.51cto.com/12942149/1944619,如需转载请自行联系原作者
正则表达式的一些探索(偏JavaScript)
简单的探索下正则表达式的相关知识,首先先了解下正则表达式的引擎和匹配过程区别,再试着掌握如何在场景中编写正则表达式,再然后探索下根据上文已知的原理和编写过程怎么去优化正则表达式,最后给出一些js里正则相关的小扩展。
基础及原理简单介绍
了解一下正则表达式的正则引擎(正则表达式使用的理论模型是有穷自动机,具体实现称为正则引擎)。
正则引擎分有DFA(确定型有穷自动机)的和NFA(非确定型有穷自动机)的实现,根据编译相关知识的描述,两者是可以等价转换的。NFA又分传统型和POSIX标准,下面是三者一个简单的对照表:
-/- 回溯 忽略优先量词 捕获型括号 应用算法
DFA N N N 文本主导
传统型NFA Y ️ Y ️ Y 表达式主导
POSIX NFA Y N ️ Y 表达式主导
回溯指的是:一个类似枚举的搜索尝试过程,在搜索尝试过程中寻找问题的解,当在分歧点选择一条路径,记住另一/多条路径供之后使用,在选择路径后的探索过程中发现不满足求解条件时,就返回到分歧点,尝试其他路径(回溯遵循后进先出原则/LIFO-last in first out)。
忽略优先量词指的是:支持尽可能少的匹配结果。
捕获型括号指的是:匹配并捕获结果,获取组号。
文本主导指的是:匹配搜索过程中的每个字符都对匹配过程进行控制(这样的结果就是:虽然我不能回溯,但我速度快呀 - DFA的傲娇...)。
表达式主导指的是:表达式中的控制权在不同的表达式元素之间转换,然后与文本进行匹配。
三者还有一些其他的差异,如编译阶段的处理及匹配最左最长原则等,由于文章里以js的正则为准,就不做更多区别的介绍,详情可参考书籍《精通正则表达式》,不过值得注意的是本书中的内容不一定完全适合各个语言或平台中的正则表达式...
正则匹配的过程规则总结:
1. 优先选择最左端的匹配结果(匹配是从左到右开始的)
匹配先从需要查找的字符串的第一个字符尝试匹配;
如果匹配到结果,则返回结果;
如果匹配不到结果,则就需要从字符串的第二个字符位置开始重新匹配;
以此类推,直到尝试过所有的字符串位置作为匹配起始点进行匹配后,返回匹配失败结果。
如 forever 中匹配是否存在 ever:
第一轮:从f开始匹配,fore不匹配ever;
第二轮:从o开始匹配,orev不匹配ever;
第三轮:从r开始匹配,reve不匹配ever;
第四轮:从e开始匹配,ever匹配ever,获得ever匹配起始点(此处为3)的索引和匹配结果。
2. 标准量词总是匹配优先的(*、+、?、{m,n})
匹配优先:尽可能多的匹配;忽略优先:尽可能少的匹配。
标准量词总是匹配优先的,如:foo 针对表达式取 fo|foo(下面匹配基于JavaScript)
匹配优先结果:foo,/f[o]{1,2}/
忽略优先结果:fo,/f[o]{1,2}?/,加?后表示忽略优先量词
更多原理和匹配过程详解可以参考文章"浅析正则表达式—(原理篇)"
在需求场景中构建正则表达式
要构建正则表达式,得先了解一些正则表达式的一些普通字符和元字符。这些可以参照"百度百科-正则表达式_符号",该表对于正则使用生疏的同学可以作字典参考,这里对于这些字符就不多做赘述。
以几个例子来说明下在有正则需求的场景中该如何来写表达式。
例子一:查询句子中某些单词出现的次数。
首先分析情况得出:先给出个大致形状,/pattern/g ,经测试发现:
"the weather's often cold in the north and windy in the east".match(/the/g).length
// output 4
输出4,明显不对,这时候还需要考虑单词的边界情况,需要在pattern两边加上限制\b,并且注意不需要匹配单词边界,于是得出:
/\bthe\b/g
再测一下:
"the weather's often cold in the north and windy in the east".match(/\bthe\b/g).length
// output 3
正确。
例子二:比如需要给数字加 ",(逗号)" 形成货币格式的场景,如1234567,变成1,234,567。
首先分析情况得出:逗号应该加在"左边存在数字,右边的数字个数是3的倍数"。
这里用到正则的"非获取匹配,反向肯定预查 : (?<=pattern)" 和 "非获取匹配,正向肯定预查 : (?=pattern)",非获取匹配指的是匹配但不获取匹配值,正/反向肯定预查指的是正/反向匹配时在任何匹配条件的字符串开始处匹配查找字符串。
先反向肯定预查"左边存在数字"的组合,给出(?<=\d);再正向肯定预查"右边数字个数是3的倍数的组合",给出(?=(\d{3})+$);结合两者,加上全局的匹配 g,得出:
/(?<=\d)(?=(\d{3})+$)/g
测试一下效果:
'123456789'.replace(/(?<=\d)(?=(\d{3})+$)/g,',')
// output 123,456,789
'23456789'.replace(/(?<=\d)(?=(\d{3})+$)/g,',')
// output 23,456,789
'1234'.replace(/(?<=\d)(?=(\d{3})+$)/g,',')
// output 1,234
例子三:比如想把雷老板的Are you ok 歌词处理一下。
首先不想替换掉"...",其次注意转义字符,最后结合需要取的是引号内包含且非"的字符,于是得出:
/\"([^\"\.]*)\"/g
测试一下:
var str = `
Are you ok?
Auther Mr.Lei
1. "Thank you!"
2. "Are you ok?"
3. "Hello"
4. "Thank you"
5. "Thank you every much"
6. "How are you Indian Mi fans?"
7. "Do you like Mi 4i?"
8. "..."
`;
var reg = /\"([^\"\.]*)\"/g;
var replaceStr = str.replace(reg,'\"???\"');
// output
// Are you ok?
// Auther Mr.Lei
// 1. "???"
// 2. "???"
// 3. "???"
// 4. "???"
// 5. "???"
// 6. "???"
// 7. "???"
// 8. "..."
如何写正则表达式的总结:先写出明确的匹配条件,再根据需求去组合或添加细节。
正则表达式优化的一些探索
《精通正则表达式》书中和网上查找了不少资料,并且根据对NFA的理解,列出了一些符合理论的优化建议(不排除更多可能):
1. 减少或者优化判断分支;
2. 精确匹配条件(字符和量词);
3. 限制匹配优先的作用范围,如+和*的区别,+减少了多选结构的回溯次数;
4. 节省引用(使用非获取匹配);
5. 将复杂的多条件正则拆分成多个简单的单条件正则;
6. 锚点的优化,^(a|b)而非(^a|^b);
7. 量词等价转换的效率差异(因语言而异);
8. 使用固化分组(在支持的情况下,js并不支持);
9. 使用变量存储正则表达式(减少正则编译过程);
10. 还有更多细节...
《精通正则表达式》的第五及第六章都有涉及这些优化的介绍,网上google/baidu也是不少资料和案例。
根据其主要做的优化过程大致可总结出以下几点(不排除更多可能):
1. 减少匹配的回溯次数,减少时间;
2. 节省引用(使用非获取匹配),减少空间;
3. 正则表达式自身优化以达到编译最优;
4. 也许更多吧...
以上是书籍和网上给出的优化建议,给出的案例大多是针对的perl和PHP的,在JavaScript中并不适用,为了验证js里的正则如何优化,下面是我给出的一些测试结果...
基于JavaScript的正则表达式...这一小节给不出完整的测试代码,因为在多个浏览器和nodejs端跑了很多测试代码,给定的情况是不同的...
但单独某个端或者浏览器给出的结果是稳定的,以下是给出Chrome和Firefox浏览器和Nodejs各跑100000次(原先是1W长度字符串跑1000次,后面为了效果更明显最终增加为10W长度字符串跑10W次)测试的结果总结...还有Safari浏览器、360浏览器、QQ浏览器的测试结果也统一不了.
Chrome浏览器下:
使用.、*这类元字符或范围比精确查找条件的性能要优;
非贪婪模式比贪婪模式性能优;
获取匹配比非获取匹配性能优;
优化判断分支(将最可能匹配的结果放前面)后性能更差;
Firefox浏览器下:
精确查找条件比使用.、*这类元字符或者范围的性能优;
非贪婪模式比贪婪模式性能优;
获取匹配比非获取匹配性能优;
优化判断分支(将最可能匹配的结果放前面)后性能更差;
Nodejs环境下:
使用.、*这类元字符或范围比精确查找条件的性能优;
贪婪模式比非贪婪模式性能优;
获取匹配比非获取匹配性能优;
优化判断分支(将最可能匹配的结果放前面)性能优;
以上的测试结果并不能给出肯定的答案,所以建议在JavaScript里使用正则的时候先测一下性能以免导致不愉快的意外,至于JavaScript中正则表达式的实现过程更是需要看V8的代码了...
JavaScript RegExp 小扩展
lastIndex 和 test 方法的爱恨情仇
var str = 'abcabc';
var regexp = /a/g;
console.log(regexp.test(str)); // output true
console.log(regexp.lastIndex); // output 1
console.log(regexp.test(str)); // output true
console.log(regexp.lastIndex); // output 4
console.log(regexp.test(str)); // output false
console.log(regexp.lastIndex); // output 0
test 方法在执行后将 lastIndex 属性值置为匹配到的结果索引值,并返回匹配结果;下一次执行 test 方法时从 lastIndex 位置开始匹配,并返回匹配结果;以此类推;最后一次执行 test 方法,索引重置为0,匹配结果为false。
RegExp vs indexOf 谁才是快男
在固定匹配值和"只匹配一次"的条件下,针对判断字符串是否包含某个"固定的"字符串,indexOf 优于 regexp,性能将近一半。
var str = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36";
console.time('reg');
/Mac/.test(str);
console.timeEnd('reg');
console.time('indexOf');
str.indexOf('Mac');
console.timeEnd('indexOf');
经过多次测试 reg 的平均值 是 indexOf 的近2倍(环境:chrome & nodejs);然而在其他匹配条件下,RegExp 更灵活,更方便,更全面。
关于String.prototype.indexOf方法内部代码暂时不知道怎么去探究,根据以下文档有解释,但未明确的理解,参考文档:
[ECMAScript 2015 (6th Edition, ECMA-262)]
[ECMAScript 1st Edition (ECMA-262)](初始定义)
注意:本文只是一次知识探讨,仅供做参考作用,请勿以之为准,如果需要更进一步的学习请以书籍以及自我实践所得为准。
ReactNative调用Android原生模块
有时候App需要访问平台API,但React Native可能还没有相应的模块包装;或者你需要复用一些Java代码,而不是用Javascript重新实现一遍;又或者你需要实现某些高性能的、多线程的代码,譬如图片处理、数据库、或者各种高级扩展等等。我们把React Native设计为可以在其基础上编写真正的原生代码,并且可以访问平台所有的能力。要想实现访问Android原生API,总结一下,主要有以下几个步骤:
1. 创建一个原生模块
这个原生模块是一个继承ReactContextBaseJavaModule的Java类,它可以实现一些JavaScript所调用的原生功能。我们的目标是可以在JavaScript里写ToastAndroid.show('Awesome', ToastAndroid.SHORT);,来调起一个Toast通知。例如:
public class RnTest extends ReactContextBaseJavaModule {
public RnTest(ReactApplicationContext reactContext) {
super(reactContext);
}
// ReactContextBaseJavaModule要求派生类实现getName方法。这个函数用于返回一个字符串
// 这个字符串用于在JavaScript端标记这个原生模块
@Override
public String getName() {
return "ToastByAndroid";
}
// 获取应用包名
// 要导出一个方法给JavaScript使用,Java方法需要使用注解@ReactMethod
@ReactMethod
public void getPackageName() {
String name = getReactApplicationContext().getPackageName();
Toast.makeText(getReactApplicationContext(),name,Toast.LENGTH_LONG).show();
}
}
ReactContextBaseJavaModule要求派生类实现getName方法。这个函数用于返回一个字符串名字,这个名字在JavaScript端标记这个模块。这里我们把这个模块叫做ToastByAndroid,这样就可以在JavaScript中通过React.NativeModules.ToastByAndroid访问到这个模块。注意:模块名前的RCT前缀会被自动移除。所以如果返回的字符串为"RCTToastAndroid",在JavaScript端依然通过React.NativeModules.ToastByAndroid访问到这个模块。
2. 注册模块
要使JavaScript端调用到原生模块还需注册这个原生模块,需要实现一个类实现ReactPackage接口,并实现其中的抽象方法。例如:
public class ExampleReactPackage implements ReactPackage {
@Override
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
List<NativeModule> modules = new ArrayList<>();
modules.add(new RnTest(reactContext));
return modules;
}
@Override
public List<Class<? extends JavaScriptModule>> createJSModules() {
return Collections.emptyList();
}
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return Collections.emptyList();
}
}
MainApplication声明
除了上面的步骤外,还需在MainApplication.java文件中的getPackages方法中,实例化上面的注册类。例如:
@Override
protected List<ReactPackage> getPackages() {
return Arrays.<ReactPackage>asList(
new MainReactPackage(),
// 实例化注册类
new ExampleReactPackage());
}
};
3. JS调用android原生方法
3.1 引入NativeModules模块
import { NativeModules } from 'react-native';
3.2 调用Android原生方法
var rnToastAndroid = NativeModules.ToastByAndroid;
rnToastAndroid.getPackageName();
4. 获取android返回值
提供给js调用的原生android方法的返回类型必须是void,React Native的跨语言访问是异步进行的,所以想要给JavaScript返回一个值的唯一办法是使用回调函数或者发送事件。
4.1 回调函数
Callback是React.bridge中的一个接口,它作为ReactMethod的一个传参,用来映射JavaScript的回调函数(function)。Callback接口只定义了一个方法invoke,invoke接受多个参数,这个参数必须是react.bridge中支持的参数。Android端代码:
@ReactMethod
public void tryCallBack(String name,String psw,Callback errorCallback,Callback successCallback){
try{
if(TextUtils.isEmpty(name)&&TextUtils.isEmpty(psw)){
// 失败时回调
errorCallback.invoke("user or psw is empty");
}
// 成功时回调
successCallback.invoke("add user success");
}catch(IllegalViewOperationException e){
// 失败时回调
errorCallback.invoke(e.getMessage());
}
}
rn端代码:
var rnToastAndroid = NativeModules.ToastByAndroid;
rnToastAndroid.tryCallBack("luo","131",(errorCallback)=>{alert(errorCallback)},(successCallback)=>{alert(successCallback);});
5.调用测试
android主动向rn发送消息。
5.1 android端代码
public static void sendEvent(ReactContext reactContext, String eventName, int status)
{
System.out.println("reactContext="+reactContext);
reactContext
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
.emit(eventName,status);
}
5.2 RN端代码
// eventName为5.1中的eventName,reminder为5.1中的status
DeviceEventEmitter.addListener(eventName, (reminder) => {
console.log(reminder):
});
RN调用Android原生模块的代码如下:
const RNBridgeModule = NativeModules.RNBridgeModule;
nativeLanuchApp(message) {
RNBridgeModule.nativePlayVideo(message);
}
<TouchableOpacity onPress={() => {
this.nativeLanuchApp("111");
}} >
<Text >
try
</Text>
</TouchableOpacity>
json从立地到成佛
声明
本文原创作者bigsai(同公众号),本文以浅显易懂的方式给大家讲解自己所认知的json,如果有错误或者不准确地方还请大家给出指正,另外本文以关卡课程的方式在博学谷也是免费开放的,大家也可通过关卡方式学习。链接地址。
诞生于JavaScript,json的前世今生
json含义
在开始之前,问个问题,什么是json?
a:我猜它应该是某一门高深的技术(语重心长)
b:json这个词为啥谷歌翻译?是啥新词语嘛?是不是搞错了哟?(底气十足)
c:json这个我听过,我只知道他是一种很轻量存储结构,但具体真的不太懂(轻声)
json它不是一个原有单词,其实是4个单词JavaScript Object Notation(JavaScript对象表示)的简写,是一种轻量级的文本数据交换格式,并且json独立于语言(使用JavaScript语法描述对象),很多编程语言都支持json。 json 已成为当前服务器与 web 应用之间数据传输的公认标准。
json诞生
问个问题,json是如何诞生的呢?
这个问题首先由于互联网应用之间需要传输数据,且很多跨平台的程序需要交互,只能采取纯文本方式的交互,而xml当初就是一个选择,但是xml规范越来越多也越来越复杂,解析效率也比较低,很多攻城狮看到xml头都大了,可能搞了很久也搞不明白。
然后独钟于JavaScript的JavaScript大宗师(JavaScript之父评价)Douglas Crockford根据JavaScript的规范发明推广了json,json格式简单易用,且同样可跨平台传出,得到广泛的认可和传播。就这样,json就越来越流行了,现在已经成为主流技术之一。
(选择题)章节练习:json是一种什么东西呢(B)?
A.JavaScript对象
B.轻量级文本数据交换格式
C.一种语言
D.一种框架
搞清json兄弟姐妹,看清区别
json VS xml
谈起json,那xml肯定是少不了对比的东西啊,没有对比就是没有伤害,在和json履行相同职责的文本传输交换格式还有json的老大哥xml(可扩展标记语言),在json横空出世以前,咱们用的可都是xml进行文件数据传输。
首先咱们要从定义上来看看json和xml的区别:
JSON(JavaScript Object Notation)一种轻量级的数据交换格式,具有良好的可读和便于快速编写的特性。可在不同平台之间进行数据交换。其语言习惯具备类c的习惯体系(c,c++,java等)。XML(Extensiable Markup Language,可扩展标记语言)用于标记电子文件使其具有结构性的标记语言,可以用来标记数据、定义数据类型,是一种允许用户对自己的标记语言进行定义的源语言。
json的横空出世,是充分吸取借鉴了xml的优点,故json和xml有着一些相同的优点:
可读性好,结构清晰
分层存储(层次嵌套)
都可作为Ajax传输数据
都跨平台,可作为数据传输格式
但json毕竟青出于蓝而青于蓝,肯定有着xml一些没有的特点和优势,例如:
数据格式简单,易读易写,且数据都是压缩的,文件较小,便于传输。
json解析难度较低,而xml需要循环遍历DOM进行解析,效率较低。
服务端和客户端可以直接使用json,便于维护。而不同客户端解析xml可能使用不同方法。
json 已成为当前服务器与 web 应用之间数据传输的公认标准。
尽管如此,xml仍有它独有应用领域:
xml格式较为严谨,可读性更强,更易于拓展,可以良好的做配置文件。
出现较早,在各个领域有广泛的应用,具有普遍的流行性。
当然,不是所有的json都是"特仑苏",适合场景需要的才是最好的。但在web领域的数据传输,它就是王者!
(选择题)小练习:下列哪一项是错误选项?(B)
A.相同内容文件json通常要比xml更简洁更小。
B.json解析起来比xml复杂很多。
C.json和xml都是一种跨平台文本传输格式。
D.json是JavaScript Object Notation单词的简写。
小小翻译官,json的应用
json之所以很流行,是因为json有着广泛的应用领域。主要包括与ajax结合使用的统一平台的前端传输;跨平台的数据传输;非关系型数据库的文档存储等。这些领域通过使用json使得应用效率大大提高。
前端ajax+json异步传输:
json本身就起源于JavaScript,JavaScript解析处理json有天然的优势,而在Ajax异步加载的数据中,整体页面已经加载过了,我们只需要在对应位置渲染真实的数据就行了,。而这部分的真实数据我们用json文本来存储,使用JavaScript异步向服务端请求,请求成功之后JavaScript对其渲染填充就可以了。下图就是对前后端交互传统方式和ajax异步交互的简要描述:
如果对AJAX也不熟悉?流程也看不懂,也不能明白异步传输的真谛在哪里,那好咱们以下图这个例子来解释一下,对于一个非常庞大的网页部分,可以有各个模块组成,其中评论模块是我们非常小的模块,但是评论可能涉及很多条可能涉及分页,如果我们每次为了看下一页的评论,点击下一页就向服务端请求整个页面资源进行刷新,那样就太浪费服务端资源和带宽了(就评论的文本变了,就要把其他模块全部渲染一遍?)
所以我们采取所谓AJAX异步更新这个东西,也就是通过JavaScript请求下一页的评论相关数据(用json作为数据交互文本),JavaScript得到这串json字符串中就有页面需要的评论信息,然后我们强大到无所不能的JavaScript将这部分重现渲染到评论模块的对应位置。
这个流程下来,我们不仅节约了带宽,提高了响应速度,还提高了服务器相对负载能力(每次请求需要的资源更少),提高了用户的使用体验,还提高了------(此处省略万字)
跨平台webservice:
前面提到的是前后端的交互,前提是后端同是一门编程语言、平台,向前端提供服务。但随着互联网的发展,很多时候会遇到服务拆分、跨平台等服务的需要。而跨语言跨平台的最大障碍就是服务的交流问题。你总不至于用你电脑上的c++代码直接调用我电脑上某个java函数吧?为了解决这种问题,这时候通过restful风格的接口和json作为文本传输的格式就能良好的解决服务通信问题。
例如某个公司业务太大,将服务分配给A团队和B团队。很多时候A可能需要进行调用B服务。如果A团队全部是java,B团队全部是php,互相喊着天下第一不肯相让。这该怎么办?那么通过json进行通信是一种非常好的方式。流程如图简要所示:
非关系数据库存储(Nosql)
随着互联网web2.0网站的兴起,传统关系数据库在处理超大规模网站和高并发方面显得有点力不从心。而非关系型的数据库由于它本身的特点得到非常迅速的发展,非关系数据库在大规模数据下也有非常良好的读写性能,且数据之间无关系,无形之间就在架构层面带来了可拓展的能力。
而有很多基于文档存储的非关系数据库采取json作为其存储格式,其中比较著名的有:MongoDB、CouchDB、RavenDB等。存储的内容是文档型的,这样也有机会对某些字段建立索引,实现关系数据库的某些功能。
有些同学可能会问:既然json可以,那xml可以实现相似功能嘛?
答案是不可以,因为像xml类型的字段,不管是查询还是更改效率都很一般,主要原因是是DB层对xml字段很难建高效索引,应用层又要做从字符流到dom的解析转换。NoSQL以json方式存储,提供了原生态的支持,在效率方面远远高于传统关系型数据库。
此外,Elasticsearch等搜索引擎还用json和java api 提供其所有特性和功能。json在开源中间件的应用也越来越多!
(多选题)小练习:json常用于以下那些领域?(ABC)
前端Ajax异步交互
webservice提供接口
非关系数据库数据存储
拒绝四不像,json语法有要求
json语法规则
json语法是JavaScript语法的子集,而json一般也是用来传输对象和数组。也就是json语法是JavaScript语法的一部分(满足特定语法的JavaScript语法)。
数据保存在名称、值对中,数据由逗号分隔
花括号表示对象
中括号表示数组
json名称/值
json 数据的书写格式为:"名称":"值"。对应JavaScript的概念就是:名称="值"但json的格式和JavaScript对象格式还是有所区别:
JavaScript对象的名称可以不加引号,也可以单引号,也可以双引号,但json字符串的名称只能加双引号的字符表示。
JavaScript对象的键值可以是除json值之外还可以是函数等其他类型数据,而json字符串的值对只能是数字、字符串(要双引号)、逻辑值、对象(加大括号)、数组(中括号)、null。
json对象
json有两种表示结构—对象和数组,通过着两种表示结构可以表示更复杂的结构。对比java的话json数组和json对象就好比java的列表/数组(Object类型)和对象(Map)一样的关系。并且很多情况其对象值可能相互嵌套多层,对对象中存在对象,对象中存在数组,数组中存在对象...下面这张图能够一定程度反应json对象和数组的关系:
json对象很容易理解,它代表一个实体,这个实体有一些其他的属性,这些属性可能是数字、字符串(要双引号)、逻辑值、对象(加大括号)、数组(中括号)、null。如果从java语言来看他就是对应一个实体类或者一个Map,其中有一些用键值的方式描述名称和值。
var a = {"name":"bigsai" , "sex":"man","school":{"name":"博学谷","localtion":"Bei Jing"}};
取值:可以通过(.)或者([])进行取值,例如a.name(a.sex)和a["name"](a["sex"]),代码解释如下:
对象套对象:可以通过(.)或者([])进行取值。代码解释如下:
遍历:可以用 for - in 进行对象遍历。代码解释如下:修改:可以使用(.)或者([])进行修改对象的值。示例代码如下:删除:可以通过delete关键词删除json对象属性值。示例代码如下:完整代码截图为:附上代码:
var a = {"name":"bigsai" , "sex":"man","school":{"name":"博学谷","localtion":"Bei Jing"}};
a.name+" "+a["name"]
a.school
a["school"].name
for (key in a){//遍历对象
console.log(key+" "+a[key]);}
a.name="saisai"
a["sex"]="woman"
a
delete a["school"]
a
json数组
学习完json对象,那么json数组的学习就容易的多了。json数组与json对象有一些区别,json数组用中括号表示([]),各个值用逗号(,)分隔,并且数组值需要是json合法数据类型(字符串, 数字, 对象, 数组, 布尔值或 null).
var names=["bigsai","bigpian","bigbig"];//json数组
var students=[{"name":"bigsai","high":180},{"name":"bigpian","high":165},{"name":"Yao Ming","high":226}];//对象套数组
取值:可以通过中括号([])进行取值,例如names[0]或names["0"],示例代码如下:数组套对象:取值到对象后遵从对象的语法。示例代码如下:遍历:可以用 for - in 或者for 对json数组进行遍历。示例代码如下:修改:可以使用([])索引号进行修改数组。示例代码如下:删除:可以通过delete关键词删除json数组中的项目。示例代码如下:完整json数组示例代码如下:附上源码:
var names=["bigsai","bigpian","bigbig"];//json数组
var students=[{"name":"bigsai","high":180},{"name":"bigpian","high":165},{"name":"Yao Ming","high":226}];//对象套数组
names["0"]+" "+names[0]//json数组取值
students[2]["name"]+" 身高:"+students[2].high//json数组套对象(对象套数组同理)
for (i in names){ console.log(names[i]); }//for in 遍历
for (i=0;i<names.length;i++){console.log(names[i]);}
names[0]="bigsai666";
delete names[0];
names
JavaScript对象 VS json对象 VS json字符串
在JavaScript中谈到json,很多人会对JavaScript对象、json对象、json字符串混淆和概念不清晰,我们可以举个例子来一起看一下:
var a1={ name:"bigsai" , sex:"man" };//JavaScript对象
var a2={'name':'bigsai' , 'sex':'man'};//JavaScript对象
var a3={"name":"bigsai" , "sex":"man"};//满足json格式的JavaScript对象
var a4='{"name":"bigsai" , "sex":"man"}';//json字符串
总的来说:
JavaScript对象:除了字符串、数字、true、false、null和undefined之外,JavaScript中的值都是对象。
json对象:这个说法其实不太准确,没有单独的json对象,我们常说的json对象它实质是满足json格式要求的JavaScript对象。如上a3对象。
json字符串,满足json语法格式的字符串(json是一种数据格式),有时也称json串。
在这里多说几句,你可能会对json对象还是有点不够清晰,你可能在其他地方也会遇到json对象。首先,json是一种数据格式,而json有对象和数组两种具体表现格式。
当你直接说json对象,json数组的时候。它其实就是直接谈json的两种表示结构。它主要体现的是结构。
在JavaScript中,我们通常说的json对象,json数组通常代指满足json格式的JavaScript对象,JavaScript数组。
在java中我们有时也说json对象,json数组,这个其实就是第三方工具包基于json规范封装的JSONObject、JSONArray类。
总的来说,我们通常说的json对象、json数组它实质是一种数据格式。但同在在不同语言中满足这种格式要求的对象、数组我们会称其为json对象、json数组。
(选择题)小练习:下列哪一项是满足json格式的JavaScript对象?(D)
A. { name : "博学谷" , value : "very well" };
B. { 'name' : "博学谷" , 'value' : "very well" };
C. { name : "张三" , age : "18" };
D. { "name" : "李四" , "age" : 25 };
小结
本章小结:大家可以发现json的语法规则还是相对简单的,对于json语法格式,大家要谨记json的数据名称只能是带双引号("")的字符串,而json对象的值要谨记有哪几种满足的类型。对于json对象和json数组来说,是json的两种表示结构,而json的灵活性也允许两种类型的相互嵌套,可以表示更为复杂的结构。
(单选题)既然大家和我一起学了json对象、数组以及一些基本语法,下面考考大家,json对象的值不可以是下面哪种类型呢?(D)
字符串
数字
json对象/json数组
函数
谷歌Gson,精简而强大
序列化/反序列化介绍
前面我们学习了json的一些概念和基础语法,也知道了json起身于JavaScript,在很多语言如python中得到较好的支持,但也有很多语言从语言本身来说是不支持json的(就比如咱们强大的java)。这虽然是一大障碍但并不阻止我们在java这门语言中使用json。我们可以通过添加一些工具包使得java支持json处理。
这些工具包能够将json字符串转换成java对象,从而在Java中使用。反过来也可以将java对象转换成json字符串,从而更广泛地用在其他地方。将Java对象到Json字符串的过程我们称为Serialization序列化,将Json字符串转换成Java对象的过程我们称为Deserialization反序列化。
如果理解起来容易混淆,那么可以借助下面这张图进行结合记忆:咱们从java角度来看,java对象需要从一个整体对象拆分成一个个碎片按序列往json字符串中写入,这就是一个序列化过程。而json字符串的一个个碎片反过来重新组装成一个完整的java对象这个过程就是反序列化。
对于json本身来说是不复杂的,但是在java中如果程序员直接操作json字符串那是一件非常麻烦和复杂的事情,不少优秀的程序员/团队/公司努力研究,将他们经验和智慧开源出来供大家使用,在其中,Gson/fastjson/Jackson要更流行一些。咱们一个个了解一下。
Gson介绍
在学习之前,你知道什么是Gson吗?Gson是谷歌开源的一个Java库,可用于将Java对象转换为(序列化)其JSON表示形式。它还可以用于将JSON字符串转换为(反序列化)等效的Java对象。Gson可以处理任意Java对象,包括没有源代码的现有对象。下图为Gson在github主页一些信息。每种json工具包都有它自己的优点和长处,对于Gson来说,有以下几点特点:
提供简单的toJson()和fromJson()方法,将Java对象转换成json字符串,反之亦然
允许将现有的不可修改对象与JSON相互转换
Java泛型的广泛支持
允许对象的自定义表示
支持任意复杂的对象(具有深层次的继承层次结构、泛型等)
Gson实战
下面和大家一起动手进行Gson实战,Gson的功能比较强大,在这里呢和大家一起实现一些基础和常用的使用。
首先创建一个java项目(Maven),要引入Gson的Maven依赖或jar包,其Maven依赖为:
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.6</version>
</dependency>
有了Gson的依赖之后,那么实现Java对象与Json的转化也就很简单啦,大体就是分为两步啦:
首先创建Gson对象,这里可以直接new 或者使用GsonBuilder进行创建,如果使用直接new的方式创建Gson对象是使用默认的配置;而使用GsonBuilder首先要创建GsonBuilder,然后GsonBuilder调用一些配置方法,然后调用create()方法构建Gson对象。
然后通过Gson对象的toJson(),fromJson()方法进行序列化和反序列化操作。
javaBean与json字符串互相转换:首先创建一个student对象
public class student {
private String name;
private int age;
private String sex;
public student(String name, int age, String sex) {//构造方法
this.name = name;
this.age = age;
this.sex = sex;
}
@Override
public String toString() {//重写toString方法
return "student{" +
"name='" + name + '\'' +
", age=" + age +
", sex='" + sex + '\'' +
'}';
}
public String getName() {//获取name字符串
return name;
}
public void setName(String name) {//设置对象name
this.name = name;
}
public int getAge() {//获取年龄int
return age;
}
public void setAge(int age) {//设置年龄值
this.age = age;
}
public String getSex() {//获取性别
return sex;
}
public void setSex(String sex) {//设置性别
this.sex = sex;
}
}
其次,在测试中进行JavaBean(student)与json字符串的转换。主要通过toJson()和fromJson()进行序列化和反序列化操作。toJson,直译过来就是“到达json”,所以是将java对象转成json字符串,也就是序列化。fromJson,直译过来就是“来自json”,所以是将json字符串转化为java对象,也就是反序列化。
//Gson gson= new GsonBuilder().create();//可以自定义一些配置
Gson gson=new Gson();//创建json对象
//java对象 to json
student stu=new student("Ben",22,"man");
String stustr=gson.toJson(stu,student.class);//json转为string
System.out.println("student对象为"+stu.toString());
System.out.println("转化为json字符串:"+stustr);
//json to java对象
///满足条件的json字符串{"name":"tony","age":32,"sex":"woman"}
String jsonstr="{\"name\":\"tony\"," +
"\"age\":32," +
"\"sex\":\"woman\"}";
student jsonstrobject=gson.fromJson(jsonstr,student.class);//转换为student对象
System.out.println("json字符串为"+jsonstr);
System.out.println("转化为student对象为:"+jsonstrobject.toString());
执行的结果为:java集合与json字符串互相转化:在实际开发中,我们很可能遇到的并不是javaBean与json字符串的直接转化,而是集合之类的转化工作,java集合种类繁多。在此,我们实现Map、List、String数组的序列化和反序列化操作。在进行序列化操作时,我们首先创建Map<String,String>,List<Object>,String[]对象然后填充一定数据以便进行序列化和反序列化操作。
Gson gson=new Gson();//创建json对象
//Map
Map<String,String>map=new HashMap<>();//Map
map.put("博学谷","666");map.put("小老弟","要加油");
//List
List<Object>list=new ArrayList<>();//List类型
list.add("hello");list.add("world");list.add(map);
//String[]
String []str={"Hello","World"};//String
String mapTojsonStr=gson.toJson(map);//{"小老弟":"要加油","博学谷":"666"}
String listTojsonStr=gson.toJson(list);//["hello","world",{"小老弟":"要加油","博学谷":"666"}]
String strTojsonStr=gson.toJson(str);//["Hello","World"]
System.out.println("Map转为json:"+mapTojsonStr);
System.out.println("List转为json:"+listTojsonStr);
System.out.println("String[]转为json:"+strTojsonStr);
执行的结果为:我们将这些字符串复制到新的代码域进行反序列化操作,在反序列化时候,我们会用到fromJson()这个函数时,有两种我们常用的构造方式fromJson(String json, Class<T> classOfT)和fromJson(String json, Type typeOfT),如果遇到泛型等类型时候需要借助 TypeToken来获取对象类型。
Gson gson=new Gson();//创建json对象
String mapTojsonStr="{\"小老弟\":\"要加油\",\"博学谷\":\"666\"}";//{"小老弟":"要加油","博学谷":"666"}
String listTojsonStr="[\"hello\",\"world\",{\"小老弟\":\"要加油\",\"博学谷\":\"666\"}]";//["hello","world",{"小老弟":"要加油","博学谷":"666"}]
String strTojsonStr="[\"Hello\",\"World\"]";//["Hello","World"]
//方式一方便简洁(这里避免冲突注释掉)
//Map<String,String>map1=gson.fromJson(mapTojsonStr,Map.class);
//方式二可以获取泛型等数据类型
Map<String,String>map1=gson.fromJson(mapTojsonStr,new TypeToken<Map<String,String>>(){}.getType());
List<Object>list=gson.fromJson(listTojsonStr,List.class);
Map<String,String>map2=(Map<String,String>)list.get(2);
String str[]=gson.fromJson(strTojsonStr,String[].class);
System.out.println("json转Map:"+map1.toString());
System.out.println("json转List"+list.toString());
System.out.println("map1和map2是否相等:"+map2.equals(map2));//相等
System.out.println("String[]:"+ Arrays.toString(str));
输出结果为:
上面只是介绍了java对象与json字符串的转换,实际上Gson不仅入手容易,还有其他非常强大的功能,在使用Gson开发中除了java对象和json字符串的转换,我们经常也会对JsonObject直接进行操作(类似JavaScript中操作json串一样),这里你需要了解学习Gson封装的JsonEelement,JsonObject,JsonArray,JsonPrimitive,JsonNull等数据类型。
不同的数据类型有各自的使用场景,下面给大家介绍下各个数据类型之间的区别与联系:
JsonElement:表示Json元素的类。 它可以是JsonObject,JsonArray,JsonPrimitive或JsonNull。这个你可以理解一下java中List(Arraylist,LinkedList),Map(HashMap.TreeMap,ConcurrentHashMap)等联系。也可以理解为Object的类与其他类的关系。
JsonObject:表示Json中对象类型的类。 对象由名称-值对组成,其中名称是字符串,而值是任何其他类型的JsonElement。
JsonArray: 表示Json中数组类型的类。 数组是JsonElement的列表,每个JsonElement的类型可以不同。 这是一个有序列表,意味着保留添加元素的顺序。
JsonPrimitive:表示Json基本值的类。 基本值可以是String,Java基本类型或Java基本类型的包装类。
JsonNull:表示Json空值的类。
对于这些数据类型,你可能会问:为啥json字符串已经可以和java对象互相转了,还需要这些数据类型呢?答案是这些数据类型让java中多一种可以处理json格式数据的方式。一方面让java处理json格式数据更加灵活,另一方面在某些场景下这样直接操作JsonObject、JsonArray等能够简化工作流程。
其实这些数据类型就是相当于用java的数据结构构造一个json的数据结构和方法(java本身不直接支持json),让我们能够直接使用和操作json。从上图可以看得出,上面这些数据结构也是根据java的一些数据结构作为存储,然后写一些操作函数,封装一些方法。当然,JsonObject也可以通过Gson的toJson()和fromJson()方法灵活转成字符串和java对象。
有很多时候我们后台处理接受到的是一个json字符串,可能其内部结构也很复杂,如果我们将其转成java对象可能要编写java对应的实体,但是如果直接操作json对象可能就省下一些操作和流程,如果只需增删改其中很小的一部分,那么这种选择也是一种不错的方案。当然具体的使用方法和方案还需要根据具体的业务来判断!
(多选)既然学习了Gson,那么就来考考大家到底有没有真的掌握Gson的关键函数,仔细思考哦,这题有点狠,一错俱错呦:(AD)
A.函数 toJson() 是将对象转成json字符串。
B.函数 toJson() 是将json字符串转成对象。
C.函数fromJson() 是将对象转成json字符串。
D.函数fromJson() 是将json字符串转成对象。
飞人fastjson,阿里的"黑科技"
fastjson介绍
除了谷歌的Gson,咱们国内也有一款非常强大的java序列化工具包—fastjson。下图为fastjson在github的主页:学习fastjson之前同样问:什么是fastjson?
fastjson是阿里巴巴的开源JSON解析库,它可以解析JSON格式的字符串,支持将Java Bean序列化为JSON字符串,也可以从JSON字符串反序列化到JavaBean。
除了是国内阿里开源的,fastjson还有优异的性能,fastjson的优点如下:
速度快:fastjson相对其他JSON库的特点是快,从2011年fastjson发布1.1.x版本之后,其性能从未被其他Java实现的JSON库超越。
使用广泛、测试完备。在阿里内部有广泛的应用。
使用简单、功能完备。支持泛型、复杂类型等。
fastjson实战
下面带大家实战fastjson,同样首先我们需要引入依赖,下载jar包引入或者maven的依赖。
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.70</version>
</dependency>
fastjson与Gson虽然大体相似但有所区别,fastjson自己也有实现的JSONObject,JSONArray类,前面在Gson中介绍过此类的作用我们在进行转换时候就把JSONObject加入进行转换。在fastjson主要提供以下三个类:
(1)JSON:fastJson的解析器,用于JSON格式字符串与JSON对象及javaBean之间的转换。
(2)JSONObject:fastJson提供的json对象。
(3)JSONArray:fastJson提供json数组对象。
json字符串、JSONObject及JavaBean之间的相互转换首先,我们同样定义一个student类(同Gson的student类)
public class student {
private String name;
private int age;
private String sex;
public student(String name, int age, String sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
//get set方法
}
在测试代码中,我们分别编写一些代码实现三者的相互转换,但JSONObject、JSONArrray、JSON以及fastjson仍然有很多方法功能在这里就无法进行很详细的展示了:
//对象转json字符串,JSONObject
student student1=new student("xiaoming",5,"man");
String jsonstr1= JSON.toJSONString(student1);//对象转json字符串
JSONObject jsonObject1=(JSONObject) JSON.toJSON(student1);//对象转JSONObject
System.out.println(jsonObject1.toString());
System.out.println(jsonstr1+"\n");
//json字符串转java对象,JSONObject
String jsonstr2="{\"age\":5,\"name\":\"xiaoming\",\"sex\":\"man\"}";
JSONObject jsonObject2=JSON.parseObject(jsonstr2);//json字符串转JSONObject对象
student student2 = JSON.parseObject(jsonstr2,student.class);//json字符串转java对象
System.out.println(jsonObject2.toString());
System.out.println(student2.toString()+"\n");
//JSONObject 转java对象,json字符串
JSONObject jsonObject3=jsonObject2;
jsonObject3.put("age",18);//修改年龄
student student3=jsonObject3.toJavaObject(student.class);//JSONObject转java对象
String jsonstr3=jsonObject3.toJSONString();//JSONObject转json字符串
String name=jsonObject3.getString("name");//JSONObject取值
int age=jsonObject3.getInteger("age");
String sex=jsonObject3.getString("sex");
System.out.println("姓名"+name+" 年龄"+age+" 性别"+sex);
System.out.println(student3.toString());
System.out.println(jsonstr3+"\n");
对应输出的结果与预期一致:json字符串、JSONObject及Java集合相互转换上面进行了基于javabean的一些转换和操作,下面我们进行对java集合的一些转化实战。看看fastjson又是以什么样的参数进行的。java的Map是常用集合之一,咱们先看看Map的相关转化:
//map的相关转化
Map<String,String>map1=new HashMap<>();
map1.put("name","xiaoming");map1.put("sex","woman");
String jsonstr=JSON.toJSONString(map1);//Map转json字符串
JSONObject jsonObject=(JSONObject) JSON.toJSON(map1);//Map转json对象
System.out.println(jsonstr);
System.out.println(jsonObject.toString());
//Map<String,String>map2=JSON.parseObject(jsonstr,Map.class);//方式一
Map<String,String>map2=JSON.parseObject(jsonstr,new TypeReference<Map<String, String>>(){});//方式二json字符串转Map
Map<String,String>map3=jsonObject.toJavaObject( new TypeReference<Map<String, String>>(){});//JSONObject
System.out.println(map2.toString());
System.out.println(map3.toString());
控制台的输出为:此外,List同样也是java中使用较多的集合之一,咱们可以看下它的相关转化:
//List相关转化
List<Map<String,String>>list1=new ArrayList<>();//集合
Map<String,String>map1=new HashMap<>();
map1.put("name","map1");
Map<String,String>map2=new HashMap<>();
map1.put("name","map2");map2.put("sex","man");
list1.add(map1);list1.add(map2);
String jsonstr=JSON.toJSONString(list1);//list转json字符串
JSONArray jsonArray =(JSONArray) JSON.toJSON(list1);//list转jsonArray
JSONObject jsonObject=jsonArray.getJSONObject(0);
System.out.println(jsonstr);
System.out.println(jsonArray+" "+jsonArray.get(0));
//json 字符串转list
List<Map<String,String>>list2=JSON.parseObject(jsonstr,new TypeReference<ArrayList<Map<String,String>>>(){});
//List<student>list3=JSON.parseArray("",student.class);//普通list的转换方式
System.out.println(list2.get(0).equals(map1)+" "+list2.get(1).equals(map2));//如果相等则证明成功序列化
System.out.println(list2.toString());
得到输出结果为:不难看的出,fastjson在入门还是非常简单的。并且和Gson有很多相似之处,在Api的设计方面,Gson需要一个Gson对象来进行相关操作,而fastjson的JSON、JSONObject、JSONArray定义了很多静态的方法可以直接使用。同时两者的反序列化的TypeToken(Gson)和TypeReference(fastjson)有异曲同工之妙。
这两者在开发过程中使用很多,各有优劣,并且这里只是里面很小的一部分内容,要想深入学习还需要了解官方全面API才行(Gson官方API,fastjson官方文档)。但是对于fastjson来说,有些地方可能存在一些漏洞和不稳定因素,但是阿里很快就进行修复。所以在实际使用中要考虑fastjson的安全性。
(习题)介绍完咱们国内开源的科技—fastjson,那么我来看看对fastjson掌握程度如何,考考大家:下面哪个不是fastjson的类(D)
A.JSON
B.JSONObject
C.JSONArray
D.JsonElement
备受开源认可,Jackson亦是真王者
Jackson介绍
最后咱们介绍的就是当前更为成熟一点的jackson。jackson 是一个能够将java对象序列化为json字符串,也能够将json字符串反序列化为java对象的框架。下图为jackson在github主页情况:其实jackson的应用非常广泛,在这里我们简单了解以下jackson然后对比分析三个json工具包的异同,对于jackson来说,拥有以下几点优势:
出师较早,国际流行度较高,且社区学习内容较为丰富。
安全稳定,是很多开源框架的内置序列化框架(例如Springmvc)。
解析大的json文件效率较高,占用内存较少,性能比较好。
拥有灵活的API,可以很容易的拓展和定制。
jackson实战
下面带大家实战jackson,在Gson和fastjson使用时只需引用一个jar,而jackson却不是将所有全部集成到一个jar内,而是分成了多个不同的模块(不同模块具有不同功能),咱们首先引入jackson的依赖:
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-core -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.11.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.11.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-annotations -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.11.0</version>
</dependency>
有了jackson依赖之后,我们就可以进行实战了,在jackson中有三种方式操作json:
流式API - 使用 Stream(流) 的方式对 Json 的每一个组成部分进行最细粒度的控制,JsonParser 读取数据,JsonGenerator 写入数据。(json streaming 流式计算,开销最低,读写最快)
树模型 - 将 JSON 文件在内存里以树的形式表示,通过 JsonNode 处理单个Json节点,类似于 XML 的 DOM 解析器。(数模型Json文件在内存里以树形式表示 ObjectMapper构建JsonNode 节点树 最灵活)
databind 模块 - ObjectMapper 读/写 JSON ,是 序列化与反序列化 json 最方便的方式。 (本篇实战采用的方法)
javaBean与json字符串相互转换对于javaBean,我们创建student类(注意使用jackson的类必须有空参构造器):
public class student {
private String name;
private int age;
private String sex;
public student(String name, int age, String sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
public student(){}//空参构造器
//get set方法 toString方法
}
在测试代码中,首先创建一个ObjectMapper对象,序列化和反序列化都需要它。然后根据writeValueAsString()这个函数就可以把java对象转成json字符串(序列化)。而json字符串通过readValue()就可以将json字符串转化为java对象(反序列化)。代码的展示如下:
//创建 ObjectMapper对象
ObjectMapper mapper=new ObjectMapper();
//序列化的实体类
student stubigsai=new student("bigsai",6,"man");
//writeValueAsString()方法进行序列化
String stubigsaistr=mapper.writeValueAsString(stubigsai);
System.out.println(stubigsaistr+"\n");
//反序列化的json字符串
String stuxiaohongstr="{\"name\":\"xiaohong\",\"age\":8,\"sex\":\"woman\"}";
//readValue()方法进行反序列化
student stuxiaohong= mapper.readValue(stuxiaohongstr,student.class);
System.out.println(stuxiaohong.toString());
对于上述代码执行结果为:java集合与json字符串相互转换除了javaBean和json字符串转换外,java集合和json字符串的转换也很简单,我们这里就只演示java的Map与json字符串相互转化了,其流程与上述的javaBean和json互转有点类似。需要注意的是从json转换为Map对象(或其他泛型对象)的时候,由于Java的类型擦除,有些无法直接正确转换的类型需要我们手动用new TypeReference给出,实例代码如下:
//创建 ObjectMapper实体类
ObjectMapper mapper=new ObjectMapper();
//需要序列化的Map和List
Map<String,Object>map1=new HashMap<>();
map1.put("博学谷","666");map1.put("bigsai",666);//注意两个666类型不同
map1.put("string",new String[]{"bigsai", "博学谷"});
//序列化结果
String map1str=mapper.writeValueAsString(map1);
System.out.println(map1str+"\n");
//方式一反序列化结果
Map<String,Object>m1=mapper.readValue(map1str,Map.class);
//方式二TypeReference指定反序列化类型(适合泛型和复杂类型)
Map<String,Object>m2=mapper.readValue(map1str, new TypeReference<Map<String, Object>>() {});
System.out.println(m1.toString());
System.out.println(m2.toString());
执行结果为:看完实例,你是不是发现:哇,原来三者有很相似的之处啊,是的,一个优秀的开源框架不光光要考虑其功能、性能,其易用性、对用户友好程度、官方文档等也是非常重要的指标!当然,这里仅仅是jackson功能的九牛一毛,更详细深入的学习需要到jackson官方文档去查阅。
Gson VS fastjson VS jackson
现在三种java的序列化/反序列化json框架都已经介绍啦。三者都是非常优秀的框架,这是毋庸置疑的,但是一对比,它就有一些伤害啊(嘤嘤嘤)。
名称
Gson
fastjson
Jackson
社区生态
较好
一般
较好
易用性
易用
易用
一般
小文件速度
最快
较快
较慢
大文件速度
较慢
较快
较快
稳定性
稳定
一般
稳定
安装包大小
非常小
较小
一般
综上所述来说:Gson:轻便简洁,适合中小json文件的解析。在大json文件上效率略差,且其功能比较全面,生态较好。jackson:处理大文件速度快一些,且比fastjson稳定一些。生态较好。fastjson:速度较快,但经常爆出安全问题。生态一般,主要国内使用。
对选取哪个json框架主要根据自己的需要,如果是测试或者比较简单个人使用,推荐Gson和fastjson上手比较容易。如果是需要上线,那么fastjson得慎用,可能某些数据会序列化失败。
在介绍完的最后,偷偷赞扬一下咱们fastjson主要贡献者温少,不仅是fastjson开源者温少也是另一开源框架主要贡献者—druid(阿里数据库连接池),所以这位作者大大是非常努力,将自己奉献给了开源的事业中,服务更多的人,点赞!!! 希望未来的我们,也能像温少那样有所作为!加油!
在这里,本篇对json得介绍、json语法使用以及Gson/fastjson/jackson的一些实战的介绍就此已经完毕了,json很容易,但json也可能很复杂,这要取决你具体业务的使用和需求,希望在日后的日子里,能够对json的理解和应用能够有更深一步的认知!加油,我们下次再见!
(单选)既然对比完Gson,fastjson,Jackson,那么小老弟妹们,俺来考考你对三者认识咋样,那么问题来了,你给我看看下面那个是错的呢?(B)
A.Gson体积较小,但功能丰富强大,社区生态完整。处理小文件效率较高。
B.fastjson速度较快,在国内外都比较流行,且社区生态较好。
C.jackson广泛应用于开源框架,稳定且效率较高,流行度和认可度最高。
D.Gson是谷歌开源的,fastjson是阿里开源的,Jackson是fastXML团队开源的。
小试牛刀
上面讲了那么多,对于我们来说掌握java中操作json很重要,那么咱们动手进行一个转换小案例。这里咱们要求用Gson,fastjson将下面对象进行转化:
javaBean转json字符串
假设我们有个teacher 类有name和age两个属性。
public class teacher {
private String name;
private int age;
//省略get set 以及构造函数
}
首先我们有一个对象
teacher teachersai=new teacher("bigsai",22);
大家想一下这个对象的json格式的字符串会是什么样的呢?
没错,答案就是这样的:
{"name":"bigsai","age":22}
如果是Gson,我们是这样操作的:
Gson gson=new Gson();
String teacherstr=gson.toJson(teachersai);
System.out.println(teacherstr);//{"name":"bigsai","age":22}
而如果是fastjson,它又会是怎样的呢?
String teacherstr=JSON.toJSONString(teachersai);
System.out.println(teacherstr);//{"name":"bigsai","age":22}
这样就完成java对象转成json字符串啦,是不是非常简单,因为fastjson将JSON中的很多方法写成静态static所以我们连这个对象都不需要创建。
json字符串转javaBean
上面咱们轻轻松松的就可以将javaBean转成json字符串,下面如果咱们有这么一个字符串需要转成java的teacher对象,需要怎么搞呢?
String teacherpian="{\"name\":\"bigpian\",\"age\":21}";//{"name":"bigpian","age":21}
那么创建完Gson对象只需一行代码就搞定:
Gson gson=new Gson();
teacher teacherp=gson.fromJson(teacherpian,teacher.class);
同理,咱们fastjson也是一行代码搞定:
teacher teacherp=JSON.parseObject(teacherpian,teacher.class);
是不是很容易呢?当然如果细心好学的你肯定会发现还有其他的写法啦,修行靠个人啦,更多的用法肯定还需要自己去挖掘啦!
本文总结
轻松愉快的json介绍课程到这里就结束了,通过本节课程,你了解了json的概念,分清了json和xml的异同点,也知道了json在web领域和非关系数据库的那些应用。通过系列介绍、比较、结合实际场景相信你对json的认识肯定都是非常清晰。
紧接着文章又介绍了json的语法规则以及在java中操作json的实战操作,学习和比较了Gson、fastjson、jackson的应用以及他们之间的区别点,相信你也掌握了在java语言中json的实战能力,且能够根据自己需求灵活使用哪种框架,并深入的研究下去。
json是web技术进步与发展的产物,它是一种数据交换格式。它几乎是前后端程序员不可避免的技术点,希望大家在日后能够多多回顾课程,结合其他教材文本等,将json 从立地到成佛!加油,我们下次再会!
创作不易,还请支持下,微信公众号:bigsai,一个有趣的程序员~