深複製、淺複製與JQuery中的插件開發

學習前端半年多了,還停留在新手村級,寫的文章可能有不少問題,思惟方式和邏輯上還不夠嚴密,但願能指出問題,謝謝!css

=====================================================================前端


數據類型

數據類型分爲兩種,基本數據類型和引用數據類型。ES6中的數據類型不作討論,基本類型包括了:string、null、undefined、number、boolean。引用類型:object。它是由一個或者多個鍵名鍵值對的對象。基本數據類型保存在棧內存中,而引用類型保存在堆內存中,棧內存中的數據必須是固定大小的,而引用類型的大小不固定,因此只能白存在堆內存中,將堆內存中的地址直接賦值出來以後就能夠訪問引用類型數據。他們之間的差異以下:node

區別 基本數據類型 引用數據類型
保存位置 棧內存 堆內存
數據大小 固定大小 不固定
訪問方式 經過變量保存的值 經過保存的地址訪問

淺複製與深複製

在淺複製時直接將簡單類型值分別賦值給target,而若是複製的是對象就不能經過簡單的復值來複制。這樣作成的結果就是target引用值改變時也會引發原對象的改變。ajax

1.淺複製

var person1 = ['Nicholas','Greg',[{
                        name : "linda",
                        age : 21                    
                    },{
                        name : "Nancy",
                        age : 19
                    }]
                ]; 
    var person2 = [];
    // //複製
    for(var i  = 0;i < person1.length;i++){
        person2[i] = person1[i]; 
    }
    person2[2].push('Leo') ;//改變color1的值
    console.log(person2);//'Nicholas','Greg',Array(3)
    console.log(person1);//'Nicholas','Greg',Array(3)

只複製第一層屬性的方式爲淺複製,若是數據類型所有是基本類型是能夠成功的,而在複製引用類型時,則須要複製到基本類型爲止才能夠保證互不影響。數組

2.深複製

原生方法的基本實現方式:app

var objA = {
        a : "fc",
        b : "Ig",
        c : {
            d(){
                console.log(1)
            }
        }
    }
    function deepCopy(sub,sup){
        for(var key in sup){
            if(typeof sup[key] === 'object'){
                sub[key] = {};
                //複製到對象上的屬性值爲基本類型值爲止
                deepCopy(sub[key],sup[key]);
            }else{
                sub[key] = sup[key];
            } 
        }
        return sub;
    }
    var objB = {};
    deepCopy(objB,objA);

    objA.c.d = function(){
        console.log(2)
    }
    //修改源對象上的屬性並不會修改目標對象上已經複製的屬性
    objB.c.d();//1

深複製的原理就是若是複製時若是複製的對象時引用類型,那麼就遞歸運行復制一次,直到爲簡單數據類型爲止。ide

$.extend方法

在JQuery的extend方法中,若是傳入的是一個或多個對象,那麼就會將後面對象的屬性複製給target對象,第一個參數能夠選擇是深複製(true)或淺複製,默認爲淺複製,返回的是被擴展的對象。函數

1.合併但不修改object1。$.extend({}, object1, object2);工具

var settings = {first:'hello', second: 'world'};
    var options = {first:'hello', second: 'JavaScript',third: 'nodeJs'};
    var results = $.extend({}, settings, options);

2.合併並修改第一個對象。$.extend(obj1,obj2)源碼分析

var obj1 = {first: 1, second: {height: 178, weight: 70,length:100}};
    var obj2 = {second: {height:180, weight:65, width: 90}, third: 90};
    $.extend(obj1, obj2);
    
    //輸出結果爲:{first: 1, second: {height:180,weight:65, width: 90}, third: 90}

3.深複製對象。$.extend(true,obj1,obj2)

var obj1 = {first: 1, second: {height: 178, weight: 70}};
    var obj2 = {second: {height:180, weight: 65, width: 90}, third: 90};
    $.extend(true,obj1,obj2);
    console.log(obj1,obj2);
    //輸出結果爲:{first: 1, second: Object, third: 90}

深複製的不一樣種實現方式

已知的三種方式,$.extend、lodash、Underscore都有能夠實現複製的功能,可是也會有一些細微的區別。

1.在Underscore中的_.clone(),以下:

