实例详解jQuery的无new构建


Posted in Javascript onAugust 02, 2016

jQuery的无new构建

jQuery框架的核心就是从HTML文档中匹配元素并对其执行操作、

回想一下使用 jQuery 的时候,实例化一个 jQuery 对象的方法:

// 无 new 构造
$('#test').text('Test');
 
// 当然也可以使用 new
var test = new $('#test');
test.text('Test');

大部分人使用 jQuery 的时候都是使用第一种无 new 的构造方式,直接 $('') 进行构造,这也是 jQuery 十分便捷的一个地方。

当我们使用第一种无 new 构造方式的时候,其本质就是相当于 new jQuery(),那么在 jQuery 内部是如何实现的呢?看看:

(function(window, undefined) {
  var
  // ...
  jQuery = function(selector, context) {
    // The jQuery object is actually just the init constructor 'enhanced'
    return new jQuery.fn.init(selector, context, rootjQuery);
  },
 
  jQuery.fn = jQuery.prototype = {
    init: function(selector, context, rootjQuery) {
      // ...
    }
  }
  jQuery.fn.init.prototype = jQuery.fn;
})(window);

 没看懂?没关系,我们一步一步分析。

函数表达式和函数声明

在ECMAScript中,创建函数的最常用的两个方法是函数表达式和函数声明,两者期间的区别是有点晕,因为ECMA规范只明确了一点:函数声明必须带有标示符(Identifier)(就是大家常说的函数名称),而函数表达式则可以省略这个标示符: 

   //函数声明:
function 函数名称 (参数:可选){ 函数体 }

//函数表达式:

function 函数名称(可选)(参数:可选){ 函数体 }

所以,可以看出,如果不声明函数名称,它肯定是表达式,可如果声明了函数名称的话,如何判断是函数声明还是函数表达式呢?

ECMAScript是通过上下文来区分的,如果function foo(){}是作为赋值表达式的一部分的话,那它就是一个函数表达式,

如果function foo(){}被包含在一个函数体内,或者位于程序的最顶部的话,那它就是一个函数声明。

function foo(){} // 声明,因为它是程序的一部分
 var bar = function foo(){}; // 表达式,因为它是赋值表达式的一部分
 new function bar(){}; // 表达式,因为它是new表达式
 (function(){
  function bar(){} // 声明,因为它是函数体的一部分
 })();

还有一种函数表达式不太常见,就是被括号括住的(function foo(){}),他是表达式的原因是因为括号 ()是一个分组操作符,它的内部只能包含表达式

 再来看jQuery源码:

(function(window, undefined) {
  /...
})(window)

可以将上面的代码结构分成两部分:(function(){window, undefined}) (window) ,

第1个()是一个表达式,而这个表达式本身是一个匿名函数,

所以在这个表达式后面加(window)就表示执行这个匿名函数并传入参数window。

原型 prototype

认识一下什么是原型?

在JavaScript中,原型也是一个对象,通过原型可以实现对象的属性继承,JavaScript的对象中都包含了一个" [[Prototype]]"内部属性,这个属性所对应的就是该对象的原型。

对于"prototype"和"__proto__"这两个属性有的时候可能会弄混,"Person.prototype"和"Person.__proto__"是完全不同的。

在这里对"prototype"和"__proto__"进行简单的介绍:

    1.对于所有的对象,都有__proto__属性,这个属性对应该对象的原型

    2.对于函数对象,除了__proto__属性之外,还有prototype属性,当一个函数被用作构造函数来创建实例时,该函数的prototype属性值将被作为原型赋值给所有对象实例(也就是设置实例的__proto__属性)

function Person(name, age){
  this.name = name;
  this.age = age;
}
Person.prototype.getInfo = function(){
  console.log(this.name + " is " + this.age + " years old");
};
//调用
var will = new Person("Will", 28);
will.getInfo();//"Will is 28 years old"

闭包

闭包的定义:

当一个内部函数被其外部函数之外的变量引用时,就形成了一个闭包。

闭包的作用:

在了解闭包的作用之前,我们先了解一下 javascript中的GC机制:

