变量
之前看过一遍书,对ES中的参数传递,作用域链,无块级作用域,闭包等有了一定的了解,但是似懂非懂,今天把整个过程用代码演示出来,加深理解。
- 变量名
学过汇编和C我们知道,像C/C++这种编译成机器代码的语言,变量名是不需要存储在内存中的,在代码执行时都编译为(栈地址+偏移地址)的形式,变量名只是编程时提供给编译器识别的一个名字,比如int n = 5;
在机器代码中是不存在n的,只对内存中5的地址产生类似于mov [0x00410FC0],5的操作; - 指针
指针,int *p = &n;
,p这个指针变量保存着n对应的内存空间的地址,即它的值为0x00410FC0,而p这个变量名,也不存在于内存和机器代码中; - 引用
C++中引入引用的概念,除了需要初始化,且只能初始化一次,其实它和变量名没有区别,它就是一个别名;
提到前面这些概念,就是为了更好的理解javascript中的变量机制,JS是弱类型动态语言(关于强、弱,静态、动态类型请参考 https://www.zhihu.com/question/19918532 ),是一种解释性语言,同时是一种脚本语言(编程性语言、解释性语言、脚本语言请参考 http://blog.csdn.net/mooncom/article/details/60955411 )。
在计算机科学中,指针(Pointer)是编程语言中的一个对象,利用地址,它的值直接指向(points to)存在电脑存储器中另一个地方的值。由于通过地址能找到所需的变量单元,可以说,地址指向该变量单元。因此,将地址形象化的称为“指针”。意思是通过它能找到以它为地址的内存单元。——百度百科
JS与C/C++是不同的,JS中保存基本类型的值和标识符在栈区,而引用类型的标识符和地址保存在栈区,值保存在堆内存中;
存储.png通过变量名操作引用类型称为引用,这里的引用和C/C++中的引用不是同一个概念,因此催生出“指针”的概念,操作在栈区的地址,也可以叫做操作指针,或是堆内存地址。
//C++中指针存放地址
int n = 5;
int *p = &n;
int *q = p;
cout<<p<<"\t"<<q<<endl;//0x6ffe2c 0x6ffe2c
cout<<*p<<"\t"<<*q<<endl;//5 5
//变量
function sum1 () {
}
var sum2 = sum1;
console.log(sum2.name);//'sum1'
console.log(typeof sum1);//'function'
sum1 = null;
console.log(typeof sum1);//'null'
console.log(sum2.name);//'sum1'
保存在栈区或堆内存可以是基本类型的值,也可以是引用类型的值,基本类型的值与引用类型的值的区别:
- 1.动态属性
引用类型的值可以动态的改变其属性,因为它是对象;而基本类型的值不可以;
//动态属性
var person = new Object();
person.name = 'Matthew';
console.log(person.name);//'Matthew'
var name = 'Matthew';
name.age = 27;
console.log(name.age);//undefined
- 2.复制变量值
基本类型的复制是把值复制到新变量分配的内存空间中,两个变量完全独立;
//变量赋值
var num1 = 5;
var num2 = num1;
num1 = 4;
console.log(num1 + '\t' + num2);//4 5
我们都知道变量是类似于引用的方式来映射的,《JS高程》中把它解释为指针的复制,实际上指的是传递一个引用,即传递栈区标识符对应的那个地址,和参数中传递基本类型和引用类型是一样的,都是按值传递,只不过前者是传递真值,后者是传递引用(地址)。
var obj1 = new Object();
var obj2 = obj1;
obj1.name = 'Matthew';
console.log(obj2.name);//'Matthew'
- 3.传递参数
上面也讲了,JS中参数传递都是按值传递,基本类型很好理解,参数是真值;
//参数传递
function add(num) {
num++;
return num;
}
var count = 1;
var result = add(count);
console.log(count);//1 没有变化
console.log(result);//2
而参数为引用类型时,传递的是一个引用,上面讲到,引用类型在栈区的标识符对应的地址,可以理解为一个指针,那么这里可以解释为传递的是这个指针的副本,即这个地址,所以也是值传递,我们看代码:
function setName(obj) {
obj.name = 'Matthew';
obj = new Object();
obj.name = 'Alex';
}
var person = new Object();
setName(person);
console.log(person.name);//'Matthew'
如果是按引用传递,结果应该显示为'Alex';
什么是引用传递?,在JS中是不存在引用传递的,在C++中,通过取地址符,取到变量地址,使得形参的地址与实参的相同,改变形参也就改变了实参,所以通常用来通过函数内部修改改变外部环境,看代码:
void swap(int &a,int &b)
{
int temp;
temp=a;
a=b;
b=temp;
cout<<a<<""<<b<<"\n";
}
int main()
{
int x=1;
int y=2;
swap(x,y);
cout<<x<<""<<y<<"\n";//21 21
return 0;
}
形参a,b作为局部变量在栈区开辟了内存空间,存放的是实参变量的地址;如此,才是引用传递;
作用域
- 当程序开始执行时,解析器(编译器)会创建一个全局执行环境,又称为执行上下文,在web浏览器中,这个环境就是window对象,以后执行流每进入一个函数,函数的环境的都会被推入这个环境栈中,在函数执行完后,栈将其环境推弹出,把控制权交给之前的执行环境,ECMAScript的执行流正是由这个方便的机制控制的;
每个执行环境都会保存一个变量对象和一个作用域链,变量对象就是上文提到的标识符和值或标识符和地址(基本类型或引用类型);作用域链的前端就是当前环境的变量对象,下一个对象就是下一个包含(外部)环境的变量对象,以此类推,直到全局环境的变量对象;
//作用域
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();
作用域链.png
每个矩形代表一个执行环境,作用域链是以链表的形式连接的,只能向上访问,不能向下访问;
- 没有块级作用域
1.Javascript中没有块级作用域:
//没有块级作用域
for (var i = 0;i < 10;i++) {
}
console.log(i);//10
if(true) {
var color = 'blue';
}
console.log(color);//'blue'
2.声明变量
未使用var 关键字声明的是全局变量,及时在函数内部;
3.查询标识符
从作用域链前端向上逐级查询,直到全局环境;
函数表达式和闭包放在下一篇吧
网友评论