坚持周总结系列第十一周(ES6总结)

本文深入解析ES6(ECMAScript 2015)的新特性,包括块级作用域、let与const变量声明、解构赋值、扩展运算符、模板字符串、箭头函数、Promise异步处理、类定义、Proxy与Reflect、Set与Map数据结构、Symbol类型、模块化语法及浏览器加载方式。通过示例代码详细说明各特性的应用场景与优势。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

ES6总结

摘要

新的标准规范

ECMAScript2015js的一种新的标准规范,就是对js的写法上提出了新的语法要求和写法格式。

ECMAScriptjs的关系

ECMAScriptJavaScript 的关系是,前者是后者的规格,后者是前者的一种实现。javascriptnetscape创 造的并交给了国际标准化组织 ECMA,之所以不叫做 JavaScript 由于商标的问题,java 是 sun 公司的商标,根据 授权协议只有 Netscape 公司可以合法使用 JavaScript 这个名字,另外就是为了体现 JavaScript 的标准的制定者 不是 ECMA 所以取名为 ECMAScript

ES6ECMAScript2015的关系

ES6ECMA的为 JavaScript 制定的第 6 个版本的标准,标准委员会最终决定,标准在每年的 6 月份正式发布一 次,作为当年的正式版本。ECMAscript2015是在 2015 年 6 月份发布的 ES6 的第一个版本。依次类推 ECMAscript 2016ES6 的第二个版本、 ECMAscript 2017ES6的第三个版本……

块级作用域

块级作用域的种类

ECMAscript2015js提出第三个作用域,凡是带{}的都是一个块级作用域。

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])==56

      var 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)// ture
    
    • startWith

    返回布尔值,表示参数字符串是否在原字符串的头部

    var st='lagou web'
    var b=st.startWith('la')
    console.log(b)// true
    
    • endWith

    返回布尔值,表示参数字符串是否在原字符串的尾部

    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 objdelete obj[name] ,而Reflect.has(obj.name)Reflect.deleteProperty(obj,name)让它们变成了函数行为。

静态方法

  • Reflect.get

    • Reflect.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.set

    • Reflect.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.has

    • Reflect.has(obj,name)Reflect.has方法对应name in obj里面的in运算符
    • 如果第一个参数不是对象,会报错
  • Reflect.deleteProperty

    • Reflect.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 一样,在“类”的内部可以使用getset关键字,对某个属性设置存值函数和取值函数,拦截该属性的存取行为。
  • 属性表达式

    • 类的属性名,可以采用表达式
    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; // undefined
    

    Set

基本用法

  • 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 语言的第七种数据类型,前六种是:undefinednull、布尔值(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...infor...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 又添加了MapSet。这样就有了四种数据集合,用户还可以组合使用它们,定义自己的数据结构,比如数组的成员是MapMap的成员是对象。这样就需要一种统一的接口机制,来处理所有不同的数据结构。

    遍历器(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 };
    
  • 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>
    

    解说:上面代码中,标签打开deferasync属性,脚本就会异步加载。渲染引擎遇到这一行命令,就会开始下载外部脚本,但不会等它下载和执行,而是直接执行后面的命令。defer 与 async 的区别是:defer 要等到整个页面在内存中正常渲染结束(DOM 结构完全生成,以及其他脚本执行完成),才会执行;async 一旦下载完,渲染引擎就会中断渲染,执行这个脚本以后,再继续渲染。一句话,defer 是“渲染完再执行”,async 是“下载完就执行”。另外,如果有多个 defer 脚本,会按照它们在页面出现的顺序加载,而多个 async脚本是不能保证加载顺序的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值