模板字符串
// 可支持换行
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()
对象字面量
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) // 报错
// }