學習前端半年多了,還停留在新手村級,寫的文章可能有不少問題,思惟方式和邏輯上還不夠嚴密,但願能指出問題,謝謝!css
=====================================================================前端
數據類型分爲兩種,基本數據類型和引用數據類型。ES6中的數據類型不作討論,基本類型包括了:string、null、undefined、number、boolean。引用類型:object。它是由一個或者多個鍵名鍵值對的對象。基本數據類型保存在棧內存中,而引用類型保存在堆內存中,棧內存中的數據必須是固定大小的,而引用類型的大小不固定,因此只能白存在堆內存中,將堆內存中的地址直接賦值出來以後就能夠訪問引用類型數據。他們之間的差異以下:node
區別 | 基本數據類型 | 引用數據類型 |
---|---|---|
保存位置 | 棧內存 | 堆內存 |
數據大小 | 固定大小 | 不固定 |
訪問方式 | 經過變量保存的值 | 經過保存的地址訪問 |
在淺複製時直接將簡單類型值分別賦值給target,而若是複製的是對象就不能經過簡單的復值來複制。這樣作成的結果就是target引用值改變時也會引發原對象的改變。ajax
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)
只複製第一層屬性的方式爲淺複製,若是數據類型所有是基本類型是能夠成功的,而在複製引用類型時,則須要複製到基本類型爲止才能夠保證互不影響。數組
原生方法的基本實現方式: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
在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插件主要分爲兩類:1,類級別 2,對象級別
類方法。直接使用$類引用,不須要實例化就可以使用。類方法在項目中被設置爲工具類使用。
對象級別。必須先建立實例,而後才能經過實例調用該實例方法。
直接擴展JQuery類,至關於靜態方法,典型的方法有$.ajax,擴展方法:
$.extend({ add:function(a,b){ return a + b; }, divide:function(a,b){ return a/b; } }) //調用方式 $.add(3,0); $.divide(9,3);
這種擴展方式是基於原型對象的,擴展插件時通常使用這種方式,擴展以後只有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" });
爲何須要面向對象的插件方法?方便管理直接使用,第二不會影響外部命名空間。
;(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" })