文章目录
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 除了添加let
和const
命令,另外两种声明变量的方法: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] );
}
});
};