学习ES6笔记──工作中常用到的ES6语法
一、let和const
在JavaScript中咱们以前主要用关键var来定义变量,ES6之后,新增了定义变量的两个关键字,分别是let和const。
对于变量来说,在ES5中var定义的变量会提升到作用域中所有的函数与语句前面,而ES6中let定义的变量则不会,let声明的变量会在其相应的代码块中建立一个暂时性死区,直至变量被声明。
let和const都能够声明块级作用域,用法和var是类似的,let的特点是不会变量提升,而是被锁在当前块中。
一个非常简单的例子:
function test() { if(true) { console.log(a)//TDZ,俗称临时死区,用来描述变量不提升的现象 let a = 1 } } test() // a is not defined function test() { if(true) { let a = 1 } console.log(a) } test() // a is not defined
唯一正确的使用方法:先声明,再访问。
function test() { if(true) { let a = 1 console.log(a) } } test() // 1
const
声明常量,一旦声明,不可更改,而且常量必须初始化赋值。
const虽然是常量,不允许修改默认赋值,但如果定义的是对象Object,那么可以修改对象内部的属性值。
const type = { a: 1 } type.a = 2 //没有直接修改type的值,而是修改type.a的属性值,这是允许的。 console.log(type) // {a: 2}
const和let的异同点
相同点:const和let都是在当前块内有效,执行到块外会被销毁,也不存在变量提升(TDZ),不能重复声明。
不同点:const不能再赋值,let声明的变量可以重复赋值。
const实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址所保存的数据不得改动。对于简单类型的数据(数值、字符串、布尔值),值就保存在变量指向的那个内存地址,因此等同于常量。但对于复合类型的数据(主要是对象和数组),变量指向的内存地址,保存的只是一个指向实际数据的指针,const只能保证这个指针是固定的(即总是指向另一个固定的地址),至于它指向的数据结构是不是可变的,就完全不能控制了。因此,将一个对象声明为常量必须非常小心。
块级作用域的使用场景
除了上面提到的常用声明方式,我们还可以在循环中使用,最出名的一道面试题:循环中定时器闭包的考题
在for循环中使用var声明的循环变量,会跳出循环体污染当前的函数。
for(var i = 0; i < 5; i++) { setTimeout(() => { console.log(i) //5, 5, 5, 5, 5 }, 0) } console.log(i) //5 i跳出循环体污染外部函数 //将var改成let之后 for(let i = 0; i < 5; i++) { setTimeout(() => { console.log(i) // 0,1,2,3,4 }, 0) } console.log(i)//i is not defined i无法污染外部函数
在实际开发中,我们选择使用var、let还是const,取决于我们的变量是不是需要更新,通常我们希望变量保证不被恶意修改,而使用大量的const。使用const声明,声明一个对象的时候,也推荐使用const,当你需要修改声明的变量值时,使用let,var能用的场景都可以使用let替代。
symbol
ES6 以前,我们知道5种基本数据类型分别是Undefined,Null,Boolean,Number以及String,然后加上一种引用类型Object构成了JavaScript中所有的数据类型,但是ES6出来之后,新增了一种数据类型,名叫symbol,像它的名字表露的一样,意味着独一无二,意思是每个 Symbol类型都是独一无二的,不与其它 Symbol 重复。
可以通过调用 Symbol() 方法将创建一个新的 Symbol 类型的值,这个值独一无二,不与任何值相等。
var mySymbol=Symbol(); console.log(typeof mySymbol) //"symbol"
二、字符串
ES6字符串新增的方法
UTF-16码位:ES6强制使用UTF-16字符串编码。关于UTF-16的解释请自行百度了解。
codePointAt():该方法支持UTF-16,接受编码单元的位置而非字符串位置作为参数,返回与字符串中给定位置对应的码位,即一个整数值。
String.fromCodePoiont():
作用与codePointAt相反,检索字符串中某个字符的码位,也可以根据指定的码位生成一个字符。
normalize():提供Unicode的标准形式,接受一个可选的字符串参数,指明应用某种Unicode标准形式。
在ES6中,新增了3个新方法。每个方法都接收2个参数,需要检测的子字符串,以及开始匹配的索引位置。
模板字符串
字符串是JavaScript中基本类型之一,应该算是除了对象之外是使用最为频繁的类型吧,字符串中包含了例如substr,replace,indexOf,slice等等诸多方法,ES6引入了模板字符串的特性,用反引号来表示,可以表示多行字符串以及做到文本插值(利用模板占位符)。
// 以前的多行字符串我们这么写: console.log("hello world 1\n\ hello cala"); // "hello world // hello cala" //有了模板字符串之后 console.log(`hello world string text line 2`); // "hello world // hello cala"
可以用${}来表示模板占位符,可以将你已经定义好的变量传进括弧中,例如:
var name="cala"; var age=22; console.log(`hello,I'am ${name},my age is ${age}`) //hello,I'am cala,my age is 22
ncludes(str, index):如果在字符串中检测到指定文本,返回true,否则false。
let t = 'abcdefg' if(t.includes('cde')) { console.log(2) } //true
startsWith(str, index):如果在字符串起始部分检测到指定文本,返回true,否则返回false。
let t = 'abcdefg' if(t.startsWith('ab')) { console.log(2) } //true
endsWith(str, index):如果在字符串的结束部分检测到指定文本,返回true,否则返回false。
let t = 'abcdefg' if(t.endsWith('fg')) { console.log(2) } //true
如果你只是需要匹配字符串中是否包含某子字符串,那么推荐使用新增的方法,如果需要找到匹配字符串的位置,使用indexOf()。
三、函数
函数的默认参数
在ES5中,我们给函数传参数,然后在函数体内设置默认值,如下面这种方式。
function a(num, callback) { num = num || 6 callback = callback || function (data) {console.log('ES5: ', data)} callback(num * num) } a() //ES5: 36,不传参输出默认值 //你还可以这样使用callback a(10, function(data) { console.log(data * 10) // 1000, 传参输出新数值 })
在ES6中,我们使用新的默认值写法
function a(num = 6, callback = function (data) {console.log('ES6: ', data)}) { callback(num * num) } a() //ES6: 36, 不传参输出默认值 a(10, function(data) { console.log(data * 10) // 1000,传参输出新数值 })
四、箭头函数(=>)
const arr = [5, 10] const s = arr.reduce((sum, item) => sum + item) console.log(s) // 15
箭头函数中this的使用跟普通函数也不一样,在JavaScript的普通函数中,都会有一个自己的this值,主要分为:
普通函数:
1、函数作为全局函数被调用时,this指向全局对象
2、函数作为对象中的方法被调用时,this指向该对象
3、函数作为构造函数的时候,this指向构造函数new出来的新对象
4、还可以通过call,apply,bind改变this的指向
箭头函数:
1、箭头函数没有this,函数内部的this来自于父级最近的非箭头函数,并且不能改变this的指向。
2、箭头函数没有super
3、箭头函数没有arguments
4、箭头函数没有new.target绑定。
5、不能使用new
6、没有原型
7、不支持重复的命名参数。
箭头函数的简单理解
1、箭头函数的左边表示输入的参数,右边表示输出的结果。
const s = a => a console.log(s(2)) // 2
2、在箭头函数中,this属于词法作用域,直接由上下文确定,对于普通函数中指向不定的this,箭头函数中处理this无疑更加简单,如下:
//ES5普通函数 function Man(){ this.age=22; return function(){ this.age+1; } } var cala=new Man(); console.log(cala())//undefined //ES6箭头函数 function Man(){ this.age=22; return () => this.age+1; } var cala=new Man(); console.log(cala())//23
3、箭头函数中没有arguments(我们可以用rest参数替代),也没有原型,也不能使用new 关键字,例如:
//没有arguments var foo=(a,b)=>{return arguments[0]*arguments[1]} console.log(foo(3,5)) //arguments is not defined //没有原型 var Obj = () => {}; console.log(Obj.prototype); // undefined //不能使用new 关键字 var Obj = () => {"hello world"}; var o = new Obj(); // TypeError: Obj is not a constructor
4、箭头函数给数组排序
const arr = [10, 50, 30, 40, 20] const s = arr.sort((a, b) => a - b) console.log(s) // [10,20,30,40,50]
尾调用优化
尾调用是指在函数return的时候调用一个新的函数,由于尾调用的实现需要存储到内存中,在一个循环体中,如果存在函数的尾调用,你的内存可能爆满或溢出。
ES6中,引擎会帮你做好尾调用的优化工作,你不需要自己优化,但需要满足下面3个要求:
1、函数不是闭包
2、尾调用是函数最后一条语句
3、尾调用结果作为函数返回
尾调用实际用途——递归函数优化
在ES5时代,我们不推荐使用递归,因为递归会影响性能。
但是有了尾调用优化之后,递归函数的性能有了提升。
//新型尾优化写法 "use strict"; function a(n, p = 1) { if(n <= 1) { return 1 * p } let s = n * p return a(n - 1, s) } //求 1 x 2 x 3的阶乘 let sum = a(3) console.log(sum) // 6
七、迭代器(Iterator)
1、entries() 返回迭代器:返回键值对
//数组 const arr = ['a', 'b', 'c']; for(let v of arr.entries()) { console.log(v) } // [0, 'a'] [1, 'b'] [2, 'c'] //Set const arr = new Set(['a', 'b', 'c']); for(let v of arr.entries()) { console.log(v) } // ['a', 'a'] ['b', 'b'] ['c', 'c'] //Map const arr = new Map(); arr.set('a', 'a'); arr.set('b', 'b'); for(let v of arr.entries()) { console.log(v) } // ['a', 'a'] ['b', 'b']
2、values() 返回迭代器:返回键值对的value
//数组 const arr = ['a', 'b', 'c']; for(let v of arr.values()) { console.log(v) } //'a' 'b' 'c' //Set const arr = new Set(['a', 'b', 'c']); for(let v of arr.values()) { console.log(v) } // 'a' 'b' 'c' //Map const arr = new Map(); arr.set('a', 'a'); arr.set('b', 'b'); for(let v of arr.values()) { console.log(v) } // 'a' 'b'
3、keys() 返回迭代器:返回键值对的key
//数组 const arr = ['a', 'b', 'c']; for(let v of arr.keys()) { console.log(v) } // 0 1 2 //Set const arr = new Set(['a', 'b', 'c']); for(let v of arr.keys()) { console.log(v) } // 'a' 'b' 'c' //Map const arr = new Map(); arr.set('a', 'a'); arr.set('b', 'b'); for(let v of arr.keys()) { console.log(v) } // 'a' 'b'
ES6给数组添加了几个新方法:find()、findIndex()、fill()、copyWithin()
1、find():传入一个回调函数,找到数组中符合当前搜索规则的第一个元素,返回它,并且终止搜索。
onst arr = [1, "2", 3, 3, "2"] console.log(arr.find(n => typeof n === "number")) // 1
2、findIndex():传入一个回调函数,找到数组中符合当前搜索规则的第一个元素,返回它的下标,终止搜索。
const arr = [1, "2", 3, 3, "2"] console.log(arr.findIndex(n => typeof n === "number")) // 0
3、fill():用新元素替换掉数组内的元素,可以指定替换下标范围。
arr.fill(value, start, end)
4、copyWithin():选择数组的某个下标,从该位置开始复制数组元素,默认从0开始复制。也可以指定要复制的元素范围。
arr.copyWithin(target, start, end) const arr = [1, 2, 3, 4, 5] console.log(arr.copyWithin(3)) // [1,2,3,1,2] 从下标为3的元素开始,复制数组,所以4, 5被替换成1, 2 const arr1 = [1, 2, 3, 4, 5] console.log(arr1.copyWithin(3, 1)) // [1,2,3,2,3] 从下标为3的元素开始,复制数组,指定复制的第一个元素下标为1,所以4, 5被替换成2, 3 const arr2 = [1, 2, 3, 4, 5] console.log(arr2.copyWithin(3, 1, 2)) // [1,2,3,2,5] 从下标为3的元素开始,复制数组,指定复制的第一个元素下标为1,结束位置为2,所以4被替换成2