强烈推荐阮一峰老师es6
常量
- ES5 中常量的写法(借助
Object.defineProperty
实现)
Object.defineProperty(window,'PI2',{
value : 3.1415926,
writable : false //通过将其属性设置为不可写,实现常量
})
- ES6 中常量的定义(使用
const
)
const PI=3.1415926
作用域
- ES5 使用立即执行函数实现
;(function(){
var a=1
console.log(a) // 1
})()
console.log(a) //a is not defined
此时在该立即执行函数外打印 a
会报错,未定义
- ES6 使用
{}
实现块级作用域
{
let b=2
console.log(b) //2
{
let b=3
console.log(b) //3
}
console.log(b) //2
}
console.log(b) // b is not defined
此时,可以发现在内部重新定义 b
的值并不会影响外部 b
的值。通过配合 let
使用,可以实现只在该块级作用域内实现变量的声明。
let var 和 const
let
- let 的作用域在最近的 {} 之间
- 如果在 let a 之前使用 a 会报错,即声明前使用会报错
- 如果重复 let a 会报错,即重复声明同一变量会报错
const
1.2.3. 同上
- 只有一次赋值机会,而且必须在声明时立即赋值
数组方法
forEach
在 ES5 中遍历数组使用 for 循环,但是 ES6 出现新的数组方法 forEach 方法
var colors=['red','green','black']
//ES6
colors.forEach(function(color){ //可以使用箭头函数
console.log(color)
})
使用场景:
-遍历数组中的值,并计算求和
var list=[1,2,3,4]
var sum=0
function adder(number){
sum +=number
}
list.forEach(adder) //也可以直接将求和函数放入其中
map
是将数组中的每一项做相同的函数处理,再返回。在 ES5 中需要使用到 for 循环将每一项做相同处理。
使用场景:
- 将数组 A 中每一项乘以2,放入数组 B
var numbers=[1,2,3]
var double=[]
double=numbers.map(function(item){
return item*2
})
- 将对象数组 A 中的某一指定项存入到数组 B 中
var cars=[{
model:'a',price:'cheap'
},{
model:'b',price:'expensive'
}]
var prices=cars.map(function(item){
return item.price
})
filter
返回数组中指定项。(找到所有满足条件的项)
使用场景:
- 返回对象数组 A 中指定项到数组 B 中
var porducts = [
{name:"cucumber",type:"vegetable"},
{name:"banana",type:"fruit"},
{name:"celery",type:"vegetable"},
{name:"orange",type:"fruit"}
]
var filter=porducts.filter(function(item){
return item.type === 'fruit'
})
- 可以在对象数组 A 中过滤掉不满足条件的项
var products = [
{name:"cucumber",type:"vegetable",quantity:0,price:1},
{name:"banana",type:"fruit",quantity:10,price:16},
{name:"celery",type:"vegetable",quantity:30,price:8},
{name:"orange",type:"fruit",quantity:3,price:6}
]
var pros=products.filter(function(item){
return item.type === 'fruit' && item.quantity > 5 && item.price >5
})
- 两个数组 A 和 B ,根据 A 中 id 过滤掉 B 中不符合项
var a = {id:4,title:"Javascript"}
var b = [
{postId:4,content:"Angular4"},
{postId:2,content:"Vue.js"},
{postId:3,content:"Node.js"},
{postId:4,content:"React.js"},
]
var c=b.filter(function(item){
return item.postId === a.id
})
find
与 filter
方法类似,但是也不同。是找到符合条件的那一项,就会停止(找到即停止)。
var users = [
{name:"Jill"},
{name:"Alex",id:2},
{name:"Bill"},
{name:"Alex"}
]
var user=users.find(function(item){
return item.name === 'Alex'
})
console.log(user) //{name: "Alex", id: 2}
在上例中,只会返回带有 id
的那一项(找到即停止)
every & some
可以根据其英文意思来推断它的作用。 every
:每一个条件都要满足(一真即真); some
:满足其一条件即可(一假即假)。
使用场景:
- 用来判断数组中每一项是否满足某某条件
var computers = [
{name:"Apple",ram:16},
{name:"IBM",ram:4},
{name:"Acer",ram:32}
]
var every=computers.every(function(item){
return item.ram > 16
})
console.log(every) // false
var some=computers.some(function(item){
return item.ram > 16
})
console.log(some) // true
- 用于判断表单中是否有数据(具体还需去除两端空格等等)
var username = "xxx"
var telephone = "888"
var password = "666"
var fields = [username,telephone,password]
var p=fields.every(function(item){
return item.length > 0
})
reduce
可以理解为减少,对累计器和数组中的每个元素(从左到右)应用一个函数,将其简化为单个值。可以实现相加,将对象数组中的某一项抽离等。其中 reduce
方法中传入四个参数,acc
(累计器)、cur
(当前值)、idx
(当前索引)、src
(源数组)
使用场景:
- 数组中每一项相加
var a=[1,2,3]
var b=a.reduce(function(sum,item){ // 第一个参数是累加的项,第二个参数是源数组的项
return sum += item
},0) // 此处的0表示 sum 的初始值
- 将对象数组中的指定项抽离
var primaryColors = [
{color:"red",id:1},
{color:"yellow",id:2},
{color:"blue",id:3}
]
var colors=primaryColors.reduce(function(xxx,item){
xxx.push(item.color) //先执行每一项的push操作
return xxx //返回的是执行push操作后的数组
},[])
console.log(colors)
- 用于判断字符串中括号是否对称(定义一个变量,遇到 ‘(’ 加1,遇到 ‘)’ 减1,通过判断最后变量值确定是否对称)
function balancedParens(string){
return !string.split("").reduce(function(x,item){ // 将字符串分隔为数组
if(x<0){return x} // 判断首项是否为 ‘)’ ,是直接返回
if(item==='('){return ++x}
if(item===')'){return --x}
return x
},0)
}
console.log(balancedParens(')()()(')) // false
模板字符串
在 ES5 中对于字符串的拼接需要使用 '' 与 + 的组合来实现,而且不能在其中换行,只能使用拼接方式实现换行和变量的引入。
但是,在 ES6 中使用反引号实现模板字符串。其中还可以使用变量和方法等。
var name='Tony'
function toUp(wold){
return wold.toUpperCase()
}
var string=`${toUp(hello)},${name}
文本内容
`;
箭头函数
- 缩减代码
- 改变 this 指向
- 缩减代码
当只有一个参数时,括号可以省略
当只存在返回体时,return
可省略,花括号也可以省略
当有多条语句时,并且存在返回值时,必须添加花括号和return
var a=function(number){
return number*2
}
// 可以使用箭头函数
var a1= number => number*2
- 改变 this 指向
在 ES5 中有两种方法可以实现函数嵌套函数时 this 指向问题,如下:
var team1={
members : ['a','b'],
teamName : 'C',
teamSummary : function(){
return this.members.map(function(item){
return `成员${item}隶属于${this.teamName}小组`
})
}
}
console.log(team1.teamSummary()) //["成员a隶属于undefined小组", "成员b隶属于undefined小组"]
这样虽然不会报错,但是会出现 undefined
。这时我们需要在函数中使用变量存放外部 this
或者使用 bind
调用函数。
var team2={
members : ['a','b'],
teamName : 'C',
teamSummary : function(){
var self=this // 使用变量存放 this
return this.members.map(function(item){
return `成员${item}隶属于${self.teamName}小组`
})
}
}
/********************************************************/
var team3={
members : ['a','b'],
teamName : 'C',
teamSummary : function(){
return this.members.map(function(item){
return `成员${item}隶属于${this.teamName}小组`
}.bind(this)) //使用 bind 传入 this
}
}
但是,在 ES6 中可以使用箭头函数改变 this 指向
var team4={
members : ['a','b'],
teamName : 'C',
teamSummary : function(){
return this.members.map(item=>`成员${item}隶属于${this.teamName}小组`)
//使用 箭头函数 改变 this 的指向
}
}
总结:箭头函数的 this
指向是定义时 this
的指向
var a=function (){
this.b = 'b',
this.c = 'c',
this.d = {
b : '+b',
v : ()=>{return this.b}
}
}
console.log(new a().d.v()) // b
此时,调用函数中的 v
函数,this
指向是定义时的 this
指向。这里改为非箭头函数得到的结果是 +b
(可以理解为箭头函数不算定义函数的方式)注意:这里调用时,使用 new
方法,如果不使用 new
方法,是没有 this
的。
增强对象字面量
- 当对象的“键”和“值”相同的情况下可以省略,只写一个
- 当对象中有方法时:
:function
可以省略
function createShop(list){
return {
list, // list:list
listValue(){ //省略 :function
return this.list.reduce((sum,item)=>{return sum += item.price},0)
},
priceForTitle(title){
return this.list.find(item=>item.title === title).price
}
}
}
var bookList= [
{title:"Vue",price:10},
{title:"Angular",price:15}
]
var bookShop = createShop(bookList)
console.log(bookShop.listValue())
console.log(bookShop.priceForTitle("Angular"))
函数参数默认值
在 ES6 中可以设置函数参数的默认值,通过以下方式:
function a(b,c=3){
return c
}
console.log(a(1)) // 3
console.log(a(1,2)) //2
可以看出第一个输出的情况下,并没有传入第二个参数 c
但是第二个参数通过 c=3
传入一个默认值 3 。第二个输出的情况下,传入第二个参数,所以 a
函数中第二个参数的默认参数失效。
展开运算符
可以在函数调用/数组构造时, 将数组表达式或者
string
在语法层面展开;还可以在构造字面量对象时, 将对象表达式按key-value的方式展开。(译者注: 字面量一般指[1, 2, 3]
或者{name: "mdn"}
这种简洁的构造方式)
function add(number){
return number.reduce((sum,item)=>{
return sum += item
},0)
}
console.log(add([1,2,3]))
使用展开运算符进行操作,就简单很多。
function add(...number){
return number.reduce((sum,item)=>{
return sum += item
},0)
}
console.log(add(1,2,3,4,5,6))
同时,展开运算符的方式等同于 apply
的方式(但是通过 new
方式构造的函数不适用):
function add(x,y,z){
return x+y+z
}
console.log(add.apply(null,[1,2,3]))
还可以通过展开语法实现数组的拼接与复制等。
//数组拼接
var a = ['1' , '3']
var b = ['0' , ...a , '4' , '5'] // ["0", "1", "3", "4", "5"]
// 数组复制
var c = [6 , 7]
var d = [...c] // [6 , 7]
d.push(8) // [6 , 7 , 8]
console.log(c) // [6 , 7] 可得出通过展开运算复制的数组,不影响源数组
// 还可以实现不同位置的拼接,达到 unshift 的方法
var e = [...c , ...a] // [6, 7, "1", "3"]
解构赋值
解构赋值语法是一个 Javascript 表达式,这使得可以将值从数组或属性从对象提取到不同的变量中。
特点:更快,更便捷。
结构复制可以是数组,也可以是对象。
- 对象
在 ES5 中,实现方法。
var img = {
extension: "jpg",
name:"girl",
size:14040
}
// es5
function imgSummary(file){
return `${file.name}.${file.extension}的尺寸为${file.size}`
}
console.log(imgSummary(img)) //girl.jpg的尺寸为14040
但是在 ES6 中可以使用解构赋值的方式实现该方法
function imgSummary({name,extension,size}){
return `${name}.${extension}的尺寸为${size}`
}
上述方法中可以看做 {name,extension,size}=img
等同于 name=img.name
extension=img.extension
size=img.size
这三行代码。
- 数组
在数组的解构赋值中:当解构个数等于数组的项数时,可得到数组每一项;当不等于时,会得到从左边开始解构个数;当只有一个值并且以{}
包住时,返回数组个数;
var list = ['a' , 'b' , 'c']
// 返回全部数组
var [name1,name2,name3] = list
console.log(name1,name2,name3) // 'a' 'b' 'c'
// 返回需要个数
var [name4,name5] = list
console.log(name4,name5) // 'a' 'c'
// 返回数组个数
var {length} = list
console.log(length) // 3
还可以结合展开运算符,使用了展开运算的存放剩下的数组。
var [name6 , ...rest] = list
console.log(name6) // 'a'
console.log(rest) // ["b", "c"]
- 对象数组
var people = [
{name:'a',age:15},
{name:'b',age:20},
{name:'c',age:25}
]
// es5 取其中某一项的 age
var age = people[0].age
// es6 中取某一项的age 使用 [] 取到项,在使用 {} 结构到具体某一项
var [{age}] = people
上述,先使用数组的解构得到第一项,在使用对象的解构得到需要的项。
- 使用场景:将数组转换为对象
var points = [
[4,5],
[10,1],
[0,40]
]
// 期待格式
[
{x:4,y:5},
{x:10,y:1},
{x:0,y:40}
]
// es6 解构赋值
var newPoint = points.map(([x,y])=>{
return {x,y}
})
面向对象(class)
ES5 有很多方法实现继承
// 定义 Car
function Car(options) {
this.title=options.title
}
// 定义 Car 的方法
Car.prototype.drive=function(){return 'move'}
// new 一个 Car
var car = new Car({title:'BMW'})
// 重新定义一个车的品牌,并继承 Car 的p品牌和行为
function Toyota (options){
Car.call(this,options) // 通过call调用Car实现title的继承
this.color=options.color
}
// 实现方法的继承
Toyota.prototype=Object.create(Car.prototype) // 通过Object.create创建一个函数指向Car
Toyota.constructor=Toyota // Toyota的构造函数指向Toyota(可不加)
var toyota=new Toyota({title:'Toyota ',color:'red'})
这样就是在 ES5 通过 Object.create()
实现继承,这时 toyota
拥有 title
color
属性和 drive
方法。注:这种方法有兼容性的问题,低版本浏览器可能不兼容
但是,在 ES6 中只需要使用 extends
配合 super
实现继承
class Car{
constructor({title}){
this.title=title
}
drive(){
return 'move'
}
}
// Toyota 只需要使用 extends 关键字
class Toyota extends Car {
constructor(options){
super(options)
this.color=options.color
}
}
在 ES6 中构造器函数(constructor
)定义对象的属性,然后在定义其方法(不加逗号)。
generator生成器
生成器对象是由一个 generator function 返回的,并且它符合可迭代协议和迭代器协议。
简单说:可以返回多次函数,类似于迭代器函数
function* number(){
yield 1
yield 2
}
var n=number()
console.log(n.next()) // {value: 1, done: false}
console.log(n.next()) // {value: 2, done: false}
console.log(n.next()) // {value: undefined, done: true}
如上所述:在 function
后添加 *
号既是 generator
生成器。在 generator
生成器中有 yield 可以返回相应的值,格式是 key-value
。当超出数量再次调用时,如上。
- 使用
generator
生成器完成斐波那契数列
function* fib(max){
var a=0,b=1,n=0
while(n
注意:在 yield 后必须加分号,否则会出 bug
- 使用迭代器还原 generator 生成器的结构
function number(numbers){
let nextIndex=0
return {
next:function(){
return nextIndex < numbers.length?
{value:numbers[nextIndex++],done:false}:
{value:undefined,done:true}
}
}
}
var n=[1,2]
var ns=number(n)
console.log(ns.next()) // {value: 1, done: false}
console.log(ns.next()) // {value: 2, done: false}
console.log(ns.next()) // {value: undefined, done: true}
在实际应用场景中,可以应用到 id自增器
新的数据结构
数据结构:map
键值对:与对象不同的是键和值都可以是任意数据类型
var map1=new Map()
// 设置 key 值
var key1='string',
key2={},
key3=function(){}
// 使用set设置value值
map1.set(key1,'Value of key1')
map1.set(key2,'Value of key2')
map1.set(key3,'Value of key3')
// 使用for-of遍历key值(结合keys方法)
for(let key of map1.keys()){
console.log(key) // string {} ƒ (){}
}
//使用set获取value值
console.log(map1.get(key1)) // Value of key1
//使用size获取数据数量
console.log(map1.size) // 3
数据结构:set
集合:可以储存任意数据类型,并且是唯一的(不重复)
var set1=new Set()
// 使用add方法向set1添加数据
set1.add(100)
set1.add('string')
set1.add({a:'b'})
set1.add(true)
set1.add(100) // 重复数据,不会再set1中显示,且不占位置
console.log(set1) //{100, "string", {…}, true}
// 使用size计算数据数量
console.log(set1.size) // 4
// 使用has方法判断set1是否有该数据
console.log(set1.has(100)) // true
console.log(set1.has(50+50)) // true
console.log(set1.has({a:'b'})) // false 由于对象是引用类型,地址不同所以这里是false
// 使用delete方法删除某一数据
set1.delete(100)
console.log(set1) // {"string", {…}, true}
// 使用for-of遍历数据
for(let item of set1){
console.log(set1) // 打印三遍{"string", {…}, true}
}
新的数据结构总结
因为,这两个新数据结构都是类数组类型,都可以使用 forEach
方法遍历。
并且都可以使用数组的新方法 from
使类数组结构转换为数组结构。
var map2=Array.from(map1)
var set2=Array.from(set1)
console.log(map2) //[["string", "Value of key1"], [{…}, "Value of key2"], [ƒ, "Value of key3"]]
console.log(set2) // [100, "string", {…}, true]
同时,由于 new Set()
数据结构中数据的不重复性,所以可以用来实现数组去重。
var a=[1,1,1,2,3,4,5,3,2]
var b=Array.from(new Set(a))
console.log(b) // [1, 2, 3, 4, 5]
promise
Promise 对象用于表示一个异步操作的最终状态(完成或失败),以及其返回的值。
Promise
返回的是一个函数,所以需要使用.then()
使用。即是链式操作。
一个promise可能有三种状态:等待(pending
)、已完成(fulfilled
)、已拒绝(rejected
)。
- 已完成
Fulfilled
resolve
成功时,调用onFulfilled
- 已拒绝 Rejected
reject
失败时,调用onReject
- 等待
Pending
既不是成功(resolve
)也不是失败(reject
),而是在promise函数创建后的初始状态
let promise=new Promise((resolve,reject)=>{
resolve()
//reject()
})
promise.then(()=>console.log('成功1'))
.then(()=>console.log('成功2'))
.catch(()=>console.log('失败'))
当上述代码中存在 resolve() 且执行时,在控制台会打印出‘成功1’和‘成功2’。当存在 reject() 且执行时,控制台会打印出‘失败’。
在没有 promise 对象的 ES5 中使用回调函数实现。
Promise.all() 可以集中处理多个异步
Promise.all()生成的是一个新的Promise实例。成功和失败返回值不同,成功时返回的是成功的数组,失败时只要有一个失败,就会返回最先失败的结果
其执行顺序与传入到 Promise.all()
的数组的顺序一致
Promise.all(iterable)
方法返回一个Promise
实例,此实例在iterable
参数内所有的promise
都“完成(resolved)”或参数中不包含promise
时回调完成(resolve);如果参数中promise
有一个失败(rejected),此实例回调失败(reject),失败原因的是第一个失败promise
的结果。
简单的说:就是将代码中所有的 Promise 对象以数组的形式放在 Promise.all(iterable)
中(即是 iterable),将 all 中所有的异步执行完毕后才会去执行 Promise.all(iterable)
后的 .then() 方法,但是如果有一个失败则次实例回调失败。
var p1=Promise.resolve(1)
var p2=Promise.resolve(2)
var p3=Promise.resolve(3)
Promise.all([p1,p2,p3]).then(val=>console.log(val)).catch(()=>console.log('error')) // [1,2,3]
// 当其中有一个执行了reject函数
var p4=Promise.resolve(4)
var p5=Promise.reject(5)
var p6=Promise.resolve(6)
Promise.all([p4,p5,p6]).then(val=>console.log(val)).catch(()=>console.log('error')) // error
注:如果 Promise.all()
中有多个异步执行,最终返回的事件按照延时最长的计算。
有关于Promise试题
关于数据请求的新API fetch (ES7)
fetch
是在 ES7 出现的新的关于实现数据请求的 API 。传统情况下,一般使用 XHR
(XMLHttpRequest
) 浏览器的原生 API 实现或者使用 jQuery 封装的 AJAX
(异步的JS请求)。
现在出现了新的浏览器原生 API fetch
可以实现数据请求。基于Promise,更简单,更便捷。
Fetch API 提供了一个获取资源的接口(包括跨域)。
因为 fetch()
得到的是 Promise 的 pending
状态,.then()
得到的是Response对象,所以需要再 .then()
。一般情况下都需要两次
let url = "http://jsonplaceholder.typicode.com/posts"
fetch(url) // 获得的是promise的pending 等待任务完成
.then(res=>res.json()) // 得到的是Response对象,需要进行json解析
.then(data=>console.log(data)) // [{…}, {…},.....,{…}, {…}]
.catch(error=>console.log(error)) // 如果出错会打印出错误
注:如果是返回状态码大于300,都不会打印出错误,只会在控制台提示;只有状态码300以下的才会打印出错误
let url = "http://jsonplaceholder.typicode.com/posts123" // 这里更改
fetch(url) // 获得的是promise的pending 等待任务完成
.then(res=>res.json()) // 得到的是Response对象,需要进行json解析
.then(data=>console.log(data)) // [{…}, {…},.....,{…}, {…}]
.catch(error=>console.log(error)) // 如果出错会打印出错误
如果是下边一种情况,在请求域名出现问题:
let url = "http://jsonplaceholder.typicode123.com/posts" // 这里更改
fetch(url) // 获得的是promise的pending 等待任务完成
.then(res=>res.json()) // 得到的是Response对象,需要进行json解析
.then(data=>console.log(data)) // [{…}, {…},.....,{…}, {…}]
.catch(error=>console.log('错误:'+ error)) // 如果出错会打印出错误
fetch()
可以请求三种数据格式:本地 text 数据、本地 json 数据、网络 api 数据。(都是使用两次 .then()
得到数据)
参考文章:传统 Ajax 已死,Fetch 永生
参考文章: fetch,终于认识你
async & await (ES7)
async
关键字放置在函数的前面,确保函数返回的是 Promise 对象。如果代码中有 return <非promise>
语句,JavaScript会自动把返回的这个value值包装成promise的resolved值。
async function time(){
return 'hello'
}
console.log(time()) // Promise {: "hello"}
await
关键字在函数内部使用,async
函数返回的 Promise
对象,必须等到内部所有的 await
命令的 Promise
对象执行完,才会发生状态改变
当函数内部有 await
关键字时,会等待执行完成后,再去执行后边的函数
可以看到有 async
关键字的函数返回的是 Promise
对象,并且虽然其先执行,但是先打印出了‘1’,说明其是异步的。
正常情况下,await
命令后面跟着的是 Promise
,如果不是的话,也会被转换成一个 立即 resolve
的 Promise
async function time(){
return await 1
}
time().then(val=>console.log(val)) // 1
注意:await 不能在顶级作用域(即不能直接跟 fetch(url)
,需要在包裹在 async 函数内)
封装 fetch 库实现增删改查(使用async/await简化代码)
定义一个类,使用 fetch 获取数据,并返回一个 Promise 对象
class Http{
// get 请求
get(url){
return new Promise((resolve,reject)=>{
fetch(url)
.then(res=>res.json()) // 将得到的数据转为可读的json数据
.then(data=>resolve(data)) // 成功函数
.catch(error=>reject(error)) // 失败函数
})
}
// post 请求
post(url,data){
return new Promise((resolve,reject)=>{
fetch(url,{ // fetch 的第二个参数
method:'POST', // 定义请求方式
headers:{ // 定义请求头
'Content-type':'application/json'
},
body:JSON.stringify(data) // 定义第四部分的格式
})
.then(res=>res.json())
.then(data=>resolve(data))
.catch(error=>reject(error))
})
}
// put 和 delete 请求类似于 post 请求,delete请求没有第四部分
}
// 使用该库
let http=new Http
http.get('http://jsonplaceholder.typicode.com/users')
.then(data=>console.log(data))
使用 async/await 简化代码
class Http{
// get 请求
async get(url){
let response=await fetch(url)
let resData=await response.json()
return resData
}
// post 请求
async post(url,data){
let response=await fetch(url,{ // fetch 的第二个参数
method:'POST', // 定义请求方式
headers:{ // 定义请求头
'Content-type':'application/json'
},
body:JSON.stringify(data) // 定义第四部分的格式
})
let resData=await response.json()
return resData
}
// put 和 delete 请求类似于 post 请求,delete请求没有第四部分
}
// 使用该库
let http=new Http
http.get('http://jsonplaceholder.typicode.com/users')
.then(data=>console.log(data))
async/await
的引入是代码更加简洁和易读