js高级程序设计学习-变量和作用域

391 阅读6分钟

导言

js变量与其他语言的变量又很大的区别。js变量松散类型的本质,决定了它只是在特定时间用于保存特定值的名字而已。由于不存在定义某个变量必须要保存哪些数据类型,所以可以在脚本在执行时改变。

基本数据类型和引用数据类型

Js变量可能包含两种不同的数据类型的值:

  • 基本数据类型:简单数据段
Undefined , Null , Boolean , Number , String ,Symbol

基本数据类型是按值访问的

  • 引用数据类型:由多个值构成的对象。
Object

引用数据类型是按引用访问的

动态的属性

这个好像有点意思啊

  • 对于引用类型的值,我们可以为其添加属性和方法,也可以改变和删除其属性和方法。
  • 但是,我们不能给基本类型的值添加属性,尽管这样做不会导致任何错误。
var person = new Object();
person.name = 'zs'
console.log(person.name); // zs

var name = 'zs';
name.age = 24;
console.log(name.age); // undefined

传递参数

这个之前我都忽略了
JS中所有函数的参数都是按值传递的。也就是说,把函数外部的值复制给函数内部的参数,就把和把值从一个变量复制到另一个变量一样。基本类型值的传递如同基本变量的复制一样,而引用类型值的传递,则如同引用类型的复制一样。

Note
在向参数传递基本类型的值时, 被传递的值会被复制给一个局部变量(或者用js的概念来说,就是arguments对象中的一个元素). 在向参数传递引用类型的值时,会把这个值在内存中的地址复制给一个局部变量,因此这个局部 变量的变化会反映在函数的外部。

很多开发人员错误地认为:在局部作用域中修改的对象会在全局作用域中反映出来,就说明参数传递是按引用传递的

下面,我再看一个例子

function setName(obj) {
    obj.name = 'zs';
    obj = new Object();
    obj.name = 'h'
}
var person = new Object();
setName(person);
person.name; // zs

这里说明即使在函数内部修改了参数的值,但原始的引用仍然保持未变。实际上,当在函数内部重写obj时,这个变量的引用的就是一个局部对象了。而这个局部对象会在函数执行完毕后立即被销毁。

可以把JS函数的参数想象成局部变量

执行环境及作用域

执行环境是 js 中最为主要的一个概念。执行环境定义了变量或函数有权访问的其他数据,决定了他们各自的行为。每个执行环境都有一个与之关联的变量对象,环境中定义的所有变量和函数都保存在这个对象中。

全局执行环境是最外围的一个执行环境。在web浏览器中,全局执行环境被认为是window 对象,因此所有全局变量和函数都是作为window对象的属性和方法创建的。

每个函数都有自己的执行环境。当执行流进入一个函数时,函数的环境就会被推入一个环境栈中。

作用域链:保证对执行环境有权访问的所有变量和函数的有序访问
全局执行环境的变量对象始终都是作用域链中的最后一个对象

此外,在局部作用域中定义的变量可以在局部环境中与全局变量互换使用,如下面这个例子:

var color = 'blue'

function changeColor() {
    var anthor = 'red';

    function swapColors() {
        var tempColor = anthor;
        anthor = color;
        color = tempColor;
    }

    swapColors();
}
changeColor();

内部环境可以通过作用域链访问所有的外部环境,但外部环境不能访问内部环境中的任何变量和函数。这些环境之间的联系是线性,有次序的。每个环境都可以向上搜索作用域链,以查询变量和函数名;但任何环境都不能通过向下搜索作用域链而进入另一个执行环境

函数参数也被当作变量来对待,因此其访问规则与执行环境中的其他变量相同

延长作用域链

虽然执行环境的类型只有两种

  • 全局
  • 局部(函数)

但是还是有其他办法来延长作用域链。

这么说是因为有些语句可以在作用域链的前端临时增加一个变量对象,该变量对象会在代码执行后被移除。在两种情况下回发生这种现象。具体来说,就是当执行流进入下列任何一个语句时,作用域链就会得到加长:

  • try-catch 语句的 catch 块
  • with 语句

这两个语句都会在作用域链的前端加一个变量对象。
对with语句来说,会将指定的对象添加到作用域链中。
对catch语句来说,会创建一个新的变量对象,其中包含的是被抛出的错误对象的声明。

这两个语句都会在作用域链的前端添加一个变量对象。
对 with 语句来说, 会将指定的对象添加到作用域链中。
对 catch 语句来说 , 会创建一个新的变量对象 , 其中包含的是被抛出的错误对象的声明。

没有块级作用域

javaScript没有快级作用域经常会导致理解上的困难。在其他类 C 的语言中 , 由花括号封闭的代码块都要自己的作用域,因而支持根据条件来定义变量。
例如,下面的代码在 javaScript 中并不会得到想象中的结果:

if(true) {
    var color = 'blue';
}
console.log(color)

这里在一个 if 语句中定义了变量 color。 如果是在 C , C+ 中, color 会在 if 语句执行完毕后被销毁。 但在 js 中,if 语句中的变量声明会将变量添加到当前执行环境(这里是全局环境)中。
这一点,在使用 for 语句时尤其重要

for(var i = 0 ; i < 10 ; i ++) {
    console.log(i)
}
console.log(i); // 10

对于有快级作用域的语言来说, for 语句初始化变量的表达式所定义的变量, 只会存在于循环的环境中。而对于 javaScript 来说, 由 for 语句创建的变量 i 即使在 for 循环执行结束后 , 也依旧会存在于循环外部的执行环境中。

1.声明变量

使用 var 声明的变量会自动被添加到最接近的环境中。在函数内部, 最接近的环境就是函数的局部环境;在 with 语句中 , 最接近的环境是函数环境。
如果初始化变量时没有使用 var 声明,该变量会自动被添加到全局环境。

2.查询标识符

var color = 'blue';
function getColor() {
    return color;
}
console.log(getColor()); // 'blue'