JS 变量

ES5 基础

变量是存储信息的容器。命名规则如下:
1.变量名由字母数字$_ 组成。
2.变量名必须不能以数字开头
3.变量名称对大小写敏感(y 和 Y 是不同的变量)

声明(创建) JavaScript 变量:

var name;              //未赋值的变量,其实际值是:undefined
name = "aaa";          //使用等号向变量赋值
var value = "bbb";     //声明变量时对其赋值

age = 234//未声明的变量直接赋值,默认为全局变量: var age=234;

一条语句,多个变量:

var name,age,job;                        
var name="Gates", age, job="CEO";      

变量作用域
变量分全局变量和局部变量,全局变量可在任何地方被调用,局部变量只能作用于局部。全局变量会一直存在,直到页面关闭或手动清除。局部变量一般在函数内部,当函数执行完毕后就会被销毁释放内存。

var a = 1;               // a 是全局变量,任何地方可调用
if(true){
  var b = 2;             // b 是全局变量,任何地方可调用
}
function run(){
  var c = 3;             // c 是局部变量,只能在函数内部调用
  console.log(a,b,c);
}

run();                   // 1 2 3    
console.log(a);          // 1 
console.log(b);          // 2
console.log(c);          // 报错!c is not defined( c未定义 ) 

变量提升现象
JS执行时会将变量声明提到最前面执行,赋值操作按书写顺序执行。

console.log(1);
var name = "Le";

//上面这两句代码实际执行顺序如下:
var name;
console.log(1);
name = "Le";

所以,一个好的编程习惯是,在代码开始处,统一对需要的变量进行声明。

看下面2个函数:

aaa();   //1
bbb();   //报错,bbb 不是一个函数

function aaa(){          //声明一个函数变量
  console.log(1)
}
var bbb = function(){    //声明一个变量 bbb 赋值为函数
  console.log(1)
}

作用域示例1:

var a = 2;
function fn(){
  console.log(a);
  var a = 5;
} 
fn();                     //输出: undefined

/*说明:上面函数在最终被解析为
function(){
    var a;                //声明变量默认提到该作用域的最前面
    console.log(a);
    a = 5;                //赋值留在原地
}  
*/

作用域示例2:

function fn(){
  var a = b = c = 6;
  console.log(a);        //6
  console.log(b);        //6
  console.log(c);        //6
}
fn();
console.log(a);          //undefined
console.log(b);          //6
console.log(c);          //6

/*说明: 上面函数最终解析为
function fn(){
  var a ;
  a = 6;
  b = 6;         //未声明的默认是全局变量
  c = 6;         //未声明的默认是全局变量
   ......
}  
*/

ES6 新增变量声明

ES5 只有全局作用域和函数作用域,没有块级作用域, ES6 增加的块级作用域:

  • let 命令用来声明变量
  • const 命令用来声明常量

const,let 与 var 的不同之处:

  • let,const 声明的变量,常量只在代码块内有效
  • let,const 声明的变量,常量不能再重复声明
  • let,const 声明的变量,常量不会提升, 存在暂时性死区,只能在声明的位置后面使用
  • let,const 声明的全局变量,全局常量不属于顶层对象属性(例浏览器环境中不属于 window 的属性)

const 与 let 的不同之处:

  • const 声明的常量必须立刻初始化赋值,不能留到后面再赋值
  • const 声明的常量初始化赋值后不能更改(对于简单类型的数据:数值、字符串、布尔值)

ES6 规定暂时性死区和let、const语句不出现变量提升,主要是为了减少运行时错误,防止在变量声明前就使用这个变量,从而导致意料之外的行为。这样的错误在 ES5 是很常见的,现在有了这种规定,避免此类错误就很容易了。

ES5 只有两种声明变量的方法: var 命令和 function 命令。
ES6 除了添加letconst命令,另外两种声明变量的方法:import 命令和 class 命令。所以,ES6 一共有 6 种声明变量的方法。

不允许重复声明
let,const不允许在相同作用域内,重复声明同一个变量。

//例1:
var a = 0;
let a = 1;                // 报错

//例2:
let a = 0;
let a = 1;                // 报错

//例3:
function func(arg) {
  let arg;                // 报错
}
function func(arg) {
  {
    let arg;              // 不报错
  }
}

块级作用域
ES6 允许块级作用域的任意嵌套。外层作用域无法读取内层作用域的变量。

let a = 0;
{
  var b = 1;
  let c = 2;
}
console.log(a)      // 0
console.log(b)      // 1
console.log(c)      // 报错 'c is not defined'
{{{
  let a = 0;
  let b = 1;
  {
    let a = 0;
    let c = 2;
    console.log(b)        //输出 1
  }
  console.log(c)          //报错
}}}

块级作用域的出现,实际上使得获得广泛应用的立即执行函数表达式(IIFE)不再必要了。

// IIFE 写法
(function () {
  var tmp = 0;
  console.log(tmp)
})();

// 块级作用域写法
{
  var tmp = 0;
  console.log(tmp)
}

for循环的计数器,就很合适使用let命令

//下面for循环中的变量 i 是 var 命令声明的,在全局范围内都有效,所以全局只有一个变量i
var a = [];
for (var i = 0; i < 10; i++) {      
  a[i] = function(){
    console.log(i)
  }
}
a[6]();                //10
console.log(i);        //10

