@actualwave/tree-walker 中文文档教程

发布于 6年前 浏览 10 项目主页 更新于 3年前

TreeWalker

构建状态覆盖状态这是基于 ES6 代理的线框,用于制作树遍历 API。 您可以检查准备使用实现 DOMWalker 以获得更好的理解。 受 E4X(ECMAScript for XML) 及其 ActionScript 3 实现 (RIP) 的启发。

Demo

Table of Contents

Installation

通过 NPM

npm install @actualwave/tree-walker --save

或 Yarn

yarn add @actualwave/tree-walker

导入 TreeWalker 后,需要配置指定默认适配器和扩充、前缀。

没有任何配置,您将能够访问子节点

const root = create(souceData);
const myDescendants = root.child.otherChild.descendant;

之外,不能在它们上调用任何方法

  • valueOf() -- to return raw data(source node)
  • toString() -- to call toString() method on source node

,但除了

How it works

要开始使用源数据树,它应该被包装到一个 使用create() 函数的代理包装器对象。 但是 wrapper 不直接与源数据一起工作,所有与源数据的通信都是通过 adapter 对象完成的,该对象应该实现特定的 API。

因此,我们将源数据和知道如何使用该数据的适配器对象传递给 create() 函数。

const root = create(souceData, myAdapter);

当从 Proxy wrapper 请求任何字段/属性/索引时,它不会返回源节点相应属性的值,而是尝试获取同名或索引的子节点。

/*
  this does not request property "child" of "souceData"
  it will look for all chidren nodes with name "child"
*/
const child = root.child;

/*
  this will look for fifth child node with name "child"
*/
const fifth = root.child[5];

Wrapper 将使用适配器对象的 getChildren()getChildrenByName() 方法访问子节点。

const root = create(mySourceTreeData, myAdapter);
const myDescendants = root.child.otherChild.descendant;
const myThirdChild = root.child[2];
const jerries = myDescendants.jerry;

使用此代码,在第 1 行,为源树的根节点创建代理对象。 在第 2 行,为 childotherChild 创建了额外的代理,descendant 节点的代理存储在 myDescendants 中。

当按名称请求子节点时,它返回的不是一个节点,而是所有具有相同名称的子节点的列表,但是当子节点从列表中请求时(如 otherChild from childotherChilddescendantmyDescendantsjerry),Proxy 将从该列表中获取第一个节点并返回其子节点.

因此,如果您有 2 个节点的列表,其中第一个没有子节点

const root = <source data>;
/**
node structure
     root
     / \
one[0] one[1]
        /  \
    two[0] two[1]
*/

const rootProxy = create(root, myAdapter);
const twoProxy = rootProxy.one.two;

twoProxy 将包含空列表,因为只有第一个 one 节点会检查子节点。 下面是等效代码

const rootProxy = create(root, myAdapter);
const twoProxy = rootProxy.one[0].two;

要从第二个 one 节点检索子节点,您应该像这样指定它

const rootProxy = create(root, myAdapter);
const twoProxy = rootProxy.one[1].two;

现在我们将在 twoProxy 中有两个 two 节点。

如果以这种方式找不到节点,适配器应始终返回空列表

const rootProxy = create(root, myAdapter);
const veryDeepNode = rootProxy.one.five.seven.anyOther.dont.know.if.it.exists;

,您可以从任何深度请求任何后代,而不必担心它们的存在。

Usage

,应该为其提供两个必需的组件:

  1. 源数据 - 树数据结构,您希望使用

  2. 适配器 - 提供标准化 API 以处理源数据

要使用TreeWalker 和前缀可用于丰富最终的 API。

在使用之前,必须通过工厂函数 create() 创建 TreeWalker,它返回包装到代理包装器中的源数据(下面文本中的“包装器”),它将使用适配器获取子节点,调用扩充并应用前缀。 TreeWrapper 是只读的树遍历器,所以它只有gethasapply 陷阱,set 或 < code>deleteProperty 未实现。 但是您可以为源数据突变创建扩充(如下所述)。

import { create } from '@actualwave/tree-walker';

// root is a wrapped "source" node
const root = create(source, myAdapter);

实例化后,您可以将子节点作为属性访问,将扩充作为方法访问。 可能有同名的方法和节点,因为当为子对象创建包装器时,它存储父节点和子节点的名称。

如果从包装器请求属性,它将返回具有该节点和属性名称的新包装器:

// stores { target: node, name: "parent" }
const parent = node.parent;