在javascript中,如果一个对象不再被引用,那么这个对象就会被GC回收,否则这个对象一直会保存在内存中。

在上述例子中,B定义在A中,因此B依赖于A,而外部变量 c 又引用了B, 所以A间接的被 c 引用,

也就是说,A不会被GC回收,会一直保存在内存中。为了证明我们的推理,看如下例子:

function A(){
  var count = 0;
  function B(){
    count ++;
    console.log(count);
  }
  return B;
}
var c = A();
c();// 1
c();// 2
c();// 3

count是A中的一个变量,它的值在B中被改变,函数B每执行一次,count的值就在原来的基础上累加1。因此,A中的count一直保存在内存中。

这就是闭包的作用,有时候我们需要一个模块中定义这样一个变量:希望这个变量一直保存在内存中但又不会“污染”全局的变量,这个时候,我们就可以用闭包来定义这个模块

在看jQuery源码:

(function(window, undefined) {
  var
  // ...
jQuery = function(selector, context) {
    // The jQuery object is actually just the init constructor 'enhanced'
    return new jQuery.fn.init(selector, context, rootjQuery);
  },
  jQuery.fn = jQuery.prototype = {
    init: function(selector, context, rootjQuery) {
      // ...
    }
  }
  jQuery.fn.init.prototype = jQuery.fn;
})(window);
 

我们知道了 什么是闭包:当一个内部函数被其外部函数之外的变量引用时,就形成了一个闭包。

jQuery.fn的init 函数被jQuery 的构造函数调用了,这里形成了一个闭包。
构造函数及调用代码:

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

问题关键来了。

如何实现无new构建

JavaScript是函数式语言,函数可以实现类,类就是面向对象编程中最基本的概念

var aQuery = function(selector, context) {
    //构造函数
}
aQuery.prototype = {
  //原型
  name:function(){},
  age:function(){}
}
var a = new aQuery();
a.name();
 

这是常规的使用方法,显而易见jQuery不是这样玩的

要实现这样,那么jQuery就要看成一个类,那么$()应该是返回类的实例才对

按照jQuery的抒写方式

$().ready() 
$().noConflict()

要实现这样,那么jQuery就要看成一个类,那么$()应该是返回类的实例才对

所以把代码改一下:

var aQuery = function(selector, context) {
    return new aQuery();
}
aQuery.prototype = {
  name:function(){},
  age:function(){}
}

通过new aQuery(),虽然返回的是一个实例,但是也能看出很明显的问题,死循环了

那么如何返回一个正确的实例?

在javascript中实例this只跟原型有关系

那么可以把jQuery类当作一个工厂方法来创建实例,把这个方法放到aQuery.prototye原型中

var aQuery = function(selector, context) {
    return aQuery.prototype.init(selector);
}
aQuery.prototype = {
  init:function(selector){
    return this;
  }
  name:function(){},
  age:function(){}
}

当执行aQuery() 返回的实例:

实例详解jQuery的无new构建

很明显aQuery()返回的是aQuery类的实例,那么在init中的this其实也是指向的aQuery类的实例

问题来了init的this指向的是aQuery类,如果把init函数也当作一个构造器,那么内部的this要如何处理?

var aQuery = function(selector, context) {
    return aQuery.prototype.init(selector);
}
aQuery.prototype = {
  init: function(selector) {
    this.age = 18
    return this;
  },
  name: function() {},
  age: 20
}
aQuery().age //18

 因为this只是指向aQuery类的,所以aQueryage属性是可以被修改的。

这样看似没有问题,其实问题很大的

为什么是new jQuery.fn.init?

看如下代码:

var aQuery = function(selector, context) {
    return aQuery.prototype.init(selector);
}
aQuery.prototype = {
  init: function(selector) {
    if(selector=="a")
      this.age = 18
    return this;
  },
  name: function() {},
  age: 20
}
aQuery("a").age //18
aQuery("b").age //18
 

当我调用 传入"a"的时候,修改age=18,及aQuery("a").age 的值为18

但是当我  传入"b"的时候 并没又修改 age的值,我也希望得到默认age的值20,但是aQuery("b").age 的值为18.

