Decorator装饰器
针对属性 / 方法的装饰器
// decorator 外部可以包装一个函数,函数可以带参数
function Decorator (type) {
/**
* 这里是真正的decorator
* @description: 装饰的对象的描述对象
* @target:装饰的属性所述类的原型,不是实例后的类。如果装饰的是Animal的某个属性,这个target就是Animal.prototype
* @name 装饰的属性的key
*/
return function (target, name, desciptor) {
// 因为babel的缘故 通过value并不能获取值,以此可以获取实例化的时候此属性的默认值
let v = desciptor.initializer && desciptor.initializer.call(this)
// 返回一个新的描述对象,或者直接修改desciptor也可以
return {
enumerable: true, //可以遍历
configurable: true, //可以删除
get: function () {
return v
},
set: function (c) {
v = c
}
}
}
}
// 上面的不能和业界商用的Decorator混用
function Check (type) {
return function (target, name, desciptor) {
let v = desciptor.initializer && desciptor.initializer.call(this)
// 将属性名字以及需要的类型的对应关系记录到类的原型上
if (!target.constructor._checkers_) {
// 将这个隐藏属性定义成no enumerable,遍历的时候是取不到的
Object.defineProperty(target.constructor, '_checkers_', {
value: {},
enumerable: false,
writable: true,
configurable: true
})
}
target.constructor._checkers_[name] = {
type: type
}
return desciptor
}
}
// 装饰函数的第一个参数 target 是包装属性所属的类的原型(prototype)
// 也就是把对应关系挂载到了开发定义的子类上。
vue中使用Decorator
-
ts开发一定对vue-property-decorator不会感到陌生,这个插件提供了许多装饰器
-
在methods里面的方法上面使用装饰器,这时候装饰器的target对应的是methods。
-
可以在生命周期钩子函数上面使用装饰器,这时候target对应的是整个组件对象。
import {log,confirmation} from "./test"
methods: {
@log()
name() {
console.log("获取数据");
},
@confirmation('此操作将永久删除文件,是否继续?')
deleteFile(data){
//删除文件操作
}
},
mounted () {
this.name()
}
test.js
import {MessageBox}from "element-ui"
export function confirmation(message){
return function(target,name,descriptor){
let oldValue = descriptor.value
descriptor.value = function(...args){
MessageBox.confirm(message,'提示').then(oldValue.bind(this,...args)).catch(()=>{})
}
return descriptor
}
}
export function log(){
/**
* @description:
* @param {*} target 对应methods
* @param {*} name 对应属性方法的名称
* @param {*} descriptor 对应属性方法的修饰符
* @return {*}
*/
return function(target,name,descriptor){
console.log(target,name,descriptor);
// 获取实例化的时候此属性的默认值
const fn = descriptor.value
/* 重写 */
descriptor.value = function(...rest){
console.log(`调用${name}方法打印的`);
fn.call(this,...rest)
}
}
}
Iterator 迭代器
-
目的是为不同的数据结构提供统一的数据访问机制 主要为for of 服务的 (当for of执行的时候,循环过程中会自动调用这个对象上的迭代器方法,依次执行迭代器对象的next方法,并把next返回结果赋值给for of的变量,从而得到具体的值)
-
迭代器对象,返回此对象的方法叫做迭代器方法 此对象有一个next方法 每次调用next方法都会返回一个结果值
-
这个结果值是一个object 包含两个属性value和done
-
value表示具体的返回值 done是布尔类型的,表示集合是否遍历完成或者后续还有可用数据,没有可用返回true, 否则返回false
-
内部会维护一个指针 用来指向当前集合的位置 每调用一次next方法 指针都会向后移动一个位置(可以想象成数组的索引)
代码实现
getInterator(list){
var i = 0;
return {
next:function(){
var done = (i>=list.length);
var value = !done ? list[i++]:undefined
return {
done:done,
value:value
}
}
}
}
var it = this.getInterator(['a','b','c'])
console.log(it.next());// {done: false, value: 'a'}
console.log(it.next());//{done: false, value: 'b'}
console.log(it.next());//{done: false, value: 'c'}
console.log(it.next());//{done: true, value: undefined}
可迭代对象
-
Symbol.Iterator是一个表达式 返回Symbol的Iterator属性, 这是一个预定好的类型为Symbol的特殊值
-
ES6规定,只要在对象上部署了Iterator接口,具体实现为给对象添加Symbol.Iterator属性,此属性指向一个迭代器方法,这个迭代器会返回一个迭代器对象。
-
而部署了这个属性,并且实现迭代器方法 返回的对象就是迭代器对象,此时这个对象就是可迭代的 可以被for for遍历
实现一个可迭代对象
getIterator(){ let iteratorObj = { items:[100,200,300], [Symbol.iterator]: function(){ var self = this var i =0 return { next:function(){ var done = (i>=self.items.length) var value = !done?self.items[i++]:undefined return { done:done, value:value } } } } } for(var item of iteratorObj){ console.log(item); //100 200 300 } } //上面的对象就是可迭代对象,可以被for of遍历 this.getIterator()
for of 中断
如果for of 循环提前退出,则会自动调用return方法,需要注意的是return 方法必须有返回值,且返回值必须是一个object
var arr = [100, 200, 300] arr[Symbol.iterator] = function () { var self = this var i = 0 return { next: function () { var done = i >= self.length var value = !done ? self[i++] : undefined return { done: done, value: value } }, return (){ console.log('提前退出'); return { //必须返回一个对象 done:true } } } } for (var o of arr) { if(o == 200){ break; } console.log(o) // 100 提前退出 }
除了for of 会调用对象的Iterator, 结构赋值也会
var str = '123' let [a,b]=str console.log(a,b); // 1 2 let map = new Map() map.set('q','1') map.set('a','2') map.set('b','3') let [c,d] = map console.log(c,d); //['q', '1'] ['a', '2']
因为普通对象不是可迭代对象。
自定义的可迭代对象进行解构赋值
var interatorObj = { items: ['橙', '红', '白'], [Symbol.iterator]: function () { let i = 0 let self = this return { next: function () { let done = i >= self.items.length let value = !done ? self.items[i++] : undefined return { done: done, value: value } } } } } let [a,b] = interatorObj console.log(a,b); //橙 红
解构赋值的变量的值就是迭代器对象next方法的返回值 且是按顺序返回
扩展运算符
// 扩展运算符的执行(…)也会默认调用它的Symbol.iterator方法,可以将当前迭代对象转换为数组
*/\* 字符串 \*/* var str = "1234" console.log([...str]);*//【1,2,3,4】转换成数组* */\* map对象\*/* var map = new Map([[1,2],[3,4]]) [...map]*//[[1,2],[3,4]]* */\* set对象 \*/* var set = new Set([1,2,3]) [...set] *//[1,2,3]*
使用普通对象是不可以转换为数组的
var obj = {name:'zhang'} [...obj] *//报错*
作为数据源
作为一些数据的数据源 比如某些参数是数组的API方法,都会默认的调用自身的迭代器
例如 map set Array.from等
为了证明,先把一个数组的默认迭代器给覆盖掉
var arr= [100,200,300]
arr[Symbol.iterator]=function(){
var self = this
var i = 0;
return {
next:function(){
var done = (i>=self.length)
var value = !done?self[i++]:undefined
return {
done:done,
value:value + '前端'
}
}
}
}
用for of
for(var o of arr){
console.log(o);//100前端 200前端 300前端
}
生成set对象
var set = new Set(arr)
set*//Set(3) {'100前端', '200前端', '300前端'}*
调用Array.from方法
Array.from(arr) *//['100前端', '200前端', '300前端']*
*yield**关键字
*yield**后面跟的是一个可遍历的结构,执行时也会调用迭代器函数
let foo = function*(){
*yield* 1;
*yield** [1,2,3];
*yield* 5;
}
判断对象是否可迭代
既然可迭代对象的规则必须在对象上部署Symbol.iterator属性,那么就可以依次来判断是否为可迭代对象,然后就知道是否能使用for of 取值了
function isIterable(*object*){
*return* typeof *object*[Symbol.iterator] === 'function'
}
console.log(isIterable('asdd'));*//true*
console.log(isIterable([1,2,3]));*//true*
console.log(isIterable(new Map()));*//true*
console.log(isIterable(new Set()));*//true*
console.log(isIterable(new WeakMap()));*//false*
console.log(isIterable(new WeakSet()));*//false
Generate生成器*
let obj = {
*[Symbol.iterator]("Symbol.iterator"){
*yield* 'hello';
*yield* 'world';
}
}
*for*(let x of obj){
console.log(x);
} *//hello world
Generator
Generator函数是ES6提供的一种异步编程解决方案,语法行为与传统函数完全不同
Generator函数将javascript异步编程带入了一个全新的阶段
声明
与函数声明类似,不同的是function关键字与函数名之间有一个星号,以及函数体内部使用yield表达式,定义不同的内部状态(yield在英语里的意思就是产出)
function* foo(){
yield 'result1'
yield 'result2'
yield 'result3'
}
const gen = foo()
console.log(gen.next().value);
console.log(gen.next().value);
console.log(gen.next().value);
vue中使用
textMy () {
const gen = this.foo()
console.log(gen.next().value);
console.log(gen.next().value);
console.log(gen.next().value);
},
* foo(){
yield 'result1'
yield 'result2'
yield 'result3'
}
执行Generator会返回一个对象,而不是像普通函数返回return 后面的值
调用指针next方法,会从函数的头部或上一次停下来的位置开始执行,直到遇到下一个yield表达式或者return语句暂停,也就是执行yeild这一行
value就是执行yield后面的值 done表示函数是否执行完毕
console.log(g.next()); *//{value: 7, done: false}*
console.log(g1.next()); *//{value: 2, done: false}*
console.log(g.next());*//{value: undefined, done: true}
因为最后一行 return y 被执行完成 所以done为true
调用Generator函数后,该函数并不执行,返回的也不是函数运行结果 而是一个指向内部状态的指针对象,也就是遍历器对象(Iterator Object).必须调用遍历器对象的next方法
使得指针移向下一个状态
function* fetch(){
*yield* ajax('aaa')
*yield* ajax('bbb')
*yield* ajax('ccc')
}
let gen = fetch()
let res1 = gen.next() *//{value:'aaa',done:false}*
let res2 = gen.next() *//{value:'bbb',done:false}*
let res3 = gen.next() *//{value:'ccc',done:false}*
let res4 = gen.next() *//{value:undefined,done:true} //done为true表示执行结果
由于Generator函数返回的是遍历器对象 只有调用next方法才会遍历下一个内部状态 所以其实提供了一种可以暂停执行的函数。yield表达式就是暂停标志
遍历器对象的next方法运行逻辑:
1 遇到yield表达式,就暂停执行后面的操作,并将紧跟在yield后面的表达式的值,作为返回对象的value属性值
2. 如果下次调用next方法时,再继续往下执行,直到遇到下一个yield表达式
3 如果没有再遇到新的yiled表达式 就一直运行到函数结束
4 如果该函数没有return 语句 则返回对象的value属性值就是underfined
yiled表达式本身没有返回值 或者说总返回underfined next 方法可以带一个参数,该参数就会被当做上一个yield表达式的返回值
一个函数可以有多个yield,但是只能有一个return
Proxy
- Proxy用于创建对象的代理,用于监听对象的相关操作。代理对象可以监听我们对原对象的操作
- 在实例化Proxy对象时,第二个参数传入的是捕获器集合,我们在其对象内定义一个get捕获器,用于监听获取对象值的操作
- objProxy 对象的拦截器中set捕获器,用于监听对象的某个属性被设置时触发
// 定义一个普通的对象
const obj = {
name:'_isLand'
}
// 代理obj这个对象 并传入get捕获器
const objProxy = new Proxy(obj,{
// get捕获器
get:function(target,key){
console.log(`触发对象捕获${key}`);
return target[key]
},
set:function(target,key,val){
console.log(`捕获到对象设置${key},新值为${val}`);
return target[key] = val
}
})
objProxy.name = 'liming'
// 通过代理对象操作obj对象
console.log(objProxy.name);//触发对象捕获name _isLand
// 捕获到对象获取name属性值的操作
如果不想这个属性被设定成这个值,你可以抛出异常告诉开发者,这个值不能设定
set:function(target,key,val){
if(key==='age' && typeof val ==='number'){
return target[key] = val
}else{
throw new TypeError('该属性的值必须是number类型')
}
}
监听对象是否调用了getPrototypeof操作,使用getPrototypeOf捕获器
getPrototypeOf:function(){
console.log('监听到对象的protptypeOf操作');
}
Proxy一共有十三个捕获器,用于我们对 对象或者函数的方法调用的监听
this指向的问题
Proxy对象可以对我们的目标对象进行访问,但没有做任何拦截时,也不能保证与目标行为一致,因为目标对象 内部的this会自动改变为Proxy代理对象
let obj = {
name:'lili',
foo:function(){
return this === objProxy
}
}
let objProxy = new Proxy(obj,{})
console.log(obj.foo()); //false
console.log(objProxy.foo()); //true
对象监听
在vue2中的watchApi是使用的Es5的Object.defineProperty(对象属性描述符)对对象监听,将一个对象进行遍历,
并设定setter。getter方法进行监听和拦截
const obj = {
name: 'liki',
age: 12
}
Object.keys(obj).forEach(key => {
let val = obj[key]
Object.defineProperty(obj, key, {
get: function () {
console.log('触发get')
return val
},
set: function (newVal) {
console.log('触发set')
val = newVal
}
})
})
obj.age = 100
console.log(obj.name);
- Object.defineProperty的设计初衷并不是为了去拦截拦截一个对象中的属性,且他也实现不了更加丰富的操作,例如删除、添加属性等操作
- 所以在Es6中新增了Proxy,对象,用于监听Object,Function的操作。
- 在Vue3框架中的响应式原理也是用到了Proxy对象进行对属性的监听操作
proxy是一个代理对象 他可以代理我们对原目标的操作。相比Object.defineProperty方法 Proxy监听的事件更加方便
Reflect隐射对象
Reflect是一个对象,翻译过来就是反射的意思,它提供了很多操作Javascript对象的方法,是为弥补Object中对象的一些缺陷。且所有的属性和方法都是静态的
最初,js的一些内部方法都是放在Object这个对象上的,比如getPrototye,defineProperty等API,
in、delete操作符都放在了Object对象上。但是Object作为一个构造函数,这些方法都放在Object上并不合适,
所以ES6之后的内部新方法都在Reflect对象中。Refelect不是构造函数
使用Reflect对象操作Object对象
Reflect对象让我们操作Object对象不再是通过点语法 而是变成了函数行为
所以 获取对象属性可以使用Reflect.get方法,将对象的属性赋值使用Reflect.set方法
const obj = {
name:'lili',
age:12
}
/* 获取对象的属性值 */
console.log(obj.name);
console.log(Reflect.get(obj,'name'));
// 对对象的属性赋值
obj.name = 'lala'
Reflect.set(obj,'name','lala')
console.log(Reflect.get(obj,'name'));
// 判断一个对象中是否有该属性
console.log('name' in obj);
console.log(Reflect.has(obj,'name'));
Reflect中的方法
在返回值方面Reflect对象中的方法设计的更加合理 比如defineProperty方法,如果没有将属性设置
成功,在Reflect中会返回boolean值,而Object对象中如果没有定义成功则会抛出TypeError
Reflect搭配Proxy
Reflect对象中的方法和Proxy对象中的方法对应的,Proxy对象中的方法也能在Reflect对象中调用
通常我们将Reflect对象搭配Proxy一起使用
在下面Proxy对象中get,set捕获器多了一个recerive参数 这是这两个捕获器特有的 代表的是当前的代理对象
当Proxy和Reflect搭配使用时 Proxy对象会拦截对应的操作 后者完成对应的操作,如果传入receiver,那么Reflect.get属性
会触发Proxy.definProperty捕获器。如果没有传入receive参数 则不会触发defineProperty捕获器
let obj = {
name:'lili'
}
const objProxy = new Proxy(obj,{
get:function(target,key,receiver){
return Reflect.get(target,key,receiver)
},
set:function(target,key,val,receiver){
return Reflect.set(target,key,val,receiver)
},
defineProperty:function(target,key,attr){
console.log('defineProperty',target,key,attr); //defineProperty {name: 'lili'} name {value: 'ppp'}
return Reflect.defineProperty(target,key,attr)
}
})
objProxy.name = 'ppp'
console.log(objProxy.name);
//传入在我们获取代理对象中的name属性时,当Reflect有receive
总结
- Reflect 对象中集合了javascript内部方法
- 操作Object对象的方式变成了函数行为
- Reflect 对象中的方法返回结果更加合理
ES6中的class
在ES6之前,js语法是不支持类的,导致面向对象编程无法直接使用,而是通过function来实现模拟类,随着js的更新,ES6中出现了Class,
用于定义类
class Animal {}
const Animal = class {}
类的构造函数
每一个类都可以有一个自己的构造函数,这个名称是固定的construtor,当我们通过new 调用一个类时,这个类就会调用自己的constructor方法(构造函数)
- 它用于创建对象时给类传递一些参数
- 每一个类只能有一个构造函数
- 通过 new 调用一个类时 会调用构造函数 执行如下操作
1 在内存中开辟一块新的空间用于创建新的对象
2 这个对象内部的_proto_属性会被赋值为该类的prototype属性
3 构造函数内部的this,指向创建出来的新对象
4 执行构造函数内部的代码
5 如果函数没有返回值 则返回this
class Animal {
constructor(name){
this.name = name
}
}
var a = new Animal("ABC")
console.log(a); //Animal {name: 'ABC'}
class 中定义的constructor,这个就是构造方法
而this代表的是实例对象
这个class 可以看作是构造函数的另一种写法 因为它和它的构造函数的相等的,即是 类本身指向构造函数
console.log(Animal === Animal.prototype.constructor); *//true*
所以其实类上的所有方法都会放到prototype属性上
类中的属性
实例属性
实例的属性必须定义在类的方法中
class Animal {
constructor(name,height,weight){
this.name = name
this.height = height
this.weight = weight
}
}
静态属性
当我们把一个属性赋值给类本身,而不是赋值给它的prototype,这样子的属性被称之为静态属性(static)
静态属性直接通过类来访问 无需在实例中访问
class Foo{
static name = "liLI"
}
console.log(Foo.name);
私有属性
私有属性只能在类中读取,写入,不能通过外部引用私有字段
class Person{
#age;
constructor(age,name){
this.#age = age
this.name = name
}
}
var a = new Person(17,'lili')
console.log(a); //Person {name: 'lili'}
console.log(a.name);//lili
console.log(a.age);//undefined
console.log(a.#age);// Private field '#age' must be declared in an enclosing class
console.log(Object.getOwnPropertyDescriptors(a));
通过getOwnPropertyDescriptors获取属性同样获取不到
{
name: {
value: '_island',
writable: true,
enumerable: true,
configurable: true
}
}
在ES6之前,我们定义类中的方法是类中的原型上定义的 防止类中的方法重复在多个对象中
function Animal{}
Animal.prototype.eating = function(){
console.log(this.name + 'eating');
}
在Es6中定义类中的方法更简洁 直接在类中定义即可 这样的写法优雅可读性也强
class Animal{
eating(){
console.log(this.name + 'eating');
}
}
静态方法
静态方法是与静态属性是一样的 在方法前面加上stati关键字声明,之后调用这个方法时不需要通过类的实例来调用,可以直接通过类名来调用它
class Animal{
static creatName(*name*){
*return* *name*
}
}
var a2 = Animal.creatName('lll')
console.log(a2); *//lll
私有方法
在面向对象中 私有方法是一个常见需求 ES6中没有提供,可以通过某个方法实现它
class Foo{
_getBoodType(){
*return* '0'
}
}
需要注意的是,通过下划线开头通常我们会局限它是一个私有方法 但是在类的外部还是可以正常调用到这个方法的
类的继承
extends关键字用于扩展子类,创建一个类作为另一个类的子类
它会将父类中的属性和方法一起继承到子类的,减少子类中重复的业务代码
这比之前在ES5中修改原型链实现继承的方法可读性要强的多,而且写法更简洁
extends的使用
class Animal{}
dog继承Animal类
class dog extends Animal{}
继承类的属性和方法
- 定义一个dog类 通过extends关键字继承Animal类的属性和方法
- 在子类的construtor方法中 使用super关键字,在子类中它上市必须存在的 否则新建实例会抛出异常。
- 这是因为子类的this对象是继承父类的this对象 如果不调用super方法 子类就得不到this对象
class Animal {
constructor (*name*) {
this.name = *name*
}
eating () {
console.log(this.name + 'eating')
}
}
class dog extends Animal{
constructor(*name*,*legs*){
super(*name*)
this.legs = *legs*
}
speaking(){
console.log(this.name + "speaking");
}
}
var wang = new dog('tom',4)
wang.speaking()
wang.eating()
console.log(wang.name);
Super
super关键字用于访问调用一个对象的父对象上的函数
super指的是超级,顶级,父类的意思
在子类的构造函数中使用this或者返回默认对象之前,必须先使用super调用父类的构造函数
子类的construtor方法先调用了super方法,它代表了父类的构造函数,也就是我们把参数传递进去之后,其实它调用了父类的构造函数
class Animal{
constructor(*name*)
}
class dog{
constructor(*name*,*type*,*weight*){}
super(*name*)
this.type = type
this.weight = weight
}
使用super调用父类的方法
class Animal{
constructor(*name*){
this.name = name
}
eating(){
console.log(this.name + 'eating');
}
}
*// dog继承Animal类*
class dog extends Animal{
constructor(*name*,*lengs*){
super(*name*)
this.lengs = *lengs*
}
speaking(){
super.eating()
console.log(this.name + 'speaking');
}
}
var a = new dog('旺财',4)
a.speaking() *//旺财eating 旺财speaking
Getter和Setter
在类内部可以使用get和set关键字,对应某个属性设置存值和取值函数,拦截属性的存取行为
class Animal{
constructor(){
this._age = 3
}
get age(){
console.log('get');
*return* this._age
}
set age(*val*){
console.log('set');
this._age = val
}
}
var a = new Animal()
console.log(a.age);
a.age = 4
console.log(a.age);
关于class扩展
严格模式
在类和模块的内部 默认是严格模式 所以不需要使用use strict指定运行模式。只要你的代码写在类或模块之中,就只有严格模式可用
name属性
Es6中的类只是Es5构造函数的一层包装,所以函数的许多属性都被class继承了,包括name属性
class Animal{}
console.log(Animal.name);
变量提升
class不存在变量提升,这与Es5中实现类是不同的, function关键字会存在变量提升
new Foo() * ReferenceError*
class Foo{}
在Es6之后,在定义类以及它内部的属性方法,还有继承操作的语法变得很简洁易懂
class 是一个语法糖 其内部还是通过Es5中的语法实现的。且有些浏览器不支持class语法 我们可以通过babel来进行转换