var x = {
        a: 1,
        b: { z: 0 },
        c:[
            2,3,9
        ]
    };

    var y = _.clone(x);
    y.c.push(29);
    console.log(x.c,y.c);//[2, 3, 9, 29][2, 3, 9, 29];
    //_.clone源碼部分
    _.clone = function(obj) {
      if (!_.isObject(obj)) return obj;
      return _.isArray(obj) ? obj.slice() : _.extend({}, obj);
    };
    //數組使用slice方法截取全部,對象採用淺複製上的方法複製對象後按鍵值賦值,因而可知該功能並不能實現深複製

2.$.extend的複製方法
該方法下的深複製原理是:經過添加參數來實現遞歸extend,所以JQuery能夠實現深複製。源碼(3.2版本)以下:

jQuery.extend = jQuery.fn.extend = function(){
        var options, name, src, copy, copyIsArray, clone,
            target = arguments[ 0 ] || {},// 常見用法 jQuery.extend( obj1, obj2 ),此時,target爲arguments[0]
            i = 1,
            length = arguments.length,
            deep = false;
            /*
            變量 options:指向某個源對象。
            變量 name:表示某個源對象的某個屬性名。
            變量 src:表示目標對象的某個屬性的原始值。
            變量 copy:表示某個源對象的某個屬性的值。
            變量 copyIsArray:指示變量 copy 是不是數組。
            變量 clone:表示深度複製時原始值的修正值。
            變量 target:指向目標對象,申明時先臨時用第一個參數值。
            變量 i:表示源對象的起始下標,申明時先臨時用第二個參數值。
            變量 length:表示參數的個數,用於修正變量 target。
            變量 deep:指示是否執行深度複製,默認爲 false。
            */
            
        if( typeof target === "boolean" ){
        //若是第一個參數爲true,即 jQuery.extend( true, obj1, obj2 ); 的狀況
            deep = target;
            target = arguments[ i ] || {};
            i++;
        }
        
        //Handle case when target is a string or something (possible in deep copy)
        //好比$.extend({},{adress:"LosAngles"})
        if( typeof target !== "object" && !jQuery.isFunction( target ) ){
            target = {};
        }
        // Extend jQuery itself if only one argument is passed
        // 處理這種狀況 jQuery.extend(obj),或jQuery.fn.extend(obj)
        if( i === length ){
            target = this;
            i--;
        }
    
        for( ; i < length; i++ ){
            // Only deal with non-null/undefined values
            if( ( options = arguments[ i ] ) != null ){
                //好比 jQuery.extend(obj1,obj2,obj3,ojb4),options則爲obj二、obj3...
                for( name in options ) {
                    src = target[ name ];
                    copy = options[ name ];
    
                    // 防止自引用
                    if( target === copy ) {
                        continue;
                    }
                    // 若是是深拷貝,且被拷貝的屬性值自己是個對象或數組
                    if( deep && copy && ( jQuery.isPlainObject( copy ) ||
                        ( copyIsArray = Array.isArray( copy ) ) ) ) {
                        if ( copyIsArray ) {
                            copyIsArray = false;
                            clone = src && Array.isArray( src ) ? src : [];
                        } else {
                            clone = src && jQuery.isPlainObject( src ) ? src : {};
                        }
                        // Never move original objects, clone them
                        target[ name ] = jQuery.extend( deep, clone, copy );
                    //普通對象的定義是:經過 "{}"或者"new Object" 建立的    
                    //以前的例子走到了這一步,直接的賦值給對象,因此改變了源對象的屬性後,target對象的屬性也會發生改變
                    }else if( copy !== undefined ){
                        target[ name ] = copy;
                    }
                }
            }
        }
        return target;
    };

根據extend的源碼分析,在關於深複製這部分的核心代碼中,判斷源對象的屬性上是否是"普通對象"這個問題可能會引發深複製結果的錯誤。

例如在如下的代碼中就能夠看出,由於判斷obj不是"普通對象",因此會影響深複製的結果,在這個例子中,當改變了源對象的屬性時,目標對象的屬性也被改變,顯然這不符合深複製的目的。一樣的問題在下面的lodash深複製中並不會出現。

function Obj(){
        this.a = 1;
    }
    var obj = new Obj();
    var tobeCloned = {o:obj};
    var result  = $.extend(true,{},tobeCloned);
    tobeCloned.o.a = 2;
    console.log(result.o.a)//2
    
    console.log($.isPlainObject(obj));//false

3.lodash中的複製方法
複製的方法分別是_.clone()和_.cloneDeep()。其中_.clone(obj, true)等價於_.cloneDeep(obj)。

