ECMAScript新特性

模板字符串

// 可支持换行
const msg = `
              hey
              boy
            `

// 支持插值表达式
const name = 'zsl'
const msg2 = `hey ${name} -- ${1 + 1} --- ${Math.random()}`
console.log(msg2) // hey zsl -- 2 --- 0.7603014058103252


// 可以在定义模板字符串之前添加一个标签
// 标签 实际上是一个特殊的函数
const str = console.log`hello world`  // 打印了一个数组 [ 'hello world' ]

const name = 'zsl'
const gender = false
// 可以接收模板字符串当中表达式的返回值
// 作用是 可以对模板字符串进行加工
function myTagFunc(strings, name, gender) {
    console.log(strings) // [ 'hey, ', ' is a ', '.' ]
    console.log(name, gender)
    const sex = gender ? 'man' : 'woman'
    return strings[0] + name + strings[1] + sex + strings[2]
}
const result = myTagFunc`hey, ${name} is a ${gender}.`
console.log(result) // hey, zsl is a woman.

字符串的拓展方法:incluedes()、startWith()、endWith()

/*
    incluedes()
    startWith()
    endWith()
    更方便判断字符串当中是否包含指定的内容
*/

const message = 'Error: foo is not defined.'

console.log(message.startsWith('Error'))  // 判断是否以Error开头
console.log(message.endsWith('.'))  // 判断是否以.结尾
console.log(message.includes('foo'))  // 判断是否含有foo

参数默认值

以往设置默认值都会使用 短路运算符,如下:

function foo(enable) {
    // 以往设置默认值都会使用 短路运算符
    // enable = enable || true  // 但其实这种写法是不严谨的 假设传入的参数是false enable也会变成true
    // 正确的 应该是判断参数是否存在
    // 参数默认值得定义是在没有传递实际参数时所使用得值
    enable = enable === undefined ? true : enable

    console.log(enable)
}
// es6 参数默认值
/*
    可以在形参后面直接用 = 设置默认值了
    假设有多个参数时, 带有默认值得形参一定要在参数列表得最后
*/

function foo(enable = true) {
    console.log(enable)
}

foo(false)

剩余参数

对于未知个数的参数, 以前都是使用arguments接收

function foo() {
    /*
        对于未知个数的参数, 以前都是使用arguments接收
        arguments 是伪数组
    */
   console.log(arguments) // [Arguments] { '0': 1, '1': 2, '2': 3, '3': 4 }
}

foo(1, 2, 3, 4)
// es6新增了一个 ... 的操作符
function foo(first,...args) {
    /*
        ...操作符有两个作用
        这里需要用到的是rest作用 即剩余操作符
        形参会以数组的形式去接收当前参数的位置开始往后所有的实参
        这种操作符只能出现在形参的最后一位, 而且只能使用一次
    */
   console.log(first) // 1
   console.log(args) // [2, 3, 4]
}


foo(1, 2, 3, 4)

展开操作符

...操作符除了rest剩余的用法,还有spread用法 展开

const arr = ['foo', 'bar', 'baz']

// 以前的写法
console.log(arr[0],arr[1],arr[2])


//... 自动会展开arr,按照次序传递到参数列表中
console.log(...arr)

箭头函数

箭头函数的出现 简化了传统函数

// 传统
function inc(number) {
    return number + 1
}
// 箭头函数
// const inc = n => n + 1 
const inc = (n, m) => {
    return n + 1
}

console.log(inc(100))


const arr = [1, 2, 3, 4, 5]
// arr.filter(function(item) {
//     return item % 2
// })
arr.filter(item => item % 2)

箭头函数this指向问题

箭头函数中的this引用的是距离最近的作用域中的this,从this的所在处向外层层寻找,直到有this的定义.