// stores { target: parent, name: "otherChild" }
const child = parent.otherChild;

// stores { target: child, name: "sibblings" }
const sibblings = child.sibblings;

或者

// stores { target: otherChild, name: "sibblings" }
const sibblings = node.parent.otherChild.sibblings;

如果上面示例中的 sibblings 节点将在源树中被替换或删除,您的包装器对象将反映变化。

当请求一个孩子时,包装器使用适配器的 getChildren()getChildrenByName() 方法,所以它总是一个节点列表,即使单个孩子可用——它将是一个孩子的名单。

const firstSibbling = sibblings[0];

从包装的节点列表中请求子节点时,包装器将从列表中请求第一个节点的子节点。

适配器方法 toList()getChildren()getChildrenByName() 必须始终返回节点列表。 在 0 个节点的情况下,它应该是空列表。 单个节点的长度必须为 1,如果有请求,索引 0 必须返回相同的节点。

如果您希望在包装器中包含特定节点,您可以通过指定索引来请求它:

// stores { target: otherChild }
const sibblings = node.parent.otherChild[0];

在这种情况下,包装器将包含一个按名称解析的节点。

如果包装器作为函数被调用,它将查找为该名称注册的扩充:

// stores { target: node, name: "parent" }
const parent = node.parent;

// will call augmentation
console.log(parent());

或者

// will call augmentation "descendants" on "otherChild" node
console.log(node.child.otherChild.descendants());

重要的是,该包装器具有一组子节点的受限名称 构造函数 原型 这些属性的值将直接从源节点或列表请求并按原样返回。

API

  • create(rootNode, adapter) -- 为节点创建包装器并使用提供的适配器来处理它

  • setDefaultAdapter(adapter) -- 指定默认适配器使得在 create() 工厂函数中可选定义适配器

  • getDefaultAdapter(adapter) -- 获取默认适配器

  • addAugmentations({...}) -- 向池中添加扩充,接受具有函数的对象,属性的名称将用作扩充的名称。

addAugmentations({
    children: (node, adapter, utils) => {...},
    parent: (node, adapter, utils) => {...},
    name: (node, adapter, utils) => {...},
});
  • hasAugmentation(name) -- 检查是否添加了扩充

  • resetAugmentations({…}) -- 删除所有当前注册的扩充(包括核心扩充),如果有则用扩充替换它们已通过

  • setNamePrefix(char, handlerFunc) -- 设置前缀处理程序

  • isValidPrefix(char) -- 检查前缀是否有效以及是否已注册处理程序

/a>

Adapter

Adapter 是一个对象,具有包装器和扩充处理源数据所需的一组方法。 包装器和扩充调用的每个实例都接收适配器实例以及要传递到适配器的目标节点或节点列表。 所有对源数据的调用都应通过适配器 API 完成。

适配器 API(用粗体标记的必需方法)

  • validateRoot(item) -- 在为根节点包装包装器之前调用,可以接受任何东西并且必须返回根节点。

  • isList(item) -- 检查 item 是否是列表

  • toList(item) -- 将任何内容转换为列表,将节点转换为包含一个项目的列表,或者将任何内容转换为空列表

  • getLength( item) -- 返回项目的长度,如果是节点,它应该是 1

  • getNodeAt(item, index = 0) -- 通过索引从列表中返回节点

  • isNode(item) -- 检查 item 是否是单个节点

  • toNode(item) -- 如果可能,将任何东西转换为节点。 如果是列表,从中获取第一个节点

  • getName(item) -- 返回节点的名称

  • hasChild(item, name) -- 检查节点是否有指定名称的子节点

  • getChildren(item) -- 获取列表of all children from node

  • getChildrenByName(item, name) -- 获取指定名称的子节点列表

  • getChildAt: (item, index = 0) -- 通过索引获取节点的子节点

  • getNodeParent(item) - - 获取父节点

  • getNodeRoot(item) -- 获取树的根节点

  • value(item) -- 获取包装器后面的源值

  • string(item) -- 获取源值的字符串表示。

所有方法都应该同样适用于列表和节点,如果它需要节点但提供了列表,它应该从列表中获取第一个节点并继续,反之亦然——如果需要列表但提供了节点,则将其转换为具有一个节点的列表并继续。

您可以自由地将任何方法或属性添加到适配器以进行自定义扩充,但不建议更改此处描述的方法的签名。

Augmentations