//下面for循环中的变量 g 是 let 声明的,只在本轮循环有效,每次循环的 g 都是一个新的变量
var b = [];
for (let g = 0; g < 10; g++) {     
  b[g] = function(){
    console.log(g)
  }
}
b[6]();                //6
console.log(g);        //报错: g is not defined

for循环还有一个特别之处,就是设置循环变量的那部分是一个父作用域,而循环体内部是一个单独的子作用域。

for (let i = 0; i < 3; i++) {
  let i = 'abc';    // 不会报错
  console.log(i);   // 三次结果都输出: abc 
}

let,const 命令不会发生”变量提升“现象,它所声明的变量一定要在声明后使用。

console.log(foo);          // 输出 undefined
var foo = 2;

console.log(bar);          // 报错 ReferenceError
let bar = 2;

暂时性死区
只要块级作用域内存在let,const命令,它所声明的变量就“绑定”(binding)这个区域,不再受外部的影响。

//例1:
var tmp = 123;
if (true) {
  tmp = 'abc';             // 报错 ReferenceError
  let tmp;
}

//例2:“暂时性死区”也意味着typeof不再是一个百分之百安全的操作。如下 aaa, bbb 都未声明
console.log(typeof aaa)     // undefined
console.log(typeof bbb)     // 报错 ReferenceError
let bbb;

//例3:
let x = x;                 // 报错 ReferenceError

块级作用域与函数声明
ES5 规定,函数只能在顶层作用域和函数作用域之中声明,不能在块级作用域声明。但是,浏览器没有遵守这个规定,为了兼容以前的旧代码,还是支持在块级作用域之中声明函数。下面两种函数声明,根据 ES5 的规定都是非法的,但都能运行,不会报错。

// 情况一
if (true) {
  function f() {}
}

// 情况二
try {
  function f() {}
} catch(e) {
  // ...
}

应该避免在块级作用域内声明函数。如果确实需要,也应该写成函数表达式,而不是函数声明语句。

// 函数声明语句 (不建议)
{
  let a = 'secret';
  function f() {
    return a;
  }
}

// 函数表达式 (建议)
{
  let a = 'secret';
  let f = function () {
    return a;
  };
}

顶层对象
顶层对象,在浏览器环境指的是window对象,在 Node 指的是global对象。ES5 之中,顶层对象的属性与全局变量是等价的。顶层对象的属性与全局变量挂钩,被认为JavaScript 语言最大的设计败笔之一。

ES6 为了改变这一点,一方面规定,为了保持兼容性,var命令和function命令声明的全局变量,依旧是顶层对象的属性;另一方面规定,let命令、const命令、class命令声明的全局变量,不属于顶层对象的属性。也就是说,从 ES6 开始,全局变量将逐步与顶层对象的属性脱钩。

a = 1;
console.log( window.a )       // 1

let b = 1;
console.log( window.b )       // undefined

ES5 的顶层对象,本身也是一个问题,因为它在各种实现里面是不统一的。

–浏览器里面,顶层对象是window,但 Node 和 Web Worker 没有window。
–浏览器和 Web Worker 里面,self也指向顶层对象,但是 Node 没有self。
–Node 里面,顶层对象是global,但其他环境都不支持。

同一段代码为了能够在各种环境,都能取到顶层对象,现在一般是使用this变量,但是有局限性。

–全局环境中,this会返回顶层对象。但是,Node 模块和 ES6 模块中,this返回的是当前模块。
–函数里面的this,如果函数不是作为对象的方法运行,而是单纯作为函数运行,this会指向顶层对象。但是,严格模式下,这时this会返回undefined。
–不管是严格模式,还是普通模式,new Function(‘return this’)(),总是会返回全局对象。但是,如果浏览器用了 CSP(Content Security Policy,内容安全策略),那么eval、new Function这些方法都可能无法使用。

综上所述,很难找到一种方法,可以在所有情况下,都取到顶层对象。下面是两种勉强可以使用的方法。

// 方法一
(typeof window !== 'undefined'
   ? window
   : (typeof process === 'object' &&
      typeof require === 'function' &&
      typeof global === 'object')
     ? global
     : this);

// 方法二
var getGlobal = function () {
  if (typeof self !== 'undefined') { return self; }
  if (typeof window !== 'undefined') { return window; }
  if (typeof global !== 'undefined') { return global; }
  throw new Error('unable to locate global object');
};

常量
const 声明一个只读的常量。必须立即初始化,不能留到以后赋值。一旦声明,常量的值就不能改变。

const aaa = 0;
aaa = 1;             // 报错(值不能更改)
const bbb;           // 报错(必须初始化)

但是,不能控制复合类型的数据(主要是对象和数组)的改变。

const foo = {};

// 为 foo 添加一个属性,可以成功
foo.prop = 123;       

// 将 foo 指向另一个对象,就会报错
foo = {};      // TypeError: "foo" is read-only

冻结一个对象的需使用 Object.freeze 方法。

const obj = Object.freeze({});

// 常规模式时无效
// 严格模式时报错
obj.num = 555;

下面函数用于将对象彻底冻结(冻结对象本身及属性)

var constObj = (obj) => {
  Object.freeze(obj);
  Object.keys(obj).forEach( (key, i) => {
    if ( typeof obj[key] === 'object' ) {
      constObj ( obj[key] );
    }
  });
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值