const person = {
    name: 'tom',
    sayHi1: function() {
        console.log(`hi, my name is ${this.name}`)  // hi, my name is tom
    },

    sayHi2: () => {
        console.log(`hi, my name is ${this.name}`)  // hi, my name is undefined
    },

    sayHiAsync1: function() {
        setTimeout(function() {
            console.log(this.name)  // undefined
        },1000)
    },

    sayHiAsync2: function() {
        setTimeout(() => {
            console.log(this.name)  // tom
        },1000)
    }
}

person.sayHi1()
person.sayHi2()
person.sayHiAsync1()
person.sayHiAsync2()

扫描二维码关注公众号,回复: 14698962 查看本文章

对象字面量

const bar = '345'
// 传统对象
const obj = {
    foo : '123',
    bar: bar
}

// 传统为对象添加普通方法
const obj = {
    foo: '134',
    method: function() {
        console.log('method')
    }
}


// 对象字面量
// 如果变量名 与 添加到到对象中的属性名一致 则可以省略冒号和 变量名
const obj = {
    foo: '123',
    bar
}

// 可以省略调冒号和function
const obj = {
    foo: '134',
    bar,
    mehods1() {
        console.log('medjfslk')
        console.log(this)  // 指向当前对象
    }
}


/*
    还可以使用表达式的返回值作为对象的属性名
    直接使用[] 可以使用动态的值了  ---- 叫计算属性名
*/
const obj = {
    foo: '134',
    bar,
    mehods1() {
        console.log('medjfslk')
        console.log(this)  // 指向当前对象
    },
    [Math.random()]: 123,
    [bar]: 345,
    [1 + 1]: 2
}
obj.mehods1()
console.log(obj)

对象扩展方法Object.assign、Object.js

Object.assign:将多个源对象中的属性复制到一个目标对象中,如果对象中有相同得属性, 那么源对象得属性会覆盖掉目标对象中的属性

const srouce1 = {
    a: 123,
    b: 123
}

const source2 = {
    b: 789,
    d: 789
}

const target = {
    a: 456,
    c: 456 
}

// 第一个参数是目标对象 assign的返回值也就是这个目标对象
const result = Object.assign(target, srouce1, source2)
console.log(target)
console.log(result === target)  // true
// 这种写法 会污染变量
function func(obj) {
    obj.name = 'func obj'

    console.log(obj)  // { name: 'func obj' }
}

const obj = {name: 'global obj'}
func(obj)
console.log(obj)  // { name: 'func obj' }


// 使用 Object.assign({}, obj) 写法不会污染变量赋值
function func(obj) {
    const funcObj = Object.assign({}, obj)
    funcObj.name = 'func Obj'
    console.log(funcObj)  // { name: 'func Obj' }
}
const obj = {
    name: 'global obj'
}
func(obj)
console.log(obj)  // { name: 'global obj' }

Object.is:用来判断两个值是否相等

/*
    判断两个值是否相等
    可以使用两个== 的相等运算符 或者是三个=== 严格相等运算符
    == 运算符会在比较之前自动转化数据类型
    === 会严格比较两个数值是否相同

    严格相等运算符也有两个特殊情况
    对于数字0 他的正负是没有办法区分的
    对于NaN 两个NaN在三等比较时是不相等的, 但从今天来看, NaN 应该是相等的
    以上两个特殊情况 可以通过Object.is方法处理

    但一般情况下比较少用Object.is方法 大多数还是使用 严格相等运算符
*/

console.log(
    // 0 == false  // true
    // 0 === false  // false
    // +0 === -0  // true
    // NaN === NaN  // false
    // Object.is(+0, -0)  // false
    Object.is(NaN, NaN)  // true
)  

Proxy代理对象

如果想要监视某个对象中的属性读写 可以使用es5所提供的Object.defineProperty 方法,可以捕获到对象当中属性的读写过程

在ES2015中 全新设计了一个叫Proxy的类型,专门为对象设置访问代理器的,可以轻松的监视到对象的读写过程,对比defineProperty, Proxy的功能更为强大 ,使用更方便

