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相加即可。注意一点的是,这里的偏移量一定是相对于父节点的。

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

相关推荐

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——模型理解(二:Node)

上一节我们理解了基本的CK5的模型基本信息,今天我们来学习一些模型的API。节点说明首先,需要理解的就是模型的节点。在这一点上,CK5的模型节点和dom的节点有点类似,也有一些不同。我会在文章中一一介绍。节点是模型树的基本结构。它是模型中不同节点类型的一种抽象。这里需要指出的一点是:如果一个节点从模型树中分离出来,你可以使用它的 API 来操作它。但是,非常重要的是,已经附加到模型树的节点只能通过

CKEditor5——模型理解(三:Element Text)

在上一节,我们学习了CK5中模型节点Node的API,今天我们学习另一个常用的API:Element。元素节点说明element表示模型的元素节点类型,它包含一个拥有名称和子节点的节点类型,继承自Node类。元素属性说明1、name,元素的名称举个例子哈,段落的名称是paragraph,代码块的名称是codeBlock等等。2、childCount, 子元素的数目这里指的是此元素节点包含的子元素的