扩充是简单的函数,它接收参数集并产生任何类型的数据(不限于节点)。 增强仅在作为函数调用时起作用,否则它被视为包装节点:

// add "children" augmentation
addAugmentations({
    children: (node, adapter, [childName], utils) => {
        let list;
        if (childName) {
            list = adapter.getChildrenByName(node, childName);
        } else {
            list = adapter.getChildren(node);
        }

        return utils.wrap(list, adapter);
    },
}};

// now augmentation may be called from any wrapped node
const root = create(source, myAdapter);

// list of "children" nodes
const children1 = root.children;

// result of "children" augmentation call
const children2 = root.children();

当包装器收到函数调用时,它检查可用的增强,如果可用,则被调用。 在其他情况下,它将充当按名称检索的节点列表,因此这也将起作用。

// result of "children" augmentation call
const children2 = children1();

因为 children1 中的 wrapper 已经存储了它的父 root 节点和 children 名称。

TreeWalker 提供了一组基本的扩充:

coreAugmentations

只有两个扩充 toString()valueOf(),它们是预先应用的。

  • valueOf():any -- 如果适配器有 value() 方法,它将被调用,否则打开源节点或节点列表并返回它

  • toString( ):String -- 如果适配器有 string() 方法,它将被调用,否则调用源节点上的 toString() 方法 一

nodeAugmentations

组扩充工作与节点和他们的孩子。

  • children(name:String?):WrappedNode[] - 所有子节点的列表,如果提供名称,列表将按名称过滤。

  • descendants(name:String?):WrappedNode[] - 所有后代节点的列表,如果提供名称,列表将按名称过滤。

  • childAt (index:Number=0):WrappedNode - 索引处的子节点

  • root():WrappedNode - 树的根节点

  • parent():WrappedNode - 用于列表的扩充父节点

listAugmentations

集。

  • length():Number - 列表的长度,对于单个包装节点将返回 1

  • at(index:Number=0):WrappedNode - 通过索引从列表中获取项目,可以在单个节点上调用,就像在具有一个项目的列表上一样

  • first():WrappedNode - 从列表中获取第一个项目,可以在单个节点中调用,将返回自身

  • filter(handler:Function ):WrappedNode[] - 过滤节点列表,将返回过滤后的列表

  • ma​​p(handler:Function):any[] - 映射节点列表,将返回每个节点的映射结果列表node

  • reduce(handler:Function, initialValue:any?):any - 减少节点列表将返回最终值

Prefixes

前缀始终是一个符号字符串,任何字符。 它映射一个函数,该函数将在请求属性时调用( code>get 代理陷阱)。

要访问该对象的属性,我们可以使用前缀,因此我们注册了一个特殊的处理函数,当我们从 data 对象请求属性时将调用该函数:

const dataPrefixGetHandler = (target, adapter, [name]) => {
    const node = adapter.toNode(target);
    return node.data ? node.data[name] : undefined;
};

setNamePrefix("$", dataPrefixGetHandler);

处理程序接收源节点、适配器、参数列表,以防万一函数调用和 utils 对象,其中包含用 Proxy 包装的方法。

注册此前缀后,我们可以像这样访问存储在 data 中的节点属性

const root = create(source, myAdapter);

console.log(root.$prop1); // will request "prop1" from "data" object
console.log(root.child.$prop1); // will request "prop1" from "data" object of "child" node

例如,假设我们树中的每个节点都有一些 data 对象,该对象包含节点。 但是数据对象可能具有与子节点同名的属性:

// node object structure
const source = {
  name: 'node',
  data: {
    prop1: true,
    prop2: 'my value',
  },
  children: [
    { name: 'prop1', data: {} },
    { name: 'prop2', data: {} },
  ],
};

访问 data 属性的解决方案是在每个数据属性前加上特殊符号——你不需要重命名每个 data< /code> 属性,通过包装器请求它时只需添加前缀。

// register prefix
setNamePrefix("$", dataPrefixGetHandler);

// access data
console.log(wrappedNode.$prop1); // true
console.log(wrappedNode.$prop2); // "my value"

通过使用一个处理程序,您可以获得对前缀属性的只读访问权限,以扩展控制,除了 get 处理程序之外,您还可以将处理程序注册到 has, setdepeteProperty 代理陷阱。

const dataPrefixGetHandler = (target, adapter, [name]) => {
  const node = adapter.toNode(target);
  return node.data ? node.data[name] : undefined;
};

