Stack(栈)是自动分配的内存空间,由系统自动释放;堆(heap)堆是动态分配的内存,它的大小是可变的。会自动释放。JavaScript中的变量分为原始类型和引用类型。基本类型(Undefined、Null、Boolean、Number和String)基本类型在内存中占用的空间很小,并且有固定的大小。它们的值存储在栈(stack)空间中,也就是通过值来访问引用类型(对象、数组、函数)。引用类型占用空间大,大小不固定。栈内存中存储的地址指向堆内存中的对象。它是按引用访问的,如下图所示:栈内存只存放对象的访问地址,并在堆内存中为这个值分配空间。由于这样的值是可变大小的,所以它们不能存储在栈内存中。但是内存地址的大小是固定的,所以内存地址可以保存在栈内存中。这样,在查询一个引用类型的变量时,先从栈中读取内存地址,然后通过地址找到堆中的值。当我们看到一个变量类型是已知的,就在栈上分配,比如INT,Double等。对于其他未知的类型,比如自定义类型,因为系统不知道需要多大,程序自己申请,所以分配在堆中。上面的例子表明,当我改变arr2中的数据时,arr1中的数据也随之改变,但是当我改变str1的数据值时,arr1并没有改变。为什么?这就是按值传递和按引用传递的区别。因为arr1是数组,属于引用类型,所以赋值给arr2时,传递的是栈中的地址(相当于新建了一个不同名字的“指针”),而不是堆中对象的值记忆。str1得到的是一个基本类型的赋值,因此,str1只是从arr1堆内存中取一个值,直接保存在栈上。arr1和arr2都指向同一个堆内存。当arr2修改堆内存时,也会影响到arr1。str1直接在栈上修改,不能影响arr1堆内存中的数据。数据类型访问&&复制基本数据类型:基本数据类型是指存储在栈内存中的简单数据段。访问方式为按值访问。vara=1;a=2;基本类型变量的复制:从一个变量复制到另一个变量时,会在栈上创建一个新值,然后将该值复制到为新变量分配的位置。变量b=a;vara=newObject();a.name='xz';stackmemory&heapmemory为了尽量减少程序运行时占用的内存,通常需要实现垃圾回收机制。当一个方法执行时,每个方法都会创建自己的内存栈,这个方法中定义的变量会一个一个的放入这个栈内存中。随着方法执行结束,这个方法的栈内存也会自然销毁。向上。因此,方法中定义的所有变量都放在栈内存中;当我们在程序中创建一个对象时,该对象会被保存在运行时数据区中,以供重复使用(因为对象创建成本开销较大),这个运行时数据区就是堆内存。堆内存中的对象不会随着方法的结束而销毁。即使在方法结束后,这个对象可能被另一个引用变量引用(这在传递方法参数时很常见),那么这个对象仍然不会被销毁。只有当一个对象没有任何引用变量来引用它时,系统的垃圾回收机制才会在验证时回收它。思考题:demo1.vara=1;变量b=a;b=2;//此时a是多少?当demo1中的变量对象中的数据被复制时,系统会自动为新的变量赋一个新的值。varb=a执行后,虽然b被重新赋值给了2,但实际上它们是独立的,相互独立的。demo2.varm={a:1,b:2}varn=m;n.a=2;//此时m.a的值是多少?在demo2中,我们使用varn=m来执行一个复制引用类型的操作。引用类型的副本也会自动为新变量赋一个新值,并存储在变量对象中,但不同的是,这个新值只是引用类型的一个地址指针。当地址指针相同时,虽然相互独立,但在变量对象中访问的具体对象其实是相同的。所以当我改变n时,m也会改变。这是引用类型的本质。栈和队列要了解JavaScript数组的栈和队列方法的操作,您需要了解栈和队列的基础知识。在继续看下面的内容之前,我们先简单了解一下栈和队列的概念。栈和队列都是动态集合。在栈中,可以移除的元素是最近插入的元素。栈道实现后进先出。在队列中,可以移除的元素总是在集合中存在时间最长的元素。队列执行先进先出策略。栈的基本概念先上图:栈是一个LIFO(后进先出)数据结构,即新加入的项先被移除。堆栈中项目的插入(称为压入)和移除(称为弹出)只发生在一个位置——堆栈的顶部。开始时,栈中没有任何数据,称为空栈。此时栈顶就是栈底。然后数据从栈顶进入,栈顶和栈底分离,整个栈的当前容量变大。当数据出栈时,从栈顶弹出,栈顶向下移动,整个栈的当前容量变小。比如我们在一个盒子里放了很多书,如果要取出第二本书,那么就必须先取出第一本书,才能取出第二本书;取出第二本书后,再次放入第一本书。ECMAScript专门为数组提供了push()和pop()方法,以实现类似的行为。push()方法可以接收任意数量的参数,将它们一个一个添加到数组的末尾,返回修改后的数组长度。pop()方法从数组末尾删除最后一项,减少数组的长度,并返回删除的项目。队列的基本概念栈数据结构的访问规则是LIFO(后进先出),而队列数据结构的访问规则是FIFO(先进先出,先进先出)。队列将项目添加到列表的末尾并从列表的前面删除项目。如下图所示:比如火车站排队买票,第一个买,第一个买,第一个走。入队操作实际上是在队尾添加一个元素,没有任何移动,时间复杂度为O(1)。而dequeue则不同,因为我们已经将标记为0的位置设置为队头,所以每次dequeue操作时,所有元素都必须向前移动。如下图所示:ECMAScript专门为数组提供了shift()和unshift()方法,以实现类似队列的行为。由于push()是将项目添加到数组末尾的方法,因此模拟队列只需要一种从数组前面获取项目的方法。执行此操作的数组方法是shift(),它删除数组中的第一项并返回该项,同时将数组的长度减1。顾名思义,unshift()与shift()相反:它将任意数量的数组项添加到数组的前面并返回新数组的长度。因此,通过同时使用unshift()和pop()方法,您可以从相反的方向模拟队列,将项目添加到数组的前面并从数组的末尾删除项目。push()方法该方法是向数组末尾添加一个或多个元素,并返回新的长度。push()方法可以接收任意数量的参数,将它们一个一个添加到数组的末尾,返回修改后的数组长度。如:vararr=[];//创建一个空数组console.log(arr);//[]console.log("push");//推送arr.push(1);//把1添加到数组arrconsole.log(arr);//[1]arr.push(2);//将2添加到数组arrconsole.log(arr);//[1,2]arr.push([3,4]);//添加数组[3,4]到arrconsole.log(arr);//[1,2,[3,4]]console.log(arr.length);//3Chrome浏览器控制台中的输出效果如下图:pop()方法pop()方法与push()方法正好相反。pop()方法删除数组的最后一个元素,将数组的长度减1,并返回删除元素的值。如果数组变为空,则该方法不会更改数组并返回undefine。下面代码演示:vararr=[1,2,3,4];//创建一个数组console.log(arr);//[1,2,3,4]console.log(arr.length);//4console.log("弹出,后进先出");//弹出,后进先出arr.pop();console.log(arr);////[1,2,3]arr.pop();控制台日志(arr);//[1,2]arr.pop();console.log(arr);//[1]arr.pop();console.log(arr);//[]Chrome浏览器控制台中的输出效果如下图所示:unshift()方法unshift()方法向数组的开头添加一个或多个元素,并返回新的长度。变量arr=[];//创建一个空数组console.log(arr);//[]console.log("排队");//入队arr.unshift(1,2,3,4);//将1,2,3,4压入数组arrconsole.log(arr);//[1,2,3,4]console.log(arr.length);//4在Chrome浏览器控件中平台输出的效果如下图所示:shift()方法shift()方法和unshift()方法刚好相反。该方法用于删除数组的第一个元素,并返回删除后的值。如果数组为空,则shift()方法将不执行任何操作并返回undefined值。vararr=[1,2,3,4];//创建一个数组console.log(arr);//[1,2,3,4]arr.shift();//获取第一项控制台。日志(arr);//[2,3,4]arr.shift();//获取第一项console.log(arr);//[3,4]arr.shift();//获取第一项console.log(arr);//[4]arr.shift();//获取第一项console.log(arr);//[]在Chrome浏览器控制台输出的效果如下图所示:简单回忆一下:push()方法可以在数组末尾添加一个或多个元素,shift()方法可以删除第一个数组中的元素,unshift()方法可以在数组的前端添加一个或多个元素pop()方法删除数组中的最后一个元素。JavaScript实现类似于堆栈和队列的行为。了解了这些方法后,我们可以将它们结合起来,轻松实现类似于栈和队列的行为。实现类栈行为结合push()和pop(),我们可以实现类栈行为://创建一个数组来模拟栈vara=newArray();console.log(a);//push:在数组末尾添加一个或多个元素并返回新的长度console.log("push");a.push(1)console.log(a);//------>1a.push(2);console.log(a);//----->1,2a.push(3);console.log(a);//----->1,2,3a.push(4);console.log(a);//----->1,2,3,4console.log("出栈,后进先出");console.log(a);//pop:从数组中删除最后一个元素并返回该元素的值a.pop();//----->4console.log(a);a.pop();//----->3console.log(a);a.pop();//----->2console.log(a);a.pop();//----->1console.log(a);Chrome浏览器控制台中的输出效果如下图所示:实现类似队列的行为,结合shift()和push()方法,就可以像队列一样使用数组。即在数组后端添加项,在数组前端移除项://创建一个数组模拟队列vara=newArray();console.log(a);//push:在数组末尾添加一个或多个元素,并返回新的长度console.log("enqueue");a.push(1)console.log(a);//------>1a.push(2);控制台。日志(a);//----->1,2a.push(3);console.log(a);//----->1,2,3a.push(4);控制台。log(a);//----->1,2,3,4console.log("出队列,先进先出");console.log(a);//shift:从数组中shift第一个删除一个元素,并返回这个元素的值a.shift();//------>1console.log(a);a.shift();//------>2console.log(a);a.shift();//------>3console.log(a);a.shift();//----->4console.log(a);在Chrome浏览器控制台输出的效果如下图所示:另外,还可以同时使用unshift()和pop()方法从反方向模拟队列,即添加项到数组的前端并从数组的后端删除项目。如下例所示://创建一个数组模拟一个队列vara=newArray();console.log(a);//unshift:在数组最前面添加一个或多个元素,并返回新的长度console.log("enqueue");a.unshift(1)console.log(a);//------->1a.unshift(2);console.log(a);//----->2,1a.unshift(3);console.log(a);//------>3,2,1a.unshift(4);console.log(a);//----->4,3,2,1console.log("出队列,先进先出");console.log(a);//pop:从数组中删除最后一个元素,并返回该元素a的值.pop();//----->4console.log(a);a.pop();//----->3console.log(a);a.pop();//----->2console.log(a);a.pop();//----->1console.log(a);Chrome浏览器控制台输出效果如下图:push()方法和unshift()方法性能测试Array的push()和unshift()方法可以添加元素到当前数组,不同的是push()是加在末尾,而unshift()是加在开头,从原理上可以知道unshift()的效率较低。原因是每次添加一个元素时,都会将现有元素向下移动一个位置。但是效率上的差别有多大呢?下面我们做一个简单的测试。/*“vars=+newDate();”的技术说明代码中解释如下:=+这个运算符不存在;+等同于.valueOf();+newDate()等同于newDate().valueOf()//4结果返回当前时间的毫秒数alert(+newDate());警报(+新日期);vars=新日期();警报(s.valueOf());警报(s.getTime());*/vararr=[];varstartTime=+newDate();//+newDate()等同于newDate().valueOf(),返回当前时间的毫秒数//推送性能测试for(vari=0;i<100000;i++){ arr.推(我);}varendTime=+newDate();console.log("调用push方法向数组添加100000个元素耗时"+(endTime-startTime)+"毫秒");开始时间=+新日期();arr=[];//unshift性能测试(vari=0;i<100000;i++){ arr.unshift(i);}endTime=+newDate();console.log("调用unshift方法向数组添加10万个元素需要时间"+(endTime-startTime)+"毫秒");这段代码分别执行了10万次push()和unshift()操作,在chrome浏览器中运行一次。结果如下图所示:可以看出unshift()比push()慢了将近100倍!因此,通常要谨慎使用unshift(),尤其是对于大型数组。如果一定要实现unshift()的效果,可以使用Array的reverse()方法,可以反转一个数组。先用push()将要放入数组的元素添加进去,然后再次执行reverse(),达到unshift()的效果。例如://创建一个数组来模拟一个栈vara=newArray();//使用push方法在数组末尾添加元素a.push(1)a.push(2);a.push(3);a.push(4);console.log("数组前数组元素顺序倒序");console.log(a);//---->1,2,3,4//数组有一个叫reverse的方法可以反转一个数组。先用push将要放入的元素添加到数组中,然后进行reverse,达到unshift的效果a.reverse();//使用reverse的方法对数组进行reverseconsole.log("在array之后的arrayisreversedTheorderofelements");console.log(a);chrome浏览器控制台输出效果如下图所示:从运行结果来看,数组元素的顺序已经颠倒了。reverse()方法的性能测试;vararr=[],s=+新日期;对于(vari=0;i<100000;i++){ arr.push(i);}//调用reverse方法将数组中的100000个元素倒序arr.reverse();console.log("调用reverse方法将数组中100000个元素的顺序颠倒需要时间:"+(+newDate-s)+"milliseconds");chrome浏览器控制台输出的效果如下图所示:从运行效果可以看出reverse()方法性能极高,可以放心使用。小结本文主要介绍JavaScript数组的push()、pop()、shift()和unshift()方法。以及如何通过组合这些方法来实现类似栈和队列的行为。1.删??除js中的栈:js中的splice方法。splice(index,len,[item])注意:这个方法会改变原来的数组。splice有3个参数,也可以用来替换/删除/添加数组中的一个或几个值。index:数组的起始下标len:替换/删除的长度item:替换的值,如果操作为删除,则item为空。如:arr=['a','b','c','d']delete----item不设置arr.splice(1,1)//['a','c','d']删除一个起始下标为1的值,长度为1,len设置为1,如果为0则数组不变arr.splice(1,2)//['a','d']delete起始下标为1,一个长度为2的值,替换为len设置的2----item为替换值arr.splice(1,1,'ttt')//['a','ttt','c','d']替换1的起始下标,长度为1的值为'ttt',1arr.splice(1,2,'ttt')//['a','ttt','d']替换起始下标为1,长度为2的两个值'ttt',len置1并相加----len置0,item为相加值arr.splice(1,0,'ttt')//['a','ttt','b','c','d']的意思是在下标1处加一个'ttt'项貌似最方便拼接2:deletedelete删除数组中的元素后,下标的值会被置为undefined,数组的长度不会改变。例如:deletearr[1]//['a',,'c','d']中间出现两个逗号,数组长度不变,有一项未定义
