本文记录了我在学习前端上的笔记,方便之后的复习和巩固。javascript
ECMAScript
变量可能包含两种不一样数据类型的值:基本类型值和引用类型值。基本类型指的是简单的数据段,而引用类型值指那些可能由多个值构成的对象。前端
基本类型值:Undefined、Null、Boolean、Number、String;java
引用类型值,也就是对象类型:Object、Array、Function、Date等;浏览器
声明变量时不一样的内存分配闭包
基本类型值:存储在栈(stack)
中的简单数据段,它们的值直接存储在变量访问的位置。这是由于这些基本类型占据的空间是固定的,因此能够将它们存储在较小的内存区域 - 栈
中。这样存储更便于迅速查寻变量的值。函数
引用值:存储在堆(heap)
中的对象,也就是说,存储在变量处的值是一个指针(point)
,指向存储对象的内存地址。这是由于:引用值
的大小会改变,因此不能把它放在栈
中,不然会下降变量查寻的速度。相反,放在变量的栈空间中的值是该对象存储在堆
中的地址。地址的大小是固定的,因此把它存储在栈
中对变量性能无任何负面影响。性能
不一样的内存分配机制也带来了不一样的访问机制学习
在javascript
中是不容许直接访问保存在堆内存
中的对象的,也就是说不能直接操做对象的内存空间。因此在访问一个对象时,首先获得的是这个对象在堆内存中的地址
,而后再按照这个地址
去得到这个对象中的值,这就是传说中的按引用访问
。而原始类型的值则是能够直接访问到的。优化
注意:当复制保存着对象的某个变量时,操做的事对象的引用。但在为对象添加属性时,操做的是实际的对象spa
基础类型值:在将一个保存着基础类型值的变量复制给另外一个变量时,会将原始值的副本赋值给新变量,此后这两个变量是彻底独立的,他们只是拥有相同的value而已。
function addTen(num) { num += 10; return num; } var count = 20; var result = addTen(count); console.log(count); //20 没有变化 console.log(result); //30
引用值:在将一个保存着对象内存地址的变量复制给另外一个变量时,会把这个内存地址赋值给新变量,也就是说这两个变量都指向了堆内存中的同一个对象,他们中任何一个做出的改变都会反映在另外一个身上。(这里要理解的一点就是,复制对象时并不会在堆内存中新生成一个如出一辙的对象,只是多了一个保存指向这个对象指针的变量罢了)
function setName(obj) { obj.name = "Nicholas"; } var person = new Object(); setName(person); console.log(person.name); //"Nicholas"
首先咱们应该明确一点:ECMAScript中全部函数的参数都是按值来传递的
。可是为何涉及到基础类型与引用类型的值时仍然有区别呢,还不就是由于内存分配时的差异。
基础类型值:只是把变量里的值传递给参数,以后参数和这个变量互不影响。
引用类型值:对象变量它里面的值是这个对象在堆内存中的内存地址,这一点你要时刻铭记在心!所以它传递的值也就是这个内存地址,这也就是为何函数内部对这个参数的修改会体如今外部的缘由了,由于它们都指向同一个对象呀。或许我这么说了之后你对书上的例子仍是有点不太理解,那么请看图吧:
因此,若是是按引用传递的话,是把第二格中的内容(也就是变量自己)整个传递进去(就不会有第四格的存在了)。但事实是变量把它里面的值传递(复制)给了参数,让这个参数也指向原对象。所以若是在函数内部给这个参数赋值另外一个对象时,这个参数就会更改它的值为新对象的内存地址指向新的对象,但此时原来的变量仍然指向原来的对象,这时候他们是相互独立的;但若是这个参数是改变对象内部的属性的话,这个改变会体如今外部,由于他们共同指向的这个对象被修改了呀!来看下面这个例子吧:(传说中的call by sharing)
var obj1 = { value:'111' }; var obj2 = { value:'222' }; function changeStuff(obj){ obj.value = '333'; obj = obj2; return obj.value; } var foo = changeStuff(obj1); console.log(foo);// '222' 参数obj指向了新的对象obj2 console.log(obj1.value);//'333'
obj1
仍然指向原来的对象,之因此value
改变了,
是由于changeStuff
里的第一条语句,这个时候obj
是指向obj1
的 .
再啰嗦一句,若是是按引用传递的话,这个时候obj1.value
应该是等于'222'
的
能够把ECMAScript函数的参数想象成局部变量
若是变量的值是一个对象
或null
,则typeof
操做符会返回"object"
.
一般咱们并非想知道某个值是对象,而是想知道它是什么类型的对象。为此,ECMAScript
提供了instanceof
操做符;
若是对象是给定引用类型的实例,那么instanceof
操做符就会返回true
。
console.log(person instanceof Object); //变量person是Object吗? console.log(colors instanceof Array); //变量colors是Array吗? console.log(pattern instanceof RegExp); //变量pattern是RegExp吗?
根据规定,全部引用类型的值都是
Object
的实例。在检查一个引用类型值和Object
构造函数时,instanceof
操做符始终会返回true
。
每一个函数
都有本身的执行环境
。当执行流进入一个函数时,函数的环境就会被推入一个环境栈
中。而在函数执行后,栈
将其环境弹出,把控制权返回给以前的执行环境。
每一个环境
都有一个与之关联的变量对象
,环境中定义的全部变量和函数都保存在这个对象中。虽然咱们编写的代码没法访问这个对象,但解析器在处理数据时会在后台使用它
当代码在一个环境执行时,会建立变量对象的一个做用域链
。做用域链的用途,是保证对执行环境
有权访问的全部变量和函数的有序访问
有些语句能够在做用域链的前端临时增长一个变量对象,该变量对象会在代码执行后被移除。有两种状况下会发生这种现象。
try-catch 语句中的 catch 块
with 语句
对 with 来讲,将会指定对象添加到做用域链中。对 catch 来讲,会建立一个新的变量对象,其中包含的是被抛出的错误对象的声明。
var oMyself = { sFirstname: "Aidan", sLastName: "Dai" } function create(){ var sLastName = "Wen" with(oMyself){ //将oMyself做为本身的执行环境 sAllName = sFirstname +" " + sLastName; } return sAllName; } var sMyName = create(); console.log(sMyName); //Aidan Dai
对于有块级做用域的语言来讲,for语句
初始化变量的表达式所定义的变量,只会存在于循环的环境之中。而对于JavaScript来讲,由for语句建立的变量i
即便在for循环执行结束后,也依旧会存在于循环外部的执行环境中。
1. 声明变量
使用var声明
的变量会自动被添加到最接近的环境
中。在函数内部,最接近环境的就是函数的局部环境;在with
语句中,最接近的环境就是函数环境。若是初始化变量时没有使用var声明
,该变量会自动被添加到全局环境。
注意:在编写
JavaScript
中,不声明而直接初始化变量时一个错误的作法,由于这样可能会致使意外。在严格模式下,初始化未经声明的变量会致使错误。
2.查询标识符
搜索过程从做用域链的前端开始,向上逐级查询与给定名字匹配的标识符。若是在局部环境找到,搜索过程中止,变量就绪。若是在局部环境中没有找到该变量名,则继续沿做用域向上搜索。搜索过程将一直追溯到全局环境的变量对象。在全局环境也没找到的话则说明该变量还没有声明。
var color = "blue"; function getColor() { return color; } console.log(getColor()); //"blue";
JavaScript具备自动垃圾收集机制,也就是说,执行环境会负责管理代码执行过程当中使用的内存。
JavaScript垃圾回收的机制很简单:找出再也不使用的变量,而后释放掉其占用的内存,可是这个过程不是时时的,由于其开销比较大,因此垃圾回收器会按照固定的时间间隔周期性的执行。
变量生命周期
什么叫再也不使用的变量?再也不使用的变量也就是生命周期结束的变量,固然只多是局部变量,全局变量的生命周期直至浏览器卸载页面才会结束。局部变量只在函数的执行过程当中存在,而在这个过程当中会为局部变量在栈或堆上分配相应的空间,以存储它们的值,而后再函数中使用这些变量,直至函数结束(闭包中因为内部函数的缘由,外部函数并不能算是结束
一旦函数结束,局部变量就没有存在必要了,能够释放它们占用的内存。貌似很简单的工做,为何会有很大开销呢?这仅仅是垃圾回收的冰山一角,就像刚刚提到的闭包,貌似函数结束了,其实尚未,垃圾回收器必须知道哪一个变量有用,哪一个变量没用,对于再也不有用的变量打上标记,以备未来回收。用于标记无用的策略有不少,常见的有两种方式
这是JavaScript
最多见的垃圾回收方式,当变量进入执行环境的时候,好比函数中声明一个变量,垃圾回收器将其标记为“进入环境”
,当变量离开环境的时候(函数执行结束)将其标记为“离开环境”
。至于怎么标记有不少种方式,好比特殊位的反转、维护一个列表等,这些并不重要,重要的是使用什么策略,原则上讲不可以释放进入环境
的变量所占的内存,它们随时可能会被调用的到。
垃圾回收器会在运行的时候给存储在内存中的全部变量加上标记
,而后去掉环境中的变量以及被环境中变量所引用的变量(闭包),在这些完成以后仍存在标记
的就是要删除的变量了,由于环境中的变量已经没法访问到这些变量了,最后,垃圾收集器完成内存清除工做,销毁那些带标记
的值并回收它们所占用的内存空间。
在低版本IE中常常会出现内存泄露,不少时候就是由于其采用引用计数方式进行垃圾回收。引用计数的策略是跟踪记录每一个值被使用的次数,当声明了一个变量并将一个引用类型赋值给该变量的时候这个值的引用次数就加1,若是该变量的值变成了另一个,则这个值得引用次数减1,当这个值的引用次数变为0的时候,说明没有变量在使用,这个值无法被访问了,所以能够将其占用的空间回收,这样垃圾回收器会在运行的时候清理掉引用次数为0的值占用的空间。
垃圾收集器是周期性运行的,并且若是为变量分配的内存数量很可观,那么回收工做量也是至关大的。在这种状况下,肯定垃圾收集的时间间隔是一个很是重要的问题。
事实上,在有的浏览器中能够触发垃圾收集过程,但咱们不建议这样作。在IE中调用window.CollectGarbage()方法会当即执行垃圾收集。在Opera7及更高版本中,调用window.opera.collect()也会启动垃圾收集例程。
确保占用最少的内存可让页面得到更好的性能。而优化内存占用的最佳方式,就是为执行中的代码只保存必要数据。一旦数据再也不可用,最好经过将其值设置为null来释放其引用——这个方法叫作解除引用(dereferencing)。这一作法适用于大多数全局变量和全局对象属性。局部变量会在它们离开执行环境时自动被解除引用。
function createPerson(name) { var localPerson = new Object(); localPerson.name = name; return localPerson; } var globalPerson = createPerson("Nicholas"); //手工解除globalPerson的引用 globalPerson = null;
基本类型值和引用类型值具备如下特色:
基本类型值在内存中占据固定大小的空间,所以被保存在栈内存中;
从一个变量向另外一个变量复制基本类型的值,会建立这个值得一个副本;
引用类型的值是对象,保存在堆内存中;
包含引用类型值得变量实际上包含的并非对象自己,而是指向该对象的指针;
从一个变量向另外一个变量复制引用类型的值,复制的实际上是指针,所以两个变量最终都指向同一个对象;
肯定一个值是哪一种基本类型可使用typeof操做符,而肯定一个值是哪一种引用类型可使用instanceof操做符。
全部变量(包括基本类型和引用类型)都存在于一个执行环境(也称为做用域)当中,这个执行环境决定了变量的生命周期,以及哪一部分代码能够访问其中的变量。
最后,若有错误和疑惑请指出,多谢各位大哥