关于This指向
起因是抖音的大雷老师问了 btn上面的this指向哪里。
<button @click="btnClick">点击</button>
const btnClick=()=>{
console.log(this)
}
大雷老师给出的答案是在严格模式下指向undefined、非严格模式指向window
然后我就想了一个问题,可不可能我们每个this都指向当前这个模块内部呢?不让箭头函数的this指向window?所以我就想到了AST
抽象语法树分析
可以看到箭头函数在抽象语法树中是这样表示的箭头函数表示为:ArrowFunctionExpression
知道这个结构之后的话呢,我们开始配置babel插件,配置遇到ArrowFunctionExpression的代码块的时候我们就做一次处理 获取到他的节点数据进行处理
怎么通过nodejs解析抽象语法树?
-
安装依赖
npm install -D esprima estraverse escodegen -
怎么通过安装的模块解析js代码为ast数据?
let esprima=require('esprima')
let estraverse=require('estraverse')
let escodegen=require('escodegen')
let sourceCode=`const ast=()=>{}`
let ast=esprima.parse(sourceCode)
console.log(ast)
打印结果
Script {
type: 'Program',
body: [
VariableDeclaration {
type: 'VariableDeclaration',
declarations: [Array],
kind: 'const'
}
],
sourceType: 'script'
}
- 怎么遍历抽象语法树的代码块
estraverse.traverse(ast,{
enter(node){
console.log(padding()+"进入"+node.type)
indent+=2
},
leave(node){
indent-=2
console.log(padding()+"离开"+node.type)
}
})
打印结果
进入Program
进入VariableDeclaration
进入VariableDeclarator
进入Identifier
离开Identifier
进入ArrowFunctionExpression
进入BlockStatement
离开BlockStatement
离开ArrowFunctionExpression
离开VariableDeclarator
离开VariableDeclaration
离开Program
自定义Babel转换插件
插件功能,遇到模块中的箭头函数都去找其最上层非箭头函数的部分,保存其this赋值给一个变量_this,然后将箭头函数中所有用到this的地方都换成_this不指向window
- 新建一个文件夹
- npm init -y
- 安装@babel/core babel-types用来解析代码
这里我们先引入babel-plugin-transform-es2015-arrow-functions官方es6箭头函数转换插件做个对比
let core=require('@babel/core')
let types=require('babel-types')
let BabelPluginTransformEs2015ArrowFunctions=require('babel-plugin-transform-es2015-arrow-functions')
const sourceCode=`
const sum=(a,b)=>{
return a+b
}
`
/**
* babel-core本身就是为了生成语法树,遍历语法树,生成新代码的
* 它本身并不支持转换语法树
*/
let targetCode=core.transform(sourceCode,{
plugins: [BabelPluginTransformEs2015ArrowFunctions]
})
console.log(targetCode.code)
- node运行得到结果 可以看到它把箭头函数转换成匿名函数了
const sum = function (a, b) {
return a + b;
};
这里使用的是它官方的插件 我们仿照官方的插件源码进行改造
let BabelPluginTransformEs2015ArrowFunction2={
visitor: {
ArrowFunctionExpression(nodePath){
let node=nodePath.node
const thisBinding=hoistFunctionEnvironment(nodePath)
node.type='FunctionExpression'
}
}
}
function hoistFunctionEnvironment(fnPath){
// 往上找有自己this的祖宗
const thisEnvFn=fnPath.findParent(p=>{
// 如果他的父亲是函数,那不能是箭头函数
return (p.isFunction()&& p.isArrowFunctionExpression())||p.isProgram()
})
// thisEnvFn指向program 哪些地方用到了this 如果用到了 那么就需要在thisEnvfn上添加一个语句 let _this=this
let thisPaths=getScopeInfoInformation(fnPath)
let thisBinding='_this' // 把this变成下划线this
if(thisPaths.length>0){
// 表示在this环境中添加一个变量,变量的名字叫做_this, 初始值为thisExpression
thisEnvFn.scope.push({
id:types.identifier('_this'),
init:types.thisExpression()
})
// 遍历所有使用到this的路径节点 把所有的this expression变成_this标识符
thisPaths.forEach(thisChlid=>{
let thisRef=types.identifier(thisBinding)
thisChlid.replaceWith(thisRef)
})
}
}
// 获取自身的作用域信息
function getScopeInfoInformation(fnPath){
let thisPaths=[]
fnPath.traverse({
ThisExpression(thisPath){
thisPaths.push(thisPath)
}
})
return thisPaths
}
- 首先我们获取到他的节点数据,在这个自定义的babel转换插件里面的nodePath.node
- 获取当前this的指向,将this的指向做作用域提升,指向他的上层
- 找他的上层
// 往上找有自己this的祖宗
const thisEnvFn=fnPath.findParent(p=>{
// 如果他的父亲是函数,那不能是箭头函数
return (p.isFunction()&& p.isArrowFunctionExpression())||p.isProgram()
})
- 获取模块中哪些地方用到了this
function getScopeInfoInformation(fnPath){
let thisPaths=[]
fnPath.traverse({
ThisExpression(thisPath){
thisPaths.push(thisPath)
}
})
return thisPaths
}
- 如果说模块中有地方用到了this的话,那么我们就要将模块最顶层加上一个_this=this
// 表示在this环境中添加一个变量,变量的名字叫做_this, 初始值为thisExpression
thisEnvFn.scope.push({
id:types.identifier('_this'),
init:types.thisExpression()
})
- 遍历所有用到了this的地方,将this替换为_this
// 遍历所有使用到this的路径节点 把所有的this expression变成_this标识符
thisPaths.forEach(thisChlid=>{
let thisRef=types.identifier(thisBinding)
thisChlid.replaceWith(thisRef)
})
- 最后将箭头函数改为普通函数
// 碰到箭头函数就把它变成普通函数
ArrowFunctionExpression(nodePath){
let node=nodePath.node
const thisBinding=hoistFunctionEnvironment(nodePath)
node.type='FunctionExpression'
}
最终实现效果
- 解析以下代码块:
const btnClick=()=>{
console.log(this)
const a=()=>{
console.log(this)
const b=()=>{
console.log(this)
}
}
}
- 解析结果