你的浏览器不支持canvas

Enjoy life!

jQuery - 代码分析之jquery变量

Date: Author: JM

本文章采用 知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议 进行许可。

其他链接:

一、变量 jQuery

1.1 介绍

  • 一般面向对象的写法是这样的:
var jQuery = function() {
  //...
};
jQuery.prototype.init = function() {
  //...
}
jQuery.prototype.css = function() {
  //...
}

// 调用
var jq = new jQuery();
jq.init();
jq.css();
  • 到了jq这里就不一样了,是链式调用
var jQuery = function() {
   return new jQuery.fn.init();
};
jQuery.fn = jQuery.prototype = {
    init: function() {
      
    },
    css: function() {
      
    }
}
// 使用jQuery的原型对象覆盖init的原型对象
jQuery.fn.init.prototype = jQuery.fn;

//调用
jQuery().css();

二、 疑问

2.1 为什么要jQuery.fn = jQuery.prototype,而不是fn = jQuery.prototype?

  • 首先,其实是由于jQuery.prototype太长了,我们只是换一个比较名字而已,他们两个是一样的。
  • 接着,将jQuery.prototype挂在jQuery对象的的fn属性下,是为了安全着想,不然任何人都可以随意改了。

2.2 为什么要在jQuery的构造函数里返回 return new jQuery.fn.init();

  • 由于需要起到“链式”调用的作用,那么jQuery的构造函数返回的必然是一个对象才可以,因此,一开始我们的想法是:
  • 点击打开demo
// Uncaught RangeError: Maximum call stack size exceeded
// 报错的原因是:jQuery在不停地自己调用自己,没有限制
var $ = jQuery = function () {
  return new jQuery()
}
console.log($())
  • 从上述代码的输出结果,你可以发现报错了: Maximum call stack size exceeded,即:内存外溢,出现了死循环引用

 var $jq = new jQuery();
  • 看到上述代码,尽管真实的jq我们是不会这样去操作的,但是这也提示了我们,为什么要new一个实例出来:
    • $jq 实例对象就有jQuery.prototype包含的原型属性和方法。
    • 确定this的指向:this也会指向$jq的实例对象。

  • 既然自己调用自己不行,那么我就借助别人,然后再转化成自己的!
  • 我们就可以尝试用工厂方法来创建一个实例,并且将这个方法放在jQuery.prototype原型对象上,并在jQuery函数中返回这个原型方法的调用。
  • 点击打开demo
    const $ = jQuery = function () {
      return jQuery.fn.init()
    }
    jQuery.fn = jQuery.prototype = {
      init: function () {
        this.length =   0
        this.test = function () {
          return this.length
        }
        return this
      },
      jquery: '1.3.2',
      length: 1,
      size: function () {
        return this.length
      }
    }
    console.log($().jquery) // 1.3.2
    console.log($().test()) // 0
    console.log($().size()) // 0
  • 上面的代码貌似可以实现我们的链式调用了,然而,它还是存在问题的: return jQuery.fn.init();这一句话其实破环了作用域的独立性
  • 可以试想一下:init()方法中的this究竟指代的是jQuery呢还是init()方法本身呢?
  • 分析上述代码:this关键字引用了init()函数作用域所在的对象,所以$().test()返回0。
  • 但是,this关键字又可以访问上一级对象jQuery.fn的作用域,所以$().version返回'2.0.3'
  • 记得,$().size()返回的是0。很明显,这样的设计很糟糕!!因此我们需要分隔作用域
    var $ = jQuery = function() {
        // 实例化init初始化函数,起到分隔作用域的效果
          return new jQuery.fn.init();
    }
    jQuery.fn = jQuery.prototype = {
          init: function() {
              this.length = 0;
              this.test = function() {
                  return this.length;
              };
              console.log(this)
              return this;
          },
          size: function() {
              return this.length;
          },
          length: 1,
          version: '2.0.3'
     };
   
    // 测试
    console.log($().version); // 'undefined'
    console.log($().test()); // 0
    console.log($().size()); // 报错
  • 解决了“作用域”的问题,现在又出现了另外一个问题(我们也可以从上述代码的测试中看到):居然无法访问jQuery.fn对象的属性和方法。那我们究竟怎么样才能在返回的实例中访问jQuery的原型对象呢?
  • 以下是输出的this对象,你会发现,其__proto__对象里没有 jQuery.fn的属性和方法:

jquery


  • 解决上述问题的方法如下:【点击打开demo
    var $ = jQuery = function() {
        // 实例化init初始化函数,起到分隔作用域的效果
          return new jQuery.fn.init();
    }
    jQuery.fn = jQuery.prototype = {
          init: function() {
              this.length = 0;
              this.test = function() {
                  return this.length;
              };
              console.log(this)
              return this;
          },
          size: function() {
              return this.length;
          },
          length: 1,
          version: '2.0.3'
     };
    
    // 新增代码:使用jQuery原型对象覆盖init的原型对象,这就相当于init方法有权利去访问jQuery原型对象的所有属性和方法
    jQuery.fn.init.prototype = jQuery.fn;
   
    // 测试
    console.log($().version); // '2.0.3'
    console.log($().test()); // 0
    console.log($().size()); // 0
  • 以下是输出的this对象,你会发现,其__proto__对象里有 jQuery.fn的属性和方法:

jquery

  • 注意:$().size()的值仍然是0,原因是:根据作用域链寻找变量的原理,init方法中存在length,自然就不会寻找 jQuery.prototype下的length

三、总结

  • 由于要实现链式调用,返回的必然是一个对象,通过这个对象,再调用其下的属性或者方法
  • 由于在自己的函数里返回自己的对象,会出现内存溢出,所以这个方法无效
  • 既然,自己的不行,为何不借助别人的?然后,再将别人的转化成自己的?
  • 个人对于 jQuery 的变量对象的实现是这样子的:
    • 既然要实现链式调用,函数肯定是返回一个对象,那么返回对象的最经典方法就是“工厂函数模式”,所以,这里我们使用工厂模式返回对象!
    • 就像创造对象一样,一般构造方法是存储属性的地方,而原型主要是存储方法;
    • jQuery这里,归根到底就是创建一个变量,所以属性就存在“构造函数” init 里,而方法,则存储在 jQuery.prototype
    • 这里其实也用到了一丢丢“原型创建对象”的味道,在 return new jQuery.fn.init(); 之后,无法访问jQuery.prototype上的方法, 所以,我们就要 Query.fn.init.prototype = jQuery.fn;
  • 说到这里,突然觉得,jQuery 对象的创建不就是 “构造函数结合原型” 去创建?只不过,这里不是用自己的,而是借用了别人的!
  • 哦,对了,为什么不是新建一个函数,再借用这个函数的作用域,而是用jQuery原型对象上的init方法的作用域呢?
    • 个人理解是:其实 jQuery.prototype.init 还不是属于jQuery自己身体的一部分,用自己本身的东西要比借用别人的东西要好得多吧!

对于本文内容有问题或建议的小伙伴,欢迎在文章底部留言交流讨论。