ECMAScript 学习
通过我们说的 ES6 泛指 ES5 之后的下一代标准,涵盖了 ES6, ES7, ES8…
ES5-数组的新方法
forEach
forEach
方法对数组的每个元素执行一次提供的函数。功能等同于for
循环.
应用场景:为一些相同的元素,绑定事件处理器!
var arr = ['张飞', '关羽', '赵云', '马超']
//第一个参数:item,数组的每一项元素
//第二个参数:index,数组的下标
//第三个参数:array,正在遍历的数组
//常用的参数就第一个和第二个,第三个参数基本上没啥用
arr.forEach(function(item, index, array) {
console.log(item, index, array)
})
// 箭头函数写法----箭头函数说明在后面
arr.forEach((item, index, array) => {
console.log(item, index, array)
})
// 优点 1. 不占用全局变量
// 优点 2: 结合箭头函数使用
map
map()
方法创建一个新数组,其结果是该数组中的每个元素都调用一个提供的函数后返回的结果。返回的数组的长度和原数组是一样的
var arr = [1, 2, 3, 4, 5] // 1 4 9 16 25
//第一个参数:element,数组的每一项元素
//第二个参数:index,数组的下标
//第三个参数:array,正在遍历的数组
//返回值:一个新数组,每个元素都是回调函数的结果。
var newArray = arr.map(function(element, index, array) {
return element * element
})
console.log(newArray) //[1,4,9,16,25]
// 箭头函数写法
var newArray = arr.map(element => element * element)
filter
filter
用于过滤掉“不合格”的元素
返回一个新数组,如果在回调函数中返回 true,那么就留下来,如果返回 false,就扔掉,因此返回的数组的长度和原数组的长度不一定一致
var arr = [1000, 5000, 20000, 3000, 10000, 800, 1500]
//第一个参数:element,数组的每一项元素
//第二个参数:index,数组的下标
//第三个参数:array,正在遍历的数组
//返回值:一个新数组,存储了所有返回true的元素
var newArray = arr.filter(function(element, index, array) {
if (element > 5000) {
return false
} else {
return true
}
})
console.log(newArray) //[1000, 5000, 3000, 800, 1500]
// 箭头函数写法
var newArray = arr.filter(element => element > 5000)
some
some
用于遍历数组,如果有至少一个满足条件,就返回 true,否则返回 false。同样的每一个元素都会执行一遍 function,相当于最后返回的结果进行了一个 ||
运算,全为 false 才是 false
var arr = [2, 4, 6, 8, 10, 21] // 判断数组是否包含奇数
//第一个参数:element,数组的每一项元素
//第二个参数:index,数组的下标
//第三个参数:array,正在遍历的数组
//返回值:布尔类型的值,只要有一个回调函数返回true,就返回true
var flag = arr.some(function(element, index, array) {
console.log(element, index, array)
if (element % 2 == 1) {
return true
} else {
return false
}
})
console.log(flag) //true
// 箭头函数写法
var flag = arr.some(element => element % 2 == 1)
every
every
用于遍历数组,只有当所有的元素返回 true,才返回 true,否则返回 false,每个元素执行完 function 返回的结果做一个 &&
运算,全为 true 结果才是 true
var arr = [2, 4, 6, 8, 10, 21] //判断数组是否都是偶数
//第一个参数:element,数组的每一项元素
//第二个参数:index,数组的下标
//第三个参数:array,正在遍历的数组
//返回值:布尔类型的值,只有当所有的元素返回true,才返回true,否则返回false。
var flag = arr.every(function(element, index, array) {
console.log(element, index, array)
if (element % 2 == 0) {
return true
} else {
return false
}
})
console.log(flag) //false
// 箭头函数写法
var flag = arr.some(element => element % 2 == 0)
-
forEach: 作用:只会让每个元素执行一次函数, 没有别的功能 for 循环
-
map: 作用:返回一个新的数组,长度和原数组一样 新数组会保存每次 function 返回的值
-
filter: 作用:得到一个新数组, 保留哪些满足条件(返回 true)
-
some: 作用:得到布尔值 只要有函数返回 true,整体结果就是 true
-
every: 作用:得到布尔值,要所有的函数都返回 true,结果就是 true
ES6 常用点
变量声明
ES6 中提供了两个声明变量的关键字:const 和 let
注意:
在ES5中var function 声明的全局变量都是顶级对象(window)的属性,而ES6中声明的全局变量不属于顶级对象的属性
var age = 18
console.log(this.age) // 18
let age = 18
console.log(this.age) // undefined
let 使用
ES6 新增了let
命令,用来声明变量。它的用法类似于var
。
- let 声明的变量只有在当前作用域有效
- 不存在变量提升
- 不允许重复声明
- 块级作用域
简单的理解就是 { } 一对花括号之间就是一个块级作用域,在这个块级作用域外是无法访问内部用 let 声明的变量
{
let a = 10
var b = 1
}
a // ReferenceError: a is not defined.
b // 1
// let 的情况
console.log(bar) // 报错ReferenceError
let bar = 2
let a = 10
let a = 1 //报错 Identifier 'a' has already been declared
for
循环的计数器,就很合适使用let
命令。
for (let i = 0; i < 10; i++) {
// ...
}
console.log(i)
// ReferenceError: i is not defined
上面代码中,计数器i
只在for
循环体内有效,在循环体外引用就会报错。
下面的代码如果使用var
,最后输出的是10
。
var a = []
for (var i = 0; i < 10; i++) {
a[i] = function() {
console.log(i)
}
}
a[6]() // 10
上面代码中,变量i
是var
命令声明的,在全局范围内都有效,所以全局只有一个变量i
。每一次循环,变量i
的值都会发生改变,而循环内被赋给数组a
的函数内部的console.log(i)
,里面的i
指向的就是全局的i
。也就是说,所有数组a
的成员里面的i
,指向的都是同一个i
,导致运行时输出的是最后一轮的i
的值,也就是 10。
如果使用let
,声明的变量仅在块级作用域内有效,最后输出的是 6。
var a = []
for (let i = 0; i < 10; i++) {
a[i] = function() {
console.log(i)
}
}
a[6]() // 6
上面代码中,变量i
是let
声明的,当前的i
只在本轮循环有效,所以每一次循环的i
其实都是一个新的变量,所以最后输出的是6
for
循环还有一个特别之处,就是设置循环变量的那部分是一个父作用域,而循环体内部是一个单独的子作用域。
for (let i = 0; i < 3; i++) {
let i = 'abc'
console.log(i)
}
// abc
// abc
// abc
上面代码正确运行,输出了 3 次abc
。这表明函数内部的变量i
与循环变量i
不在同一个作用域,有各自单独的作用域。
暂时性死区
只要块级作用域内存在let
命令,它所声明的变量就“绑定”(binding)这个区域,不再受外部的影响。
var tmp = 123
if (true) {
tmp = 'abc' // ReferenceError
let tmp
}
上面代码中,存在全局变量tmp
,但是块级作用域内let
又声明了一个局部变量tmp
,导致后者绑定这个块级作用域,所以在let
声明变量前,对tmp
赋值会报错。
ES6 明确规定,如果区块中存在let
和const
命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错。
总之,在代码块内,使用let
命令声明变量之前,该变量都是不可用的。这在语法上,称为“暂时性死区”(temporal dead zone,简称 TDZ)。
if (true) {
// TDZ开始
tmp = 'abc' // ReferenceError
console.log(tmp) // ReferenceError
let tmp // TDZ结束
console.log(tmp) // undefined
tmp = 123
console.log(tmp) // 123
}
上面代码中,在let
命令声明变量tmp
之前,都属于变量tmp
的“死区”。
const 使用
const
声明一个只读的常量。一旦声明,常量的值就不能改变。const
声明的变量不得改变值,这意味着,const
一旦声明变量,就必须立即初始化,不能留到以后赋值。
- 如果 const 声明了一个对象,仅仅保证地址不变
const obj = {name:'zs'};
obj.age = 18;//正确
obj = {};//报错
如果真的想将对象冻结,应该使用Object.freeze方法。
const foo = Object.freeze({});
// 常规模式时,下面一行不起作用;
// 严格模式时,该行会报错
foo.prop = 123;
- 其他用法和 let 一样
1. 只能在当前代码块中使用
2. 不会提升
3. 不能重复
let 与 const 的使用场景
1. 如果声明的变量不需要改变,那么使用const
2. 如果声明的变量需要改变,那么用let
3. 学了const和let之后,尽量别用var
解构赋值
数组解构
其实字符串也是可以解构赋值的,字符串被转换成了一个类似数组的对象,类似数组的对象都有一个
length
属性,因此还可以对这个属性解构赋值。
ES6 允许写成下面这样。
let [a, b, c] = [1, 2, 3];
// ...tail这个写法是ES6中函数优化增加的rest参数,表示剩余的所有参数,后面有说明
let [head, ...tail] = [1, 2, 3, 4];
head // 1
tail // [2, 3, 4]
对于 Set 结构,也可以使用数组的解构赋值。
let [x, y, z] = new Set(['a', 'b', 'c']);
x // "a"
解构默认值
let [a = 0, b, c] = [1, 2, 3]
对象解构
解构不仅可以用于数组,还可以用于对象。
let { foo, bar } = { foo: 'aaa', bar: 'bbb' }
foo // "aaa"
bar // "bbb"
如果变量名与属性名不一致,必须写成下面这样。
let { foo: baz } = { foo: 'aaa', bar: 'bbb' }
baz // "aaa"
let obj = { first: 'hello', last: 'world' }
let { first: f, last: l } = obj
f // 'hello'
l // 'world'
函数的参数也可以使用解构赋值。
function add([x, y]) {
return x + y
}
add([1, 2]) // 3
;[[1, 2], [3, 4]].map(([a, b]) => a + b)
// [ 3, 7 ]
let { foo: foo, bar: bar } = { foo: 'aaa', bar: 'bbb' }
也就是说,对象的解构赋值的内部机制,是先找到同名属性,然后再赋给对应的变量。真正被赋值的是后者,而不是前者。
let { foo: baz } = { foo: 'aaa', bar: 'bbb' }
baz // "aaa"
foo // error: foo is not defined
上面代码中,foo
是匹配的模式,baz
才是变量。真正被赋值的是变量baz
,而不是模式foo
。
默认值生效的条件是,对象的属性值严格等于undefined
。
let { x = 3 } = { x: undefined }
x // 3
let { x = 3 } = { x: null }
x // null
上面代码中,属性x
等于null
,因为null
与undefined
不严格相等,所以是个有效的赋值,导致默认值3
不会生效。
解构赋值的用途
具体用途可以看阮一峰的《ECMAScript 入门》一书
- 交换变量的值
- 从函数返回多个值
- 函数参数的定义
- 提取 JSON 数据
- 函数参数的默认值
- 遍历 Map 结构
- 输入模块的指定方法
字符串
模版字符串
传统的 JavaScript 语言,输出模板通常是这样写的(下面使用了 jQuery 的方法)。
$('#result').append(
'There are <b>' +
basket.count +
'</b> ' +
'items in your basket, ' +
'<em>' +
basket.onSale +
'</em> are on sale!'
)
上面这种写法相当繁琐不方便,ES6 引入了模板字符串解决这个问题。
$('#result').append(`
There are <b>${basket.count}</b> items
in your basket, <em>${basket.onSale}</em>
are on sale!
`)
字符串模版的优点
- 允许换行
- 可以使用插值
${}
字符串方法
- includes():返回布尔值,表示是否找到了参数字符串。
- startsWith():返回布尔值,表示参数字符串是否在原字符串的头部。
- endsWith():返回布尔值,表示参数字符串是否在原字符串的尾部。
数组
find
find 是 ES6 新增的语法
find()
方法返回数组中满足提供的测试函数的第一个元素的值。否则返回 undefined
。
// 获取第一个大于10的数
let array1 = [5, 12, 8, 130, 44]
let found = array1.find(function(element) {
return element > 10
})
console.log(found)
findexIndex
findIndex 是 ES6 新增的语法
findIndex()
方法返回数组中满足提供的测试函数的第一个元素的索引。否则返回-1。
// 获取第一个大于10的下标
let array1 = [5, 12, 8, 130, 44]
function findFirstLargeNumber(element) {
return element > 13
}
console.log(array1.findIndex(findFirstLargeNumber))
函数
ES6 标准新增了一种新的函数:Arrow Function(箭头函数)。
基本使用
let fn = function(x, y) {
console.log(x + y)
}
相当于
//语法: (参数列表) => {函数体}
let fn = (x, y) => {
console.log(x + y)
}
参数详解
- 如果没有参数列表,使用()表示参数列表
- 如果只有一个参数,可以省略()
- 如果有多个参数,需要使用()把参数列表括起来
// 没有参数
let sum = () => {
console.log('哈哈')
}
// 有一个参数
let sum = n1 => {
console.log('哈哈')
}
// 有多个参数
let sum = (n1, n2) => {
console.log('哈哈')
}
返回值详解
- 如果箭头函数的代码块部分多于一条语句,就要使用大括号将它们括起来
- 如果函数体只有一行一句,那么可以省略{}和return
var fn = (n1, n2) => n1 + n2;
箭头函数的注意点
- 箭头函数内部没有this,因此箭头函数内部的this指向了外部的this
- 箭头函数不能作为构造函数,因为箭头函数没有this
// 箭头函数内部没有this
const obj = {
name: 'zs',
age: 18,
// 以后碰到了this的指向问题
sayHi: function() {
// 1. let that = this
let that = this
setInterval(function() {
console.log(`大家好,我是${that.name},我今年${that.age}`)
}, 1000)
},
sayHi1: function() {
// 2. 使用bind(this)
setInterval(
function() {
console.log(`大家好,我是${this.name},我今年${this.age}`)
}.bind(this),
1000
)
},
sayHi2: function() {
setInterval(() => {
// 3. 直接使用箭头函数
console.log(`大家好,我是${this.name},我今年${this.age}`)
}, 1000)
}
}
rest参数
// rest参数 剩余参数
// arguments: 没有定义参数, 别人用的时候不知道传什么参数
// es6中新增了一个 rest参数, 剩余参数
// 剩余参数必须是最后一个参数
const add = function(num, ...colors) {
// console.log(arguments)
console.log(colors) // 2,3,4
// 如果没有num这个参数,那么输出的就是1,2,3,4
}
add(1, 2, 3, 4)
对象
属性的简写
ES6中,如果对象的属性名 和 属性值的变量相同, 可以省略一个
let page = 1
let pageSize = 5
data:{
page:page,
pageSize:pageSize
}
// 简写成
data:{
page,
pageSize
}
方法的简写
对象中的方法 可以省略 :function
const o = {
method: function() {
return "Hello!";
}
};
// 简写成
const o = {
method() {
return "Hello!";
}
};
展开运算符( … )
- 展开一个数组
const arr = [1, 2, 3]
console.log(...arr)
const arr = [1, 2, 3]
const arr2 = [4, 5, 6]
const newArr = [...arr, ...arr2, 5, 6, 7, ...arr]
console.log(newArr)
const arr = [1, 2, 3, 5, 4, 7, 6]
const result = Math.max.call(null, ...arr)
console.log(result)
const result = Math.max(...arr)
console.log(result)
- 展开一个对象
const obj = {
name: 'zs',
age: 18,
gender: '男'
}
const obj1 = {
money: 100,
house: '房子'
}
const obj2 = {
...obj,
...obj1,
sayHi() {
console.log('哈哈')
}
}
console.log(obj2)
Set 对象
// Set类型和数组非常的像, set中的数据不允许重复,Set集合的意思,集合中不允许有重复
let arr = [1, 2, 3, 4, 5, 4, 3, 2, 1]
arr = [...new Set(arr)]
console.log(arr)
// 接受一个数组,但是不会有重复的
const s = new Set(arr)
console.log([...s])
伪数组转为数组
let inputs = document.querySelectorAll('ul input') // DOM对象的伪数组
// 方法一 借调slice方法
let arr = [].slice.call(inputs,0) // 转换成了一个真数组
// 方法二 展开运算符
let arr = [...inputs] // 转换成了真数组
Promise对象
Promise 是异步编程的一种解决方案,所谓Promise
,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息
Promise
对象有以下两个特点。
-
对象的状态不受外界影响。
Promise
对象代表一个异步操作,有三种状态:pending
(进行中)、fulfilled
(已成功)和rejected
(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态 -
一旦状态改变,就不会再变,任何时候都可以得到这个结果。
Promise
对象的状态改变,只有两种可能:从pending
变为fulfilled
和从pending
变为rejected
。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved(已定型)。
有了Promise
对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数
注1: Promise任然是一个异步操作,
const p = Promise.resolve('Hello');
p.then(function (s) {
console.log(s)
});
console.log('1111')
// 输出顺序
// 1111
// Hello
注2:Promise 对象,是在本轮“事件循环”(event loop)的结束时执行,而不是在下一轮“事件循环”的开始时
setTimeout(function () {
console.log('three');
}, 0);
Promise.resolve().then(function () {
console.log('two');
});
console.log('one');
// setTimeout(fn, 0)在下一轮“事件循环”开始时执行,Promise.resolve()在本轮“事件循环”结束时执行,console.log('one')则是立即执行,因此最先输出。
// one
// two
// three
Promise的基本使用
基本语法
/*
参数是两个函数
调用resolve函数来将promise状态改成fulfilled,
调用reject 函数将promise的状态改为rejected。
如果在executor函数中抛出一个错误,那么该promise 状态为rejected
*/
const promise = new Promise((resolve, reject) => {
/* 常常是一些异步的操作 */
if(/*成功*/){
resolve('success')
}else{
reject('error')
}
})
// 这时候异步操作的结果就会存在promise这实例对象中
/*
Promise 对象的 then 方法绑定的处理方法(handlers )就会被调用
(then方法包含两个参数:onfulfilled 和 onrejected,它们都是 Function 类型。 当Promise状态为fulfilled时,调用 then 的 onfulfilled 方法,
当Promise状态为rejected时,调用 then 的 onrejected 方法)
*/
/*
第一个回调函数是Promise对象的状态变为resolved时调用,
第二个回调函数是Promise对象的状态变为rejected时调用。
第二个函数是可选的,不一定要提供。这两个函数都接受Promise对象传出的值作为参数。
*/
promise.then(function(result){
// 执行fulfilled状态的
},function(err){
// 执行rejected状态的
})
调用resolve
函数和reject
函数时带有参数,那么它们的参数会被传递给回调函数。reject
函数的参数通常是Error
对象的实例,表示抛出的错误;resolve
函数的参数除了正常的值以外,还可能是另一个 Promise 实例,比如像下面这样
const p1 = new Promise(function (resolve, reject) {
setTimeout(() => reject(new Error('fail')), 3000)
})
const p2 = new Promise(function (resolve, reject) {
setTimeout(() => resolve(p1), 1000)
})
p2
.then(result => console.log(result))
.catch(error => console.log(error))
// Error: fail
p1
和p2
都是 Promise 的实例,但是p2
的resolve
方法将p1
作为参数,即一个异步操作的结果是返回另一个异步操作。
注意,这时p1
的状态就会传递给p2
,也就是说,p1
的状态决定了p2
的状态。如果p1
的状态是pending
,那么p2
的回调函数就会等待p1
的状态改变;如果p1
的状态已经是resolved
或者rejected
,那么p2
的回调函数将会立刻执行。
采用链式的
then
可以指定一组按照次序调用的回调函数。这时,前一个回调函数,有可能返回的还是一个Promise
对象(即有异步操作),这时后一个回调函数,就会等待该Promise
对象的状态发生变化,才会被调用。
这是一个读写文件的链式then,读取数据后,把读取的数据在写入一个新文件中
const fs = require('fs')
const path = require('path')
const filepath = path.join(__dirname, '1.txt')
const filepath1 = path.join(__dirname, '2.txt')
function writeData(data) {
const p1 = new Promise((resolve, reject) => {
fs.writeFile(filepath1, data, err => {
if (err) return reject(err)
return resolve('success')
})
})
return p1
}
function readData() {
const p = new Promise((resolve, reject) => {
fs.readFile(filepath, 'utf8', (err, data) => {
if (err) return reject(err)
return resolve(data)
})
})
return p
}
readData().then(data => writeData(data))
.then(res => console.log(res))
.catch(err => console.log(err))
Promise.prototype.catch
方法是.then(null, rejection)
或.then(undefined, rejection)
的别名,用于指定发生错误时的回调函数。
// 改写上面的promise
promise.then(function(result){
// 执行fulfilled状态的
},function(err){
// 执行rejected状态的
})
// 改写成
promise.then(result => { })// 执行fulfilled状态的
.catch(err =>{})// 执行rejected状态的
Promise 对象的错误具有“冒泡”性质,会一直向后传递,直到被捕获为止。也就是说,错误总是会被下一个catch
语句捕获。
Promise.prototype.finally() finally
方法用于指定不管 Promise 对象最后状态如何,都会执行的操作
再次改写
promise
.then(result => {···})
.catch(error => {···})
.finally(() => {···});
Promise.try() 实际开发中,经常遇到一种情况:不知道或者不想区分,函数f
是同步函数还是异步操作,但是想用 Promise 来处理它。因为这样就可以不管f
是否包含异步操作,都用then
方法指定下一步流程,用catch
方法处理f
抛出的错误
注: try 不是实例上的方法
Promise.try(readData())
.then(result => {···})
.catch(error => {···})
.finally(() => {···});
Promise.all
方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。
const p = Promise.all([p1, p2, p3]);
上面代码中,Promise.all
方法接受一个数组作为参数,p1
、p2
、p3
都是 Promise 实例,如果不是,就会先调用下面讲到的Promise.resolve
方法,将参数转为 Promise 实例,再进一步处理。(Promise.all
方法的参数可以不是数组,但必须具有 Iterator 接口,且返回的每个成员都是 Promise 实例。)
p
的状态由p1
、p2
、p3
决定,分成两种情况。
-
只有
p1
、p2
、p3
的状态都变成fulfilled
,p
的状态才会变成fulfilled
,此时p1
、p2
、p3
的返回值组成一个数组,传递给p
的回调函数。 -
只要
p1
、p2
、p3
之中有一个被rejected
,p
的状态就变成rejected
,此时第一个被reject
的实例的返回值,会传递给p
的回调函数。
Promise.race
方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。
const p = Promise.race([p1, p2, p3]);
上面代码中,只要p1
、p2
、p3
之中有一个实例率先改变状态,p
的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p
的回调函数。