潮流虽然太快,但我们不停下学习的步伐,就不会被潮流丢下的,下面来领略下ES6中新特性,一睹新生代JS的风采。
1,let和const
- let和const都是块级作用域,说白了只要是在{}里面的代码块就是let和const的作用域。下面我们分别了解一下它们。
- let
- let 的作用域是在它所在当前代码块,但不会被提升到当前函数的最顶部。如下:
function f(){
console.log(test)
let test = "Hello,World"
}
f()
//这时会报错test is not defined
复制代码
- const
- const 声明的变量必须提供一个值,而且会被认为是常量,意思就是它的值被设置完成后就不能再修改了。如下:
const name = "zhangsan"
name = "lisi"
//再次赋值会报错
复制代码
- 如果 const的是一个对象,对象所包含的值是可以被修改的。抽象一点儿说,就是对象所指向的地址不能改变,而变量成员是可以修改的。
const student = {name:"zhangsan"}
student.name = "lisi"
console.log(student)
//这时就不会报错了
复制代码
2,字符串
- 模板字符串
- ES6模板字符简直是开发者的福音啊,解决了 ES5 在字符串功能上的痛点。
- 第一个用途,基本的字符串格式化。将表达式嵌入字符串中进行拼接。用${}来界定。
var w = " World"
console.log(`Hello${w}`)
复制代码
- 第二个用途,ES6反引号(``)来做多行字符串或者字符串一行行拼接。
var name = "张三"
var age = 18
console.log(`我叫${name},今年${age}了。`)
复制代码
- trim
- 除去字符串空格的。
- trim 左右空格都是去掉。
- trimLeft 左空格去掉。
- trimRight 右空格去掉。
var str = " a ab abc "
str.trim()
复制代码
- repeat
- 重复字符串的值。
var str = "123456"
str.repeat(10)
//此时重复str中的值10遍
复制代码
- includes
- 判断字符串中是否有需要的字符。
var str = "abc def"
str.includes('c d')
//此时输出结果为true
复制代码
- startsWith
- 判断字符串是不是以此开头。
var str = "abc def"
str.startsWith("abc")
//此时输出结果为true
复制代码
- endsWith判断字符串是不是以此结尾和startsWith用法一样
- padStart
- 直接看例子,例子中解释。
var str = "abc def"
str.padStart(15,"*")
//输出结果是"********abc def"
//意思是字符串输出15个字符,不够15个在字符串前面补上*
复制代码
- padEnd和padStart用法一样,只不过是在后面补够一定数量的字符
- 字符串的扩展在ES6中还有很多,在这不一一列举了。
3,函数
- 函数默认参数
- ES6为参数提供了默认值。在定义函数时便初始化了这个参数,以便在参数没有被传递进去时使用。
function f(num = 200){
console.log(num)
}
f(0)//0
f()//200
f(300)//300
复制代码
- 箭头函数
- ES6很有意思的一部分就是函数的快捷写法。也就是箭头函数。箭头函数有最直观的三个特点。
- (1)不需要 function 关键字来创建函数
- (2)省略 return 关键字
- (3)继承当前上下文的 this 关键字
//例如
[1,2,3,].map(x => x+1)
//等同于
[1,2,3].map((function(x){
return x + 1
}).bind(this))
//当你的函数有且仅有一个参数的时候,是可以省略掉括号的。
//当你函数返回有且仅有一个表达式的时候可以省略{} 和 return。
复制代码
4,数组——ES6中的扩展
- Array.from()
- 这个方法是Array构造器的静态方法。作用:将把类数组对象转成真正的数组。
- 格式1:Array.from(类数组对象);
- 格式2:Array.from(类数组对象,function(item,index){return;})
var lis = document.getElementsByTagName("li");
console.log(Array.isArray(lis))/结果false,判断不是真数组
var rs = Array.from(lis))
console.log(Array.isArray(rs))//结果true,判断是真数组
复制代码
- Array.of
- 作用:将一组值转换为数组。与Array.from功能相似,理解用来创建数组。主要目的是弥补构造器Array()的不足。
- 之前使用new创建数组的痛点:
var arr = new Array(3);
console.log(arr);//结果[<3 empty >]
复制代码
- 使用Array.of来改造,如下:
var arr = Array.of(3);
console.log(arr);//结果[3]
复制代码
- find和findIndex
- find:用于找出第一个符合条件的数组元素。找不到则是 undefined 。注意,它是不会返回多个,只找一个,找到了就返回。如果你要把所有的满足条件的数组元素素都找出来,你应该用filter()。
- findIndex:返回第一个符合条件的数组元素的索引。找不到则是-1;
- 格式:arr.find(function(item,index){ Return 条件;})
let arr = [
{name:"zhangsan",score:90}
{name:"lisi",score:80}
]
let rs = arr.find(item => item.name == "zhangsan")
let rs1 = arr.findIndex(item => item.name == "zhangsan")
console.log(rs)//输出{name:"zhangsan",score:90}
console.log(rs1)//输出0
复制代码
- includes
- 作用:判断元素是否在数组中存在(关于字符串的上面有列举)。返回值是true|false
let myarr = [1,2,3]
myarr.includes(1);//true
myarr,includes("a");//false
复制代码
- indexOf也可以做类似的工作:(返回索引)
let myarr = [1,2,3]
myarr.indexOf(1);//0
myarr,indexOf("a");//-1,找不到的返回-1
复制代码
- 有一点要注意indexOf对NaN的判断是错误的,如下:
[NaN,1,2,3].indexOf(NaN);//-1
复制代码
- NaN也比较奇怪,他自己不全等于他自己,如下:
NaN === NaN;//false
复制代码
- 但是includes对NaN的判断是比较准确的,如下;
[NaN,1,2,3].includes(NaN);//true
复制代码
- fill
- 作用:给数组填充指定值。fill方法用于空数组的初始化非常方便。已有数据会被覆盖。fill方法还可以接受第二个和第三个参数,用于指定填充的起始位置和结束位置。
- 格式1:arr.fill(值)
- 格式2:arr.fill(值,起点,终点)包括起点,不包括终点
let arr = new Array(3);
arr.fill("*")
console.log(arr)//输出['*','*','*']
复制代码
- 覆盖之前的元素,如下:
let arr = [1,2,3];
arr.fill("*")
console.log(arr)//输出['*','*','*']
复制代码
- 还可以指定填充的位置,如下:
let arr = new Array(5);
arr.fill("*",0,2)
console.log(arr)//输出['*','*',<3 empty items>]
复制代码
- 扩展运算符(...)
- 功能:把数据结构转成数组。
var arr = [1,2,3];
var arr2 = [...arr];
console.log(arr2);//输出结果[1,2,3]
复制代码
- 还可以利用扩展运算符,把类数组转成数组,如下:
<ul>
<li>1</li>
<li>1</li>
<li>1</li>
</ul>
<script>
var lis = document.getElementsByTagName("li");
var arr = [...lis]
console.log(Array.isArray(arr))//输出true
</script>
复制代码
- 把字符串转成数组,如下:
var str = "hello";
var arr = [...str];
console.log(arr);//输出结果['h','e','l','l','o']
复制代码
- 还可以利用扩展运算符来合并数组(或对象),如下:
var arr1 = [1,2];
var arr2 = [3,4];
var arr = [...arr1]
console.log(arr);//输出结果['h','e','l','l','o']
复制代码
5,数组的解构赋值
- 在ES6允许按照一定的模式,从数组或对象中提取值,对变量进行赋值,这被称为解构。是对变量的赋值,变量的值是数组或对象。
- 数组的解构赋值
let [a,b,c] = [1,2,3]
console.log(a)//1
console.log(b)//2
console.log(c)//3
复制代码
- 数组解构赋值中的细节
- 左右结构不一样。
let [a,b,c] = [1,2]
console.log(a)//1
console.log(b)//2
console.log(c)//undefined
复制代码
- 跳过一部分。
let [a,,c] = [1,2,3]
console.log(a)//1
console.log(c)//3
复制代码
- 默认值。
let [a = 11,b,c,d = 666] = [1,2,3]
console.log(a)//1
console.log(b)//2
console.log(c)//3
console.log(d)//666
复制代码
- 嵌套。
let [a,b,[c],d] = [1,2,[3],[4]]
console.log(a)//1
console.log(b)//2
console.log(c)//3
console.log(d)//[4]
复制代码
6,拓展的对象功能
- 对象初始化简写
- 键值对重名,ES6可以简写如下:
function man(name,age){
return{
name,//这里简写一个name
age//这里简写一个age
}
}
复制代码
- ES6可以省略冒号与function,如下:
var man = {
name:"zhangsan",
getName(){//这里省略了冒号和function
console.log(this.name)
}
}
复制代码
- ES6 对象提供了Object.assign()这个方法来实现浅复制。Object.assign()可以把任意多个源对象自身可枚举的属性拷贝给目标对象,然后返回目标对象。第一参数即为目标对象。在实际项目中,我们为了不改变源对象。一般会把目标对象传为{}。
const objA = { name: 'cc', age: 18 }
const objB = { address: 'beijing' }
const objC = {} // 这个为目标对象
const obj = Object.assign(objC, objA, objB)
// 我们将 objA objB objC obj 分别输出看看
console.log(objA) // { name: 'cc', age: 18 }
console.log(objB) // { address: 'beijing' }
console.log(objC) // { name: 'cc', age: 18, address: 'beijing' }
console.log(obj) // { name: 'cc', age: 18, address: 'beijing' }
// 是的,目标对象ObjC的值被改变了。
// so,如果objC也是你的一个源对象的话。请在objC前面填在一个目标对象{}
Object.assign({}, objC, objA, objB)
复制代码
7,ES6下的严格模式
- 之前学习的JS,语法非常灵活,JS中这个灵活的特性,弊大于先利。后来增加了严格模式。使用严格模式的目的:规则,提高编译效率。
- 启动严格模式:"use strict"
- (1),严格模式下不能不声明变量
"use strict"
a = 110;//此时是错的,运行时会报错
复制代码
- (2),严格模式下不能用8进制的数字
"use strict"
var a = 01;//此时是错的,运行时会报错
复制代码
- (3),严格模式下不能把函数定义在if语句中
"use strict"
if(true){
function f(){//此时是错的,运行时会报错
console.log("f...")
}
}
f();
复制代码
- (4),严格模式下函数不能有重名的形参
"use strict"
function f(a,a){//此时是错的,运行时会报错
console.log("f...")
}
f();
复制代码
- (5),严格模式下arguments不再跟踪形参的变化
"use strict"
function f(a,b){
console.log(a,b)//1 2
console.log(arguments[0],arguments[1])//1 2
arguments[0] = 1111;
arguments[1] = 2222;
console.log(a,b)//1 2
console.log(arguments[0],arguments[1])//1111 2222
}
f(1,2);
复制代码
- (6),严格模式下不能function中的this不再指向window,但是全局的变量和函数还是属于window的
"use strict"
function f(){
console.log(this)//undefined
}
f();
复制代码
- (7),严格模式也是有作用域范围的,分整个代码段和函数内。
a = 111;//这里是正确的
function f(){
"use strict"
b = 111;//这里是在严格模式下,是错误的
}
f();
复制代码
8,新的两种数据结构(集合)
- Set
- set和数组差不多,是伪数组,也是一种集合,区别在于:它里面的值都是唯一的,没有重复的。
- 放一个数组到set中必须上[]
var s1 = new Set([1,2,3,'true'])
复制代码
- 放一个对象到set中,使用add来添加
var s1 = new Set();
s1.add(1)
s1.add(2)
s1.add(3)
console.log(s1)//Set(3){1,2,3}
复制代码
- 要遍历上面的s1,不能使用forin,使用forEach或者for of
var s1 = new Set();
s1.add(1)
s1.add(2)
s1.add(3)
s1.forEach(item => console.log(item))//竖着1 2 3
//for(var p of s1){console.log(p)}
复制代码
- 删除set其中的一个元素
var s1 = new Set();
s1.add(1)
s1.add(2)
s1.add(3)
s1.delete(1)
复制代码
- Map
- 它类似于对象,里面存放也是键值对,区别在于:对象中的键名只能是字符串,如果使用map,它里面的键可以是任意值。
- 在map中放数据
var a = new Map([[1,"123"],["a",'hello']])
复制代码
- 使用set进行添加
var a = new Map([
[1,"123"],
["a",'hello']
]);
a.set(false,"abc")
a.set([1,2,3],{name:"wangcai"})
复制代码
- 重复的键会覆盖
var m = new Map();
m.set(1,"aaa")
m.set(1,"bbb")
console.log(m)//Map(1){1 => "bbb"}
复制代码
9,class
- 从形式上,向主流的面向对象的语言靠拢。我们之前写对象方式可以用 Class用进行优前面我们都是创建构造器,然后去new构造器,构造器就相当于一个类,在ES6中,就可以使用class来创建对象了。
- class创建对象
- 格式:class类名{constructor(参数){this.属性=参数;}method(){}}
- (1)class 是关键字,后面紧跟类名,类名首字母大写,采取的是大驼峰命名法则。类名之后是{}。
- (2)在{}中,不能直接写语句,只能写方法,方法不需要使用关键字。
- (3)方法和方法之间没有逗号。不是键值对。
- 使用class声明一个类,如下:
class NBAPlayer{
constructor(name,age){
this.name = name;
this.age = age;
}
}
var p1 = new NBAPlayer("库里","30")
复制代码
- 使用extends实现继承
- 格式:class子类extends父类{constructor(参数){super(参数)this.属性 = 值}}
- (1)使用 extends 关键字来实现继承。
- (2)在子类中的构造器constructor中,必须要显式调用父类的 super 方法,如果不调用,则 this 不可用。
- 使用ES6中的extends来实现继承:
class NBAPlayer{
constructor(name,age){
this.name = name;
this.age = age;
}
}
class MVP extends NBAPlayer{
constructor(name,age){
super(name,age)
}
}
var mvp1 = new MVP("库里","30")
复制代码
- 类的静态方法(static)
- 直接通过类名来访问的方法就是静态方法。如:Math.abs();这里的 abs()是静态方法。Array.isArray();isArray()是静态方法。 在方法名前加 static 就可以了。
class NBAPlayer{
constructor(name,age){
this.name = name;
this.age = age;
}
static jump(){
console.log("jump...")
}
}
NBAPlayer.jump();
复制代码
10,import和export
- import导入模块、export导出模块
//全部导入
import people from './example'
//有一种特殊情况,即允许你将整个模块当作单一对象进行导入
//该模块的所有导出都会作为对象的属性存在
import * as example from "./example.js"
console.log(example.name)
console.log(example.age)
console.log(example.getName())
//导入部分
import {name, age} from './example'
// 导出默认, 有且只有一个默认
export default App
// 部分导出
export class App extend Component {};
复制代码
- 导入的时候有没有大括号的区别是什么。下面是我在工作中的总结:
- 1.当用export,default,people导出时,就用import,people 导入(不带大括号)
- 2.一个文件里,有且只能有一个export,default。但可以有多个export。
- 3.当用exportname时,就用import{name}导入(记得带上大括号)
- 4.当一个文件里,既有一个export default people, 又有多个export name 或者exportage时,导入就用import people, { name, age }
- 5.当一个文件里出现n多个export导出很多模块,导入时除了一个一个导入,也可以用import * as example
11,Promise
- 在promise之前代码过多的回调或者嵌套,可读性差、耦合度高、扩展性低。通过Promise机制,扁平化的代码机构,大大提高了代码可读性;用同步编程的方式来编写异步代码,保存线性的代码逻辑,极大的降低了代码耦合性而提高了程序的可扩展性。
- 说白了就是用同步的方式去写异步代码。
- 发起异步请求
fetch('/api/todos')
.then(res => res.json())
.then(data => ({ data }))
.catch(err => ({ err }));
复制代码
- 当然以上promise的知识点,这个只是冰山一角。需要更多地去学习了解一下。
12,Generators
- 生成器( generator)是能返回一个迭代器的函数。生成器函数也是一种函数,最直观的表现就是比普通的function多了个星号*,在其函数体内可以使用yield关键字,有意思的是函数会在每个yield后暂停。
- 这里生活中有一个比较形象的例子。咱们到银行办理业务时候都得向大厅的机器取一张排队号。你拿到你的排队号,机器并不会自动为你再出下一张票。也就是说取票机“暂停”住了,直到下一个人再次唤起才会继续吐票。
- OK。说说迭代器。当你调用一个generator时,它将返回一个迭代器对象。这个迭代器对象拥有一个叫做next的方法来帮助你重启generator函数并得到下一个值。next方法不仅返回值,它返回的对象具有两个属性:done和value。value是你获得的值,done用来表明你的generator是否已经停止提供值。继续用刚刚取票的例子,每张排队号就是这里的value,打印票的纸是否用完就这是这里的done。
// 生成器
function *createIterator() {
yield 1;
yield 2;
yield 3;
}
// 生成器能像正规函数那样被调用,但会返回一个迭代器
let iterator = createIterator();
console.log(iterator.next().value); // 1
console.log(iterator.next().value); // 2
console.log(iterator.next().value); // 3
复制代码
- 那生成器和迭代器又有什么用处呢?
- 围绕着生成器的许多兴奋点都与异步编程直接相关。异步调用对于我们来说是很困难的事,我们的函数并不会等待异步调用完再执行,你可能会想到用回调函数,(当然还有其他方案比如Promise比如Async/await)。
- 生成器可以让我们的代码进行等待。就不用嵌套的回调函数。使用generator可以确保当异步调用在我们的generator函数运行一下行代码之前完成时暂停函数的执行。
- 那么问题来了,咱们也不能手动一直调用next()方法,你需要一个能够调用生成器并启动迭代器的方法。就像这样子的。
function run(taskDef) { //taskDef即一个生成器函数
// 创建迭代器,让它在别处可用
let task = taskDef();
// 启动任务
let result = task.next();
// 递归使用函数来保持对 next() 的调用
function step() {
// 如果还有更多要做的
if (!result.done) {
result = task.next();
step();
}
}
// 开始处理过程
step();
}
复制代码
- 生成器与迭代器最有趣、最令人激动的方面,或许就是可创建外观清晰的异步操作代码。你不必到处使用回调函数,而是可以建立貌似同步的代码,但实际上却使用 yield 来等待异步操作结束。
总结
ES6新特性远不止于此,但对于我们日常的学习来说。这算不上全部,但是能算得上是高频使用了。当然还有很有好玩有意思的特性。让我们一起学习吧!
- 有什么问题,欢迎不吝赐教!