今天学习了javascript 的变量和做用域的基本知识,对于之前在开发中遇到的一些不懂的小问题也有了系统的认识,收获仍是比较多的。javascript
【基本类型和引用类型】前端
ECMAScript 变量可能包含两种不一样数据类型的值:基本类型值和引用类型值。基本类型值指的是简单的数据段,而引用类型值指那些可能由多个值构成的对象。咱们常见的五种基本类型的值:Undefined、Null、Boolean、Number 和 String ,这五种基本数据类型是按值访问的,所以能够操做保存在变量中的实际的值。引用类型的值是保存在内存中的对象,也就是说不可以直接操做对象的内存空间,引用类型的值是按引用访问的。注意:咱们不能给基本类型的值添加属性,例如如下代码:java
var name = 'name1'; name.age = 22; console.log(name.age); // undefined
【复制变量值】浏览器
从一个变量向另外一个变量复制基本类型值很引用类型值时存在不一样的状况,若是从一个变量向另外一个变量复制基本类型的值,会在变量对象上建立一个新值,而后把该值复制到为新变量分配的位置上,例如:闭包
var num1 = 5; var num2 = num1;
经过以上的复制方式,num1 中的 5 和 num2 中的 5 是彻底独立的,也就是说修改 num1 或者 num2 是不会影响到另一个值的,咱们参考以下的代码:函数
var num1 = 5; var num2 = num1; console.log(num1,num2); // 5 5 num1 = 6; console.log(num1,num2); // 6 5
下面的表格形象的展现的复制基本类型值的一个过程:学习
复制前的变量对象 | 复制后的变量对象 | ||
num2 | 5spa (Number类型)指针 |
||
num1 | 5code (Number类型) |
num1 |
5 (Number类型) |
当从一个变量向另外一个变量复制引用类型的值的时候,一样也会将存储在变量对象中的值复制一份放到为新变量分配的空间中。可是这个值的副本其实是一个指针,而这个指针指向存储在堆中的一个对象。复制操做结束后,两个变量引用的实际上是一个值。所以,改变任意一个变量都会影响到另一个变量。例如如下代码:
var per1 = new Object(); var per2 = per1; per1.name = 'name1'; console.log(per1.name,per2.name); // name1 name1 per1.name = 'name2'; console.log(per1.name,per2.name); // name2 name2 per2.name = 'name3'; console.log(per1.name,per2.name); // name3 name3
下图详细的展现了保存在变量对象中和保存在堆中的对象之间的关系:
【传递参数】
ECMAScript 中全部函数的参数都是按值传递的,也就是说,把函数外部的值复制给函数内部的参数,就和把值从一个变量复制到另一个变量同样。基本类型值的传递如同基本类型变量的复制同样,引用类型的传递如同引用类型变量的复制同样。例如如下代码:
function addNum(num){ num += 10; return num; } var count = 20; var result = addNum(count); console.log(count,result); // 20 30
这里的函数 addNum() 有一个参数 num ,而参数其实是函数的局部变量。在调用这个函数时,变量 count 做为参数被传递给函数,这个变量的值是20。因而,数值20被复制给参数 num 。可是 num 的改变并不能影响 count 的值,因此 count 输出的值仍然是20 。再举一个例子:
function setName(obj){ obj.name = 'name5'; } var newObj = new Object(); setName(newObj); console.log(newObj.name); // name5
这段代码看起来是在局部做用域中修改了 newObj 的 name 的值,在全局做用域也反映出来了,这样的理解是错误的。再看一段代码:
function setName(obj){ obj.name = 'name6'; var obj = new Object(); obj.name = 'name7'; } var newObj = new Object(); setName(newObj); console.log(newObj.name); // name6
对比两段代码能够看出,若是 newObj 是按引用传递的,那么 newObj 的 name 属性应该是 name7 才对,可是 name 属性是 name6,这说明及时在函数内部修改了参数的值,但原始的引用仍然保持未变。
【执行环境和做用域】
执行环境定义了变量或函数有权访问的其余数据,决定了他们各自的行为。全局执行环境是最外围的一个执行环境,在Web浏览器中,全局执行环境被认为是 window 对象,所以全部的全局变量和函数都是做为 window 对象的属性和方法建立的。每一个函数都有本身的执行环境。当代码在一个环境中执行时,会建立变量对象的一个做用域链。做用域链的用途,是保证对执行环境有权访问的全部变量和函数的有序访问。做用域链的前端,时钟都是当前执行的代码所在环境的变量对象。若是这个环境是函数,则将其活动对象做为变量对象。做用域链中的下一个变量对象来自包含(外部)环境,再下一个变量对象则来自下一个包含环境。全局执行环境的变量对象始终都是做用域链的最后一个对象。标识符解析是沿着做用域链一级一级的搜索标识符的过程。请看以下代码:
var color = 'blue'; function changeColor(){ if(color == 'blue'){ color = 'red'; } else{ color = 'blue'; } } changeColor(); console.log(color); // red
函数 changeColor() 的做用域包含两个对象:它本身的变量对象(其中定义着 arguments 对象)和全局环境的变量对象。当 changeColor 在执行的时候,在本身的做用域中并无找到 color ,因而便到全局环境中找,找到了 color 的值为 blue ,而后按照 changeColor() 函数的规则将 color 的值设置为 red 。再看一段更加详细的代码:
//这里只能访问 color var color = 'blue'; function changeColor(){ //这里能够访问 color 、newColor ,可是不能访问 temColor var newColor = 'red'; function swapColor(){ //这里能够访问 color 、newColor 和 temColor var temColor = newColor; newColor = color; color = temColor; } swapColor(); console.log(color,newColor); //red blue } function showColor(){ console.log(color); //blue } showColor(); changeColor();
代码的内容本身体会一下,这里不作详细的解释。
【没有块级做用域】
看以下的代码:
for(var i = 0;i < 10;i ++){ i += 1; } console.log(i); // 10
对于有块级做用域的语言来讲, for 语句初始化变量的表达式所定义的变量,只会存在于循环的环境之中。在 javascript 中, i 并会在 for 循环执行结束后被销毁,反而被添加到了当前的执行环境(全局环境)中。
1.声明变量
使用 var 声明的变量会自动被添加到最接近的环境中。在函数内部,最接近的环境就是函数的局部环境。请看以下代码:
function addNum(num1,num2){ var num = num1 + num2; return num; } var result = addNum(10,20); console.log(result); // 30 console.log(num); // num is not defined
以上代码若是不使用 var 声明 num 的话是不会致使错误的,例如:
function addNum(num1,num2){ num = num1 + num2; return num; } var result = addNum(10,20); console.log(result); // 30 console.log(num); // 30
2.查询标识符
当在某个环境中为了读取或写入而引用一个标识符时,必须经过搜索来肯定该标识符实际表明什么。搜索过程从做用域链的前端开始,向上逐级查询与给定名字匹配的标识符。若是在局部环境中找到该标识符,搜索过程中止,变量就绪。若是在局部变量中没有找到该变量名,则继续沿做用域链向上搜索。搜索过程会一致追溯到全局环境变量。若是在全局环境变量也没有找到该标识符,说明该变量还没有定义。请查看如下代码:
var color = 'blue'; function getColor(){ return color; } console.log(getColor()); // blue
getaColor() 在搜索局部变量的时候没有找到 color ,而函数执行语句是必定要返回一个 color ,与是便到全局环境变量中去搜索,找到了 color 。须要注意的是,搜索的过程当中若是存在一个局部变量的定义,则搜索会自动中止,再也不进入另外一个变量对象。也就是说,若是局部环境存在着同名标识符,就不会使用位于父环境的标识符,例如如下代码:
var color = 'blue'; function getColor(){ var color = 'red'; return color; } console.log(getColor()); // red
做用域链对于理解闭包的概念相当重要,还望可以加深理解。