JS变量、作用域、内存详细讲解

时间:2018-10-12
本文章向大家介绍JS变量、作用域、内存详细讲解,需要的朋友可以参考一下

  写到这个题目《JS变量、作用域,内存》,我就不由自主想起了黄金三嫖客。可能是名字有点像,嗯,一定是这样子的!

  JS接触下来,应该是要比Java简单不少的,所以,要学好啊。立个flag半年后来看到底学得怎么样。

  这会儿来到了本书的第四章。感觉这水平,后天的面试要跪啊,唉想不了那么多了,就当为实习做准备吧。


ES变量的两种数据类型

  • 基本类型值:5种,Undefined、Null、Boolean、Number、String。按值访问(因为可以操作保存在变量中的实际值)。
  • 引用类型值:保存在内存中的对象,按引用访问

  定义基本类型值和引用类型值的方式类似,都是创建一个变量并为该变量赋值。

    • 区别1,对于引用类型的值,我们可以为其添加属性和方法,也可以改变和删除其属性和方法,而对于基本类型值,我们不能给其添加属性,尽管这样不会导致错误。

例如

1      var person = new Object();
2         person.name = "Nicholas";
3         person.sex = "male";
4         alert(person.sex);
5         alert(person.name);    //"Nicholas"
1      var name = "Nicholas";
2         name.age = 27;
3         alert(name.age);    //undefined
    • 区别2   复制变量值不同

在从一个变量向另一个变量复制基本类型值和引用类型值时,存在不同。

如果从一个变量向另一个变量复制基本类型的值,会在变量对象上创建一个新值,然后把该值复制到为新变量分配的位置上。

拿书中的例子来说,基本类型值的复制如下

1 var num1 = 5;
2 var num2 = num1;

num1 中保存的值是 5,当使用 num1 的值来初始化 num2 时, num2 中也保存了值 5. 但是 num2 中的 5 和 num1 中的 5 是完全独立的,该值只是 num1 中 5 的一个副本。这两个变量互不影响。上图即此过程。

而对于引用类型值的复制就不一样了。

当从一个变量向另一个变量复制引用类型的值时,同样也会把存储在变量对象中的值复制一份到为新变量分配的空间中。不同的是,这个值的副本其实是一个指针。这个指针指向存储在堆中的一个对象。复制操作结束后,两个变量实际上将引用同一个对象。因此,改变其中一个变量,就会影响另一个变量。它们之间相互影响。

1 var obj1 = new Object();
2 var obj2 = obj1;
3 obj1.name = "Nicholas";
4 alert(obj2.name);  // "Nicholas"

 分析此过程:

首先,变量 obj1 保存了一个对象的实例,然后,这个值被复制到了 obj2 中。obj1 和 obj2 指向同一个对象。这样,当为 obj1 添加 name 属性后,可以通过 obj2 来访问这个属性,因为这两个变量引用的都是同一个对象。


# 传递参数

ES中所有的函数都是按值传递。 访问变量有按值和按访问 这两种方式,但参数只能是按值传递。这个是一个困惑点。

在向参数传递基本类型的值时,被传递的值会被复制给一个局部变量。在向参数传递引用类型的值时,会把这个值在内存中的地址复制给一个局部变量,因此这个局部变量的变化会反映在函数的外部。

看例子理解个中玄妙

1         function addTen(num) {
2             num += 10;
3             return num;
4         }
5         
6         var count = 20
7         var result = addTen(count);
8         alert(count);    //20
9         alert(result);   //30

 这里的函数 addTen()有一个参数 num,参数实际上是函数的局部变量。在调用这个函数时,变量 count 作为参数被传递给函数,这个变量的值是 20. 于是,数值 20 被复制给参数 num 以便于在 addTen()中使用。在函数的内部,参数 num 的值被加上了 10 ,但这一变化不会影响函数外部的 count 变量。参数 num 与变量 count 互不认识。它们仅仅是具有相同的值。加入 num 是按引用传递,那么变量 count的值也会是30,从而反映函数内部的修改。

再举一个例子

     function setName(obj) {
            obj.name = "Nicholas";
        }
        
        var person = new Object();
        setName(person);
        alert(person.name);    //"Nicholas"

 代码创建了一个对象,并将其保存在变量 person 中。然后,这个对象被传递到 setName()函数中之后就被复制给了 obj。在这个函数内部,obj 和 person 引用的是同一个对象。换句话说,即使这个对象是按值传递的,obj 也会按引用来访问同一个对象。于是,当函数内部为 obj 添加 name 属性后,函数外部的 person 也将有所反映。因为 person 指向的对象在堆内存中只有一个,而且是全局对象。

(以上不太好理解。)

有人建议,可以把ES函数的参数想象成局部变量。


# 检测类型

typeof 操作符,可检测变量为 字符串、数值、布尔值、undefined,然鹅,对 null 失效,null 会被检测成 object。

怎么办?

 1         var s = "Nicholas";
 2         var b = true;
 3         var i = 22;
 4         var u;
 5         var n = null;
 6         var o = new Object();
 7         
 8         alert(typeof s);   //string
 9         alert(typeof i);   //number
10         alert(typeof b);   //Boolean
11         alert(typeof u);   //undefined
12         alert(typeof n);   //object
13         alert(typeof o);   //object

 通常,通过 typeof 操作符,我们只能得到对象是 object ,但用处不大,就好像,我通过检测知道,你是一个 人 ,但重要的是要获知你究竟是什么人,这是更重要的。这一点上,typeof 就显得没那么有效了。ES提供了另一个操作符满足我们的要求——instanceof 操作符

result = variable instanceof constructor

  如果变量是给定引用类型的实例,instanceof 操作符就会返回 true。所有的引用类型都是 object 的实例。

# 执行环境

全局执行环境是 window 对象。每个函数都有自己的执行环境。当执行流进入到一个函数时,函数的环境就会被推入一个环境栈中。在函数执行之后,栈将其环境弹出,把控制权返回给之前的执行环境。

当代码在一个环境中执行时,会创建变量对象的一个作用域链

作用域链,是保证对执行环境有权访问的所有变量和函数的有序访问。作用域链的前端是当前执行的代码所在环境的变量对象。如果这个环境是函数,则将其活动对象作为变量对象。作用域链中的下一个变量对象来自包含(外部)环境,而再下一个变量对象则来自下一个包含环境。这样,已知延续到全局执行环境。全局执行环境的变刘昂对象始终都是作用域链中的最后一个对象。

标识符解析是沿着作用域链一级一级地搜索标识符的过程。搜索过程始终从作用域链的前端开始,然后逐级向后回溯,直到找到标识符未知。如果找不到标识符,通常会导致错误发生。

 1      var color = "blue";
 2         
 3         function changeColor(){
 4             if (color === "blue"){
 5                 color = "red";
 6             } else {
 7                 color = "blue";
 8             }
 9         }
10         
11         changeColor();
12 
13         alert("Color is now " + color);

 明天再干,今晚干不动了已经,脑子要不好使了。