因为在 调用aQuery("a").age 的时候age被修改了。

这样的情况下就出错了,所以需要设计出独立的作用域才行。

jQuery框架分隔作用域的处理

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

很明显通过实例init函数,每次都构建新的init实例对象,来分隔this,避免交互混淆

我们修改一下代码:

var aQuery = function(selector, context) {
    return new aQuery.prototype.init(selector);
}
aQuery.prototype = {
  init: function(selector) {
    if(selector=="a")
      this.age = 18
    return this;
  },
  name: function() {},
  age: 20
}
aQuery("a").age //18
aQuery("b").age //undefined
aQuery("a").name() //Uncaught TypeError: Object [object Object] has no method 'name'

又出现一个新的问题,

age  :undefined,

name() :抛出错误,无法找到这个方法,所以很明显new的init跟jquery类的this分离了

怎么访问jQuery类原型上的属性与方法?

     做到既能隔离作用域还能使用jQuery原型对象的作用域呢,还能在返回实例中访问jQuery的原型对象?

实现的关键点

// Give the init function the jQuery prototype for later instantiation
jQuery.fn.init.prototype = jQuery.fn;

我们再改一下:

var aQuery = function(selector, context) {
    return new aQuery.prototype.init(selector);
}
aQuery.prototype = {
  init: function(selector) {
    if(selector=="a")
      this.age = 18
    return this;
  },
  name: function() {
     return age;
  },
  age: 20
}
aQuery.prototype.init.prototype = aQuery.prototype; 

aQuery("a").age //18
aQuery("b").age //20
aQuery("a").name()  //20

最后在看一下jQuery源码:

(function(window, undefined) {
  var
  // ...
jQuery = function(selector, context) {
    // The jQuery object is actually just the init constructor 'enhanced'
    return new jQuery.fn.init(selector, context, rootjQuery);
  },
  jQuery.fn = jQuery.prototype = {
    init: function(selector, context, rootjQuery) {
      // ...
    }
  }
  jQuery.fn.init.prototype = jQuery.fn;
})(window);

是不是明白了?

哈哈哈~~~

在简单说两句:

大部分人初看 jQuery.fn.init.prototype = jQuery.fn 这一句都会被卡主,很是不解。但是这句真的算是 jQuery 的绝妙之处。理解这几句很重要,分点解析一下:

1)首先要明确,使用 $('xxx') 这种实例化方式,其内部调用的是 return new jQuery.fn.init(selector, context, rootjQuery) 这一句话,也就是构造实例是交给了 jQuery.fn.init() 方法取完成。

2)将 jQuery.fn.init 的 prototype 属性设置为 jQuery.fn,那么使用 new jQuery.fn.init() 生成的对象的原型对象就是 jQuery.fn ,所以挂载到 jQuery.fn 上面的函数就相当于挂载到 jQuery.fn.init() 生成的 jQuery 对象上,所有使用 new jQuery.fn.init() 生成的对象也能够访问到 jQuery.fn 上的所有原型方法。

3)也就是实例化方法存在这么一个关系链 

    1.jQuery.fn.init.prototype = jQuery.fn = jQuery.prototype ;

    2.new jQuery.fn.init() 相当于 new jQuery() ;

    3.jQuery() 返回的是 new jQuery.fn.init(),而 var obj = new jQuery(),所以这 2 者是相当的,所以我们可以无 new 实例化 jQuery 对象。

总结

以上就是jQuery的无new构建的全部内容,希望本文对大家学习jQuery有所帮助。也请大家继续支持三水点靠木。