// Proxy 对象
const person = {
    name: 'zsl',
    age: 25
}

// 给person 创建一个代理对象
/*
    Proxy构造函数的第一个参数是 需要代理的目标对象

    第二个参数 也是一个对象(称为代理的处理对象)
        get- 可以通过get方法去监视属性的访问
        set- 通过set方法去监视对象的设置属性的过程
*/

const personProxy = new Proxy(person, {
    // 第一个参数是代理的目标对象 第二个参数是外部所访问的属性名
    get(target, property) {
        // console.log(target, property)
        // 这个方法的返回值将会作为外部去访问这个属性得到的结果
        // return 100

        // 正常的逻辑 会先判断代理的目标对象中是否存在这个属性 如果存在则返回对应的值 反之可以返回一个默认值
        return property in target ? target[property] : 'defalut'
    },
    // 第一个参数是代理目标对象 第二个是写入的属性名称 第三个是写入的属性值
    set(target, property, value) {
        // console.log(target, property, value)
        // 可以做一些数据校验
        if(property === 'age') {
            if(!Number.isInteger(value)) 
            throw new TypeError(`${value} is not a int`)
        }
        // 正常的逻辑 为代理的目标设定指定的属性
        target[property] = value
    }
})

console.log(personProxy.name)
console.log(personProxy.xxx)

personProxy.gender = true
personProxy.age = 'jjj'

Proxy与Object.defineProperty

defineProperty 只能监视对象属性的读取 和 写入,而Proxy可以比defineProperty监视到更多对象操作

const person = {
    name : 'zsl',
    age: 25
}

const personProxy = new Proxy(person, {
    // 第一个代理目标对象 第二个要删除的属性名称
    // 这个方法会在外部对当前这个代理对象进行delete的操作时 会自动执行
    deleteProperty(target, property) {
        console.log('delete', property)
        delete target[property]
    }
})

delete personProxy.age
console.log(person) // { name: 'zsl' }


/*
    proxy 有更好的支持数组对象的监视
    以往defineProperty对数组的监视操作最常见的方法是通过重写数组的操作方法
    大体的思路就是通过自定义的方法去覆盖掉数组原型对象上的push shit ...方法
    以此来劫持对应方法调用的过程

    如何使用Proxy对象 监视数组
*/

const list = []
const listProxy = new Proxy(list, {
    // 监视数据的写入
    set(target, property, value) {
        console.log('set', property, value) // set 0 100
        target[property] = value
        return true // 表示写入成功
    }
})
listProxy.push(100)
console.log(list)

/*
    Proxy是以非侵入的方式监管了对象的读写
    即一个已经定义好的对象 proxy不需要对对象本身去做任何的操作就可以监视到内部成员的读写

    而definePropety 就必须要通过特定的方式 单独去定义对象当中那些需要被监视的属性
*/

const people = {}

Object.defineProperty(people, 'name', {
    get() {
        return person._name
    },
    set(value) {
        return person._name = value
    }
})

Object.defineProperty(people, 'age', {
    get() {
        return person._age
    },
    set(value) {
        return person._age = value
    }
})

const people2 = {
    name: 'zsl',
    age: 25
}

const peopleProxy = new Proxy(people2,{
    get(target, property) {
        return property in target ? target[property] : 'default'
    },
    set(target, property, value) {
        target[property] = value
    }
})

Reflect

Reflect 是ES2015提供的一个全新的内置对象,是属于一个静态类, 即不能够通过new Reflect()的方式去构建一个实例对象,只能够调用静态类当中的一些静态方法 eg: Reflect.get()

Reflect 内部封装了一系列对对象的底层操作 具体是提供了常用的13中方法,这13种方法和 Proxy对象的处理对象里面的方法是完全一致的

 Reflect 成员方法就是Proxy处理对象的默认实现

const obj = {
    foo: '123',
    bar: '456'
}

