const声明的是一个只读的常量,一旦声明,值就不能改变。
一旦声明常量,必须初始化,不能留到以后赋值。
顶层对象,在浏览器环境指的是window对象,在Node指的是global对象。
let [foo = true] = [];
foo // true
对象与数组解构的不同:数组的元素必须按次序排列,变量值由位置决定;对象的属性没有次序,变量必须与属性同名,才能取到值。
对象解构的内部机制:先找到同名属性,然后赋给对应的变量,被赋值的事后者,而不是前者。
解构可以用于嵌套解构的对象。
var {x = 3} = {}
x // 3
字符串也可以解构赋值,因为此时,字符串被转换成了一个类数组的对象。
数值和布尔值的包装对象都有toString属性
function add([x, y]){
return x + y;
}
add([1, 2]); // 3
函数add的参数表面上是一个数组,但是传入参数的那一刻,数组参数就被解构为变量x、y,对于函数内部的代码来说,能感受到的参数就是x、y。
只要有可能导致解构的歧义,就不得使用圆括号
变量的解构赋值用途很多
let x = 1;
let y = 2;
[x, y] = [y, x];
//返回一个数组
function example(){
return [1, 2, 3];
}
let [a, b, c] = example();
function example(){
return {
foo: 1,
bar: 2,
}
}
let {foo, bar} = example();
解构赋值可以方便地将一组参数与变量名对应起来。
let jsonData = {
id: 42,
status: "OK",
data: [867, 5309]
}
let { id, status, data: number } = jsonData;
console.log(id, status, number);
// 42, "OK", [867, 5309]
任何部署了Iterator接口的对象,都可以用for…of遍历循环。
Map结构原生支持Iterator接口,配合变量的解构赋值,获取键名和键值非常方便
ES6为字符串添加了遍历器接口,使得字符串可以被for…of循环遍历
模板字符串时增强版的字符串,用反引号(`)标识。
可以紧跟在一个函数名后面,该函数将被调用来处理这个模板字符串。被称为“标签模板”
标签模板其实不是模板,而是函数调用的一种特殊形式。
标签指的就是函数,紧跟在后面的模板字符串就是它的参数。
传统上,js只有indexOf方法,用来确定一个字符串是否包含另一个字符串中。
如果某个字符串不够指定长度,会在头部或尾部补全。
// 补全指定位数
'1'.padStart(10, '0'); // "0000000001"
// 提示字符串格式
'12'.padStart(10, 'YYYY-MM-DD'); // "YYYY-MM-12"
"09-12".padStart(10, "YYYY-MM-DD"); // "YYYY-09-12"
返回新字符串,不会修改原字符串
返回新字符串,不会修改原字符串
ES6将全局方法 parseInt()和parseFloat(),移植到Number对象上,行为完全保持不变。
这样做的目的,逐步减少全局性方法,使得语言逐步模块化。
如果对于数据精度的要求较高,不建议使用该方法判断一个数值是否为整数
ES6在Math对象上新增了17个与数学相关的方法。
所有这些方法都是静态方法,只能在Math对象上调用。
新增了一个指数运算符(**),特点是右结合。
第八种数据类型,bigint只用来表示整数,没有位数的限制。
参数默认值可以与解构赋值的默认值,结合起来使用。
//写法一
function m1({x=0,y=0} = {}){
return [x, y];
}
//写法二
function m2({x, y} = {}){
return [x, y]
}
/
*
*上面两种写法都对函数的参数设定了默认值
*写法一的函数参数的默认值是一个空对象,并设置了对象解构赋值的默认值
*写法二的函数参数的默认值是一个具有属性的对象,没有设置对象解构赋值的默认值。
/
//没有参数
m1() //[0, 0]
m2() //[0, 0]
//有参数
m1({x:3, y: 8}) //[3, 8]
m2({x:3, y: 8}) //[3, 8]
//x有值,y无值
m1({x: 3}) //[3, 0]
m2({x: 3}) //[3, undefined]
//x和y都无值
m1({}) //[0, 0]
m2({}) //[undefined, undefined]
m1({z: 3}) //[0, 0]
m2({z: 3}) //[undefined, undefined]
定义了默认值的参数,应该是函数的尾参数。
如果值为undefined,触发默认值,undefined不全等null,所以null参数不会触发默认值。
利用参数默认值,可以指定某一个参数不得省略,如果省略就抛出一个错误。
ES6引入rest参数(形式为…变量名),用于获取函数的多余函数,这样就不用arguments对象了
rest参数搭配的变量是一个数组,将多余的参数放入数组中。
ES6规定只要函数参数使用了默认值、解构赋值、扩展运算符,那么函数内部就不能显式设定为严格模式,否则会报错。
var f = v => v;
//等于
var f = function(v){
return v;
}
箭头函数使得表达式更加简洁
//正常函数写法
[1,2,3].map(function(x){
return x*x;
});
//箭头函数写法
[1,2,3].map((x)=>x*x);
在箭头函数中,this 是固定的。(外层代码块的this)
this指向的固定化,并不是箭头函数内部有绑定this的机制,实际是箭头函数根本没有自己的this,导致内部的this就是外层代码块的this。
正是因为它没有this,所以不能用作构造函数。
对象的属性 建议使用传统的写法定义,不要使用箭头函数定义。
原理:指某个函数的最后一步是调用另一个函数
function f(x){
return g(x);
}
尾递归:尾调用自身。
函数式编程有一个概念,柯里化:将多参数的函数转换成单参数的函数。
函数参数,最后一个参数后面出现逗号。
返回一模一样的原始代码。
允许catch语句省略参数
try{
}catch{
}
扩展运算符是三个点(…),好比rest参数的逆运算,将一个数组转为用逗号分隔的参数序列。
类数组对象,本质特征有一点,必须有length属性。
任何有length属性的对象,都可以通过Array.from方法转为数组,而扩展运算符就无法转换。
Array.of()方法用于将一组值,转换为数组。
目的:弥补数组构造函数Array()的不足。
会修改当前数组。
copyWithin(target, start, end)
fill方法使用给定值,填充一个数组。
三个方法用于遍历数组,都返回一个遍历器对象,可以用for…of循环进行遍历
唯一区别:keys()是对键名遍历,values()是对键值遍历,entries()是对键值对的遍历。
返回一个布尔值,表示某个数组是否包含给定的值。
flat()用于将嵌套的数组拉平,变成一维数组,返回新数组,对原数组无影响。
flatMap() 对原数组每个成员执行一个函数,相当于执行map(),返回一个新数组。
ES6允许大括号里面直接写入变量和函数,作为对象的属性和方法。
简写的对象方法不能用作构造函数,会报错。
//方法一
obj.foo = true;
//方法二
obj['a'] = 123;
let str = 'foo';
let obj = {
[str]: true,
[str + 'hello'](){
return 'hi';
}
}
引入可枚举目的:让某些属性可以规避掉for…in操作,不然所有内部属性和方法都会被遍历到。
所有Class的原型方法都是不可枚举的。
大多数我们只关心对象自身的属性,所以尽量不要用for…in循环,用Object.keys()代替。
序号 | 方法名 | 作用 | 返回 |
---|---|---|---|
1 | for…in | 循环遍历对象自身的和继承的可枚举属性(不含Symbol属性) | 无 |
2 | Object.keys() | 自身的所有可枚举属性的键名(不含Symbol属性) | 数组 |
3 | Object.getOwnPropertyNames(obj) | 自身所有属性的键名(不含Symbol属性) | 数组 |
4 | Object.getOwnPropertySymbols(obj) | 自身所有Symbol属性的键名 | 数组 |
5 | Reflect.ownKeys(obj) | 自身的所有属性的键名,包括Symbol和不可枚举 | 数组 |
this关键字指向函数所在的当前对象
ES6又新增了super关键字,指向当前对象的原型对象。
const obj = {
find(){
return super.foo;
}
}
解构赋值必须是最后一个参数,否则会报错
let {x, y, ...z} = {x:1, y:2, a:3, b: 4};
z // {a: 3, b: 4}
用于取出参数对象的所有可遍历属性,拷贝到当前对象中。
如果扩展运算符后面是个空对象,没有任何效果。
如果后面不是对象,自动将其转为对象。
扩展运算符等同于使用Object.assign();
可以用于合并两个对象
let ab = {...a, ...b};
//等同于
let ab = Object.assign({}, a, b);
如果读取对象某个属性,往往需要判断一下该对象是否存在。
?. 运算符,直接在链式调用的时候判断,左侧的对象是否为null或undefined。如果是的,返回undefined。
使用?. 运算符的场合,不应该使用圆括号。
读取对象属性,如果某个属性的值是null或undefined,需要为他们指定默认值。常见做法通过||指定默认值。
但是||运算符,左侧如果为空字符串或false或0,默认值也会生效。
该运算符目的:跟链判断运算符?. 配合使用,为null或undefined的值设置默认值。
ES5比较两个值相等,只有两个运算符:相等和全等
Object.is比较两个值是否严格相等,与全等行为基本一致
Object.is('foo', 'foo'); //true
Object.is({}, {}); //false
Object.assign()用于对象的合并,将源对象的所有可枚举属性,复制到目标对象。
const target = { a: 1 }
const source1 = { b: 2 }
const source2 = { c: 3 }
Object.assign(target, source1, source2);
target // {a: 1, b: 2, c: 3}
如果源对象有同名属性,则后面的属性会覆盖前面的属性。
如果undefined和null不在首参数,不会报错
Object.assign()拷贝的属性:只拷贝源对象的自身属性,包括Symbol属性,不拷贝继承属性,不拷贝不可枚举属性(enumerable: false)
Object.assign([1, 2, 3], [4, 5]);
// [4, 5, 3]
用来读取或设置当前对象的原型对象(prototype)
//ES5写法
const obj = {
method:function(){}
}
obj.__proto__ = OtherObj;
//ES6
var obj = Object.create(OtherObj);
obj.method = function(){}
用来设置一个对象的原型对象(prototype),返回参数对象本身。
第一个参数为undefined或null,无法转为对象,会报错。
用于读取一个对象的原型对象。
Object.keys() 返回一个数组,成员是参数自身的所有可遍历属性的键名。
Object.values() 返回一个数组,成员是参数自身的所有可遍历属性的键值。
Object.entries() 返回一个数组,成员是参数自身的所有可遍历属性的键值对数组。
是Object.entries()的逆操作,用于将一个键值对数组转为对象。
Object.fromEntries([
['foo', 'bar'],
['baz', 42]
])
// {foo: 'bar', baz: 42}
该方法的主要目的,是将键值对的数据结构还原为对象,因此特别适合将Map结构转为对象。
const entries = new Map([
['foo', 'bar'],
['baz', 42]
]);
Object.fromEntries(entries);
// { foo: 'bar', baz: 42 }
对象属性名,容易造成重名冲突,引入Symbol,保证每个属性名对一无二,从根本上防止属性名的冲突。
原始数据类型Symbol
undefined、null、Boolean、String、Number、Object
Symbol值作为对象属性名时,不能用点运算符。因为点运算符后面总是字符串,所以导致属性名实际是一个字符串,而不是一个Symbol值。
Symbol值作为属性名时,该属性还是公开属性,不是私有属性。
let s = Symbol();
let obj = {
[s]: function(arg){}
}
obj[s](123)
魔术字符串:在代码中多次出现,与代码形成强耦合的某一个具体的字符串或数值。
尽量消除魔术字符串,改由含义清晰的变量代替。
重新使用同一个Symbol值,Symbol.for()方法,接受一个字符串作为参数,搜索以该参数作为名称的Symbol值,如果有,就返回这个值,否则就新建一个值,并注册到全局。
Symbol.keyFor()方法返回一个已登记的Symbol类型值得key。
let s1 = Symbol.for('foo');
Symbol.keyFor(s1); // 'foo'
let s2 = Symbol('foo');
Symbol.keyFor(s2); // undefined
新的数据结构 Set,类似于数组,但是成员的值都是唯一的,没有重复的值。
Set 本身是一个构造函数,用来生成Set数据结构
const s = new Set();
[2,3,4,4,5,6,6,1,2].forEach((x)=> s.add(x));
for(let i of s){
console.log(i);
}
//1 2 3 4 5 6
[...new Set(array)]
//去除字符串重复字符
[...new Set('abbccc')].join('');
属性
方法
操作方法
遍历操作
Set遍历顺序就是插入顺序。
可以使用for…of遍历Set
WeakSet结构与Set类似,也是不能重复值的集合。
成员只能是对象,不能是其他类型的值
WeakSet中的对象都是弱引用。
是一个构造函数,可以使用new命令
js对象,本质上是键值对的集合,但是只能用字符串当做键,给它的使用带来了很大的限制。
Map结构提供了‘值-值’的对应,是一种更完善的Hash结构实现。如果你需要‘键值对’的数据结构,Map比Object更合适。
属性
遍历方法
结构与Map结构类似,也是用于生成键值对的集合。
WeakMap只接受对象作为键名,不接受其他类型
WeakMap键名所指向的对象,不计入垃圾回收机制。键名所引用的对象,都是弱引用。
用于修改某些操作的默认行为,等同于在语言层面做出修改,属于一种‘元编程’,既对编程语言进行编程。
在目标对象之前架设一层拦截,外界对该对象的访问,都必须通过这层拦截。
对外界的访问进行过滤和改写,代理某些操作,译为代理器。
var proxy = new Proxy(target, handler);
Proxy支持的拦截操作 一共13种
Proxy.revocable(),返回一个可取消的Proxy实例。
可以拦截目标对象的任意属性,很适合用来写Web服务的客户端。
Reflect对象与Proxy对象一样,也是为了操作对象而提供的新API。
指的是函数自动观察数据对象,一旦对象有变化,函数就会自动执行。
Promise是异步编程的一种解决方案
Promise对象是一个构造函数,用来生成Promise实例
const promise = new Promise(function(resolve, reject){
});
Promise新建后就会立即执行。
resolved的Promise是在本轮事件循环的末尾执行,总是晚于本轮循环的同步任务。
then方法返回的是一个新的Promise实例(不是原来那个Promise实例)。可以采用链式写法,then后面再跟一个then方法。
用于指定发生错误时的回调函数。
建议使用catch方法,而不是then方法的第二个参数。
如果没有使用catch方法指定错误处理的回调函数,Promise对象不会有任何反应。
不管Promise对象最后状态如何,都会执行的操作。
Promise.all()可以将多个Promise实例,包装成一个新的Promise实例
新的实例的状态,由多个参数Promise实例决定
参数实例中有一个实例率先改变状态,新的实例的状态就会跟着改变。
只有等参数Promise实例全部返回结果后,包装实例才会结束。
接受一组Promise实例作为参数,包装成一个新的Promise实例返回。
只要有一个成功的状态,就会变成成功的状态
如果所有的参数实例都变成失败的状态,包装实例就会变成失败状态。
有时需要将现有对象转为Promise对象
模拟try代码块,捕获所有同步和异步的错误。
遍历器(iterator)是一种接口,为各种不同的数据结构提供统一的访问机制。
任何数据结构只要部署Iterator接口,就可以完成遍历操作(依次处理该数据结构的所有成员)
当使用for…of循环遍历某种数据结构时,该循环会自动去寻找Iterator接口
字符串是一个类似数组的对象,原生具有Iterator接口。
for…of循环可以使用的范围包括 数组、Set和Map结构、类数组对象(arguments,DOM NodeList对象)、Generator对象、字符串。
Generator函数是ES6提供的一种异步编程解决方案。
async 使得异步操作变得更加方便
是Generator函数的语法糖
async函数返回一个Promise对象,可以使用then方法添加回调函数
async函数的实现原理,就是将Generator函数和自动执行器,包装在一个函数里。
最简洁、最符合语义,几乎没有语义不相关的代码。
顶层await命令有点像,交出代码的执行权给其他的模块加载,等异步操作完成后,再拿回执行权,继续向下执行。
js中生成实例对象的传统方法是通过构造函数
function Point(x, y){
this.x = x;
this.y = y;
}
Point.prototype.toString = function(){
return this.x + ',' + this.y;
}
这种写法与传统的面向对象语言差异很大。
ES6引入了Class这个概念,通过class关键字可以定义类。
class可以看做一个语法糖,绝大部分功能,ES5都能做到。
class Point{
constructor(x, y){
this.x = x;
this.y = y;
}
toString(){
return this.x + ',' + this.y;
}
}
是类的默认方法,通过new命令生成对象实例时,自动调用该方法。
一个类必须有constructor()方法,如果没有显示定义,一个空的constructor()方法会被默认添加。
类必须使用new调用,和普通构造函数的重要区别。
实例的属性除非显示定义在本身上(即定义在this对象上),否则都是定义在原型上(即定义在class上)。
类的所有实例都共享一个原型对象。
可以通过实例的__proto__属性为‘类’添加方法。不推荐使用,因为这会改变‘类’的原始定义,影响到所有实例。
在类内部可以使用get和set关键字,对某个属性设置存值函数和取值函数,拦截该属性的存取行为。
类相当于实例的原型,所有在类中定义的方法,都会被实例继承。
如果在一个方法前,加上static关键字,表示该方法不会被实例继承,而是直接通过类来调用,这就称为‘静态方法’。
class Foo{
static classMethod(){
return 'hello';
}
}
Foo.classMethod(); //hello
var foo = new Foo();
foo.classMethod(); //is not a function
静态属性指的是Class本身的属性。
私有方法和私有属性,只能在类的内部访问的方法和属性,外部不能访问。有利于代码的封装。
该属性一般用在构造函数中,返回new命令作用于的那个构造函数。这个属性可以用来确定构造函数是怎么调用的。
如果构造函数不是通过new命令或者Reflect.construct()调用的,new.target就会返回undefined
class Shape{
constructor(){
if(new.target === Shape){
throw new Error('本类不能实例化');
}
}
}
class Rectangle extends Shape{
constructor(x, y){
super();
}
}
var x = new Shape(); //报错
var y = new Rectangle(3, 4); //正确
Class可以通过extends关键字实现继承,这比ES5通过修改原型链实现继承,要清晰和方便很多。
class Point{}
class ColorPoint extends Point{}
ES5继承
ES6继承
用来从子类上获取父类,可以使用这个方法判断,一个类是否继承了另一个类。
super既可以当做函数使用,也可以当做对象使用。
在子类普通方法中通过super调用父类方法,方法内部的this指向当前子类实例。
使用super的时候,必须显示的指定是作为函数、还是作为对象使用,否则会报错。
ES5中,每个对象都有__proto__属性,指向对应的构造函数的prototype属性
Class作为构造函数的语法糖,同时又prototype属性和__proto__属性,因此同时存在两条继承链。
class A{}
class B extends A{}
B.__proto___ === A //true
B.prototype.__proto__ === A.prototype //true
实例的__proto__属性
Mixin指的是多个对象合成一个新的对象,新对象具有各个组成成员的解构。
const a = {
a: 'a'
}
const b = {
b: 'b'
}
const c = {...a, ...b}; // {a: 'a', b: 'b'}
ES6实现了模块功能,完全可以取代CommonJS和AMD规范,成为浏览器和服务器通用的模块解决方案。
ES6模块自动采用严格模式,不管你有没有在模块头部加上‘use strict’
模块功能主要由两个命令构成:export和import
使用了export命令定义了模块对外的接口以后,其他js文件就可以通过import命令加载这个模块。
import { lastName as surname } from './name.js'
使用整体加载,用星号(*)执行一个对象,所有输出值都加载在这个对象上面。
为模块指定默认输出
export default function(){
console.log('foo');
}
export { foo, bar } from 'name.js';
import命令会被js引擎静态分析,先于模块内其他语句执行。
import和export命令只能在模块的顶层,不能在代码块之中。
import无法实现动态加载功能