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那一部分为基础的,如果不理解的话,可以查看那一部分。

欢迎讨论。

 

 

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

相关推荐

Android dex文件反编译为Java源码

工具准备 dex2jar:国人写的一个dex反编译为java的工具jd-gui:查看java源码的gui工具反编译步骤步骤一把test_apk-debug.apk里的classes.dex转换为test_apk-debug_dex2jar.jard2j-dex2jar.sh -f -o output_jar.jar apk_to_decompile.apk

JavaScript使用for...in迭代数组分析

for...in与其说是迭代,更恰当的说法应该是枚举。其目的是用来枚举object对象的属性,包括对象所继承的属性。有部分人会使用for...in来迭代数组,这是一种误用。以下对使用for...in迭代数组分析。问题一var a = ['a','b']; a[5] = 'e'; for (var x in a

Android安装错误App not installed原因分析

App not installed可能原因:App与Android设备或版本不兼容使用了不同的证书签名不同版本的apk,这会导致重新安装失败。app的签名不正确,检查下是否与选择的Signature Version相关v2为Android 7新增的签名方式。可以参考APK signature scheme v2

使用awk分析nginx访问日志access.log的ip

access.log为nginx的访问日志,默认路径在/var/log/nginx/access.log 分析access.log的ip命令如下:awk '{print $1}' access.log |sort|uniq -c|sort -n 命令里使用awk过滤出访问的ip使用sort对ip排序对排序后的ip进行统计,统计每一个ip访

Java源码分析:产生随机数Random与ThreadLocalRandom的区别

Java用于产生随机数的方法主要有两种:java.util.Random和java.util.concurrent.ThreadLocalRandom。Random从Jdk 1.0开始就有了,而ThreadLocalRandom是Jdk1.7才新增的。简单从命名和类所在的包上看,两者的区别在于对并发的支持。RandomRandom是一个伪随机数生成器,它内置了一个种子数seed。获取随机

Rxjs expand的用法分析

Rxjs的expand()函数声明:public expand(project: function(value: T, index: number), concurrent: number, scheduler: Scheduler): Observable expand()会递归调用project函数,project函数把源值映射为一个Observable,每次递归

(转)Android 5.1.1 源码目录结构

转自:http://blog.csdn.net/tfslovexizi/article/details/51888458最近公司培训新同事,我负责整理一点关于android的基础知识,遥想当年,刚接触android,也是一头雾水,啥都不懂,就是靠看文档和视频,对android有一个初步了解,然后就通过查看源码,才有更深入的了解。android有成千上万,说太少了,是成百万上亿的代码,当然要全部都了

JIT的分层编译和逃逸分析

JIT到底在Java的运行中发挥了什么作用呢?根据查阅到的资料,一个作用是做分层编译,一个是做对象的逃逸分析。对于循环体中的代码,循环到一定的程度的时候,就会被再次被编译,编程执行速度更加迅速的代码。对于新建的对象,讲过逃逸分析,如果数据不会逃逸,则将数据放在栈上,不再在heap上新建这个对象。这样的好处是:避免了在堆上新建的锁堆导致的资源损耗不需要GC

CKEditor5——模型理解(五:Position, Range, Selection)

今天我们继续学习CK5中模型的一些知识,主要包括:Position, Range, Selection首先,我们需要知道:position表示模型树中的一个位置。模型的位置有两部分组成:root,path。即位置由其根和该根中的路径表示。位置基于偏移量,而不是索引。这意味着两个文本节点 foo 和 bar 之间的位置偏移为 3,而不是 1。由于模型中的位置由位置根和位置路径表示,因此可以创建不存在