const proxy = new Proxy(obj, {
    get(target, property) {
        return Reflect.get(target, property)
    }
})

console.log(proxy.foo) // 123

/*
    Reflect对象的意义
    统一提供一套用于操作对象的API
*/

const obj2 = {
    name : 'zsl',
    age: 18
}

console.log('name' in obj2)  // 判断对象上是否有该属性
console.log(delete obj2['age'])  // 删除对象上的属性
console.log(Object.keys(obj2))   // 获取对象上所有的属性名

// 使用Reflect 方式
console.log(Reflect.has(obj2, 'name'))  // 判断对象上是否有该属性
console.log(Reflect.deleteProperty(obj2, 'age'))  // 删除对象上的属性
console.log(Reflect.ownKeys(obj2)) // 获取对象上所有的属性名
console.log(obj2)

Promise

Promise 也是ES2015提供的一个内置对象,提供了一种更优的异步编程解决方案,通过链式调用的方式解决了在传统JavaScript异步编程当中回调函数嵌套过深的问题

Class 类

在此之前 ECMAScript中是通过定义函数以及函数的原型对象来去实现的类型

function Person(name) {
    this.name = name
}

// 如果想要实例之间共享成员 可以借助于函数对象的prototype去实现
Person.prototype.say = function() {
    console.log(`hi, my name is ${this.name}`)
}

ES2015 可以使用class的关键词来去声明一个类型

class Person {
    /*
        如果需要在构造函数中写入一些逻辑
        constructor相当于是这个类的构造函数
    */
    constructor(name) {
        this.name = name
    }

    say() {
        console.log(`hi, my name is ${this.name}`)
    }
}

const p = new Person('tom')
p.say()

类中静态成员static

在类型当中的方法一般分为 实例方法 和 静态方法

  • 实例方法是需要通过这个类构造的实例对象去调用

  • 而静态方法则是直接通过类型本身去调用

class Person {
    constructor(name) {
        this.name = name
    }

    say() {
        console.log(`hi, my name is ${this.name}`)
    }

    /*
        静态方式是挂载在类上的 
        所以在静态方法内部他的this不会指向某一个实例对象
        而是指向当前的类型
     */
    static create(name) {
        console.log(this, "pepeep")
        return new Person(name)
    }
}
const p = new Person('zsl')
const p1 = Person.create('lili') // 直接访问静态方法
const p2 = p.constructor.create('zsl') // 实例可以通过constructor访问静态方法
p1.say()
p2.say()
console.log(p1)
console.log(p2)

类的继承 extends

通过继承这种特性可以抽象出来相似类型之间重复的地方

在ES2015之前大多会使用原型的方式实现继承,在ES2015中实现了专门用于类型继承的关键词 extends

class Person {
    constructor(name) {
        this.name = name
    }

    say() {
        console.log(`hi, my name is ${this.name}`)
    }
}

// student 可以拥有Person里面的所有成员了
class Student extends Person {
    constructor(name, number) {
        // 因为name参数在父类当中也需要用到
        // super始终指向父类 调用它即调用了父类的构造函数
        super(name)
        this.number = number
    }

    hello() {
        // 调用父类的方法
        super.say()
        console.log(`my age is ${this.number}`)
    }
}

const s1 = new Student('zsl', 25)
s1.hello() // 打印出hi, my name is zsl 和 my age is 25

Set数据结构

Set内部的成员是不允许重复的,即每个值在同一个Set中都是唯一的

const s = new Set()
// 添加数据
// 如果添加了之前已经存在的值 那么这个值就会被忽略掉
s.add(1).add(2).add(3).add(2)
console.log(s)   // Set { 1, 2, 3 }

// 遍历集合
s.forEach(item => console.log(item))
for(let item of s) {
    console.log(item)
}

// 可以用size属性来获取整个集合的长度
console.log(s.size)  // 3

// has方法 用来判断集合当中是否存在某个特定的值
console.log(s.has(100))

