所有文章搬运自我的个人主页:sheilasun.me

引子

最近打算试试看看jQuery的源码,刚开个头就卡住了。无论如何都理解不了jQuery源码入口部分中的

return new jQuery.fn.init( selector, context )

看了好多帖子都没看懂,觉得自己很蠢,心里很苦,吃宵夜都不香了。昨晚去游泳,游完8*100后靠在池壁上喘气,有人从我旁边出发,水花溅起的瞬间,我突然,想通了!这大概就是回光返照 (划掉)福至心灵吧!

下面一点点地说下我对jQuery入口源码的理解。

自执行的匿名函数

jQuery源码最外层的结构如下:

  1. (function(window,undefined){
  2. ...
  3. })(window);

任何库的引入都得做到不污染全局变量,得有自己的命名空间。上面的自执行匿名函数就可以做到这点,把所有库私有的变量和方法,都包到一个私有的空间内,允许外界访问的属性或方法可以挂载到window上。

例如下面这段代码:

  1. (function(){
  2. var count=0;
  3. var addOne=function(){
  4. alert(count++);
  5. };
  6. window.outerAddOne=addOne; //挂到window上外界方可访问
  7. })();
  8. outerAddOne();//alert "0"
  9. console.log(count);//error
  10. console.log(addOne);//error

内部定义的count变量以及addOne方法,外部环境下是无法访问到的,但是在window上挂载一个方法outerAddOne,指向addOne,外界就可以访问到了。

OK,了解了这个自执行匿名函数的作用,这里还有两个问题。

第一,为什么要传入window?

看了上面的outerAddOne这个例子,就会发现,不传入window也没什么嘛,照样可以把方法挂到window身上啊。

两个原因:

首先,从代码压缩混淆的角度考虑。

我们用线上工具来压缩混淆下面这段示例代码:

  1. function say(){
  2. var name="naima";
  3. window.description="hi "+name;
  4. }

压完混完后瘦了一点:

  1. function say(){var a="naima";window.description="hi "+a}

看到没有,用a代替了name,但是window既不是声明的局部变量也不是参数,是不会被压缩混淆的,所以将window作为参数传入可解决这个问题。

其次,传入window参数,就可以不用沿着作用域链一层层向上查找直到顶层作用域去获取window对象了,访问更快了。

第二,为什么要传入undefined?

undefined并不是JS中的关键字,在IE8及以下中是可以对其重新赋值的。

  1. var undefined="new value";
  2. alert(undefined);//alert “new value"

在参数列表中给出undefined参数,但是不传入值,那么这个参数值就是undefined值了。

jQuery对象的构建

先看jQuery源码中如何对jQuery赋值的:

  1. jQuery = function( selector, context ) {
  2. // The jQuery object is actually just the init constructor 'enhanced'
  3. return new jQuery.fn.init( selector, context, rootjQuery );
  4. }