Javascript 相关文章推荐
一个背景云变换js特效 鼠标移动背景云变化
Dec 28 Javascript
自动刷新网页,自动刷新当前页面,JS调用
Jun 24 Javascript
手机端图片缩放旋转全屏查看PhotoSwipe.js插件实现
Aug 25 Javascript
NODE.JS跨域问题的完美解决方案
Oct 20 Javascript
Vue2.0实现1.0的搜索过滤器功能实例代码
Mar 20 Javascript
深入讲解xhr(XMLHttpRequest)/jsonp请求之abort
Jul 26 Javascript
Node.js  REPL (交互式解释器)实例详解
Aug 06 Javascript
JavaScript函数的4种调用方法实例分析
Mar 05 Javascript
利用vue-i18n实现多语言切换效果的方法
Jun 19 Javascript
vue 动态给每个页面添加title、关键词和描述的方法
Aug 28 Javascript
Javascript异步流程控制之串行执行详解
Sep 27 Javascript
JS hasOwnProperty()方法检测一个属性是否是对象的自有属性的方法
Jan 29 Javascript
AngularJS基础 ng-mouseover 指令简单示例
Aug 02 #Javascript
JavaScript比较当前时间是否在指定时间段内的方法
Aug 02 #Javascript
图文详解JavaScript的原型对象及原型链
Aug 02 #Javascript
AngularJS基础 ng-mousemove 指令简单示例
Aug 02 #Javascript
功能强大的Bootstrap使用手册(一)
Aug 02 #Javascript
js实现图片缓慢放大缩小效果
Aug 02 #Javascript
基于Vuejs实现购物车功能
Aug 02 #Javascript
You might like
php5 图片验证码实现代码
2009/12/11 PHP
一组PHP可逆加密解密算法实例代码
2014/01/21 PHP
thinkphp 中的volist标签在ajax操作中的特殊性(推荐)
2018/01/15 PHP
WordPress伪静态规则设置代码实例
2020/12/10 PHP
一些有关检查数据的JS代码
2006/09/07 Javascript
javascript中的继承实例代码
2011/04/27 Javascript
深入理解JavaScript系列(8) S.O.L.I.D五大原则之里氏替换原则LSP
2012/01/15 Javascript
怎么选择Javascript框架(Javascript Framework)
2013/11/22 Javascript
jQuery添加删除DOM元素方法详解
2016/01/18 Javascript
论JavaScript模块化编程
2016/03/07 Javascript
javascript实现方法调用与方法触发小结
2016/03/26 Javascript
jquery实现全选和全不选功能效果的实现代码【推荐】
2016/05/05 Javascript
jquery 动态增加删除行的简单实例(推荐)
2016/10/12 Javascript
基于Bootstrap模态对话框只加载一次 remote 数据的解决方法
2017/07/09 Javascript
浅谈js基础数据类型和引用类型,深浅拷贝问题,以及内存分配问题
2017/09/02 Javascript
关于在LayUI中使用AJAX提交巨坑记录
2019/10/25 Javascript
Vue extend的基本用法(实例详解)
2019/12/09 Javascript
JS图片预加载三种实现方法解析
2020/05/08 Javascript
[01:00:25]NB vs Secret 2018国际邀请赛小组赛BO1 B组加赛 8.19
2018/08/21 DOTA
详解Python的Django框架中inclusion_tag的使用
2015/07/21 Python
CentOS6.5设置Django开发环境
2016/10/13 Python
Python对文件和目录进行操作的方法(file对象/os/os.path/shutil 模块)
2017/05/08 Python
Python实现MySQL操作的方法小结【安装,连接,增删改查等】
2017/07/12 Python
python中的内置函数max()和min()及mas()函数的高级用法
2018/03/29 Python
python实现转圈打印矩阵
2019/03/02 Python
python实现翻译word表格小程序
2020/02/27 Python
python和js交互调用的方法
2020/06/23 Python
python性能测试工具locust的使用
2020/12/28 Python
Sunglasses Shop瑞典:欧洲领先的太阳镜网上商店
2018/04/22 全球购物
俄罗斯在线手表和珠宝商店:AllTime
2019/09/28 全球购物
阿里巴巴Oracle DBA笔试题答案-备份恢复类
2013/11/20 面试题
教师党员公开承诺事项
2014/05/28 职场文书
民主生活会意见
2015/06/05 职场文书
golang如何去除多余空白字符(含制表符)
2021/04/25 Golang
python 网络编程要点总结
2021/06/18 Python
在windows server 2012 r2中安装mysql的详细步骤
2022/07/23 Servers