Javascript:链接 jQuery 等元素
Javascript: Chaining on elements like jQuery
我正在尝试在一定程度上复制 jQuery 的元素操作。现在我发现 .first()
选择器非常有用。我希望能够像这样链接函数;
getElement(selector).first().hasClass(className);
现在,我的进展有 2 个问题(请注意我的代码示例已最小化,所以请不要对 error-handling 发表评论。)
var getElement = function(selector, parent) {
ret = document.querySelectorAll(selector);
this.element = ret;
this.hasClass = function(className) {
className.replace('.', '');
if(this.multiple())
{
console.log('Cannot use hasClass function on multiple elements');
return false;
}
};
this.first = function() {
this.element = this.element[0];
return this;
};
return this;
};
我目前的问题
如果我调用我的函数;
var $test = getElement('.something');
//result: nodelist with .something divs
如果我调用结果中的第一个元素;
$test.first();
//Result: First div, perfect!
但是,现在如果我再次调用 $test
,它将用 first()
的结果替换 elements
属性,这意味着我有 "lost"我的旧价值观。我不想丢失它们,我只想要 first()
函数来实现该特定功能。然后我想再次 $test
到 return 所有元素。
此外,调用 first()
现在将以 undefined
结束,因为 this
中只剩下 1 个元素,因为它已从 object.[=29 中删除了旧元素=]
再次尝试
现在,我还尝试通过 returning first-child 而不是整个 class object;[=29= 来稍微改变一下]
this.first = function() {
return this.element[0];
};
不过,我会
$test.first().hasClass(className);
//Returns ERROR, method hasClass undefined
这是因为 .hasClass
存在于原始 this
上,它不再 returned,因为我现在 returning 元素。
我试图从 jQuery 的图书馆中得到一些东西,尽管那让我更加困惑...
我用谷歌搜索了这个主题,但是我找到的所有 'chaining methods' 解决方案似乎都覆盖了 object 的原始值,这不是我想要的即将发生。另一种解决方案实际上要求我一遍又一遍地 re-initiate object,这对我来说似乎效率不高......感谢任何帮助。我假设我正在以完全错误的方式解决这个问题。
-- 如果您能帮助我,请解释您的解决方案为何有效。我真的觉得如果我理解了这一点,我对 javascript 的理解可以进一步扩展。我只需要解决这个结构性(?)问题。
您可以更新 getElement
,以便在您向它发送元素时它 returns 再次返回。
var getElement = function(selector, parent) {
var ret = null
if (typeof selector === "string") {
ret = document.querySelectorAll(selector);
} else {
ret = selector
}
this.element = ret;
this.hasClass = function(className) {
className.replace('.', '');
if (this.multiple()) {
console.log('Cannot use hasClass function on multiple elements');
return false;
}
};
this.first = function() {
this.element = getElement(this.element[0]);
return this;
};
return this;
};
var test = getElement(".foo");
console.log(test.first())
console.log(test.first().hasClass)
<div class="foo">1</div>
<div class="foo">2</div>
<div class="foo">3</div>
<div class="foo">4</div>
您可以使用 .querySelectorAll()
、展开元素和 Array.prototype.find()
,其中 returns 数组中的第一个匹配项或 undefined
const getElement = (selector = "", {prop = "", value = "", first = false} = {}) => {
const el = [...document.querySelectorAll(selector)];
if (first) return el.find(({[prop]:match}) => match && match === value)
else return el;
};
let first = getElement("span", {prop: "className", value: "abc", first: true});
console.log(first);
let last = getElement("span");
console.log(all);
<span class="abc">123</span>
<span class="abc">456</span>
外部函数中的 this
指的是 window / 全局对象。
相反,return ret
变量本身。
在 inner 函数(成为对象的方法)中,this
以您期望的方式运行。
这是一个替代解决方案,它允许链接,即使在您调用了 first
方法之后:
var getElement = function(selector, parent) {
var ret = typeof selector == 'string' ? document.querySelectorAll(selector)
: selector;
ret.hasClass = function(className) {
if(!this.classList) {
console.log('Cannot use hasClass function on multiple elements');
return false;
} else {
return this.classList.contains(className);
}
};
ret.first = function() {
return new getElement(this[0]);
};
return ret;
};
console.log(getElement('p').length); //2
console.log(getElement('p').first().innerHTML); //abc
console.log(getElement('p').first().hasClass('test')); //true
console.log(getElement('p').first().hasClass('nope')); //fase
console.log(getElement('p').hasClass('test')); //false (multiple elements)
<p class="test">
abc
</p>
<p>
def
</p>
像 first()
这样的方法不应该修改 this
,它应该创建一个新对象和 return。您仅在修改元素的方法中使用 return this;
而不是 returning 从元素派生的信息。
this.first = function() {
return new getElement(this.element[0]);
};
请注意,您必须使用 new getElement
来创建对象,而不仅仅是 getElement
。
这也需要对构造函数进行更改,因此它可以接受选择器字符串或元素:
var getElement = function(selector, parent) {
var ret = typeof selector == "string" ? document.querySelectorAll(selector) : [selector];
...
}
您还应该考虑以适当的 OO 方式执行此操作,方法是将方法放在原型中,而不是在每个对象中定义它们。
var getElement = function(selector, parent) {
var ret = typeof selector == "string" ? document.querySelectorAll(selector) : [selector];
this.element = ret;
};
getElement.prototype.hasClass = function(className) {
className.replace('.', '');
if (this.multiple()) {
console.log('Cannot use hasClass function on multiple elements');
return false;
}
};
getElement.prototype.first = function() {
return new getElement(this.element[0])
};
我认为最简单的方法是 return 一个包含您选择的节点的新 class。那将是最简单的解决方案,因为您真的不想改变任何以前的选择器。
我做了一个小例子,使用一些 ES6 使一些事情更容易使用,它还有一个 $
来启动正在做出的选择。
您会注意到,首先,所做的任何选择都只是调用本机 document.querySelectorAll
,但 return 是一个新的 Node
class。 first
和 last
方法也 return 这些元素。
最后,hasClass
应该对当前节点选择中的所有元素起作用,因此它将迭代当前节点,并检查其中的所有 classes,这个 return这是一个简单的布尔值,因此您不能继续在那里链接方法。
您希望链接的任何方法都应该:
- return
this
对象(当前节点)
- return
this
对象的一个元素作为新节点,因此可以在那里进行任何进一步的操作
const $ = (function(global) {
class Node extends Array {
constructor( ...nodes ) {
super();
nodes.forEach( (node, key) => {
this[key] = node;
});
this.length = nodes.length;
}
first() {
return new Node( this[0] );
}
last() {
return new Node( this[this.length-1] );
}
hasClass( ...classes ) {
const set = classes.reduce( (current, cls) => {
current[cls] = true;
return current;
}, {} );
for (let el of this) {
for (let cls of el.classList) {
if (set[cls]) {
return true;
}
}
}
return false;
}
}
global.$ = function( selector ) {
return new Node( ...document.querySelectorAll( selector ) );
};
return global.$;
}(window));
let selector = $('.foo');
let first = selector.first(); // expect 1
console.log(first[0].innerHTML);
let last = selector.last();
console.log(last[0].innerHTML); // expect 4
console.log( first.hasClass('foo') ); // expect true
console.log( first.hasClass('bar') ); // expect false
console.log( selector.hasClass('foo') ); // expect true
console.log( selector.hasClass('bar') ); // expect true
<div class="foo">1</div>
<div class="foo">2</div>
<div class="foo bar">3</div>
<div class="foo">4</div>
以下是我的处理方法:
- 创建一个构造函数,比如
Search
,负责根据输入查找元素。使用构造函数是正确的 OO 编程,您还可以在原型中定义一次方法,所有实例都可以访问它们。
- 确保上下文 (
this
) 是一个类似数组的对象,具有数字属性和长度,以便您可以轻松地以传统方式遍历每个匹配的元素 (使用 for
循环,[].forEach
等).
- 创建一个函数,比如
getElement
,它将使用构造函数和 return 结果,而不必一直使用 new
关键字。由于函数 return 是我们的构造函数的一个实例,您可以像往常一样链接您想要的方法。
- 方法
first
使用构造函数创建新实例而不是修改原始实例,因为它的作用是return第一个元素,而不是删除第一个元素以外的所有内容。
- 每次你想出一个新的方法,你希望你的对象拥有,你可以简单地将它添加到构造函数的原型中。
片段:
;(function () {
function Search (value) {
var elements = [];
/* Check whether the value is a string or an HTML element. */
if (typeof value == "string") {
/* Save the selector to the context and use it to get the elements. */
this.selector = value;
elements = document.querySelectorAll(value);
}
else if (value instanceof Element) elements.push(value);
/* Give a length to the context. */
this.length = elements.length;
/* Iterate over every element and inject it to the context. */
for (var i = 0, l = this.length; i < l; i++) this[i] = elements[i];
}
/* The method that returns the first element in a Search instance. */
Object.defineProperty(Search.prototype, "first", {
value: function () {
return new Search(this[0]);
}
});
/* The global function that uses the Search constructor to fetch the elements. */
window.getElement = (value) => new Search(value);
/* Create a reference to the prototype of the constructor for easy access. */
window.getElement.fn = Search.prototype;
})();
/* Get all elements matching the class, the first one, and the first's plain form. */
console.log(getElement(".cls1"));
console.log(getElement(".cls1").first());
console.log(getElement(".cls1").first()[0]);
/* ----- CSS ----- */
.as-console-wrapper {
max-height: 100%!important;
}
<!----- HTML ----->
<div id = "a1" class = "cls1"></div>
<div id = "a2" class = "cls1"></div>
<div id = "a3" class = "cls1"></div>
示例:
在这个例子中,我将一个名为 hasClass
的新方法添加到构造函数的原型中。
/* The method that returns whether the first element has a given class. */
Object.defineProperty(getElement.fn, "hasClass", {
value: function (value) {
return this[0].classList.contains(value);
}
});
/* Check whether the first element has the 'cls2' class. */
console.log(getElement(".cls1").first().hasClass("cls2"));
<!----- HTML ----->
<script src="//pastebin.com/raw/e0TM5aYC"></script>
<div id = "a1" class = "cls1 cls2"></div>
<div id = "a2" class = "cls1"></div>
<div id = "a3" class = "cls1"></div>
我正在尝试在一定程度上复制 jQuery 的元素操作。现在我发现 .first()
选择器非常有用。我希望能够像这样链接函数;
getElement(selector).first().hasClass(className);
现在,我的进展有 2 个问题(请注意我的代码示例已最小化,所以请不要对 error-handling 发表评论。)
var getElement = function(selector, parent) {
ret = document.querySelectorAll(selector);
this.element = ret;
this.hasClass = function(className) {
className.replace('.', '');
if(this.multiple())
{
console.log('Cannot use hasClass function on multiple elements');
return false;
}
};
this.first = function() {
this.element = this.element[0];
return this;
};
return this;
};
我目前的问题
如果我调用我的函数;
var $test = getElement('.something');
//result: nodelist with .something divs
如果我调用结果中的第一个元素;
$test.first();
//Result: First div, perfect!
但是,现在如果我再次调用 $test
,它将用 first()
的结果替换 elements
属性,这意味着我有 "lost"我的旧价值观。我不想丢失它们,我只想要 first()
函数来实现该特定功能。然后我想再次 $test
到 return 所有元素。
此外,调用 first()
现在将以 undefined
结束,因为 this
中只剩下 1 个元素,因为它已从 object.[=29 中删除了旧元素=]
再次尝试
现在,我还尝试通过 returning first-child 而不是整个 class object;[=29= 来稍微改变一下]
this.first = function() {
return this.element[0];
};
不过,我会
$test.first().hasClass(className);
//Returns ERROR, method hasClass undefined
这是因为 .hasClass
存在于原始 this
上,它不再 returned,因为我现在 returning 元素。
我试图从 jQuery 的图书馆中得到一些东西,尽管那让我更加困惑...
我用谷歌搜索了这个主题,但是我找到的所有 'chaining methods' 解决方案似乎都覆盖了 object 的原始值,这不是我想要的即将发生。另一种解决方案实际上要求我一遍又一遍地 re-initiate object,这对我来说似乎效率不高......感谢任何帮助。我假设我正在以完全错误的方式解决这个问题。
-- 如果您能帮助我,请解释您的解决方案为何有效。我真的觉得如果我理解了这一点,我对 javascript 的理解可以进一步扩展。我只需要解决这个结构性(?)问题。
您可以更新 getElement
,以便在您向它发送元素时它 returns 再次返回。
var getElement = function(selector, parent) {
var ret = null
if (typeof selector === "string") {
ret = document.querySelectorAll(selector);
} else {
ret = selector
}
this.element = ret;
this.hasClass = function(className) {
className.replace('.', '');
if (this.multiple()) {
console.log('Cannot use hasClass function on multiple elements');
return false;
}
};
this.first = function() {
this.element = getElement(this.element[0]);
return this;
};
return this;
};
var test = getElement(".foo");
console.log(test.first())
console.log(test.first().hasClass)
<div class="foo">1</div>
<div class="foo">2</div>
<div class="foo">3</div>
<div class="foo">4</div>
您可以使用 .querySelectorAll()
、展开元素和 Array.prototype.find()
,其中 returns 数组中的第一个匹配项或 undefined
const getElement = (selector = "", {prop = "", value = "", first = false} = {}) => {
const el = [...document.querySelectorAll(selector)];
if (first) return el.find(({[prop]:match}) => match && match === value)
else return el;
};
let first = getElement("span", {prop: "className", value: "abc", first: true});
console.log(first);
let last = getElement("span");
console.log(all);
<span class="abc">123</span>
<span class="abc">456</span>
this
指的是 window / 全局对象。
相反,return ret
变量本身。
在 inner 函数(成为对象的方法)中,this
以您期望的方式运行。
这是一个替代解决方案,它允许链接,即使在您调用了 first
方法之后:
var getElement = function(selector, parent) {
var ret = typeof selector == 'string' ? document.querySelectorAll(selector)
: selector;
ret.hasClass = function(className) {
if(!this.classList) {
console.log('Cannot use hasClass function on multiple elements');
return false;
} else {
return this.classList.contains(className);
}
};
ret.first = function() {
return new getElement(this[0]);
};
return ret;
};
console.log(getElement('p').length); //2
console.log(getElement('p').first().innerHTML); //abc
console.log(getElement('p').first().hasClass('test')); //true
console.log(getElement('p').first().hasClass('nope')); //fase
console.log(getElement('p').hasClass('test')); //false (multiple elements)
<p class="test">
abc
</p>
<p>
def
</p>
像 first()
这样的方法不应该修改 this
,它应该创建一个新对象和 return。您仅在修改元素的方法中使用 return this;
而不是 returning 从元素派生的信息。
this.first = function() {
return new getElement(this.element[0]);
};
请注意,您必须使用 new getElement
来创建对象,而不仅仅是 getElement
。
这也需要对构造函数进行更改,因此它可以接受选择器字符串或元素:
var getElement = function(selector, parent) {
var ret = typeof selector == "string" ? document.querySelectorAll(selector) : [selector];
...
}
您还应该考虑以适当的 OO 方式执行此操作,方法是将方法放在原型中,而不是在每个对象中定义它们。
var getElement = function(selector, parent) {
var ret = typeof selector == "string" ? document.querySelectorAll(selector) : [selector];
this.element = ret;
};
getElement.prototype.hasClass = function(className) {
className.replace('.', '');
if (this.multiple()) {
console.log('Cannot use hasClass function on multiple elements');
return false;
}
};
getElement.prototype.first = function() {
return new getElement(this.element[0])
};
我认为最简单的方法是 return 一个包含您选择的节点的新 class。那将是最简单的解决方案,因为您真的不想改变任何以前的选择器。
我做了一个小例子,使用一些 ES6 使一些事情更容易使用,它还有一个 $
来启动正在做出的选择。
您会注意到,首先,所做的任何选择都只是调用本机 document.querySelectorAll
,但 return 是一个新的 Node
class。 first
和 last
方法也 return 这些元素。
最后,hasClass
应该对当前节点选择中的所有元素起作用,因此它将迭代当前节点,并检查其中的所有 classes,这个 return这是一个简单的布尔值,因此您不能继续在那里链接方法。
您希望链接的任何方法都应该:
- return
this
对象(当前节点) - return
this
对象的一个元素作为新节点,因此可以在那里进行任何进一步的操作
const $ = (function(global) {
class Node extends Array {
constructor( ...nodes ) {
super();
nodes.forEach( (node, key) => {
this[key] = node;
});
this.length = nodes.length;
}
first() {
return new Node( this[0] );
}
last() {
return new Node( this[this.length-1] );
}
hasClass( ...classes ) {
const set = classes.reduce( (current, cls) => {
current[cls] = true;
return current;
}, {} );
for (let el of this) {
for (let cls of el.classList) {
if (set[cls]) {
return true;
}
}
}
return false;
}
}
global.$ = function( selector ) {
return new Node( ...document.querySelectorAll( selector ) );
};
return global.$;
}(window));
let selector = $('.foo');
let first = selector.first(); // expect 1
console.log(first[0].innerHTML);
let last = selector.last();
console.log(last[0].innerHTML); // expect 4
console.log( first.hasClass('foo') ); // expect true
console.log( first.hasClass('bar') ); // expect false
console.log( selector.hasClass('foo') ); // expect true
console.log( selector.hasClass('bar') ); // expect true
<div class="foo">1</div>
<div class="foo">2</div>
<div class="foo bar">3</div>
<div class="foo">4</div>
以下是我的处理方法:
- 创建一个构造函数,比如
Search
,负责根据输入查找元素。使用构造函数是正确的 OO 编程,您还可以在原型中定义一次方法,所有实例都可以访问它们。 - 确保上下文 (
this
) 是一个类似数组的对象,具有数字属性和长度,以便您可以轻松地以传统方式遍历每个匹配的元素 (使用for
循环,[].forEach
等). - 创建一个函数,比如
getElement
,它将使用构造函数和 return 结果,而不必一直使用new
关键字。由于函数 return 是我们的构造函数的一个实例,您可以像往常一样链接您想要的方法。 - 方法
first
使用构造函数创建新实例而不是修改原始实例,因为它的作用是return第一个元素,而不是删除第一个元素以外的所有内容。 - 每次你想出一个新的方法,你希望你的对象拥有,你可以简单地将它添加到构造函数的原型中。
片段:
;(function () {
function Search (value) {
var elements = [];
/* Check whether the value is a string or an HTML element. */
if (typeof value == "string") {
/* Save the selector to the context and use it to get the elements. */
this.selector = value;
elements = document.querySelectorAll(value);
}
else if (value instanceof Element) elements.push(value);
/* Give a length to the context. */
this.length = elements.length;
/* Iterate over every element and inject it to the context. */
for (var i = 0, l = this.length; i < l; i++) this[i] = elements[i];
}
/* The method that returns the first element in a Search instance. */
Object.defineProperty(Search.prototype, "first", {
value: function () {
return new Search(this[0]);
}
});
/* The global function that uses the Search constructor to fetch the elements. */
window.getElement = (value) => new Search(value);
/* Create a reference to the prototype of the constructor for easy access. */
window.getElement.fn = Search.prototype;
})();
/* Get all elements matching the class, the first one, and the first's plain form. */
console.log(getElement(".cls1"));
console.log(getElement(".cls1").first());
console.log(getElement(".cls1").first()[0]);
/* ----- CSS ----- */
.as-console-wrapper {
max-height: 100%!important;
}
<!----- HTML ----->
<div id = "a1" class = "cls1"></div>
<div id = "a2" class = "cls1"></div>
<div id = "a3" class = "cls1"></div>
示例:
在这个例子中,我将一个名为 hasClass
的新方法添加到构造函数的原型中。
/* The method that returns whether the first element has a given class. */
Object.defineProperty(getElement.fn, "hasClass", {
value: function (value) {
return this[0].classList.contains(value);
}
});
/* Check whether the first element has the 'cls2' class. */
console.log(getElement(".cls1").first().hasClass("cls2"));
<!----- HTML ----->
<script src="//pastebin.com/raw/e0TM5aYC"></script>
<div id = "a1" class = "cls1 cls2"></div>
<div id = "a2" class = "cls1"></div>
<div id = "a3" class = "cls1"></div>