let
- ES6新增的用于变量声明的关键字
- 通过let声明的变量,不允许重复声明
- 不支持变量声明预解析,let变量,必须先声明后使用,我们把当前作用域最开始到let声明的变量之间的区域,称为 - 暂存死区
- let支持块级作用域
- 全局
- 函数
- 块 - 一对{}包含的部分,称为一个块,在这样的块中会有独立的作用域,var不支持块作用域
const
- 常量声明,和var与let不一样,var、let声明的值是可以变化的,但是通过const声明的值是固定的,不能再后期去改变他的
- 其他特性与let一致
- 因为const定义的是常量值,所以通过const定义一个常量的时候一定要初始化
object.freeze(对象)
冻结对象,无法解冻
如果希望一个对象本身以及自数据都不能变化,那么通过const和freeze同时使用
解构赋值
允许按照一定模式,从数组和对象中提取值
- 声明{}中的变量
- 根据{}中的变量,去=后面的对象中提取对应的属性,并把该属性对应的值赋值给前面的变量,如果没有该属性,返回undefined
- 如果解构出来的变量名不希望和解构对象的属性名一致,这个时候需要给需要使用的变量名设置一个别名
let {left:L,top:T} = getComputedStyle(sdad);
- 数组的解构赋值和对象的解构赋值
- 数组使用[],对象使用{}
- 数组解构赋值是一一对应的,而对象解构赋值是按照名称来进行的
函数扩展
函数参数默认值
- 在es6之前,如果一个函数的参数是可选,那么我们通常需要手动在该函数的内部处理该参数的默认值
- 在es6之后,函数在定义的时候,可以直接在参数上处理默认值
- 注意:必选参数一般写在前面,可选参数写在最后
//es5
function fn (a,b) {
a = a||10;
b = b||10;
}
//es6
function fn (a=10,b=10)
剩余参数
- 我们通常会给一个函数定义一些参数,有些参数是确定的,而有些参数是不确定(个数)的
- 如果一个函数的参数个数不确定,原来我们是通过arguments(不定参)来处理
- 如果一个函数定义了剩余参数,那么在调用该函数的时候传入的实参会赋值给对应的形参,剩下的没有对应的实参,会全部赋值给剩余参数
- 注意 :
- 剩余参数必须是伪参数
function push (arr,...data){};
push(arr,'1','2','3');
扩展运算符
- ...
- 把数组转成参数序列
function fn (a,b){};
var arr = [10,10]
fn(...arr)
var arr1 = ['a','b','c'];
var arr2 = [1,2,3];
arr1.splice(1,1,...arr2);
var arr3 = [...arr1,...arr2]
//相当于
var arr3 = arr1.concat(arr2);
//找数组中最大值的方法
var arr4 = [4,2,6,1,6,7];
Math.max(...arr4);
箭头函数
- 在es6中,提供了一种新的函数格式:箭头函数
- 有且只有一个形参的时候,才可以省略括号
- 有只有一条语句的时候,可以省略函数体的{},同时该条语句的结果将作为该函数的返回值
- 但是如果有多条语句或者返回值是一个对象,则必须用{}
- 注意:
- 箭头函数不能作为构造函数,也就是箭头函数不能使用new运算符
- 箭头函数的this永远指向当声明作用域对象
- 普通函数this指向取决于调用
- 箭头函数this指向取决于声明位置
- 箭头函数没有arguments对象
//函数声明
function fn1 (a,b){};
//函数表达式
var fn2 function (a,b){};
//箭头函数
//几种有条件的简化写法
//当参数有多个的时候
var fn3 = (a,b) => {};
//当参数只有一个的时候
var fn4 = a => {};
//当没有参数的时候
var fn5 = () => {};
//有只有一条语句的时候,可以省略函数体的{},同时该条语句的结果将作为该函数的返回值
var fn6 = r => r*r;
//但是如果有多条语句或者返回值是一个对象,则必须用{}
对象扩展
对象属性简洁表示法
- 当一个对象的key和对应的值(必须是一个变量名称)同名的话,那么可以简写成一个key
var a = 1;
var b = 1;
//es5
var obj{
left:100,
top:100,
a:a,
b:b
}
//es6
var obj{
left:100,
top:100,
a,
b
}
对象方法的简洁表示法
//es5
let obj2 = {
a:a,
fn:function(){};
}
//es6
let obj3 = {
a,
fn(){};
}
对象属性名表达式
//es5
var x = 'username'
let obj4 = {
x: 'maotao' //这里的x不会作为表达式被解析
}
//es6
let obj4 = {
[x]: 'maotao' //如果把key放在一个[]中,那么[]中的内容将被作为表达式进行解析
}
Iterator==迭代器==
- 数组通过forof得到的是数组的值,但是对于forof得到不一定是指,具体要看呗迭代对象的迭代器如何实现
- 如果我们希望一个对象能够被迭代,那么久需要去实现该对象的迭代协议和迭代器
迭代协议
- 当我们通过forof去迭代obj的时候,js内部会去查找并调用obj的
Symbol.Iterator
方法
var obj = {x:10,y:20};
obj[Symbol.iterator] = function(){
let keys = Object.keys(obj);
let n = -1;
return {
next(){
if (n < keys.length-1) {
n++;
return {done: false, value: {
k: keys[n],
v: obj[keys[n]]
}};
} else {
return {done: true};
}
}
}
}
迭代器
-
obj[Symbol.iterator]()
=>iterator.next()
=> 根据返回的对象中的done值,来决定是否已经完成或继续调用next,如果done为真,表示迭代结束通过忽略这次value值,如果为false,表示当前value有效,并继续下一次迭代(next()
)
字符串扩展
字符串的遍历器接口
- ES6为字符串添加了遍历器接口(详见《Iterator》一章),使得字符串可以被for...of循环遍历。
for (let codePoint of 'foo') {
console.log(codePoint)
}
// "f"
// "o"
// "o"
- 除了遍历字符串,这个遍历器最大的优点是可以识别大于0xFFFF的码点,传统的for循环无法识别这样的码点。
var text = String.fromCodePoint(0x20BB7);
for (let i = 0; i < text.length; i++) {
console.log(text[i]);
}
// " "
// " "
for (let i of text) {
console.log(i);
}
// "𠮷"
includes(), startsWith(), endsWith()
-
includes()
:返回布尔值,表示是否找到了参数字符串。 -
startsWith()
:返回布尔值,表示参数字符串是否在源字符串的头部。 -
endsWith()
:返回布尔值,表示参数字符串是否在源字符串的尾部。 - 这三个方法都支持第二个参数,表示开始搜索的位置。
var s = 'Hello world!';
s.startsWith('Hello') // true
s.endsWith('!') // true
s.includes('o') // true
s.startsWith('world', 6) // true
s.endsWith('Hello', 5) // true
s.includes('Hello', 6) // false
上面代码表示,使用第二个参数n时,endsWith的行为与其他两个方法有所不同。它针对前n个字符,而其他两个方法针对从第n个位置直到字符串结束。
str.repeat(num)
repeat方法返回一个新字符串,表示将原字符串重复n次。
'x'.repeat(3) // "xxx"
'hello'.repeat(2) // "hellohello"
'na'.repeat(0) // ""
参数如果是小数,会被取整。
'na'.repeat(2.9) // "nana"
如果repeat
的参数是负数或者Infinity
,会报错。
'na'.repeat(Infinity)
// RangeError
'na'.repeat(-1)
// RangeError
但是,如果参数是0到-1之间的小数,则等同于0,这是因为会先进行取整运算。0到-1之间的小数,取整以后等于-0,repeat
视同为0,NAN
等同于0
'na'.repeat(-0.9) // ""
'na'.repeat(NAN) // ""
如果repeat
的参数是字符串,则会先转换成数字。
'na'.repeat('na') // ""
'na'.repeat('3') // "nanana"
模板字符串
(`)
- 模板字符串是增强版的字符串,用反引号(`)标识
- 保持编辑格式
- 支持表达式
- ${表达式}
- 我们可以把一个js的表达式放置在一个${}中,这里的表达式会被js所解析,注意:不支持语句,比如if,for,while等
- 他可以当做普通字符串使用,也可以用来定义多行字符串,或者在字符串中嵌入变量
- 如果需要引入变量,则使用
${变量名}
,在{}中可以进行运算,也可以引用对象属性 - 模板字符串之中还能调用函数。
var name = "Bob", time = "today";
`Hello ${name}, how are you ${time}?`
上面代码中的模板字符串,都是用反引号表示。如果在模板字符串中需要使用反引号,则前面要用反斜杠转义。
var greeting = `\`Yo\` World!`;
.trim()
可以消除模板字符串中的换行或空格
`
<ul>
<li>first</li>
<li>second</li>
</ul>
`.trim()
数组扩展
Array.from()
-
Array.from()
用于将类数组对象转换成真数组 - 只要是部署了
Iterator
接口的数据结构,Array.from
都能将其转为数组。 - 任何有length属性的对象,都可以通过
Array.from
方法转为数组,而扩展运算符(...)并不可以 - 扩展运算符(...)也可以将某些数据结构转为数组。
Array.of()
-
Array.of()
用于将一组参数转换成数组 - 这个方法的主要目的,是弥补数组构造函数
Array()
的不足。因为参数个数的不同,会导致Array()
的行为有差异。
//es5
Array() // []
Array(3) // [, , ,]
Array(3, 11, 8) // [3, 11, 8]
//es6
Array.of(3, 11, 8) // [3,11,8]
Array.of(3) // [3]
Array.of(3).length // 1
arr.find()
- 找出第一个符合条件的数组元素
- 参数:
- 回调函数
- 回调函数内this的指向
- 遍历整个数组,遍历过程中调用回调函数,如果回调函数的返回值为true,则返回当前遍历的元素,如果所有元素都不符合则返回undefined
- find方法的回调函数可以接受三个参数,依次为当前的值、当前的位置和原数组。
var arr = '43157'.split('');
console.log(arr.find( function(item) {
return item > 5;
} , document));//打印出来7
arr.findIndex()
findIndex方法的用法与find方法非常类似,返回第一个符合条件的数组成员的位置,如果所有成员都不符合条件,则返回-1。
console.log( arr.findIndex( function(item) {
//返回true,就表示找到了满足条件的值,则不会继续查找了,那么当前的值的位置作为findIndex的返回值
return item > 4;
} ) );//打印出来0
arr.fill()
- 用来填充数组(会把原位置上的元素替换掉)
- 第一个参数 :填充的内容
- 第二个参数 :启始位置
- 第三个参数 :结束位置
for of
作用:用来遍历拥有遍历接口的对象的属性的值
arr.keys()
遍历并获取数组的所有key,返回数组对象
arr.values()
遍历并获取数组的所有value,返回数组对象
arr.entries()
遍历并获取数组的所有键值对,返回数组对象
数组推导
通过现有数组生成新数组,称为数组推导
var arr = [1,2,3,4];
var arr2 = [for (i of arr) i*2];
var arr3 = [for (i of arr) if(i > 2) i];
var arr4 = [for (i of arr) if(i > 2) if(i < 3) i];
数据结构
Set
- 在es6中新增了一个新的数据结构 - 集合
- Set是一个构造函数,可以不传入任何参数创建一个空的集合,也可以传入一个数组对其进行初始化
- Set和数组类似,但是Set的值是唯一的不重复的
- 不能通过下标进行取值
- 集合是一个可迭代的对象
- 有一个属性,size,类似数组的length属性
- 我们通过Set来保证集合中的值得唯一性,然后如果需要单个取值可以转换成数组来操作
- 操作:
- add(value) : 添加某个值,返回Set结构本身
- delete(value) : 删除某个值,返回一个布尔值,表示删除成功
- has(value) : 返回一个布尔值,表示参数是否为Set的成员
- clear() : 清除所有成员,没有返回值
let s1 = new Set(['a','b','c']);
s1.add('d');
console.log(s1);//a,b,c,d//可以添加元素
s1.add('a');
console.log(s1);//a,b,c,d//可以添加元素,但是如果添加的元素已经存在,则不会添加进去
//s1.clear();//清空
s1.delete('a');
console.log(s1);//b,c,d//可以删除元素
for (let v of s1) {
console.log(v);
}
var arr = [...s1];
console.log(arr);
var arr1 = 'shdfkjwehfhjsdf'.split('');
console.log( [...new Set(arr1)] );//这里把数组转化成set集合再转回数组,由于set集合中的值具有唯一性从而达到去重的目的
WeakSet
WeakSet类似于Set,也是不重复的值得集合,但是他只能用于存储对象,而不能是其他类型的值
- 方法 :
- WeakSet.protoptype.add(value) : 向WeakSet实例添加一个新成员
- WeakSet.protoptype.delete(value) : 删除WeakSet实例指定成员
- WeakSet.protoptype.has(value) : 返回一个布尔值,表示某个值是否在WeakSet实例中
let ws = new WeakSet();
var arr = [1,2];
ws.add(arr);
ws.add(window);
ws.has(window);//true
ws.delete(arr);
ws.has(arr);//false
- WeakSet不能遍历,因为成员都是弱引用,随时可能消失,遍历不能保证成员的存在,可能刚刚遍历结束,成员就取不到了
- WeakSet的一个用处是存储DOM节点,而不用担心这些节点从文档移除时,会引起内存泄漏
let ws = new WeakSet();
function fn () = {};
ws.add(fn());
console.log(ws);//返回一个WeakSet对象,包含fn函数
setTimeout(function () {
console.log(ws);//返回一个WeakSet空对象
},1000)
Map
- Map数据结构类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键
- Object结构停工了“字符串-值”的对应,Map结构提供了“值-值”的对应,是一种更完善的Hash结构实现
- 方法:
- set(key,value) :给Map结构新添加一对键值
- get(key) :返回key所对应的值
- has(key) :返回一个布尔值,判断是否有这个key
- delete(key) :删除key所对应的这一对键值
const m = new Map();
const o = {p : 'Hello World'};
m.set(o,'content');
m.get(o);//返回content
m.has(o);//true
m.delete(o);
Map也接受数组作为参数,该数组的成员是一个个表示键值对的数组
const map = new Map([['name','张三'],['title','标题']]);
map.size;//2
map.has('name');//true
map.get('name');//张三
map.has('title');//true
map.get('title');//标题
Generator 函数
- 特征 :
-
function
关键字与函数名之间有一个星号 - 函数体内部使用
yield
表达式,定义不同的内部状态 - 函数外部调用使用
next
方法,每次调用next
方法,内部指针就从函数头部或上一次停下来的地方开始执行,直到遇到下一个yield
表达式(或return
语句)为止
-
- Generator调用函数后,不会立即执行,返回的也不是函数运行结果,而是一个指向内部状态的指针对象
- 换言之,Generator 函数是分段执行的,yield表达式是暂停执行的标记,而next方法可以恢复执行。
- Generator 函数返回的遍历器对象,只有调用next方法才会遍历下一个内部状态,所以其实提供了一种可以暂停执行的函数。yield表达式就是暂停标志。
function* helloWorldGenerator() {
yield 'hello';
yield 'world';
return 'ending';
}
var hw = helloWorldGenerator();
hw.next()
// { value: 'hello', done: false }
hw.next()
// { value: 'world', done: false }
hw.next()
// { value: 'ending', done: true }
hw.next()
// { value: undefined, done: true }
yield 表达式
yield
表达式就是暂停标志遍历器对象的
next
方法的运行逻辑如下。-
遇到
yield
表达式,就暂停执行后面的操作,并将紧跟在yield
后面的那个表达式的值,作为返回的对象的value
属性值。下一次调用
next
方法时,再继续往下执行,直到遇到下一个yield
表达式。如果没有再遇到新的
yield
表达式,就一直运行到函数结束,直到return
语句为止,并将return
语句后面的表达式的值,作为返回的对象的value
属性值。如果该函数没有
return
语句,则返回的对象的value
属性值为undefined
。
需要注意的是,
yield
表达式后面的表达式,只有当调用next
方法、内部指针指向该语句时才会执行,因此等于为 JavaScript 提供了手动的“惰性求值”(Lazy Evaluation)的语法功能。yield
表达式与return
语句既有相似之处,也有区别。相似之处在于,都能返回紧跟在语句后面的那个表达式的值。区别在于每次遇到yield
,函数暂停执行,下一次再从该位置继续向后执行,而return
语句不具备位置记忆的功能。一个函数里面,只能执行一次(或者说一个)return
语句,但是可以执行多次(或者说多个)yield
表达式。正常函数只能返回一个值,因为只能执行一次return
;Generator 函数可以返回一系列的值,因为可以有任意多个yield
。从另一个角度看,也可以说 Generator 生成了一系列的值,这也就是它的名称的来历
next方法的参数
-
yield
表达式本身没有返回值,或者说总是返回undefined
。next
方法可以带一个参数,该参数就会被当作上一个yield
表达式的返回值。 - 这个功能有很重要的语法意义。Generator 函数从暂停状态到恢复运行,它的上下文状态(context)是不变的。通过next方法的参数,就有办法在 Generator 函数开始运行之后,继续向函数体内部注入值。也就是说,可以在 Generator 函数运行的不同阶段,从外部向内部注入不同的值,从而调整函数行为。
function* foo(x) {
var y = 2 * (yield (x + 1));
var z = yield (y / 3);
return (x + y + z);
}
var a = foo(5);
a.next() // Object{value:6, done:false}
a.next() // Object{value:NaN, done:false}
a.next() // Object{value:NaN, done:true}
var b = foo(5);
b.next() // { value:6, done:false }
b.next(12) // { value:8, done:false }
b.next(13) // { value:42, done:true }
Promise
解决js中异步编程的问题
当我们new Promise会得到一个Promise对象,该对象下会有一个任务要完成,同时该对象会维护一个状态
Promise :
- ES6新增的一个内置对象
- 解决JS中异步编程问题
异步/同步
- 被请求者(该事情的处理者)在处理完事情的时候的通知机制
阻塞/非阻塞
- 针对请求者来说的,委托其他人处理一些事情的时候,请求者是等待请求处理完成还是继续向下执行其他的任务
Promise:
- 构造函数
- new Promise(callback)
- callback: 要异步处理的任务
通过Promise构造函数得到一个Promise对象,我们传入的callback将会被Promise对象所执行
Promise会维护一个任务状态,这个状态是一个Promise内部的属性
[[PromiseStatus]] :
- pending : 正在处理,Promise对象一旦被创建就会更改为该状态,他是默认值
- resolved : 已完成
- rejected : 失败
- resolved,rejected这两个状态是需要靠我们手动去维护的。因为异步任务的结果是成功还是失败,是由我们的具体业务所决定的,Promise对象是没有办法确定的。我们需要在我们业务处理中,去根据实际情况改变Promise对象的状态
当我们把一个callback函数传递给Promise对象的时候,Promise对象会去执行该callback函数,同时,还会传递两个参数给这个callback,所以,我们可以在callback函数接收这两个参数
- resolve :当我们调用该函数的时候,会把当前的Promise对象的状态改成resolved
- reject :当我们调用该函数的时候,会把当前的Promise对象的状态改成rejected
这两个参数都是一个函数,当我们调用他们的时候,会改变当前任务的状态
- resolve() => resolved
- reject() => rejected
let p1 = new Promise(function(resolve, reject) {
setTimeout(() => {
var a = Math.random();
if (a < 0.5) {
reject(a);
} else {
resolve(a);
}
}, 1000)
});
then方法
Promise对象还有一个then方法,当前置任务完成的时候,会调用该方法,并执行该方法传入函数参数
then方法接收两个参数,他们都是函数
- 该方法接收两个参数,这两个参数都是callback函数,这两个callback不会立即执行,当Promise的状态一旦发生改变就会执行then方法中传入的函数,有点类似事件绑定
- 第一个参数是当状态为resolved时候执行
- 第二个参数是当状态为rejected时候执行
p1.then(function(a) {
console.log('成功', a);
},function(a) {
console.log('失败', a);
});
then方法传递数据
其中a,在promise内部为实参,then方法中函数里的a为形参,接收内部传过来的实参a
一旦一个Promise的状态改变了,是没有办法在重置该Promise对象的状态的,我们只能另外创建一个新的Promise对象
虽然then方法中的参数接收两个
一个是成功的callback
一个是失败的callback
但是,在书写的时候,如果每一个then都传这两个callback的话,编写过程太麻烦
为了能够统一的去处理失败,Promise对象又提供了一个新的方法:catch
catch方法也接收一个callback
只要有一个Promise的状态变成了rejected,那么就会被catch方法捕获,执行catch的callback
var b = 10;
new Promise((resolve, reject) => {
setTimeout(() => {
b += 10;
resolve();
// reject();
}, 1000);
}).then(function() {
console.log(b);
return new Promise((resolve, reject) => {
setTimeout(() => {
b *= 2;
resolve();
}, 1000);
});
}).then(function() {
console.log(b);
return new Promise((resolve, reject) => {
setTimeout(() => {
b *= b;
// resolve();
reject();
}, 1000);
});
}).then(function() {
console.log(b);
}).catch(function() {
console.log(1);
});
all方法
- 有的时候在一个Promise任务中需要处理多个异步任务,那这多个异步的任务是同时执行的,但是执行时间有是不确定的。后续的任务需要这几个异步任务全部完成以后再执行,那么就需要用到Promise中提供的all方法来实现
- 把两个不同的异步任务分别包装在一个Promise对象中,然后调用Promise对象静态方法all,把上面多个不同异步Promise作为数组传递给all方法的参数
- 这个时候Promise.all方法中会维护一个状态,这个状态是根据传入的多个异步任务的状态共同决定的
- 当多个异步任务的状态都变成了resolved,那么all的状态才是resolved,但是只要有一个异步任务的状态变成了rejected,那么all的状态就会变成rejected
var p1 = new Promise((resolve, reject) => {
let a = 1;
setTimeout(() => {
a++;
// reject('one');
resolve(a);
}, Math.random() * 1000);
});
var p2 = new Promise((resolve, reject) => {
let b = 2;
setTimeout(() => {
b++;
resolve(b);
}, Math.random() * 1000);
});
Promise.all([p1, p2]).then(([a, b]) => {
console.log(a, b);
}).catch((err) => {
console.log(err);
})
async,await
- 把异步的任务通过Promise进行包装
- 在异步函数(这个函数必须返回Promise对象)前面加上一个关键字 await,后续的代码不需要在then中去执行,而是想普通的同步代码一样书写就行
- 注意:await必须在函数中才能使用,并且该函数必须申明成异步函数 : 通过关键字 async
async function fn() {
var a = 1;
a = await todo(a);
console.log(a);
}
function todo(a) {
return new Promise((resolve) => {
setTimeout(() => {
a += 10;
resolve(a);
}, 1000);
})
}
运动实例
button.onclick = async function() {
await startMove(div, {
width: 200
});
await startMove(div, {
height: 200
});
await startMove(div, {
left: 200
});
await startMove(div, {
top: 200
});
}
function startMove(ele, attrs, duration=1000, fx='linear') {
return new Promise((resolve) => {
animation(ele, attrs, duration, fx, () => {
resolve();
});
})
}
Object.defineProperty()
对对象的属性进行 定义/修改
返回值 :被传递给函数的对象
var obj = {};
Object.defineProperty(obj,y,{
configurable: false,
enumerable:false,
value: 100
})
参数 :
- obj :要在其上定义属性的对象
- y :要定义或修改的属性的名称
- 参数对象 :
- configurable :设置是否能删除,默认为false
- value :设置属性值
- enumerable :改属性是否能够出现在对象的美剧属性中,默认为false
- writable :该属性值能否被赋值运算符改变,默认为false
- get() : 当obj的y属性被调用的时候触发,该方法的返回值将作为获取的结果
- set(value) :当obj的y属性被设置的时候触发,该方法拥有唯一参数,并将该参数的新值分配给该属性
- get和set不能与configurable,enumerable,value,writable同时存在
let obj = {x:10}
let y = 100;
Object.defineProperty(obj, 'y', {
get() {
//当obj的y属性被调用的时候触发,该方法的返回值将作为获取的结果
console.log('get');
return y;
},
set(value) {
//当obj的y属性被设置的时候触发
console.log('set', value);
y = value;
}
})
console.log(obj.y);
obj.y = 1;
console.log(obj.y);
数据双向绑定实例 :
<input type="text" id="age">
<h1></h1>
<script>
var ageElement = document.querySelector('#age');
var h1Element = document.querySelector('h1');
let obj = {};
Object.defineProperty(obj, 'age', {
get() {
},
set(value) {
ageElement.value = value;
h1Element.innerHTML = value;
}
});
obj.age = 10;
ageElement.oninput = function() {
obj.age = this.value;
}
</script>