var arr = new Int16Array(5),
    obj = { a: arr },
    obj2;
    arr[0] = 5;
    arr[1] = 6;
    
    //Int16Array是類型化數組。16位二補碼有符號整數。
    // 1. jQuery
    obj2 = $.extend(true,{},obj);
    console.log(obj2.a);                            // [5, 6, 0, 0, 0]
    Object.prototype.toString.call(obj2);           // [object Int16Array]
    obj2.a[0] = 100;
    console.log(obj);                               // [100, 6, 0, 0, 0]
    
    //此處jQuery不能正確處理Int16Array的深複製!!!
    // 2. lodash
    obj2 = _.cloneDeep(obj);                       
    console.log(obj2.a);                            // [5, 6, 0, 0, 0]
    Object.prototype.toString.call(arr2);           // [object Int16Array]
    obj2.a[0] = 100;
    console.log(obj);                               // [5, 6, 0, 0, 0]

綜合三種方法來看,JQuery不能複製JSON對象之外的對象。而在lodash中用了大量的代碼來實現ES6引入的新標準對象,而且還能夠對Date、RegExp進行深複製。單就深複製的實現上來講,lodash的效率和適用範圍要優於JQuery。所以能夠說lodash是一種更擁抱將來的類庫。

JQuery插件

JQuery插件主要分爲兩類:1,類級別 2,對象級別

  • 類方法。直接使用$類引用,不須要實例化就可以使用。類方法在項目中被設置爲工具類使用。

  • 對象級別。必須先建立實例,而後才能經過實例調用該實例方法。

1.$.extend擴展

直接擴展JQuery類,至關於靜態方法,典型的方法有$.ajax,擴展方法:

$.extend({
        add:function(a,b){
            return a + b;
        },
        divide:function(a,b){
            return a/b;
        }
    })
    //調用方式
    $.add(3,0);
    $.divide(9,3);

2.$.fn.extend()擴展插件

這種擴展方式是基於原型對象的,擴展插件時通常使用這種方式,擴展以後只有JQuery的實例才能夠調用該方法,好比但願使頁面上全部的鏈接轉爲紅色。

$.fn.myLink = function(){
        this.css({
            "color":"red"
        })
    }
    $("a").myLink();

若是須要對每一個具體的元素進行操做,能夠對該方法再次進行擴展。

$.fn.myLink = function(){
        this.css({
            "color":"red"
        });
        this.each(function(){
            $(this).append($(this).attr("href"));
        })
    }
    $("a").myLink();

注意在each的內部遍歷出來的是DOM元素,因此須要在包裝一次纔可使用JQuery的方法。而若是咱們但願能夠本身定製,根據自身需求來設置,因此能夠利用$.extend方法合併對象以後來做爲參數使用,在沒有參數時使用默認值。
在extend時使用空對象做爲第一個參數,避免修改defaults默認的屬性值,保護好defaults的默認參數。

$.fn.myLink = function(options){
        var defaults = {
            "color" : "red",
            "fontSize" : "18px",
            "lineHeight" : "18px"
        }
        //此處使用空對象是爲了保護默認參數,避免被修改以後複用出錯,注意默認仍是淺複製,若是options有引用類型參數時,仍是會對defaults形成印象
        var setting = $.extend({},defaults,options);
        return this.css({
            "color" : setting.color,
            "fontSize" : setting.fontSize,
            "lineHeight" : setting.lineHeight
        })
    }
    $("a").myLink({
        "color":"#333"
    });

3.面向對象的插件開發

爲何須要面向對象的插件方法?方便管理直接使用,第二不會影響外部命名空間。

;(function($,window,document,undefined){
        var Beautify = function(ele,opt){
            this.$elements = ele,
            this.defaults = {
                "color" : "red",
                "fontSize" : "18px",
                "textShadow" : "none"
            },
            this.options = $.extend({},this.defaults,opt);
        }
        Beautify.prototype = {
            constructor : Beautify,
            beautiful(){
                return this.$elements.css({
                    "color" : this.options.color,
                    "fontSize" : this.options.fontSize,
                    "textShadow" : this.options.textShadow
                })
            }
        }
        $.fn.myPlug = function(options){
            //this指向新的實例beautify,其中有一個函數beautify(),能夠返回指定樣式;
            var beautify = new Beautify(this,options);
            return beautify.beautiful();
        }
    })(jQuery,window,document,undefined);
    $("").myPlug({
        "fontSize":"30px",
        "textShadow": "3px 2px 3px #ff0000"
    })
相關文章
相關標籤/搜索