ECMAScript变量包含两种不一样数据类型的值:基本类型值和引用类型值。基本类型值是简单的数据段,而引用类型值指那些可能由多个值构成的对象。javascript
在将一个值赋给变量时,解析器必须肯定这个值是基本类型仍是引用类型。基本类型包括如Undefined、Null、Boolean、Number和String,这5种基本类型数据类型是按值访问的,所以能够操做保存在变量中的实际的值;引用类型类型的值是保存在内存中的对象。前端
与其余语言不一样,JavaScript不容许直接访问内存中的位置,也就是说不能直接操做对象的内存空间。在操做对象时,实际上实在操做对象的引用而不是实际的对象,所以,引用类型的值是按引用访问的。java
1.1 动态的属性
定义基本类型和定义引用类型的方法是类似的。可是对不一样类型值的操做倒是大相近庭。对于引用类型的值,咱们能够为其添加属性和方法,也能够改变和删除其属性和方法,以下:浏览器
var person = new Object(); person.name = "张三"; alert(person.name); //"张三"
但不能给基本类型的值添加属性。以下:函数
若是从一个变量向另外一个变量复制基本类型的值,会在变量对象上建立一个新值,而后将该值复制到为新变量分配的位置上。工具
var num1 = 5; var num2 = num1; //5
num1与num2中的5是彻底独立的,该值只是num1中5的一个副本。此后,这两个变量能够参与任何操做而不会相互影响。以下图所示:spa
而当一个变量向另外一个变量复制引用类型的值时,一样也会将存储在变量对象中的值复制一份放到为新变量分配的内存空间里。不一样的是,这个值其实是一个指针,而这个指针指向存储在堆中的一个对象。复制结束后,两个变量实际上将引用同一个对象。所以,改变其中一个变量,就会影响到另外一个变量,以下所示:设计
var obj1 = new Object(); var obj2 = obj1; obj1.name = "张三"; alert(obj2.name); //"张三"
1.3 传递参数指针
ESMAScript中全部函数的参数都是按值传递的。也就是说,把函数外部的值复制给函数内部的参数,就是把值从一个变量复制到另外一个变量同样。基本类型值的传递如同基本类型变量的复制同样,而引用类型值的传递,则如同引用类型变量的复制同样。有很多开发者在这一点上可能感到困惑,由于访问变量有按值和按引用两种方式,而参数只能按值传递。code
在向参数传递基本类型的值时,被传递的值会被复制给一个局部变量(即命名参数)。在向参数传递引用类型的值时,会把这个值在内存中的地址复制给一个局部变量,所以这个局部变量的变化会反映在函数的外部。以下代码所示
function addTen(num) { num += 10; return num; } var count = 20; var result = addTen(count); alert(count); // 20,没有变化 alert(result); // 30
这里的函数addTen()有一个参数num,而参数其实是函数的局部变量。在调用这个函数时,变量count做为参数被传递给函数,这个变量的值是20。因而,数值20被复制给参数num以便在addTen()中使用。在函数内部,参数num的值被加上了10,但这一变化不会影响外部的count变量。参数num和变量count互不认识,它们仅仅具备相同的值。假如num是按引用传递的话,那么变量count的值也将变成30,从而反映函数内部的变化。固然,使用数值等基本类型值来讲明按值传递比较简单,但若是使用对象,那问题就不怎么好理解了。再举一个例子:
function setName(obj) { obj.name = "张三"; } var person = new Object(); setName(person); alert(person.name); //"张三"
以上代码中建立一个对象,并将其保存在了变量person中。而后这个对象被传递到setname()函数中以后就被复制给了obj。在这个函数内部,obj和person引用的是同一个对象。换句话说,即便这个对象是按值传递的,obj也会按引用来访问同一个对象。因而,当在函数内部为obj添加name属性后,函数外部的person也会有所反映,由于person指向的对象在堆内存中只有一个,并且是全局对象。不少开发者错误的认为:在局部做用域中修改的对象会在全局做用域中反映出来,就说明参数是按引用传递的。为了证实对象是按值传递的,咱们在看看下面这个进过修改的例子:
function setName(obj) { obj.name = "张三"; obj = new Object(); obj.name = "李四"; } var person = new Object(); setName(person); alert(person.name); //"张三"
上面这个例子能够看出,如过person是按引用传递的,那么person就会自动被修改成指向其name属性值为"李四"的新对象。可是,当接下来再访问person.name时,显示的仍然是"张三"。这说明即便在函数内部修改了参数的值,但原始的引用仍然保存不变。实际上,当在函数内部重写obj时,这个变量引用的就是一个局部对象了。而这个局部对象会在函数执行完毕时当即销毁。
能够将ECMAScript函数的参数想象成局部变量。
1.4 检测类型
typeof操做符是肯定一个变量是字符串、数值、布尔值,仍是undefined的最佳工具。若是变量的值是一个对象或null,则typeof操做符会返回“object”。
虽然在检测基本数据类型时typeof是一个得力助手,可是在检测引用类型时,这个操做符用处却不大。一般,咱们并不想知道某个值是对象,而是想知道他是什么类型的对象。为此ECMAScript提供了instanceof操做符,其语法以下:
result = varible instanceof constructor
若是变量是给定引用类型的实例,那么instanceof操做符会返回true.
执行环境(execution context,为简单起见,有时也称为“环境”)是 JavaScript 中最为重要的一个概念。执行环境定义了变量或函数有权访问的其余数据,决定了它们各自的行为。每一个执行环境都有一个与之关联的变量对象(variable object),环境中定义的全部变量和函数都保存在这个对象中。虽然咱们编写的代码没法访问这个对象,但解析器在处理数据时会在后台使用它。
全局执行环境是最外围的一个执行环境。根据 ECMAScript 实现所在的宿主环境不一样,表示执行环,因境的对象也不同。在 Web 浏览器中,全局执行环境被认为是 window 对象,所以全部全局变量和函数都是做为 window 对象的属性和方法建立的。某个执行环境中的全部代码执行完毕后,该环境被销毁,保存在其中的全部变量和函数定义也随之销毁(全局执行环境直到应用程序退出——例如关闭网页或浏览器——时才会被销毁)。
每一个函数都有本身的执行环境。当执行流进入一个函数时,函数的环境就会被推入一个环境栈中。而在函数执行以后,栈将其环境弹出,把控制权返回给以前的执行环境。ECMAScript 程序中的执行流正是由这个方便的机制控制着。
当代码在一个环境中执行时,会建立变量对象的一个做用域链(scope chain)。做用域链的用途,是保证对执行环境有权访问的全部变量和函数的有序访问。做用域链的前端,始终都是当前执行的代码所在环境的变量对象。若是这个环境是函数,则将其活动对象(activation object)做为变量对象。活动对象在最开始时只包含一个变量,即 arguments 对象(这个对象在全局环境中是不存在的)。做用域链中的下一个变量对象来自包含(外部)环境,而再下一个变量对象则来自下一个包含环境。这样,一直延续到全局执行环境;全局执行环境的变量对象始终都是做用域链中的最后一个对象。
标识符解析是沿着做用域链一级一级地搜索标识符的过程。搜索过程始终从做用域链的前端开始, 而后逐级地向后回溯,直至找到标识符为止(若是找不到标识符,一般会致使错误发生)。在局部做用域中定义的变量能够在局部环境中与全局变量互换使用,以下面这个例子所示:
var color = "blue"; function changeColor(){ var anotherColor = "red"; function swapColors(){ var tempColor = anotherColor; anotherColor = color; color = tempColor; // 这里能够访问 color、anotherColor 和 tempColor } // 这里能够访问 color 和 anotherColor,但不能访问 tempColor swapColors(); } // 这里只能访问 color changeColor();
以上代码共涉及 3 个执行环境:全局环境、 changeColor() 的局部环境和 swapColors() 的局部环境。全局环境中有一个变量 color 和一个函数 changeColor() 。 changeColor() 的局部环境中有一个名为 anotherColor 的变量和一个名为 swapColors() 的函数,但它也能够访问全局环境中的变量 color 。 swapColors() 的局部环境中有一个变量 tempColor ,该变量只能在这个环境中访问到。不管全局环境仍是 changeColor() 的局部环境都无权访问 tempColor 。然而,在 swapColors() 内部则能够访问其余两个环境中的全部变量,由于那两个环境是它的父执行环境。下图形象地展现了前面这个例子的做用域链。
图中的矩形表示特定的执行环境。其中,内部环境能够经过做用域链访问全部的外部环境,但外部环境不能访问内部环境中的任何变量和函数。这些环境之间的联系是线性、有次序的。每一个环境均可以向上搜索做用域链,以查询变量和函数名;但任何环境都不能经过向下搜索做用域链而进入另外一个执行环境。对于这个例子中的 swapColors() 而言,其做用域链中包含 3 个对象: swapColors() 的变量对象、 changeColor() 的变量对象和全局变量对象。 swapColors() 的局部环境开始时会先在本身的变量对象中搜索变量和函数名,若是搜索不到则再搜索上一级做用域链。 changeColor() 的做用域链中只包含两个对象:它本身的变量对象和全局变量对象。这也就是说,它不能访问swapColors() 的环境。
函数参数也被看成变量来对待,所以其访问规则与执行环境中的其余变量相同。
JavaScript没有块级做用域常常会形成理解上的困惑。在其余语言中,由花括号封闭的代码块都有本身的做用域(若是用ECMAScript的话来说,就是它们本身的执行环境),于是支持根据条件来定义变量。
例如,以下的代码在JavaScript中并不会获得想象中的结果:
<script type="text/javascript"> //javascript没有块级做用域 if(true){ var color="blue"; } alert(color);//弹出的值是 blue for(var i=0;i<10;i++){ } alert(i);//弹出的值是10 </script>
在JavaScript中:
(1) if语句中的变量声明会将变量添加到当前的 执行环境(在这里是全局环境)中,若是再函数中,则添加到函数的局部环境中.
(2) for语句建立的变量i,即便在for循环执行结束后,也依旧会存在于循环外部的执行环境中,若是在函数中,则添加到函数的局部环境中.
不少语言中都有块级做用域,但JS没有,它使用var声明变量,以function来划分做用域,大括号“{}” 却限定不了var的做用域。用var声明的变量具备变量提高(declaration hoisting)的效果。
ES6里增长了一个let,能够在{}, if, for里声明。用法同var,但做用域限定在块级,let声明的变量不存在变量提高。
示例1: 块级做用域 if
function getVal(boo) { if (boo) { var val = 'red' // ... return val } else { // 这里能够访问 val return null } // 这里也能够访问 val }
变量val在if块里声明的,但在else块和if外均可以访问到val。
把var换成let,就变成这样了
function getVal(boo) { if (boo) { let val = 'red' // ... return val } else { // 这里访问不到 val return null } // 这里也访问不到 val }
示例2: 块级做用域 for
function func(arr) { for (var i = 0; i < arr.length; i++) { // i ... } // 这里也能够访问到i }
变量i在for块里声明的,但在for外也能访问到。
把var换成let,for外就访问不了i
function func(arr) { for (let i = 0; i < arr.length; i++) { // i ... } // 这里访问不到i }
示例3: 变量提高(先使用后声明)
function func() { // val先使用后声明,不报错 alert(val) // undefined var val; }
变量val先使用后声明,输出undefined,也不报错。
把var换成let,就报错了
function func() { // val先使用后声明,报语法错 alert(val) let val; }
示例4: 变量提高(先判断后声明)
function func() { if (typeof val == 'undefined') { // ... } var val = '' }
使用typeof判断时也能够再var语句的前面
但把var换成let,if处报语法错
function func() { if (typeof val == 'undefined') { // ... } let val = ''; }
ES6规定,若是代码块中存在let,这个区块从一开始就造成了封闭做用域。凡是在声明以前就使用,就会报错。即在代码块内,在let声明以前使用变量都是不可用的。语法上有个术语叫“暂时性死区”(temporal dead zone),简称TDZ。固然TDZ并无出如今ES规范里,它只是用来形象的描述。
let的注意事项
1. 不能重复声明
// var和let重复声明 var name = 'Jack'; let name = 'John'; // 两个let重复声明 let age = 24; let age = 30;
执行时报语法错
2. 有了let后,匿名函数自执行就能够去掉了
// 匿名函数写法 (function () { var jQuery = function() {}; // ... window.$ = jQuery })(); // 块级做用域写法 { let jQuery = function() {}; // ... window.$ = jQuery; }
以上所述就是本文的所有内容。其中大多数来自于JavaScript高级程序设计