這次帶給大家jQuery無new建置使用詳解,jQuery無new建置使用的注意事項有哪些,下面就是實戰案例,一起來看一下。
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()
返回的实例:
很明显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
类的,所以aQuery
的age
属性是可以被修改的。
这样看似没有问题,其实问题很大的
为什么是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 对象。
相信看了本文案例你已经掌握了方法,更多精彩请关注php中文网其它相关文章!
推荐阅读:
以上是jQuery無new建置使用詳解的詳細內容。更多資訊請關注PHP中文網其他相關文章!