实例详解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 相关文章推荐
jquery多浏览器捕捉回车事件代码
Jun 22 Javascript
jQuery之排序组件的深入解析
Jun 19 Javascript
jquery.cookie.js用法实例详解
Dec 25 Javascript
javascript ES6中箭头函数注意细节小结
Feb 17 Javascript
vue.js提交按钮时进行简单的if判断表达式详解
Aug 08 Javascript
VUE简单的定时器实时刷新的实现方法
Jan 20 Javascript
详解Vue用cmd创建项目
Feb 12 Javascript
解决微信小程序云开发中获取数据库的内容为空的方法
May 15 Javascript
layui默认选中table的CheckBox复选框方法
Sep 19 Javascript
js实现无刷新监听URL的变化示例代码详解
Jun 03 Javascript
js实现表单项的全选、反选及删除操作示例
Jun 05 Javascript
解决echarts echarts数据动态更新和dataZoom被重置问题
Jul 20 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
javascript 框架小结 个人工作经验
2009/06/13 Javascript
jquery 得到当前页面高度和宽度的两个函数
2010/02/21 Javascript
jquery toolbar与网页浮动工具条具体实现代码
2014/01/12 Javascript
跟我学Nodejs(二)--- Node.js事件模块
2014/05/21 NodeJs
window.location的重写及判断location是否被重写
2014/09/04 Javascript
JS是按值传递还是按引用传递
2015/01/30 Javascript
基于javascript实现单选及多选的向右和向左移动实例
2015/07/25 Javascript
基于jQuery实现放大镜特效
2020/10/19 Javascript
jquery 多个radio的click事件实例
2016/12/03 Javascript
jQuery仿写百度百科的目录树
2017/01/03 Javascript
详解node child_process模块学习笔记
2018/01/24 Javascript
基于jQuery实现无缝轮播与左右点击效果
2018/05/13 jQuery
使用Angular material主题定义自己的组件库的配色体系
2019/09/04 Javascript
layui 富文本图片上传接口与普通按钮 文件上传接口的例子
2019/09/23 Javascript
js实现打字小游戏
2019/12/17 Javascript
JavaScript实现鼠标移入随机变换颜色
2020/11/24 Javascript
python使用MySQLdb访问mysql数据库的方法
2015/08/03 Python
Python基于回溯法子集树模板解决m着色问题示例
2017/09/07 Python
2018年Python值得关注的开源库、工具和开发者(总结篇)
2018/01/04 Python
详解tensorflow载入数据的三种方式
2018/04/24 Python
python实现AES和RSA加解密的方法
2019/03/28 Python
pytorch:实现简单的GAN示例(MNIST数据集)
2020/01/10 Python
Python中url标签使用知识点总结
2020/01/16 Python
tensorflow从ckpt和从.pb文件读取变量的值方式
2020/05/26 Python
Html5写一个简单的俄罗斯方块小游戏
2019/12/03 HTML / CSS
美国手工艺品市场的领导者:Annie’s
2019/04/04 全球购物
英国奢侈品牌时尚购物平台:Farfetch(支持中文)
2020/02/18 全球购物
自动化工程专业个人应聘自荐信
2013/09/26 职场文书
优秀经理获奖感言
2014/03/04 职场文书
销售内勤岗位职责
2014/04/15 职场文书
勾股定理课后反思
2014/04/26 职场文书
建设投标担保书
2014/05/13 职场文书
离婚起诉书怎么写
2015/05/19 职场文书
毕业欢送晚会主持词
2019/06/25 职场文书
私人贷款担保书该怎么写呢?
2019/07/02 职场文书
Java比较两个对象中全部属性值是否相等的方法
2021/08/07 Java/Android