首页 > 开发 > 前端 > 正文

js学习笔记 2-03 变量 作用域 和内存的问题

2017-07-06 10:37:45  来源:慕课网
基本类型和引用类型的值
5种基本数据类型是按值访问的,因为可以操作保存在变量中的实际的值。

引用类型的值是保存在内存中的对象。JS 不允许直接访问内存中的位置,也就是说不能直接操作对象的内存空间。在操作对象时,实际上是在操作对象的引用而不是实际的对象。
复制变量值
如果从一个变量向另一个变量复制基本类型的值,会在变量对象上创建一个新值,然后把该值复制到为新变量分配的位置上。
复制结束后,这两个变量可以参与任何操作而不会相互影响。

图片描述

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

图片描述

传递参数
所有函数的参数都是按值传递的。
基本类型值的传递如同基本类型变量的复制一样
而引用类型值的传递,则如同引用类型变量的复制一样。

在向参数传递基本类型的值时,被传递的值会被复制给一个局部变量(即命名参数,或者用 ECMAScript的概念来说,就是 arguments 对象中的一个元素)。

再向参数传递引用类型的值时,会把这个值在内存中的地址复制给一个局部变量,因此这个局部变量的变化会反映在函数的外部。
//num 按照基本类型传递
function addTen(num){
    num+=10;
    return num;
}
var count = 20;
var result = addTen(count);
console.log(count);//20  没有变化
console.log(result);//30
//假如num 按引用类型传递,那么变量 count 的值也将变成30,从而反映函数内部的修改
function setName(obj){
    obj.name = "Nicholas";
}
var person = new Object();
setName(person);
console.log(person.name)//"Nicholas"
//上面的代码创建了一个对象,并将其保存在了变量 person 中。
//然后,这个变量被传递到 setName()函数中之后就被复制给了 obj。
//在函数内部,  obj 和 pereson 引用的是同一个对象。
function setName(obj){
    obj.name = "Nicholas";
    obj = new Object();//为 obj 重新定义了一个对象
    obj.name = "Greg";//为该对象定义了一个带有不同值的name 属性
}
var person = new Object();
setName(person);
console.log(person.name);//"Nicholas"
//上面的代码说明即使在函数内部修改了参数的值,但是原始的引用仍然保持未变。
//当在函数内部重写 obj 时,这个变量应用的就是一个局部对象了。而这个局部对象会在函数执行完毕后立即被销毁。
检查类型
 确定一个值是哪种基本类型可以使用 typeof 操作符,而确定一个值是哪种引用类型可以使用 instanceof 操作符。
小总结
基本类型值在内存中占据固定大小的空间,因此被保存在栈内存中;
从一个变量向另一个变量复制基本类型的值,会创建这个值的一个副本;

引用类型的值是对象,保存在堆内存中;
包含引用类型值的变量实际上包含的并不是对象本身,而是而是一个指向该对象的指针;
从一个变量向另一个变量复制引用类型的值,复制的其实是指针,因此两个变量最终都指向同一个对象;

确定一个值是哪种基本类型可以使用 typeof 操作符,而确定一个值是哪种引用类型可以使用 instanceof 操作符。
执行环境(作用域)

所有变量(基本类型/引用类型)都存在于一个执行环境(也称作用域)当中,这个执行环境决定了变量的生命周期,以及哪一部分代码可以访问其中的变量。

执行环境有全局执行环境和函数执行环境之分;(全局作用域/局部作用域)

每次进入一个新的执行环境,都会创建一个用于搜索变量和函数的作用域链;

函数的局部环境不仅有权访问函数作用域中的变量,而且有权访问其包含(父)环境,乃至全局环境;

全局环境只能访问在全局环境中定义的变量和函数,而不能直接访问局部环境中的任何数据;

变量的执行环境有助于确定应该何时释放内存。
变量提升
定义变量的时候,变量的声明会被提升到作用域的最上面,变量的赋值不会提升
函数提升
JavaScript 解析器首先会把当前作用域的函数声明提前到整个作用域的最前面
函数两种定义方式
函数声明/函数表达式
//函数声明  
function f(){
    ...
}
//函数表达式
 var myFun = function(){
    ....
}
自调用函数
只能执行一次
(function(){
    ...
})()
练习
var num =10;
fun();
function fun(){
    console.log(num);//undefined
    var num = 20;
}

//js通过解析器执行:
//1.预解析
       //找 var 、 function 和参数
       //找到 var 和 function 之后,会把 var 和 function 提前;声明提前赋值不用
       //找到 num 和 fun
       //var  num;
       //function fun (){
            //console.log(num);
           // var num = 20;
       // }
//2.解析一行执行一行
       //num = 10
      // fun();
           //执行 fun 后,进入局部作用域;
                //预解析  找到 num
               //从上到下一行一行执行代码
var a= 18;
f1();
function f1(){
    var b=9;
    console.log(a);//undefined
    console.log(b);//9
    var a="123";
}
//函数声明   可以先调用再声明      因为预解析function提前了
console.log(fnName(1,2)) //3
function fnName(a,b){
    return a+b;
}
//函数表达式  
console.log(fnName(1,2)) //报错  fnName is not a function
var fnName = function(a,b){
    return a+b;
}
//先预解析  var fnName  声明提前  赋值没有
f1();
console.log(c);//9
console.log(b);//9
console.log(a);// a is not defined
function f1(){
    var a=b=c=9;
    console.log(a);//9
    console.log(b);//9
    console.log(c);//9
}
//预解析  function f1() 提前
//        function f1(){
//预解析函数内部  var a 提前
//            var a;
//赋值   a是局部变量   b,c 全局变量
//            a=b=c=9;
//            console.log(a);
//            console.log(b);
//            console.log(c);
//        }
//一行一行代码执行
//        f1();
//        console.log(c);
//        console.log(b);
//        console.log(a);