前言
这篇和大家说一下javascript中的变量和作用域,由于是将基础嘛,主要给大家捋一下知识,不想翻开书复习的道友可以看一下,打算刚开始学习javascript的同学可以扫一眼。
PS:jQuery源码交流群( 239147101)等你来,群里高手云集,让我受益匪浅,尽量少灌水。
变量
javascript中有两种变量,分别是基本类型和引用类型,基本类型是Null,Undefined,String,Boolean,Number这五种,前面简单的介绍了。引用类型是指Object,Array,Date,RegExp,Function这些。创建这两种变量是类似的,都是创建一个变量然后给它赋值。不同的原因主要是在内存中位置和操作不同。
基本类型
基本类型比较简单,基本类型的值保存在栈中。看例子
var v = 1;
基本类型变量在内存中的表示,没有涉及堆
看看复制变量之后在内存中的表示,执行的代码如下
var v = 1; var n = v;
解释一下,如果一个变量把基本类型的值复制给另一个变量时,会创建一个新的相同的值,把这个新的值赋值给新的变量,这样,内存中就有两个一样的值了,分别是新的变量和就的变量指向的值,虽然值是一样的。
引用类型
应用类型的变量创建和基本类型的创建是一样的,主要看在内存中的存储方式,代码
var obj = new Object();
可以看到,变量obj存储的是一个地址(个人的理解),其实obj的值是一个指针,指向了堆中的对象,可以找到堆中的new出来的对象。再看看复制一个变量时的情况
var obj = new Object(); var o = obj;
解释一下,这和基本类型的复制是不同的,我们可以看出,当一个变量向另一个变量复制引用类型的值时,也会将存储在变量对象中的值复制一个给新的变量。复制的这个值实际上就是一个指针,指向堆中的一个对象,由于指针是相同的,所以知指向的对象就是同一个对象,此时无论你该哪一个变量,都会影响另一个变量,因为指向的对象是用一个嘛,例如
var obj = new Object(); var o = obj; o.name = "hainan"; console.log(obj.name);//"hainan"
而基本类型则不会,看例子
var v = 1; var n = v; v = 100; console.log(n);//1 console.log(v);//100
这个比较简单,不懂的话看上面的那个内存的图就懂了。
区别
上面说了复制的那个区别,还有一个就是基本类型不能添加属性和方法,而引用类型则可以添加,看例子吧,很简单
//基本类型 var peo = "hainan"; peo .age = 25; console.log(peo.age);//undefined,但是不报错 //引用类型 var people = new Object(); people.age = 25; console.log(people.age);//25
函数传参
javascript中函数参数都是按值传递的,也就是把函数外部的值复制给函数内部的参数,和复制变量一样,无论传递的是什么类型,都和复制变量一样。首先看一下传递基本类型的例子
function test(n){ return n = n+100; } var num = 10; var result = test(num); console.log(num);//10 没有发生变化 console.log(result);//110
简单解释一下,调用函数时,传递一个基本类型的参数num给函数,此时,复制值给内部的参数n,这样num和n变量都有了相同的值,但是,这两个之间没有任何的关系,只是值相同而已,想想前面的图就清楚了,内部的变量也就是参数n改变了之后,num的值并没有发生改变。
接下来看看传递引用类型的例子
function test(obj){ obj.name = "hainan"; } var people = new Object(); test(people); console.log(people.name);//"hainan"
解释一下,其实这个也和复制引用类型变量是一样的,传递一个引用类型给函数参数时,把外部的值复制给内部的函数参数,由于是引用类型这个值是一个指针,所以外部的引用类型变量和内部的参数此时会指向同一个对象,回想上面复制的图,当内部的参数指向的对象改变时,外部的变量指向的对象一定会改变,是同一个对象嘛。
所以,现在你只需知道,传递参数和复制变量是同一回事,不会的时候回想内存中的存储方式就明白了,管它是按值还是按引用传递呢,只是一个说法罢了,呵呵,但是面试的时候可能会问,记住javascript是值传递就行了。
作用域
执行环境
每个函数都有自己的执行环境(execution context),执行环境定义了变量和函数有权访问的数据,太官方了,就是每一个函数都有自己可以访问的范围,在自己的范围内的数据才可以得到。每一个执行环境中都有一个变量对象,保存着该环境中定义的变量和函数。全局执行环境是最外面的一个执行环境,就是window对象,所有的全局变量和函数就是它的属性。每一个执行环境执行完之后,这个环境就会被销毁,里面的变量对象也就没有了,当然变量对象中的变量和函数也就销毁了。
作用域
javascript的作用域是指变量和函数可以访问的范围,分为局部作用域和全局作用域,这个和C语言是类似的,但是不同点是javascript的作用域没有块级作用域,不像C语言的{}可以表示一个块级的作用域,javascript只有函数作用域,在函数内部声明的变量只能在函数体和子函数可以访问,这个函数的外部不能访问。看例子
//没有块级作用域 if(true){ var n = 1; } console.log(n);//1 //注意 for(var i=0;i<10;++i){ } console.log(i);//10
上面的例子要是在C语言或者java中n和i会在{}语言执行完之后销毁,在javascript中可以看到,它们并没有销毁,说明并没有块级作用域。
function test(a,b){ var sum = a + b; return sum; } test(1,2);//3 console.log(sum);//sum is not defined
可以看出sum是在函数的外部访问不到的,因为sum函数是在函数的局部作用域中定义的,在函数执行结束时,内部的变量就会销毁,所以外部的作用域访问不到它,这就是函数的作用域。
作用域链
作用域链(scope chain)是保证在执行环境中有序的访问变量和函数,我们可以这样想,每个函数都有一个自己的执行环境,这个执行环境的嵌套就像套娃是一样的,大的套小的,内部的执行环境的变量有权访问外部的执行环境的数据,而外部的不可以反问内部的,所以当内外的执行环境中都有一个相同的变量或函数时,你是先访问哪一个呢?所以就有了作用域链这个概念。这个作用域链的每一个节点是一个变量对象,函数中有一个活动变量对象的概念,刚开始时只有函数的arguments对象,之后会把当前的变量对象中的变量和函数复制到活动对象中。作用域链的第一个变量对象是活动对象,之后就是下一个包含环境,之后是包含环境的包含环境......直到全局执行环境的变量对象。 看例子可能大家会明白
function test(a,b){ var sum = a + b; return sum; } var s = test(1,2);
分析上面的这段代码,在未调用test()函数之前,看看作用域链情况
在未执行函数的时候,可以看到函数的作用域链只用一个全局的变量对象,里面有window和test函数等,在执行var s = test(1,2);语句的时候,作用域链会发生变化,会增加一个函数的活动对象到作用域链的前面,这个活动对象的初始值只有函数的arguments对象,之后会把函数的内部变量等复制到这个活动对象中,请看下面的图
这就是函数的作用域链,以后要访问某个变量的时候,会沿着这个作用域链进行查找,即沿着活动对象->外部函数的活动对象->外部函数的外部函数的活动对象->......->终点的全局执行环境,在这期间在某个活动对象中内部有这个值就会返回这个值,这个过程就会停止,不在进入另一个执行环境中,看个例子
//全局变量 var color = "red"; function getColor(){ //内部变量 var color = "blue"; return color; } console.log(getColor());//blue
看到这里大家会明白了。
PS:其实javascript的闭包和作用域链有很大的联系,这里咱不讨论,闭包会单独讨论。
小结
这就是javascript的作用域了,主要大家还是好好看看变量内存那块,分析几个就会了。注意一下javascript没有块级作用域这一说法,这点大家要小心了。最近几天天天在群里看大家的聊天记录,论文也不想写了,哎,坐等回家吃猪肉了,希望老师别鄙视我。