# AOP切面编程
涉及知识点:
- 闭包(词法作用域产生是根据函数定义的位置,执行上下文是执行的时候观察的)
- 词法作用域和执行上下文的概念。
- this指向,箭头函数没有this,arguments和原型
- 扩展运算符和用
apply(this,arguments)
来实现 - call的用法,改变this指向,让函数执行
AOP切面编程的思想在前端很多地方使用频繁,需要对基础功非常扎实。
AOP(面向切面编程)的主要作用是把一些跟核心业务逻辑模块无关的功能抽离出来,其实就是给原函数增加一层,不用管原函数内部实现。
常见:1.vue的对数组实现响应式,用此法去完成数组方法拦截,形成变异方法。
2.在vueRouter中使用此法,进行登录路由拦截。
下面我们来了解下AOP切面编程思想,比如下面这个例子。
# 1】before:修改原型使用闭包实现
//我们想要实现对say方法执行console之前之后进行一些操作。
function say(){
//before---todo..
console.log('说话')
//after---todo..
}
此时我们就需要利用高阶函数,什么是高阶函数?高阶函数就是函数的参数是一个函数,或者返回值是一个函数。(回调函数是高阶函数的一种)
理一下思路,我们这里就可以修改方法原型,使用闭包来实现:
let newFn = say.before(function (){
console.log('说话前')
})
//先say.before方法执行say中console之前需要执行的东西,返回一个say方法
//形成这种闭包之后,再执行当前newFn()方法即可调用say方法
newFn()
完整的实现过程:
function say(who,sth){
console.log(who + '说话时' + sth)
}
Function.prototype.before=function (beforeFunc){
let that=this//也可以使用下面箭头函数,因为箭头函数没有this,没有arguments,没有原型
return function(){
beforeFunc()
that(...arguments)//如果是this的话,就需要看调用时的上下文,此时使用闭包,保留that变量在函数外也可以使用。
}
}
let newFn=say.before(function (){
console.log('说话前')
})
newFn('我','大笑');//如果是this的话,需要看调用时的上下文。
//打印出:
//说话前
//我说话时大笑
这里有一个知识点:...arguments
ES6中的拓展运算符是使、通过apply(this,arguments)
来实现的。
//箭头函数回调的时候不能使用arguments,需要如下使用:
let fun =(...args)=>{//args数组展开形成括号里面的所有参数
console.log(args)//[ 1, 2, 3, 4 ]
console.log(...args)//1 2 3 4
}
fun(1,2,3,4)
注意:this只有在执行的时候才能确定,与词法作用域无关。
Function.prototype.before = function () {
console.log(this);//[Function: say]
//这个this在函数执行的时候才能确定的,所以say.before()的时候才能确定。
}
function say(){
}
say.before()
# 2】Vue2.0你用 函数劫持 AOP 重写数组的方法
在调用push方法时,触发一次更新操作
[1,2,3].push(4)
首先拿到老的push方法
let oldPush=Array.prototype.push
写自己新的push方法
function push(...args){
console.log('数据更新啦')
oldPush.call(this,...args)
}
let arr=[1,2,3]
push.call(arr,4,5,6,7)
console.log(arr)
//打印出:
//数据更新啦
//[ 1, 2, 3, 4, 5, 6 ]
# 3】react setState 事务 等同于before/after
# 4】AOP面向切面编程思路图:
function perform(anyMethod, wrappers) {
wrappers.forEach(wrapper => wrapper.initialize());
anyMethod();
wrappers.forEach(wrapper => wrapper.close());
}
perform(function () {
console.log('say');
}, [{//wrapper1
initialize: function () {
console.log('wrapper1 beforeSay');
},
close: function () {
console.log('wrapper1 close');
}
}, {//wrapper2
initialize: function () {
console.log('wrapper2 beforeSay');
},
close: function () {
console.log('wrapper2 close');
}
}])
//wrapper1 beforeSay
//wrapper2 beforeSay
//say
//wrapper1 close
//wrapper2 close
增强写法:使用闭包让它不立即执行
function perform(anyMethod, wrappers) {
return function () {
wrappers.forEach(wrapper => wrapper.initialize());
anyMethod();
wrappers.forEach(wrapper => wrapper.close());
}
}
let Func = perform(function () {
console.log('say');
}, [{//wrapper1
initialize: function () {
console.log('wrapper1 beforeSay');
},
close: function () {
console.log('wrapper1 close');
}
}, {//wrapper2
initialize: function () {
console.log('wrapper2 beforeSay');
},
close: function () {
console.log('wrapper2 close');
}
}])
Func()
//wrapper1 beforeSay
//wrapper2 beforeSay
//say
//wrapper1 close
//wrapper2 close
# 5】使用generator和yield进行aop编程
原理:使用yield*
进行再次迭代
function* generator() {
yield* 'HEY'
}
for (let i of generator()) {//执行generator函数返回一个迭代器
console.log(i)
}
//H
//E
//Y
let arr1 = [1, 2]
let arr2 = [10, 20]
function* chain(params) {
yield 'before'
for (let arr of params) {
yield* arr
}
yield 'after'
}
for (let num of chain([arr1, arr2])) {
console.log(num)
}
//before
//1
//2
//10
//20
//after
# 6】after:使用闭包来保存变量控制执行
function after(times, callback) {//闭包
return function () {//闭包将times变量一直存在栈中
if (--times == 0) {
callback();
}
}
}
let fn = after(3, function () {
console.log('函数已执行');
})
fn()
fn()
fn()
//函数已执行
来做个练习:需求是在下面fun方法中3和5之间插入打印4
function fun(){
fn()
console.log('1')
fn()
console.log('2')
fn()
console.log('3')
fn()
console.log('5')
}
function after(times, callback) {//闭包
return function () {//闭包将times变量一直存在栈中
if (--times == 0) {
callback();
}
}
}
let fn = after(4, function () {
console.log('4');
})
fun()