ES6基础知识
一、声明 let、const
1. let
1). 作用域是块级作用域(在ES6之前,js只存在函数作用域以及全局作用域)
if(1){ let a=1; console.log(a) }
2). 不存在变量声明提前;
console.log(b); //ReferenceError: b is not defined let b=2;
3). 不能重复定义
let a=1; let a=2; console.log(a);//Identifier 'a' has already been declared
4). 存在暂时性死区:可以这样来理解
var a=1; if(1){ console.log(a); let a=2; }
① 在一个块级作用域中,变量唯一存在,一旦在块级作用域中用let声明了一个变量,那么这个变量就唯一属于这个块级作用域,不受外部变量的影响;
② 无论在块中的任何地方声明了一个变量,那么在这个块级作用域中,任何使用这个名字的变量都是指这个变量,无论外部是否有其他同名的全局变量;
③ 暂时性死区的本质就是,只要一进入当前作用域,所要使用的变量就已经存在了,但是不可获取,只有等到声明变量的那一行代码出现,才可以获取和使用该变量。
④ 暂时性死区的意义:让我们标准化代码。将所有的变量的声明放在作用域的最开始。
2. const
const一般用来声明常量,且声明的常量是不允许改变的,只读属性,因此就要在声明的同时赋值。const与let一样,都是块级作用域,存在暂时性死区,不存在变量声明提前,不允许重复定义
const A=1;//重新给常量A赋值会报错 A=3;// Uncaught TypeError: Assignment to constant variable. //错误:赋值给常量
二、解构赋值(es6允许按照一定的模式,从数组或对象中提取值,给变量进行赋值)
//解构赋值,两边格式要一致 let [a,b,c] = [1,2,3];let [a,[b,c]] = [1,[2,3]]; //交互数据 let a = 10; let b = 20; [a,b] = [b,a];
三、声明类与继承:class、extend
用class关键字定义对象类型,用extends关键字实现继承
const private2 = Symbol('I am symbol value') class A { a1 = '1' // ES7 实例属性,需要new实例来访问, ES6规定class没有静态属性,只有静态方法所以只能在constructor中定义属性 static a2 = '2' // ES7的静态属性,直接 A.a2 访问,不需要new实例 getA1() { return this.a1 // this指向new实例 } static getA2() { return ‘2’ // 静态方法 } constructor(name) { //一定要有构造方法,如果没有默认生成空构造方法 this.a3 = '3' // 这里定义实例属性 this.name = name } // 私有方法写法 publicMethod() { private1() // 私有方法1,可以写在class体外 private2() // 利用Symbol值来定义 } [private2]() { // 这里是私有方法 } } const private1 = function() { // 这里也是私有方法,但别export出去} // 最后export class export default A // 通过extends继承 class B extends A{ constructor() { // 一定要在构造函数的第一句调用super super() // 这是调用父类的构造方法 this.b1 = '11' this.b2 = super.a1 // super直接调用时指向父类构造方法,范围属性时,指向父类实例,或调用父类静态方法 } }
四、Promise的使用与实现
Promise是JS异步编程中的重要概念,异步抽象处理对象,是目前比较流行Javascript异步编程解决方案之一。
1. Promise 处理多个相互关联的异步请求
const request = url => { return new Promise((resolve, reject) => { $.get(url, data => { resolve(data) }); }) }; // 请求data1 request(url).then(data1 => { return request(data1.url); }).then(data2 => { return request(data2.url); }).then(data3 => { console.log(data3); }).catch(err => throw new Error(err));
2. Promise使用
2.1 Promise 是一个构造函数, new Promise 返回一个 promise对象 接收一个excutor执行函数作为参数, excutor有两个函数类型形参resolve reject
const promise = new Promise((resolve, reject) => { // 异步处理 // 处理结束后、调用resolve 或 reject });
2.2 promise相当于一个状态机
promise的三种状态
- pending
- fulfilled
- rejected
①promise 对象初始化状态为 pending
②当调用resolve(成功),会由pending => fulfilled
③当调用reject(失败),会由pending => rejected
注意:promsie状态 只能由 pending => fulfilled/rejected, 一旦修改就不能再变
2.3 promise对象方法
1)then方法注册 当resolve(成功)/reject(失败)的回调函数,then方法是异步执行的
// onFulfilled 是用来接收promise成功的值 // onRejected 是用来接收promise失败的原因 promise.then(onFulfilled, onRejected);
2)resolve(成功) onFulfilled会被调用
const promise = new Promise((resolve, reject) => { resolve('fulfilled'); // 状态由 pending => fulfilled }); promise.then(result => { // onFulfilled console.log(result); // 'fulfilled' }, reason => { // onRejected 不会被调用 })
3)reject(失败) onRejected会被调用
const promise = new Promise((resolve, reject) => { reject('rejected'); // 状态由 pending => rejected }); promise.then(result => { // onFulfilled 不会被调用 }, reason => { // onRejected console.log(rejected); // 'rejected' })
4)promise.catch
在链式写法中可以捕获前面then中发送的异常
promise.catch(onRejected) 相当于 promise.then(null, onRrejected); // 注意 // onRejected 不能捕获当前onFulfilled中的异常 promise.then(onFulfilled, onRrejected); // 可以写成: promise.then(onFulfilled) .catch(onRrejected);
3. promise chain
promise.then方法每次调用 都返回一个新的promise对象 所以可以链式写法
function taskA() { console.log("Task A"); } function taskB() { console.log("Task B"); } function onRejected(error) { console.log("Catch Error: A or B", error); } var promise = Promise.resolve(); promise .then(taskA) .then(taskB) .catch(onRejected) // 捕获前面then方法中的异常
4. Promise的静态方法
1)Promise.resolve 返回一个fulfilled状态的promise对象
Promise.resolve('hello').then(function(value){ console.log(value); }); Promise.resolve('hello'); // 相当于 const promise = new Promise(resolve => { resolve('hello'); });
2)Promise.reject 返回一个rejected状态的promise对象
Promise.reject(24); new Promise((resolve, reject) => { reject(24); });
3)Promise.all 接收一个promise对象数组为参数
只有全部为resolve才会调用 通常会用来处理 多个并行异步操作
const p1 = new Promise((resolve, reject) => { resolve(1); }); const p2 = new Promise((resolve, reject) => { resolve(2); }); const p3 = new Promise((resolve, reject) => { reject(3); }); Promise.all([p1, p2, p3]).then(data => { console.log(data); // [1, 2, 3] 结果顺序和promise实例数组顺序是一致的 }, err => { console.log(err); });
4)Promise.race 接收一个promise对象数组为参数
Promise.race 只要有一个promise对象进入 FulFilled 或者 Rejected 状态的话,就会继续进行后面的处理
function timerPromisefy(delay) { return new Promise(function (resolve, reject) { setTimeout(function () { resolve(delay); }, delay); }); } var startDate = Date.now(); Promise.race([ timerPromisefy(10), timerPromisefy(20), timerPromisefy(30) ]).then(function (values) { console.log(values); // 10 });
5. promise实现
/** * 摘自https://www.cnblogs.com/minigrasshopper/p/9141307.html * Promise类实现原理 * 构造函数传入一个function,有两个参数,resolve:成功回调; reject:失败回调 * state: 状态存储 [PENDING-进行中 RESOLVED-成功 REJECTED-失败] * doneList: 成功处理函数列表 * failList: 失败处理函数列表 * done: 注册成功处理函数 * fail: 注册失败处理函数 * then: 同时注册成功和失败处理函数 * always: 一个处理函数注册到成功和失败 * resolve: 更新state为:RESOLVED,并且执行成功处理队列 * reject: 更新state为:REJECTED,并且执行失败处理队列 **/ class PromiseNew { constructor(fn) { this.state = 'PENDING'; this.doneList = []; this.failList = []; fn(this.resolve.bind(this), this.reject.bind(this)); } // 注册成功处理函数 done(handle) { if (typeof handle === 'function') { this.doneList.push(handle); } else { throw new Error('缺少回调函数'); } return this; } // 注册失败处理函数 fail(handle) { if (typeof handle === 'function') { this.failList.push(handle); } else { throw new Error('缺少回调函数'); } return this; } // 同时注册成功和失败处理函数 then(success, fail) { this.done(success || function () { }).fail(fail || function () { }); return this; } // 一个处理函数注册到成功和失败 always(handle) { this.done(handle || function () { }).fail(handle || function () { }); return this; } // 更新state为:RESOLVED,并且执行成功处理队列 resolve() { this.state = 'RESOLVED'; let args = Array.prototype.slice.call(arguments); setTimeout(function () { this.doneList.forEach((item, key, arr) => { item.apply(null, args); arr.shift(); }); }.bind(this), 200); } // 更新state为:REJECTED,并且执行失败处理队列 reject() { this.state = 'REJECTED'; let args = Array.prototype.slice.call(arguments); setTimeout(function () { this.failList.forEach((item, key, arr) => { item.apply(null, args); arr.shift(); }); }.bind(this), 200); } } // 下面一波骚操作 new PromiseNew((resolve, reject) => { resolve('hello world'); // reject('you are err'); }).done((res) => { console.log(res); }).fail((res) => { console.log(res); })
五、generator(异步编程、yield、next()、await 、async)
使用Generator
可以很方便的帮助我们建立一个处理Promise
的解释器;
async
/await
这样的语法,可以让我们以接近编写同步代码的方式来编写异步代码(无需使用.then()
或者回调函数)
1. Generator
Generator
是一个函数,可以在函数内部通过yield
返回一个值(此时,Generator
函数的执行会暂定,直到下次触发.next()
) 创建一个Generator
函数的方法是在function
关键字后添加*
标识。
在调用一个Generator
函数后,并不会立即执行其中的代码,函数会返回一个Generator
对象,通过调用对象的next
函数,可以获得yield
/return
的返回值。 无论是触发了yield
还是return
,next()
函数总会返回一个带有value
和done
属性的对象。 value
为返回值,done
则是一个Boolean
对象,用来标识Generator
是否还能继续提供返回值。 P.S. Generator
函数的执行时惰性的,yield
后的代码只在触发next
时才会执行。
function * oddGenerator () { yield 1 yield 3 return 5 } let iterator = oddGenerator() let first = iterator.next() // { value: 1, done: false } let second = iterator.next() // { value: 3, done: false } let third = iterator.next() // { value: 5, done: true }
2. Async
function getRandom () { return new Promise(resolve => { setTimeout(_ => resolve(Math.random() * 10 | 0), 1000) }) } async function main () { let num1 = await getRandom() let num2 = await getRandom() return num1 + num2 } console.log(`got data: ${await main()}`)
Async函数始终返回一个Promise
async function throwError () { throw new Error() } async function returnNumber () { return 1 } console.log(returnNumber() instanceof Promise) // true console.log(throwError() instanceof Promise) // true
Await是按照顺序执行的,并不能并行执行,JavaScript
是单线程的,这就意味着await
一只能一次处理一个,如果你有多个Promise
需要处理,则就意味着,你要等到前一个Promise
处理完成才能进行下一个的处理,这就意味着,如果我们同时发送大量的请求,这样处理就会非常慢。
function delay () { return new Promise(resolve => setTimeout(resolve, 1000)) } let tasks = [1, 2, 3, 4] //要4s才能执行完 async function runner (tasks) { for (let task of tasks) { await delay() } } //优化,缩短执行时间 async function runner (tasks) { tasks = tasks.map(delay) await Promise.all(tasks) } console.time('runner') await runner(tasks) console.timeEnd('runner')
Generator
与async function
都是返回一个特定类型的对象:
Generator
: 一个类似{ value: XXX, done: true }
这样结构的Object
Async
: 始终返回一个Promise
,使用await
或者.then()
来获取返回值
Generator
是属于生成器,一种特殊的迭代器,用来解决异步回调问题感觉有些不务正业了。。 而async
则是为了更简洁的使用Promise
而提出的语法,相比Generator + co
这种的实现方式,更为专注,生来就是为了处理异步编程。
六、箭头函数this指向问题、拓展运算符
// 两个参数: (x, y) => x * x + y * y // 无参数: () => 3.14 // 可变参数: (x, y, ...rest) => { var i, sum = x + y; for (i=0; i<rest.length; i++) { sum += rest[i]; } return sum; }
1. 不绑定this
var obj = { age: 1, say: function() { setTimeout(function() { console.log(this, this.age); // window undefined }, 0); }, } var obj1 = { age: 1, say: function() { setTimeout(() => { console.log(this, this.age); // obj1 1 }, 0); } };
这里可以看出箭头函数中访问的this实际上是其父级作用域中的this,箭头函数本身的this是不存在的,这样就相当于箭头函数的this是在声明的时候就确定了(即this
总是指向词法作用域,也就是外层调用者handler),这个特性是很有用的,所以,用call()
或者apply()
调用箭头函数时,无法对this
进行绑定,即传入的第一个参数被忽略。
var handler = { id: '111', doSomething: function(e) { console.log(e); }, init: function() { document.addEventListener('click', (event) => { // 这里绑定事件,函数this就可以访问到handler的方法doSomething this.doSomething(event); }, false); } } handler.init();
2. 不可以作为构造函数来使用
var Person = (name) => { // Uncaught TypeError: Person is not a constructor this.name = name; } var person = new Person('Jack');
3. 不绑定arguments(如果有要使用arguments的时候可以使用rest参数代替)
var foo = (val) => { console.log(arguments); // Uncaught ReferenceError: arguments is not defined }; foo(); //这个特性也很好测试,但是实在要使用arguments对象要怎么办呢?我们可以使用es6的另一个新特性rest参数,完美替代 var foo = (...args) => { console.log(args); // [1, 2, 3] }; foo(1, 2, 3);
4. 不可以使用yield命令,因此箭头函数不能用作Generator函数
七、map和set有没有用过,如何实现一个数组去重,map数据结构有什么优点?
JavaScript的默认对象表示方式{}
可以视为其他语言中的Map
或Dictionary
的数据结构,即一组键值对。但是JavaScript的对象有个小问题,就是键必须是字符串。但实际上Number或者其他数据类型作为键也是非常合理的。为了解决这个问题,最新的ES6规范引入了新的数据类型Map。
Map的遍历:
- keys():返回键名的遍历器。
- values():返回键值的遍历器。
- entries():返回所有成员的遍历器。
- forEach():遍历 Map 的所有成员。
- 需要特别注意的是,Map 的遍历顺序就是插入顺序
var m = new Map([['Michael', 95], ['Bob', 75], ['Tracy', 85]]); m.get('Michael'); // 95 //初始化Map需要一个二维数组,或者直接初始化一个空Map。Map具有以下方法: var m = new Map(); // 空Map m.set('Adam', 67); // 添加新的key-value m.set('Bob', 59); m.has('Adam'); // 是否存在key 'Adam': true m.get('Adam'); // 67 m.delete('Adam'); // 删除key 'Adam' m.get('Adam'); // undefined //由于一个key只能对应一个value,所以,多次对一个key放入value,后面的值会把前面的值冲掉 //遍历Map var m = new Map([[1, 'x'], [2, 'y'], [3, 'z']]); m.forEach(function (value, key, map) { console.log(value); });
Set
和Map
类似,也是一组key
的集合,但不存储value
。由于key
不能重复,所以,在Set
中,没有重复的key。
//要创建一个Set,需要提供一个Array作为输入,或者直接创建一个空Set: var s1 = new Set(); // 空Set var s2 = new Set([1, 2, 3]); // 含1, 2, 3 //重复元素在Set中自动被过滤: var s = new Set([1, 2, 3, 3, '3']); s; // Set {1, 2, 3, "3"} //通过add(key)方法可以添加元素到Set中,可以重复添加,但不会有效果: s.add(4); s; // Set {1, 2, 3, 4} s.add(4); s; // 仍然是 Set {1, 2, 3, 4} //通过delete(key)方法可以删除元素: var s = new Set([1, 2, 3]); s; // Set {1, 2, 3} s.delete(3); s; // Set {1, 2} //遍历Set var s = new Set(['A', 'B', 'C']); s.forEach(function (element, sameElement, set) { console.log(element); });
数组去重
//1.for循环嵌套,利用splice去重 function newArr(arr){ for(var i=0;i<arr.length;i++){ for(var j=i+1;j<arr.length;j++) if(arr[i]==arr[j]){ //如果第一个等于第二个,splice方法删除第二个 arr.splice(j,1); j--; } } } return arr; } var arr = [1,1,2,5,6,3,5,5,6,8,9,8]; console.log(newArr(arr)) //2.建新数组,利用indexOf去重 function newArr(array){ //一个新的数组 var arrs = []; //遍历当前数组 for(var i = 0; i < array.length; i++){ //如果临时数组里没有当前数组的当前值,则把当前值push到新数组里面 if (arrs.indexOf(array[i]) == -1){ arrs.push(array[i]) }; } return arrs; } var arr = [1,1,2,5,5,6,8,9,8]; console.log(newArr(arr)) //3.ES6中利用Set去重 function newArr(arr){ return Array.from(new Set(arr)) } var arr = [1,1,2,9,6,9,6,3,1,4,5]; console.log(newArr(arr))
八、ES6怎么编译成ES5,css-loader原理,过程
现在的Chrome浏览器已经支持ES6了,但是有些低版本的浏览器还是不支持ES6的语法,这就需要我们把ES6的语法自动的转变成ES5的语法。Webpack是有自动编译转换能力的,除了Webpack自动编译,还可以使用用Babel来完成。
- 初始化项目
打开终端或者通过cmd打开命令行工具,进入项目目录,输入下边的命令:
npm init -y
-y代表全部默认同意,就不用一次次按回车了。命令执行完成后,会在项目根目录下生产package.json文件。
{ "name": "es6", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "", "license": "ISC" }
可以根据自己的需要进行修改,比如我们修改name的值为es6。
- 全局安装Babel-cli
npm install -g babel-cli - 本地安装babel-preset-es2015 和 babel-cli
npm install --save-dev babel-preset-es2015 babel-cli - 新建.babelrc
在根目录下新建.babelrc文件,并打开录入下面的代码
{ "presets":[ "es2015" ], "plugins":[] }
这个文件我们建立完成后,现在可以在终端输入的转换命令了,这次ES6成功转化为ES5的语法。 babel dist/index.js -o src/index.js
- 简化转化命令:
在学习vue 的时候,可以使用npm run build 直接利用webpack进行打包,在这里也希望利用这种方式完成转换。打开package.json文件,把文件修改成下面的样子。
{ "name": "es6", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "build": "babel src/index.js -o dist/index.js" }, "keywords": [], "author": "", "license": "ISC", "devDependencies": { "babel-cli": "^6.24.1", "babel-preset-es2015": "^6.24.1" } }
修改好后,以后我们就可以使用 npm run build 来进行转换了。
webpack的loaders是一块很重要的组成部分。我们都知道webpack是用于资源打包的,里面的所有资源都是“模块”,内部实现了对模块资源进行加载的机制。但是Webpack本身只能处理 js模块,如果要处理其他类型的文件,就需要使用 loader 进行转换。
Loader 可以理解为是模块和资源的转换器,它本身是一个函数,接受源文件作为参数,返回转换的结果,例如可以使用loader加载器可以快速编译预处理器(less,sass,coffeeScript)。 Loader 可以在require()引用模块的时候添加,也可以在 webpack 全局配置中进行绑定,还可以通过命令行的方式使用。
loader的特性是:
- loaders可以串联,他们应用于管道资源,最后的loader将返回javascript,其它的可返回任意格式(传递给下一个loader)
- loaders 可以同步也可以异步
- loaders在nodejs下运行并且可以做一切可能的事 loader接受参数,可用于配置里
- loaders可以绑定到extension/RegExps 配置
- loaders可以通过npm发布和安装 正常的模块儿可以到处一个
- loader除了 loaders可以访问配置 插件可以给loaders更多的特性
- loaders可以释放任意额外的文件
九、import和export AMD
ES6之前已经出现了js模块加载的方案,最主要的是CommonJS和AMD规范。commonjs主要应用于服务器,实现同步加载,如nodejs。AMD规范应用于浏览器,如requirejs,为异步加载。同时还有CMD规范,为同步加载方案如seaJS。
ES6在语言规格的层面上,实现了模块功能,而且实现得相当简单,完全可以取代现有的CommonJS和AMD规范,成为浏览器和服务器通用的模块解决方案。
ES6模块主要有两个功能:export和import
export用于对外输出本模块(一个文件可以理解为一个模块)变量的接口
import用于在一个模块中加载另一个含有export接口的模块。
CommonJS模块的重要特性是加载时执行,即脚本代码在require的时候,就会全部执行。一旦出现某个模块被“循环加载”就只输出已经执行的部分,还没有执行的部分是不输出的。
ES6模块是动态引用,如果使用import从一个模块加载变量,那些变量不会缓存,而是成为一个指向被加载模块的引用,需要开发者自己保证,真正取值的时候能够取到值
impor/export(都是语法糖啦)最终都是编译为require/exports来执行的
CommonJS规范规定,每个模块内部,module变量代表当前模块,这个变量是一个对象,他的exports属性是对外的接口,加载某个模块,其实是加载该模块module.exports属性。export命令规定的是对外的接口,必须与模块内部的变量建立一一对应的关系
AMD是RequireJS在推广过程中对模块定义的规范化产出,它是一个概念,RequireJS是对这个概念的实现,就好比JavaScript语言是对ECMAScript规范的实现。AMD是一个组织,RequireJS是在这个组织下自定义的一套脚本语言
CMD---是SeaJS在推广过程中对模块定义的规范化产出,是一个同步模块定义,是SeaJS的一个标准,SeaJS是CMD概念的一个实现,SeaJS是淘宝团队提供的一个模块开发的js框架
CommonJS规范---是通过module.exports定义的,在前端浏览器里面并不支持module.exports,通过node.js后端使用的。Nodejs端是使用CommonJS规范的,前端浏览器一般使用AMD、CMD、ES6等定义模块化开发的
十、ES6转成ES5的常见例子
bable.js
babel是一个转译器,感觉相对于编译器compiler,叫转译器transpiler更准确,因为它只是把同种语言的高版本规则翻译成低版本规则,而不像编译器那样,输出的是另一种更低级的语言代码。
但是和编译器类似,babel的转译过程也分为三个阶段:parsing、transforming、generating,以ES6代码转译为ES5代码为例,babel转译的具体过程如下:
ES6代码输入 ==》 babylon进行解析 ==》 得到AST
==》 plugin用babel-traverse对AST树进行遍历转译 ==》 得到新的AST树
==》 用babel-generator通过AST树生成ES5代码
此外,还要注意很重要的一点就是,babel只是转译新标准引入的语法,比如ES6的箭头函数转译成ES5的函数;而新标准引入的新的原生对象,部分原生对象新增的原型方法,新增的API等(如Proxy、Set等),这些babel是不会转译的。需要用户自行引入polyfill来解决
//摘自博客:https://blog.csdn.net/qq_42149830/article/details/88295747 function _defineProperties(target,prop){ prop.forEach(ele => { //可能会传入多个属性 Object.defineProperty(target,ele.key,{ value:ele.value, writable:true, configurable:true, }) });//设置所设置的属性是否可写,可枚举 } function _createClass(_constructor,_prototypeProperties,_staticProperties){ //这里传入的三个参数分别是构造函数,原型上的属性,静态属性 if(_prototypeProperties){ //设置公有属性 _defineProperties(_constructor.prototype,_prototypeProperties) } if(_staticProperties){ //设置静态属性 _defineProperties(_constructor,_staticProperties) } } function _classCallCheck(_this,_constructor){ if(!(_this instanceof _constructor)){ //判断是否是通过new(创建实例)来调用_constructor throw "TypeError: Class constructor AirPlane cannot be invoked without 'new'" } } var FatherPlane=(function(){ function FatherPlane(name,color){ _classCallCheck(this,FatherPlane) this.name=name||'liu'; this.color=color||'red' } _createClass(FatherPlane,[ { key:'fly', value:function(){ console.log('fly') } } ],[ { key:'static', value:function(){ console.log('static') } } ]) return FatherPlane; })() var airplane=new FatherPlane()