Let和const
不存在变量提升
只要块级作用域内存在let命令,它所申明的变量就绑定这个区域,不再受外部影响。凡是在声明之前就使用这些变量就会报错。
Let不允许在相同的作用域内,重复声明一个变量
ES5只有全局作用域和函数作用域,没有块级作用域,这带来很多不合理的场景。一对大括号就是一个块。
ES5规定函数只能在顶层作用域和函数作用域中声明,不能在块级作用域声明。
ES6引入了块级作用域,明确允许在块级作用域中声明函数。并且函数声明的行为类似于let,在块级作用域之外不可引用。但是考虑到环境导致的行为差异太大,应该避免在块级作用域内声明函数,写成函数表达式更好。
Const声明一个只读的常量,一旦声明,常量的值就不能改变。这意味着const一旦声明变量,就必须立即初始化。
也存在暂时性死区,只在声明所在的块级作用域内有效。也不可重复声明
对于复合类型的变量,变量名不指向数据,而是指向数据的地址。Const只是保证变量名指向的地址不变。
ES6一共又6种声明变量的方法:let,const,var,function,import,class
Let和const声明的全局变量不属于全局对象的属性。
变量的解构赋值
事实上,只要某种结构具有Iterator接口,都可以采用数组形式的解构赋值。
解构赋值允许指定默认值。
Var [foo = true] = []; //foo = true
解构不仅可以用于数组,还可以用于对象。数组的元素是按次序排序的,变量的取值由他的位置决定,而对象的属性没有次序,变量必须与属性同名才能取得正确的值。
Let { log, sin, cos } = Math; //将Math对象的对数,正弦,余弦三个方法赋值到对应的变量。
var o ={a: 5};
let {a,b=2,c} = o;
console.log(a)
字符串的解构赋值
Const [a,b,c,d,e] = ‘hello’
Let { length: len} = ‘hello’ //len = 5
解构赋值的规则是只要等号右边的值不是对象,就先将其转为对象。由于undefined和null无法转为对象,所以对它们进行解构赋值,都会报错。
函数参数的解构赋值
变量解构赋值用途很多
1.交换变量的值
[x, y] = [ y, x ]
2.从函数返回多个值
3.函数参数的定义:解构赋值可以方便的将一组参数与变量名对应起来。
4.提取JSON数据
5.函数参数的默认值
6.遍历Map结构:任何部署了Iterator接口的对象,都可以用for…of循环遍历。Map结构原生支持Iterator接口,配合变量的解构赋值,获取键名和键值就非常方便
7.输入模块的指定方法
const { SourceMapConsumer, SourceNode } = require(“source-map”);
字符串的扩展
字符的Unicode表示法
codePointAt()方法:能够正确处理四个字节储存的字符,返回一个字符的码点。
String.fromCodePoint()方法可以识别0xFFFF的字符
为字符串添加了遍历接口,使得字符串可以被for…of循环遍历。
除了遍历字符串,这个遍历器的最大的优点是可以识别大于0xFFFF的码点。
ES5提供charAt方法,返回字符串给定位置的字符
Normalize()
传统上,JavaScript只有indexOf方法,可以用来确定一个字符串是否包含在另一个字符串种,ES6又提供了三种新方法
Includes()
startsWith()
endsWith()
这三个方法都支持第二个参数,表示开始搜索的位置。
Repeat()方法:返回一个新字符串,表示将原字符串重复n次
padStart(),padEnd():ES7提供了字符串补全长度的功能。
模板字符串:增强版的字符串,,用反引号标识,它可以当作普通字符串使用,也可以用来定义多行字符串,或者在字符串中嵌入变量
模板字符串中嵌入变量,需要将变量名写在${}之中
数组的扩展
Array.from():用于将两类对象转为真正的数组:类数组对象和可遍历对象(包括ES6新增的数据结构Map和Set
比如DOM操作返回的NodeList集合,以及函数内部的arguments对象,Array.from都可以将它们转为真正的数组。
只要是部署了Iterator接口的数据结构,Array.from都能将其转为数组。
Array.from接收第二个参数,作用类似与数组的map方法,用来对每个元素进行处理,讲处理后的值放入返回的数组。如果map函数里面用到了this关键字,还可以传第三个参数,用来绑定this。
注意:扩展运算符…也可以将某些数据结构转为数组。
扩展运算符背后调用的是遍历接口,如果一个对象没有部署这个接口,就无法转换。
Array.of()方法:用于将一组值转换为数组,主要是为了弥补构造函数Array()的不足。
copyWithin()方法:在当前数组内部,将指定位置的成员复制到其他位置,然后返回当前数组。也就是说,这个方法会修改当前数组。
Array.prototype.copyWithin(target, start = 0, end = this.length)
find()和findIndex()方法
Find()方法用于找出第一个符合条件的数组成员 ,它的参数是一个回调函数,直到找出第一个返回值为true的对象,然后返回该成员
[1, 4, -5, 10].find((n) => n < 0)
数组实例的findIndex方法的用法与find方法非常类似,返回第一个符合条件的数组成员的位置,如果所有成员都不符合条件,则返回-1。
这两个方法都可以接受第二个参数,用来绑定回调函数的this对象
Fill()方法:使用给定的值填充一个数组。还可以接受第二个和第三个参数,用于指定填充的起始位置和结束位置。
ES6提供三个新的方法——**entries(),keys(),values()用于遍历数组。它们都返回一个遍历器对象。可以用for…of循环进行遍历。**唯一的区别是keys()是对键名的遍历,values()对键值遍历,entries()是对键值对的遍历。
如果不使用for…of循环,可以手动调用遍历器对象的next方法,进行遍历。
includes()方法:表示某个数组是否包含给定的值,与字符串的includes方法类似,属于ES7
函数的扩展
函数参数的默认值
与解构赋值默认值结合使用
参数默认值的位置:定义了默认值的参数,应该是函数的尾参数。因为这样比较容易看出来,到底省略了哪些参数。如果非尾部的参数设置默认值,实际上这个参数是没法省略的
函数的length属性:指定了默认值后,length属性将失真
Rest参数:形式为 …变量名。用于获取函数的多余参数,这样就不需要使用arguments对象了,rest参数搭配的变量是一个数组,该变量将多余的参数放入数组中。
函数的length属性不包括rest参数。
扩展运算符
是三个点… 。它好比rest参数的逆运算,将一个数组转为用逗号分隔的参数序列。
由于扩展运算符可以展开数组,所以不再需要apply方法,将数组转为函数的参数了。
扩展运算符的应用
1.合并数组
2.与解构赋值结合
3.函数的返回值
4.将字符串转为真正的数组
5.任何实现了Iterator接口的对象都可以用扩展运算符转为真正的数组
6.Map和Set结构,Generator函数
Name属性
function foo() {}
foo.name // "foo”
箭头函数
一个用处是简化回调函数
使用注意点:
1.函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象
2.不可以当作构造函数
3.不可以使用arguments(箭头函数内部的arguments是外部函数的)
4.不可以使用yield命令
函数绑定
箭头函数可以绑定this对象,大大减少了显式绑定this对象的写法(call, apply, bind)。
函数绑定运算符是并排的两个冒号(::),
foo::bar;
// 等同于
bar.bind(foo);
foo::bar(…arguments);
// 等同于
bar.apply(foo, arguments);
尾调用优化
尾调用指的是某个函数的最后一步是调用另一个函数
function f(x){
return g(x);
}
尾递归:函数调用自身是递归,如果尾调用自身,就称为尾递归。
递归函数的改写
尾递归的实现,往往需要改写递归函数,确保最后一步只调用自身。就是把所有用到的内部变量改写成函数的参数。
函数式编程有一个概念,叫做柯里化,就是将多参数的函数转换成单参数的形式。
对象的扩展
**Object.is()**用来比较两个值是否严格相等,与严格比较运算符的行为基本一致
不同之处:+0不等于-0; NaN等于NaN
Object.assign():用于对象的合并,将源对象的所有可枚举属性,复制到目标对象
var target = { a: 1 };
var source1 = { b: 2 };
var source2 = { c: 3 };
Object.assign(target, source1, source2);
target // {a:1, b:2, c:3}
注意:该方法实行的是浅拷贝,也就是说,如果源对象某个属性值是对象,那么目标对象拷贝得到的是这个对象的引用。
用法:
1.为对象添加属性
2.为对象添加方法
3.克隆对象
4.合并多个对象
5.为属性指定默认值
属性的可枚举性
ES5又三个操作会忽略enumerable为false的属性
For…in
Object.keys()
JSON.stringify()
ES6新增的Object.assigin()也会忽略不可枚举属性
ES6规定,所有Class的原型的方法都是不可枚举的。
总的来说,操作中引入继承属性会让问题复杂化,大多数时候,我们只关心对象自身的属性。所以,尽量不要用for…in循环,而用Object.keys()代替。
属性的遍历
ES6一共又5中方法可以遍历对象的属性
For…in
Object.keys()
Object.getOwnPropertyNames
Object.getOwnPropertySymbols()
Reflect.ownKeys()
_proto_属性,Object.setPropertotypeOf(),Object.getPrototypeOf()
_proto_用来读取或设置当前对象的prototype对象。尽量不要使用。
而是使用下面的Object.setPrototypeOf()(写操作)、Object.getPrototypeOf()(读操作)、Object.create()(生成操作)代替。
Object.values(), Object.entries()
ES5引入了Object.keys方法,返回一个数组,成员是参数对象自身的所有可遍历属性的键名。
Object.values方法返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键值。
对象的扩展运算符
ES7的一个提案:把Rest解构赋值/扩展运算符…引入对象
Symbol
ES5的对象属性名都是字符串,这容易造成属性名的冲突。
ES6引入了一种新的原始数据类型Symbol,表示独一无二的值。
Symbol值通过Symbol函数生成的。这就是说,对象的属性名现在可以有两种类型,一种是原来就有的字符串,另一种是新增的Symbol类型。
它是一种类似字符串的数据类型。
var mySymbol = Symbol();
// 第一种写法
var a = {};
a[mySymbol] = ‘Hello!’;
// 第二种写法
var a = {
[mySymbol]: ‘Hello!’
};
// 第三种写法
var a = {};
Object.defineProperty(a, mySymbol, { value: ‘Hello!’ });
// 以上写法都得到同样结果
a[mySymbol] // “Hello!”
注意:Symbol值作为对象的属性名时,是不能用点运算符的。
Symbol作为属性名只能通过Object.getOwnPropertySymbols方法或Reflect.ownKeys获取对象的所有Symbol属性名
Set和Map数据结构
Set类似于数组,但是成员的值都是唯一的,没有重复的值。
用Set和扩展运算符可以实现数组和字符串去重。
Map类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。
Proxy和Reflect
Proxy用于修改某些操作的默认行为,等同于在语言层面做出修改。
可以理解成,在目标对象之前架设一层”拦截“,外界对该对象的访问,都必须先通过这层拦截,可以翻译为”代理器”
Iterator和for…of循环
Iterator的作用有三个:
为各种数据结构,提供一个统一的,简便的访问接口;
使得数据结构的成员能够按某种次序排列;
给for…of买单
Iterator遍历器的遍历过程:
1.创建一个指针对象,指向当前数据结构的起始位置。遍历器对象本质上就是一个指针对象。
2.第一次调用指针对象的next方法,可以将指针指向数据结构的第一个成员。
3.第二次调用指针对象的next方法,指针就指向数据结构的第二个成员
4.不断调用指针对象的next方法,直到它指向数据结构的结束位置。
Javascript原有的for…in循环,只能获得对象的键名,不能直接获取键值。ES6提供for…of循环,允许遍历获得键值。
如果要通过for…of循环获取数组的索引,可以借助数组实例的entries方法和keys方法。
注意:For…of循环调用遍历器接口,数组的遍历器接口只返回具有数字索引的属性。
Generator函数
Generator函数是ES6提供的一种异步编程解决方案。
执行Generator函数会返回一个遍历器对象,也就是说,Generator函数除了状态机。还是一个遍历器对象生成函数。返回的遍历器对象,可以依次遍历Generator函数内部的每一个状态。
形式上,Generator函数是一个普通函数,但是有两个特征:
Function关键字与函数名之间有一个*
函数体内部使用yield语句,定义不同的内部状态
Generator函数的调用方法与普通函数一样,也是在函数名后面加上一对圆括号,不同的是,调用Generator函数后,该函数并不执行,返回的也不是函数运行的结果,而是一个指向内部状态的指针对象。
下一步必须调用遍历器对象的next方法,使得指针移向下一个状态。换言之,Generator函数是分段执行的,yield语句是暂停执行的标记,而next方法可以恢复执行。
Generator函数从暂停状态恢复运行,它的上下文是不变的。通过next方法的参数,就有办法在Generator函数开始运行之后,继续向函数内部注入值。也就是说,可以在Generator函数运行的不同阶段,从外部向内部注入不同的值,从而调整函数行为。
使用for…of语句时可以不用next方法
应用:
1.异步操作的同步化表达
2.多步操作,依次执行
3.部署Iterator接口
4.作为数据结构
异步操作和Async函数
异步编程对Javascript太重要,Javascript语言执行环境是单线程的,如果没有异步编程,根本没法用,非卡死不可
ES6诞生前,异步编程的方法:
回调函数
事件监听
发布/订阅
Promise对象
所谓“异步”,就是一个任务分成两段,先执行第一段,然后转而执行其他任务,等做好了准备,再回头执行第二段。
回调函数
Promise就是为了解决回调函数噩梦提出的。它不是新的语法功能,而是一种新的写法,允许将回调函数的嵌套,改成链式调用。
Generator函数就是一个封装的异步任务,或者说是异步任务的容器。异步操作需要暂停的地方,都用yield语句注明
Generator函数可以暂停执行和恢复执行这是它能封装异步任务的根本原因。
ES7提供了async函数,该函数就是Generator函数的语法糖。
Async函数就是将Generator函数的*替换成async,将yield替换成await。
Async函数对Generator函数的改进:
1.内置执行器
2.更好的语义
3.更广的适用性
4.返回值是Promise
进一步说,async函数完全可以看作多个异步操作,包装成的一个Promise对象,而await命令就是内部then命令的语法糖。
语法:
1.async函数返回一个Promise对象。
2.Async函数返回的Promise对象,必须等到内部所有await命令的Promise对象执行完,才会发生状态改变。也就是说,只有async函数内部的异步操作执行完,才会执行then方法指定的回调函数
3.正常情况下,await命令后面是一个promise对象,如果不是,会被转成一个立即resolve的Promise对象。
4.如果await后面的异步操作出错,那么等同于async函数返回的Promise对象被reject。
Async函数返回一个Promise对象,可以使用then方法添加回调函数,当函数执行的时候,一旦遇到await就会先返回,等到触发的异步操作完成,再接着执行函数体内后面的语句。
注意点:
1.await命令后面的Promise对象,运行结果可能是rejected,所以最好把await命令放在try…catch代码块中。
2.多个await命令后面的异步操作,如果不存在继发关系,最好让他们同时触发。
3.Await命令只能用在async函数中,如果用在普通函数,会报错。
Class
Class之间可以通过extends关键字实现继承,这比ES5的通过修改原型链实现继承,要清晰和方便很多。
Class colorPoint extends Point {}
该类通过extends关键字,继承了Point类的所有属性和方法。
Extends的继承目标
三种特殊情况:
1.子类继承Object类:这种情况下,A其实就是构造函数Object的复制,A的实例就是Object的实例。
2.不存在任何继承
3.子类继承null
Object.getPrototypeOf()
可以用来从子类上获取父类。
Object.getPrototypeOf( ColorPoint ) === Point
可以使用这个方法判断一个类是否继承了另一个类。
编程风格
1.块级作用域
(1)let取代var
(2)在let和const之间,优先使用const。尤其是全局环境,只应设置常量。
2.静态字符串一律使用单引号或反引号,不使用双引号,动态字符串使用反引号
3.解构赋值
(1)使用数组成员对变量赋值,优先使用解构赋值。
(2)函数的参数如果是对象的成员,优先使用解构赋值
(3)如果函数返回多个值,优先使用对象的解构赋值,而不是数组的解构赋值
4.数组
(1)使用扩展运算符…拷贝数组
(2)使用Array.from方法将类似数组中的对象转为数组
5.函数
(1)立即执行函数可以写成箭头函数的形式。
(2)那些需要使用函数表达式的场合,尽量用箭头函数代替
(3)箭头函数取代Function.prototype.bind
(4)不要再函数体内使用arguments变量,使用rest(…)代替
(5)使用默认值语法设置函数参数的默认值
6.注意区分Object和Map,只有模拟现实世界的实体对象时,才使用Objiect。如果只是需要key: value的数据结构,使用Map结构。
7.Class
(1)总是用Class取代需要prototype的操作
(2)使用extends实现继承,因为这样更简单,不会有破坏instanceof运算的危险
8.模块
(1)使用import取代require
(2)使用export取代module.exports
(3)如果模块默认输出一个函数,函数名的首字母应该小写
(4)如果模块默默人输出一个对象,对象名的首字母应该大写