jQuery源码学习笔记系列(三)

时间:2022-12-02 19:14:17

先接着上篇写,也就是,return new jQuery.fn.init( selector, context ); ,从new,这个函数调用时忽略的,再讲到原型。

new仅仅只是函数调用

首先需要注意的是不要将其它语言中的new代入到js中的new中,这里的new,仅仅只是函数调用,只是这个函数调用有些特殊 ,一般会返回一个新对象,仅此而已 ,看代码:
function A(){console.log(‘test’)}
var k = new A()

再看图:
jQuery源码学习笔记系列(三)
这是k,此时k已经是一个对象了,这里再引入到另一个问题:如何创建一个对象?
像这样var c = {},或者像这样var k ={a:3}?当然是都可以,那么如何根据已知对象再创建一个对象?根据已知对象再创建,首先就有两种方式,也就是在cpp里面常说的深拷贝和浅拷贝,浅拷贝通常只是建立一个引用,所以,可以这样c=k,这时改变k中a,c中a也会变(本就是一个值),如果是深拷贝,就要借助js中json,也就是JSON.stringify和JSON.parse配套使用(注意,通常实际使用时要对格式进行检查,或者使用try…catch(因为这两个函数是同步函数)),而在浅拷贝,有一种更加“官方”的操作,利用Object.create()函数,这是一个静态函数:

var k = Object.create({a:4})
k.a//a:4

而在Object.create函数出现之前,通常会用这样的方式:

function f(){}
f.prototype = {a:4}
var k = new f()
k.a //4

值得注意的是,这里不要把new和对象的复制混为一谈,下面一个例子来说明情况:

function A(a){this.a = a}
var k = new A(4)
k.a //4

这样写,是不是很熟悉,就像一个类的构造函数那样?这个时候,对象是存在复制,但是它所复制的(“=”代表的)是通过new创造或者说构造的一个对象,而这个对象被唯一的与k相关联(函数与对象在这里概念要分清楚),所以

var m = new A(8)
m.a//8
k.a //4

这样,就是所谓的new仅仅是函数调用,其中this绑定,在new创建一个对象时,相当于this就绑定到了这个对象上

现在回到正题,也就是那张图:不过首先,还是要引入别的知识:

类只是设计模式一种,不要强求

面向对象的思想固然很赞,但是不要太过拘泥,在js中设计模式可以自己去实现,但是底层就没有类这样的概念,取而代之的是原型,所以js常常被称为基于原型的编程。
首先说继承,这个在类里面很常见,爸爸儿子什么的,还有一些虚函数重载覆盖什么的,学过cpp应该知道,继承有一个弊端就是会有菱形问题,因为其可以允许多继承,但js中可以继承,但是却不允许多继承。另外,js有一种想法就是不在意对象类型,只在意对象作用,被称做“鸭式辩型”(定义像鸭子一样走路然后嘎嘎叫的就是鸭子,只要满足这两点要求就可以认为它是鸭子(不管是不是真的)),而这两点,造就了js中的原型链,原型简单来说就是像类中儿子与爸爸这样的角色关系,只不过是无性繁殖什么的,然后原型对象就是当前实例对象所继承其属性的对象,又因为js不追求类型,所以,为了简化或者统一,所有正常对象都有同一个祖先的原型对象(非正常一个是这个祖先自己,另一个是奇怪null对象(对的,null是一个对象)),好了概念就先到这。
js中与原型密切相关的几个东西:prototype,原型对象,Object.prototype,_ proto _ 一一来说。
首先,那个祖先的原型对象,就是Object.prototype,就像Object.create()是一个静态函数一样,Object.prototype也是一个早就写好了的对象,对的,原型对象,它是一个对象,这个对象,就是上图中第二个proto所引用的的对象,里面包含了一系列方法,这些方法通常是一个对象所需要的基本方法,比如toString(),所以:

var k = Object.create(null)
k.a = 3
k //简单的{a:3},无其它方法

_ proto _ 就是指向当前对象所继承对象的引用,所以:

var k = {a:4,b:5}
var m = {}//proto指向Object.prototype,
m._ proto _=k
m //指向k,而k又指向Object.prototype,所以也合理

不过注意,能够设置proto是Object.prototype做的,所以如果像上个例子那样传入null是修改不了的
这样的指向构成了原型链,下面一个例子可以看到对属性的读取与设置:

var k = {a:8}
var d ={}
d._ proto _ = k
d.a //8
d.a =9
k.a //8
d // {a:9} …{a:8}

在这样一个例子中,当是作为查找时会沿着原型链向上查找,直到找到或者原型指向null(Object.prototype._ proto _= null),然后返回,但是设置时却不会去设置来自继承的属性,而是会重新在本对象中设置属性

原型的基本知识就是这样,虽然还和类有差距,但实际上已经可以做到模仿类的效果的”伪类“,但是,不要太过拘泥类,那只是一个设计模式而已,
所以,这个return new jQuery.fn.init(selector,context),就是返回了一个对象,

而这里再提一下prototype,这个很有迷惑性,看起来和Object.prototype差不多,但是,prototype实际上是函数的一个属性,而设置这个很大程度上是为了new,具体看上面示例

回到正题,代码

先跳过正则表达式,直接来看jQuery.fn = jQuery.prototype = {}
上一篇提到,jQuery是一个接口函数,设置了其prototype并且给了其一个别名(或者说给一个新变量赋值),再看这个对象内部:

jquery: version,
constructor: jQuery,
length: 0,

constructor是构造不假,但一般用处不大,其和new关系还有一些,这几个设置很普通,接着看,是一系列的方法,我选取一些有代表性的:

get: function( num ) {
// Return all the elements in a clean array
if ( num == null ) {
return slice.call( this );
}
// Return just the one element from the set
return num < 0 ? this[ num + this.length ] : this[ num ];
},

首先silce.call(this),就是上一篇关于函数调用提到的
而get方法则给出了一个可以填负数的,也就是a[-1]表示倒数第一个
代码继续走,可以看到一些调用函数使用的callback,也就是回调,这个先等等,回调是下一篇的主要内容,先看别的。不过这里开始实现功能了,就不能那样一行行看代码了,而是首先思考下一个别的东西:

jQuery的优点

这才是源码学习的核心,首先它是有优点的,不管是功能上还是性能上,而如何实现这些,就是学习源码了。
首先,说一个简单也是核心的优点:便于查找以及后续操作。
这一点是html结构的日益复杂多样,从简单的各类标签到流行div span再到各种奇怪的语义化标签,单纯的原生js难以满足需求,所以下面源码学习关注就是jQuery如何实现这些。也就是,先撇开其它功能,专注在实现查找以及后续操作上面,下一篇将会开始针对性的代码选择。