一、ES6概念
- ES6,全称为ECMAScript 6.0 ;是 JavaScript 的下一个版本标准,2015.06 发版
- ES6 主要是为了解决 ES5 的先天不足,比如 JavaScript 里并没有类的概念
- 但是目前浏览器的 JavaScript 是 ES5 版本,大多数高版本的浏览器也支持 ES6,不过只实现了 ES6 的部分特性和功能。
二、ES6语法
1.let和const
let
和const
是ES6新增加的js关键字
(1)let与var
let
是在代码块内有效,而var
是在全局范围内有效(可以理解为本该在代码块生效的代码,却在外面也生效,说明“红杏出墙”)
{
let a = 0;
var b = 1;
}
console.log(a); // ReferenceError: a is not defined
console.log(b); // 1
let
只能声明一次,var
可以声明多次;let
声明多次时,会报错,告诉你已经定义了此变量。避免了项目中存在变量覆盖(理解为套牌车,其是违法的)的问题- 同一个项目中,发生变量覆盖可能会导致数据丢失以及各种不可预知的bug,原则上来说:变量不能重名
let a = 1;
let a = 2;
var b = 3;
var b = 4;
console.log(a); // Identifier 'a' has already been declared,
console.log(b); // 4
let
不存在变量提升,var
会变量提升- 变量 a 用 let 声明不存在变量提升,在声明变量 a 之前,a不存在,所以会报错;
- 而变量 b 用 var 声明存在变量提升,所以当脚本开始运行的时候,b 已经存在了,但是还没有赋值,所以会输出 undefined。
console.log(a); //ReferenceError: a is not defined
let a = "abc";
console.log(b); //undefined
var b = "edg";
let
适合于循环计数器
for (var i = 0; i < 10; i++) {
setTimeout(function() {
console.log(i);
})
}
// 输出十个 10
for (let j = 0; j < 10; j++) {
setTimeout(function() {
console.log(j);
})
}
// 输出 0123456789
- 用
var
声明的变量i
是在全局范围内有效的,所以全局中只有一个变量i
,, 每次循环时,setTimeout
定时器里面的i
指的是全局变量i
,而循环里的十个setTimeout
是在循环结束后才执行,所以此时的i
都是 10。- 变量
j
是用 let 声明的,当前的j
只在本轮循环中有效,每次循环的j
其实都是一个新的变量,所以setTimeout
定时器里面的 j 其实是不同的变量,即最后输出0123456789
(2)const
const
声明一个只读变量,声明之后不允许改变
const
其实保证的不是变量的值不变,而是保证变量指向的内存地址所保存的数据不允许改动,因此const
声明的简单类型变量等同于常量
- 一般用于全局变量,变量名通常全部大写
const PI = "3.1415926"
2.解构赋值
- 解构赋值是对赋值运算符的扩展
- 针对数组或者对象进行模式匹配,然后对其中的变量进行赋值
- 解构中,解构的源是赋值表达式的右边;解构的目标是赋值表达式的左边
- 在代码上更加简洁易读,语义更清晰,方便复杂对象中数据字段的获取
(1)数组模型的解构
基本语法
let [a, b, c] = [1, 2, 3];
// a = 1
// b = 2
// c = 3
可嵌套
let [a, [[b], c]] = [1, [[2], 3]];
// a = 1
// b = 2
// c = 3
可忽略,相当于,
=一个占位符
let [a, , b] = [1, 2, 3];
// a = 1
// b = 3
剩余运算符
let [a, ...b] = [1, 2, 3];
//a = 1
//b = [2, 3]
若解构的源是字符串
let [a, b, c, d, e] = 'hello';
// a = 'h'
// b = 'e'
// c = 'l'
// d = 'l'
// e = 'o'
不完全解构
let [a = 1, b] = []; // a = 1, b = undefined
解构默认值,当解构模式有匹配结果,且匹配结果是 undefined
时,会触发默认值作为返回结果。
let [a = 3, b = a] = []; // a = 3, b = 3
let [a = 3, b = a] = [1]; // a = 1, b = 1
let [a = 3, b = a] = [1, 2]; // a = 1, b = 2
- a 与 b 匹配结果为 undefined ,触发默认值:
a = 3; b = a =3
- a 正常解构赋值,匹配结果:
a = 1
,b 匹配结果undefined
,触发默认值:b = a =1
- a 与 b 正常解构赋值,匹配结果:
a = 1,b = 2
(2)对象模型的解构
跟数组模型的结构差不多
基本语法
let { foo, bar } = { foo: 'aaa', bar: 'bbb' };
// foo = 'aaa'
// bar = 'bbb'
let { baz : foo } = { baz : 'ddd' };
// foo = 'ddd'
3.字符串
(1)子串的识别
在ES6之前,字符串用indexOf
方法判断字符串是否包含子串
在ES6中,提供新的方法:
includes()
:判断是否找到参数字符串。startsWith()
:判断参数字符串是否在原字符串的头部。endsWith()
:判断参数字符串是否在原字符串的尾部。
这三种方法都是返回布尔值,可以接受两个参数:搜索的字符串和搜索起始位置
let string = "apple,banana,orange";
console.log(string.startsWith("banana", 6));
string.includes("banana"); // true
string.startsWith("apple"); // true
string.endsWith("apple"); // false
string.startsWith("banana", 6) // true
注意:
- 如果需要知道子串的位置,还是得用
indexOf
(返回子字符串第一次出现字符位置。没有找到,则返回 -1)和lastIndexOf
(返回子字符串最后出现的位置。没有找到,则返回 -1) - 这三个方法如果传入了正则表达式而不是字符串,会抛出错误
(2)字符串重复
str.repeat()
方法,将字符串以重复指定次数返回
let str = "hello,";
console.log(str.repeat(2)); //hello,hello,
- 如果参数是小数,则向下取整
let str = "hello,"
console.log(str.repeat(3.2)); //hello,hello,hello,
- 如果参数是
-1到0
之间的小数,会进行取整运算,0 至 -1 之间的小数取整得到 -0 ,等同于 repeat 零次
console.log(str.repeat(-0.5)); //''
- 如果参数是
NaN
,等同于 repeat 零次
console.log(str.repeat(NaN));//''
- 如果参数是负数或
Infinity
(无穷),则会报错
console.log("Hello,".repeat(-1));
// RangeError: Invalid count value
console.log("Hello,".repeat(Infinity));
// RangeError: Invalid count value
(3)字符串补全
padStart()
:返回新的字符串,表示用参数字符串从头部(左侧)补全原字符串。padEnd
:返回新的字符串,表示用参数字符串从尾部(右侧)补全原字符串。- 这两个方法也是接收两个参数,第一个是生成的新的字符串的总长度,第二个是用来补全的字符串;如果没有指定第二个参数,默认用空格填充
console.log("h".padStart(5,"o")); // "ooooh"
console.log("h".padEnd(5,"o")); // "hoooo"
console.log("h".padStart(5)); // " h"
- 如果指定的长度小于等于原字符串的长度,则返回原字符串
console.log("hello".padStart(5,"A")); // "hello"
- 如果原字符串加上补全字符串长度大于指定长度,则截去超出位数的补全字符串:
console.log("hello".padEnd(10,",world!")); // "hello,worl"
- 常用于补全位数:
console.log("123".padStart(10,"0")); // "0000000123"
(4)模板字符串
用反引号来创建,${}
里面放入变量名或者js表达式,模板字符串中的换行和空格会被保留
4.ES6函数
(1)箭头函数
箭头函数简化了函数的书写方式
- 基本语法
参数 => 函数体
var f = v => v;
//等价于
var f = function(a){
return a;
}
f(1); //1
- 当箭头函数没有参数或者有多个参数时,要用
()
括起来;当函数体有多行语句时,用{}
括起来,只有一行时可以省略
var f = (a,b) => a+b;
f(6,2); //8
- 当箭头函数返回对象时,要用
()
将对象包裹起来,否则会报错
// 报错
var f = (id,name) => {id: id, name: name};
f(6,2); // SyntaxError: Unexpected token :
// 不报错
var f = (id,name) => ({id: id, name: name});
f(6,2); // {id: 6, name: 2}
- 注意箭头函数中没有
this、super、arguments 和 new.target
绑定
var func = () => {
// 箭头函数里面没有 this 对象,
// 此时的 this 是外层的 this 对象,即 Window
console.log(this)
}
console.log(func(55)); // Window
var func = () => {
console.log(arguments)
}
console.log(func(55));; // ReferenceError: arguments is not defined
- 箭头函数体中的this对象,是定义函数时的对象,而不是使用函数时的对象
A1-箭头函数中的this问题
A2-箭头函数中的this问题
A3-箭头函数中的this问题
在ES5中:在全局函数中,this
指向的是window
;当函数被作为某个对象的方法调用时,this就等于那个对象
var name = "window";
var obj = {
name: 'object',
getName: function() {
return function() {//匿名函数
return this.name;
}
}
}
console.log(obj.getName()());//window
由于匿名函数的执行环境是全局的,且this只在函数内部起作用,而在匿名函数function
中找不到this.name
是什么,所以就从全局中找,所以是window
,无法找到object
在没有ES6前,解决匿名函数中tihs的指向问题是先用一个变量存储起来,var that = this
var name = "window";
var obj = {
name: 'object',
getName: function() {
var that = this;
return function() { //匿名函数
return that.name;
}
}
}
console.log(obj.getName()()); //object
我们在getName
函数内将this
赋给that
,此时that
指向的是调用的对象,即obj
,此时在匿名函数中调用that.name
会在object
上查找相应的数据,而不是在全局上查找,最终打印object
。
但是在ES6中,箭头函数在定义时,this就继承了定义函数的对象:
var name = "window";
var obj = {
name: 'object',
getName: function() {
return () => {
console.log(this.name);
}; //object
}
}
obj.getName()();//object
用箭头函数定义getName
函数里面的匿名函数function
时,this就继承了obj对象,所以在调用时会打印出object
总结来讲,普通函数中的
this
:谁调用就指向谁;箭头函数中:一层一层网上找,找离得最近的this
,就指向它。
箭头函数常用于setTimeout()、回调函数中
,不适合用于动态this
中
// 回调函数
var Person = {
'age': 18,
'sayHello': function () {
setTimeout(function () {
console.log(this.age);
});
}
};
var age = 20;
Person.sayHello(); // 20
//非箭头函数,在函数中,指向全局对象window,所以打印出的age=20
var Person1 = {
'age': 18,
'sayHello': function () {
setTimeout(()=>{
console.log(this.age);
});
}
};
var age = 20;
Person1.sayHello(); // 18
//箭头函数,所以在定义sayHello这个函数时,this就指向了sayHello的对象Person1,所以打印出的age=18
(2)函数参数的扩展
默认参数
function fn(name,age=17){
console.log(name+","+age);
}
fn("Amy",18); // Amy,18
fn("Amy",""); // Amy,
fn("Amy"); // Amy,17
- 使用函数默认参数时,不允许有同名参数
- 只有在未传递参数,或者参数为 undefined 时,才会使用默认参数,null 值被认为是有效的值传递。
function fn(name,age=17){
console.log(name+","+age);
}
fn("Amy",null); // Amy,null
- 函数参数默认值存在暂时性死区,在函数参数默认值表达式中,还未初始化赋值的参数值无法作为其他参数的默认值。
function f(x=y){
console.log(x);
}
f(); // ReferenceError: y is not defined
不定参数
不定参数用来表示不确定参数个数,...变量名
function f(...values){
console.log(values.length);
}
f(1,2); //2
f(1,2,3,4); //4
5.数组
6.ES6 类
面向过程编程是分析出解决问题所需要的的步骤,通过函数一步一步实现这些步骤
面向对象思想:面向对象是以对象来划分问题,每一个对象都是功能中心,具有明确的分工;
思维特点:
- 抽取对象共用的属性和行为封装成一个类(模板)
- 对类进行实例化,获取类的对象
(1)对象
对象是一个具体的事物,由属性(事物的特征)和方法(事物的行为)组成
(2)类的定义
- class (类)作为对象的模板被引入,可以通过 class 关键字定义类
- class 的本质是 function,同样可以看成一个块
- 可以看作一个语法糖,让对象原型的写法更加清晰
- 更加标准的面向对象编程语法
类是泛指的某一大类,是抽象的;对象是特指某一个,通过类实例化一个具体的对象
a.创建类
//用关键字class创建类
class Star {
}
//通过new创建对象
new Star();
b.类中的构造函数constructor
constructor()
方法是类的构造函数(即默认方法),用于传递参数,返回实例对象- 通过
new
命令生成对象时,自动调用该方法 - 如果没有显示定义,类内部会自动给我们创建一个
constructor()
class Star {
constructor(name) {//函数
this.name = name;
}
}
//通过new创建对象
var uname = new Star('易烊千玺');
console.log(uname.name);
this指向的是实例化对象
c.类中添加方法
class Star {
constructor(name) {
this.name = name;
}
sing(song) { //创建sing这个方法 不需要加function
console.log(this.name + song);
}
}
//通过new创建对象
var uname = new Star('易烊千玺');
console.log(uname.name);
uname.sing('精彩才刚刚开始')
- 类中方法不需要
function
关键字 - 方法间不能加分号
(3)类的继承
extends关键字
子类继承父类的一些属性和方法,同时子类可以保留自己特有的方法
class Father {
constructor() {
}
money() {
console.log(1000);
}
}
class Son extends Father {
}
// 实例化Son
var son = new Son();
son.money();
super关键字
super
关键字可以用于访问和调用对象父类上的函数(可以调用父类的构造函数,也可以是普通函数)
//super关键字,调用父类的构造函数
class Father {
constructor(x, y) {
this.x = x;
this.y = y;
}
sum() {
console.log(this.x + this.y);
}
}
class Son extends Father {
constructor(x, y) {
super(x, y); //调用了父类中的构造函数
}
}
var son = new Son(1, 2);
son.sum();
// 调用父类的普通函数
class Father {
say() {
return '爸爸';
}
}
class Son extends Father {
say() {
console.log('儿子');
console.log(super.say() + '的儿子');
}
}
var son = new Son();
son.say();
继承中,如果实例化子类输出一个方法,先看子类自己有没有这个方法,如果有就执行子类的;如果没有,则去父类里找有没有这个方法,如果有则执行父类的方法(就近原则)
// 调用父类的方法,同时保留子类特有的方法
class Father {
constructor(x, y) {
this.x = x;
this.y = y;
}
sum() {
console.log(this.x + this.y);
}
}
class Son extends Father {
constructor(x, y) {
super(x, y); //调用了父类中的构造函数
//super 必须在子类this之前调用
this.x = x;
this.y = y;
}
subtract() {
console.log(this.x - this.y);
}
}
var son = new Son(5, 3);
son.sum();
son.subtract();
ps:super 必须在子类this之前调用,即必须先调用父类的构造方法,再使用子类的构造方法
总结:
- 在ES6中,类没有变量提升,所以必须先定义类,然后才能通过类实例化对象
- 类里面的共有的属性和方法要加
this
使用
(4)类中this的指向问题
constructor
里面的this指向的是创建的实例对象
- 方法中的
this
是谁调用this就指向谁
三、ES6模块化
- ES6依赖模块需要编译打包处理
- 由于浏览器兼容性问题,目前部分浏览器不支持ES6语法,通常开发完项目后,要将ES6语法转换为ES5,这样浏览器才能识别
1.ES6模块化语法
- 导出模块:即规定模块的对外接口,用
export
- 引入模块:即输入其他模块提供的功能,用
import
,语法:import xxx from '路径'
2.ES6在浏览器端的实现
(1)用Babel将ES6编译成ES5代码
(2)使用Browserify编译打包js
a.定义package.json
文件
{
"name":"es6_browserify",//下载browserify,不能直接起名为browserify
"version": "1.0.0"
}
b.安装babel-cli, babel-preset-es2015
和browserify
cli
的全称为:command line interface
,命令行接口,需要下载babel-cli
库去调用babel的命令babel-preset-es2015
,将es6转换成es5的所有插件打包(preset,预设)- 步骤
npm install babel-cli browserify -g
npm install babel-preset-es2015 --save-dev
c.定义.babelrc
文件
rc
文件:全称为run control
,运行时控制文件
跟package.json
在同一目录下,其是用来转换ES6语法的
{
"presets": ["es2015"]
}
d.定义模块代码
module1.js
//暴露模块(即导出模块) 分别暴露
export function foo() {
console.log('foo() module1'); //调用foo函数
}
export function bar() {
console.log('bar() module1');
}
// 暴露数组
export let arr = [1, 2, 3, 4, 5]
module2.js
// 统一暴露,将数据打包在一个对象里面统一暴露
function fun() {
console.log('fun() module2');
}
function fun2() {
console.log('fun2() module2');
}
//暴露对象里面有两个函数
export {
fun,
fun2
};
main.js
:引入其他模块
//引入 其他的模块
//语法:import xxx from '路径',引入的时候要用对象解构赋值,不能直接定义一个变量取接收模块
import {
foo,
bar
} from './module1';
import {
fun,
fun2
} from './module2';
foo();
bar();
fun();
fun2();
// console.log(module1, module2);
e.编译
- 使用Babel将ES6编译为ES5代码:
babel js/src -d js/build
js/src
:要编译的文件目录;js/bulid
:编译为ES5后放入的文件目录
可能会遇到babel无法加载文件,参考
- 使用Browserify编译js:
browserify js/build/main.js -o js/dist/bundle.js