const dataPrefixHasHandler = (target, adapter, [name]) => {
  const node = adapter.toNode(target);
  return node.data ? node.data.hasOwnProperty(name) : false;
};

const dataPrefixSetHandler = (target, adapter, [name, value]) => {
  const node = adapter.toNode(target);
  if (!node.data) {
    node.data = {};
  }

  node.data[name] = value;
  return true;
};

const dataPrefixDeleteHandler = (target, adapter, [name]) => {
  const node = adapter.toNode(target);
  return node.data ? delete node.data[name] : false;
};

setNamePrefix('$', {
  get: dataPrefixGetHandler,
  has: dataPrefixHasHandler,
  set: dataPrefixSetHandler,
  deleteProperty: dataPrefixDeleteHandler,
});

有关前缀利用的其他示例,请查看 js-dom-walker 项目,它使用$ 作为 DOM 节点属性的前缀。

TreeWalker

Build StatusCoverage StatusThis is wireframe based on ES6 Proxies for making tree traversing APIs. You may check ready to use implementation DOMWalker for better understanding. Inspired by E4X(ECMAScript for XML) and its ActionScript 3 implementation(R.I.P.).

Demo

Table of Contents

Installation

Via NPM

npm install @actualwave/tree-walker --save

Or Yarn

yarn add @actualwave/tree-walker

After importing TreeWalker, it needs to be configured to specify default adapter and augmentations, prefixes.

Without any configurations you will be able to access child nodes

const root = create(souceData);
const myDescendants = root.child.otherChild.descendant;

but no methods can be called on them except

  • valueOf() -- to return raw data(source node)
  • toString() -- to call toString() method on source node

How it works

To start working with source data tree, it should be wrapped into a Proxy wrapper object usingcreate() function. But wrapper does not work with source data directly, all communication with source data is done via adapter object which should implement specific API.

So we pass source data and adapter object which knows how to work with this data to create() function.

const root = create(souceData, myAdapter);

When requesting any field/property/index from Proxy wrapper, it does not return values of corresponding properties of the source node, but tries to get children nodes of same name or index.

/*
  this does not request property "child" of "souceData"
  it will look for all chidren nodes with name "child"
*/
const child = root.child;

/*
  this will look for fifth child node with name "child"
*/
const fifth = root.child[5];

Wrapper will access children nodes with getChildren() and getChildrenByName() methods of adapter object.

const root = create(mySourceTreeData, myAdapter);
const myDescendants = root.child.otherChild.descendant;
const myThirdChild = root.child[2];
const jerries = myDescendants.jerry;

With this code, on line 1, Proxy object for root node of the source tree is created. On line 2 additional Proxies are created for child, otherChild and proxy for descendant nodes is stored in myDescendants.

When requesting a child node by name, it returns not one node but a list of all child nodes with same name, but when children requested from the list(like otherChild from child or descendant from otherChild or jerry from myDescendants), Proxy will take first node from this list and return its children.

So, if you have a list of 2 nodes where first does not have children

const root = <source data>;
/**
node structure
     root
     / \
one[0] one[1]
        /  \
    two[0] two[1]
*/

const rootProxy = create(root, myAdapter);
const twoProxy = rootProxy.one.two;

twoProxy will contain empty list, because only first one node is checked for children. Here are equivalent code

const rootProxy = create(root, myAdapter);
const twoProxy = rootProxy.one[0].two;

To retrieve children from second one node you should specify it, like this

const rootProxy = create(root, myAdapter);
const twoProxy = rootProxy.one[1].two;

Now we will have two two nodes in twoProxy.

Adapter should always return empty list if no nodes found

const rootProxy = create(root, myAdapter);
const veryDeepNode = rootProxy.one.five.seven.anyOther.dont.know.if.it.exists;

in this way you can request any descendants from any depth without worrying about their existence.

Usage

To use TreeWalker, it should be supplied with two required components:

  1. Source data - Tree data structure, that you want to work with

  2. Adapter - Provides standardized API to work with source data

Additionally augmentations and prefixes could be used to enrich final API.

Before using, TreeWalker must be created via factory function create(), it returns source data wrapped into a Proxy wrapper(just "wrapper" in text below) which will use adapter to get children nodes, call augmentations and apply prefixes. TreeWrapper is read-only tree traverser, so it has only get, has and apply traps, set or deleteProperty are not implemented. But you may create an augmentation for source data mutations(explained below).

import { create } from '@actualwave/tree-walker';

// root is a wrapped "source" node
const root = create(source, myAdapter);