我就是被new jQuery.fn.init()这里弄晕了,先在这里暂停,回想一下平常我是如何使用jQuery的($即对应‘jQuery'):

  1. $('body').css('background','red');
  2. $.parseJSON('{}');

要实现这两种调用,$('body')应该是一个实例对象,css是每个实例共享的方法,是原型上的方法。而$则是一个类,parseJSON则是类的静态方法。

接下来,我们试着往这个结果上靠。

如何不用new关键字得到jQuery对象

回想一下平常我都是怎么构建实例对象的,通常我会这样写一个Prince类:

  1. function Prince(name){
  2. this.name=name;
  3. this.body="human";
  4. }
  5. Prince.prototype.change=function(){
  6. this.body="frog";
  7. };

然后我会这样去获取一个Prince实例对象:

  1. var prince=new Prince("Harry");
  2. prince.change();

如果我年纪大了忘记用new关键字了,程序就报错了:

  1. var a=Prince('harry');
  2. a.change();//error,"Cannot read property 'change' of undefined"

除了调用方法会出错之外,window还被挂载了两个变量上去,何其无辜。

但是获取jQuery对象(以下简称JQ对象)用new和不用new都可以,返回的是一样样的。

  1. console.log($('*').length);//14
  2. console.log(new $('*').length);//14

为了做到这点,我们很容易想到需要在构造函数内部返回对象。引用下我在另一篇博文JavaScript中的普通函数与构造函数里写的:

构造函数有return值怎么办?

构造函数里没有显式调用return时,默认是返回this对象,也就是新创建的实例对象。

当构造函数里调用return时,分两种情况:

1.return的是五种简单数据类型:String,Number,Boolean,Null,Undefined。

这种情况下,忽视return值,依然返回this对象。

2.return的是Object

这种情况下,不再返回this对象,而是返回return语句的返回值。

所以我们应该在jQuery构造函数内部去返回一个对象,这样就可以不用new的方式去创建JQ对象了,其实这时候,构造函数就相当于一个工厂函数了。

那么核心问题来了。

该返回什么样的对象?对于这个对象有何要求?

这个对象必须可以调用jQuery.prototype上的方法。

我们使用或自己写jQuery插件的时候会经常遇到$.fn这个对象,很多插件都是通过扩展这个对象来实现的。

$.fn其实对应着jQuery.prototype,$和fn分别是jQuery和prototype的简写方式,只要我们把方法扩展到这个原型对象身上,通过$()获取的JQ对象都是可以访问到方法的。

例如:

  1. $.fn.greeting=function(){alert('hi')};
  2. $('body').greeting();//alert 'hi'

所以,工厂函数内部返回的对象一定要可以调用jQuery.prototype上的方法。

是时候看John Resig到底是怎么做的啦。

jQuery源码

  1. jQuery = function( selector, context ) {
  2. return new jQuery.fn.init( selector, context, rootjQuery );
  3. },
  4. jQuery.fn = jQuery.prototype = { //fn即对应prototype
  5. constructor: jQuery,
  6. init: function( selector, context, rootjQuery ) {
  7. ...
  8. return this;
  9. }
  10. ...
  11. }
  12. jQuery.fn.init.prototype = jQuery.fn;

在chrome里调试时候添加JQ对象的watch,会看到类似如下的结果:

  1. $('*'): n.fn.init[14]

看到上面这段源码,原因就很明显了,其实我们所说的JQ对象根本就是init函数的实例对象,而init则是jQuery原型上的一个对象,它本身是没有什么方法的,全靠从jQuery原型上拿。

"jQuery.fn.init.prototype = jQuery.fn"这句很重要,它将init的原型指向jQuery的原型,所以JQ对象才可以访问‘css'、'show'、'hide'这些写在jQuery.fn上的方法。

我们可能会有疑问,为何要从init这绕这么一大圈来访问jQuery的原型,而不是直接返回一个jQuery实例直接通过这个实例来访问自身原型?比如说代码可以写成这样:

  1. jQuery = function( selector, context ) {
  2. return new jQuery();
  3. }

问题很明显,这样做只会大家一起死,死在循环里。

好,那我接受init的存在,但是我这样写难道不可以吗?

  1. jQuery = function( selector, context ) {
  2. return jQuery.fn.init();//不同点在于去掉了new关键字
  3. }

让我们做点动作来证明加上new是有用的。

  1. jQuery = function( selector, context ) {
  2. return jQuery.fn.init();
  3. },
  4. jQuery.fn = jQuery.prototype = {
  5. init: function() {
  6. this.name='sheila';
  7. return this;
  8. },
  9. anotherName:'sunwukong'
  10. };
  11. var jq=jQuery();
  12. console.log(jq.anotherName);//"sunwukong"
  13. console.log(jq.name);//"sheila"

上面这段代码是为了说明this的作用域问题,其不仅能访问init函数内部,还能向上一层到fn对象。我听人家说,做框架的,作用域要独立才好呢。

给它加上new关键字:

  1. ...
  2. return new jQuery.fn.init();
  3. ...
  4. console.log(jq.anotherName);//undefined
  5. console.log(jq.name);//"sheila"

这样this的作用域就独立出来了。

经博友评论提醒,加不加new还牵涉到一个更重要的问题:返回的对象究竟是谁。不加new的情况下,'jQuery.fn.init()'相当于调用方法,this指向的以及最后返回的都是同一个jQuery.fn对象,$('body')和$('p')就没有区分了。显然,这是不合理的。而加了new,就是每次用构造函数实例化了一个新对象,彼此都是不同的。

有任何不妥之处或错误欢迎各位指出,不胜感激~

题外话

经常看别人的博客,有些表述方式实在独特而有趣,每每读来都觉妙趣横生,哑然失笑。不禁心生羡慕,技术过硬,知识面广还写得一手好文章,赞!

想起在学校时每次我们做presentation,上台第一句,“大家好,我今天讲的题目是……”,然后幻灯片一页页划过去,“历史背景”,“研究现状”,“我使用的方法”……导师都听得一脸崩溃,“nonono,不要,不要这样,你们这样讲,不会有人有耐心听下去的……我们要像说故事一样娓娓道来,抓住听众的注意力,一点点引入……”于是以后我都尽量按照“说故事”这个思路去讲,最后毕业答辩的时候,一个老师说,“为什么我觉得你像故宫导览哈哈哈哈”……

果然还是没有掌握表述的技巧啊。

jQuery源码中的“new jQuery.fn.init()”什么意思?的更多相关文章

  1. jQuery 源码解析二:jQuery.fn.extend=jQuery.extend 方法探究

    终于动笔开始 jQuery 源码解析第二篇,写文章还真是有难度,要把自已懂的表述清楚,要让别人听懂真的不是一见易事. 在 jQuery 源码解析一:jQuery 类库整体架构设计解析 一文,大致描述了 ...

  2. Jquery源码中的Javascript基础知识(一)

    jquery源码中涉及了大量原生js中的知识和概念,文章是我在学习两者的过程中进行的整理和总结,有不对的地方欢迎大家指正. 本文使用的jq版本为2.0.3,附上压缩和未压缩版本地址: http://a ...

  3. jquery源码中noConflict(防止$和jQuery的命名冲突)的实现原理

    jquery源码中noConflict(防止$和jQuery的命名冲突)的实现原理 最近在看jquery源码分析的视频教学,希望将视频中学到的知识用博客记录下来,更希望对有同样对jquery源码有困惑 ...

  4. jQuery源码中的赌博网站

    前言 jQuery源码中有赌博网站? 起因是公司发的一份自查文件,某银行在日常安全运营过程中发现在部分jQuery源码中存在赌博和黄色网站链接. 链接分为好几个: www.cactussoft.cn ...

  5. jQuery 源码解析一:jQuery 类库整体架构设计解析

    如果是做 web 的话,相信都要对 Dom 进行增删查改,那大家都或多或少接触到过 jQuery 类库,其最大特色就是强大的选择器,让开发者脱离原生 JS 一大堆 getElementById.get ...

  6. jQuery源码-dom操作之jQuery.fn.html

    写在前面 前面陆陆续续写了jQuery源码的一些分析,尽可能地想要cover里面的源码细节,结果导致进度有些缓慢.jQuery的源码本来就比较晦涩,里面还有很多为了解决兼容问题很引入的神代码,如果不g ...

  7. Jquery源码中的Javascript基础知识(三)

    这篇主要说一下在源码中jquery对象是怎样设计实现的,下面是相关代码的简化版本: (function( window, undefined ) { // code 定义变量 jQuery = fun ...

  8. 关于jQuery源码中(function(window,undefined){//dosomething()})(window)写法解释

    一.首先是最常见的闭包 (Closure) 范式自执行函数的写法,这里用匿名函数封装(构造块级作用域),避免了匿名函数内部的代码与外部之间发生冲突(如使用了相同的变量名). (function() { ...

  9. Jquery源码中的Javascript基础知识(二)

    接上一篇,jquery源码的这种写法叫做匿名函数自执行 (function( window, undefined ) { // code })( window ); 函数定义了两个参数window和u ...

随机推荐

  1. HDU - 3006 The Number of set(状态压缩位运算)

    http://acm.hdu.edu.cn/showproblem.php?pid=3006 题意 给定n个集合,每个集合都是由大于等于1小于等于m的数字组成,m最大为14.问由给出的集合可以组成多少 ...

  2. C# 简单的反射机制实例

    using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.R ...

  3. luogu P4036 [JSOI2008]火星人

    传送门 很久以前xzz大佬就喊我做这题,结果现在才做qwq 因为要在序列中插入,所以直接用\(Splay\)维护这个串的哈希值,插入就直接把那个点插♂进去,修改就把点旋到根,然后修改和pushup,询 ...

  4. luogu P1445 [Violet]嘤F♂A

    博主决定更博文啦 这道题一开始没什么思路啊qwq 要求 \(\frac{1}{x}+\frac{1}{y}=\frac{1}{n!}\) 的正整数解总数 首先通分,得 \[\frac{x+y}{xy} ...

  5. delphi-TTcpServer与TTcpClient

    最简单的TTcpServer与TTcpClient通信实例-Delphi_海盗船长_新浪博客http://blog.sina.com.cn/s/blog_5383794d0100nt9u.html d ...

  6. JavaScript学习 - 基础(五) - string/array/function/windows对象

    String对象 更详细转:http://www.w3school.com.cn/jsref/jsref_obj_string.asp //------------------------------ ...

  7. [转]python 装饰器

    以前你有没有这样一段经历:很久之前你写过一个函数,现在你突然有了个想法就是你想看看,以前那个函数在你数据集上的运行时间是多少,这时候你可以修改之前代码为它加上计时的功能,但是这样的话是不是还要大体读读 ...

  8. caffe-win10-cifar10

    因为是在win10下安装的GPU版caffe,所以不能直接运行linux里的shell脚本.但是win10自带bash,可以运行.sh文件,网上也有直接下Cygwin和git的.我是下载好git后才知 ...

  9. Python之matplotlib库学习

    matplotlib 是python最著名的绘图库,它提供了一整套和matlab相似的命令API,十分适合交互式地进行制图.而且也可以方便地将它作为绘图控件,嵌入GUI应用程序中. 它的文档相当完备, ...

  10. Linux内存管理3---分页机制

    1.前言 本文所述关于内存管理的系列文章主要是对陈莉君老师所讲述的内存管理知识讲座的整理. 本讲座主要分三个主题展开对内存管理进行讲解:内存管理的硬件基础.虚拟地址空间的管理.物理地址空间的管理. 本 ...