ECMAScript 6(以下简称ES6)是JavaScript语言的下一代标准。
因为当前版本的ES6是在2015年发布的,所以又称ECMAScript 2015(简称ES2015)。虽然浏览器在不断更新,但并不是所有用户的电脑浏览器都支持ES6,所以在使用的过程中建议还是转成es5,保证代码的可执行性。至于转换的方式大家可以用Babel或者Traceur转码器。
在ES6以前,Javascript并没有块级作用域的概念,有的是全局作用域和函数作用域,而let的出现就是为了打破局面,let是块级作用域。const是代表常量,必须在定义的时候初始化,不可改变。
{
var a=5;
let b=10;
}
console.log(a);
console.log(b);
控制台就是这样输出:
也就是说,var声明的变量由于不存在块级作用域所以可以在全局环境中调用,而let声明的变量由于存在块级作用域所以不能在全局环境中调用。
再来看一个经典例子(闭包):
var a=[];
//执行for循环
for(var i=0;i<10;i++){
a[i]=function(){ //因为这个是定义,并没有调用方法,不会执行
console.log(i);
};
}
//for循环之后,此时 i = 10;再次执行a[6]();因为 i 一直被引用,所以不会回收,进入到 a[i] 的方法里面, 打印的是 i ,也就是10
a[6](); //输出10
如果使用 let
var a=[];
for(let i=0;i<10;i++){
a[i]=function(){
console.log(i);
};
}
a[6](); //打印6
a[6]函数(闭包)这个执行环境中,它会首先寻找该执行环境中是否存在 i,没有找到,因为 i 是块级作用域,就沿着作用域链继续向上到了其所在的代码块执行环境,找到了i=6,于是输出了6,即a[6]();的结果为6。这时,闭包被调用,所以整个代码块中的变量i和函数a[6]()被销毁。
const 是定义常量:const a = 14; 此后变量 a 的值无法更改覆盖。
//传统字符串拼接
var s1 = '生物膜系统组装又拆分,变幻莫测;';
var s2 = '你的好多细胞在分裂,';
var str = '孩子们:请听我说!'+s2+'有丝,减数,哪管白天和黑夜。'+
'染色体,细胞核时隐时现,'+s1+'核糖体在mRNA上穿梭忙碌,'+'几千种酶各司其职,将活化能狠狠打折。';
console.log(str);
// 字符模板的写法
var s1 = '染色体,细胞核时隐时现,';
var s2 = '你的好多细胞在分裂,';
var str = `孩子们:请听我说!${s2}有丝,减数,哪管白天和黑夜。${s1}生物膜系统组装又拆分,变幻莫测;核糖体在mRNA上穿梭忙碌,几千种酶各司其职,将活化能狠狠打折。`;
console.log(str);
// 以前我们给变量赋值,只能直接指定值
var a = 1;
var b = 2;
var c = 3;
console.log(a,b,c); // 1 2 3
// 现在用解构赋值的写法就变得简单了,只要模式匹配上了就行了,如下
// 注意数组是有顺序的
var [a,b,c] = [11,22,33];
console.log(a,b,c); // 11 22 33
var [b,a,c] = [11,22,33];
console.log(a,b,c); // 22 11 33
// 当然解构赋值还有嵌套比较复杂的写法,如下
let [foo,[[bar],[baz]]] = [111,[[222],[333]]];
console.log(foo,bar,baz); // 111 222 333
let [head,...foot] = [1,2,3,4];
console.log(head,foot); // 1 [2,3,4]
// 如果解构不成功,变量的值就等于undefined,如下
var [bar3,foo3] = [1000];
console.log(bar3,foo3); // 1000 undefined
// 另一种情况是不完全解构,即等号左边的模式,只匹配一部分的等号右边的数组。这种情况下,解构依然可以成功
let [x,y] = [10000,20000,30000];
console.log(x,y); // 10000 20000
// 默认值可以引用解构赋值的其他变量,但该变量必须已经声明
let [a=1,b=a] = [2,3];
console.log(a,b); // 2 3
// 对象的解构也可以指定默认值
var {x,y=5} = {x:1};
console.log(x,y); // 1 5
//对象的解构赋值解构不仅可以用于数组,还可以用于对象(json)
//对象的解构与数组有一个重要的不同。数组的元素是按次序排列的,变量的取值由它的位置决定;
//而对象的属性没有次序,变量必须与属性同名,才能取到正确的值
var {a,b} = {a:'apple',b:'banana'};
console.log(a,b); // apple banana
var {b,a} = {a:'apple',b:'banana'};
console.log(a,b); // apple banana
// 如果变量名与属性名不一致,必须写成下面这样
let obj = {first:'hello',last:'world'};
// first ---> f,那么此时f就是first,而不是undefined了,有点类似别名的概念
let {first:f,last} = obj;
console.log(f,last); // hello world
//1.也就是说,对象的解构赋值的内部机制,是先找到同名属性,然后再赋给对应的变量。 真正被赋值的是后者,而不是前者
//2.v是匹配的模式,n才是变量。真正被赋值的是变量n,而不是模式v。
//注意,采用这种写法时,变量的声明和赋值是一体的
// v ---> n,那么此时n就是vue,而不是undefined了
var {v:n} = {v:'vue',r:'react'};
console.log(n); // vue
console.log(v); // Uncaught ReferenceError: v is not defined
console.log(r); // Uncaught ReferenceError: r is not defined
// 数组的浅拷贝,引用之间的拷贝,没有实现数组的真正复制
var arr1 = [1, 2, 3];
var arr2 = arr1;
arr2.push(4);
console.log(arr1, arr2); //[1, 2, 3, 4] [1, 2, 3, 4]
// 复制数组深拷贝,传统做法
var arr1 = [1,2,3];
var arr2 = [];
//通过for循环遍历之后将arr1数组的每一项赋值给arr2数组的每一项, 就实现了数组的深拷贝,这时候我再去操作arr2的数组的时候,arr1就不会受影响了
for(var i=0;i
var map = new Map();
// 设置
// map.set(name,value);
map.set('a','apple');
map.set('b','banana');
// 获取
// map.get(name);
console.log(map.get('a') + ' ' + map.get('b'));
// 删除之前map对象
console.log(map);
// 删除
// map.delete(name);
map.delete('a');
// 删除之后map对象
console.log(map);
// 注意for..in是不能循环map对象的,不报错也无任何反应,所以下一代码无任何输出,稍微注意下
for(var name in map){
console.log(name);
}
// 实体 map对象的循环输出
for(var name of map){
//循环出来的结果就是:a,apple b,banana 循环key,value
console.log(name);
}
//循环出来的结果就是: a,apple b,banana 循环key,value
for(var [key,value] of map.entries()){
console.log(key,value);
}
//只循环key
for(var key of map.keys()){
console.log(key);
}
//只循环value
for(var val of map.values()){
console.log(val);
}
//for of一个arr对象
var arr = ['红楼梦','西游记','三国演义','水浒传','火影'];
//只循环key 0 1 2 3 4 输出key值,也就是下标索引
for(var key of arr.keys()){
console.log(key);
}
//只循环value,注意数组是没有.values() 直接 var value of arr ,输出 红楼梦,西游记,三国演义,水浒传,火影
for(var value of arr){
console.log(value);
}
//循环key,value
for(var [key,value] of arr.entries()){
console.log(key,value);
}
//for in循环与for of循环的区别
var arr = ['apple','banana','orange','pear'];
for(var i in arr){
// i打印出来的就是arr数组对应的索引
// 0 1 2 3
console.log(i);
}
for(var i of arr){
// i值打印出来的就是我们想要的数组具体的值
// apple banana orange pear
console.log(i);
}
//for of不能循环json
var json = {'a':'apple','b':'banana','c':'orange','d':'pear'};
for(var name in json){
// a b c d
console.log(name);
// apple
console.log(json.a);
// pear
console.log(json['d']);
}
// 注意for..of可以循环arr,但是不可以循环json,会报错,特别注意下
for(var name of json){
Uncaught TypeError: undefined is not a function
console.log(json);
}
this
。箭头函数表达式的语法比函数表达式更短,并且不绑定自己的this,arguments,super或 new.target。这些函数表达式最适合用于非方法函数,并且它们不能用作构造函数,不能使用new。
箭头函数的写法 function(){ } 变成 ()=>{ }
1 var a = ()=>{ 2 return 1; 3 }
等价于
1 function a(){ 2 return 1; 3 }
如果函数体只有一条语句,可以这样写:
1 var fun = ()=>Math.random()*10 2 console.log(fun());
这样子调用这个箭头函数就会直接返回这条语句的值
箭头函数不绑定arguments,取而代之用rest参数…解决
function A(a){
console.log(arguments); //[object Arguments] [1, 2, 3]
}
var B = (b)=>{
console.log(arguments); //错误:ReferenceError: arguments is not defined
}
var C = (...c)=>{ //...c即为rest参数
console.log(c); //[3, 1, 2]
}
A(1,2,3);
B(2,1,3);
C(3,1,2);
箭头函数会捕获其所在上下文的 this 值,作为自己的 this 值
var obj = {
a: 10,
b: function(){
console.log(this.a); //输出10
},
c: function() {
return ()=>{
console.log(this.a); //输出10,捕获了上面obj的this作为自己的this
}
}
}
obj.b();
obj.c()();
所谓箭头函数的 this 捕获的是所在的上下文,比如下面这个例子:b
是一个箭头函数,然后它的 this
是指向window
,这是为什么呢,因为箭头函数捕获的是obj{}
这个对象的环境,然后这个环境的this
指向的是window
,就相当于上一条的例子:在c
方法里面return
的那个箭头函数捕获的是c:function(){}
这个环境的this
,而这个环境的this
是obj
var obj = {
a: 10,
b: () => {
console.log(this.a); //undefined
console.log(this); //window
},
c: function() {
console.log(this.a); //10
console.log(this); //obj{...}
}
}
obj.b();
obj.c();
对于函数的this
指向问题:
this
永远指向其上下文的 this
,任何方法都改变不了其指向,如call(), bind(), apply()
this
指向调用它的那个对象
//传统对象_单体模式写法 key-value模式
var person = {
name:'krry',
age:21,
showName:function(){
return this.name;
},
showAge:function(){
return this.age;
}
};
// 调用
console.log(person.showName()); // krry
console.log(person.showAge()); // 21
//ES6_单体模式写法 不需要写key
var name = 'krry';
var age = 21;
var person = {
name,
age,
showName(){
return this.name;
},
showAge(){
return this.age;
}
};
// 调用
console.log(person.showName()); // krry
console.log(person.showAge()); // 21
function Person(name,age){ // 类、构造函数
this.name = name;
this.age = age;
}
Person.prototype.showName = function(){
return this.name;
};
Person.prototype.showAge = function(){
return this.age;
};
var p1 = new Person('allen',28);
var p2 = new Person('xiaoxiaoyou',101);
console.log(p1.showName()); // allen
console.log(p2.showAge()); // 101
console.log(p1.showName == p2.showName); //true 注意不是调用方法,没有括号,所以才true
console.log(p1.constructor == Person); // true 构造方法相等
class Person{
// 构造器
constructor(name,age){
this.name = name;
this.age = age;
}
showName(){
return this.name;
}
showAge(){
return this.age;
}
}
var p1 = new Person('aaa',18);
var p2 = new Person('bbb',20);
console.log(p1.name); // aaa
console.log(p1.showName()); // aaa
console.log(p2.showAge()); // 20
console.log(p1.showAge == p2.showAge); // true
console.log(p1.constructor == Person); // true
class Person{
// 构造器
constructor(name='default',age=0){
this.name = name;
this.age = age;
}
showName(){
return this.name;
}
showAge(){
return this.age;
}
}
var p1 = new Person();
console.log(p1.name); // 构造器里面给的默认值 default
console.log(p1.age); // 构造器里面给的默认值 0
//传统写法原型继承
function Person(name,age){ // 类、构造函数
this.name = name;
this.age = age;
}
Person.prototype.showName = function(){
return this.name;
};
Person.prototype.showAge = function(){
return this.age;
};
// 工人类
function Worker(name,age){
// 属性继承过来
Person.apply(this,arguments);
}
// 原型继承
Worker.prototype = new Person();
var p1 = new Person('allen',28);
var w1 = new Person('worker',1000);
console.log(w1.showName()); // 确实继承过来了 result:worker
ES6中面向对象实现类继承
class Person{
// 构造器
constructor(name,age){
this.name = name;
this.age = age;
}
showName(){
return this.name;
}
showAge(){
return this.age;
}
}
class Worker extends Person{
constructor(name,age,job='啦啦啦'){
// 继承超父类的属性
super(name,age);
this.job = job;
}
showJob(){
return this.job;
}
}
var p1 = new Person('aaa',18);
var w1 = new Person('www',36);
var w2 = new Worker('wwwwwwww',90);
console.log(w1.showName()); // www
console.log(w2.showJob()); // 默认给的值 ‘啦啦啦’
import 导入模块、export 导出模块
可以直接在任何变量或者函数前面加上一个 export
关键字,就可以将它导出。
在一个文件中:
export const sqrt = Math.sqrt;
export function square(x) {
return x * x;
}
export function diag(x, y) {
return sqrt(square(x) + square(y));
}
然后在另一个文件中这样引用:
import { square, diag } from 'lib';
console.log(square(11)); // 121
console.log(diag(4, 3));
总结
//mod.js
// 第一种模块导出的书写方式(一个个的导出)
// 导出普通值
export let a = 12;
export let b = 5;
// 导出json
export let json = {
a,
b
};
// 导出函数
export let show = function(){
return 'welcome';
};
// 导出类
export class Person{
constructor(){
this.name = 'jam';
}
showName(){
return this.name;
}
}
//index.js
//导出模块如果用default了,引入的时候直接用,若没有用default,引入的时候可以用{}的形式
// 导入模块的方式
import {
a,
b,
json,
show,
Person
} from './mod.js';
console.log(a); // 12
console.log(b); // 5
console.log(json.a); // 12
console.log(json.b); // 5
console.log(show()); // welcome
console.log(new Person().showName()); // jam
//mod1.js
// 第二种模块导出的书写方式
let a = 12;
let b = 5;
let c = 10;
export {
a,
b,
c as cc // as是别名,使用的时候只能用别名,特别注意下
};
//index1.js
// 导入模块的方式
import {
a,
b,
cc // cc是导出的,as别名
} from './mod1.js';
console.log(a); // 12
console.log(b); // 5
console.log(cc); // 10
//mod2.js
// 第三种模块导出的书写方式 ---> default
// default方式的优点,import无需知道变量名,就可以直接使用,如下
// 每个模块只允许一个默认出口
var name = 'jam';
var age = '28';
export default {
name,
age,
default(){
console.log('welcome to es6 module of default...');
},
getName(){
return 'bb';
},
getAge(){
return 2;
}
};
//index2.js
// 导入模块的方式
import mainAttr from './mod2.js';
var str = ' ';
// 直接调用
console.log(`我的英文名是:${mainAttr.name}我的年龄是${mainAttr.age}`);
mainAttr.default(); // welcome to es6 module of default...
console.log(mainAttr.getName()); // bb
console.log(mainAttr.getAge()); // 2
//mod3.js
var name = 'jam';
var age = '28';
export function getName(){
return name;
};
export function getAge(){
return age;
};
//index3.js
// 导入模块的方式
import * as fn from './mod3.js';
// 直接调用
console.log(fn.getName()); // jam
在promise之前代码过多的回调或者嵌套,可读性差、耦合度高、扩展性低。通过Promise机制,扁平化的代码机构,大大提高了代码可读性;用同步编程的方式来编写异步代码,保存线性的代码逻辑,极大的降低了代码耦合性而提高了程序的可扩展性。
就是用同步的方式去写异步代码。
//Promise对象 ---> 用来传递异步操作过来的数据的
//Pending(等待、处理中) ---> Resolve(完成,fullFilled) ---> Reject(拒绝,失败)
//这里只是定义,还没开始执行
var p1 = new Promise(function(resolve,reject){
resolve(1); // 成功了,返回一个promise对象1
// reject(2); // 失败了
});
// 接收成功和失败的数据,通过then来传递
// then也是返回一个promise对象,会继续往下传递数据,传递给下一个then
p1.then(function(value){
// resolve
console.log(value); //执行打印1
return value + 1; // 1
alert(`成功了:${value}`);
},function(value){
// reject
alert(`失败了:${value}`);
}).then(function(value){
console.log(value); // 2
});
//catch捕获异常错误
var p1 = new Promise(function(resolve,reject){
resolve('成功了'); //返回一个promise对象“成功了”
});
//then也是返回一个promise对象,会继续往下传递数据
p1.then(function(value){
console.log(value); //打印“成功了”
// throw是用来抛错误的
throw '发生了点小意外';
}).catch(function(e){
// catch用来捕获这个错误的 ---> 追踪
console.log(e);
});
//all ---> 全部,用于将多个promise对象,组合,包装成
//Promise.all([p1,p2,p3,...]); 所有的promise对象,都正确,才走成功
//否则,只要有一个错误,就走失败
var p1 = Promise.resolve(1);
var p2 = Promise.reject(0);
Promise.all([true,p1,p2]).then(function(obj){
console.log(`成功了:${obj}`);
},function(obj){
console.log(`失败了:${obj}`);
});
// race ---> 返回的也是一个promise对象
//最先执行的的promise结果,哪个最快我用哪个,所以下面打印的是one
var p1 = new Promise(function(resolve,reject){
setTimeout(resolve,50,'one');
});
var p2 = new Promise(function(resolve,reject){
setTimeout(resolve,100,'two');
});
Promise.race([p1,p2]).then(function(val){
console.log(val);
});
//resolve ---> 生成一个成功的promise对象
//语法规则:Promise.resolve(val); // 普通值
// Promise.resolve(arr); // 数组之类
//Promise.resolve(promise); // 传递另一个promise对象
//传递普通值
Promise.resolve('success').then(function(val){
// 注意resolve,走得是这里
console.log(val); // success
},function(err){
console.log("err:"+ err);
});
//传递数组
Promise.resolve([1,2,3]).then(function(val){
// 注意resolve,走得是这里
console.log(val); // [1,2,3]
},function(err){
console.log(err);
});
//传递一个promise对象
var p1 = Promise.resolve(520);
var p2 = Promise.resolve(p1);
p2.then(function(val){
//从p1那边传递过来的
console.log(val); // 520
});
再来一道经典面试题:
setTimeout(function() {
console.log(1)
}, 0);
new Promise(function executor(resolve) {
console.log(2);
for( var i=0 ; i<10000 ; i++ ) {
i == 9999 && resolve();
}
console.log(3);
}).then(function() {
console.log(4);
});
console.log(5);
首先先碰到一个 setTimeout,于是会先设置一个定时,在定时结束后将传递这个函数放到任务队列里面,因此开始肯定不会输出 1 。
然后是一个 Promise,里面的函数是直接执行的,因此应该直接输出 2 3 。
然后,Promise 的 then 应当会放到当前 tick 的最后,但是还是在当前 tick 中。
因此,应当先输出 5,然后再输出 4 。
最后在到下一个 tick,就是 1 。
“2 3 5 4 1”
生成器( generator)是能返回一个迭代器的函数。生成器函数也是一种函数,最直观的表现就是比普通的function多了个星号*,在其函数体内可以使用yield关键字,有意思的是函数会在每个yield后暂停。
这里生活中有一个比较形象的例子。咱们到银行办理业务时候都得向大厅的机器取一张排队号。你拿到你的排队号,机器并不会自动为你再出下一张票。也就是说取票机“暂停”住了,直到下一个人再次唤起才会继续吐票。
当你调用一个generator时,它将返回一个迭代器对象。这个迭代器对象拥有一个叫做next的方法来帮助你重启generator函数并得到下一个值。next方法不仅返回值,它返回的对象具有两个属性:done和value。value是你获得的值,done用来表明你的generator是否已经停止提供值。继续用刚刚取票的例子,每张排队号就是这里的value,打印票的纸是否用完就这是这里的done。
看个例子:
// 生成器
function *createIterator() {
yield 1;
yield 2;
yield 3;
}
// 生成器能像正规函数那样被调用,但会返回一个迭代器
let iterator = createIterator();
console.log(iterator.next().value); // 1
console.log(iterator.next().value); // 2
console.log(iterator.next().value); // 3
console.log(iterator.next().value); // undefined 因为generator已经停止提供值
总结:
//Generator ---> 生成器就是一个函数
//特点:
//1.函数名前面带一个*,和普通函数做区分
//2.内部使用yield语句
//调用方式,如下var res = show(); 与普通函数一样
//value指的是generator函数内容yield定义的值,done:false表示还没遍历完
//直接找到返回值return了,那么此时done才会为true
//console.log(res.next());{value:'值1',done:false}
function* show(){
yield 'Hello';
yield 'World';
yield 'ES6';
return 'xx';
}
var res = show();
console.log(res.next()); // {value: "Hello", done: false}
console.log(res.next()); // {value: "World", done: false}
console.log(res.next()); // {value: "ES6", done: false}
console.log(res.next()); // {value: "allen", done: true}
// 已经找到return返回值了,继续下去就没有意义了
// console.log(res.next()); // {value: "undefined", done: true}
//yield本身没有返回值,或者可以说每次给你返回的是undefined
function* show(){
var a = yield 'Hello';
return a;
}
var res = show();
console.log(res.next()); // {value: "Hello", done: false}
console.log(res.next()); // {value: "undefined", done: true}
//next方法是可以带参数的,死循环的generator函数
function* fn(){
for(var i=0;true;i++){
// 如果里面传了一个值,那么它会把这个参数赋给最近的一个yield
var a = yield i;
if(a) i = -1;
}
}
var d = fn();
console.log(d.next()); // {value: 0, done: false}
console.log(d.next()); // {value: 1, done: false}
console.log(d.next()); // {value: 2, done: false}
// 如果里面传了一个值,那么它会把这个参数赋最近的一个yield
console.log(d.next(true)); // {value: 0, done: false}
console.log(d.next()); // {value: 1, done: false}
console.log(d.next()); // {value: 2, done: false}
console.log(d.next()); // {value: 3, done: false}
// for..0f循环generator函数
function* fn(){
yield 1;
yield 2;
yield 3;
yield 4;
yield 5;
return 6;
}
//for..0f循环generator函数,可以取值
for(let val of fn()){
document.write(val); // 12345
}
// 对象里使用generator函数的特殊写法,注意下
var json = {
*show(){
yield 'a';
yield 'b';
return 'c';
}
};
var res = json.show();
console.log(res.next()); // {value: "a", done: false}
console.log(res.next()); // {value: "b", done: false}
console.log(res.next()); // {value: "c", done: true}
自动调用生成器并启动迭代器的方法:
function run(taskDef) { //taskDef即一个生成器函数
// 创建迭代器,让它在别处可用
let task = taskDef();
// 启动任务
let result = task.next();
// 递归使用函数来保持对 next() 的调用
function step() {
// 如果还有更多要做的
if (!result.done) {
console.log(result.value); //这里就执行该做的事
result = task.next();
step();
}
}
// 开始处理过程
step();
}
//生成器
function *createIterator() {
yield 1;
yield 2;
yield 3;
}
//启动
run(createIterator);