ECMAScript5,即ES5,是ECMAScript的第五次修订,于2009年完成标准化
ECMAScript6,即ES6,是ECMAScript的第六次修订,于2015年完成,也称ES2015
ES6是继ES5之后的一次改进,相对于ES5更加简洁,提高了开发效率
ES6新增的一些特性:
let声明变量和const声明常量,两个都有块级作用域
ES5中是没有块级作用域的,并且var有变量提升,在let中,使用的变量一定要进行声明
箭头函数
中的函数定义不再使用关键字function(),而是利用了()=>来进行定义
模板字符串
模板字符串是增强版的字符串,用反引号(`)标识,可以当作普通字符串使用,也可以用来定义多行字符串
解构赋值
ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值
or of循环
for…of循环可以遍历数组、Set和Map结构、某些类似数组的对象、对象,以及字符串
import、export导入导出
ES6标准中,Js原生支持模块(module)。将JS代码分割成不同功能的小块进行模块化,将不同功能的代码分别写在不同文件中,各模块只需导出公共接口部分,然后通过模块的导入的方式可以在其他地方使用
set数据结构
Set数据结构,类似数组。所有的数据都是唯一的,没有重复的值。它本身是一个构造函数
… 展开运算符
可以将数组或对象里面的值展开;还可以将多个值收集为一个变量
修饰器 @
decorator是一个函数,用来修改类甚至于是方法的行为。修饰器本质就是编译时执行的函数
class 类的继承
ES6中不再像ES5一样使用原型链实现继承,而是引入Class这个概念
async、await
使用 async/await, 搭配promise,可以通过编写形似同步的代码来处理异步流程, 提高代码的简洁性和可读性
async 用于申明一个 function 是异步的,而 await 用于等待一个异步方法执行完成
promise
Promise是异步编程的一种解决方案,比传统的解决方案(回调函数和事件)更合理、强大
Symbol
Symbol是一种基本类型。Symbol 通过调用symbol函数产生,它接收一个可选的名字参数,该函数返回的symbol是唯一的
Proxy代理
使用代理(Proxy)监听对象的操作,然后可以做一些相应事情
var声明变量可以重复声明,而let不可以重复声明
var是不受限于块级的,而let是受限于块级
var会与window相映射(会挂一个属性),而let不与window相映射
var可以在声明的上面访问变量,而let有暂存死区,在声明的上面访问变量会报错
const声明之后必须赋值,否则会报错
const定义不可变的量,改变了就会报错
const和let一样不会与window相映射、支持块级作用域、在声明的上面访问变量会报错
let 定义的变量不会被变量提升,const 定义的常量不能被修改,let 和 const 都是块级作用域
ES6前,js 是没有块级作用域 {} 的概念的。(有函数作用域、全局作用域、eval作用域)
ES6后,let 和 const 的出现,js 也有了块级作用域的概念
变量提升:在ES6以前,var关键字声明变量。无论声明在何处,都会被视为声明在函数的最顶部;不在函数内即在全局作用域的最顶部。这样就会引起一些误解。例如:
console.log(a); // undefined
var a = 'hello';
# 上面的代码相当于
var a;
console.log(a);
a = 'hello';
# 而 let 就不会被变量提升
console.log(a); // a is not defined
let a = 'hello';
const 定义的常量不能被修改
var name = "bai";
name = "ming";
console.log(name); // ming
const name = "bai";
name = "ming"; // Assignment to constant variable.
console.log(name);
let声明的变量具有块级作用域
let声明的变量不能通过window.变量名进行访问
形如for(let x…)的循环是每次迭代都为x创建新的绑定
/*下面是var带来的不合理场景 变量i是var声明的,
在全局范围类都有效,所以用来计数的循环变量泄露为全局变量。
所以每一次循环,新的i值都会覆盖旧值,导致最后输出都是10*/
var arr = [];
for (var i = 0; i < 10; i++) {
arr[i] = function () {
console.log(i);
}
}
arr[5]() //10,a[5]输出f(){console.log(i);},后面加个括号代表执行f()
/*而如果对循环使用let语句的情况,
那么每次迭代都是为x创建新的绑定代码如下:*/
var arr = [];
for (let i = 0; i < 10; i++) {
arr[i] = function () {
console.log(i);
}
}
arr[5]() //5,a[5]输出f(){console.log(i);}后面加个括号代表执行f()
【拓展】
当然,除了这种方式让数组找中的各个元素分别是不同的函数,我们还可以采用ES5中的闭包和立即函数两种方法。
function showNum(i) {
return function () {
console.log(i)
}
}
var a = []
for (var i = 0; i < 5; i++) {
a[i] = showNum(i)(); //循环输出1,2,3,4
}
var a = []
for (var i = 0; i < 5; i++) {
a[i] = (function (i) {
return function () {
console.log(i)
}
})(i)
}
a[2](); //2
相关面试题 把以下代码使用两种方法,依次输出0-9
var funcs = []
for (var i = 0; i < 10; i++) {
funcs.push(function () {
console.log(i)
})
}
funcs.forEach(function (func) {
func(); //输出十个10
})
var funcs = []
for (var i = 0; i < 10; i++) {
funcs.push((function (value) {
return function () {
console.log(value)
}
}(i)))
}
funcs.forEach(function (func) {
func(); //依次输出0-9
})
function show(i) {
return function () {
console.log(i)
}
}
var funcs = []
for (var i = 0; i < 10; i++) {
funcs.push(show(i))
}
funcs.forEach(function (func) {
func(); //0 1 2 3 4 5 6 7 8 9
})
var funcs = []
for (let i = 0; i < 10; i++) {
funcs.push(function () {
console.log(i)
})
}
funcs.forEach(function (func) {
func(); //依次输出0-9
})
forEach() 方法用于调用数组的每个元素,并将元素传递给回调函数。
(1)用了箭头函数,this就不是指向window,而是父级(指向是可变的)
(2)不能够使用arguments对象 ,取而代之用rest参数…解决
(3)不能用作构造函数,这就是说不能够使用new命令,否则会抛出一个错误
(4)不可以使用yield命令,因此箭头函数不能用作 Generator 函数
箭头函数表达式的语法比函数表达式更短,并且不绑定自己的this,arguments,super或 new.target。
这些函数表达式最适合用于非方法函数,并且它们不能用作构造函数,不能使用new。
#箭头函数的写法 function () { } 变成 `() => { }
var a = ()=>{
return 1;
}
#等价于
function a(){
return 1;
}
#如果函数体只有一条语句,可以这样写:这样子调用这个箭头函数就会直接返回这条语句的值
var fun = ()=>Math.random()*10
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); //[object Arguments] [1, 2, 3]
B(2,1,3); //错误:ReferenceError: arguments is not defined
C(3,1,2); //[3, 1, 2]
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 捕获的是所在的上下文,比如下面这个例子:
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(); // `b`是一个箭头函数,然后它的 `this`是指向`window` ? 因为箭头函数捕获的是`obj{}`这个对象的环境,然后这个环境的`this`指向的是`window`
obj.c(); // 在`c`方法里面`return`的那个箭头函数捕获的是`c:function(){}`这个环境的`this`,而这个环境的`this`是`obj`
this
指向问题:this
永远指向其上下文的 this
,任何方法都改变不了其指向,如call(), bind(), apply()
this
指向调用它的那个对象基本的字符串格式化。将表达式嵌入字符串中进行拼接。用${}来界定
在ES5时我们通过反斜杠()来做多行字符串或者字符串一行行拼接。ES6反引号(``)就能解决
类模板字符串的功能
let name = 'web';
let age = 10;
let str = '你好,${name} 已经 ${age}岁了'
str = str.replace(/\$\{([^}]*)\}/g,function(){
return eval(arguments[1]);
})
console.log(str);//你好,web 已经 10岁了
//传统字符串拼接
var s1 = '生物膜系统组装又拆分,变幻莫测;';
var s2 = '你的好多细胞在分裂,';
var str = '孩子们:请听我说!'+s2+'有丝,减数,哪管白天和黑夜。'+
'染色体,细胞核时隐时现,'+s1+'核糖体在mRNA上穿梭忙碌,'+'几千种酶各司其职,将活化能狠狠打折。';
console.log(str);
// 字符模板的写法
var s1 = '染色体,细胞核时隐时现,';
var s2 = '你的好多细胞在分裂,';
var str = `孩子们:请听我说!${s2}有丝,减数,哪管白天和黑夜。${s1}生物膜系统组装又拆分,变幻莫测;核糖体在mRNA上穿梭忙碌,几千种酶各司其职,将活化能狠狠打折。`;
console.log(str);
应用场景Set用于数据重组,Map用于数据储存
Set:
(1)成员不能重复
(2)只有键值没有键名,类似数组
(3)可以遍历,方法有add, delete,has
Map:
(1)本质上是健值对的集合,类似集合
(2)可以遍历,可以跟各种数据格式转换
ES6的class可以看作是一个语法糖,它的绝大部分功能ES5都可以做到,新的class写法只是让对象原型的写法更加清晰、更像面向对象编程的语法
//定义类
class Point {
constructor(x,y) {
//构造方法
this.x = x; //this关键字代表实例对象
this.y = y;
}
toString() {
return '(' + this.x + ',' + this.y + ')';
}
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
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()); // 默认给的值 ‘啦啦啦’
//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); // 2 3 5 4 1
#首先先碰到一个 setTimeout,于是会先设置一个定时,在定时结束后将传递这个函数放到任务队列里面,因此开始肯定不会输出 1。
# 然后是一个 Promise,里面的函数是直接执行的,因此应该直接输出 2 3 。
# 然后,Promise 的 then 应当会放到当前 tick 的最后,但是还是在当前 tick 中。
# 因此,应当先输出 5,然后再输出 4 。
# 最后在到下一个 tick,就是 1 。
console.log('script start');
setTimeout(function() {
console.log('setTimeout');
}, 0);
Promise.resolve().then(function() {
console.log('promise1');
}).then(function() {
console.log('promise2');
});
console.log('script end');
// 输出:script start, script end, promise1, promise2, setTimeout
# promise是ES6语言标准提供的,定时器是宿主环境提供的,所以promise会比定时器更早执行。
宏任务和微任务 : 宿主环境提供的叫宏任务,由语言标准提供的叫微任务,
宿主环境:
简单来说就是能使javascript完美运行的环境,只要能完美运行javascript的载体就是javascript的宿主环境。目前我们常见的两种宿主环境有浏览器和node。
我们都知道window是我们一直使用的全局对象,但其实global 是 javascript 运行时所在宿主环境提供的全局对象,在node出生前这个对象一直都存在于概念里。直到node的出现才使我们真正看到了global。
global 是 javascript 运行时所在宿主环境提供的全局对象,在浏览器中,没有实现global对象,而是通过window对象来指向global对象,代替global成为全局对象。因为浏览器暴露了一系列操作 DOM, Location, History 等 Api 供 javascript 调用,而这些操作对象在global中是不存在的。对于node来说,它不需要DOM这些操作,用到的只是javascript的原生功能。
宿主环境内所有的内建或自定义的变量/函数都是 global/window 这个全局对象的属性/方法,而由宿主环境提供的也叫宏任务。
语言标准:
我们都知道JavaScript是一种编程语言,但其实JavaScript由ECMA制定标准,称之为ECMAScript,所以由语言标准提供的就是微任务,比如ES6提供的promise。
当然,不同浏览器不同环境对于这两个的概念会有不同,相同的代码在不同浏览器执行会有不同的顺序,在不同浏览器也会有不同的顺序。想要深入研究的可以自行查一查资料。
三个状态:pending、fulfilled、reject
两个过程:padding -> fulfilled、padding -> rejected
当pending为rejectd时,会进入catch
const promise = new Promise((resolve, reject) => {
console.log(1);
resolve();
console.log(2);
})
promise.then(() => {
console.log(3);
})
console.log(4);//1 2 4 3
Promise 新建后立即执行,所以会先输出 1,2,
而 Promise.then()
内部的代码在 当次 事件循环的 结尾 立刻执行 ,所以会继续输出4,最后输出3
let a = 1;let b = 2;
[a,b] = [b,a];
let name = Symbol('name');
let product = {
[name]:"洗衣机",
"price":799
};
Reflect.ownKeys(product);
let s = new Set();
s.add([1]);
s.add([1]);console.log(s.size);//答案:2
两个数组[1]并不是同一个值,它们分别定义的数组,在内存中分别对应着不同的存储地址,因此并不是相同的值
都能存储到Set结构中,所以size为2
reject 是用来抛出异常,catch 是用来处理异常
reject 是 Promise 的方法,而 catch 是 Promise 实例的方法
reject后的东西,一定会进入then中的第二个回调,如果then中没有写第二个回调,则进入catch
网络异常(比如断网),会直接进入catch而不会进入then的第二个回调
//创建一个Promise的类
class Promise {
constructor(executer) {
//构造函数constructor里面是个执行器
this.status = 'pending'; //默认的状态 pending
this.value = undefined //成功的值默认undefined
this.reason = undefined //失败的值默认undefined
//状态只有在pending时候才能改变
let resolveFn = value => {
//判断只有等待时才能resolve成功
if (this.status == pending) {
this.status = 'resolve';
this.value = value;
}
}
//判断只有等待时才能reject失败
let rejectFn = reason => {
if (this.status == pending) {
this.status = 'reject';
this.reason = reason;
}
}
try {
//把resolve和reject两个函数传给执行器executer
executer(resolve, reject);
} catch (e) {
reject(e); //失败的话进catch
}
}
then(onFufilled, onReject) {
//如果状态成功调用onFufilled
if (this.status = 'resolve') {
onFufilled(this.value);
}
//如果状态失败调用onReject
if (this.status = 'reject') {
onReject(this.reason);
}
}
}
let arr = [12, 43, 23, 43, 68, 12];
let item = [...new Set(arr)];
console.log(item); //[12, 43, 23, 68]
let arr = [11, 22, 33, 44, 55];
let sum = 0;
for (let i = 0; i < arr.length; i++) {
sum += arr[i];
}
答案:
let arr = [11, 22, 33, 44, 55];
let sum = 0;
for (value of arr) {
sum += value;
}
async await 是用来解决异步的,async函数是Generator函数的语法糖
使用关键字async来表示,在函数内部使用 await 来表示异步
async函数返回一个 Promise 对象,可以使用then方法添加回调函数
当函数执行的时候,一旦遇到await就会先返回,等到异步操作完成,再接着执行函数体内后面的语句
async较Generator的优势:
(1)内置执行器。Generator 函数的执行必须依靠执行器,而 Aysnc 函数自带执行器,调用方式跟普通函数的调用一样
(2)更好的语义。async 和 await 相较于 * 和 yield 更加语义化
(3)更广的适用性。yield命令后面只能是 Thunk 函数或 Promise对象,async函数的await后面可以是Promise也可以是原始类型的值
(4)返回值是 Promise。async 函数返回的是 Promise 对象,比Generator函数返回的Iterator对象方便,可以直接使用 then() 方法进行调用
forEach更多的用来遍历数组
for in 一般常用来遍历对象或json
for of数组对象都可以遍历,遍历对象需要通过和Object.keys()
for in循环出的是key,for of循环出的是value
// 全部导入
import people from './example'
// 将整个模块当作单一对象进行导入,该模块的所有导出都会作为对象的属性存在
import * as example from "./example.js"
console.log(example.name)
console.log(example.getName())
// 导入部分,引入非 default 时,使用花括号
import {name, age} from './example'
// 导出默认, 有且只有一个默认
export default App
// 部分导出
export class App extend Component {};
导入通过import关键字
// 只导入一个
import {
sum
} from "./example.js"
// 导入多个
import {
sum,
multiply,
time
} from "./exportExample.js"
// 导入一整个模块
import * as example from "./exportExample.js"
导出通过export关键字
//可以将export放在任何变量,函数或类声明的前面
export var firstName = 'Michael';
export var lastName = 'Jackson';
export var year = 1958;
//也可以使用大括号指定所要输出的一组变量
var firstName = 'Michael';
var lastName = 'Jackson';
var year = 1958;
export {
firstName,
lastName,
year
};
//使用export default时,对应的import语句不需要使用大括号
let bosh = function crs() {}
export default bosh;
import crc from 'crc';
//不使用export default时,对应的import语句需要使用大括号
let bosh = function crs() {}
export bosh;
import {
crc
} from 'crc';