ECMASript 6
文章目录
ECMASript 6新特性
let 关键字
let 关键字用来声明变量,使用 let 声明的变量有几个特点:
1.不允许重复声明
let star="罗志祥"
let star="小猪"
2.块儿级作用域(即只在代码块里面有效)
JS中作用域有:全局作用域、函数作用域。没有块作用域的概念。ECMAScript 6(简称ES6)中新增了块级作用域。
块作用域由 { } 包括,if
,else
,while
,for
语句里面的{ }也属于块作用域。
{
let girl="周扬青";
}
console.log(girl);
ES6 允许块级作用域的任意嵌套。
{{{{
{let insane = 'Hello World'}
console.log(insane); // 报错
}}}};
上面代码使用了一个五层的块级作用域,每一层都是一个单独的作用域。第四层作用域无法读取第五层作用域的内部变量。
3.不存在变量提升
var命令会发生“变量提升”现象,即变量可以在声明之前使用,值为undefined。这种现象多多少少是有些奇怪的,按照一般的逻辑,变量应该在声明语句之后才可以使用。
为了纠正这种现象,let命令改变了语法行为,它所声明的变量一定要在声明后使用,否则报错。
4.不影响作用域链
作用域
作用域,就是变量起作用的区域(范围)
JS的变量作用域分为:全局作用域和函数作用域(局部作用域)
作用域链
作用域链是指:当js编译器在寻找变量时,先在最近的作用域(花括号)里找,如果找不到,则抄上一级作用域(花括号)里找,依次类推,直到找到或者找不到为止。这就是作用域链。
let v1=10;
function f1() {
let v2=20;
if(true){
let v3=30;
console.log(v1);
}
}
f1()
应用场景:以后声明变量使用 let 就对了
const 关键字
const 关键字用来声明常量,const 声明有以下特点:
1.声明必须赋初始值
2.标识符一般为大写(潜规则,不做硬性要求)
3.值不允许修改
4.不允许重复声明
5.块儿级作用域
注意: 对象属性修改和数组元素变化不会出发 const 错误
本质
const实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址所保存的数据不得改动。对于简单类型的数据(数值、字符串、布尔值),值就保存在变量指向的那个内存地址,因此等同于常量。但对于**复合类型的数据(主要是对象和数组),变量指向的内存地址,保存的只是一个指向实际数据的指针,const只能保证这个指针是固定的(即总是指向另一个固定的地址),至于它指向的数据结构是不是可变的,就完全不能控制了。**因此,将一个对象声明为常量必须非常小心。应用场景:声明对象类型使用 const,非对象类型声明选择 let
变量的解构赋值
数组的解构赋值
ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构(Destructuring)。
以前,为变量赋值,只能直接指定值。
let a = 1;
let b = 2;
let c = 3;
ES6 允许写成下面这样
let [a, b, c] = [1, 2, 3];
上面代码表示,可以从数组中提取值,按照对应位置,对变量赋值.本质上,这种写法属于“模式匹配”,只要等号两边的模式相同,左边的变量就会被赋予对应的值。如果解构不成功,变量的值就等于undefined。
let [foo, [[bar], baz]] = [1, [[2], 3]];
console.log(foo);
console.log([[bar], baz]);
let [foo] = [];
let [bar, p] = [1];
console.log([foo]);
console.log([bar, p]);
对象的解构赋值
对象的解构与数组有一个重要的不同。数组的元素是按次序排列的,变量的取值由它的位置决定;而对象的属性没有次序,变量必须与属性同名,才能取到正确的值。如果解构失败,变量的值等于undefined
。
let obj={
a:1,
b:2,
loc: {
start: {
line: 1,
column: 5
}
},
friends:[{
name:"cyy2",
age:20
},{
name:"cyy3",
age:30
},{
name:"cyy4",
age:40
}]
};
// 如果出现对象属性名重复的情况,会报错,解决方法是使用: 来定义别名
//如果声明的变量名和对象属性名一致,那就可以简写
let {a,b,loc:{start:s1, start: {line:l1,column:c1}},
friends:f1,friends:[{name:n1,age:ag1},{name:n2,age:ag2},{name:n3,age:ag3}]}=obj;
console.log(a); console.log(b);
console.log(s1); console.log(l1);
console.log(c1); console.log(f1);
console.log(n1); console.log(ag1);
console.log(n2); console.log(ag2);
console.log(n3); console.log(ag3);
模板字符串
模板字符串(template string)是增强版的字符串,用反引号(``)标识,
特点:
1.字符串中可以出现换行符
2.在反引号(``)里面写${变量名}字符串
//es6引入新的声明字符串的方式反引号(``)
let s=`反引号也可以定义字符串`
console.log(s,typeof s);
//反引号(``)字符串中可以出现换行符,其他的'',""不行
let str = `<ul>
<li>沈腾</li>
<li>玛丽</li>
<li>魏翔</li>
<li>艾伦</li>
</ul>`;
console.log(str);
// 变量拼接
let star = '王宁';
//以前变量和字符串拼接是使用+
let r=star+'在前几年离开了开心麻花'
console.log(r);
//es6在反引号(``)里面写${变量名}字符串
let result = `${star}在前几年离开了开心麻花`
console.log(result);
对象的简化写法
ES6允许在大括号里面,直接写入变量和函数,作为对象的属性和方法。
属性的简洁表示法
如果声明的变量名和对象属性名一致,那就可以简写
let s=`反引号也可以定义字符串`
//简写形式:变量名和对象属性名一样
const str={
s
}
console.log(str);
//es5
const str1={
s1:s
}
console.log(str1);
对象里面的方法简写
//es6把方法中的:function省略了
const o = {
method() {
return "Hello!";
}
};
// 等同于
const o = {
method: function() {
return "Hello!";
}
};
注意:对象简写形式简化了代码,所以以后用简写就对了
箭头函数
ES6 允许使用「箭头」(=>)定义函数。
//es6箭头函数小括号是参数,花括号写实体内容
let fn = (arg1, arg2, arg3) => {
return arg1 + arg2 + arg3;
}
//普通的写法
let fn = function(arg1, arg2, arg3) {
return arg1 + arg2 + arg3;
}
箭头函数的注意点:
1.如果形参只有一个,则小括号可以省略
/**
* 2. 省略小括号的情况
*/
let fn2 = num => {
return num * 10;
};
2.函数体如果只有一条语句,则花括号可以省略,函数的返回值为该条语句的 执行结果
/**
* 3. 省略花括号的情况
*/
let fn3 = score => score * 20;
3.箭头函数 this 实始终指向函数声明时所在作用域下 this 的值
普通函数中this
(1)总是代表着它的直接调用者,如obj.fn,fn里的最外层this就是指向obj
//hello直接调用者是obj,第一个this指向obj,setTimeout里匿名函数没有直接调用者,this指向window
const obj = {
num: 10,
hello: function () {
console.log(this); // obj
setTimeout(function () {
console.log(this); // window
});
}
}
obj.hello();
(2)默认情况下,没有直接调用者,this指向window
//hello是全局函数,没有直接调用它的对象,也没有使用严格模式,this指向window
function hello() {
console.log(this); // window
}
hello();
(3)严格模式下(设置了’use strict’),this为undefined
function hello() {
'use strict';
console.log(this); // undefined
}
hello();
(4)当使用call,apply,bind(ES5新增)绑定的,this指向绑定对象
ES6箭头函数中this
(1)默认指向定义它时,所处上下文的对象的this指向。即ES6箭头函数里this的指向就是上下文里对象this指向,偶尔没有上下文对象,this就指向window
//hello直接调用者是obj,第一个this指向obj,setTimeout箭头函数,this指向最近的函数的this指向,即也是obj
const obj = {
num: 10,
hello: function () {
console.log(this);
// obj
setTimeout(() => {
console.log(this); // obj
});
}
}
obj.hello();
//diameter是普通函数,里面的this指向直接调用它的对象obj。perimeter是箭头函数,this应该指向上下文函数this的指向,这里上下文没有函数对象,就默认为window,而window里面没有radius这个属性,就返回为NaN。
const obj = {
radius: 10,
diameter() {
return this.radius * 2
},
perimeter: () => 2 * Math.PI * this.radius
}
console.log(obj.diameter()) // 20
console.log(obj.perimeter()) // NaN
(2)即使是call,apply,bind等方法也不能改变箭头函数this的指向
4.箭头函数不能作为构造函数实例化
let Person = (name, age)=>{
this.name = name
this.age=age;
}
let me = new Person('张三',30);
console.log(me);
5.不能使用 arguments
rest 参数(其它参数,,,,,…theArgs或… args)
ES6 引入 rest 参数,用于获取函数的实参,用来代替 arguments
Rest就是为解决传入的参数数量不一定, rest parameter(Rest 参数) 本身就是数组,数组的相关的方法都可以用。
你可以将rest参数定义为(其它参数,,,,,…theArgs或… args)
…theArgs或… args必须放在最后
扩展运算符(…)
扩展运算符(spread)也是三个点(…)。它好比 rest 参数的逆运算,将一 个数组转为用逗号分隔的参数序列,对数组进行解包。
let boys = ['德玛西亚之力','德玛西亚之翼','德玛西亚皇子'];
function fn(){
console.log(arguments);
}
fn(...boys)
应用
数组合并
let skillOne = {
q: '致命打击',
};
let skillTwo = {
w: '勇气'
};
let skillThree = {
e: '审判'
};
let skillFour = {
r: '德玛西亚正义'
};
let gailun = {...skillOne, ...skillTwo,...skillThree,...skillFour};
console.log(gailun);
Symbol 基本使用
ES5 的对象属性名都是字符串,这容易造成属性名的冲突。比如,你使用了一个他人提供的对象,但又想为这个对象添加新的方法(mixin 模式),新方法的名字就有可能与现有方法产生冲突。如果有一种机制,保证每个属性的名字都是独一无二的就好了,这样就从根本上防止属性名的冲突。这就是 ES6 引入Symbol的原因。
ES6 引入了一种新的原始数据类型Symbol,表示独一无二的值。它是 JavaScript 语言的第七种数据类型,前六种是:undefined、null、布尔值(Boolean)、字符串(String)、数值(Number)、对象(Object)。
Symbol 值通过Symbol函数生成。这就是说,对象的属性名现在可以有两种类型,一种是原来就有的字符串,另一种就是新增的 Symbol 类型。凡是属性名属于 Symbol 类型,就都是独一无二的,可以保证不会与其他属性名产生冲突。
let s=Symbol("哈哈")
console.log(s,typeof s);
//注意,Symbol函数的参数只是表示对当前 Symbol 值的描述,因此相同参数的Symbol函数的返回值是不相等的。
let s1=Symbol("哈哈")
console.log(s===s1)
// 有时,我们希望重新使用同一个 Symbol 值,Symbol.for()方法可以做到这一点
let s3 = Symbol.for('哈哈');
let s4 = Symbol.for('哈哈');
console.log(s3 === s4);
Symbol 特点
-
Symbol 的值是唯一的,用来解决命名冲突的问题
-
Symbol 值不能与其他数据进行运算
-
Symbol 定义 的 对象属 性 不能 使 用 for…in 循 环遍 历 ,但 是可 以 使 用 Reflect.ownKeys 来获取对象的所有键名
CommonJS模块规范
CommonJS 概述
Node.js 应用由模块组成,每个文件就是一个模块,有自己的作用域。在一个文件里面定义的变量、函数、类,都是私有的,对其他文件不可见。
CommonJS 规范还规定,每个模块内部有两个变量可以使用,require 和 module。
require 用来加载某个模块
module 代表当前模块,是一个对象,保存了当前模块的信息。exports 是 module 上的一个属性,保存了当前模块要导出的接口或者变量,使用 require 加载的某个模块获取到的值就是那个模块使用 exports 导出的值
// a.js
var name = 'morrain'
var age = 18
module.exports.name = name
module.exports.getAge = function(){
return age
}
//b.js
var a = require('a.js')
console.log(a.name) // 'morrain'
console.log(a.getAge())// 18
CommonJS 之 exports
为了方便,Node.js 在实现 CommonJS 规范时,为每个模块提供一个 exports的私有变量,指向 module.exports。你可以理解为 Node.js 在每个模块开始的地方,添加了如下这行代码。
var exports = module.exports
于是上面的代码也可以这样写:
// a.js
var name = 'morrain'
var age = 18
exports.name = name
exports.getAge = function(){
return age
}
有一点要尤其注意,exports 是模块内的私有局部变量,它只是指向了 module.exports,所以直接对 exports 赋值是无效的,这样只是让 exports 不再指向module.exports了而已。
CommonJS 之 require
require 命令的基本功能是,读入并执行一个 js 文件,然后返回该模块的 exports 对象。如果没有发现指定模块,会报错。
第一次加载某个模块时,Node.js 会缓存该模块。以后再加载该模块,就直接从缓存取出该模块的 module.exports 属性返回了。
// a.js
var name = 'morrain'
var age = 18
exports.name = name
exports.getAge = function(){
return age
}
// b.js
var a = require('a.js')
console.log(a.name) // 'morrain'
a.name = 'rename'
var b = require('a.js')
console.log(b.name) // 'rename'
Module 的语法
模块化是指将一个大的程序文件,拆分成许多小的文件,然后将小文件组合起来。ES6 模块的设计思想是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。
模块化的好处 模块化的优势有以下几点:
- 防止命名冲突
- 代码复用
- 高维护性
模块化规范产品 ES6 之前的模块化规范有:
- CommonJS => NodeJS、Browserify
- AMD => requireJS
- CMD => seaJS
ES6 模块化语法 模块功能主要由两个命令构成:export 和 import。
- export 命令用于规定模块的对外接口
- import 命令用于输入其他模块提供的功能
一个模块就是一个独立的文件。该文件内部的所有变量,外部无法获取。如果你希望外部能够读取模块内部的某个变量,就必须使用export
关键字输出该变量。使用方式是,可以将export放在任何变量,函数或类声明的前面,从而将他们从模块导出
另外,export
语句输出的接口,与其对应的值是动态绑定关系,即通过该接口,可以取到模块内部实时的值。
使用export
命令定义了模块的对外接口以后,其他 JS 文件就可以通过import
命令加载这个模块。
es6.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script type="module">
//type="module"一定要加,不加会出现Uncaught SyntaxError: Cannot use import statement outside a module
//如果想为输入的变量重新取一个名字,import命令要使用as关键字,将输入的变量重命名。
//后缀名,js最好不要省掉可能报错
import { firstName, lastName, age,multiply,streamV1,streamV2,streamLatestVersion as streamV3 } from '../js/es6.js';
/* 这是的import命令,可以用任意名称指向es6.js输出的方法,这时就不需要知道原模块输出的函数名。
需要注意的是,这时import命令后面,不使用大括号。*/
import sss from '../js/es6.js'
console.log(`我叫${firstName}${lastName},今年${age}岁`)
const m=multiply(1,4)
console.log(m);
streamV1();
streamV2();
streamV3();
sss.foo();
sss.bar();
</script>
</body>
</html>
es6.js
// 分别暴露
export var firstName = 'Michael';
export var lastName = 'Jackson';
export var age = 20;
//export命令除了输出变量,还可以输出函数或类(class)
export function multiply(x, y) {
return x * y;
}
//统一暴露
/*下面代码在export命令后面,使用大括号指定所要输出的一组变量。它与前一种写
法(直接放置在var语句前)是等价的,但是应该优先考虑使用这种写法。因为这样就可以在脚本尾部,一眼看清楚输出了哪些变量。*/
// export { firstName, lastName, age ,multiply};
// 下面代码使用as关键字,重命名了函数v1和v2的对外接口。重命名后,v2可以用不同的名字输出两次。
function v1() {console.log('你好') }
function v2() {console.log('大胆') }
export {
v1 as streamV1,
v2 as streamV2,
v2 as streamLatestVersion
};
// 默认暴露
// 默认暴露的方式只允许有一个: export default {}且在主模块引入时可以使用定义变量来接收的方式!
/*使用import命令的时候,用户需要知道所要加载的变量名或函数名,否则无法加载。
为了给用户提供方便,让他们不用阅读文档就能加载模块,就要用到export default命令,为模块指定默认输出。*/
export default {
foo() {
console.log('默认暴露方式')
},
bar() {
console.log('默认暴露')
}
}