【AST 基础】四、AST 的 scope 作用域

scope 作用域

scope 提供了一些属性和方法

使用的例子

const a = 1000;
let b = 2000;
let obj = {
    name: 'haha',
    add: function (a) {
        a = 400;
        b = 300;
        let e = 700;

        function demo() {
            let b = 600;
        }

        demo();
        return a + a + b + 1000 + obj.name
    }
}
obj.add(100)

获取’标识符’作用域

path.scope.block() 可以获取 标识符 作用域,返回 Node 对象

visitor = {
    Identifier(path) {
        if (path.node.name === 'e') { // e 的作用域就是 整个 函数
            console.log(generator(path.scope.block).code)
        }
    }
}
traverse(ast, visitor)
/*
function (a) {
  a = 400;
  b = 300;
  let e = 700; 

  function demo() {
    let b = 600;
  }

  demo();
  return a + a + b + 1000 + obj.name;
}

 */

但有时候 作用域输出的范围 与 实际不符合,就需要获取 父节点的作用

visitor = {
    FunctionDeclaration(path) { // 函数声明 只有  demo() 满足要求
        console.log(generator(path.scope.block).code)
    }
}
traverse(ast, visitor)

/* 
function demo() {
  let b = 600;   
}
 */

demo()作用域 实际上应该是 整个 add() 的范围,这时就与实际输出不符合,就需要 获取 父节点的作用域

visitor = {
    FunctionDeclaration(path) {
        console.log(generator(path.scope.parent.block).code)
    }
}
traverse(ast, visitor)
/*
function (a) {     
  a = 400;
  b = 300;
  let e = 700;     

  function demo() {
    let b = 600;   
  }

  demo();
  return a + a + b + 1000 + obj.name;
}
 */

path.scope.getBinding

获取标识符对应的绑定对象 (Binding)

visitor = {
    FunctionDeclaration(path) {
        let binding = path.scope.getBinding('a')
        console.log(binding)
    }
}
traverse(ast, visitor)
/*
Binding {
    identifier : Node {type: "Identifier", start: 83, end: 84, loc: SourceLocation, name: "a"}, // 标识符 的 Node对象
    scope : Scope {uid: 1, path: NodePath, block: Node, labels: Map(0), inited: true, ...},  // 对应 标识符的 scope 对象
    path : NodePath {contexts: Array(0), state: Object, opts: Object, _traverseFlags: 0, skipKeys: null, ...}, // 对应标识符的 Path 对象 
    kind : "param", // 该标识符类型 这里表示 是一个参数
    constantViolations : Array(1) [NodePath], // 存放 修改了 该标识符的节点
    constant : false, // 是否是常量
    referencePaths : Array(2) [NodePath, NodePath], // 引用了 该标识符的 NodePath 数组
    referenced : true, // 该标识符 是否被引用
    references : 2, // 该标识符的 引用次数
    hasDeoptedValue : false, // 
    hasValue : false,
    value : null
}
 */

referencePaths & constantViolations

这两个是 Binding 对象里的 属性

/*
Binding {
    ...
    referencePaths : Array(2) [NodePath, NodePath], // 引用了 该标识符的 NodePath 数组,没有引用时为 空数组
    constantViolations : Array(1) [NodePath], // 存放 修改了 该标识符的节点,没有被修改时为 空数组
    ...
}
 */

遍历作用域

有两种方式遍历作用域

  • path.scope.traverse({})
  • binding.scope.traverse({}): 推荐
//  path.scope.traverse({})
visitor = {
    FunctionExpression(path) {
        let binding = path.scope.traverse({
            Identifier(path){
                console.log('')
            }
        })
    }
}
traverse(ast, visitor)

// binding.scope.traverse({}) 推荐
visitor = {
    FunctionExpression(path) {
        let binding = path.scope.getBinding('a')
        binding.scope.traverse({
            Identifier(path){
                console.log('')
            }
        })
    }
}
traverse(ast, visitor)

把例子 函数中的 a=400 改成 a=60

visitor = {
    FunctionExpression(path) {
        let binding = path.scope.getBinding('a')
        binding.scope.traverse(path.scope.block, { // 遍历 a的 作用域
            AssignmentExpression(path) {
                if (path.node.left.name === 'a'){
                    path.node.right = type.valueToNode(60)
                }
            }
        })
    }
}
traverse(ast, visitor)

标识符重命名

binding.scope.rename() 该方法,会重命名所有 引用了该标识符的 地方

visitor = {
    FunctionExpression(path) {
        let binding = path.scope.getBinding('b')
        binding && binding.scope.rename('b','xx')
    }
}
traverse(ast, visitor)
/*
const a = 1000;
let b = 2000;
let obj = {
  name: 'haha',
  add: function (xx) { // a --> xx 
    xx = 400;  // 函数的参数 a --> xx
    b = 300;
    let e = 700;

    function demo() {
      let b = 600;
    }

    demo();
    return xx + xx + b + 1000 + obj.name;
  }
};
obj.add(100);
 */

随机生成标识符

path.scope.generateUidIdentifier('_0xoO').name 该方法 会生成一个字符串 且不会重复

可以用来实现 简单的 标识符 混淆,也可以用来 反混淆,使变量看起来 好分析

visitor = {
    Identifier(path) {
        path.scope.rename(path.node.name,path.scope.generateUidIdentifier('_0xoO').name)
    }
}
traverse(ast, visitor)
/*
const _0xoO = 1000;
let _0xoO15 = 2000;
let _0xoO18 = {
  name: 'haha',
  add: function (_0xoO14) {
    _0xoO14 = 400;
    _0xoO15 = 300;
    let _0xoO9 = 700;

    function _0xoO12() {
      let _0xoO11 = 600;
    }

    _0xoO12();

    return _0xoO14 + _0xoO14 + _0xoO15 + 1000 + _0xoO18.name;
  }
};

_0xoO18.add(100);
 */

scope 其他方法

path.scope.hasBinding('a') 返回值是 bool 值,作用是 查询 该作用域 是否有该标识符的 绑定

该方法 可以用 path.scope.getBinding('a') 代替,只是 该方法的返回值是 undefinedBinding {}

visitor = {
    Identifier(path) {
        console.log(path.scope.hasBinding('a'))
    }
}
traverse(ast, visitor)
/*
    true
    ...
 */

path.scope.getAllBindings() 获取该节点的所有绑定,返回的对象标识符名属性名,对应的 属性是Binding {..}

visitor = {
    Identifier(path) {
        console.log(path.scope.getAllBindings())
    }
}
traverse(ast, visitor)
/*
Object {a: Binding {..}, b: Binding {..}, obj: Binding {..}}
 */

path.scope.hasReference('a') 查询 当前节点是否有 标识符a 的引用,返回值是 true / false

发表评论 / Comment

用心评论~