变量 & 作用域

什么是变量 ?

变量,是计算机内存中存储数据的标识符,根据变量名,可以获取到内存中存储的数据。

为什么要使用变量 ?

根据变量可以方便的获取或者修改内存中的数据。


变量

ECMAScript 的变量是松散类型的。所谓松散类型,就是可以用来保存任何类型的数据,即:

  • 声明时不用给变量指定数据类型;
  • 赋值时可以修改数据类型;

简单来讲,每个变量仅仅是一个用于保存值的占位符而已。


[ 声明与赋值 ] 定义变量时要使用 var (或者 let、const — 块级声明?!)操作符,后跟变量名即可。

var abc;    // 声明时,如果没有赋值,会保存为一个特殊的值 undefined

var xyz = 'xyz';    // 声明的同时又进行了赋值(直接初始化变量)
xyz = 123;    // 更改为其他类型(有效,但不建议修改变量所保持的值的类型,因为没必要)

// 可以使用一条语句定义多个变量,只要把每个变量(初始化或不初始化均可)用逗号分开即可
var abc = 'abc',xyz = 'xyz',num = '123';    // 同时声明多个变量

function demo(){
   // 使用 var 定义的变量将成为定义该变量的作用域中的局部变量;
   // 也就是说,如果在函数中使用 var 定义一个变量,那么这个变量在函数退出后就会被销毁,具体见“作用域”。

   // 省略 var 操作符可以定义全局变量,但不推荐这样的做法(难以维护)。
   var abc = abcd = 'abc';  // 这种写法,会产生一个全局变量 abcd,应当避免
}
demo();
console.log(abcd);  // =>abcd

[ 全局变量 ]  在全 { 作用域?↓ } 中定义的变量以及没有使用 var、let 或者 const 定义的变量。

var abc = 'abc';                             let xyz = 'xyz';
console.log(abc);    // =>abc                  console.log(xyz); // =>xyz
console.log(window.abc);    // =>abc           console.log(window.xyz); // undefined

function func(){
   abc = 'abc';
}
func();
console.log(abc);    // =>abc
console.log(window.abc);    // =>abc

[ 直接量 ]  又称字面量,用来表示值,它们是固定的值,不是变量。在 JavaScript 中,直接量可以分为:

  • 字符串直接量:"abc"    "123"
  • 模板字符串直接量:`abc`    `123`
  • 数字直接量:123    0.123
  • 布尔直接量:true    false
  • 正则直接量:/ab+/g
  • Null直接量:null
  • 数组直接量:[]    [1,2,3]
  • 对象直接量:{}    {name:'tom',age:25}

变量的解构赋值( ES6 )


在 ES6 中,按照一定模式从数组和对象中提取值,然后对变量进行赋值,这被称为解构赋值。

从本质上来讲,这种模式属于“模式匹配”,只要等号两边的模式相同,左边的变量就会被赋予对应的值。

数组解构赋值与对象结构赋值的差异:

  • 数组元素是按次序排列的,变量取值由位置决定;
  • 而对象没有次序,变量名必须与属性名相同,才能取到正确的值。

[1] 数组的解构赋值

[ 基本用法 ]

let [foo,[bar],baz]=[1,[2],3]
foo // 1
bar // 2
baz // 3

如果解构不成功,变量的值就为 undefined

let [x,y,...z]=['a']
x // 'a'
y // undefined
z // []

[ 默认值 ]  解构赋值允许指定默认值。

但是,ES6 内部使用严格相等于(===)来判断一个位置是否有值,如果数组的成员不严格等于 undefined,默认值就不会生效。即,一个位置的值不是 undefined,那么即使设置了默认值,也不会有效。

let [foo=true]=[];
foo   //  true

let [x=1]=[undefined]
x    //  1
let [x=1] =[null]
x    //  null

[2] 对象的解构赋值

let {foo,bar}={foo:'abc',bar:'xyz'}

foo // 'abc'
bar // 'xyz'

