目录
1. let / const
2. 模版字符串(template string)
3. 解构赋值
4. 块级作用域
5. 函数默认参数
6. 箭头函数() => {}
7.Set()
8. 模块化
9. promise
async await 与 promise 哪个好用?
10.async await函数
11. class
12.keys,values,entries
13、Generator
1. let / const
es6 以前,都是用 var 关键字来标识,这样有个变量提升的坑。在 es6 中,添加了 let 和 const 两个关键字,let 定义变量,const 定义常量,并且添加了块级作用域。
ES6 新增了let命令,用来声明变量。它的用法类似于var,但是所声明的变量,只在let命令所在的代码块内有效。const声明一个只读的常量。
1、let和const的相同点:
① 只在声明所在的块级作用域内有效。
② 不提升,同时存在暂时性死区,只能在声明的位置后面使用。
③ 不可重复声明。
变量提升:es6 以前,js 引擎将所有的变量都提到最前面,初始化为 undefined。
2、let和const的不同点:
① let声明的变量可以改变,值和类型都可以改变;const声明的常量不可以改变,这意味着,const一旦声明,就必须立即初始化,不能以后再赋值。
② 数组和对象等复合类型的变量,变量名不指向数据,而是指向数据所在的地址。const只保证变量名指向的地址不变,并不保证该地址的数据不变,所以将一个复合类型的变量声明为常量必须非常小心。
若想让定义的对象或数组的数据也不能改变,可以使用object.freeze(arr|object)进行冻结。冻结指的是不能向这个对象或数组添加新的属性,不能修改已有属性的值,不能删除已有属性。
const arr = [];
Object.freeze(arr);
arr[0] = 1; // 不报错,但数据改变无效
console.log(arr.length); // 输出:0
补充:变量提升
JavaScript 代码的执行分为两个阶段。第一个阶段在当前词法环境中注册所有的变量和函数声明,简单说就是 解析,解析完成之后,第二个阶段的 JavaScript 执行就开始了!
JS中创建函数有两种方式:函数声明式和函数字面量式。只有函数声明才存在函数提升。
JavaScript 仅提升声明,而不提升初始化。如果你先使用的变量,再声明并初始化它,变量的值将是 undefined。
1:所有的声明都会提升到作用域的最顶上去。
2:同一个变量只会声明一次,其他的会被忽略掉。
3:函数声明的优先级高于变量申明的优先级,并且函数声明和函数定义的部分一起被提升。
2. 模版字符串(template string)
使用 ``(反引号作为标识),既可以当作普通字符串使用,又可以用来定义多行字符串,还可以在字符串中嵌入变量(使用模板变量${})。
如果遇到特殊的字符 比如`,则需要在前面加转义字符\。 这样使用起来很方便,避免字符串拼接中出现的不必要的错误,而且更简单简洁,最重要的是人易懂。需要注意的是 ${ } 要和 `` 一起使用不然不会被解析。
3. 解构赋值
解构赋值属于浅拷贝!
顾名思义,就是先解构,再赋值! 比如先定义一个对象和数组:
var obj = { a: 1, b: 2, c: 3 };
var arr = [1,2,3];
// 在 es6 以前,这样获取属性的值: obj.a; obj.b; arr[i];
// 在 es6 中如下方式获取:
const { a, c } = obj;
const [x, y, z] = arr;
// 很简单就可以获取对象的属性和数组的值,看下编译得过程:
var a = obj.a, c = obj.c;
var arr = [1, 2, 3];
var x = arr[0], y = arr[1], z = arr[2];
4. 块级作用域
代码块(用{}
表示)块,就是代码中我们用两个{ }包起来的内容。
5. 函数默认参数
function test (a, b = 0) {
// ...
}
编译一下:
function test(a) {
var b = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
// ...
}
6. 箭头函数() => {}
定义:ES6箭头函数结构为 X => X*X
等价:function(X) { return X*X }
箭头函数特点:
- 更简洁的语法
- 不绑定this,捕获其所在上下文的 this 值,作为自己的 this 值
- 箭头函数是匿名函数,不能作为构造函数,不能使用new
- 不绑定arguments,用rest参数...解决
- 使用call()和apply()调用,只是传入了参数而已,对 this 并没有什么影响
- 箭头函数没有原型属性
- 不能简单返回对象字面量(返回对象时需要用小括号包起来,因为大括号被占用解释为代码块了)
- 箭头函数不能当做Generator函数,不能使用yield关键字
- 箭头函数不能换行
let a = ()
=> 1; //SyntaxError: Unexpected token =>
箭头函数与普通函数的区别?
• 箭头函数都是匿名函数
• this的指向不同:
(1) 箭头函数的 this 永远指向其上下文的 this ,任何方法都改变不了其指向,如 call() , bind() , apply()
(2) 普通函数的this指向调用它的那个对象
7.Set()
Set()是有序列表的结合,而且是没有重复的,因此Set()对象可以用于数组的去重。
数组去重:
1)[...new Set([1,2,3,1,'a',1,'a'])]
2)Array.from(new Set([1,2,3,1,'a',1,'a'])
Set中包含的方法:add()、has、delete()、clear()
Set也能用来保存NaN和undefined, 如果有重复的NaN, Set会认为就一个NaN(实际上NaN!=NaN);
实例Set以后的对象拥有这些属性和方法:
属性:Set.prototype 、Set.prototype.size
方法:Set.prototype.add()、Set.prototype.clear()、Set.prototype.delete()、Set.prototype.entries()、Set.prototype.forEach()、Set.prototype.has()、Set.prototype.values()、Set.prototype[@@iterator] ()
8. 模块化
在现在多个人开发同一个项目很常见,每个人负责不同的模块,还有可能会几个人使用同一个模块,在这种情况下,模块化就很重要!其实使用起来也很简单,比如说有模块A、B、C三个 js 文件,各自在其中定义好自己的代码,使用 export 关键字导出自己的东西,别人使用时用 import 关键字引用即可,模块化的处理工具有 webpack、rollup 等。一个模块就是一个实现特定功能的文件。
1. 模块化开发的4点好处:
1.避免变量污染,命名冲突 2.提高代码复用率 3.提高维护性 4.依赖关系的管理
2. 实现模块化
开始的模块化体现:函数 =》 对象 =》 立即执行函数
1)commonJS
根据commonJS规范,一个单独的文件是一个模块,每一个模块都是一个单独的作用域,也就是说,在该模块内部定义的变量,无法被其他模块读取,除非为global对象的属性。模块只有一个出口,module.exports对象,我们需要把模块想要输出的内容放入该对象。加载模块用require方法,该方法读取一个文件并且执行,返回文件内部的module.exports对象。
2)AMD (Asynchronous Module Definition) 异步模块定义
它是一个在浏览器端模块化开发的规范,由于不是js原生支持,使用AMD规范进行页面开发需要用到RequireJS函数库(实际上AMD是RequireJS在推广过程中对模块定义的规范化的产出)。 AMD推崇的是依赖前置,在定义模块的时候就有声明其依赖的模块。(被提前罗列出来并会被提前下载并执行,后来做了改进,可以不用罗列依赖模块,允许在回调函数中就近使用require引入并下载执行模块。)
requireJS主要解决两个问题:
1 多个js文件可能有依赖关系,被依赖的文件需要早于依赖它的文件加载到浏览器。
2 js加载的时候浏览器会停止页面渲染,加载文件愈多,页面失去响应的时间愈长。
3)CMD (common module definition)
就像AMD有个requireJS,CMD有个浏览器实现的sea.js,sj要解决的问题和rj一样,只不过在模块定义方式和模块加载时机上有所不同(cmd是sea.js在推广过程中的规范化产出),sea.js是另一种前端模块化工具,它的出现缓解了requireJS的几个痛点。CMD推崇依赖就近,只有在用到某模块的时候再去require。
4)ES6 Modules模块化实现
import引入, module.export导出
9. promise
- promise是一个对象,对象和函数的区别就是对象可以保存状态,函数不可以(闭包除外)
- 并未剥夺函数return的能力,因此无需层层传递callback,进行回调获取数据
- 代码风格,容易理解,便于维护
- 多个异步等待合并便于解决
promise详解
new Promise(
function (resolve, reject) {
// 一段耗时的异步操作
resolve('成功') // 数据处理完成
// reject('失败') // 数据处理出错
}).then(
(res) => {console.log(res)}, // 成功
(err) => {console.log(err)} // 失败
)
- resolve作用:将Promise对象的状态从“未完成”变为“成功”(即从 pending 变为 resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去; reject作用:将Promise对象的状态从“未完成”变为“失败”(即从 pending 变为 rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。
- promise有三个状态: 1、pending[待定]初始状态 2、fulfilled[实现]操作成功 3、rejected[被否决]操作失败 当promise状态发生改变,就会触发then()里的响应函数处理后续步骤; promise状态一经改变,不会再变。
- Promise对象的状态改变,只有两种可能: 从pending变为fulfilled 从pending变为rejected。 这两种情况只要发生,状态就凝固了,不会再变了。
常见用法: 异步操作和定时器放在一起,,如果定时器先触发,就认为超时,告知用户; 例如我们要从远程的服务家在资源如果5000ms还没有加载过来我们就告知用户加载失败
Promise.all
可以将多个Promise实例包装成一个新的Promise实例。同时,成功和失败的返回值是不同的,成功的时候返回的是一个结果数组,而失败的时候则返回最先被reject失败状态的值。
1.它接受一个数组作为参数。
2.数组可以是Promise对象,也可以是其它值,只有Promise会等待状态改变。
3.当所有的子Promise都完成,该Promise完成,返回值是全部值的数组,数组顺序不变。
4.如果有任何一个失败,该Promise失败,返回值是第一个失败的子Promise的结果。
Promise.race
区别在于 它一旦有一个promise返回就算完成(但是进程不会立即停止),该promise对象 resolve 以后,立即把 resolve 的值作为 Promise.race()
resolve 的结果;如果该对象 reject,Promise.race
也会立即 reject。
1.它接受一个数组作为参数。
2.数组可以是Promise对象,也可以是其它值,只有Promise会等待状态改变。
3.当某一个Promise完成,该Promise完成,返回值是该Promise的结果。
async await 与 promise 哪个好用?
假设同时发十个请求,要求十个请求都返回后再做其他操作?
10.async await函数
awiat必须在使用async的情况下才能使用,它的作用是阻断主函数的执行,等待异步执行的结果返回后才会向下继续执行。
受await阻断影响,必须要在await fn()执行完后才能执行,同时如果fn()没有返回结果,就是说没有reslove()的话,那么下面的代码将不会执行
11. class
实际上应该说是构造函数,通过原型实现的
12.keys,values,entries
用于遍历数组,它们都返回一个遍历器对象,可以用for...of循环进行遍历,唯一的区别是keys()是对键名的遍历、values()是对键值的遍历,entries()是对键值对的遍历。
for (let index of ['a', 'b'].keys()) {
console.log(index);
}
// 0
// 1
for (let elem of ['a', 'b'].values()) {
console.log(elem);
}
// 'a'
// 'b'
for (let [index, elem] of ['a', 'b'].entries()) {
console.log(index, elem);
}
// 0 "a"
// 1 "b"
13、Generator
1.相关概念
Generator(生成器)是一类特殊的函数,跟普通函数声明时的区别是加了一个*号。
Iterator(迭代器):当我们实例化一个生成器函数之后,这个实例就是一个迭代器。可以通过next()方法去启动生成器以及控制生成器的是否往下执行。
yield/next:这是控制代码执行顺序的一对好基友。 通过yield语句可以在生成器函数内部暂停代码的执行使其挂起,此时生成器函数仍然是运行并且是活跃的,其内部资源都会保留下来,只不过是处在暂停状态。 在迭代器上调用next()方法可以使代码从暂停的位置开始继续往下执行。
// 首先声明一个生成器函数
function *main() {
console.log('starting *main()');
yiled; // 打住,不许往下走了
console.log('continue yield 1');
yield; // 打住,又不许往下走了
console.log('continue yield 2');
}
// 构造处一个迭代器it
let it = main();
// 调用next()启动*main生成器,表示从当前位置开始运行,停在下一个yield处
it.next(); // 输出 starting *main()
// 继续往下走
it.next(); // 输出 continue yield 1
// 再继续往下走
it.next(); // 输出 continue yield 2
2.消息传递
3.Generator在流程控制中的应用
4.Generator+Promise实现完美异步
5.async和await
6.yield委托