After instantiating, you can access child nodes as properties and augmentations as methods. Its possible to have methods and nodes of same name, because, when wrapper is created for child object, it stores parent node and name of child node.

If property is requested from wrapper, it will return new wrapper with that node and name of the property:

// stores { target: node, name: "parent" }
const parent = node.parent;

// stores { target: parent, name: "otherChild" }
const child = parent.otherChild;

// stores { target: child, name: "sibblings" }
const sibblings = child.sibblings;

or

// stores { target: otherChild, name: "sibblings" }
const sibblings = node.parent.otherChild.sibblings;

If sibblings node from example above will be replaced in a source tree or removed, your wrapper object will reflect changes.

When requesting a child, wrapper uses getChildren() and getChildrenByName() methods of adapter, so its always a list of nodes, even if single child available -- it will be a list with one child.

const firstSibbling = sibblings[0];

When requesting a child from wrapped list of nodes, wrapper will request children of first node from the list.

Adapter methods toList(), getChildren() and getChildrenByName() must always return list of nodes. In case of 0 nodes it should be empty list. Single node must have length 1 and, if requested, index 0 must return same node.

If you wish to have specific node in a wrapper, you can request it by specifying index:

// stores { target: otherChild }
const sibblings = node.parent.otherChild[0];

In this case wrapper will contain one node resolved by name.

If wrapper is being called as function, it will look for augmentation registered for that name:

// stores { target: node, name: "parent" }
const parent = node.parent;

// will call augmentation
console.log(parent());

or

// will call augmentation "descendants" on "otherChild" node
console.log(node.child.otherChild.descendants());

Important to say, that wrapper has set of restricted names for child nodes constructor prototype Values of these properties will be requested directly from source node or list and returned as is.

API

  • create(rootNode, adapter) -- create wrapper for node and use supplied adapter to work with it

  • setDefaultAdapter(adapter) -- specifying default adapter makes optional defining adapter in create() factory function

  • getDefaultAdapter(adapter) -- get default adapter

  • addAugmentations({…}) -- add augmentations to the pool, accepts an object with functions, name of the property will be used as name of augmentation.

addAugmentations({
    children: (node, adapter, utils) => {...},
    parent: (node, adapter, utils) => {...},
    name: (node, adapter, utils) => {...},
});
  • hasAugmentation(name) -- check if augmentation was added

  • resetAugmentations({…}) -- remove all currently registered augmentations(including core augmentations) and, replace them with augmentations if passed

  • setNamePrefix(char, handlerFunc) -- set prefix handler

  • isValidPrefix(char) -- check if prefix is valid and has handler registered

Adapter

Adapter is an object with set of methods required by wrapper and augmentations to work with source data. Each instance of wrapper and augmentation calls receive instance of adapter as well as target node or list of nodes to pass into adapter. All calls to source data should be done via adapter API.

Adapter API(required methods marked with bold)

  • validateRoot(item) -- is called before crating wrapper for root node, could accept anything and must return root node.

  • isList(item) -- check if item is a list

  • toList(item) -- convert anything to list, node to list with one item or nothing to empty list

  • getLength(item) -- returns length of the item, in case of node it should be 1

  • getNodeAt(item, index = 0) -- return node from a list by its index

  • isNode(item) -- check if item is single node

  • toNode(item) -- convert anything to node, if possible. If list, get first node from it

  • getName(item) -- return name of the node

  • hasChild(item, name) -- check if node has children with specified name

  • getChildren(item) -- get list of all children from node

  • getChildrenByName(item, name) -- get list of children with specified name

  • getChildAt: (item, index = 0) -- get child of the node by index

  • getNodeParent(item) -- get node parent

  • getNodeRoot(item) -- get root node of the tree

  • value(item) -- get source value behind the wrapper

  • string(item) -- get string representation of source value .

All methods should work equally with lists and nodes, if it requires node but was supplied with list, it should get first node from the list and continue and vice versa -- if list is required but node supplied, convert it to list with one node and proceed.

You are free to add any methods or properties to adapter for your custom augmentations, but its not recommended to change signature of described here methods.

Augmentations

Augmentations are simple functions which receive set of arguments and result with any kind of data(not limited to nodes). Augmentations work only when called as function, otherwise it's treated as wrapped node:

// add "children" augmentation
addAugmentations({
    children: (node, adapter, [childName], utils) => {
        let list;
        if (childName) {
            list = adapter.getChildrenByName(node, childName);
        } else {
            list = adapter.getChildren(node);
        }

        return utils.wrap(list, adapter);
    },
}};