# 当变量名和属性名相同时

let {foo,bar}={foo:'abc',bar:'xyz'}
foo // 'abc'
bar // 'xyz'
上述代码的实质应该是:
let {foo:foo,bar:bar}={foo:'abc',bar:'xyz'}
// 当变量名和属性名一样时,可以简写 {foo,bar}来代替{foo:foo,bar:bar}

对象解构赋值的内部机制:是先找到同名属性,然后再赋值给对应的变量。真正赋值的是后者,而不是前者

# 当变量名与属性名不同时

let {foo:hello,bar:world}={foo:'abc',bar:'xyz'}
hello // 'abc'
world // 'xyz'
foo // error: foo is not defined

foo 是匹配的模式,hello 才是真正的变量


[3] 字符串的解构赋值

字符串结构赋值的时候,字符串被转换成了一个类似数组的对象

const [a,b,c,d,e]='hello'
a // 'h'
b // 'e'
c // 'l'
d // 'l'
e // 'o'

// 这种类数组的对象,有 length 属性,因此,也可以对 length 属性进行解构赋值

let {length:len}='hello'
len // 5

[4] 数值和布尔值的解构赋值

数值和布尔值进行解构赋值的时候,会先转换为对象

let {toString:s} = 123
s // Number.prototype.toString

let {toString:s} = true

s // Boolean.prototype.toString

//数值对象和布尔值的包装对象都有toString属性,因为变量s可以取到值

解构赋值的规则:只要等号右边的值不是数组或者对象,就先将其转化为对象,由于 null 和 undefined 无法转化为对象,所以对它们解构赋值会报错

let {proxy:x}=undefined;
x // TypeError

let {proxy:y} = null;
y // TypeError

[5] 函数参数的解构赋值

function add ([x,y]){
    return x+y;
}

add([1,2])  // 3

[6] 用途

[ 交换变量的值 ] 没有解构赋值的情况下,交换两个变量需要一个临时变量

let x=1;
let y=2;
[x,y] = [y,x]
x // 2
y // 1

[ 从函数中返回多个值 ] 从一个函数返回一个数组是十分常见的情况。但是,函数只能返回一个值,如果需要返回多个值,只能将他放在数组或者对象里返回,有了结构赋值,取出这些值就非常方便

// 返回数组
function example(){
    return [1,2,3]
}

let [a,b,c]=example()
a // 1
b // 2
c // 3

// 返回对象
function example(){
    return {
    foo:1,
    bar:2
    }
}

let [foo,bar]=example()
foo // 1
bar // 2

[ 函数参数的定义 ] 解构赋值,可以很方便的将一组参数与变量名对应起来


[ 提取 JSON 数据 ] 解构赋值在提取 JSON 数据尤为有用

let jsonData = {
    id:42,
   status:"OK",
   data:[23,45]   
}

let {id,status,data}=jsonData

console.log(id,status,data)
// 42,"OK",[23,45]

[ 定义函数参数的默认值 ] 避免在函数体内部再写 var foo = config.foo || "default foo"这样的语句

jQuery.ajax= function(url,{
      asyc=true,
      beforeSend=function(){},
      cache=true,
      complete=function(){},
      crossDomain=false,
      global = true,
      //  .... more config
    }){
      // ... do stuff
    }

[ 遍历 Map 结构 ] 任何部署了 Iterator 接口的对象都可以使用 for...of 循环遍历。

Map 结构原生支持 Iterator 接口,配合变量的解构赋值,获取键名和键值都非常方便

var map = new Map()
    map.set("first",'hello')
    map.set("second",'world')

    for (const [key,value] of map) {
      console.log(key + "is" +value)
    }

    // first is hello
    // second is world
    
    // 只想获取键名
     for (const [key] of map) {
      //
    }
     // 只想获取键值
     for (const [,value] of map) {
      //
    }

[ 引入模块中的某些方法 ]

cosnt {sourceMapConsumer,SourceNode} = require('source-map')