一、let声明变量
1、基本用法:
ES6 新增了let命令,用来声明变量。它的用法类似于var,但是所声明的变量,只在let命令所在的代码块内有效。
如下代码:
{
let a = 10;
var b = 1;
}
a // ReferenceError: a is not defined.
b // 1
上面代码在代码块之中,分别用let和var声明了两个变量。然后在代码块之外调用这两个变量,结果let声明的变量报错,var声明的变量返回了正确的值。这表明,let声明的变量只在它所在的代码块有效。
还有我们经常会遇到的坑:for循环结合定时器的使用,如下代码:
{
let a = 10;
var b = 1;
}
a // ReferenceError: a is not defined.
b // 1
如果换成let,则是:
for (var i=0; i<5; i++) {
setTimeout(function () {
console.log(i);
}, 0);
} // 5 5 5 5 5
以上两段代码出现不同结果的原因就是var声明的是全局变量,就算setTimeout设置的是0ms,但是也是等for循环内的全局变量i执行到5时,才会执行,因此每次打印的结果都是5。而let声明的变量只在其所在代码块内有效,也就是说每次for循环都保存了一次i的值,因此最后依次打印出0、1、2、3、4。
同样的:《JavaScript中闭包结合for循环的使用》也是这个原因。
2、不存在变量提升:
通过var声明的变量,会出现变量提升的情况,这是因为程序是从上到下执行,执行之前先声明,代码如下:
1 |
console.log(a); // undefined |
|
但是let声明的变量不存在变量提升:
1 |
console.log(a); // ReferenceError: a is not defined |
|
想要使用let声明的变量,在使用之前必须先声明,否则会报错。
相关阅读:《JavaScript闭包+作用域+变量提升》。
3、暂时性死区:
只要块级作用域内存在let命令,它所声明的变量就“绑定”(binding)这个区域,不再受外部的影响,如下代码:
4 |
tmp = 'abc' ; // ReferenceError |
|
上面代码中,存在全局变量tmp,但是块级作用域内let又声明了一个局部变量tmp,导致后者绑定这个块级作用域,所以在let声明变量前,对tmp赋值会报错。
ES6明确规定,如果区块中存在let和const命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错。
总之,在代码块内,使用let命令声明变量之前,该变量都是不可用的。这在语法上,称为“暂时性死区”(temporal dead zone,简称 TDZ)。
4、不允许重复声明同一个变量:
let不允许在相同作用域内,重复声明同一个变量。如下代码:
因此,不能在函数内部重新声明参数。
之前有个小习惯,发生一个事件的时候,有时候要用到事件对象中的属性,就习惯了在事件函数内重复声明参数,如下代码:
1 |
var btn = document.getElementById( 'btn' ); |
2 |
btn.onclick = function (ev) { |
3 |
var ev = ev || window.event; // 重复声明参数,如果是let是不允许的 |
4 |
ev.cancelBubble = true ; |
|
二、const声明常量
const声明一个只读的常量。一旦声明,常量的值就不能改变。
1 |
const Work = 'WEB前端开发' ; |
2 |
console.log(Work); // WEB前端开发 |
5 |
// TypeError: Assignment to constant variable. |
|
上面代码表明改变常量的值会报错。
const声明的变量不得改变值,这意味着,const一旦声明变量,就必须立即初始化,不能留到以后赋值。
2 |
// SyntaxError: Missing initializer in const declaration |
|
上面代码表示,对于const来说,只声明不赋值,就会报错。
const命令声明的常量也是不提升,同样存在暂时性死区,只能在声明的位置后面使用,如下代码:
2 |
console.log(MAX); // ReferenceError |
|
const声明的常量,也与let一样不可重复声明。
const实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址不得改动。对于简单类型的数据(数值、字符串、布尔值),值就保存在变量指向的那个内存地址(栈内存),因此等同于常量。但对于复合类型的数据(主要是对象和数组),变量指向的内存地址(堆内存),保存的只是一个指针,const只能保证这个指针是固定的,至于它指向的数据结构是不是可变的,就完全不能控制了。因此,将一个对象声明为常量必须非常小心。
2 |
const person = '' ; // 报错 |
|
改变const常量对象的属性则不会报错,代码如下:
3 |
console.log(person); // Object {name: "赵一鸣"} |
|
三、块级作用域
1、为什么需要块级作用域呢?
ES5 只有全局作用域和函数作用域,没有块级作用域,这带来很多不合理的场景。
第一种场景,内层变量可能会覆盖外层变量,代码如下:
06 |
var tmp = 'hello world' ; |
|
以上代码,在函数f内外都声明了tmp变量,因此在函数内部会优先读取局部变量tmp,但是在if内判断为false,不会继续执行,再加上变量提升的原因,最后打印结果是undefined。
第二种场景,用来计数的循环变量泄露为全局变量,代码如下:
3 |
for ( var i = 0; i < s.length; i++) { |
|
上面代码中,变量i只用来控制循环,但是循环结束后,它并没有消失,泄露成了全局变量。
let实际上为 JavaScript 新增了块级作用域。
上面的函数有两个代码块,都声明了变量n,运行后输出5。这表示外层代码块不受内层代码块的影响。如果两次都使用var定义变量n,最后输出的值才是10。
ES6 允许块级作用域的任意嵌套,如下代码:
1 |
{{{{{let insane = 'Hello World' }}}}}; |
|
2、块级作用域与函数声明:
ES5 规定,函数只能在顶层作用域和函数作用域之中声明,不能在块级作用域声明,如下代码:
上面两种函数声明,根据 ES5 的规定都是非法的。
但是,浏览器没有遵守这个规定,为了兼容以前的旧代码,还是支持在块级作用域之中声明函数,因此上面两种情况实际都能运行,不会报错。
ES6 引入了块级作用域,明确允许在块级作用域之中声明函数。ES6 规定,块级作用域之中,函数声明语句的行为类似于let,在块级作用域之外不可引用。
ES6 的块级作用域允许声明函数的规则,只在使用大括号的情况下成立,如果没有使用大括号,就会报错,如下代码:
四、顶层对象的属性
在全局环境下声明一个变量,这个变量实际是作为window全局对象的属性,代码如下:
2 |
console.log(window.name); // 赵一鸣 |
|
顶层对象的属性与全局变量挂钩,被认为是JavaScript语言最大的设计败笔之一。这样的设计带来了几个很大的问题,首先是没法在编译时就报出变量未声明的错误,只有运行时才能知道(因为全局变量可能是顶层对象的属性创造的,而属性的创造是动态的);其次,程序员很容易不知不觉地就创建了全局变量(比如打字出错);最后,顶层对象的属性是到处可以读写的,这非常不利于模块化编程。另一方面,window对象有实体含义,指的是浏览器的窗口对象,顶层对象是一个有实体含义的对象,也是不合适的。
ES6为了改变这一点,一方面规定,为了保持兼容性,var命令和function命令声明的全局变量,依旧是顶层对象的属性;另一方面规定,let命令、const命令、class命令声明的全局变量,不属于顶层对象的属性。也就是说,从ES6开始,全局变量将逐步与顶层对象的属性脱钩,如下代码:
2 |
console.log(window.name); // undefined |
|
五、变量的解构赋值
1、ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构(Destructuring)。
以前,为变量赋值,只能直接指定值,代码如下:
ES6 允许写成下面这样:
1 |
let [a, b, c] = [1, 2, 3]; |
|
本质上,这种写法属于“模式匹配”,只要等号两边的模式相同,左边的变量就会被赋予对应的值。下面是一些使用嵌套数组进行解构的例子。
1 |
let [a, [b], c] = [1, [2], 3]; |
|
如果解构不成功,变量的值就等于undefined,如下代码:
以上两种情况都属于解构不成功,foo的值都会等于undefined。
2、解构赋值允许指定默认值:
1 |
let [a=1, b=2] = [3, 4]; |
|
3、对象的解构赋值:
解构不仅可以用于数组,还可以用于对象。
1 |
let { foo, bar } = { foo: "aaa" , bar: "bbb" }; |
|
对象的解构与数组有一个重要的不同。数组的元素是按次序排列的,变量的取值由它的位置决定;而对象的属性没有次序,变量必须与属性同名,才能取到正确的值。
1 |
let { bar, foo } = { foo: "aaa" , bar: "bbb" }; |
5 |
let { baz } = { foo: "aaa" , bar: "bbb" }; |
|
上面代码的第一个例子,等号左边的两个变量的次序,与等号右边两个同名属性的次序不一致,但是对取值完全没有影响。第二个例子的变量没有对应的同名属性,导致取不到值,最后等于undefined。
如果变量名与属性名不一致,必须写成下面这样:
1 |
let {foo : baz} = {foo : 'aaa' , bar : 'bbb' }; |
|
这实际上说明,对象的解构赋值是下面形式的简写:
1 |
let { foo: foo, bar: bar } = { foo: "aaa" , bar: "bbb" }; |
|
4、字符串的解构赋值:
字符串也可以解构赋值。这是因为此时,字符串被转换成了一个类似数组的对象,代码如下:
1 |
const [a, b, c, d, e] = 'hello' ; |
|
类似数组的对象都有一个length属性,因此还可以对这个属性解构赋值:
1 |
let {length : len} = 'hello' ; |
|
5、函数参数的结垢赋值:
函数的参数也可以使用解构赋值:
上面代码中,函数add的参数表面上是一个数组,但在传入参数的那一刻,数组参数就被解构成变量x和y。对于函数内部的代码来说,它们能感受到的参数就是x和y。
6、变量的解构赋值有很多用途,例如:
(1)交换变量的值:
(2)从函数返回多个值:
函数只能返回一个值,如果要返回多个值,只能将它们放在数组或对象里返回。有了解构赋值,取出这些值就非常方便,代码如下:
06 |
let [a, b, c] = example(); |
16 |
let { foo, bar } = example(); |
|
(3)函数参数的定义:
解构赋值可以方便地将一组无序参数与变量名对应起来:
1 |
function fn1 ([a, b, c]) { |
4 |
fn1([1, 2, 3]); // 1 2 3 |
6 |
function fn2 ({a, b}) { |
9 |
fn2({b : 4, a : 5}); // 5 4 |
|
(4)提取JSON数据:
解构赋值对提取JSON对象中的数据,尤其有用,如下代码:
04 |
work: [ 'web前端开发' , 'SEO搜索引擎优化' ] |
07 |
let {name, sex, work} = person; |
08 |
console.log(name); // 赵一鸣 |
09 |
console.log(sex); // 男 |
10 |
console.log(work); // ["web前端开发", "SEO搜索引擎优化"] |
|
上面代码可以快速提取 JSON 数据的值。
(5)输入模块的指定方法:
加载模块时,往往需要指定输入哪些方法。解构赋值使得输入语句非常清晰,代码如下:
1 |
import {mapActions, mapGetters} from Vuex; |
|
六、关于数组的扩展
1、将dom集合(或者是类数组)转为数组:
1 |
var div = document.getElementsByTagName( 'div' ); |
2 |
var divArry = Array.from(div); |
3 |
console.log(divArry); // [div, div, div, div, div, div] |
|
2、接收一串参数,转为数组:
1 |
var arry = Array.of(1, 2, 3, 4); |
2 |
console.log(arry); // [1, 2, 3, 4] |
|
七、对象Object的扩展
1、合并对象,将obj2、obj3等对象的属性合并到obj1里边,类似于jQuery的$.extend({opt1, opt2, opt3})方法:
1 |
Object.assign(obj1, obj2, obj3); |
|
2、获取对象的prototype:
1 |
Object.getPrototypeOf(obj) |
|
八、函数的扩展
1、函数可以设置默认参数值:
1 |
function fn(a=1, b=2){ return a + b;} |
|
2、函数剩余参数组成的数组:
1 |
function fn (a, b, ...c) { |
4 |
console.log(fn(1, 2, 3, 4, 5, 6)); // 3 4 5 6 |
|
上面的代码,...c代表a和b参数后面所有的参数,是一个数组。
3、箭头函数:
箭头函数如果只有一行,可以省略大括号:
1 |
let fn = (a=1, b=2) => a + b; |
|
如果只有一个参数,并且没有默认值的时候,可以省略小括号:
需要注意的是:
(1)使用箭头函数定义的函数(构造函数),不能被new;
(2)箭头函数不存在arguments;
(3)this永远指向定义时所在的对象;
04 |
setTimeout( function () { |
|
九、Set:ES6新增的数据结构,类似于数组
SET内元素的值都是唯一的,如果加入了重复的值,它会自动去重。
1 |
let set = new Set([1, 1, '1' , 2, 3, '2' ]); |
2 |
console.log(set); // Set(5) {1, "1", 2, 3, "2"} |
3 |
console.log(set.size); // 5 |
5 |
console.log(set); // Set(5) {1, "1", 2, 3, "2", 5} |
6 |
set. delete ( '1' ); // 返回值的true或false |
7 |
console.log(set); // Set(5) {"1", 2, 3, "2", 5} |
9 |
console.log(set); // Set(0) {} |
|
十、Map:ES6新增的数据结构,类似键值对的对象,可以用object当作key,也可以用任意类型的数据当作key
01 |
let map = new Map([ [ 'name' , 'zym' ], [ 'age' , 24], [ 'sex' , 'man' ] ]); |
02 |
console.log(map); // Map(3) {"name" => "zym", "age" => 24, "sex" => "man"} |
03 |
console.log(map.size); // 3 |
04 |
map.set( 'work' , 'WEB前端开发' ); |
05 |
console.log(map); // Map(4) {"name" => "zym", "age" => 24, "sex" => "man", "work" => "WEB前端开发"} |
07 |
map.set(obj, 'this is a obj' ); |
08 |
console.log(map); // Map(5) {"name" => "zym", "age" => 24, "sex" => "man", "work" => "WEB前端开发", Object {} => "this is a obj"} |
09 |
console.log(map.has( 'height' )); //查找key是否存在,返回true或false |
11 |
console.log(map); // Map(4) {"name" => "zym", "sex" => "man", "work" => "WEB前端开发", Object {} => "this is a obj"} |
|
十一、promise 是一个对象,用来传递异步操作的消息。它代表了某个未来才会知道结果的事件(通常是一个异步操作),并且这个事件提供统一的 API,可供进一步处理
01 |
let p = new Promise((resolve, reject) => { |
11 |
result === 1 ? resolve(result) : reject( 'error' ); |
|
promise.all() 只有当所有Promise对象都为成功时才会执行then里面的方法:
01 |
let p1 = new Promise((resolve, reject) => { |
08 |
let p2 = new Promise((resolve, reject) => { |
15 |
Promise.all([p1, p2]).then((res) => { |
16 |
let a = res[0] > res[1] ? 1 : 0; |
|
十二、class类
constructor方法是类的默认方法,通过new命令生成对象实例时,自动调用该方法。一个类必须有constructor方法,如果没有显式定义,一个空的constructor方法会被默认添加。
定义一个空的类Point,JavaScript 引擎会自动为它添加一个空的constructor方法。
constructor方法默认返回实例对象(即this),完全可以指定返回另外一个对象。
3 |
return Object.create( null ); |
7 |
console.log( new Foo() instanceof Foo) // false |
|
例如以下一个完整的类:
03 |
constructor (name, age, sex) { |
05 |
// 类的内部通过new.target返回当前类 |
06 |
if(new.target === Person){ |
07 |
throw new Error('本类不能被实例化,只能被继承!'); |
17 |
return '(' + this .name + ')' ; |
30 |
// 自定义一个静态方法,只能由类本身来调用,实例化的对象不能调用,会报错。 在函数外部,使用new.target会报错。 |
36 |
let person = new Person( 'zym' , 24, 'man' ); |
37 |
console.log(person.constructor === Person.prototype.constructor); // true |
39 |
console.log(Object.getOwnPropertyNames(person)); |
40 |
console.log(Object.keys(person)); |
41 |
console.log(Object.getPrototypeOf(person)); |
42 |
console.log(Person.name); |
45 |
console.log(person.work); |
47 |
console.log(Person.run()); |
|
ES6中的class类只是对ES5中的构造函数做了一个封装,相当于外边包了一个壳子,看起来和其他编程语言的类一样。
类继承:
03 |
this .name = 'zhangsan' ; |
21 |
console.log(b.showName()); |
|
ES5的继承,实质是先创造子类的实例对象this,然后再将父类的方法添加到this上面(Parent.apply(this))。
ES6的继承机制完全不同,实质是先创造父类的实例对象this(所以必须先调用super方法),然后再用子类的构造函数修改this。如果子类没有定义constructor方法,这个方法会被默认添加。
(1)super作为函数时,指向父类的构造函数。super()只能用在子类的构造函数之中,用在其他地方就会报错:super虽然代表了父类的构造函数,但是返回的是子类的实例,即super内部的this指的是子类。因此super()在这里相当于A.prototype.constructor.call(this)。
(2)super作为对象时,指向父类的原型对象。
如果子类没有定义constructor方法, 这个方法会被默认添加, 也就是说, 不管有没有显式定义, 任何一个子类都有constructor方法。
在子类的构造函数中, 只有调用super之后, 才可以使用this关键字, 否则会报错。 这是因为子类实例的构建, 是基于对父类实例加工, 只有super方法才能返回父类实例。
十三、Module模块扩展
历史上,JavaScript 一直没有模块(module)体系,无法将一个大程序拆分成互相依赖的小文件,再用简单的方法拼装起来。其他语言都有这项功能,比如 Ruby 的require、Python 的import,甚至就连 CSS 都有@import,但是 JavaScript 任何这方面的支持都没有,这对开发大型的、复杂的项目形成了巨大障碍。
在 ES6 之前,社区制定了一些模块加载方案,最主要的有 CommonJS 和 AMD 两种。前者用于服务器,后者用于浏览器。ES6 在语言标准的层面上,实现了模块功能,而且实现得相当简单,完全可以取代 CommonJS 和 AMD 规范,成为浏览器和服务器通用的模块解决方案。
ES6 模块的设计思想,是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。CommonJS 和 AMD 模块,都只能在运行时确定这些东西。比如,CommonJS 模块就是对象,输入时必须查找对象属性。
2 |
let { stat, exists, readFile } = require( 'fs' ); |
5 |
let _fs = require( 'fs' ); |
7 |
let exists = _fs.exists; |
8 |
let readfile = _fs.readfile; |
|
上面代码的实质是整体加载fs模块(即加载fs的所有方法),生成一个对象(_fs),然后再从这个对象上面读取3个方法。这种加载称为“运行时加载”,因为只有运行时才能得到这个对象,导致完全没办法在编译时做“静态优化”。
ES6 模块不是对象,而是通过export命令显式指定输出的代码,再通过import命令输入。
2 |
import { stat, exists, readFile } from 'fs' ; |
|
上面代码的实质是从fs模块加载3个方法,其他方法不加载。这种加载称为“编译时加载”或者静态加载,即 ES6 可以在编译时就完成模块加载,效率要比 CommonJS 模块的加载方式高。当然,这也导致了没法引用 ES6 模块本身,因为它不是对象
- ES6常用新特性
https://segmentfault.com/a/1190000011976770?share_user=1030000010776722 该文章为转载文章!仅个人喜好收藏文章! 1.前言 前几天 ...
- HTML5的常用新特性你必须知道
HTML5的常用新特性你必须知道 1 新的 声明 HTML 有多个不同的版本,只有完全明白页面中使用的确切 HTML 版本,浏览器才能完全正确地显示出 HTML 页面.这就是 的用处. 不是 HTML ...
- es6/es7/es8常用新特性总结(超实用)
本文标题有误导性,因为我其实想写node8的新特性,说实话一下子从node v1.x跳跃到node 8.x+ 真有点受宠若惊的感觉.一直觉得node 数组. 对象.序列等的处理没有python方便,因 ...
- Java8常用新特性实践
前言: 时下Oracle开速迭代的Java社区以即将推出Java10,但尴尬的是不少小中企业仍使用JDK7甚至JDK6开发. 从上面列出的JDK8特性中我们可以发现Java8的部分特性很明显的是从Sc ...
- ECMAScript和JavaScript的区别,ECMAScript发展更新历史,ECMAScript5和ECMAScript6的新特性及浏览器支持情况,ECMAScript 5/ECMAScript 2015正式发布
ECMAScript和JavaScript的区别 ECMA是European Computer Manufacturers Association的缩写,即欧洲计算机制造商协会.欧洲计算机制造商协会是 ...
- ES6、7、8常用新特性总结(超实用)
ES6常用新特性 1. let && const let 命令也用于变量声明,但是作用域为局部 { let a = 10; var b = 1; } 在函数外部可以获取到b,获取不到a ...
- ECMAScript 5和ECMAScript6的新特性以及浏览器支持情况
ECMAScript简介: 它是一种由Ecma国际(前身为欧洲计算机制造商协会)制定和发布的脚本语言规范,javascript在它基础上经行了自己的封装.但通常来说,术语ECMAScript和java ...
- ECMAscript6(ES6)新特性语法总结(一)
ES6/ES2015,,在ES5的基础上扩展了很多新的功能,在使用的时候要慎重,因为有一部分js代码在部分浏览器是不兼容的,但是所有写在服务器端的代码基本上都支持ES6的写法. 新特性: 一.开启严格 ...
- CSS3常用新特性
CSS3的新特性 新增CSS3特性有兼容性问题,ie9+才支持 移动端支持优于PC端 新增选择器和盒子模型以及其他特性 CSS新增选择器 属性选择器 属性选择器可以根据元素特定属性来选择元素,这样就可 ...
随机推荐
- Android驱动学习-APP操作新硬件的两种方法(支持添加的驱动)
在给Android添加新的驱动后,app要如何使用呢? 正常的使用一个设备,需要getService.但是像LED等我们自己添加的硬件驱动,Android源代码根本没有我们自己添加的服务. 第一种: ...
- Spring Cloud是什么鬼?
研究了一段时间Spring Boot了准备向Spring Cloud进发,公司架构和项目也全面拥抱了Spring Cloud.在使用了一段时间后发现Spring Cloud从技术架构上降低了对大型系统 ...
- Python错误重试方法
前言 Tenacity是一个 Apache 2.0授权的通用重试库,用 Python 编写,用于简化向几乎所有内容添加重试行为的任务.它起源于一个重新尝试的分支,可惜这个分支已经不复存在了. 使用Te ...
- SonarQube学习(三)- 项目代码扫描
一.前言 元旦三天假,两天半都在玩86版本DNF,不得不说,这个服真的粘度太高了,但是真的很良心. 说明: 注册账号上线100w点券,一身+15红字史诗装备以及+21强化新手武器.在线泡点一分钟888 ...
- Salesforce 大数据量处理篇(一)Skinny Table
本篇参考:https://developer.salesforce.com/docs/atlas.en-us.salesforce_large_data_volumes_bp.meta/salesfo ...
- 最实用的visual studio插件,值得收藏!
1.ReSharper(VS必装,代码重构.代码修正功能) ReSharper 是一个JetBrains公司出品的著名的代码生成工具.其能帮助Microsoft Visual Studio成为一个更佳 ...
- NOIP初赛篇——06数制转换
进位计数制的基本概念 将数字符号按顺序排列成数位,并遵照某种由低到高的进位方式计数表示数值的方法,称作为计数制. 十进制 十进制计数制由0.1.2.3.4.5.6.7.8.9共10个数字符号组成. ...
- WPF DataGrid与ListView性能对比与场景选择
开门见山的说 性能对比: 在Demo中,DataGrid与ListView默认开启虚拟化(可以理解为动态渲染,类似懒加载只渲染屏幕可以看见的地方) DataGrid渲染10列50行随机字符280ms ...
- Shiro配置Session检测时Quartz版本冲突
项目背景: shiro 1.3 + quartz 2.x 2018-9-11 22:20:35补充: 经过测试,本人发现 ,通过实现 org.apache.shiro.session.mgt.Exec ...
- Linux下Too many open files问题排查与解决
作者: Grey 原文地址: Github 语雀 博客园 Too many open files是Linux系统中常见的错误,从字面意思上看就是说程序打开的文件数过多,不过这里的files不单是文件的 ...