重拾ECMAScript基础——变量、作用域

变量

之前看过一遍书,对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中保存基本类型的值和标识符在栈区,而引用类型的标识符和地址保存在栈区,值保存在堆内存中;

重拾ECMAScript基础——变量、作用域_第1张图片
存储.png

通过变量名操作引用类型称为引用,这里的引用和C/C++中的引用不是同一个概念,因此催生出“指针”的概念,操作在栈区的地址,也可以叫做操作指针,或是堆内存地址。

//C++中指针存放地址
    int n = 5;
    int *p = &n;
    int *q = p;
    cout<
//变量
        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作为局部变量在栈区开辟了内存空间,存放的是实参变量的地址;如此,才是引用传递;

作用域

  • 当程序开始执行时,解析器(编译器)会创建一个全局执行环境,又称为执行上下文,在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();
重拾ECMAScript基础——变量、作用域_第2张图片
作用域链.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.查询标识符
从作用域链前端向上逐级查询,直到全局环境;

函数表达式和闭包放在下一篇吧

你可能感兴趣的:(重拾ECMAScript基础——变量、作用域)