jquery源碼學習筆記(一)jQuery的無new構建

来源:http://www.cnblogs.com/178mz/archive/2016/07/31/5724109.html
-Advertisement-
Play Games

本人是一名.net程式員..... 你一個.net coder 看什麼jQuery 源碼啊? 原因嗎,很簡單。技多不壓身嗎(麻蛋,前端工作好高...羡慕)。 我一直都很喜歡JavaScript,廢話不多說了,直接切入正題。 最近看了好幾篇jQuery 源碼的文章,對於jQuery的無new構建 很是 ...


本人是一名.net程式員.....

你一個.net coder 看什麼jQuery 源碼啊?

原因嗎,很簡單。技多不壓身嗎(麻蛋,前端工作好高...羡慕)。

我一直都很喜歡JavaScript,廢話不多說了,直接切入正題。

最近看了好幾篇jQuery 源碼的文章,對於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__"進行簡單的介紹:

  • 對於所有的對象,都有__proto__屬性,這個屬性對應該對象的原型
  • 對於函數對象,除了__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() 返回的實例:

image

很明顯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)也就是實例化方法存在這麼一個關係鏈  

  • jQuery.fn.init.prototype = jQuery.fn = jQuery.prototype ;
  • new jQuery.fn.init() 相當於 new jQuery() ;
  • jQuery() 返回的是 new jQuery.fn.init(),而 var obj = new jQuery(),所以這 2 者是相當的,所以我們可以無 new 實例化 jQuery 對象。

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • A集成代碼生成器 [正反雙向(單表、主表、明細表、樹形表,開發利器)+快速構建表單; freemaker模版技術 ,0個代碼不用寫,生成完整的一個模塊,帶頁面、建表sql腳本,處理類,service等完整模塊B 集成阿裡巴巴資料庫連接池druid; 資料庫連接池 阿裡巴巴的 druid。Druid在 ...
  • 初探MVC Model:負責定義信息格式月信息訪問的界面,包括商業邏輯與信息驗證。 View:負責用戶界面相關呈現,包括輸入與輸出。 Controller:負責控制系統的運行流程、跟瀏覽器的交互、決定網頁操作的流程與動線、響應客戶端的各種要求、錯誤處理等等。 定義數據結構 負責與資料庫溝通 從資料庫 ...
  • 理解深拷貝和淺拷貝之前需要弄懂一些基礎概念,記憶體中存儲的變數類型分為值類型和引用類型。 1、值類型賦值的存儲特點, 將變數內的數據全部拷貝一份, 存儲給新的變數。 例如:var num = 123 ;var num1=num; 表示變數中存儲的數字是 123。然後將數據拷貝一份,就是將 123 拷貝 ...
  • SVG描邊動畫原理其實很簡單,主要利用以下兩個屬性 stroke-dasharray 製作虛線,使得黑白相間, stroke-dashoffset 使得虛線向開頭偏移,這裡的1500不精確,是我隨便取的,下文介紹通過JS獲取長度。 動畫就是減少虛線偏移,那麼實線就會慢慢漏出來了 JS獲取長度 var ...
  • SVG是什麼 SVG是什麼 SVG 指可伸縮矢量圖形 (Scalable Vector Graphics) SVG 用來定義WEB上使用的矢量圖 SVG 使用 XML 格式定義圖形 SVG 圖像在縮放時其圖形質量不會有所損失 SVG 是W3C推薦的 SVG 與諸如 DOM和 XSL 之類的W3C標準 ...
  • ES6新特性之 函數參數的預設值寫法 和 箭頭函數。 1、函數參數的預設值 ES5中不能直接為函數的參數指定預設值,只能通過以下的變通方式: 從上面的代碼可以看出存在一個問題,當傳入的參數為0或者false時,會直接取到後面的值,而不是傳入的這個參數值。 那怎麼解決呢?對於上圖的代碼,可以通過判斷是 ...
  • 一、前言 昨天我們瞭解了Js的很重要的一個概念叫做函數,函數就是對於冗餘和垃圾代碼的一種封裝機制。簡單的講就是為了能讓程式更好更快的執行我們將一些重覆性的代碼提取,封裝成一個有名字的小盒子,等到我們需要的時候我們可以直接將盒子拿出來使用。 二、引入 無論是在前端還是後臺語言中函數都是一個個成功程式的 ...
  • 在javascript中,this指代的對象時常會變化,這會造成程式,混亂,一般做法就是先將this保存在一個變數中,就不怕她變了,我們先看一個小例子 JQuery提供了proxy方法,它可以綁定代理一個對象,this變了,我不怕不怕啦,that當this,我不怕不怕不怕啦,proxy有了你出現,對 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...