前言: 在上一节,我们讲述了一下ECAMScript更新的一些基础内容,现在,接着上一节接着说一说在ES6中较为高阶的一些东西。
本节目标:
- 1、对于Object对象的新增内容
- 2、promise的见解
- 3、defindeproperty类和proxy类比较
- 3、新增数据类型Symbol
- 4、class类
- 5、新的数据结构Set和Map
- 6、for..of..的实现原理
- 7、迭代器
- 8、生成器
内容讲解:
- 对于Object对象的新增内容
assign方法
:将源对象合并到目标对象中
const obj = {
name: 'df'
age: 12
}
const objDog = {
type: 'dog'
}
const action = { say : 'www'}
const animal = Object.assign(objDog, obj, action) // 后面也可以跟多个参数,都会赋值到第一个目标对象中
console.log(animal) // {name: 'df',age: 12,type: 'dog',say: 'www'}
具体的应用场景是:
1、如果一个对象(o1)的属性引用了另一个对象(o2)的值,在修改o1的时候,也会修改o2的值,这里用到assign方法,将o2的值赋值给o1的话,两个就相互不影响了。
2、从上面这句话就可以看出,assign是对于对象,是进行浅拷贝。
- defindeproperty类和proxy类比较
proxy对象是对之前的defineProperty的一次强大升级,这个对象相当于对象的一个门卫,对于对象的读取操作以及delete都会做到监听和过滤。
const obj = {
name: 'df',
age: 18
}
//参数: t1 -- 目标对象 t2 -- {},方法的对象
const proxyObject = new Proxy(obj, {
get (targe, property) { // 对于读取对象属性进行监视
console.log(targe, property);
return property in obj ? obj[property] : 'error'
},
set (targe, property,val) { // 对于函数属性的写入进行监视
console.log(targe, property,val);
if (property === 'age') {
if(!Number.isInteger(val)) throw new TypeError(`${val} is not number`)
}
}
})
console.log(proxyObject.name); // { name: 'df', age: 18 } name df
proxyObject.age = 123 // { name: 'df', age: 18 } age 123
console.log(proxyObject.age);// 18
对于Object的proxy 和 defineProperty 的对比:
proxy中有对delete操作的监听,而defineProperty没有。
proxy更好的支持对数组对象的监听,对原数组方法进行重写的方式实现。(vue.js所使用的方式)。
proxy是以非侵入的方式监管了对象的读写,即对已经定义好的操作,不需要对对象本身进行过多的操作就可以监听到读写。
const list = []
const listProxy = new Proxy(list, {
set (targe, property, val) {
console.log(targe, property, val);
targe[property] = val // 这里会自动监听到数组的索引进行赋值
return true // 不写返回值会报错
}
})
listProxy.push(100) // [] 0 100 [ 100 ] length 1
- Reflect类的使用
Reflect是Object对象的一个静态类,里面有处理对象的操作的14个方法,这14个方法就是proxy对象的默认实现,即Proxy内部默认调用了Reflect对象的方法
Reflect存在的意义在于统一提供了一套操纵对象的API
const obj = {
name: 'df',
age: 18
}
console.log(Reflect.has(obj, 'age')); // 类似 'age' in obj
console.log(Reflect.defineProperty(obj, 'age')); // 类似 delete obj.age
console.log(Reflect.ownKeys(obj, 'age')); // 类似 Object.keys(obj)
// 之前的方法会被慢慢取代掉,建议使用Reflect的方法
- Promise
romise是ES6新增的链式调用所使用的类,多数用于异步请求,属于微任务类型,想了解更多,去看我的关于Promise的文章。
5、Class(类)
1、在之前的js中,我们会通过定义函数的方式进行构造函数的创建,在通过new 一个构造函数的类来实现一种独立的数据类型(Class)。
function Person (name) {
this.name = name
this.say = function (){
console.log('这是一个构造函数方法', this)
}
}
// 通过定义在原型链上的方式来进行类之间数据共享
const person = new Person('df')
person.say()
person.property.action = function () {console.log('原型链上的数据共享')}
现在,ES6中,定义了一个class关键字来定义类,通java、php这些面向对象语言一样中类的功能类似(强调,JavaScript是面向函数式编程)。
class Dog {
constructor (name,age) { // 这是构造器,对象一经创建就立即执行的类,里面的参数就是new对象时候传入的参数
this.name = name
this.age = age
}
say () { // 之前都是将方法放在原型上或者属性上,不能直接定义,现在可以了
console.log('这是一个calss创建的类', this)
}
}
2、通过静态方法来创建对象,静态方法只能函数自己来调用,它的实例对象不能调用,而实例方法只能通过它的实例对象来调用,它本身不能调用。实例方法都是定义在类原型上的,而通过关键字static修饰后,这个方法就变成了静态方法,只能通过类名去调用了。
class Dog {
constructor (name,age) { // 这是构造器,对象一经创建就立即执行的类,里面的参数就是new对象时候传入的参数
this.name = name
this.age = age
}
say () { // 之前都是将方法放在原型上或者属性上,不能直接定义,现在可以了
console.log('这是一个calss创建的类', this)
}
static action (name,age) {
console.log('这是Dog类的静态方法')
return new Dog(name, age)
}
}
const ha = Dog.action('哈士奇', 3)
console.log(ha); // 这是Dog类的静态方法 Dog { name: '哈士奇', age: 3 }
3、 静态方法中的this是是指向当前类型,而不是当前对象。
4、使用extends关键字来实现继承一个父类,通过super关键字来调用继承的父类的属性和方法
class Dog {
constructor (name,age) { // 这是构造器,对象一经创建就立即执行的类,里面的参数就是new对象时候传入的参数
this.name = name
this.age = age
}
say () { // 之前都是将方法放在原型上或者属性上,不能直接定义,现在可以了
console.log('这是一个calss创建的类', this)
}
static action (name,age) {
console.log('这是Dog类的静态方法')
return new Dog(name, age)
}
}
class MinDog extends Dog {
constructor (name,age,type) {
super(name,age) // super关键字是用来调用父类 的,这里相当于在调用父类的构造方法
this._type = type
}
hello () {
super.say()
console.log('上一句是调用父类super打印出来的');
}
}
const ha = new MinDog('哈士奇', 3, 'man')
ha.hello() // 这是一个calss创建的类 MinDog { name: '哈士奇', age: 3, _type: 'man' } // 上一句是调用父类super打印出来的
6、set map
set 数据结构
你可以将set结构看作是一个array的结构,但是区别是set的值都是唯一的值,不能重复,因此set经常应用的场景是数组去重。
set的方法:
1、add 因为该方法的返回值是一个set类型,所以可以链式调用。
2、size 相当于array 的length属性
3、delete 删除值
4、clear 清除set
5、has 类似于array的include
6、forEach 循环
const arr = new Set() // 参数可以放入数组array
// add 方法 返回 set,因此可以链式调用,且有重复的话会省略不加入
arr.add(1).add(2).add(2) // [1,2]
// set 不等同于arr 是一个伪数组结构,通过Array的from方法可以将其变成数组结构
console.log(Array.from(arr));
console.log(...arr); // 通过解构也可以
// size -> length
console.log(arr.size); // 2
// has 方法 检查set中是否包含 对应值
console.log(arr.has(i)); // true
// delete 删除
console.log(arr.delete(1)); // [2]
// clear 清空set
console.log(arr.clear); // []
map 数据结构
map是<key, value>结构的真正意义上的实现,这里的key是可以存储任意类型的数据,而在object对象中,对于key值,即使我们不是存放的String类型的数据,它也会自动给我们使用toString的方法。
const obj = {}
obj[true] = '123'
obj[1] = 1
obj[{a: 1}] = 'a = 1'
// 打结果都是使用过toString方法的键
console.log(Object.keys(obj)); // [ '1', 'true', '[object Object]' ]
map 的使用方法:
set 方法 存数据
get方法 获取方法
delete 方法 删除键值对
has 方法 判断是否存在
clear 方法 清楚map
forEeach方法 遍历map (value, key)
Symbol
Symbol数据类型,符号数据类型,它的每一次创建都是独一无二的,其参数是为其添加说明内容,每一个symbol都是独一无二 ,不相等的,常用的场景是为对象进行属性名的命名,那么现在对象就有String和Symbol两个格式的属性命名。
const s = Symbol()
console.log(s); // Symbol() -- 这是一个值,Symbol值
console.log(typeof s); // Symbol
console.log(Symbol() === Symbol()); // false
const obj = {}
obj[Symbol()] = 213
obj[Symbol('foo')] = 123
obj[Symbol(123)] = 321
console.log(obj); // { [Symbol()]: 213, [Symbol('foo')]: 123, [Symbol(123)]: 321 }
Symbold特点:
const s = Symbol()
// 特点一:每一个Symbol值都是独一无二的
console.log(Symbol() === Symbol() ); // false
// 特点二: 通过全局注册或者它的内置静态方法for可以拿到同一份Symbol()值
const s1 = Symbol()
console.log(s1 === s1); // true
// for方法是用来维护同一个字符串的参数的Symbol值,传入的参数如果不是String类型,也会在内部转化为String
const s2 = Symbol.for(123)
const s3 = Symbol.for('123')
console.log(s2 === s3); // true
// 特点三: Symbol中自带了很多自定义常量,用于实现对象的自定义属性标签
const obj= {}
console.log(obj.toString()); // [object Object]这个字符串就是属性标签
// 通过Symbol的自带常量可以为这些属性从新定义 (这块内容不太懂,后续再理解)
obj[Symbol.toStringTag] = 'XXX'
console.log(obj.toString()); // [object XXX]
// 特点四:通过Symbol定义的内置属性是无法通过 forEach、Object.keys、json.stringify 方式拿到值的 -- 这些都是获取到的字符串属性名
// 只有通过Object.getOwnPropertySymbol可以取到全部symbol值
for ... of ...结构
1、for 循环,使用于遍历数组,而且可以使用break中途退出
2、for ... in ... 和方法foerEach 循环键值对数组或对象,但是不能brake
3、for ... of ... ,ES6新增的循环方式,可以遍历所有的可循环数据,并且有brake中途退出,作为以后主要使用的方式,它可以遍历数组、对象、map、set、伪数组等数据结果。
// 数组
const arr = [1,2,3,4]
for( i of arr) {
console.log(i);
}
// set
const set1 = new Set(arr)
for( i of set1) {
console.log(i);
}
// map
const m = new Map()
m.set('name','df')
m.set('age',18)
for(const [i,key] of m) {
console.log(i,key);
}
// 对象
const obj = {
name: 'jxl',
age: 23
}
// 这里的循环不能执行,是因为Object没有实现Symbol.iterator的可迭代接口,详情看下面迭代器的介绍
迭代器和生成器
迭代器是什么,它是一种模式。
对于上面for...of...的方法可迭代的实现,主要是通过它原型对象上实现的Symbol.iterator接口来完成的。
// 这里我们创建一个数组
const arr = [1,2,3]
const t = arr[Symbol.iterator]() //调用iterator方法可以得到该方法的执行体,里面有一个next()方法,这个方法就是循环中指向下一个地址的指针
console.log(t.next()) // {value: '1', done: false} // 这里得到的对象就是每次循环的对象值(value)和是否完成循环(done)
这里为Object添加一个Symbol.iterator的方法,让其实现循环,要是没有Symbol.iterator方法,只要我们手动去实现该方法,就可以实现迭代器,继而进行遍历。
const obj ={
arr: ['df', 'foo', 'fn'],
// 这里其实也是利用了闭包的机制,进行循环处理的。iterator的方法可以根据结构自己进行循环
[Symbol.iterator]: function () {
const self = this // 因为下面是函数,this指向会变,所以临时保存
let index = 0 // 记录完成循环个数
return { // 返回一个迭代器对象
next: function () { // 返回一个可迭代函数
const result = { // 迭代结果对象
value: self.arr[index],
done: self.arr.length <= index
}
index++
return result
}
}
}
}
for (const i of obj) {
console.log(i,'循环体哈哈哈');
}
生成器 (Generation)
这里先说一个生成器函数,function * fn () {} 的格式,这种函数可以解决异步编程带来的代码嵌套的问题,其实,它内部就实现了Symbol.iterator方法的实现,对于自定义对象的Symbol.iterator方法,可以通过生成器去实现。
const obj = {
name: ['df', 'foo', 'fn'],
age: [12, 16, 18],
[Symbol.iterator]: function * () { // 这里通过 generator 函数进行 Symbol.iterator 的定义
const tol = [...this.age, ...this.name]
for ( const i of tol) {
yield i
}
}
}
for (const i of obj ) {
console.log(i,'generator');
}
结束
到这里,ES6的常用特性包括一些代码的实现就算是总结完了,在平常的工作或者学习中,要多使用才能尽快的掌握。