// now augmentation may be called from any wrapped node
const root = create(source, myAdapter);

// list of "children" nodes
const children1 = root.children;

// result of "children" augmentation call
const children2 = root.children();

When wrapper received a function call, it checks for available augmentations and if its available, its being called. In other cases it will act as node list retrieved by name, so this will work too.

// result of "children" augmentation call
const children2 = children1();

Because wrapper in children1 have stored its parent root node and children name.

TreeWalker supplied with set of basic augmentations:

coreAugmentations

There are only two augmentations toString() and valueOf(), they are pre-applied.

  • valueOf():any -- If adapter has value() method, it will be called, otherwise unwrap source node or list of nodes and return it

  • toString():String -- If adapter has string() method, it will be called, otherwise call toString() method on source node

nodeAugmentations

Set of augmentations to work with nodes and their children.

  • children(name:String?):WrappedNode[] - List of all children nodes, if name is supplied, list will be filtered by name.

  • descendants(name:String?):WrappedNode[] - List of all descendant nodes, if name is supplied, list will be filtered by name.

  • childAt (index:Number=0):WrappedNode - Child node at index

  • root():WrappedNode - Root node of the tree

  • parent():WrappedNode - Parent node

listAugmentations

Set of augmentations to work with lists.

  • length():Number - Length of the list, will return 1 for single wrapped node

  • at(index:Number=0):WrappedNode - Get item from list by index, can be called on single node as on list with one item

  • first():WrappedNode - Get first item from the list, can be called in single node, will return itself

  • filter(handler:Function):WrappedNode[] - Filter list of nodes, will return filtered list

  • map(handler:Function):any[] - Map list of nodes, will return list with map results for each node

  • reduce(handler:Function, initialValue:any?):any - Reduce list of nodes will return final value

Prefixes

Prefix is always a one symbol string, any character. It maps a function which will be called when property is requested(get Proxy trap).

To access properties of this object we can use prefixes, so we register a special handler function which wil be called when we request a property from data object:

const dataPrefixGetHandler = (target, adapter, [name]) => {
    const node = adapter.toNode(target);
    return node.data ? node.data[name] : undefined;
};

setNamePrefix("$", dataPrefixGetHandler);

Handler receives source node, adapter, list of arguments in case of function call and utils object which contains method to wrap with Proxy.

After registering this prefix, we may access node properties stored in data like this

const root = create(source, myAdapter);

console.log(root.$prop1); // will request "prop1" from "data" object
console.log(root.child.$prop1); // will request "prop1" from "data" object of "child" node

For example, let's imagine that every node in our tree has some data object which holds additional properties of the node. But data object may have properties with same name as children nodes:

// node object structure
const source = {
  name: 'node',
  data: {
    prop1: true,
    prop2: 'my value',
  },
  children: [
    { name: 'prop1', data: {} },
    { name: 'prop2', data: {} },
  ],
};

The solution to access data properties is to prefix every data property with special symbol -- you don't need to rename every data property, just add prefix when requesting it through wrapper.

// register prefix
setNamePrefix("$", dataPrefixGetHandler);

// access data
console.log(wrappedNode.$prop1); // true
console.log(wrappedNode.$prop2); // "my value"

By using one handler you receive read-only access to prefixed properties, to extend control, additionally to get handler you may also register handlers to has, set and depeteProperty Proxy traps.

const dataPrefixGetHandler = (target, adapter, [name]) => {
  const node = adapter.toNode(target);
  return node.data ? node.data[name] : undefined;
};

const dataPrefixHasHandler = (target, adapter, [name]) => {
  const node = adapter.toNode(target);
  return node.data ? node.data.hasOwnProperty(name) : false;
};

const dataPrefixSetHandler = (target, adapter, [name, value]) => {
  const node = adapter.toNode(target);
  if (!node.data) {
    node.data = {};
  }

  node.data[name] = value;
  return true;
};

const dataPrefixDeleteHandler = (target, adapter, [name]) => {
  const node = adapter.toNode(target);
  return node.data ? delete node.data[name] : false;
};

setNamePrefix('$', {
  get: dataPrefixGetHandler,
  has: dataPrefixHasHandler,
  set: dataPrefixSetHandler,
  deleteProperty: dataPrefixDeleteHandler,
});

For additional example of prefix utilization, check js-dom-walker project, it uses $ as prefix for DOM node attributes.

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文