今天做锚点连接的时候,发现偶尔超链接出现偏移,在文档中找打了一个 :target  CSS选择器;

定义和用法

URL 带有后面跟有锚名称 #,指向文档内某个具体的元素。这个被链接的元素就是目标元素(target element)。

:target 选择器可用于选取当前活动的目标元素。

<!DOCTYPE html>
<html>
<head>
<style>
#news1:target
{
border: 2px solid #D4D4D4;
background-color: #e5eecc;
}
</style>
</head>
<body>

<h1>这是标题</h1>

<p><a href="#news1">跳转至内容 1</a></p>
<p><a href="#news2">跳转至内容 2</a></p>

<p>请点击上面的链接,:target 选择器会突出显示当前活动的 HTML 锚。</p>

<p id="news1"><b>内容 1...</b></p>
<p id="news2"><b>内容 2...</b></p>

<p><b>注释:</b> Internet Explorer 8 以及更早的版本不支持 :target 选择器。</p>

</body>
</html>

问题汇总:

    1. 侧滑返回如何禁止?

    2. 侧滑出现上一页面 (页面 js 未执行)

    3. 侧滑出现侧拉,页面空白或页面未刷新,(js 未执行)

个人感觉是框架问题,也是机制问题,ios 很容易出现这样的问题,页面禁用侧滑事件无用,出现上一页面,上一页面也检测不到,显示为空白页面

侧拉拉出侧拉的页面,页面内没有检测到任何事件,内部 js 也没有执行!

可以试试 ios 边缘拖拽关闭禁止


ios的边缘拖拽关闭可以通过下列代码来禁止:

plus.webview.currentWebview().setStyle({
                        'popGesture': 'none'
                    });

在软件中,性能一直扮演着重要的角色。在Web应用中,性能变得更加重要,因为如果页面速度很慢的话,用户就会很容易转去访问我们的竞争对手的网站。作为专业的web开发人员,我们必须要考虑这个问题。有很多“古老”的关于性能优化的最佳实践在今天依然可行,例如最小化请求数目,使用CDN以及不编写阻塞页面渲染的代码。然而,随着越来越多的web应用都在使用JavaScript,确保我们的代码运行的很快就变得很重要。

假设你有一个正在工作的函数,但是你怀疑它运行得没有期望的那样快,并且你有一个改善它性能的计划。那怎么去证明这个假设呢?在今天,有什么最佳实践可以用来测试JavaScript函数的性能呢?一般来说,完成这个任务的最佳方式是使用内置的performance.now()函数,来衡量函数运行前和运行后的时间。

在这篇文章中,我们会讨论如何衡量代码运行时间,以及有哪些技术可以避免一些常见的“陷阱”。

Performance.now()

高分辨率时间API提供了一个名为now()的函数,它返回一个DOMHighResTimeStamp对象,这是一个浮点数值,以毫秒级别(精确到千分之一毫秒)显示当前时间。单独这个数值并不会为你的分析带来多少价值,但是两个这样的数值的差值,就可以精确描述过去了多少时间。

这个函数除了比内置的Date对象更加精确以外,它还是“单调”的,简单说,这意味着它不会受操作系统(例如,你笔记本上的操作系统)周期性修改系统时间影响。更简单的说,定义两个Date实例,计算它们的差值,并不代表过去了多少时间。

“单调性”的数学定义是“(一个函数或者数值)以从不减少或者从不增加的方式改变”。

我们可以从另外一种途径来解释它,即想象使用它来在一年中让时钟向前或者向后改变。例如,当你所在国家的时钟都同意略过一个小时,以便最大化利用白天的时间。如果你在时钟修改之前创建了一个Date实例,然后在修改之后创建了另外一个,那么查看这两个实例的差值,看上去可能像“1小时零3秒又123毫秒”。而使用两个performance.now()实例,差值会是“3秒又123毫秒456789之一毫秒”。

在这一节中,我不会涉及这个API的过多细节。如果你想学习更多相关知识或查看更多如何使用它的示例,我建议你阅读这篇文章:Discovering the High Resolution Time API。

既然你知道高分辨率时间API是什么以及如何使用它,那么让我们继续深入看一下它有哪些潜在的缺点。但是在此之前,我们定义一个名为makeHash()的函数,在这篇文章剩余的部分,我们会使用它。