// delete方法 删除集合中指定的值
console.log(s.delete(3))
console.log(s)

// clear方法 用来清除集合的全部
s.clear()
console.log(s)

// Set数据结构最常用于 为数组的元素去 去重
const arr = [1, 2, 3, 3, 4, 5, 2, 1]

// 去掉数组中重复的元素
const result = new Set(arr)

// 转成数组
// result = Array.from(new Set(arr))
result = [...new Set(arr)]
console.log(result)

Map数据结构

与对象非常相似 本质都是键值对的集合,但是对象的键只能是字符串类型,Map可以用任意类型的数据作为键

const obj = {}
obj[true] = 'value'  // 布尔值作为键
obj[123] = 'value'   // 数字作为键
obj[{a: 1}] = 'value'  // 一个对象作为键

console.log(Object.keys(obj))   // [ '123', 'true', '[object Object]' ]

/**
 * 假设用对象去存储每个学生的考试成绩, 如果用学生对象作为键
 * 那不管对象当中的属性有什么不同 , 那每个对象toString之后都是一样的 object Object
 * 这样就做不到区分
 */
console.log(obj[{}])
console.log(obj['[object Object]'])
// 那么ES2015 提出的 Map数据结构可以解决以上的问题
/*
    Map才能算得上是严格意义上的键值对集合 
    用来映射两个任意数据之间的对应关系
*/

const m = new Map()

const zsl = {name: 'zsl'}
// set方法 存储数据  键可以是任意类型的数据
m.set(zsl, 90)
console.log(m)  // Map { { name: 'zsl' } => 90 }
// get方法 获取数据
console.log(m.get(zsl))  // 90

// m.has(zsl)  // has方法 判断某个键是否存在
// m.delete() // delete方法 删除某个键
// m.clear()   // clear方法 清空所有键值

// 遍历Map所有的键值
m.forEach((value, key) => {
    console.log(value,"value")  // 90 value
    console.log(key, "key")  // { name: 'zsl' } key
})

/**
 * 总结
 * Map 可以用任意类型的数据作为键
 * 而对象实际上只能够字符串作为键
 * 
 */

Symbol一种全新的原始数据类型

在ECMAScript2015 之前 对象的属性名都是字符串,而字符串是有可能会重复的 如果重复的话就会产生冲突,Symbol 表示一个独一无二的值

/**
 * Symbol 表示一个独一无二的值
 */
const s = Symbol()
console.log(s)  // Symbol() 
console.log(typeof s)  // symbol  
// 通过Symbol创建的每一个值 都是独一无二的
console.log(Symbol() === Symbol())  // false

// 可以传入一个值作为整个描述文本 
console.log(Symbol('foo'))  // Symbol(foo)
console.log(Symbol('bar'))  // Symbol(bar)

/**
 * 自ES2015开始 
 * 对象的属性名可以是两种类型
 * 分别是string 和 symbol   
 */

const obj = {
    [Symbol()]: '123',
    [Symbol()]: '456'
}
console.log(obj)



// 可以使用Symbol 创建私有成员的属性名
// a.js ==============================================
// 假设a文件暴露 person 这个对象
const name = Symbol()
const person = {
    [name]: 'zsl',
    say() {
        console.log(this[name]) //可以拿到对应的属性成员
    }
}


// b.js ===============================================
// b文件想要获取成员
person.say()


/**
 * 总结: Symobl目前最主要的作用就是为对象添加独一无二的属性名(标识符)
 */
// Symbol 补充
/**
 * Symbol的唯一性
 * 每次通过Symbol创建的值 一定是一个唯一的值
 * 不管传入的描述文本是不是相同的 每次调用Symbol函数它得到的结果都是全新的 一个值
 */

console.log(Symbol() === Symbol()) // false
console.log(Symbol('foo') === Symbol('foo')) //false

