1.let和const关键字
let:
- 变量不能重复声明。
let a = 1;
let a = 2;
//报错:Uncaught SyntaxError: Identifier 'a' has already been declared
- 块级作用域。在if-else语句、while循环、for循环中使用了let声明的变量,都会产生块级作用域。
let flag = true
if(flag){
var a = 11
}
console.log(a) //11
//而把var换成let后就报错
let flag = true
if(flag){
let a = 11
}
console.log(a) //Uncaught ReferenceError: a is not defined
- 没有变量提升,必须先定义才能使用。
变量提升:在代码执行前,会先收集通过var声明的变量,把这些变量提到作用域的最顶端,而赋值操作不变。 - let声明的变量是独立的,不能被window调用。
let a = 10
console.log(window.a) //undefined
var b = 10
console.log(window.b) //10
let doSth = function(){
console.log('123');
}
window.doSth()//ncaught TypeError: window.doSth is not a function
var doSth = function(){
console.log('123');
}
window.doSth()//123
- 不会影响作用域链
{
let a = 10
function fun(){
console.log(a)//10
}
fun()
}
1
2
3
const:
- 初始化常量,必须给初始值,否则报错(Uncaught SyntaxError: Missing initializer in const declaration)。
- 在同一个作用域内,const定义的常量不能修改其值。在同一个作用域内,const定义的常量不能重复声明。但是在不同的作用域,用const声明一样的变量没问题。另外,如果const声明的变量是个引用数据类型,比如对象,那么对对象进行修改的时候,不会报错。
const a = 10
a = 100 // Uncaught TypeError: Assignment to constant variable.
//const定义的常量不能重复声明
const a = 10
const a = 100//Uncaught SyntaxError: Identifier 'a' has already been declared
但是在不同的作用域,用const声明一样的常量没问题。
const a = 100
function fn(){
const a = 200
console.log(a)//200
}
fn()
如果const声明的变量是个引用数据类型,比如对象,那么对对象进行修改的时候,不会报错
const obj = {
name:'yy'
}
obj.name = 'gg'
console.log(obj)
- 没有变量提升。
- 具有块级作用域。
- const声明的常量也是独立的,不能被window调用
const a = 10
console.log(window.a)
- const声明的数组和对象,对其修改不会报错
const arr = [1,2,3,4]
arr.push(5)
console.log(arr)// [1,2,3,4,5]
var/let/const的共同点:在函数内部都可以访问到在外部通过var/let/const声明的变量或常量。
2.解构赋值
//对象的解构赋值
const obj = {
name:'yy',
age:18,
sayHello:function(){
console.log('你好')
}
}
let {name,age,sayHello} = obj
sayHello() //'你好'
//还有数组的解构赋值,用的少就不细说了
3.箭头函数
//声明函数
let add1 = (a) =>{
console.log(a*2)
}
//调用函数
add1(3)//6
//箭头函数与普通函数的区别
//1.this是静态的,this始终指向函数声明时所属的那个作用域。普通函数中的this与调用它的对象有关。
window.name = '杨洋'
let getName = () =>{
console.log(this.name)
}
getName() //杨洋
const obj = {
name:'李易峰'
}
//通过call方法看能不能改变this的指向?不能
getName.call(obj)//杨洋
//2.箭头函数不能作为构造函数去使用
let Person = (name,age) =>{
this.name = name
this.age = age
}
let person = new Person('杨洋',18)
console.log(person)//Uncaught TypeError: Person is not a constructor
//3.不能使用arguments
let person = (name,age) =>{
console.log(arguments) //Uncaught ReferenceError: arguments is not defined
}
person('杨洋',18)
//4.可以简写
let person = name =>{ //只有一个参数的时候可以不用写括号
console.log(name)//杨洋
}
person('杨洋')
//代码体只有一行的时候省去花括号
let person = name => console.log(name) //杨洋
person('杨洋')
4.模板字符串:使用反引号``代替双引号创建字符串
const name='小刘',age='18'
console.log(`我是新来的同学${name},今年${age}岁。`)
let num = Math.round('12.339')
console.log(`${num}`)
模板字符串可以再嵌套模板字符串
const arr = [
{title:'响应式布局'},
{title:'Vue'},
{title:'React'},
]
function template(){ //模板字符串可以再嵌套模板字符串
return `
${arr.map(item=>`- ${item.title}
`).join('')}
`
}
document.body.innerHTML = template()
5.标签模板
let name = '杨洋'
let age = 18
console.log(tag`我是${name},今年${age}岁。`)//如果在模板字符串前面加个标签
function tag(strings,...args){ //strings:数组,数组元素是模板字符串中${}前后的元素截取出来的 args:${}内的变量组成的数组
// console.log(strings)
console.log(args)
}
//有个细节,strings的数组长度大于变量的个数
//标签模板实例
const arr = [
{title:'bootstrap框架',author:'杨老师'},
{title:'Vue框架',author:'李老师'},
{title:'React框架',author:'盛老师'},
]
function template(){
return `
${arr.map(item=>{
return links`- 作者:${item.author},课程:${item.title}
`
}).join('')}
`
}
function links(strings,...args){
return strings.map((str,key)=>{
return str + (args[key]?args[key].replace('框架',"框架"):'')
}).join('')
}
document.body.innerHTML = template()
5.扩展运算符
//扩展运算符能将数组转为逗号分割的参数列表:[1,2,3] => 1,2,3
//扩展运算符的使用:
//数组克隆
const fruits = ['苹果','梨','橘子']
const newFruits = [...fruits]
console.log(newFruits)//['苹果','梨','橘子']
const num = [{a:1},{b:2},{c:3}]
const newNum = [...num]// 扩展运算符拷贝数组,如果数组中有引用类型的数据,则是浅拷贝。如果改变克隆数组某元素的值,原数组的值也会改变,要注意
console.log(newNum)//[{a:1},{b:2},{c:3}]
const newNum1 = JSON.parse(JSON.stringify(newNum))//深拷贝一份
newNum1[0].a = 11
console.log(num)//原数组就不会被改变了
//数组合并
const flowers1 = ['月季','玫瑰']
const flowers2 = ['牡丹','菊花']
const newFlowers = [...flowers1,...flowers2]
console.log(newFlowers)
//将伪数组转为真正的数组
let items = document.querySelectorAll('.item')
let newItems = [...items]
console.log(newItems)//转成真正的数组后就能使用forEach()
6.新增了一些字符串和数组方法
//4.1 新增的字符串方法
const str = '我 们都有一个家'
console.log(str.includes('一')) //true
console.log(str.startsWith('我们')) //false,判断某个字符串是否在原字符串的头部
console.log(str.endsWith('家')) //true,判断某个字符串是否在原字符串的尾部
//4.2 新增的数组方法
const arr = [4,9,16,25]
//4.2.1 includes方法(es7新增的方法)
console.log(arr.includes(9)) //true
//注意,数组元素是引用类型的时候,使用includes()无法查找。
const arr = [{name:'yy'},{name:'uu'},{name:'jj'}]
console.log(arr.includes({name:'uu'}))//false
//4.2.2 map()方法:得到一个经过处理后的新数组
let newArr = []
arr.map(item=>{
newArr.push(Math.sqrt(item))
})
console.log(newArr)//[2,3,4,5]
//4.2.3 filter()方法:过滤操作,得到想要的结果
let newArr1 = arr.filter(item=>{
return item > 10
})
console.log(newArr1)
//4.2.4 reduce(prev,current,index,arr)
let total = arr.reduce((prev,current)=>{ //求和
return prev + current
})
console.log(total)
const arr1 = [[1,2],[3],[4,6]]
let newArr2 = arr1.reduce((prev,current)=>{ //二维数组变成一维数组(数组扁平化)
return prev.concat(current)
})
console.log(newArr2)
const arr2 = [1,2,[3,4,[5,6]],7,8]
function flatten(arr){
let newArr = arr.reduce((pre,cur)=>{ //多维数组变为一维数组(数组扁平化)
return pre.concat(Array.isArray(cur)?flatten(cur):cur)
},[])
return newArr
}
console.log(flatten(arr2))
//需求:把“我正在学习php和css”中的php和css替换成超链接
const arr = ['php','css']
const str = '我正在学习php和css'
let replaceStr = arr.reduce((pre,cur)=>{
return pre.replace(cur,`${cur}`)
},str)
document.body.innerHTML = replaceStr
//4.2.5 some(callback):检查数组中的每个元素是否符合条件,只要有一个满足条件就返回true,都不满足返回false
const ageArr = [16,17,18,19,20]
console.log(ageArr.some(item=>item>17))
//4.2.6 Array.from(new Set(arr)):数组去重
const arr3 = [1,1,2,3,4,5,5,5,6]
console.log(Array.from(new Set(arr3))) //[1,2,3,4,5,6]
//数组去重的其它方法:利用reduce()或者Object.keys(obj)
let arr31 = arr3.reduce((pre,cur)=>{
if(!pre.includes(cur)){
return pre.concat(cur)
}else{
return pre
}
},[])
console.log(arr31)//[1,2,3,4,5,6]
let obj = {}
arr3.forEach(item=>{
obj[item] = 1
})
let newArr32 = Object.keys(obj).map(item=>{
return Number(item)
})
console.log(newArr32)//[1,2,3,4,5,6]
//4.2.7 find()和findIndex()
const arr = [{name:'yy'},{name:'uu'},{name:'jj'}]
let res = arr.find(item=>{ //查找数组中符合回调函数要求的第一个数组元素。意思就是:如果有多个元素元素都符合此要求,只返回符合的第一个
return item.name === 'uu'
})
console.log(res)
let index = arr.findIndex(item=>{ //查找数组中的指定元素的索引值
return item.name === 'uu'
})
console.log(index)//1
const Arr=[1,2,3,4];
var num = Arr.find(function(value){
return value > 2
})
console.log(num)
const Arr1 = [1,2,3,4];
var num1 = Arr1.findIndex(function(value){
return value > 2
})
console.log(num1)//2
rest参数:...args,es6中引入这个参数获取多余的实参,代替es5中的arguments。
function sum(){
console.log(arguments)
}
sum(1,2,3)//结果不是个数组
function sum1(...args){
console.log(args)
}
sum1(1,2,3)//[1,2,3]
function multiple(a,...args){
return args.map(item=>{
return a * item
})
}
let result = multiple(3,4,5,6,7)
console.log(result)//[12,15,18,21]
//rest参数必须放到最后
function sum(a,b,...args){
console.log(a,b,args)
}
sum(1,2,3,4,5,6,7)//1 2 [3, 4, 5, 6, 7]
7.Symbol
Symbol是es6引入的新的原始数据类型,是js的第七种数据类型,是类似于字符串的数据类型。
在对象中,凡是属性名属于Symbol类型的,这个属性就是独一无二的,在对象中就不会产生命名冲突的问题。
Symbol不能用于和其它数据类型运算。
Symbol定义的对象不能用for...in获取对象属性,但是可以使用Reflect.ownKeys获取对象的所有属性名。
//创建Symbol
let s = Symbol()
console.log(typeof s)//symbol
//创建Symbol时,可以给Symbol函数传递参数,表示对Symbol实例的描述,容易区分不同的Symbol值。
let s1 = Symbol('哈哈')
let s2 = Symbol('哈哈')
console.log(s1)//Symbol(哈哈)
//注意:Symbol函数的参数只是对当前Symbol值的描述,两个参数一致的Symbol函数,它们的返回值是不相等的
console.log(s1 === s2)//false
//Symbol函数的参数是个对象,会自动调用对象的toString()方法
const obj = {
toString() {
return 222
}
};
let sym = Symbol(obj);
console.log(sym) //Symbol(222)
var fruits = {
type1:'苹果',
type2:'橘子',
}
//我想把otherFruits对象中的两个属性添加到fruits对象中去,但是我不确定fruits对象是否已经有了这两个属性
//所以,我把otherFruits对象中的两个属性的属性值设置成Symbol对象,即独一无二的,这样可以避免把fruits对象中的属性给覆盖掉
let mySymbol1 = Symbol()
let mySymbol2 = Symbol()
let otherFruits = {
type1:mySymbol1,
type2:mySymbol2
}
fruits[otherFruits.type1] = '哈密瓜'
fruits[otherFruits.type2] = '红柚'
//但是这添加的Symbol属性,怎么输出呢?
console.log(fruits[mySymbol1],fruits[mySymbol2])//哈密瓜 红柚
//Symbol.for创建
let s3 = Symbol.for('哈哈')
console.log(typeof s3)//symbol
//Symbol的内置属性
8.迭代器(Iterator)
迭代器:一种数据接口,为各种不同的数据结构提供统一的访问机制,任何数据结构只要部署Iterator接口,就能实现遍历操作。
es6创建了一种新的遍历方式for...of循环,迭代器使用for...of循环就能进行遍历操作。
原生具备迭代器接口的数据结构有Array、Arguments、Set、Map、String、TypedArray、NodeList
迭代器工作原理:
(1)创建一个指针对象,指向当前数据结构的起始位置。
这个对象是由Symbol(Symbol.iterator)这个函数创建
(2)第一次调用对象的next方法,指针自动指向第一个数据成员
(3)接下来不断调用next方法,指针一直往后移动,直至到最后一个成员
(4)每调用一次next方法,都会返回一个包含value和done的对象
const arr = [1,2,3,4]
//创建指针对象
let iterator = arr[Symbol.iterator]()
//调用对象的next方法
console.log(iterator.next())
console.log(iterator.next())
console.log(iterator.next())
console.log(iterator.next())
console.log(iterator.next())
迭代器的应用:使用for...of按照自己的意愿去遍历对象,依次输出hobbies的值。
思考:
当使用for...of循环遍历某种数据结构时,该循环会自动去寻找 Iterator 接口。所以,某种数据结构身上有迭代器接口,就能使用for...of
那某种数据结构没有迭代器接口,怎么办?比如,对象,对象本身是没有迭代器接口的(Uncaught TypeError: obj is not iterable),所以给对象部署个迭代器接口。
怎么部署呢?
es6默认迭代器接口部署在数据结构的Symbol.iterator属性上,只要有了这个属性,就可以进行遍历。
Symbol.iterator属性本身是一个函数,执行这个函数会返回一个遍历器。
const obj = {
name:'杨洋',
hobbies:[
'打篮球',
'踢足球',
'打网球',
'打羽毛球'
],
[Symbol.iterator](){ //部署迭代器接口
let index = 0
let _this = this
return {
next:function(){ //index++
return index < _this.hobbies.length?{value:_this.hobbies[index++],done:false}:{value:undefined,done:true}
}
}
}
}
for(let value of obj){
console.log(value)//打篮球 踢足球 打网球 打羽毛球
}
9.生成器
生成器就是一个特殊的函数,异步编程一种新的解决方案。
执行生成器函数会返回一个遍历器对象(代表生成器函数内部指针),返回的遍历器对象,可以对生成器函数内部的状态进行遍历。
生成器函数内部使用yield表达式,定义了不同的内部状态。
function * gen(){
console.log('生成器')
}
let iterator = gen()
iterator.next()
function * gen1(){ //yield:暂停标志
yield '哈哈哈'
yield '呵呵呵'
yield '嘿嘿嘿'
}
//for...of循环可以自动遍历生成器函数生成的遍历器对象
//for(let value of gen1()){
// console.log(value)
//}
let iterator1 = gen1()
console.log(iterator1.next())//{value: "哈哈哈", done: false}
console.log(iterator1.next())//{value: "呵呵呵", done: false}
console.log(iterator1.next())//{value: "嘿嘿嘿", done: false}
//上面代码定义了一个生成器函数,执行函数返回遍历器对象,然后调用了三次遍历器对象的next()方法。
//第一次调用next()方法,开始遍历生成器函数,遇到yield表达式会停止。yield表达式的值会作为next()方法返回对象的value属性的值。
//所以,第一次调用next()方法的结果就是:{value: "哈哈哈", done: false}
//第二次调用next()方法,遍历器对象从上一次停下的地方开始,遇到下一个yield表达式后又停下。输出结果:{value: "呵呵呵", done: false}
//第三次调用next()方法,遍历器对象从上一次停下的地方开始,遇到下一个yield表达式后又停下。输出结果:{value: "嘿嘿嘿", done: false}
yield表达式:
假如生成器函数内部的yield后面的表达式是12+34,不会立即求值,而是在遍历器调用了next()方法,指针指向了这个语句时,才会求值。
next()方法带参数:next()的参数会作为上一个yield表达式的返回值。
function * sum(){
let one = yield 111
console.log(one)
let two = yield 222
console.log(two)
let three = yield 333
console.log(three)
}
let itt = sum()
console.log(itt.next())
console.log(itt.next('a'))
console.log(itt.next('b'))
console.log(itt.next('c'))
//生成器函数的应用
//模拟数据获取——>先获取用户数据,再获取订单数据,最后获取商品数据
function getUsers(){
setTimeout(()=>{
let data = '用户数据'
it.next(data)
},1000)
}
function getOrders(){
setTimeout(()=>{
let data = '订单数据'
it.next(data)
},1000)
}
function getGoods(){
setTimeout(()=>{
let data = '商品数据'
it.next(data)
},1000)
}
function * gen1(){
let users = yield getUsers()
console.log(users)
let orders = yield getOrders()
console.log(orders)
let goods = yield getGoods()
console.log(goods)
}
let it = gen1()
it.next()