thief系列之二:从获取DOM和增删类中看js如何构造一个类
youngwind opened this issue · 0 comments
要解决的问题
上一节 #60 我们已经知道如何使用IIFE和闭包来处理模块化的问题。现在我们来尝试实现两个功能。
- 获取DOM元素
- 添加删除类
我们回想一下,jquery完成上面的功能大概是这样的。
$('selector');
$('selector').addClass('className');
$('selector').removeClass('className');
我们分析一下。
- $函数接受选择器作为参数,返回DOM元素。
- 返回的DOM元素拥有addClass和removeClass方法。
工厂模式
ok,上最基础的工厂模式。
var T = function (selector) {
// 获取DOM元素
var dom = document.querySelector(selector);
// 添加类
dom.addClass = function (className) {
this.classList.add(className);
};
// 删除类
dom.removeClass = function (className) {
this.classList.remove(className)
};
return dom;
};
缺点
第一,返回的DOM元素无法知道它是从T这个构造函数中来的。
证据:
typeof T('h1') === T;
// return false;
你可能会说,你这不废话吗!这里只用了工厂模式,压根没构造模式,哪儿来的构造函数!
没错,不过我们看看jquery
typeof $('h1') === jQuery
// return true;
看见没?我们现在姑且可以认为jQuery那样做的好处在于起码我知道实例化的对象是从哪儿来的。
第二,每个dom实例的addClass和removeClass方法都会在内存中存一份,并不能共享,造成资源上的浪费。证明依据。
T('h2').addClass === T('h1').addClass
// return false
构造模式
我们先来尝试解决工厂模式的第一个缺点。
// 构造模式
var T = function (selector) {
// 获取DOM元素
this.dom = document.querySelector(selector);
// 添加类
this.addClass = function (className) {
this.dom.classList.add(className);
};
// 删除类
this.removeClass = function (className) {
this.dom.classList.remove(className);
};
};
验证
new T('h1') instanceof T
// return true;
发现第一个问题已经解决。我们来看第二个问题,函数复用问题。
我们把addClass和removeClass抽象成全局函数(注意,这里的全局是指自执行表达式里面的)
(function () {
function addClass(className) {
this.dom.classList.add(className);
}
function removeClass(className) {
this.dom.classList.remove(className);
}
// 构造模式
var T = function (selector) {
// 获取DOM元素
this.dom = document.querySelector(selector);
// 添加类
this.addClass = addClass;
// 删除类
this.removeClass = removeClass;
};
window.T = T;
})(window);
验证:
new T('h1').addClass === new T('h1').addClass
// return true;
缺点
addClass和removeClass被声明为全局。虽然我们通过闭包将这种影响限制在模块内,但是这样子做也总感觉失去了对象的封装性。
原型模式
为了解决构造模式的全局函数声明问题,我们使用prototype来实现所谓的原型模式
shit.....忽然发现光用原型模式居然没法搞出一个demo来。比如一般的原型模式是这样的。
function T(selector) {
}
T.prototype.dom = document.querySelector(selector);
T.prototype.addClass = function (className) {
this.dom.classList.add(className)
};
T.prototype.removeClass = function (className) {
this.dom.classList.remove(className)
};
实际上这是会报错的
Uncaught ReferenceError: selector is not defined
由此我们可以看到原型模式的一个重大缺陷:无法传递参数,也就是说每次构造出来的对象只能是一样的,这能忍?
当然,它还有另外一个重大缺陷,那就是传递引用。由于这里还不涉及到,就不说了。
混合模式
原型模式走不通,原因是因为不能传递参数。但是前面的构造函数可以传参啊,所以结合两者就是混合模式。
// 混合模式 = 构造模式 + 原型模式
// 构造模式定义属性
function T(selector) {
this.dom = document.querySelector(selector);
}
// 原型模式定义方法
T.prototype.addClass = function (className) {
this.dom.classList.add(className)
};
T.prototype.removeClass = function (className) {
this.dom.classList.remove(className)
};
如何把new去掉?
目前我们已经做了很多了,但是还不够。比如,看下面的对比。
$('selector')
new T('selector')
jquery实例化的时候是不需要new关键字的,而我们的T因为包含构造模式,所以得使用new。那么jquery是如何把new去掉的呢?(当然,new $('selector')也是可以执行的)
这里有一篇文章讲得很不错,http://www.cnblogs.com/aaronjs/p/3278578.html
下面我直接给出改造过后的代码
function T(selector) {
return new T.prototype.init(selector);
}
T.prototype = {
init: function (selector) {
this.dom = document.querySelector(selector);
return this;
},
addClass: function (className) {
this.dom.classList.add(className);
},
removeClass: function (className) {
this.dom.classList.remove(className);
}
};
T.prototype.init.prototype = T.prototype;