ES6总结
摘要
新的标准规范
ECMAScript2015是js的一种新的标准规范,就是对js的写法上提出了新的语法要求和写法格式。
ECMAScript和js的关系
ECMAScript 和 JavaScript 的关系是,前者是后者的规格,后者是前者的一种实现。javascript 是 netscape创 造的并交给了国际标准化组织 ECMA,之所以不叫做 JavaScript 由于商标的问题,java 是 sun 公司的商标,根据 授权协议只有 Netscape 公司可以合法使用 JavaScript 这个名字,另外就是为了体现 JavaScript 的标准的制定者 不是 ECMA 所以取名为 ECMAScript。
ES6与ECMAScript2015的关系
ES6 是 ECMA的为 JavaScript 制定的第 6 个版本的标准,标准委员会最终决定,标准在每年的 6 月份正式发布一 次,作为当年的正式版本。ECMAscript2015是在 2015 年 6 月份发布的 ES6 的第一个版本。依次类推 ECMAscript 2016 是 ES6 的第二个版本、 ECMAscript 2017 是 ES6的第三个版本……
块级作用域
块级作用域的种类
ECMAscript2015为js提出第三个作用域,凡是带{}的都是一个块级作用域。
if语句中的{},for语句中的{},while语句中的{},或者是我们单独写的{},try、catch中的{}这些都提供了块级作用域。
块级作用域分析
为什么要使用块级作用域
- 内层变量会覆盖外层变量
var lagou='拉钩'
function fn(){
console.log(lagou)// undefined
if(false){
var lagou='hello'
}
}
fn()
这是因为fn函数体内以及有var声明的变量lagou,只是还没有赋值,默认为undefined
- 用来计数的的循环变量泄露为全局变量
for(var i=0;i<10;i++){}
// 10
console.log(i)
块级作用域的成员
块级作用域内的成员需要使用let或者const命令定义的变量。
var lagou='拉钩'
function fn(){
console.log(lagou)// 拉钩
if(true){
let lagou='hello'
}
}
fn()
let const
let
let 主要声明块级作用域下的成员,这个成员变量的作用域范围只能在当前块级作用域下。
const
const声明变量的同时必须要赋值。
const声明之后,不允许去修改它的值,这里面的值说的是不允许修改它,是声明之后不允许重新指向一个新的内存地址,但是可以去修改内存地址中的属性成员。
数组
数组的解构
ES6允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构。
- 完全解构:将数组中的每一个值都对应上相应的变量
var arr=['lagou','edu','web']
let [com,ind,work]=arr
console.log(work)// web
- 不完全解构:数组中的部分值对应上了相应的变量
var arr=['lagou','edu','web']
let [,,work]=arr
console.log(work)// web
注意:模式没有匹配上的可以不填,但是必须要加逗号隔开
-
扩展运算符 …
- 三个点是一个展开运算符,其功能为对三个点后面的变量进行展开操作
- 三个点展开运算符只能对具有iterator接口的对象进行展开操作
- 使用场景
// 案例一 var arr=['拉钩','edu','web'] fn(...arr) // 案例二 let a=[88,89,100] let b=[101,102,103] a.push(...b) console.log(a)// [88,89,100,101,102,103] // 案例三 let d={ e:'拉钩教育', f:'www.lagou.com' } let obj={ g:'web', h:1100, ...d } console.log(obj)// {g:'web',h:1100,e:'拉钩教育',f:'www.lagou.com'} // 案例四 var st='拉钩 edu' var arr=[...st] console.log(arr)// ['拉','钩','e','d','u'] // 数组特殊解构 let [a,...b]=[1,2,3,4,5,6] console.log(a)// 1 console.log(b)// [2,3,4,5,6] -
解构不成功
右边的变量个数超过了等号左边中数组的元素
let [a,b,c]=[12] console.log(b)// undefined如果解构没有成功,则变量的值是undefined,如果是展开运算的变量则是空数组
let [a,b,...c]=[12] console.log(c)// []
数组的扩展
-
扩展运算符 …
-
扩展运算符是三个点 … 它好比rest参数的逆运算,将一个数组转为用读好分隔的参数序列
-
替代apply的使用技巧
我们之前在求一个数组中的最大值的时候采用的方式是
Math.max.apply(null,[12,34,56,43])==56var max=Math.max.apply(null,[12,34,56,43]) console.log(max)// 56 var max2=Math.max(...[12,34,56,43]) console.log(max2)// 56
-
-
Array类的扩展方法
Array.from()
Array.from()方法用于将两类对象转为真正的数组:类似数组的对象和可遍历的对象var arraylike={ 0:'lagou', 1:'edu', 2:'web', length:3, } var arr=Array.from(arraylike)// ['lagou','edu','web']Array.from还可以接受第二个参数,作用类似于数组的map方法,用来对每个元素进行处理,将处理后的值放入返回的数组var arr=Array.from([1,2,3],function(x){ return x*x }) console.log(arr)// [1,4,9]Array.of()
Array.of()方法用于将一组值,转换为数组,这个方法的主要目的,是弥补数组构造函数Array()的不足。因为参数个数的不同,会导致Array()的行为有差异。var arr=Array(3)// [emptyx3]这里面3表示数组中元素的长度。
var arr=Array(2,3,4)// [2,3,4]当
Array()里的参数个数大于1的时候,表示的是数组元素当
Array.of()方法里面不管参数的个数多少,都将其转为数组的元素var arr=Array.of(3) console.log(arr)// [3]
对象
对象中有关变量的解构赋值
解构不仅可以用于数组,还可以用于对象。
对象的解构与数组有一个重要的不同,数组中的元素是按次序排列的,变量的取值由它的位置决定;而对象的属性没有次序,变量必须与属性同名,才能取到正确的值。
对象的扩展
-
对象的简写
- 当变量名和属性名同名时,省略同名的属性值
const foo='bar' const baz={foo} // 等同于 const baz={foo:foo} -
省略方法中的function
const obj={
method(){
return '拉钩!';
}
}
// 等同于
const obj={
method:function(){
return '拉钩!';
}
}
- 属性的赋值器(setter)和取值器(getter)
const lagou={
name:'拉钩',
get com(){
return this.name
},
set work(value){
this.name=this.name+value
}
}
console.log(lagou.com)// 拉钩
lagou.work='招聘'
console.log(lagou.name)// 拉钩招聘
- 属性名表达式
es5中定义对象中的属性有两种方法,一种是用标识符做属性,一种是用表达式做属性
// 方法一
obj.name='拉钩'
// 方法二
obj['name']='拉钩'
var lagou={
name:'拉钩'
}
如果用大括号定义对象,那么在es5中只能使用标识符定义属性
var lagou={
name:'拉钩'
}
但是ECMAScript 2015在使用大括号定义对象的时候,允许使用表达式定义属性,把表达式放在括号中
let name='lagou'
const lagou={
[name]:'web'
}
console.log(lagou)// {lagou:'web'}
三点运算符在对象中的用途
-
用于对象的解构
- 对象的解构赋值用于从一个对象取值,相当于将目标对象自身的所有可遍历的(enumerable)、但尚未被读取的属性,分配到指定的对象上面。所有的键和他们的值都会被拷贝到新对象上面。
let {x,y,...z}={x:1,y:2,a:3,b:4} console.log(z)// {a:3,b:4}上面代码中,变量z是解构赋值所在的对象。它获取等号右边的所有尚未读取的键(a和b),将它们连同值一起拷贝过来。
注意:1.解构赋值必须是最后一个参数;2.解构赋值的拷贝是浅拷贝。 -
用于扩展运算
- 对象的扩展运算符 … 用于取出参数对象的所有可遍历属性,拷贝到当前对象中
let z={name:'lagou',work:'web'} let n={...z} console.log(n)// {name:'lagou',work:'web'}
字符串
字符串模板
- 传统的字符串里不能使用换行符,必须使用转义符’\n’替代,字符串模板里面可以使用。
- 模板字符串是增强版的字符串,可以当做普通字符串使用,也可以用来定义多行字符串,或者在字符串中嵌入变量
- 模板字符串中嵌入变量
var name='拉钩'
var st=`欢迎来到${name}`
console.log(st)// 欢迎来到拉钩
模板标签
- 模板字符串的功能,不仅仅上面这些。
- 模板字符串可以紧跟在一个函数后面,该函数将被调用来处理这个模板字符串,这个被称为 标签模板 功能
console.log`hello`
// 等同于
console.log('hello')
标签模板其实不是模板,而是函数调用的一种特殊形式。
标签指的就是函数,紧跟在后面的模板字符串就是它的参数。
注意:如果模板字符里面有变量,就不是简单的调用了,而是将模板字符串先处理成多个参数,再调用函数
var name='lagou'
var work='web'
function tag(st,a,b){
console.log(st)// ['hello',',职业','开发']
console.log(a)// lagou
console.log(b)// web
return 'hello lagou'
}
const st=tag`hello${name},职业${work}开发`
console.log(st)// hello lagou
函数内的返回值,就是tag函数处理模板字符串后的返回值,如果没有返回值,默认是undefined
扩展的方法
-
字符串实例的方法
- includes
返回布尔值,表示是否找到了参数字符串
var st='lagou web' var b=st.includes('web') console.log(b)// turestartWith
返回布尔值,表示参数字符串是否在原字符串的头部
var st='lagou web' var b=st.startWith('la') console.log(b)// trueendWith
返回布尔值,表示参数字符串是否在原字符串的尾部
var st='lagou web' var b=st.endWith('web') console.log(b)// true
函数
参数默认值
ES6允许为函数的参数设置默认值,即直接写在参数定义的后面。
function fn(a,b='lagou'){
console.log(a+b)
}
fn('hello')// hello lagou
-
注意
- 参数变量是默认声明的,所以不能用
let或const再次声明 - 使用参数默认值得时候,函数不能有同名参数
- 参数变量是默认声明的,所以不能用
-
参数默认值的位置
- 通常情况下,定义了默认值的参数,应该是函数的尾参数。因为这样比较容易看出来,到底省略了哪些参数。如果非尾部的参数设置默认值,实际上这个参数是没法省略的。
function(x=1,y){ return [x,y] } f()// [1,undefined] f()// [2,undefined] f(,1)// 报错
rest参数
- ES6引入rest参数(形式为 … 变量名),用于获取函数的多余参数,这样就不要使用arguments对象了。
- rest参数搭配的变量是一个数组,该变量将多余的参数放入数组中。
function add(...values){
console.log(values)
}
add(2,5,3)// [2,5,3]
- rest参数和函数中的参数解构的区别
- rest参数是发生在函数的定义阶段,函数的参数解构是发生在函数的调用阶段
- 二者是一种互逆运算
function add(...vlaues){
// 这是rest参数
console.log(vlaues)
}
add(2,5,3)// [2,5,3]
var arr=[1,2,3]
function fn(a,b,c){
console.log(a+b+c)
}
fn(...arr)// 6 这是参数的解构
箭头函数
ES6允许使用箭头定义函数
var f=(v)=>v
// 等同于
var f=function(v){
return v
}
- 如果箭头函数不需要参数或者需要多个参数,就是用圆括号代表参数部分
var f=()=>5
var sum=(num1,num2)=>num1+num2
- 如果箭头函数的代码块多余一条语句,就要适用大括号将他们括起来,并且使用return语句返回
var sum=(num1,num2)=>{
return num1+num2
}
- 由于大括号被解释为代码块,所以箭头函数直接返回一个对象,则必须在对象外面加上括号,否则会报错
let getItem=id=>({id,name:'Temp'})// 不加括号的会报错
- 箭头函数使用注意点:
- 函数体内this对象,就是定义时所在的对象,而不是使用时所在的对象。箭头函数外面的this是什么,箭头函数里面的this还是什么。
- 不可以当做构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。
- 不可以使用arguments对象,该对象在箭头函数体内不存在。如果要用,可以使用过rest参数代替。
var name='web'
var obj={
name:'lagou',
fn(){
var t=setTimeout(function(){
console.log(this.name)// web this指向window
},1000)
}
}
obj.fn()
// 箭头函数
var name='web'
var obj={
name:'lagou',
fn(){
var t=setTimeout(()=>{
console.log(this.name)// lagou this指向obj
},1000)
}
}
obj.fn()
Object
Object.assign
Object.assign方法用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target)。
const target={
a:123,
b:123
}
const source={
a:456,
c:456
}
const result=Object.assign(target,source)
console.log(target)// {a:456,b:123,c:456}
console.log(target===result)// true
如果目标对象和源对象有同名属性,则后面的属性会覆盖前面的属性。且assign()的返回值就是第一个对象。
如果有多个源对象有同名属性,依然是后面的会覆盖前面的属性
- 利用
Object.assign复制一个对象,其中一个对象的修改不会影响到另一个对象
const source={
a:123
}
var obj=Object.assing({},source)
obj.a=456
console.log(obj)// {a:456}
console.log(source)// {a:123}
Object.is
-
Object.is就是用来比较两个值是否严格相等,与严格比较运算符(===)的行为基本一致ES5比较两个值是否相等,只有两个运算符:相等运算符(==)和严格相等运算符(===)。它们都有缺点,前者会自动转换数据类型,后者的
NaN不等于自身,以及+0等于-0。JavaScript 缺乏一种运算,在所有环境中,只要两个值是一样的,它们就应该相等。
console.log(Object.is(+0,-0))// false console.log(+0===-0)// true console.log(Object.is(NaN,NaN))// true console.log(NaN===Nan)// false
Proxy
概述
Proxy可以理解成一个快递员,我们发快递还是接受快递,都需要有这个快递员充当一个代理的作用。
ES6原生提供Proxy构造函数,用来生成proxy实例,这个实例就是一个代理对象(快递员)。
目标对象
这个代理对象有两个参数,一个是代理的目标对象;第二个是配置对象,用来指定代理的拦截行为。
const person={
name:'zce',
age:20
}
const personProxy=new Proxy(person,{
get(target,property){
console.log(target,property)// {namne:'zce',age:20} 'name'
return 100
},
set(){}
})
console.log(personProsy.name)// 100
配置对象
配置对象中一般有两个方法get和set,get用来拦截目标对象属性的访问请求。
-
get方法中有两个参数,第一个参数是目标对象,第二个参数是访问的那个属性。
get方法的返回值就是我们获取到的这个属性的返回值。
get方法也可以接受第三个参数,第三个参数是proxy实例本身,第三个参数是可选参数。
-
set方法用来拦截某个属性的赋值操作,可以接收四个参数,依次为目标对象、属性名、属性值和Proxy实例本身,其中最后一个参数可选
const person={
name:'zce',
age:20
}
const personProxy=new Proxy(person,{
get(target,property,o){
return property in target ? target[property] :undefined
},
set(obj,pro,value,o){
console.log(obj,pro,value,o)
}
})
console.log(personProxy.name='zhang')// {name:'zce',age:20} 'name' 'zhang' Proxy{name:'zce',age:20}
Reflect
概述
Reflect对象与Proxy对象一样,也是ES6为了操作对象而提供的新API。Reflect对象的设计有以下几个目的:
-
将Object对象的一些明显属于语言内部的方法(比如
Object.defineProperty),放到Reflect对象上,现阶段,某些方法同时在Object和Reflect对象上部署,未来的新方法只在Reflect对象上。也就是说,从Reflect对象上可以拿到语言内部的方法。 -
修改某些Object方法的返回结果,让其变得更合理。比如
Object.defineProperty(obj, name, desc)在无法定义属性时,会抛出一个错误,而Reflect.defineProperty(obj, name, desc)则会返回false。// 老写法 try{ // success Object.defineProperty(target,property,attributes) }catch(e){ // failure } // 新写法 if(Reflect.definproperty(target,property,attributes)){ // success }else{ // failure } -
让Object操作都变成函数行为。某些Object操作是命令式,比如
name in obj和delete obj[name],而Reflect.has(obj.name)和Reflect.deleteProperty(obj,name)让它们变成了函数行为。
静态方法
-
Reflect.getReflect.get(target,name,receiver),Reflect.get方法查找并返回target对象上的name属性,如果没有该属性,则返回undefined
var myObject={ foo:1, bar:2, get baz(){ return this.foo+this.bar } } Reflect.get(myObject,'foo')// 1 Reflect.get(myObject,'bar')// 2 Reflect.get(myObject,'baz')// 3- 如果name属性部署读取了函数(getter),则读取函数的this绑定receiver
var myObject={ foo:1, bar:2, get baz(){ return this.foo+this.bar } } var myReceiverObject={ foo:4, bar:4 } Reflect.get(myObject,'baz',myReceiverObject)// 8- 如果第一个参数不是对象,会报错
Reflect.get(1,'doo')// 报错 Reflect.get(false,'doo')// 报错 -
Reflect.setReflect.set(target,name,value,receiver),Reflect.set方法设置target对象的name属性等于value
var myObject={ foo:1 } myObject.foo// 1 Reflcet.set(myObject,'foo',2) myObject.foo// 2- 如果name属性设置了赋值函数,则赋值函数的this绑定receiver
var myObject={ foo:4, set bar(value){ return (this.foo=value) } } var myReceiverObject={ foo:0 } Reflect.set(myObject,'bar',1,myReceiverObject) myObject.foo// 4 myReceiverObject.foo// 1 -
Reflect.hasReflect.has(obj,name),Reflect.has方法对应name in obj里面的in运算符- 如果第一个参数不是对象,会报错
-
Reflect.deletePropertyReflect.deleteProperty(obj, name),Reflect.deleteProperty方法等同于delete obj[name],用于删除对象的属性- 该方法返回一个布尔值。如果删除成功,或者删除的属性不存在,返回true;删除失败,被删除的属性依然存在,返回false。
- 如果第一个参数不是对象,会报错。
Promise
概述
Promise是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更强大更合理。
Promise简单来说是一个容器,里面存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise是一个对象,从它可以获取异步操作的消息。Promise提供统一的API,各种异步操作都可以用同样的方法进行处理。
promise特点
Promise对象有以下两个特点:
-
对象状态不受外界影响。Promise对象代表一个异步操作,有三种状态
pending(进行中)、fulfilled(已成功)和rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是Promise这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变。 -
一旦状态改变,就不会再变,任何时候都可以得到这个结果。
Promise对象的状态改变,只有两种可能:从pending变为fulfilled和从pending变为rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved(已定型)。如果改变已经发生了,你再对Promise对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。有了
Promise对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。此外,Promise对象提供统一的接口,使得控制异步操作更加容易。
Promise使用方法
- Promise构造函数接收一个函数作为参数,该函数的两个参数分别是resolve和reject。它们是两个函数,由JavaScript引擎提供,不用自己部署。
var p=new Promise((resolve,reject)=>{
if(true){
resolve(data)
}else{
reject(data)
}
})
resolve函数的作用是,将Promise对象的状态从“未完成”变为“成功”(即从 pending 变为 resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;reject函数的作用是,将Promise对象的状态从“未完成”变为“失败”(即从 pending 变为 rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。
- Promise实例生成以后,可以用then方法分别指定resolve状态和reject状态的回调函数。
p.then(
function(value){
// success业务处理
},
function(error){
// failure
}
)
then方法可以接受两个回调函数作为参数。第一个回调函数是Promise对象的状态变为resolved时调用,第二个回调函数是Promise对象的状态变为rejected时调用。其中,第二个函数是可选的,不一定要提供。这两个函数都接受Promise对象传出的值作为参数。
function time(ms){
return new Promise((resolve,reject)=>{
setTimeout(resolve,ms)
})
}
time(1000).then(value=>{
console.log(value)
})
- Promise对象新建后立即执行
let promise=new Promise((resolve,reject)=>{
console.log('promise')
resolve()
})
promise.then(()=>{
console.log('resolved')
})
console.log('hello')
// promise hello resolved
class
概述
ES6 提供了更接近传统语言的写法,引入了 Class(类)这个概念,作为对象的模板。通过class关键字,可以定义类。基本上,ES6 的class可以看作只是一个语法糖,它的绝大部分功能,ES5 都可以做到,新的class写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。上面的代码用 ES6 的class改写,就是下面这样。
class Point{
constructor(x,y){
this.x=x
this.y=y
}
toString(){
return "("+this.x+","+this.y+")"
}
}
基本介绍
-
constructor
- constructor方法是类的默认方法,通过new命令生成对象实例时,自动调用该方法。一个类必须有constructor方法,如果没有显示定义,一个空的constructor方法会被默认添加。
-
类的实例
- 生成类实例的写法,与
ES5完全一样,也是使用new命令。前面说过,如果忘记加上new,象函数那样调用Class,会报错。 - 与
ES5一样,实例的属性除非显式定义在其本身(即定义在this对象上),否则都是定义在原型上(即定义在class上)。
class Point{ constructor(x,y){ this.x=x this.y=y } toString(){ return "("+this.x+","+this.y+")" } } var point=new Point(2,3) point.toString()// (2,3) point.hasOwnProperty('x')// true point.hasOwnProperty('y')// true point.hasOwnProperty('toString')// true point.__proto__.hasOwnProperty('toString')// true- 与
ES5一样,类的所有实例共享一个原型对象
- 生成类实例的写法,与
-
getter和setter
- 与
ES5一样,在“类”的内部可以使用get和set关键字,对某个属性设置存值函数和取值函数,拦截该属性的存取行为。
- 与
-
属性表达式
- 类的属性名,可以采用表达式
let methodName = "getArea"; class Square { constructor(length) { // ... } [methodName]() { // ... } }
static
-
类相当于实例的原型,所有在类中定义的方法,都会被实例继承。如果在一个方法前,加上
static关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这就称为“静态方法”。class Foo { static classMethod() { return "hello"; } } Foo.classMethod(); // 'hello' var foo = new Foo(); foo.classMethod(); // TypeError: foo.classMethod is not a function解说:上面代码中,
Foo类的classMethod方法前有static关键字,表明该方法是一个静态方法,可以直接在Foo类上调用(Foo.classMethod()),而不是在Foo类的实例上调用。如果在实例上调用静态方法,会抛出一个错误,表示不存在该方法。 -
注意,如果静态方法包含
this关键字,这个this指的是类,而不是实例class Foo{ static bar(){ this.baz() } static baz(){ console.log('hello') } baz(){ console.log('world') } } Foo.bar()// hello解说:上面代码中,静态方法
bar调用了this.baz,这里的this指的是Foo类,而不是Foo的实例,等同于调用Foo.baz。另外,从这个例子还可以看出,静态方法可以与非静态方法重名。 -
父类的静态方法,可以被子类继承
class Foo { static classMethod() { return "hello"; } } class Bar extends Foo {} Bar.classMethod(); // 'hello' -
静态属性
静态属性指的是 Class 本身的属性,即
Class.propName,而不是定义在实例对象(this)上的属性ES6明确规定,Class 内部只有静态方法,没有静态属性。现在有一个提案提供了类的静态属性,写法是在实例属性的前面,加上static关键字。class MyClass{ static myStaticProp=42 constructor(){ console.log(MyClass.myStaticProp)// 42 } }
继承
- Class 可以通过
extends关键字实现继承,这比 ES5 的通过修改原型链实现继承,要清晰和方便很多。
class Point{}
class ColorPoint extend Point{}
解说:上面代码定义了一个ColorPoint类,该类通过extends关键字,继承了Point类的所有属性和方法。但是由于没有部署任何代码,所以这两个类完全一样,等于复制了一个Point类。
- 子类必须在
constructor方法中调用super方法,否则新建实例时会报错。这是因为子类自己的this对象,必须先通过父类的构造函数完成塑造,得到与父类同样的实例属性和方法,然后再对其进行加工,加上子类自己的实例属性和方法。如果不调用super方法,子类就得不到this对象。
class Point{
...
}
class ColorPoint extends Point{
constructor(){}
}
let cp=new ColorPoint()// ReferenceError
- 在子类的构造函数中,只有调用
super之后,才可以使用this关键字,否则会报错。这是因为子类实例的构建,基于父类实例,只有super方法才能调用父类实例。
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
}
class ColorPoint extends Point {
constructor(x, y, color) {
this.color = color; // ReferenceError
super(x, y);
this.color = color; // 正确
}
}
-
super这个关键字,既可以当作函数使用,也可以当作对象使用。在这两种情况下,它的用法完全不同
- 第一种情况,
super作为函数调用时,代表父类的构造函数。ES6要求,子类的构造函数必须执行一次super函数。
class A {} class B extends A { constructor() { super(); } }解说:上面代码中,子类
B的构造函数之中的super(),代表调用父类的构造函数。这是必须的,否则 JavaScript 引擎会报错。super虽然代表了父类A的构造函数,但是返回的是子类B的实例,即super内部的this指的是B的实例,因此super() 在这里相当于A.prototype.constructor.call(this)- 第二种情况,
super作为对象时,在普通方法中,指向父类的原型对象;在静态方法中,指向父类
class A { p() { return 2; } } class B extends A { constructor() { super(); console.log(super.p()); // 2 } } let b = new B();- 由于
super指向父类的原型对象(prototype),所以定义在父类实例上的方法或属性,是无法通过super调用的
class A { constructor() { this.p = 2; } } class B extends A { get m() { return super.p; } } let b = new B(); b.m; // undefinedSet
- 第一种情况,
基本用法
ES6提供了新的数据结构Set。它类似于数组,但是成员都是唯一的,没有重复值。- Set本身是一个构造函数,用来生成Set数据结构。
- Set函数可以接收一个数组(或者具有
iterable接口的其它数据结构)作为参数,用来初始化。
const set=new Set([1,2,3,4,4])
console.log(set)// [1,2,3,4]
- 数组去重
[...new Set([1,2,3,4,5,4,2,3])]// [1,2,3,4,5]
- 字符串去重
[...new Set('ababaaabbb')].join('')// 'ab'
属性和方法
-
Set数据结构有以下属性
Set.property.constructor:构造函数,默认就是Set函数Set.property.size:返回Set实例的成员总数
-
Set实例的方法分为两类
-
操作方法(用于操作数据)
Set.property.add(value):添加某个值,返回Set数据结构本身
const items=new Set([]) items.add(1).add(2).add(3) console.dir(items)// [1,2,3]Set.property.delete(value):删除某个值,返回一个布尔值,表示是否删除成功
const items=new Set([12,23,34]) let b=items.delete(12) console.log(b)// true console.log(items)// Set(2){23,34}Set.property.has(value):返回一个布尔值,表示改值是否为Set的成员Set.property.clear():清除所有成员,没有返回值
const items=new Set([12,23,34]) var b=items.clear(12) console.log(b)// undefined console.log(items)// Set(0){} -
遍历方法(用于遍历成员)
Set.property.keys():返回键名的遍历器Set.prtoperty.values():返回键值得遍历器Set.property.entries()
lety set=new Set(['red','green','blue']) for(let item of set.keys()){ console.log(item) } // red // green // blue for(let item od set.values()){ console.log(item) } // red // green // blue for (let item of set.entries()) { console.log(item); } // ["red", "red"] // ["green", "green"] // ["blue", "blue"]Set.property.forEach()
let set=new Set([1,4,9]) set.forEach((value,key)=>console.log(key+":"+value)) // 1 : 1 // 4 : 4 // 9 : 9
-
Map
概述
JavaScript 的对象(Object),本质上是键值对的集合(Hash 结构),但是传统上只能用字符串当作键。这给它的使用带来了很大的限制。为了解决这个问题,ES6提供了 Map 数据结构。它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。也就是说,Object 结构提供了“字符串—值”的对应,Map 结构提供了“值—值”的对应,是一种更完善的 Hash 结构实现。如果你需要“键值对”的数据结构,Map 比 Object 更合适。
基本用法
- 作为构造函数,Map可以接收一个数组作为参数。该数组的成员是一个个表示键值对的数组
const map=new Map([
['name','张三'],
['title','Author']
])
map.size// 2
map.has('name')// true
map.get('name')// '张三'
map.has('title')// true
map.get('title')// 'Author'
属性和方法
- size属性返回Map数据结构的成员总数
Map.property.set(key,value):设置键名key的键值为value,返回整个Map结构。如果key已经存在,键值会被更新;否则会新生成该键;可以使用链式写法。Map.property.get(key):该方法返回key对应的键值,如果找不到,返回undefined。Map.prototype.has(key):has方法返回一个布尔值,表示某个键是否在当前 Map 对象之中。Map.prototype.delete(key):delete方法删除某个键,返回true。如果删除失败,返回false。Map.prototype.clear():clear方法清除所有成员,没有返回值。
遍历
Map.prototype.keys():返回键名的遍历器。Map.prototype.values():返回键值的遍历器。Map.prototype.entries():返回所有成员的遍历器。Map.prototype.forEach():遍历 Map 的所有成员。
Symbol
概述
ES5 的对象属性名都是字符串,这容易造成属性名的冲突。比如,你使用了一个他人提供的对象,但又想为这个对象添加新的方法(mixin 模式),新方法的名字就有可能与现有方法产生冲突。如果有一种机制,保证每个属性的名字都是独一无二的就好了,这样就从根本上防止属性名的冲突。这就是 ES6 引入Symbol的原因。ES6 引入了一种新的原始数据类型Symbol,表示独一无二的值。它是 JavaScript 语言的第七种数据类型,前六种是:undefined、null、布尔值(Boolean)、字符串(String)、数值(Number)、对象(Object)Symbol 值通过Symbol函数生成。这就是说,对象的属性名现在可以有两种类型,一种是原来就有的字符串,另一种就是新增的 Symbol 类型。凡是属性名属于 Symbol 类型,就都是独一无二的,可以保证不会与其他属性名产生冲突。
var obj={
say:'lagou'
}
var say=Symbol()// say是Symbol类型
obj[say]='web'
console.log(obj)// {say:'lagou',Symbol():'web'}
语法
- Symbol函数前面不能使用new命令,否则会报错
- Symbol函数可以接收一个字符串作为参数,表示对Symbol实例的描述,只要是为了在控制台显示,或者转为字符串时,比较容易区分
- 每一个Symbol值都不相等,这意味着Symbol值可以作为标识符,用于对象的属性名,就能保证不会出现同名的属性
- Symbol值不能与其它类型的值进行运算,会报错
- Symbol值作为对象属性名的时候,不能使用点运算符
let sym=Symbol('My Symbol')
const a={}
a.sym="Hello"
console.log(a[sym])// undefined
console.log(a['sym'])//Hello
-
Symbol 作为属性名,遍历对象的时候,该属性不会出现在
for...in、for...of循环中,也不会被Object.keys()、Object.getOwnPropertyNames()、JSON.stringify()返回。但是,它也不是私有属性,有一个
Object.getOwnPropertySymbols()方法,可以获取指定对象的所有 Symbol 属性名。该方法返回一个数组,成员是当前对象的所有用作属性名的 Symbol 值。 -
有时,我们希望重新使用同一个 Symbol 值,
Symbol.for()方法可以做到这一点。它接受一个字符串作为参数,然后搜索有没有以该参数作为名称的 Symbol 值。如果有,就返回这个 Symbol 值,否则就新建一个以该字符串为名称的 Symbol 值,并将其注册到全局。Symbol.for()与Symbol()这两种写法,都会生成新的 Symbol。它们的区别是,前者会被登记在全局环境中供搜索,后者不会。Symbol.for()不会每次调用就返回一个新的 Symbol 类型的值,而是会先检查给定的key是否已经存在,如果不存在才会新建一个值。比如,如果你调用
Symbol.for("cat")30 次,每次都会返回同一个 Symbol 值,但是调用Symbol("cat")30 次,会返回 30 个不同的 Symbol 值。由于
Symbol()写法没有登记机制,所以每次调用都会返回一个不同的值。Symbol.for()为 Symbol 值登记的名字,是全局环境的,不管有没有在全局环境运行。function foo() { return Symbol.for("bar"); } const x = foo(); const y = Symbol.for("bar"); console.log(x === y); // true
可迭代接口
Iterater的概念
-
简单介绍
JavaScript 原有的表示“集合”的数据结构,主要是数组(
Array)和对象(Object),ES6 又添加了Map和Set。这样就有了四种数据集合,用户还可以组合使用它们,定义自己的数据结构,比如数组的成员是Map,Map的成员是对象。这样就需要一种统一的接口机制,来处理所有不同的数据结构。遍历器(Iterator)就是这样一种机制。它是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署 Iterator 接口,就可以完成遍历操作(即依次处理该数据结构的所有成员)。
Iterator 的作用有三个:
一是为各种数据结构,提供一个统一的、简便的访问接口;
二是使得数据结构的成员能够按某种次序排列;
三是
ES6创造了一种新的遍历命令for...of循环,Iterator 接口主要供for...of消费。 -
Iterator 的遍历过程
- 创建一个指针对象,指向当前数据结构的起始位置。也就是说,遍历器本质上,就是一个指针对象。
- 第一次调用指针对象的next方法,可以将指针指向数据结构的第一个成员。
- 第二次调用指针对象的next方法,可以将指针指向数据结构的第二个成员。
- 不断调用指针对象的next方法,直到他指向数据结构的结束位置。
- 每一次调用next方法,都会返回数据结构的当前成员的信息。具体来说就是返回一个包含value和done两个属性的对象。其中value就是当前成员的值,done是一个布尔值,表示遍历是否结束。
let it=easyIterator(['a','b']) it.next()// {value:'a',done:false} it.next()// {value:'b',done:false} it.next()// {value:undefined,done:true} function easyIterator(array){ var nextIndex=0 return { next:function(){ return nextIdex < array.length ? {value:array[nextIndex++],done:false} : {value:undefined,done:true} } } }
iterater接口
- 字符串 数组 set map arguments 都有
iterater接口,nodelist集合,都可以用 for of 遍历
var st = "lagou";
for (i of st) {
console.log(i); // l a g o u
}
var arr = [1, 2];
for (v of arr) {
console.log(v); //1 2
}
function fn(a, b, c) {
for (i of arguments) {
console.log(i); //1 2 3
}
}
fn(1, 2, 3);
Modules
概述
JavaScript 一直没有模块(module)体系,无法将一个大程序拆分成互相依赖的小文件,再用简单的方法拼装起来。其他语言都有这项功能,比如 Ruby 的require、Python 的import,甚至就连 CSS 都有@import,但是 JavaScript 任何这方面的支持都没有,这对开发大型的、复杂的项目形成了巨大障碍。ES6 在语言标准的层面上,实现了模块功能,而且实现得相当简单,完全可以取代CommonJS 和 AMD规范,成为浏览器和服务器通用的模块解决方案。
语法
-
export用于规定模块的对外接口
- 一个模块就是一个独立的文件。该文件内部的所有变量,外部无法获取。如果希望外部能够读取模块内部的某个变量,就必须使用export关键字导出该变量。下面是一个
JS文件,使用export命令导出变量。
//demo.js export var firstName = "Michael"; export var lastName = "Jackson"; export var year = 1958; //或者 var firstName = "Michael"; var lastName = "Jackson"; var year = 1958; export { firstName, lastName, year }; - 一个模块就是一个独立的文件。该文件内部的所有变量,外部无法获取。如果希望外部能够读取模块内部的某个变量,就必须使用export关键字导出该变量。下面是一个
-
import命令用于导入其它模块提供的功能
import命令接受一对大括号,里面指定要从其他模块导入的变量名。大括号里面的变量名,必须与被导入模块(profile.js)对外接口的名称相同
// main.js import { firstName, lastName, year } from "./profile.js"; function setName(element) { element.textContent = firstName + " " + lastName; } -
export default
- 为了给用户提供方便,就要用到
export default命令,为模块指定默认输出 - 本质上,
export default就是输出一个叫做default的变量或方法,然后系统允许你为它取任意名字 - 在
import命令后面,不再使用大括号 export default命令用于指定模块的默认输出。显然,一个模块只能有一个默认输出,因此export default命令只能使用一次
// export-default.js export default function () { console.log("foo"); } // import-default.js import customName from "./export-default"; customName(); // 'foo' - 为了给用户提供方便,就要用到
浏览器端加载实现
-
浏览器加载
ES6模块,也使用标签,但是要加入type="module"属性// 01.js export var a = 123;//demo.html <script type="module">import {a} from "./01.js"; console.log(a)//123</script> -
脚本异步加载
<script src="path/to/myModule.js" defer></script> <script src="path/to/myModule.js" async></script>解说:上面代码中,标签打开
defer或async属性,脚本就会异步加载。渲染引擎遇到这一行命令,就会开始下载外部脚本,但不会等它下载和执行,而是直接执行后面的命令。defer 与async的区别是:defer 要等到整个页面在内存中正常渲染结束(DOM 结构完全生成,以及其他脚本执行完成),才会执行;async一旦下载完,渲染引擎就会中断渲染,执行这个脚本以后,再继续渲染。一句话,defer 是“渲染完再执行”,async是“下载完就执行”。另外,如果有多个 defer 脚本,会按照它们在页面出现的顺序加载,而多个async脚本是不能保证加载顺序的。
本文深入解析ES6(ECMAScript 2015)的新特性,包括块级作用域、let与const变量声明、解构赋值、扩展运算符、模板字符串、箭头函数、Promise异步处理、类定义、Proxy与Reflect、Set与Map数据结构、Symbol类型、模块化语法及浏览器加载方式。通过示例代码详细说明各特性的应用场景与优势。
1万+

被折叠的 条评论
为什么被折叠?



