jQuery 是一个快速、简洁的 JavaScript 框架,是继 Prototype 之后又一个优秀的 JavaScript 代码库(或 JavaScript 框架)。jQuery 设计的宗旨是“write Less,Do More”,即倡导写更少的代码,做更多的事情。它封装 JavaScript 常用的功能代码,提供一种简便的 JavaScript 设计模式,优化 HTML 文档操作、事件处理、动画设计和 Ajax 交互。
如此优秀的一个代码库,即便没有以前流行,也是有很多地方值得学习的。
本文并非讲解jquery源码,而是记录从源码学习到的思想、技术等
构造函数与继承
JavaScript是一门面向对象的编程语言,但是与传统面向对象语言不同的是,JavaScript没有提供class关键字,并且JavaScript的是基于原型继承的面向对象语言。那么,在JavaScript中编写类的过程,必定与传统的面向对象语言有所区别。
传统的类与继承
在ES6之前,都是通过创建构造函数的方式来创建一个类,比如:
// Book类的原型
const BookPrototype = {
getName() {
return this.name
},
}
// Book类
function Book(name, author, content) {
this.name = name
this.author = author
this.content = content
}
// Book类继承自 BookPrototype
Book.prototype = BookPrototype
const book = new Book('金瓶梅', '佚名', '此处省略999999999字')
console.log(book.getName())BookPrototype为Book类的原型(基类),使用new关键字创建的Book实例,都会包含BookPrototype里面的属性和方法。这里刚开始然我有些迷惑,参考经典的面向对象语言,基类应该也是一个类,这里却是一个对象字面量。首先,需要深刻明白一点: JavaScript 中,一切皆是对象。比如一个函数,我们可以给它加上任何的属性
function bar() {}
bar.sayHi = function() {
console.log('hi~')
}
bar.sayHi()
// hi~其次,也是这里的迷惑点,JavaScript是基于原型的继承,意思就是,一个构造函数要继承某些属性方法,只需要将这些方法放到一个容器(对象)里,再赋值给prototype属性就可以在实例中访问。当我们需要创建静态方法/变量的时候,可以直接在构造函数上绑定(一切皆对象)。在 JavaScript 中,静态方法不会被继承。
function Book(name, author, content) {
this.name = name
this.author = author
this.content = content
}
Book.prototype.getName = function() {
return this.name
}
// 静态方法
Book.foo = function() {
console.log('foo')
}
Book.foo()ES6 中的类与继承
在ES6标准中,提出了class关键字,我们可以使用类似于Java创建类的语法来创建一个JavaScript类。
class Book {
constructor(title, author, content) {
this.title = title
this.author = author
this.content = content
}
getName() {
return this.title
}
}
const book = new Book('金瓶梅', '佚名', '此处省略999999999字')
console.log(book.getName()) // 金瓶梅我们可以直接通过extends关键字进行类的继承
class StoryBook extends Book {
constructor(title, author, content, storyType) {
super(title, author, content)
this.storyType = storyType
}
getStoryType() {
return this.storyType
}
}
book = new StoryBook('金瓶梅', '佚名', '此处省略999999999字', 'fairyTale')
book.getName() // 金瓶梅
book.getStoryType() // fairyTaleES6中的class和extends实际上都是语法糖,根本上还是通过前一节中的方法来创建类和继承类。JavaScript基于原型的继承方式,赋予了更多的灵活性,jQuery的构造函数就是一个非常好的实例。
jQuery 入口
从jQuery最基本的使用出发,会发现jQuery好像不是一个构造函数。确实是这样,当我们获取一个元素const box = $('.box1'),将 box 打印出来看看。
从打印结果可以看出几个东西:
box对象是jQuery.fn.init的一个实例- 改实例是一个包含类数组集合对象
- 改对象有一个
prevObject属性,指向了两一个对象的引用
接下来就分别从这几点出发来分析jQuery源码。
jQuery 构造函数
通常情况下,我们会以函数调用方式($('selector'))获取一个jQuery对象,同时,我们也可以使用new $('selector')来获取jQuery实例,我们先来思考这一个问题:如何同时满足函数调用和构造函数调用。实际上不需要太多思考就能想到如下代码:
function myQuery(selector) {
if (!(this instanceof myQuery)) {
return new myQuery(selector)
}
// ...
return this
}但是我们发现jQuery并没有这样写,通过观察发现我们得到的是一个jQuery.fn.init的实例,下面就开始进入源码看看jQuery.fn和jQuery.fn.init是个什么东西。
// 源码
jQuery = function(selector, context) {
// The jQuery object is actually just the init constructor 'enhanced'
// Need init if jQuery is called (just allow error to be thrown if not included)
return new jQuery.fn.init(selector, context)
}这是jQuery的大门,所有故事从这里开始,至此我们知道了jQuery构造函数实际上是个工厂函数,它返回了另一个类的实例。接下来继续看看jQuery.fn
jQuery.fn = jQuery.prototype = {
// The current version of jQuery being used
jquery: version,
constructor: jQuery,
// The default length of a jQuery object is 0
length: 0,
// ...
}
init = jQuery.fn.init = function(selector, context, root) {
// ...
return this
}
// Give the init function the jQuery prototype for later instantiation
init.prototype = jQuery.fn可以看出jQuery.fn就是jQuery.prototype的一个引用,而init是原型上的一个构造函数。里面有非常核心的一行代码init.prototype = jQuery.fn。这行代码将jQuery的原型共享给了init构造函数,jQuery和init的原型是同一个,这样jQuery对象就能访问jQuery原型上的方法和变量。
到这里基本清楚了jQuery创建实例到底是怎么一回事,至于和我们刚开始想的不一样,主要原因是更方便的进行对象的初始化操作。
jQuery 插件
说到了原型顺便再看一下jQuery的插件,jQuery的插件开发基本上使用过jQuery的人都知道怎么操作,分为两种方式,一种方式是直接在$.fn上面添加方法,另一种是调用$.fn.extend()方法。前面这两种方法都是对jQuery的原型进行扩展,在其实例中才能使用,还有一个$.extend()方法,是用来扩展jQuery的静态方法。
// 源码
jQuery.extend = jQuery.fn.extend = function() {}源码中,jQuery.extend和fn.extend引用的是同一个方法,但是作用的上下文却不同。这里是非常巧妙的一个点,当我们直接使用$.extend()时添加的是静态方法,而是用来$.fn.extend()时添加的是实例方法。
小结
jQuery并没有非常高级的技术,内部原理都是非常简单的逻辑封装,但是整个内部架构设计的非常好,有许多值得学习的巧妙设计。