es6变量声明和解构赋值
/*声明:
* 本文内容多为学习借鉴性内容,大部分非原创
* 特别感谢阮一峰的 ECMAScript6 入门,推荐大家学习
*/
一.es5变量声明的不足
1.变量提升和函数声明提升
es5的代码加载顺序是自上而下的但是 var和function(不包含函数表达式)的声明,(不包含赋值),会得到优先加载,加载时提升到作用域的top位置
函数内的变量声明会提升到函数内部代码的顶部,优先运行;
并且变量的值为undefined;
(1)栗子1
var time = new Date();
function tm(){ if (false) {
var time = "aa";
};
console.log(time)
}
tm();
// 在控制台输出的是 undefined ,这是因为var 声明的变量会先提升
上述代码等价于一下内容
var time = new Date();
function tm(){
//var time; //函数内局部环境下声明会覆盖全局作用域,同时if内的声明得到提升到function的top位置,但此时time仅仅是声明提升,并没有赋值,所以运行console时输出unidefined;
if (false) {
var time = "aa";
};
console.log(time)
}
tm();
// 在控制台输出的是 undefined ,这是因为var 声明的变量会先提升
(2)栗子2
fn();
console.log(a);
var a = 0;
console.log(a); function fn(){
var a = 1;
}
//控制台输出 undefined和0
此处,函数声明先得到提升,然后var a=0;变量声明得到提升(但此时并没有变量赋值,函数体内的为局部变量),如下代码
fn();
var a; //此处a是由 var a=0;提升而来;
console.log(a);
var a = 0;
console.log(a); function fn(){
var a = 1;
}
//控制台输出 undefined和0
(3)栗子3
fn()
alert(a)
var a = 0;
alert(a);
function fn(){
a = 1;
}
//此处控制台输出的是 1 和 0;
//因为 a=1;没有关键词直接赋值使用在非严格模式下默认挂到window环境下,优先执行了声明和赋值;
//但是在严格模式下这种写法是错误的;
(4)栗子4
重复声明的变量
在同一作用域下,后声明的变量会覆盖之前声明的变量
不同作用域下局部变量会覆盖外层变量(外层为window则覆盖全局变量)
此处,不举例;
二.es6的let
1.同一作用域下的let 声明的变量是不能被覆盖的,即不允许重复声明
(1)栗子1
{
let a = 1;
let a = 2;
}
//Identifier 'a' has already been declared
2.es6新增的 let 命令所声明的变量只在let命令所在的代码块内有效 --- 块级作用域
(1)栗子1
{
let a=1;
var b = 22;
}
console.log(b) //22;
console.log(a) //报错 Uncaught ReferenceError: a is not defined
(2)栗子2,
在for循环中十分适合使用let
for(let i = 0; i<15;i++){
console.log("inner"+i) //inner 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14
}
console.log("outer"+i) //Uncaught ReferenceError: i is not defined 这样变量i不会覆盖外层的变量
(2)栗子3,
闭包问题的解决
<!-- 在页面添加三个按钮 -->
<input type="button" value="1">
<input type="button" value="2">
<input type="button" value="3">
<script>
// 获取页面所有的input
var aBtn = document.getElementsByTagName('input');
// 循环绑定点击事件,当然毫无疑问这里点击之后会弹出3,
//因为我们知道js是单线程的,当基本逻辑语句执行完之后,才会执行点击事件
//变量i
是var
命令声明的,在全局范围内都有效,所以全局只有一个变量i
。
//每一次循环,变量i
的值都会发生改变,而循环内被赋给数组aBtn
的函数内部的alert(i)
,里面的i
指向的就是全局的i
。
也就是说,所有数组a
的成员里面的i
,指向的都是同一个i
,导致运行时输出的是最后一轮的i
的值,也就是3。
//当你触发点击事件的时候,for循环都已经跑完了,所以i已经变成了3;
for(var i = 0; i < aBtn.length; i++){
aBtn[i].onclick = function(){
alert(i);
}
}
// 解决方法1:也是最简单的方法,就是将for循环的var变成let
//这样当点击每个按钮的时候,就会依次弹出0,1,2;
//let是ES6新增的一个变量声明方式,拥有块级作用域;
//变量i
是let
声明的,当前的i
只在本轮循环有效,所以每一次循环的i
其实都是一个新的变量,所以最后输出的是6
。
// JavaScript 引擎内部会记住上一轮循环的值,初始化本轮的变量i
时,就在上一轮循环的基础上进行计算
for(let i = 0; i < aBtn.length; i++){
aBtn[i].onclick = function(){
alert(i);
}
} //解决方法2:利用闭包(说是闭包,貌似也不完全是,也就是函数作用域访问原则的特性
//函数内部可以访问外部的变量,外部却访问不了里边的;
for(var i = 0; i < aBtn.length; i++){
(function(i){
aBtn[i].onclick = function(){
alert(i);
}
})(i);
}
</script>
(4)栗子4,
for的两个作用域
for (let i = 0; i < 3; i++) {//就是设置循环变量的那部分是一个父作用域
let i = 'abc'; //而循环体内部是一个单独的子作用域
console.log(i); }
// abc
// abc
// abc
for (let i = 0; i < 3; i++) {
console.log(i);
}
// 0
// 1
// 2
3.不存在变量提升
var 命令会发生"变量提升" 的现象,即变量可以在声明之前使用,值为undefined;
而 let 要求更严格,let命令要求它所声明的变量一定要在声明后使用,否则报错;
(1)栗子1
//使用var
console.log(a); //输出undefined
var a = 1; //使用let
console.log(b); //此处会报错 Uncaught ReferenceError: b is not defined
//这表示在声明它之前,变量bar
是不存在的,这时如果用到它,就会抛出一个错误。
let b = 2;
***
注:以下很多问题可以通过let变量 "先声明后使用" 来进行理解
***
4.暂时性死区
* 只要块级作用域内存在let
命令,它所声明的变量就“绑定”(binding)这个区域,不再受外部的影响 *
(1)栗子1
var tmp = 123; if (true) {
tmp = 'abc'; //Uncaught ReferenceError:tmp is not defined
let tmp;
}
上面代码存在全局变量tmp ,本来可以正常运行的;但是在块级作用域内let又声明了一个局部变量 tmp 导致 let 将tmp绑定在这个区域内,(并非整个作用域,从使用let到let所在作用域的顶部的区域)
所以tmp在let声明前就调用了,对tmp的赋值运算就会报错
ES6明确规定,如果区块中存在let
和const
命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错。
总之,在代码块内,使用let
命令声明变量之前,该变量都是不可用的。这在语法上,称为“暂时性死区”(temporal dead zone,简称 TDZ)。
(2)栗子2
暂时性死区的范围
if (true) {
// TDZ开始
tmp = 'abc'; // ReferenceError
console.log(tmp); // ReferenceError let tmp; // TDZ结束
console.log(tmp); // undefined tmp = 123;
console.log(tmp); // 123
}
虽然TDZ结束了但是在TDz开始时就已经报错,程序已经无法正常运行了
(3)栗子3
typeof 会报错
在没有let
之前,typeof
运算符是百分之百安全的,永远不会报错
“暂时性死区”也意味着typeof
不再是一个百分之百安全的操作
//暂时性死区导致报错
typeof x; // ReferenceError
let x; //没有暂时性死区 使用typeof检测一个未申明的变量也不会报错而是返回undefined
typeof un_defin_ed //undefined
(4)栗子4
比较隐蔽的暂时性死区
//使用var的情况
var x=x;
console.log(x); //undefined //使用let的情况
let x = x; //ReferenceError : x is not defined
console.log(x);
//解构赋值
function bar(x = y, y = 2) {
return [x, y];
} bar(); // 报错
上面代码中,调用bar
函数之所以报错(某些实现可能不报错),是因为参数x
默认值等于另一个参数y
,而此时y
还没有声明,属于”死区“。如果y
的默认值是x
,就不会报错,因为此时x
已经声明了。
5.块级作用域
let
实际上为 JavaScript 新增了块级作用域。
外层作用域无法读取内层作用域的变量 , 这表示外层代码块不受内层代码块的影响。
内层作用域可以定义外层作用域的同名变量 , 内层作用域可以使用外层作用域的变量。
(1)栗子1
块级作用域的出现,实际上使得获得广泛应用的立即执行函数表达式( IIFE )不再必要了
// IIFE 写法
(function () {
var tmp = ...;
...
}()); // 块级作用域写法
{
let tmp = ...;
...
}
(2)栗子2
函数能否在块级作用域内声明;
ES5规定函数只能在顶层作用域和函数作用域中声明,不能再块级作用域中声明(不包括函数表达式);
// 情况一
if (true) {
function f() {}
} // 情况二
try {
function f() {}
} catch(e) {
// ...
}
上面两种函数声明,根据 ES5 的规定都是非法的。
但是,浏览器没有遵守这个规定,为了兼容以前的旧代码,还是支持在块级作用域之中声明函数,因此上面两种情况实际都能运行,不会报错。
function f() { console.log('I am outside!'); } (function () {
if (false) {
// 重复声明一次函数f
function f() { console.log('I am inside!'); }
} f();
}());
//上面代码在 ES5 中运行,会得到“I am inside!”,因为在if内声明的函数f会被提升到函数头部,实际运行的代码如下。 // ES5 环境
function f() { console.log('I am outside!'); } (function () {
function f() { console.log('I am inside!'); }
if (false) {
}
f();
}());
ES6 就完全不一样了,理论上会得到“I am outside!”。因为块级作用域内声明的函数类似于let,对作用域之外没有影响。但是,如果你真的在 ES6 浏览器中运行一下上面的代码,是会报错的,这是为什么呢? 原来,如果改变了块级作用域内声明的函数的处理规则,显然会对老代码产生很大影响。为了减轻因此产生的不兼容问题,ES6在附录B里面规定,浏览器的实现可以不遵守上面的规定,有自己的行为方式。 允许在块级作用域内声明函数。
函数声明类似于var,即会提升到全局作用域或函数作用域的头部。
同时,函数声明还会提升到所在的块级作用域的头部。
注意,上面三条规则只对 ES6 的浏览器实现有效,其他环境的实现不用遵守,还是将块级作用域的函数声明当作let处理。 根据这三条规则,在浏览器的 ES6 环境中,块级作用域内声明的函数,行为类似于var声明的变量。
// 浏览器的 ES6 环境
function f() { console.log('I am outside!'); } (function () {
if (false) {
// 重复声明一次函数f
function f() { console.log('I am inside!'); }
} f();
}());
// Uncaught TypeError: f is not a function
上面的代码在符合 ES6 的浏览器中,都会报错,因为实际运行的是下面的代码。 // 浏览器的 ES6 环境
function f() { console.log('I am outside!'); }
(function () {
var f = undefined;
if (false) {
function f() { console.log('I am inside!'); }
} f();
}());
// Uncaught TypeError: f is not a function
考虑到环境导致的行为差异太大,应该避免在块级作用域内声明函数。如果确实需要,也应该写成函数表达式,而不是函数声明语句。
// 函数声明语句
{
let a = 'secret';
function f() {
return a;
}
} // 函数表达式
{
let a = 'secret';
let f = function () {
return a;
};
}
还有一个需要注意的地方。ES6 的块级作用域允许声明函数的规则,只在使用大括号的情况下成立,如果没有使用大括号,就会报错。
// 报错
'use strict';
if (true)
function f() {}
三.const 命令
(一)基本特性
const
声明一个只读的常量。一旦声明,常量的值就不能改变。
const
声明的变量不得改变值,这意味着,const
一旦声明变量,就必须立即初始化,不能留到以后赋值。
而且对于const
来说,只声明不赋值,就会报错。
const bntt;
Uncaught SyntaxError: Missing initializer in const declaration
const 声明变量和let有相似之处 :
变量声明不提升,
暂时性死区,块级作用域,
不可重复声明.
(二)本质
const
实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址不得改动。
对于简单类型的数据(数值、字符串、布尔值),值就保存在变量指向的那个内存地址,因此等同于常量。
但对于复合类型的数据(主要是对象和数组),变量指向的内存地址,保存的只是一个指针,
const
只能保证这个指针是固定的,至于它指向的数据结构是不是可变的,就完全不能控制了。因此,将一个对象声明为常量必须非常小心。
const foo = {}; // 为 foo 添加一个属性,可以成功
foo.prop = 123;
foo.prop // 123 // 将 foo 指向另一个对象,就会报错
foo = {}; // TypeError: "foo" is read-only Assignment to constant variable.
上述代码中常量foo储存的是一个地址,这个地址指向一个队形.不可变的是这个地址,即不能把foo指向另一个地址,但是对象本身是可变的,所以可以为其添加新的属性;
//这个栗子原理同上
coonst a = [];
a.push("aa") ;//可执行
a.length = 0; //可执行
a=["bb"] ; //报错
(三) 冻结对象
Object.freeze()
方法可以冻结一个对象(浅冻结),
冻结指的是不能向这个对象添加新的属性,
不能修改其已有属性的值,
不能删除已有属性,
以及不能修改该对象已有属性的可枚举性、可配置性、可写性。
也就是说,这个对象永远是不可变的。该方法返回被冻结的对象。
语法 Object.freeze(obj)
//下面是一个将对象彻底冻结的函数。 var constantize = (obj) => {
Object.freeze(obj);
Object.keys(obj).forEach( (key, i) => {
if ( typeof obj[key] === 'object' ) {
constantize( obj[key] );
}
});
};
四.顶层对象的属性
1.顶层对象,在浏览器环境指的是window
对象,在 Node 指的是global
对象。ES5 之中,顶层对象的属性与全局变量是等价的。
2.
顶层对象的属性与全局变量挂钩,被认为是 JavaScript 语言最大的设计败笔之一。这样的设计带来了几个很大的问题,首先是没法在编译时就报出变量未声明的错误,只有运行时才能知道(因为全局变量可能是顶层对象的属性创造的,而属性的创造是动态的);其次,程序员很容易不知不觉地就创建了全局变量(比如打字出错);最后,顶层对象的属性是到处可以读写的,这非常不利于模块化编程。另一方面,window
对象有实体含义,指的是浏览器的窗口对象,顶层对象是一个有实体含义的对象,也是不合适的。
ES6 为了改变这一点,一方面规定,为了保持兼容性,var
命令和function
命令声明的全局变量,依旧是顶层对象的属性;另一方面规定,let
命令、const
命令、class
命令声明的全局变量,不属于顶层对象的属性。也就是说,从 ES6 开始,全局变量将逐步与顶层对象的属性脱钩。
var a = 1;
// 如果在 Node 的 REPL 环境,可以写成 global.a
// 或者采用通用方法,写成 this.a
window.a // 1 let b = 1;
window.b // undefined
上面代码中,全局变量a由var命令声明,所以它是顶层对象的属性;全局变量b由let命令声明,所以它不是顶层对象的属性,返回undefined。
3.
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');
};
es6变量声明和解构赋值的更多相关文章
- ES6 变量的解构赋值
数组的解构赋值 var [a,b,c] = [1,2,3]; 左边是变量,右边是值,根据数据结构一一对应 只要等号两边的模式相同,左边的变量就会被赋予右边对应的值,必须模式相同 如果等号 ...
- es6变量的解构赋值学习笔记
1. 解构赋值的规则是,只要等号右边的值不是对象,就先将其转为对象.由于undefined和null无法转为对象,所以对它们进行解构赋值,都会报错. let { prop: x } = undefin ...
- ES6 - 变量的解构赋值学习笔记
变量的解析赋值 数组的解析赋值 es6允许按照一定的模式,从数组和对象中提取值,对变量进行赋值,这就称为解构. var a = 1; var b = 2; var c = 3; //在es6中允许写成 ...
- ES6变量的解构赋值
变量的解构赋值 1.数组的解构赋值 2.对象的解构赋值 3.字符串的解构赋值 4.数值和布尔值的解构赋值 5.函数参数的解构赋值 6.圆括号问题 7.用途 1.数组的解构赋值 ES6 允许写成下面这样 ...
- es6—变量的解构赋值
数组的解构赋值 ]]]]]]] = []}} = {}} = {}})]: first]: last} = arr} = {}) {}))}))}) {}))}))].]]]])})] }}} = { ...
- ES6变量的解构赋值(一)数组的解构赋值
let[a,...arr]=[1,2,3,4];//a==>1 arr==>[2,3,4] let [x, y, ...z] = ['a'];//a==>'a' y==>und ...
- ES6变量的解构赋值(二)对象的解构赋值
前面我们知道,数组的结构赋值需要按顺序进行赋值, let [a,,c] = [,,] console.log(a); console.log(c);//3 let [a,b] = [1];consol ...
- ES6中的变量的解构赋值, 解放我们的双手,实现变量的批量赋值
ES6--变量的解构赋值 引言 正文 一.数组的解构赋值 解构失败 不完全解构 默认值 二.对象的解构赋值 三.字符串的解构赋值 结束语 引言 变量的解构赋值, 听起来很复杂, 简单点说可以理解成批量 ...
- ES6 学习笔记(一)let,const和解构赋值
let和const let和const是es6新增的两个变量声明关键字,与var的不同点在于: (1)let和const都是块级作用域,在{}内有效,这点在for循环中非常有用,只在循环体内有效.va ...
随机推荐
- Linux文件
Linux文件类型 对于内核而言,所有打开的文件都是通过文件描述符引用(FD),文件描述符是一个非负整数,当打开现有问价或创建一个新文件时,内核向进程返回一个文件描述符. 按照惯例,shell把文件描 ...
- jQuery——动态给表格添加序号
摘录自:http://www.cnblogs.com/picaso/archive/2012/10/08/2715564.html 很多时候遇到需要对表格动态操作,而且一般都会有表格的序号,但是有时候 ...
- ASPNET 5 和 dnx commands
DNX项目是用来创建和运行.net应用程序适用于windows,mac 和linux 的,dnx提供了一个宿主进程(a host process),CLR托管逻辑( CLR hosting logic ...
- 2017-06-24(chgrp umask alias unalias)
chgrp chgrp 组名 文件名 chgrp root newfile 将newfile的所属组修改为root umask umask 查看默认权限 0 022 文件的特殊权限 文件的默认 ...
- scrapy_开发环境
scrapy开发所具备的环境 IDE pycharm 数据库 mysql, redis 开发环境 python 3.5
- 转-HTTP POST GET SOAP本质区别详解
原文链接:HTTP POST GET SOAP本质区别详解 一 原理区别 一般在浏览器中输入网址访问资源都是通过GET方式:在FORM提交中,可以通过Method指定提交方式为GET或者POST,默认 ...
- Python用户输入和代码注释
一.用户输入 若你安装的是Python3.x版本,当你在Python IDLE(编辑器) 中输入以下代码: name = input('用户名:') print('Hello',name) 保存并执行 ...
- 【转】 C++易混知识点2. 函数指针和指针函数的区别
我们时常在C++开发中用到指针,指针的好处是开销很小,可以很方便的用来实现想要的功能,当然,这里也要涉及到指针的一些基本概念.指针不是基本数据类型,我们可以理解他为一种特殊类型的对象,他占据一定空间, ...
- 2、jQuery的一些静态方法
上次粗略说了jQuery的整体结构,这次挑一些静态方法先说一下吧 一.noConflict函数 这个函数是个比较有意思的函数,基本上很少用到,之所以说他是因为这个函数在最下面,太显眼了,先把他解决掉. ...
- mongodb监控常用方法
列举mongodb监控的常用命令 1.监控统计 mongostat 可用于查看当前QPS/内存使用/连接数,以及多个shard的压力分布 命令参考 ./mongostat --port 27071 - ...