本碼農的慣例,開篇廢話幾句...
前天小生又被虐了...
沒辦法,作為一個資深code user,我用代碼的能力,解決問題的能力自問是不弱的...
但是自身的前端基礎說實話還是不過硬,最明顯的表現就是,對于JS核心的研究做得比較少。
另外就是概念方面很脆弱(PS:我的習慣是用通俗的比喻來了解技術,那些乏味的概念實在記不住幾個)
現實依舊一如既往的殘酷,許多平時用的很少的知識點經常被用作展現一個人能力水準的名額。
其實也是合理的,畢竟用的少就不去研究,這是說不過去的。
OK,廢話完畢,開始正題。
關于 delete
下面會列出許多例子,如果你要用開發者工具來調試,在測試不同例子的時候麻煩重新整理一下頁面,否則可能會由于用了同樣的變量而出現問題。
首先來個簡單的例子(其實在此之前我在工作中完全沒用過 delete,是以一旦被人問到這個,十有八九是要跪了的...)
var x = {a: 1};
delete x.a; // true
x.a; // undefined (删除對象屬性成功)
然後再試試删除變量
var x = 1;
delete x; // false;
x; // 1 (删除變量失敗了)
再試試删除一個函數看看...
//先試試這種寫法,直接聲明一個函數
function x() {};
delete x; // false;
typeof x; // function (删除函數失敗)
//再試試這種寫法,聲明一個變量,并将一個匿名函數指派給它
var y = function () {};
delete y; // false;
typeof y; // function (删除函數失敗)
下面,讓我來毀你的三觀...
z = function () {};
delete z; // true
typeof z;// undefined (删除成功了...)
為什麼這次能夠删除了呢?假如都是在全局上下文下面執行,那他們就都是 window 對象的屬性,為什麼上面的例子又删除成功了呢?
原因是上面的例子,實際上相當于執行了下面的代碼
window.z = function () {}; // 對屬性指派,而不是定義
delete z; // true
typeof z;// undefined (删除成功了...)
假如一個變量沒有用 var 進行聲明,那麼 JS 會将該變量變成 window 對象的屬性,然後對屬性進行指派。
由于這個過程對開發者而言是不可見的,是以稱它為隐式屬性指派。當然你也可以顯式的對window對象添加一個屬性并對其指派,結果是一樣的。
這種顯式/隐式指派的屬性,是可以用 delete 删除的。
上面的例子都是在全局環境下的,下面我們将戰場轉移到局部環境,匿名函數的内部。
(function () {
this.x = 1; // 顯式屬性指派
var y = 2; // 聲明一個局部變量
z = 3; // 隐式屬性指派,變成window對象的一個屬性
delete this.x; // true
delete x; // false
delete z; // true
console.info(this.x); // undefined (删除成功)
console.info(y); // 2 (删除失敗)
console.info(window.z); // undefined (删除成功)
})()
可以看到,結果全局環境下是差不多的。
我們把環境轉移到更殘酷的戰場 —— eval
//溫馨提示,測試新一段代碼前注意先重新整理下頁面
eval('function x(){}');
typeof x; // function
delete x; // true;
typeof x; // undefined (删除成功)
然後我又來毀你三觀了...
//老規矩,測試新一段代碼前注意先重新整理下頁面,後面就不再提示了...
var x = 1;
eval('function x(){}');
typeof x; // function
delete x; // false;
typeof x; // function(删除失敗!)
為什麼會這樣?甚至你再看看這個例子
//在 eval 後面聲明一個同名變量,也删除失敗
eval('function x(){}');
var x = 1;
typeof x; // function
delete x; // false;
typeof x; // function(删除失敗!)
首先,這裡明确一點就是,eval 裡的代碼聲明的變量或者函數,是可以用 delete 删除的。
eval 裡的代碼聲明的變量或者函數,如果在外部已經被聲明了(不單單是 eval 的上文,還包括 eval 的下文),則會對該變量重新進行指派,因為 eval 裡代碼的調用,永遠在其外部上下文中其他變量和函數聲明之後。
這樣一來,該變量就是已經存在的,而非執行 eval 傳入的代碼聲明的了。
是以上面的例子之是以删除失敗,不是因為 eval 裡代碼聲明的變量删除不了,而是那個變量壓根就不是 eval 裡的代碼聲明的。
OK,最後來一份比淺層總結(之是以說是淺層總結,是因為其實還有一些情況這裡是沒列出的,但那太過深入了,如果你有興趣可以研究,但如非必要,我覺得還是算了吧...記住那些東西實在是累人...)
1.顯示/隐式的對象屬性指派,該屬性都是可以通過 delete 删除的
2.通常情況下,代碼中聲明的變量和函數不能通過 delete 删除
3.通過 eval 函數傳入代碼聲明的變量和函數可以通過 delete 删除,前提是在此之前,該變量不曾在外部上下文中被聲明
最後,如果你想對delete作更深入的研究,想揪其原理,這裡有一篇很好很專業的文章:http://justjavac.com/javascript/2013/04/04/understanding-delete-in-javascript.html
關于對象複制
由于對象是引用類型的,是以當我們響應克隆一個對象的時候,不能用正常的指派來完成。
// 下面的代碼,實際上隻是讓兩個不同的變量引用了同一個對象
var a = {
name : 'Jack',
sex : 'male'
};
var b = a;
假如要将對象複制,正确的做法應該是這樣
var a = {
name : 'Jack',
sex : 'male'
};
var b = {};
for (var key in a) {
b[key] = a[key];
}
但...這樣就夠了麼?萬一對象的某個屬性是另一個對象,或者是一個數組呢?
這樣一來,就涉及到對象的淺複制和深複制了。
上面的做法,隻能實作對象的淺複制,對于深複制,我們要用下面的方法來完成。
// 聲明一個對象克隆函數
function clone (obj) {
var result = {};
var _clone = function (objO, objC) {
for (var key in objO) {
var curProperty = objO[key];
if (curProperty.constructor == Array) {
// 屬性的值為數組時...
objC[key] = [];
for (var i = 0, len = curProperty.length; i < len; i++) {
objC[key].push(curProperty[i]);
}
} else if (curProperty.constructor == Object) {
// 屬性的值為對象時...
objC[key] = {};
_clone(curProperty, objC[key]);
} else {
// 其他情況...
objC[key] = objO[key];
}
}
};
_clone(obj, result);
return result;
}
// 測試...
var a = {
name : 'Jack',
love : ['Lily', 'Kate', 'Lucy'],
fav : {
color : ['red', 'black'],
sport : 'football'
},
eat : function () {
alert('fuck you!')
}
};
var b = clone(a);