实例详解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 相关文章推荐
php is_numberic函数造成的SQL注入漏洞
Mar 10 Javascript
JavaScript制作简单的日历效果
Mar 10 Javascript
javascript验证手机号和实现星号(*)代替实例
Aug 16 Javascript
node.js + socket.io 实现点对点随机匹配聊天
Jun 30 Javascript
详解vue中使用express+fetch获取本地json文件
Oct 10 Javascript
浅谈webpack下的AOP式无侵入注入
Nov 12 Javascript
Express下采用bcryptjs进行密码加密的方法
Feb 07 Javascript
webpack实现一个行内样式px转vw的loader示例
Sep 13 Javascript
webpack开发环境和生产环境的深入理解
Nov 08 Javascript
javascript刷新父页面方法汇总详解
Oct 10 Javascript
vue页面切换项目实现转场动画的方法
Nov 12 Javascript
如何在CocosCreator里画个炫酷的雷达图
Apr 16 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
PHP字符过滤函数去除字符串最后一个逗号(rtrim)
2013/03/26 PHP
PHP不用递归实现无限分级的例子分享
2014/04/18 PHP
从性能方面考虑PHP下载远程文件的3种方法
2015/12/29 PHP
php实现贪吃蛇小游戏
2016/07/26 PHP
php+Memcached实现简单留言板功能示例
2017/02/15 PHP
Laravel timestamps 设置为unix时间戳的方法
2019/10/11 PHP
Alliance vs AM BO3 第一场2.13
2021/03/10 DOTA
用js代码和插件实现wordpress雪花飘落效果的四种方法
2014/12/15 Javascript
jquery实现相册一下滑动两次的方法
2015/02/09 Javascript
js清除浏览器缓存的几种方法
2017/03/15 Javascript
深入理解Angularjs中$http.post与$.post
2017/05/19 Javascript
JavaScript基于activexobject连接远程数据库SQL Server 2014的方法
2017/07/12 Javascript
微信小程序之电影影评小程序制作代码
2017/08/03 Javascript
简单实现jQuery手风琴效果
2017/08/18 jQuery
基于JSONP原理解析(推荐)
2017/12/04 Javascript
Nodejs中crypto模块的安全知识讲解
2018/01/03 NodeJs
node.js爬取中关村的在线电瓶车信息
2018/11/13 Javascript
基于canvas实现手写签名(vue)
2020/05/21 Javascript
使用Python的Twisted框架编写简单的网络客户端
2015/04/16 Python
Django中模型Model添加JSON类型字段的方法
2015/06/17 Python
简单解析Django框架中的表单验证
2015/07/17 Python
python编程羊车门问题代码示例
2017/10/25 Python
python中列表的切片与修改知识点总结
2019/07/23 Python
python 类的继承 实例方法.静态方法.类方法的代码解析
2019/08/23 Python
Python约瑟夫生者死者小游戏实例讲解
2021/01/04 Python
pycharm 如何取消连按两下shift出现的全局搜索
2021/01/15 Python
JAVA的事件委托机制和垃圾回收机制
2014/09/07 面试题
主治医师岗位职责
2013/12/10 职场文书
《值日生》教学反思
2014/02/17 职场文书
初中教师业务学习材料
2014/05/12 职场文书
农村党支部书记四风问题个人对照检查材料
2014/09/21 职场文书
2014年精神文明建设工作总结
2014/11/19 职场文书
环卫工作个人总结
2015/03/04 职场文书
素质教育学习心得体会
2016/01/19 职场文书
Python pygame实现中国象棋单机版源码
2021/06/20 Python
十大最强奥特曼武器:怪兽战斗仪在榜,第五奥特之父只使用过一次
2022/03/18 日漫