functionmakeHash(source){

varhash=;

if(source.length===)returnhash;

for(vari=;i

varchar=source.charCodeAt(i);

hash=((hash

hash=hash&hash;// Convert to 32bit integer

}

returnhash;

}

我们可以通过下面的代码来衡量这个函数的执行效率:

vart0=performance.now();

varresult=makeHash('Peter');

vart1=performance.now();

console.log('Took',(t1-t0).toFixed(4),'milliseconds to generate:',result);

如果你在浏览器中运行这些代码,你应该看到类似下面的输出:

Took 0.2730 milliseconds to generate: 77005292

这段代码的在线演示如下所示:

记住这个示例后,让我们开始下面的讨论。

缺陷1 – 意外衡量不重要的事情

在上面的示例中,你可以注意到,我们在两次调用performance.now()中间只调用了makeHash()函数,然后将它的值赋给result变量。这给我们提供了函数的执行时间,而没有其他的干扰。我们也可以按照下面的方式来衡量代码的效率:

vart0=performance.now();

console.log(makeHash('Peter'));// bad idea!

vart1=performance.now();

console.log('Took',(t1-t0).toFixed(4),'milliseconds');

这个代码片段的在线演示如下所示:

但是在这种情况下,我们将会测量调用makeHash(‘Peter’)函数花费的时间,以及将结果发送并打印到控制台上花费的时间。我们不知道这两个操作中每个操作具体花费多少时间, 只知道总的时间。而且,发送和打印输出的操作所花费的时间会依赖于所用的浏览器,甚至依赖于当时的上下文。

或许你已经完美的意识到console.log方式是不可以预测的。但是执行多个函数同样是错误的,即使每个函数都不会触发I/O操作。例如:

vart0=performance.now();

varname='Peter';

varresult=makeHash(name.toLowerCase()).toString();

vart1=performance.now();

console.log('Took',(t1-t0).toFixed(4),'milliseconds to generate:',result);

同样,我们不会知道执行时间是怎么分布的。它会是赋值操作、调用toLowerCase()函数或者toString()函数吗?

缺陷 #2 – 只衡量一次

另外一个常见的错误是只衡量一次,然后汇总花费的时间,并以此得出结论。很可能执行不同的次数会得出完全不同的结果。执行时间依赖于很多因素:

编辑器热身的时间(例如,将代码编译成字节码的时间)

主线程可能正忙于其它一些我们没有意识到的事情

你的电脑的CPU可能正忙于一些会拖慢浏览器速度的事情

持续改进的方法是重复执行函数,就像这样:

vart0=performance.now();

for(vari=;i

makeHash('Peter');

}

vart1=performance.now();

console.log('Took',((t1-t0)/10).toFixed(4),'milliseconds to generate');

这个示例的在线演示如下所示:

这种方法的风险在于我们的浏览器的JavaScript引擎可能会使用一些优化措施,这意味着当我们第二次调用函数时,如果输入时相同的,那么JavaScript引擎可能会记住了第一次调用的输出,然后简单的返回这个输出。为了解决这个问题,你可以使用很多不同的输入字符串,而不用重复的使用相同的输入(例如‘Peter’)。显然,使用不同的输入进行测试带来的问题就是我们衡量的函数会花费不同的时间。或许其中一些输入会花费比其它输入更长的执行时间。

缺陷 #3 – 太依赖平均值

在上一节中,我们学习到的一个很好的实践是重复执行一些操作,理想情况下使用不同的输入。然而,我们要记住使用不同的输入带来的问题,即某些输入的执行时间可能会花费所有其它输入的执行时间都长。这样让我们退一步来使用相同的输入。假设我们发送同样的输入十次,每次都打印花费了多长时间。我们会得到像这样的输出:

Took0.2730millisecondstogenerate:77005292

Took0.0234millisecondstogenerate:77005292

Took0.0200millisecondstogenerate:77005292

Took0.0281millisecondstogenerate:77005292

Took0.0162millisecondstogenerate:77005292

Took0.0245millisecondstogenerate:77005292

Took0.0677millisecondstogenerate:77005292

Took0.0289millisecondstogenerate:77005292

Took0.0240millisecondstogenerate:77005292

Took0.0311millisecondstogenerate:77005292

请注意第一次时间和其它九次的时间完全不一样。这很可能是因为浏览器中的JavaScript引擎使用了优化措施,需要一些热身时间。我们基本上没有办法避免这种情况,但是会有一些好的补救措施来阻止我们得出一些错误的结论。

一种方式是去计算后面9次的平均时间。另外一种更加使用的方式是收集所有的结果,然后计算“中位数”。基本上,它会将所有的结果排列起来,对结果进行排序,然后取中间的一个值。这是performance.now()函数如此有用的地方,因为无论你做什么,你都可以得到一个数值。

让我们再试一次,这次我们使用中位数函数:

varnumbers=[];

for(vari=;i

vart0=performance.now();

makeHash('Peter');

vart1=performance.now();

numbers.push(t1-t0);

}

functionmedian(sequence){

sequence.sort();// note that direction doesn't matter

returnsequence[Math.ceil(sequence.length/2)];

}

console.log('Median time',median(numbers).toFixed(4),'milliseconds');

缺陷 #4 – 以可预测的方式比较函数

我们已经理解衡量一些函数很多次并取平均值总会是一个好主意。而且,上面的示例告诉我们使用中位数要比平均值更好。

在实际中,衡量函数执行时间的一个很好的用处是来了解在几个函数中,哪个更快。假设我们有两个函数,它们的输入参数类型一致,输出结果相同,但是它们的内部实现机制不一样。

functionisIn(haystack,needle){

varfound=false;

haystack.forEach(function(element){

if(element.toLowerCase()===needle.toLowerCase()){

found=true;

}

});

returnfound;

}

console.log(isIn(['a','b','c'],'B'));// true

console.log(isIn(['a','b','c'],'d'));// false

我们可以立刻发现这个方法有改进的地方,因为haystack.forEach循环总会遍历所有的元素,即使我们可以很快找到一个匹配的元素。现在让我们使用for循环来编写一个更好的版本。

functionisIn(haystack,needle){

for(vari=,len=haystack.length;i

if(haystack[i].toLowerCase()===needle.toLowerCase()){

returntrue;

}

}

returnfalse;

}

console.log(isIn(['a','b','c'],'B'));// true

console.log(isIn(['a','b','c'],'d'));// false

现在我们来看哪个函数更快一些。我们可以分别运行每个函数10次,然后收集所有的测量结果:

functionisIn1(haystack,needle){

varfound=false;

haystack.forEach(function(element){

if(element.toLowerCase()===needle.toLowerCase()){

found=true;

}

});

returnfound;

}

functionisIn2(haystack,needle){

for(vari=,len=haystack.length;i

if(haystack[i].toLowerCase()===needle.toLowerCase()){

returntrue;

}

}

returnfalse;

}

console.log(isIn1(['a','b','c'],'B'));// true

console.log(isIn1(['a','b','c'],'d'));// false

console.log(isIn2(['a','b','c'],'B'));// true

console.log(isIn2(['a','b','c'],'d'));// false

functionmedian(sequence){

sequence.sort();// note that direction doesn't matter

returnsequence[Math.ceil(sequence.length/2)];

}

functionmeasureFunction(func){

varletters='a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z'.split(',');

varnumbers=[];

for(vari=;i

vart0=performance.now();

func(letters,letters[i]);

vart1=performance.now();

numbers.push(t1-t0);

}

console.log(func.name,'took',median(numbers).toFixed(4));

}

measureFunction(isIn1);

measureFunction(isIn2);

我们运行上面的代码, 可以得出如下的输出:

true

false

true

false

isIn1took0.0050

isIn2took0.0150

这个示例的在线演示如下所示:

到底发生了什么?第一个函数的速度要快3倍!那不是我们假设的情况。

其实假设很简单,但是有些微妙。第一个函数使用了haystack.forEach方法,浏览器的JavaScript引擎会为它提供一些底层的优化,但是当我们使用数据索引技术时,JavaScript引擎没有提供对应的优化。这告诉我们:在真正测试之前,你永远不会知道。

结论

在我们试图解释如何使用performance.now()方法得到JavaScript精确执行时间的过程中,我们偶然发现了一个基准场景,它的运行结果和我们的直觉相反。问题在于,如果你想要编写更快的web应用,我们需要优化JavaScript代码。因为计算机(几乎)是一个活生生的东西,它很难预测,有时会带来“惊喜”,所以如果了解我们代码是否运行更快,最可靠的方式就是编写测试代码并进行比较。

当我们有多种方式来做一件事情时,我们不知道哪种方式运行更快的另一个原因是要考虑上下文。在上一节中,我们执行一个大小写不敏感的字符串查询来寻找1个字符串是否在其它26个字符串中。当我们换一个角度来比较1个字符串是否在其他100,000个字符串中时,结论可能是完全不同的。

上面的列表不是很完整的,因为还有更多的缺陷需要我们去发现。例如,测试不现实的场景或者只在JavaScript引擎上测试。但是确定的是对于JavaScript开发者来说,如果你想编写更好更快的Web应用,performance.now()是一个很棒的方法。最后但并非最不重要,请谨记衡量执行时间只是“更好的代码”的一反面。我们还要考虑内存消耗以及代码复杂度。

three.js 学习

创建场景

    三个要素: 场景,相机,渲染器

var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 );

var renderer = new THREE.WebGLRenderer();
renderer.setSize( window.innerWidth, window.innerHeight ); // setSize 
document.body.appendChild( renderer.domElement );

PerspectiveCamera 场景

现在已经设置好了场景,相机,渲染器。 three  有几个不同的渲染器, 我们使用 PerspectiveCamera.

1. 第一个属性是视野, FOV 是任何给定时刻显示器上看到的场景的范围。该值以度为单位。

2. 第二个是设置宽高,

3. 第三个属性与第四个属性是近和远裁剪平面。这意味着,远离相机和物体远远超过或接近近处的物体不会被渲染,

WebGLRenderer 渲染器

 除了 webGLRenderer 之外 还有其他一些功能,通常用作旧版浏览器的用户的回退功能,或者由于某些原因而无法支持 WebGL 的用户。

添加到页面

我们将 renderer 元素添加到 HTML 文档中。这是渲染器用来向我们显示场景的 <canvas> 元素。

setSize 

除了创建渲染器实例外,设置 程序显示的实际大小,如果想保持应用程序的大小,但以较低的分辨率渲染它,可以通过 false 作为 updateStyle (第三个参数)调用 setSize 来实现,例如 setSize(window.innerWidth / 2, window.innerHeight / 2, false)

Array.prototype 属性表示 Array 构造函数的原型,并允许您向所有的 Array 对象添加新的属性和方法。

描述

Array 实例继承自 Array.prototype 。 与所有构造函数一样,可以更改构造函数的原型对象,以对所有的 Array 实例进行更改。 例如,可以添加新的方法和属性以扩展所有 Array 对象。 

Array.prototype 本身也是一个 Array

Array.isArray(Array.prototype)

属性

Array.prototype.constructor

     所有的数组实例都继承了这个属性,它的值就是 Array, 表明了所有的数组都是由 Array 构造函数出来的。

Array.prototype.length

    因为 Array.prototype 也是个数组,所以它也有 length 属性, 这个值为0, 因为它是个空数组。

方法

会改变自身的方法

下面这些方法会改变调用它们的对象自身的值:

Array.prototype.copyWithin()

    在数组内部,将一段元素序列拷贝到另一段元素序列上,覆盖原有的值。

Array.prototype.fill()

    将数组中指定区间的所有元素的值,都替换成某个固定的值。

Array.prototype.pop()

    删除数组的最后一个元素,并返回这个元素。

Array.prototype.push()

    在数组的末尾增加一个或多个元素,并返回数组的新长度。

Array.prototype.reverse()

    颠倒数组中元素的排列顺序,即原先的第一个变为最后一个,原先的最后一个变为第一个。

Array.prototype.shift()

    删除数组的第一个元素,并返回这个元素。

Array.prototype.sort()

    对数组元素进行排序,并返回当前数组。

Array.prototype.splice()

    在任意的位置给数组添加或删除任意个元素。

Array.prototype.unshift()

    在数组的开头增加一个或多个元素,并返回数组的新长度。

不会改变自身的方法

下面的这些方法绝对不会改变调用它们的对象的值,只会返回一个新的数组或者返回一个其他的期望值

Array.prototype.concat()

    返回一个由当前数组和其它若干个数组或者若干个非数组值组合而成的新数组。

Array.prototype.includes()

    判断单签数字是否包含某指定值,如果是返回 true ,否则返回 false。

Array.prototype.join()

    连接所有数组元素组成一个字符串。

Array.prototype.slice()

    抽取当前数组中的一段元素组合成一个新的数组。

Array.prototype.toSource()  // 这个 API 并没有标准化

    返回一个表示当前数组字面量的字符串。遮蔽了原型链上的 Object.prototype.toSource() 方法。

Array.prototype.toString()

    返回一个由所有数组元素组合而成的字符串。遮蔽了原型链上的 Object.prototype.toString() 方法

Array.prototype.toLocaleString()

   返回一个由所有数组元素组合而成的本地化后的字符串。遮蔽了原型链上的 Object.prototype.toLocaleString() 方法。

Array.prototype.indexOf()

    返回数组中第一个与指定值相等的元素的索引,如果找不到这样的元素,则返回 -1。

Array.prototype.lastIndexOf()

    返回数组中最后一个(从右边数第一个)与指定值相等的元素的索引,如果找不到这样的元素,则返回 -1。


遍历方法

    在下面的众多遍历方法中,有很多方法都需要指定一个回调函数作为参数。在回调函数执行之前,数组的长度会被缓存在某个地方,所以,如果你在回调函数中为当前数组添加了新的元素,那么那些新添加的元素是不会被遍历到的。此外,如果在回调函数中对当前数组进行了其它修改,比如改变某个元素的值或者删掉某个元素,那么随后的遍历操作可能会受到未预期的影响。总之,不要尝试在遍历过程中对原数组进行任何修改,虽然规范对这样的操作进行了详细的定义,但为了可读性和可维护性,请不要这样做

Array.prototype.forEach()

    为数组中的每个元素执行一次回调函数。

Array.prototype.entries() 

    返回一个数组迭代器对象,该迭代器会包含所有数组元素的键值对。

Array.prototype.every()

    如果数组中的每个元素都满足测试函数,则返回 true,否则返回 false。

Array.prototype.some()

    如果数组中至少有一个元素满足测试函数,则返回 true,否则返回 false。

Array.prototype.filter()

    将所有在过滤函数中返回 true 的数组元素放进一个新数组中并返回。

Array.prototype.find() 

    找到第一个满足测试函数的元素并返回那个元素的值,如果找不到,则返回 undefined。

Array.prototype.findIndex() 

    找到第一个满足测试函数的元素并返回那个元素的索引,如果找不到,则返回 -1。

 Array.prototype.keys() 

    返回一个数组迭代器对象,该迭代器会包含所有数组元素的键。Array.prototype.map() 返回一个由回调函数的返回值组成的新数组。

Array.prototype.reduce()

    从左到右为每个数组元素执行一次回调函数,并把上次回调函数的返回值放在一个暂存器中传给下次回调函数,并返回最后一次回调函数的返回值。

Array.prototype.reduceRight()

    从右到左为每个数组元素执行一次回调函数,并把上次回调函数的返回值放在一个暂存器中传给下次回调函数,并返回最后一次回调函数的返回值。

 Array.prototype.values() 

    返回一个数组迭代器对象,该迭代器会包含所有数组元素的值。

Array.prototype[@@iterator]()

    和上面的 values() 方法是同一个函数。


vue 中显示隐藏问题

vue  使用当用 遇到一个 temple 模板里面的坑,就是 v-if  和 v-show 不显示的问题,需要在标签中加入 style="display:black"

vue 中组建之间的传参问题

1.父向子组件传参

App.vue为父,引入componetA组件之后,则可以在App.vue中使用标签(注意驼峰写法要改成componet-a写法,因为html对大小写不敏感,componenta与componentA对于它来说是一样的,不好区分,所以使用小写-小写这种写法)。

而子组件componetA中,声明props参数’msgfromfa’之后,就可以收到父向子组件传的参数了。例子中将msgfromfa显示在<p>标签中。

App.vue中
<component-a msgfromfa="( 不积跬步,无以至千里;不积小流,无以成江海)"></component-a>
import componentA from './components/componentA'
export default {
    new Vue({
        components: {
          componentA
        }
    })
}

componentA.vue中
<p>{{ msgfromfa }}</p>
export default {
    props: ['msgfromfa']
}

2.子组件向父传参

2.1  .$emit

用法:vm.$emit( event, […args] ),触发当前实例上的事件。附加参数都会传给监听器回调。
例子:

App.vue中component-a绑定了自定义事件”child-say”。

子组件componentA中,单击按钮后触发”child-say”事件,并传参msg给父组件。父组件中listenToMyBoy方法把msg赋值给childWords,显示在<p>标签中。

App.vue中
<p>Do you like me? {{childWords}}</p>
<component-a msgfromfa="(Just Say U Love Me)" v-on:child-say="listenToMyBoy"></component-a>
import componentA from './components/componentA'
export default {
    new Vue({
        data: function () {
            return {
              childWords: ""
            }
        },
        components: {
          componentA
        },
        methods: {
            listenToMyBoy: function (msg){
              this.childWords = msg
            }
        }
    })
}
componentA.vue中
<button v-on:click="onClickMe">like!</button>
import componentA from './components/componentA'
export default {
    data: function () {
        return {
          msg: 'I like you!'
        }
    },
    methods: {
      onClickMe: function(){
        this.$emit('child-say',this.msg);
      }
    }
}

2.2  .$dispatch

用法:vm.$dispatch( event, […args] ),派发事件,首先在实例上触发它,然后沿着父链向上冒泡在触发一个监听器后停止。
例子:App.vue中events中注册”child-say”事件。子组件componentA中,单击按钮后触发”child-say”事件,并传参msg给父组件。父组件中”child-say”方法把msg赋值给childWords,显示在<p>标签中。

App.vue中
<p>Do you like me? {{childWords}}</p>
<component-a msgfromfa="(Just Say U Love Me)"></component-a>
import componentA from './components/componentA'
export default {
    new Vue({
        events: {
            'child-say' : function(msg){
              this.childWords = msg
            }
        }
    })
}
componentA.vue中
<button v-on:click="onClickMe">like!</button>
import componentA from './components/componentA'
export default {
    data: function () {
        return {
          msg: 'I like you!'
        }
    },
    methods: {
      onClickMe: function(){
        this.$dispatch('child-say',this.msg);
      }
    }
}

vue 中 axios 请求

axios 请求的坑:请求当中的 axios 在浏览器中需要 URLSearchParams 的支持

var params = new URLSearchParams();
params.append('param1','value1');
params.append('param2','value2');
axios.post('/foo',params);

// 注意: URLSearchParams  有兼容性问题 ios 10.11 以下不支持 需要兼容,请看 dome

一、DOM操作影响页面性能的核心问题

通过js操作DOM的代价很高,影响页面性能的主要问题有如下几点:

  • 访问和修改DOM元素
  • 修改DOM元素的样式,导致重绘重排
  • 通过对DOM元素的事件处理,完成与用户的交互功能

DOM的修改会导致重绘重排

  • 重绘是指一些样式的修改,元素的位置和大小都没有改变;
  • 重排是指元素的位置或尺寸发生了变化,浏览器需要重新计算渲染树,而新的渲染树建立后,浏览器会重新绘制受影响的元素。

页面重绘的速度要比页面重排的速度快,在页面交互中要尽量避免页面的重排操作。浏览器不会在js执行的时候更新DOM,而是会把这些DOM操作存放在一个队列中,在js执行完之后按顺序一次性执行完毕,因此在js执行过程中用户一直在被阻塞。

1.页面渲染过程

一个页面更新时,渲染过程大致如下:

  • JavaScript: 通过js来制作动画效果或操作DOM实现交互效果
  • Style: 计算样式,如果元素的样式有改变,在这一步重新计算样式,并匹配到对应的DOM上
  • Layout: 根据上一步的DOM样式规则,重新进行布局(重排
  • Paint: 在多个渲染层上,对新的布局重新绘制(重绘
  • Composite: 将绘制好的多个渲染层合并,显示到屏幕上

在网页生成的时候,至少会进行一次布局和渲染,在后面用户的操作时,不断的进行重绘或重排,因此如果在js中存在很多DOM操作,就会不断地出发重绘或重排,影响页面性能。

2.DOM操作对页面性能的影响

如前面所说,DOM操作影响页面性能的核心问题主要在于DOM操作导致了页面的重绘重排,为了减少由于重绘和重排对网页性能的影响,我们要知道都有哪些操作会导致页面的重绘或者重排。

2.1 导致页面重排的一些操作:

  • 内容改变
    • 文本改变或图片尺寸改变
  • DOM元素的几何属性的变化
    • 例如改变DOM元素的宽高值时,原渲染树中的相关节点会失效,浏览器会根据变化后的DOM重新排建渲染树中的相关节点。如果父节点的几何属性变化时,还会使其子节点及后续兄弟节点重新计算位置等,造成一系列的重排。
  • DOM树的结构变化
    • 添加DOM节点、修改DOM节点位置及删除某个节点都是对DOM树的更改,会造成页面的重排。浏览器布局是从上到下的过程,修改当前元素不会对其前边已经遍历过的元素造成影响,但是如果在所有的节点前添加一个新的元素,则后续的所有元素都要进行重排。
  • 获取某些属性
    • 除了渲染树的直接变化,当获取一些属性值时,浏览器为取得正确的值也会发生重排,这些属性包括:offsetTopoffsetLeft、 offsetWidthoffsetHeightscrollTopscrollLeftscrollWidthscrollHeight、 clientTopclientLeftclientWidthclientHeightgetComputedStyle()
  • 浏览器窗口尺寸改变
    • 窗口尺寸的改变会影响整个网页内元素的尺寸的改变,即DOM元素的集合属性变化,因此会造成重排。

2.2 导致页面重绘的操作

  • 应用新的样式或者修改任何影响元素外观的属性
    • 只改变了元素的样式,并未改变元素大小、位置,此时只涉及到重绘操作。
  • 重排一定会导致重绘
    • 一个元素的重排一定会影响到渲染树的变化,因此也一定会涉及到页面的重绘。

二、高频操作DOM会导致的问题

接下来会分享一下在平时项目中由于高频操作DOM影响网页性能的问题。

1. 抽奖项目的高频操作DOM问题

1.1 存在的问题

在最近做的抽奖项目中,就遇到了这样的由于高频操作DOM,导致页面性能变差的问题。在经历几轮抽奖后,文字滚动速度越来越慢,肉眼能感受到与第一次抽奖时文字滚动速度的明显差别,如持续时间过长或轮次过多,还会造成浏览器假死现象。

实现 demo

1.2 问题分析

下图为抽奖时文字滚动过程中的timeline记录。

timeline分析:

  1. FPS:最上面一栏为绿色柱形为帧率(FPS),顶点值为60fps,上方红色方块表示长帧,这些长帧被Chrome称为jank(卡顿)。
  2. CPU:第二栏为CPU,蓝色表示loading(网络通信和HTML解析),黄色表示scripting(js执行时间),紫色表示rendering(样式计算和布局,即重排), 绿色为painting(即重绘)。

更多timeline使用方法可参考:如何使用Chrome Timeline 工具(译)

由上图可以看出,在文字滚动过程中红色方块出现频繁,页面中存在的卡顿过多。帧率的值越低,人眼感受到的效果越差。

接下来选择一段长帧区域放大来看

在这段区域内最大一帧达到了49.7ms,帧率只有20fps,接下来看看这一帧里是什么因素耗时过长


由上图可以看出,耗时最大的在scripting,js的执行时间达到了44.9ms,占总时间的93.2%,因为主要靠js计算控制DOM的显示内容,所以js运行时间过长。

选取一段FPS值很低的部分查看造成这段值低的原因

由下图可看出主要为dom.html中的js执行占用时间。

点进dom.html文件,即可定位到该函数

由此可知,主要是rolling这个函数执行时间过长,对该部分失帧影响较大。而这个函数的主要作用就是实现文字的滚动效果,也可以从代码中看出,这个函数利用的setTimeout来反复执行,并且在这个函数中存在着循环以及大量的DOM操作,造成了页面的失帧等问题。

1.3 优化方案

针对该项目中的问题,采取的解决方法是:

  • 一次性生成全部<li>,并且隐藏这些<li>,随机生成一组随机数数组,只有index与数组里面的随机数相等时,才显示该位置的<li>,虽然也会触发重排和重绘,但是性能要远远高于直接操作DOM的添加和删除。
  • 用requestAnimationFrame取代setTimeout不断生成随机数。

requestAnimationFrame与setTimeout和setInterval类似,都是通过递归调用同一个方法不断更新页面。

  • setTimeout():在特定的时间后执行函数,而且只执行一次,如果在特定时间前想取消执行函数,可以用clearTimeout立即取消执行。但是并不是每次执行setTimeout都会在特定的时间后执行,页面加载后js会按照主线程中的顺序按序执行那个,如果在延迟时间内主线程不空闲,setTimeout里面的函数是不会执行的,它会延迟到主线程空闲时才执行。
  • setInterval():在特定的时间间隔内重复执行函数,除非主动清除它,不然会一直执行下去,清除函数可以使用clearInterval。setInterval也会等到主线程空闲了再执行,但是setInterval去排队时,如果发现自己还在队列中未执行,就会被drop掉,所以可能会造成某段时间的函数未被执行。
  • requestAnimationFrame():它不需要设置时间间隔,它会在浏览器每次刷新之前执行回调函数的任务。这样我们动画的更新就能和浏览器的刷新频率保持一致。requestAnimationFrame在运行时,浏览器会自动优化方法的调用,并且如果页面不是激活状态下的话,动画会自动暂停,有效节省了CPU开销。

在采用上面的方法进行优化后,在经历多轮抽奖后,文字滚动速度依旧正常,网页性能良好,不会出现文字滚动速度越来越慢,最后导致浏览器假死的现象。

实现demo: https://gxt19940130.github.io/demo/demo_gxt/dom_by_vue.html

1.4 优化前后FPS对比

优化前文字滚动时的timeline


优化后文字滚动时的timeline

优化前的代码对DOM操作很频繁,因此FPS值普遍偏低,而优化后可以看出红色方块明显减少,FPS值一直处于高值。

1.5 优化前后CPU占用对比

优化前文字滚动时的timeline


优化后文字滚动时的timeline


优化前js的CPU占用率较高,而优化后占用CPU的主要为渲染时间,因为优化后的代码只是控制了节点的显示和隐藏,所以在js上消耗较少,在渲染上消耗较大。

2.吸顶导航条相关及scroll滚动优化

2.1 存在的问题

吸顶导航条要求当页面滚动到某个区域时,对应该区域的导航条在设置的显示范围内保持吸顶显示。涉及到的操作:

  • 监听页面的scroll事件
  • 在页面滚动时进行计算和DOM操作
    • 计算:计算当前所在位置是否为对应导航条的显示范围
    • DOM操作:显示在范围内的导航条并且隐藏其他导航条

由于scroll事件被触发的频率高、间隔近,如果此时进行DOM操作或计算并且这些DOM操作和计算无法在下一次scroll事件发生前完成,就会造成掉帧、页面卡顿,影响用户体验。

2.2 优化方案

针对该项目中的问题,采取的解决方法是:

  • 尽量控制DOM的显示或隐藏,而不是删除或添加:

    页面加载时根据当前页面中吸顶导航的数量复制对应的DOM,并且隐藏这些导航。当页面滚动到指定区域后,显示对应的导航。

  • 一次性操作DOM:

    将复制的DOM存储到数组中,将该数组append到对应的父节点下,而不是根据复制得到DOM的数量依次循环插入到父节点下。

  • 多做缓存:

    如果某个节点将在后续进行多次操作,可以将该节点利用变量存储起来,而不是每次进行操作时都去查找一遍该节点。

  • 使用 requestAnimationFrame优化页面滚动

// 在页面滚动时对显示范围进行计算

  // 延迟到整个dom加载完后再调用,并且异步到所有事件后执行

  $(function(){

  //animationShow优化滚动效果,scrollShow为实际计算显示范围及操作DOM的函数

      setTimeout( function() {

          window.Scroller.on('scrollend', animationShow);

          window.Scroller.on('scrollmove', animationShow);

      })

  });

  function animationShow(){

      return window.requestAnimationFrame ?window.requestAnimationFrame(scrollShow) : scrollShow();

  }

对于scroll的滚动优化还可以采用防抖(Debouncing)和节流(Throttling)的方式,但是防抖和节流的方式还是要借助于setTimeout,因此和requestAnimationFrame相比,还是requestAnimationFrame实现效果好一些。

三、针对操作DOM的性能优化方法总结

为了减少DOM操作对页面性能产生的影响,在实现页面的交互效果时一定要注意一下几点:

1.减少在循环内进行DOM操作,在循环外部进行DOM缓存

//优化前代码

function Loop() {

   console.time("loop1");

   for (var count = 0; count < 15000; count++) {

       document.getElementById('text').innerHTML += 'dom';

   }

   console.timeEnd("loop1");

}

//优化后代码

function Loop2() {

    console.time("loop2");

    var content = '';

    for (var count = 0; count < 15000; count++) {

        content += 'dom';

    }

    document.getElementById('text2').innerHTML += content;

    console.timeEnd("loop2");

}

两个函数的执行时间对比:

优化前的代码中,每进行一次循环,都会读取一次divinnerHtml属性,并且对这个属性进行了重新赋值,即每循环一次就会操作两次DOM,因此执行时间很长,页面性能差。

在优化后的代码中,将要更新的DOM内容进行缓存,在循环时只操作字符串,循环结束后字符串的值写入到div中,只进行了一次查找innerHtml属性和一次对该属性重新赋值的操作,因此同样的循环次数先,优化后的方法执行时间远远少于优化前。

2.只控制DOM节点的显示或隐藏,而不是直接去改变DOM结构

在抽奖项目中频繁操作DOM来控制文字滚动的方法(demo:https://gxt19940130.github.io/demo/dom.html 导致页面性能很差,最后修改为如下代码。

<div class="staff-list" :class="list">

   <ul class="staff-list-ul">

       <li v-for="item in staffList" v-show="isShow($index)">

           <div>{{{item.staff_name | addSpace}}} </div>

           <div class="staff_phone">{{item.phone_no}} </div>

       </li>

   </ul>

</div>

上面代码的优化原理即先生成所有DOM节点,但是所有节点均不显示出来,利用vue.js中的v-show,根据计算的随机数来控制显示某个<li>,来达到文字滚动效果。

如果采用jquery,则需要将生成的所有<li>全部存放在<ul>下,并且隐藏它们,在根据生成的随机数组,利用jquery查找index与生成的随机数对应的<li>并显示,达到文字滚动效果。

优化后demo: https://gxt19940130.github.io/demo/demo_gxt/dom_by_vue.html

对比结果可查看2.4

3.操作DOM前,先把DOM节点删除或隐藏

var list1 = $(".list1");

list1.hide();

for (var i = 0; i < 15000; i++) {

    var item = document.createElement("li");

    item.append(document.createTextNode('0'));

    list1.append(item);

}

list1.show();

display属性值为none的元素不在渲染树中,因此对隐藏的元素操作不会引发其他元素的重排。如果要对一个元素进行多次DOM操作,可以先将其隐藏,操作完成后再显示。这样只在隐藏和显示时触发2次重排,而不会是在每次进行操作时都出发一次重排。

页面rendering时间对比:

下图为同样的循环次数下未隐藏节点直接进行DOM操作的rendering时间(图一)和隐藏节点再进行DOM操作的rendering时间(图二)

由对比图可以看出,总时间、js执行时间以及rendering时间都明显减少,并且避免了painting以及其他的一些操作。

4. 最小化重绘和重排

//优化前代码

var element = document.getElementById('mydiv');

element.style.height = "100px";  

element.style.borderLeft = "1px";  

element.style.padding = "20px";

在上面的代码中,每对element进行一次样式更改都会影响该元素的集合结构,最糟糕情况下会触发三次重排。

优化方式:利用js或jquery对该元素的class重新赋值,获得新的样式,这样减少了多次的DOM操作。

//优化后代码

//js操作

.newStyle {  

    height: 100px;  

    border-left: 1px;  

    padding: 20px;  

}  

element.className = "newStyle";

//jquery操作

$(element).css({

    height: 100px;  

    border-left: 1px;  

    padding: 20px;

})

到此本文结束,如果对于问题分析存在不正确的地方,还请及时指出,多多交流。

参考文章:

  • 高性能JS-DOM
  • Effective前端6:避免页面卡顿
  • 前端性能优化
  • 高性能滚动 scroll 及页面渲染优化
  • 脑洞大开:为啥帧率达到 60 fps 就流畅?
  • 如何使用Chrome Timeline 工具(译)

 

Array.prototype.map()

map() 方法创建一个新数组,其结果是该数组中的每个元素都调用一个提供的函数后返回的结果。

let numbers = [1, 5, 10, 15];
let doubles = numbers.map((x) => {
   return x * 2;
});

// doubles is now [2, 10, 20, 30]
// numbers is still [1, 5, 10, 15]


let numbers = [1, 4, 9];
let roots = numbers.map(Math.sqrt);

// roots is now [1, 2, 3]
// numbers is still [1, 4, 9]

语法

let array = arr.map(function callback(currentValue, index, array) { 
    // Return element for new_array 
}[, thisArg])

参数

callback
生成新数组元素的函数,使用三个参数:

currentValue
callback 的第一个参数,数组中正在处理的当前元素。
index
callback 的第二个参数,数组中正在处理的当前元素的索引。
array
callback 的第三个参数,map 方法被调用的数组。
thisArg
可选的。执行 callback 函数时 使用的this 值。

返回值

一个新数组,每个元素都是回调函数的结果。

描述

map 方法会给原数组中的每个元素都按顺序调用一次  callback 函数。callback 每次执行后的返回值(包括 undefined)组合起来形成一个新数组。 callback 函数只会在有值的索引上被调用;那些从来没被赋过值或者使用 delete 删除的索引则不会被调用。

callback 函数会被自动传入三个参数:数组元素,元素索引,原数组本身。

如果 thisArg 参数有值,则每次 callback 函数被调用的时候,this 都会指向 thisArg 参数上的这个对象。如果省略了 thisArg 参数,或者赋值为 null 或 undefined,则 this 指向全局对象 。

map 不修改调用它的原数组本身(当然可以在 callback 执行时改变原数组)。

使用 map 方法处理数组时,数组元素的范围是在 callback 方法第一次调用之前就已经确定了。在 map 方法执行的过程中:原数组中新增加的元素将不会被 callback 访问到;若已经存在的元素被改变或删除了,则它们的传递到 callback 的值是 map 方法遍历到它们的那一时刻的值;而被删除的元素将不会被访问到。

用一个仅有一个参数的函数来mapping一个数字数组

下面的代码表示了当函数需要一个参数时map的工作方式。这个参数会遍历原始数组中的元素。

var numbers = [1, 4, 9];
var doubles = numbers.map(function(num) {
  return num * 2;
});

// doubles is now [2, 8, 18]
// numbers is still [1, 4, 9]

一般的 map 方法

下面的例子演示如何在一个 String  上使用 map 方法获取字符串中每个字符所对应的 ASCII 码组成的数组:

var map = Array.prototype.map
var a = map.call("Hello World", function(x) { 
  return x.charCodeAt(0); 
})
// a的值为[72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100]

querySelectorAll 应用

下面代码展示了如何去遍历用 querySelectorAll 得到的动态对象集合。在这里,我们获得了文档里所有选中的选项,并将其打印:

var elems = document.querySelectorAll('select option:checked');
var values = Array.prototype.map.call(elems, function(obj) {
  return obj.value;
});

反转字符串

var str = '12345';
Array.prototype.map.call(str, function(x) {
  return x;
}).reverse().join(''); 

// Output: '54321'
// Bonus: use '===' to test if original string was a palindrome

兼容旧环境(Polyfill)Edit

map 是在最近的 ECMA-262 标准中新添加的方法;所以一些旧版本的浏览器可能没有实现该方法。在那些没有原生支持 map 方法的浏览器中,你可以使用下面的 Javascript 代码来实现它。所使用的算法正是 ECMA-262,第 5 版规定的。假定ObjectTypeError, 和 Array 有他们的原始值。而且 callback.call 的原始值也是 Function.prototype.call

// 实现 ECMA-262, Edition 5, 15.4.4.19
// 参考: http://es5.github.com/#x15.4.4.19
if (!Array.prototype.map) {
  Array.prototype.map = function(callback, thisArg) {

    var T, A, k;

    if (this == null) {
      throw new TypeError(" this is null or not defined");
    }

    // 1. 将O赋值为调用map方法的数组.
    var O = Object(this);

    // 2.将len赋值为数组O的长度.
    var len = O.length >>> 0;

    // 3.如果callback不是函数,则抛出TypeError异常.
    if (Object.prototype.toString.call(callback) != "[object Function]") {
      throw new TypeError(callback + " is not a function");
    }

    // 4. 如果参数thisArg有值,则将T赋值为thisArg;否则T为undefined.
    if (thisArg) {
      T = thisArg;
    }

    // 5. 创建新数组A,长度为原数组O长度len
    A = new Array(len);

    // 6. 将k赋值为0
    k = 0;

    // 7. 当 k < len 时,执行循环.
    while(k < len) {

      var kValue, mappedValue;

      //遍历O,k为原数组索引
      if (k in O) {

        //kValue为索引k对应的值.
        kValue = O[ k ];

        // 执行callback,this指向T,参数有三个.分别是kValue:值,k:索引,O:原数组.
        mappedValue = callback.call(T, kValue, k, O);

        // 返回值添加到新数组A中.
        A[ k ] = mappedValue;
      }
      // k自增1
      k++;
    }

    // 8. 返回新数组A
    return A;
  };      
}
 

Array.prototype.some()


some() 方法测试数组中的某些元素是否通过由提供的函数实现的测试。

const isBiggerThan10 = (element, index, array) => {
return element > 10;
}

[2, 5, 8, 1, 4].some(isBiggerThan10);
// false

[12, 5, 8, 1, 4].some(isBiggerThan10);
// true

语法: arr.some(callback[, thisArg])

参数

callback
用来测试每个元素的函数。
thisArg
执行 callback 时使用的 this 值。

描述

some 为数组中的每一个元素执行一次 callback 函数,直到找到一个使得 callback 返回一个“真值”(即可转换为布尔值 true 的值)。如果找到了这样一个值,some 将会立即返回 true。否则,some 返回 falsecallback 只会在那些”有值“的索引上被调用,不会在那些被删除或从来未被赋值的索引上调用。

callback 被调用时传入三个参数:元素的值,元素的索引,被遍历的数组。

如果为 some 提供了一个 thisArg 参数,将会把它传给被调用的 callback,作为 this 值。否则,在非严格模式下将会是全局对象,严格模式下是 undefined

some 被调用时不会改变数组。

some 遍历的元素的范围在第一次调用 callback. 时就已经确定了。在调用 some 后被添加到数组中的值不会被 callback 访问到。如果数组中存在且还未被访问到的元素被 callback 改变了,则其传递给 callback 的值是 some 访问到它那一刻的值。

兼容

if (!Array.prototype.some)
{
Array.prototype.some = function(fun /*, thisArg */)
{
'use strict';

if (this === void 0 || this === null)
throw new TypeError();

var t = Object(this);
var len = t.length >>> 0;
if (typeof fun !== 'function')
throw new TypeError();

var thisArg = arguments.length >= 2 ? arguments[1] : void 0;
for (var i = 0; i < len; i++)
{
if (i in t && fun.call(thisArg, t[i], i, t))
return true;
}

return false;
};
}

光标位置获取,设置,插入的操作

    var cursorPosition = {
        get: function (textarea) {
            var rangeData = {text: "", start: 0, end: 0};

            if (textarea.setSelectionRange) { // W3C 标准获取光标位置
                textarea.focus();
                rangeData.start = textarea.selectionStart;
                rangeData.end = textarea.selectionEnd;
                rangeData.text = (rangeData.start != rangeData.end) ? textarea.value.substring(rangeData.start, rangeData.end) : "";
            } else if (document.selection) { // IE
                textarea.focus();
                var i,
                    oS = document.selection.createRange(),
// Don't: oR = textarea.createTextRange()
                    oR = document.body.createTextRange();
                oR.moveToElementText(textarea);

                rangeData.text = oS.text;
                rangeData.bookmark = oS.getBookmark();

// object.moveStart(sUnit [, iCount])
// Return Value: Integer that returns the number of units moved.
                for (i = 0; oR.compareEndPoints('StartToStart', oS) &lt; 0 &amp;&amp; oS.moveStart("character", -1) !== 0; i++) {
// Why? You can alert(textarea.value.length)
                    if (textarea.value.charAt(i) == '\r') {
                        i++;
                    }
                }
                rangeData.start = i;
                rangeData.end = rangeData.text.length + rangeData.start;
            }

            return rangeData;
        },

        set: function (textarea, rangeData) { // 设置光标位置
            var oR, start, end;
            if (!rangeData) {
                alert("You must get cursor position first.")
            }
            textarea.focus();
            if (textarea.setSelectionRange) { // W3C
                textarea.setSelectionRange(rangeData.start, rangeData.end);
            } else if (textarea.createTextRange) { // IE
                oR = textarea.createTextRange();

// Fixbug : ues moveToBookmark()
// In IE, if cursor position at the end of textarea, the set function don't work
                if (textarea.value.length === rangeData.start) {
//alert('hello')
                    oR.collapse(false);
                    oR.select();
                } else {
                    oR.moveToBookmark(rangeData.bookmark);
                    oR.select();
                }
            }
        },

        add: function (textarea, rangeData, text) { // 向光标位置添加内容
            var oValue, nValue, oR, sR, nStart, nEnd, st;
            this.set(textarea, rangeData);

            if (textarea.setSelectionRange) { // W3C
                oValue = textarea.value;
                nValue = oValue.substring(0, rangeData.start) + text + oValue.substring(rangeData.end);
                nStart = nEnd = rangeData.start + text.length;
                st = textarea.scrollTop;
                textarea.value = nValue;
// Fixbug:
// After textarea.values = nValue, scrollTop value to 0
                if (textarea.scrollTop != st) {
                    textarea.scrollTop = st;
                }
                textarea.setSelectionRange(nStart, nEnd);
            } else if (textarea.createTextRange) { // IE
                sR = document.selection.createRange();
                sR.text = text;
                sR.setEndPoint('StartToEnd', sR);
                sR.select();
            }
        }
    }

let

let 方法  声明一个变量,这个变量的值可以改变, 与 var 不同的是他不会提升作用域, 而且变量声明之前赋值,会报错。 局部作用域,作用域外面拿不到。

const

const 方法 声明一个变量, 这个变量的值不可以改变, 但如果这个变量是对象,对象的值是可以改变的,为了安全考虑。 其他与 let 相同。

箭头函数 =>

=> 箭头函数 用法与 function 相同  区别:箭头函数 this 指向不会改变,也就是绑定了上下文 this 的指向;

  
示例:
  var fun = (val,opend)=>{ return val + opend };
  var Fun = function(val,opend){ return val + opend };

Symbol

ES5的对象属性名都是字符串,这容易造成属性名的冲突,例如你用的他人提供的方法,但又想为这个对象添加新的方法(mixin模式)新方法的名字就有可能与现有方法产生冲突。如果有一种机制,保证每个属性的名字都是独一无二的就好了,这样就从根本上防止属性名的冲突。这就是ES6引入Symbol的原因。
ES6引入了一种新的原始数据类型Symbol,表示独一无二的值。它是JavaScript语言的第七种数据类型,前六种是:Undefined、Null、布尔值(Boolean)、字符串(String)、数值(Number)、对象(Object)。
Symbol值通过Symbol函数生成。这就是说,对象的属性名现在可以有两种类型,一种是原来就有的字符串,另一种就是新增的Symbol类型。凡是属性名属于Symbol类型,就都是独一无二的,可以保证不会与其他属性名产生冲突。

let s = Symbol();
typeof s
// "symbol"

上面代码中,变量s就是一个独一无二的值。typeof运算符的结果,表明变量s是Symbol数据类型,而不是字符串之类的其他类型。
注意,Symbol函数前不能使用new命令,否则会报错。这是因为生成的Symbol是一个原始类型的值,不是对象。也就是说,由于Symbol值不是对象,所以不能添加属性。基本上,它是一种类似于字符串的数据类型。
Symbol函数可以接受一个字符串作为参数,表示对Symbol实例的描述,主要是为了在控制台显示,或者转为字符串时,比较容易区分。

不用不知道,一用就感觉自己的会的东西太少了:

数组属性 作用 返回值
arr.push(item1,item2…) 可以向数组最后添加一个或多个元素 返回数组的新长度,改变原数组。
arr.pop() 从数组最后删除一个元素 返回删除掉的元素值,改变原数组
arr.unshift(item1,item2…) 可以向数组前面添加一个或多个元素 返回数组的新长度,改变原数组
arr.shift() 从数组前面删除一个元素 返回删除掉的元素值,改变原数组
arry.join("字符串") 使用字符串连接数组的每一个元素 返回一个新的字符串,不改变原数组
arry.concat(一个或多个数组) 方法用于连接两个或多个数组 返回一个新数组,不改变原数组
arry.slice(start,end) 可以拷贝数组中从索引start开始,到end前一位的元素值,以数组形式返回。不改变原数组。 返回一个新数组,不改变原数组
arry.toString() 可以将数组转换为字符串形式(不常用) 返回字符串,不改变原数组
arry.indexOf(参数1,参数2) 参数1表示要找哪个元素,返回找到的索引值。第二个参数是一个索引值,表示从哪个位置开始查找。
没有找到,返回-1
查找到参数:返回索引值,没有就返回 -1(IE9 以上可用)
arry.reverse() 该方法用于颠倒元素的顺序 改变原来的数组,不会创建新的数组