1.基本类型和引用类型的值基本类型值:简单的数据段;引用类型值:由多个值组成的对象;审查:基本数据类型:未定义;无效的;数字;布尔值;细绳;按值访问,可以操作存储在变量中的实际值;引用数据类型:如Array;不能直接访问值,它是一个存储在内存中的对象;不允许JavaScript直接访问内存中的位置;即不能直接操作对象的内存空间;当我们操作对象时,我们实际上操作的是对象的引用,而不是对象;注意:如果我们复制持有一个对象的变量,那么这两个变量将指向同一个对象。当我们向对象添加属性时,我们是在实际对象上进行操作;1.1动态属性引用类型varperson=newObject()//创建一个对象person.name='张三'//设置对象属性console.log(person.name)//输出对象属性这个属性会一直伴随对象,除非对象被销毁,否则该属性将一直存在;basictypevarname='Nick'name.age=20console.log(name.age)//undefined只有引用值可以动态添加属性,以后可以使用;1.2复制变量值基本类型vars='hello'vars1=sconsole.log(s1)//'hello'console.log(s1==s)//true解释:新建一个变量s1,其值与s,两个字符类型都是'hello',所以s1==s;两者完全独立,互不干扰;引用类型varobj1=newObject()varobj2=obj1obj1.name='nick'console.log(obj2.name)console.log(obj2==obj1)说明:我们的变量名obj1存储了一个对象的引用,它指向一个heap对象(object),通过复制,我们只是复制了一个变量obj2,它指向和obj1同一个对象,所以在设置obj1.name='nick'之后,后面修改指向的对象的属性,因为obj2也是指向这个对象,所以obj2.name='nick';1.3参数传递函数类似于我们变量的拷贝,来看看;1.3.1基础类类型参数functionaddnum(num){num+=10returnnum}varcount=20res=addnum(count)console.log(count)//20console.log(res)//30解释:参数是函数的局部变量,但是不会影响全局变量,所以计数还是20;1.3.2引用类型参数传递functiontest(obj){obj.age=20}varobj1=newObject()test(obj1)console.log(obj1.age)//20解释:这里obj和obj1指的是同一个对象;那么问题来了,对于引用类型,参数是传值还是传引用?看下面的例子:functiontest(obj){obj.age=20obj=newObject()obj.age=21}varobj1=newObject()test(obj1)console.log(obj1.age)//20如果是通过reference,obj1的指向应该成为函数内部创建的对象,而它的age值是21,但实际输出的是20,说明即使在函数内部修改了参数的值,它原来的引用并没有改变;在函数内部创建的obj将在函数调用结束时销毁;2.范围2.1执行环境和范围执行环境:定义变量或函数可以访问的其他数据,并决定它们的行为。全局执行环境是最外层的执行环境。根据ECMAScript实现的宿主环境,代表全局执行环境的对象可能不同。在浏览器中,全局执行环境就是我们常说的window对象。当执行环境中的代码被执行时,会创建一个变量对象的作用域链。这个作用域链决定了每个上下文级别中的代码访问变量和函数的顺序。代码执行的执行环境的变量对象总是在作用域链的前端。如果上下文是一个函数,它的激活对象被用作变量对象。活动对象最初只有一个定义的变量:arguments。(该变量不存在于全局执行环境中。)作用域链中的下一个变量对象来自包含执行环境,下一个对象来自下一个包含执行环境。依此类推,直到全局执行环境;全局执行环境的变量对象总是作用域链的最后一个变量对象。代码执行时的标识符解析是通过沿着作用域链逐步搜索标识符名称来完成的。搜索过程总是从作用域链的头部开始,一直向下直到找到标识符。(如果没有找到标识符,通常会报错。)canbeaccessedcolor,anotherColorandtempColor}swapColors();//这里可以访问color和anotherColor,不能访问tempColor}changeColor();//这里只能访问color以上代码涉及3个执行环境:全局执行环境,changeColor()和swapColors()的本地执行环境。在全局执行环境中有一个变量color和一个函数changeColor()。changeColor()的本地执行环境有一个变量anotherColor和一个函数swapColors(),但在这里它可以访问全局上下文中的变量color。其他功能也是如此;2.2扩展作用域链虽然执行环境主要包括全局环境和局部环境,但是还有其他方法可以扩展作用域链。某些语句会导致变量对象临时添加到作用域链的前端,代码执行后删除。这种现象通常出现在两种情况下,即当代码执行到以下任何一种情况时:catchblockoftry/catch语句;附声明;在这两种情况下,都会在作用域链的前端添加一个变量对象。对于with语句,会将指定的对象添加到作用域链的最前面;对于catch语句,将创建一个新的变量对象,其中将包含要抛出的错误对象的声明。如下所示:functionbuildUrl(){letqs="?debug=true";with(location){leturl=href+qs;}returnurl;}这里,with语句接收到location对象,所以location会被添加到范围链的前端。变量qs在buildUrl()函数中定义。with语句中的代码在引用变量href时,实际上引用的是location.href,即自身变量对象的属性。引用qs时,引用的是buildUrl()中定义的变量,位于函数环境的变量对象中;而在with语句内部,定义了一个url变量,所以url成为函数执行环境的一部分,可以作为函数的值返回;2.3没有块级作用域if(true){varcolor='red'}console.log(color)//red这里我们很疑惑,这个颜色不应该在{}是局部变量吗?为什么可以全局输出?解释:这里if语句声明的变量会被加入到当前执行环境(即全局环境)中,for语句也是如此;for(vari=0;i<5;i++){console.log('i')}console.log(i)//5声明变量用var声明的变量会自动添加到最近的环境中,在函数内部声明,最近的环境是函数的局部环境;with语句中,最接近的环境是函数环境;如果变量没有用var声明,会自动加入到全局环境中;functiontest(a,b){varsum=a+breturnsum}控制台。log(test(10,20))//30console.log(sum)//ReferenceError:sumisnotdefined原因这里就不过多解释了,但是如果我们在函数内部省略var,直接声明sum,那么在函数外部就可以了也可以输出sum,因为此时是全局变量;在JavaScript中,在不声明变量的情况下初始化变量是错误的做法;3.垃圾收集3.1垃圾收集机制JavaScript是一种使用垃圾收集的语言,也就是说执行环境负责在代码执行时管理内存。JavaScript通过自动内存管理实现内存分配和闲置资源回收。基本过程:判断一个变量不再使用,然后释放它占用的内存。这个过程是周期性的,即垃圾收集程序会每隔一定时间自动运行一次。垃圾回收过程是一个近似的、不完美的解决方案,因为某一块内存是否还有用是一个“不可判定”的问题,也就是算法无法解决。3.2性能问题垃圾收集程序会周期性运行。如果在内存中分配了很多变量,可能会造成性能损失,所以垃圾回收的时间调度非常重要。特别是在内存有限的移动设备上,垃圾回收会显着降低渲染和帧速率。现代垃圾收集器根据对JavaScript运行时环境的探测来决定何时运行。探测机制因引擎而异,但通常基于已分配对象的大小和数量。由于调度垃圾收集器的问题导致性能下降,其策略基于分配的数量,比如256个变量,4096个对象/数组文字和数组槽,或64KB字符串。只要满足其中一个条件,垃圾收集器就会运行。此实现的问题在于,分配那么多变量的脚本可能在其整个生命周期中需要那么多变量,从而导致垃圾收集器运行过于频繁。由于对性能的严重影响,IE7终于更新了垃圾收集器。在IE7发布后,JavaScript引擎的垃圾收集器被调整为动态更改变量、文字或数组槽将触发垃圾收集的阈值。IE7的起始阈值与IE6相同。如果垃圾收集器回收的分配内存少于15%,则这些变量、文字或数组槽的阈值会加倍。如果某一时刻85%的已分配内存被回收,则阈值将重置为默认值。如此简单的修改,大大提升了浏览器中严重依赖JavaScript的网页的性能。3.3管理内存为什么需要管理内存?在使用垃圾收集的编程环境中,JavaScript运行在一个特殊的内存管理和垃圾收集环境中。分配给浏览器的内存通常比分配给桌面软件少得多,分配给移动浏览器的内存更少。这是一个比其他任何事情都更重要的安全问题,以避免在运行大量javascript的网页耗尽系统内存时操作系统崩溃。这个内存限制不仅会影响变量分配,还会影响调用堆栈和一个线程中可以同时执行的语句数。联系引用可保持较小的内存占用量并提高页面性能。优化内存使用的最佳方法是确保在执行代码时只保存必要的数据。如果不再需要数据,则将其设置为null,从而释放其引用。局部变量超出作用域后自动解引用,如下:值globalPerson=null;在上面的代码中,变量globalPerson保存了createPerson()函数调用返回的值。在createPerson()内部,localPerson创建一个对象并向其添加名称属性。然后,localPerson作为函数值返回并分配给globalPerson。localPerson在createPerson()执行完成并退出执行环境后自动取消引用,不需要显式处理。但是globalPerson是一个全局变量,当不再需要它时应该手动解除引用,这是最后一行所做的。但是请注意,取消引用一个值不会自动导致关联的内存被回收。解引用的目的是确保关联值不再存在于执行环境中,因此将在下一次垃圾收集中回收。