/*
    如果想要全局去复用一个相同的Symbol值 可以使用全局变量的方式去实现
    或者是使用Symbol类型提供的静态方法实现 Symobol的for方法
*/

// 接收字符串为参数 相同的字符串就一定会返回相同的Symbol类型的值
const s1 = Symbol.for('foo')
const s2 = Symbol.for('foo')
console.log(s1 === s2)  // true

/**
 * 注意:Symobol的for方法内部维护的是字符串和Symbol之间的对应关系
 * 即如果传入的是不是字符串, 那这个方法内部会自动转换成字符串
 * 这样会导致传入布尔值的true 和 传入的字符串的"true" 结果拿到的都是一样的
 */
 console.log(
     Symbol.for(true) === Symbol.for("true")  // true
 )

/**
  * Symbol当中还提供了很多内置的Symbol常量, 用来作为内部方法的标识
  */
 console.log(Symbol.hasInstance)  // Symbol(Symbol.hasInstance)
 console.log(Symbol.iterator)   // Symbol(Symbol.iterator)


//  const obj2 = {}
//  console.log(obj2.toString())  // [object Object]  Object称之为toString的标签

// 如果想要自定义这个对象的toString标签
const obj2 = {
    [Symbol.toStringTag]: 'XObject'
}
console.log(obj2.toString())  // [object XObject]


/**
 * 如果Symbol作为对象的属性名
 */
const obj3 = {
    [Symbol()]: 'symbol value',
    foo: 'normal value'
}

// 通过传统的for in 循环是无法拿到的
for(let key in obj3) {
    console.log(key)  // foo
}

// 通过Object.keys 也获取不到Symbol类型的属性名
console.log(Object.keys(obj3))  // [ 'foo' ]

// 通过JSON.stringify 序列化一个对象为 Json字符串 Symbol属性也会被忽略掉
console.log(JSON.stringify(obj3))  // {"foo":"normal value"}

/*
    想要获取Symbol类型的属性名
    使用Object.getOwnPropertySymbols方法
    所用类似于Object.keys方法
    不同的是Object.keys方法它只能获取到对象中所有字符串的属性名
    而getOwnPropertySymbols方法获取到的全是Symbol类型的属性名

*/
console.log(Object.getOwnPropertySymbols(obj3))  // [ Symbol() ]

for...of循环

ES2015 引入了全新的for...of循环,这种循环方式以后会作为遍历所有数据结构的统一方式

const arr = [100, 200, 300, 400]
// for..of 循环拿到的是每个元素 而不是对应的下标
for(const item of arr) {
    console.log(item)
}

// 可以取代数组的 forEach方法
// arr.forEach(item => {
//     console.log(item)
// })

/**
 * 相比forEach方法 
 * for...of 可以使用break关键词随时去终止循环
 * 而forEach是无法终止便利的
 */
for(const item of arr) {
    console.log(item)
    if(item > 100) break
}

// arr.forEach(item => {
//     console.log(item)
//     if(item > 100) break
// })  // 会报错  不能跳出循环
/**
 * 一些伪数组 也是可以用for..of循环遍历的
 * 例如函数中的arguments对象
 * 或者是dom操作中一些元素节点的列表
 * 
 * ES2015中新增的Set对象 和 Map对象 也可以
 */

const s = new Set(['foo','bar'])
for(const item of s) {
    console.log(item)
}

const m = new Map()
m.set('foo', '123')
m.set('bar', '345')
// 遍历map结构的数据 每一项是一个数组
// for(const item of m) {
//     console.log(item)  // [ 'foo', '123' ]
// }

// 可以使用数组的解构 拿到键和值
for(const [key, value] of m) {
    console.log(key, value)
}

// 使用for..of 是无法遍历对象
// const obj = {foo: '123', bar: '456'}
// for(const item of obj) {
//     console.log.log(item)  // 报错
// }

猜你喜欢

转载自blog.csdn.net/weixin_45313351/article/details/124912729
今日推荐