敲碎时间,铸造不朽的个人专栏
上一篇

CKEditor5——Node,Element,NodeList源码分析

广告
选中文字可对指定文章内容进行评论啦,→和←可快速切换按钮,绿色背景文字可以点击查看评论额。
大纲

CKEditor5——Node,Element,NodeList源码分析

前面大概知道了模型的基本组成,包括一个抽象类Node,两个实现类TextElement,以及还有一个关键的类就是NodeList

为了容易展开这个话题,我首先提出一个问题,那就是为什么是Node类是一个抽象类?先看看这个类的具体代码:

export default class Node {
	/**
	 * Creates a model node.
	 *
	 * This is an abstract class, so this constructor should not be used directly.
	 *
	 * @abstract
	 * @param {Object} [attrs] Node's attributes. See {@link module:utils/tomap~toMap} for a list of accepted values.
	 */
	constructor( attrs ) {
		/**
		 * Parent of this node. It could be {@link module:engine/model/element~Element}
		 * or {@link module:engine/model/documentfragment~DocumentFragment}.
		 * Equals to `null` if the node has no parent.
		 *
		 * @readonly
		 * @member {module:engine/model/element~Element|module:engine/model/documentfragment~DocumentFragment|null}
		 */
		this.parent = null;

		/**
		 * Attributes set on this node.
		 *
		 * @private
		 * @member {Map} module:engine/model/node~Node#_attrs
		 */
		this._attrs = toMap( attrs );
	}
}

我们看这个类,首先,这个类应该能够存储属性,因此它有一个Map属性叫做this._attrs,此外还有一个属性叫做this.parent,因此有了这个属性之后,就可以用Node节点构成一个类似树形结构。

有了这个基础知识以后,看看一个具体的方法:

get index() {
	let pos;

	if ( !this.parent ) {
		return null;
	}

	if ( ( pos = this.parent.getChildIndex( this ) ) === null ) {
		throw new CKEditorError( 'model-node-not-found-in-parent', this );
	}

	return pos;
}

注意到没有,这个类的所有实现都是代理到了它的父节点的方法,在this.parent这个属性的类型有三类:Element,DocumentFragment,null,因此,可以大胆猜想,除了null之外,其他的类一定要实现具体方法。

用获取某个节点的索引来举例,实现的逻辑就是,判断当前节点在父节点中的索引位置就是具体的索引值。因此,我们可以看看Element.getChildIndex()这个方法:

Element类

export default class Element extends Node {
	/**
	 * Creates a model element.
	 *
	 * **Note:** Constructor of this class shouldn't be used directly in the code.
	 * Use the {@link module:engine/model/writer~Writer#createElement} method instead.
	 *
	 * @protected
	 * @param {String} name Element's name.
	 * @param {Object} [attrs] Element's attributes. See {@link module:utils/tomap~toMap} for a list of accepted values.
	 * @param {module:engine/model/node~Node|Iterable.<module:engine/model/node~Node>} [children]
	 * One or more nodes to be inserted as children of created element.
	 */
	constructor( name, attrs, children ) {
		super( attrs );

		/**
		 * Element name.
		 *
		 * @readonly
		 * @member {String} module:engine/model/element~Element#name
		 */
		this.name = name;

		/**
		 * List of children nodes.
		 *
		 * @private
		 * @member {module:engine/model/nodelist~NodeList} module:engine/model/element~Element#_children
		 */
		this._children = new NodeList();

		if ( children ) {
			this._insertChild( 0, children );
		}
	}
}

从Element类可以看出,这个类除了能保存属性值之外,还有两个属性就是名称和子节点,而子节点的实现就是NodeList

我们继续看getChildIndex()

getChildIndex( node ) {
	return this._children.getNodeIndex( node );
}

这个方法的具体实现其实是代理给了NodeList这个类。

export default class NodeList {
	/**
	 * Creates an empty node list.
	 *
	 * @protected
	 * @param {Iterable.<module:engine/model/node~Node>} nodes Nodes contained in this node list.
	 */
	constructor( nodes ) {
		/**
		 * Nodes contained in this node list.
		 *
		 * @private
		 * @member {Array.<module:engine/model/node~Node>}
		 */
		this._nodes = [];

		if ( nodes ) {
			this._insertNodes( 0, nodes );
		}
	}
}

NodeList类其实就是Node数组的一个封装。

getNodeIndex()

getNodeIndex( node ) {
	const index = this._nodes.indexOf( node );
	return index == -1 ? null : index;
}

这下逻辑很清晰了吧,获取某个节点在数组中的位置,然后判断,如果不存在的话,返回null,否则返回具体的索引。知道理清楚了Node,NodeList,Element之间的关系,就能明白每一个方法是怎样实现的。

好了,剩余的其它方法都可以按照上面的逻辑来进行梳理和分析。

我们再看看一个属性叫做offsetSize,这个属性表示当前节点占据的偏移量值的大小,普通节点的值是1,而文本节点的大小就是文本的长度。

Text

get offsetSize() {
	return this.data.length;
}

Node

get offsetSize() {
	return 1;
}

另一个属性就是maxOffset

这个属性是只有Element,其实表示的就是当前节点占据的偏移量的大小。

get maxOffset() {
	return this._children.maxOffset;
}

NodeList

get maxOffset() {
	return this._nodes.reduce( ( sum, node ) => sum + node.offsetSize, 0 );
}

这里的逻辑就是将子节点的offsetSize相加即可。注意一点的是,这里的偏移量一定是相对于父节点的。

版权声明:著作权归作者所有。