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

CKEditor5——Position源码分析

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

CKEditor5——Position源码分析

前面我们介绍过CK5模型中的一个关键类,那就是Position的使用,今天我们来看看它的源码,梳理一下方便以后理解:

export default class Position {
	/**
	 * Creates a position.
	 *
	 * @param {module:engine/model/element~Element|module:engine/model/documentfragment~DocumentFragment} root Root of the position.
	 * @param {Array.<Number>} path Position path. See {@link module:engine/model/position~Position#path}.
	 * @param {module:engine/model/position~PositionStickiness} [stickiness='toNone'] Position stickiness.
	 * See {@link module:engine/model/position~PositionStickiness}.
	 */
	constructor( root, path, stickiness = 'toNone' ) {
		if ( !root.is( 'element' ) && !root.is( 'documentFragment' ) ) {
			/**
			 * Position root is invalid.
			 *
			 * Positions can only be anchored in elements or document fragments.
			 *
			 * @error model-position-root-invalid
			 */
			throw new CKEditorError(
				'model-position-root-invalid',
				root
			);
		}

		if ( !( path instanceof Array ) || path.length === 0 ) {
			/**
			 * Position path must be an array with at least one item.
			 *
			 * @error model-position-path-incorrect-format
			 * @param path
			 */
			throw new CKEditorError(
				'model-position-path-incorrect-format',
				root,
				{ path }
			);
		}

		// Normalize the root and path when element (not root) is passed.
		if ( root.is( 'rootElement' ) ) {
			path = path.slice();
		} else {
			path = [ ...root.getPath(), ...path ];
			root = root.root;
		}

		/**
		 * Root of the position path.
		 *
		 * @readonly
		 * @member {module:engine/model/element~Element|module:engine/model/documentfragment~DocumentFragment}
		 * module:engine/model/position~Position#root
		 */
		this.root = root;

		/**
		 * Position of the node in the tree. **Path contains offsets, not indexes.**
		 *
		 * Position can be placed before, after or in a {@link module:engine/model/node~Node node} if that node has
		 * {@link module:engine/model/node~Node#offsetSize} greater than `1`. Items in position path are
		 * {@link module:engine/model/node~Node#startOffset starting offsets} of position ancestors, starting from direct root children,
		 * down to the position offset in it's parent.
		 *
		 *		 ROOT
		 *		  |- P            before: [ 0 ]         after: [ 1 ]
		 *		  |- UL           before: [ 1 ]         after: [ 2 ]
		 *		     |- LI        before: [ 1, 0 ]      after: [ 1, 1 ]
		 *		     |  |- foo    before: [ 1, 0, 0 ]   after: [ 1, 0, 3 ]
		 *		     |- LI        before: [ 1, 1 ]      after: [ 1, 2 ]
		 *		        |- bar    before: [ 1, 1, 0 ]   after: [ 1, 1, 3 ]
		 *
		 * `foo` and `bar` are representing {@link module:engine/model/text~Text text nodes}. Since text nodes has offset size
		 * greater than `1` you can place position offset between their start and end:
		 *
		 *		 ROOT
		 *		  |- P
		 *		  |- UL
		 *		     |- LI
		 *		     |  |- f^o|o  ^ has path: [ 1, 0, 1 ]   | has path: [ 1, 0, 2 ]
		 *		     |- LI
		 *		        |- b^a|r  ^ has path: [ 1, 1, 1 ]   | has path: [ 1, 1, 2 ]
		 *
		 * @readonly
		 * @member {Array.<Number>} module:engine/model/position~Position#path
		 */
		this.path = path;

		/**
		 * Position stickiness. See {@link module:engine/model/position~PositionStickiness}.
		 *
		 * @member {module:engine/model/position~PositionStickiness} module:engine/model/position~Position#stickiness
		 */
		this.stickiness = stickiness;
	}
 }

首先,我们分析构造函数,有三个关键的参数,那就是root,path,以及stickness

首先会检查当前的position的root属性如果不是Element类型或者DocumentFragment类型,那么直接会抛出错误,其次path必须是一个数组,且长度必须大于0。

这里有一个逻辑就是如果传递的root参数不是根节点,那么需要处理归一化root和path,当root是根节点的时候,path就直接复制,如果不是根节点,那么需要将根节点的path补上合并为新的数组。

最后就是将关键的三个属性赋值保存。

这里我们需要理解的path属性,它其实是从根节点到当前位置之间所有节点在父节点中的索引,然后将这些索引构成一个数组。这一点我们只需要看对代码属性的注释就不难分析出来。

我们知道Position还有一个关键的属性offset偏移量:

get offset() {
	return this.path[ this.path.length - 1 ];
}

set offset( newOffset ) {
	this.path[ this.path.length - 1 ] = newOffset;
}

在这里,偏移量就是数组path的最后一个值。这样的话,我们就将偏移量与path建立了关系。

Position还有一个parent属性:

get parent() {
	let parent = this.root;

	for ( let i = 0; i < this.path.length - 1; i++ ) {
		parent = parent.getChild( parent.offsetToIndex( this.path[ i ] ) );

		if ( !parent ) {
			/**
			 * The position's path is incorrect. This means that a position does not point to
			 * a correct place in the tree and hence, some of its methods and getters cannot work correctly.
			 *
			 * **Note**: Unlike DOM and view positions, in the model, the
			 * {@link module:engine/model/position~Position#parent position's parent} is always an element or a document fragment.
			 * The last offset in the {@link module:engine/model/position~Position#path position's path} is the point in this element
			 * where this position points.
			 *
			 * Read more about model positions and offsets in
			 * the {@glink framework/guides/architecture/editing-engine#indexes-and-offsets Editing engine architecture guide}.
			 *
			 * @error model-position-path-incorrect
			 * @param {module:engine/model/position~Position} position The incorrect position.
			 */
			throw new CKEditorError( 'model-position-path-incorrect', this, { position: this } );
		}
	}

	if ( parent.is( '$text' ) ) {
		throw new CKEditorError( 'model-position-path-incorrect', this, { position: this } );
	}

	return parent;
}

我们知道path存在的是当前位置的到根节点的所有节点的offset值,而获取当前位置的parent属性,本质上就是从根节点一直根据索引找下去,然后就可以找到当前位置的parent属性,这里在查找的时候用到了我们前几节讲述的Element的偏移量到索引值,根据索引值找节点的方法,一直找下去,如果存在,且不是Text节点,那么就返回找到的值,否则直接抛出错误。

同时Position类还有一个index属性

get index() {
	return this.parent.offsetToIndex( this.offset );
}

从代码可以看出,其实就是调用父节点的一些方法来实现。因此,只要我们理解了如何寻找父节点,那么这些其他方法也可以循序渐进的推导出来的。好了,大概的理解就是这些,后续遇到问题再具体分析,这一节的功能都是在Node那一部分为基础的,如果不理解的话,可以查看那一部分。

欢迎讨论。

 

 

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

X

欢迎加群学习交流

联系我们