<script>
// 定义一个名为A的构造函数,接受selector和context参数
var A = function (selector, context) {
// 返回一个新的A.fn.init实例
return new A.fn.init(selector, context);
}
// 设置A的原型和fn属性
A.fn = A.prototype = {
// 强化构造器:
// 当显式地重写 A.prototype(如通过 A.fn = A.prototype = {...} 赋值时),
// 默认情况下 A.prototype.constructor 会丢失,变为指向 Object.
// 因此,需要手动将 constructor 重新指向 A,以保持逻辑的正确性。
constructor: A,
// 初始化方法,接受selector和context参数
init: function (selector, context) {
// 初始化length属性为0
this.length = 0;
// 设置默认上下文为document
context = context || document;
// 根据selector的不同情况,执行不同的DOM查询
if (~selector.indexOf('#')) {
// 如果是ID选择器
this[0] = context.getElementById(selector.slice(1));
this.length = 1
} else if (~selector.indexOf('.')) {
// 如果是类选择器
const elements = document.getElementsByClassName(selector.slice(1));
for (let i = 0; i < elements.length; i++) {
this[i] = elements[i];
}
this.length = elements.length;
} else {
// 如果是标签选择器
const elements = context.getElementsByTagName(selector);
for (let i = 0; i < elements.length; i++) {
this[i] = elements[i];
}
this.length = elements.length;
}
// 设置selector和context属性
this.selector = selector;
this.context = context;
// 返回当前实例
return this;
},
// 返回实例的长度
size: function () {
return this.length;
},
// 初始化length属性
length: 0,
// 增强数组。
push: [].push,
splice: [].splice,
sort: [].sort
}
// 设置A.fn.init的原型为A.fn
// 在 JavaScript 中,构造函数的实例会继承构造函数的原型 (prototype),
// 这使得实例可以访问构造函数原型上的方法。
// A.fn = A.prototype = { ... } 设置了 A 构造函数的原型为 A.fn,
// 使得 A 的实例可以访问 A.fn 中定义的方法(如 init 方法、size 方法等)。
// 但 init 是作为 A.fn.init 构造函数的 prototype 属性来定义的,而不是直接在 A.fn 上定义的。
// 所以,A.fn.init.prototype = A.fn; 是为了确保通过 new A.fn.init() 创建的实例能够继承 A.fn 中的所有方法。
A.fn.init.prototype = A.fn;
// 扩展方法,用于扩展A或A.fn
// 对 A 的方法扩展是 静态方法,直接用A.XXX() 调用。
// 对 A.fn 的方法扩展是 实例方法。用 obj = new A(); obj.xxx() 实例对象调用。
A.extend = A.fn.extend = function () {
var i = 1, len = arguments.length, target = arguments[0], j;
// 如果只有一个参数,则将target设为this
if (i == len) {
target = this;
i--;
}
// 遍历参数对象,将属性复制到target
for (; i < len; i++) {
for (j in arguments[i]) {
target[j] = arguments[i][j];
}
}
// 返回扩展后的对象
return target;
}
// 添加事件监听方法
A.fn.extend({
on: (function () {
// 根据浏览器支持情况,选择事件监听方法
if (document.addEventListener) {
return function (type, fn) {
var i = this.length - 1;
for (; i >= 0; i--) {
this[i].addEventListener(type, fn, false);
}
return this;
}
} else if (document.attachEvent) {
return function (type, fn) {
var i = this.length - 1;
for (; i >= 0; i--) {
this[i].attachEvent('on' + type, fn);
}
return this;
}
} else {
return function (type, fn) {
var i = this.length - 1;
for (; i >= 0; i--) {
this[i]['on' + type] = fn;
}
return this;
}
}
})()
})
// 驼峰命名方法,用于将CSS属性名转换为驼峰命名
A.extend({
camelCase: function (str) {
return str.replace(/-(\w)/g, function (all, letter) {
return letter.toUpperCase();
})
}
})
// CSS方法,用于设置或获取CSS属性
A.fn.extend({
css: function () {
var args = arguments, len = args.length;
// 根据参数数量和类型,执行不同的逻辑
if (this.length < 1) {
return this;
}
if (len === 1) {
if (typeof args[0] === 'string') {
if (this[0].currentStyle) {
return this[0].currentStyle[A.camelCase(args[0])];
} else {
return getComputedStyle(this[0], false)[A.camelCase(args[0])];
}
} else if (typeof args[0] === 'object') {
for (var i in args[0]) {
for (var j = this.length - 1; j >= 0; j--) {
this[j].style[A.camelCase(i)] = args[0][i];
}
}
}
} else if (len === 2) {
for (var j = this.length - 1; j >= 0; j--) {
this[j].style[A.camelCase(args[0])] = args[1];
}
}
return this;
}
})
// attr方法,用于设置或获取DOM元素的属性
A.fn.extend({
attr: function (name, value) {
var arg = arguments, len = arg.length;
if (this.length < 1) {
return this;
}
if (len === 1) {
if (typeof arg[0] === 'string') {
return this[0].getAttribute(arg[0]);
} else if (typeof arg[0] === 'object') {
for (var i in arg[0]) {
for (var j = this.length - 1; j >= 0; j--) {
this[j].setAttribute(i, arg[0][i]);
}
}
}
} else if (len === 2) {
for (var j = this.length - 1; j >= 0; j--) {
this[j].setAttribute(arg[0], arg[1]);
}
}
return this;
}
})
// html方法,用于设置或获取DOM元素的HTML内容
A.fn.extend({
html: function (html) {
var arg = arguments, len = arg.length;
if (len === 0) {
return this[0] && this[0].innerHTML;
} else {
for (var j = this.length - 1; j >= 0; j--) {
this[j].innerHTML = arg[0];
}
}
return this;
}
})
</script>
问题:为什么在 A 的构造函数中要返回 A.fn.init(selector, context) 的实例?
A
是一个构造函数,而 A.fn.init
是实际的构造函数,用来执行选择器的初始化和 DOM 操作。A
的构造函数中返回 new A.fn.init()
的实例,主要目的是为了创建一个可以处理 DOM 元素的对象实例,并确保可以使用类似 jQuery 的链式调用方式。下面详细分析为什么要这么做:
1. 为了支持链式调用
- 在现代 JavaScript 库(如 jQuery)中,通常会支持链式调用,允许开发者对同一个对象连续调用多个方法。
A.fn.init
实际上是构造函数,它执行 DOM 查询并返回一个具有相关功能的对象。通过new A.fn.init()
创建的实例会附带一个方法集合(比如.size()
、.push()
等),可以继续在同一个实例上调用其他方法。- 比如,你可以写如下代码:
这个链式调用依赖于var obj = new A('#id').size().push('item');
A.fn.init
的构造函数返回的对象保持对实例的引用,允许连续调用A.fn.init
或A.fn
上定义的其他方法。
2. A
和 A.fn.init
的关系
A
是外部可用的构造函数,用来初始化和返回一个处理 DOM 元素的对象实例。A.fn
是一个空对象,表示A
构造函数的原型,存放A.fn.init
等实例方法。A.fn.init
是实际负责 DOM 查询和实例化的构造函数。new A.fn.init()
用来执行选择器逻辑,并返回一个包含 DOM 元素的实例对象。
3. 避免重复代码
A.fn.init
被设计成执行所有与 DOM 查询和元素操作相关的逻辑。通过返回new A.fn.init(selector, context)
,我们避免了在A
构造函数中重复编写查询和初始化代码。A
仅仅作为外部接口,将具体的查询逻辑委托给A.fn.init
来处理。
4. 保持对象的正确性
- 在构造函数
A
内部使用new A.fn.init(selector, context)
是为了确保返回的对象是A.fn.init
的实例,并且它具有正确的原型链。 A.fn.init.prototype = A.fn;
这行代码确保了A.fn.init
的实例(即new A.fn.init()
创建的对象)能够访问A.fn
中的方法。这样,无论是通过new A()
还是new A.fn.init()
创建的对象,都能够继承A.fn
上的方法,保持一致的行为。
5. 处理 selector
和 context
参数
A.fn.init
的构造函数负责根据选择器和上下文来初始化 DOM 查询。它会根据选择器字符串(如#id
、.class
、div
)来选择 DOM 元素,并将其存储在实例对象的length
属性及索引属性中。- 返回
new A.fn.init(selector, context)
的实例能够确保selector
和context
正确传递并处理,且每次实例化都能返回一个新对象。
总结:
返回 new A.fn.init()
的实例有几个核心目的:
- 支持链式调用:通过返回一个包含相关方法的对象实例,允许开发者对返回的对象进行连续操作。
- 分离关注点:
A
构造函数负责创建实例并返回,而实际的初始化逻辑(如 DOM 查询)则委托给A.fn.init
。 - 保持原型链一致性:通过正确的原型设置,确保返回的实例能够继承并访问到定义在
A.fn
上的方法。 - 复用逻辑:避免代码重复,通过
new A.fn.init()
委托给A.fn.init
进行处理。