淺析JavaScript基本類型與引用類型
對(duì)于 JavaScript 類型,可以簡(jiǎn)單地概括為:相對(duì)于強(qiáng)類型語(yǔ)言來(lái)說,它是弱(松散)類型的語(yǔ)言;有基本類型和引用類型,他們是區(qū)別是一個(gè)有固定空間存在于棧內(nèi)存中,一個(gè)沒有固定空間保存在堆內(nèi)存中并且在棧內(nèi)存中保存了一個(gè)指向?qū)崿F(xiàn)位置的指針。
市面上很多書都有不小的篇幅在講。這篇文章會(huì)講幾個(gè)方面,這些方面可能會(huì)需要你對(duì) JavaScript 已經(jīng)有了一些簡(jiǎn)單的了解,特別是 JavaScript 的類型。如果還不一解,可以隨手拿起一本關(guān)于 JavaScript 的書翻翻,再來(lái)看本文。
一、基本類型與引用類型
1.基本類型:Undefined / Null / Boolean / Number / String
2.引用類型:Object / Array / Function / Date / RegExp / Error / Map / Set …
為什么引用類型沒有枚舉完呢,因?yàn)檫@里面你了解這么多就夠了,至少在我講的這篇中這些已經(jīng)足夠。其他的可能很少會(huì)用到,甚至像 Map 、Set 這樣的也不是所有瀏覽器都支持。
二、JavaScript 類型的判斷
在 JavaScript 有兩個(gè) operator 可以用以判斷類型。他們是 typeof 和 instanceof,不過圈子很小,它們混的可不是那么好,是出了名的不靠譜。少數(shù)情況也是對(duì)的,很多情況下是不靠譜的??纯淳椭懒耍?/P>
// 靠譜的時(shí)候:
typeof 'sofish' // string
new String('sofish') instanceof String // true
// 不靠譜的時(shí)候:
typeof [] // object
typeof null // object
'sofish' instanceof String // false
呃~ 可能很多初學(xué)的 JavaScript 程序員會(huì)因此爆粗口。還大部分人在需要用 JS 的時(shí)候已經(jīng)有了 jQuery 等這樣的庫(kù),他們都做了封裝,讓你可以方便地檢測(cè)類型。當(dāng)然,事實(shí)上要檢測(cè)也不麻煩,因?yàn)槟蔷洹冈?JavaScript 中,一切都是對(duì)象」,當(dāng)然像很多文檔中說到的,undefined 其實(shí)和 NaN, Infinity 都只是一個(gè)全局屬性。你大概知道就可以了。但「對(duì)象」可以幫到我們:
/* 檢測(cè)對(duì)象類型
* @param: obj {JavaScript Object}
* @param: type {String} 以大寫開頭的 JS 類型名
* @return: {Boolean}
*/
function is(obj, type) {
return Object.prototype.toString.call(obj).slice(8, -1) === type;
}
這樣的話,我們就可以利用 is 這個(gè)函數(shù)來(lái)幫我們搞定類型判斷了,并且這個(gè)簡(jiǎn)單的函數(shù)有很好的兼容性,可以用到你的項(xiàng)目中去。情況如:
is('sofish', 'String') // true
is(null, 'Null') // true
is(new Set(), 'Set') // true
三、JavaScript 類型的轉(zhuǎn)換
在 JavaScript 中,變量(屬性)的類型是可以改變的。最常看到的是 String 與 Number 之間的轉(zhuǎn)換。如何把 1 + '2' 變成 12 呢?這里面有必要理解一下 + 號(hào) operator,它是一個(gè)數(shù)學(xué)運(yùn)算符,同時(shí)也是 JavaScript 中的字符串連字符。所以新手會(huì)經(jīng)常會(huì)看到一個(gè)有趣的現(xiàn)象,當(dāng)使用 + 號(hào)的時(shí)候有時(shí)計(jì)算出來(lái)的不是想要的,而用 - 號(hào)卻總能得到「正確」的答案。
1 + '2' // '12'
1 + (+'2') // 3
1 - '2' // -1
這里面其實(shí)就是因?yàn)?+ 的雙重角色導(dǎo)致的。在上面的代碼中,可以注意到第二條表達(dá)式在 String 前面運(yùn)用了一個(gè) + 號(hào),強(qiáng)制把它的類轉(zhuǎn)換為 Number。而對(duì)于 JavaScript 的類型轉(zhuǎn)換理解,大多數(shù)情況下,只要理解 + 具有雙重角色就可以了。其他的可以理解類,類似都是可以用賦值/重載來(lái)修改的,甚至包括 Error:
var err = new Error();
console.log(err instanceof Error); // true
err = 'sofish';
console.log(err); // 'sofish'
四、JavaScript 引用類型
這一點(diǎn)是本文的一個(gè)難點(diǎn)。相于基本類型,引用可以為其添加屬性和方法;引用類似的值是一個(gè)引用,把一個(gè)引用類型的值賦給一個(gè)變量,他們所指向的是同一存儲(chǔ)在堆內(nèi)存中的值。變量(屬性)可以重載,但復(fù)制會(huì)是一件很有趣的事情,后面我們會(huì)詳細(xì)來(lái)說。
1. 添加屬性和方法
下面的代碼我們將會(huì)看到,假設(shè)我們對(duì)一個(gè)基本類似賦值,它并不會(huì)報(bào)錯(cuò),但在獲取的時(shí)候卻是失效的:
var arr = [1,2,3];
arr.hello = 'world';
console.log(arr.hello); // 'world'
var str = 'sofish';
str.hello = 'world';
console.log(str.hello); // undefined
2. 引用類型值的操作
由于引用類型存儲(chǔ)在棧內(nèi)存中的是一個(gè)引用,那么當(dāng)我們指向的同一個(gè)原始的值,對(duì)值的操作將會(huì)影響所有引用;這里有一個(gè)例是,重新賦值(并非對(duì)值的直接操作)會(huì)重新創(chuàng)建一個(gè)對(duì)象,并不會(huì)改變?cè)贾怠1热纾?BR>
var arr = [1,2,3], sofish = arr;
sofish.push('hello world');
console.log(arr); // [1, 2, 3, 'hello world']
// 非相同類型
sofish = ['not a fish']; // 當(dāng) sofish 類似改變時(shí),不會(huì)改變?cè)贾?BR>console.log(arr);// [1, 2, 3, 'hello world']
3. 引用類型值的復(fù)制
對(duì)原始值的操作會(huì)影響所有引用,而這不一定是我們想要的,有時(shí)候我們需要復(fù)制一個(gè)全新的對(duì)象,操作的時(shí)候不影響其他引用。而一般情況也,像 Date / Function / RegExp … 都很少有具體的操作,主要是像 Array 和 Object 會(huì)有添加項(xiàng)、屬性等操作。所以我們主要需要理解的是如何復(fù)制 Array 和 Object 對(duì)象。
3.1 數(shù)組的復(fù)制
在 Array 對(duì)象中,存在 slice 方法返回一個(gè)截取的數(shù)組,在 ES5 中 filter 等也返回一個(gè)新的數(shù)組,那么我們可能利用這個(gè)方法來(lái)進(jìn)行復(fù)制。
var arr = [1, 2, 3];
var sofish = arr.slice();
// 對(duì)新的數(shù)組進(jìn)行操作并不會(huì)影響到原始數(shù)組
sofish.push('hello world');
console.log(arr); // [1, 2, 3]
3.2 對(duì)象的復(fù)制
在 Array 的復(fù)制中我們使用的是 slice 方法,實(shí)際上對(duì)于 Array 和 Object 中都可以利用 for ... in 循環(huán)來(lái)進(jìn)行遍歷并賦值來(lái)進(jìn)行復(fù)制。
var obj = { name: 'sofish' }, sofish = {}, p;
for (p in obj) sofish[p] = obj[p];
// 對(duì)新的對(duì)象操作并不會(huì)影響原始值
sofish.say = function() {};
console.log(obj); // { name: 'sofish' }
3.3 Shadow / Deep Copy
像上面的操作,就是我們常說的淺拷貝(Shadow Copy)。不過在 Array 和 Object 都可以有多層(維),像這樣的拷貝只考慮到最上面一層的值,在可能存在的值中的 Array 和 Object 都還是指向了原始對(duì)象。比如:
var arr = [1, { bio: 'not a fish' } ], sofish = [], p;
for(p in arr) {
sofish[p] = arr[p];
}
// 對(duì) `sofish` 中包含的對(duì)象 `cat` 的操作會(huì)影響原始值
sofish[1].bio = 'hackable';
console.log(arr);// [1, cat: { bio: 'hackable' } ]
那么如何做呢?來(lái)一個(gè) copy() 函數(shù)解決這個(gè)問題:
/* 復(fù)制對(duì)象
* @param: obj {JavaScript Object} 原始對(duì)象
* @param: isDeep {Boolean} 是否為深拷貝
* @return: {JavaScript Object} 返回一個(gè)新的對(duì)象
*/
function copy(obj, isDeep) {
var ret = obj.slice ? [] : {}, p, prop;
// 配合 is 函數(shù)使用
if(!isDeep && is(obj, 'Array')) return obj.slice();
for(p in obj) {
if(!obj.hasOwnProperty(p)) continue;
prop = obj[p];
ret[p] = (is(prop, 'Object') || is(prop, 'Array')) ?
copy(prop, isDeep) : prop;
}
return ret;
}
這樣,我們就可以通過 copy(obj, isDeep) 函數(shù)來(lái)復(fù)制一個(gè) Array 或者 Object ??梢詼y(cè)試一下:
var arr = [1, {bio: 'not a fish'}];
var sofish = copy(arr);
// 淺拷貝對(duì)于第一層的操作不影響原始值,但影響第二層
sofish.push('cat');
console.log(arr); // [1, {bio: 'not a fish'}]
sofish[1].bio = 'hello world';
console.log(arr) // [1, {bio: 'hello world'}]
// 深拷貝則不會(huì)影響原始值
sofish = copy(arr, 1);
sofish[1].bio = 'foo or bar';
console.log(arr); // [1, {bio: 'hello world'}]
到此。你基本上要了解的關(guān)于類型的比較難的點(diǎn),應(yīng)該是都基本了解了。當(dāng)然,復(fù)制是最麻煩的一個(gè)點(diǎn),除了經(jīng)常需要操作的 Array 和 Object 來(lái)說,還有 Date / Function / RegExp 的復(fù)制。
相關(guān)文章
javascript創(chuàng)建和存儲(chǔ)cookie示例
javascript創(chuàng)建和存儲(chǔ)cookie,cookie是存儲(chǔ)于訪問者的計(jì)算機(jī)中的變量,下面看一下使用示例吧2014-01-01淺談checkbox的一些操作(實(shí)戰(zhàn)經(jīng)驗(yàn))
checkbox看起來(lái)很簡(jiǎn)單,有時(shí)很頭疼,有什么難的,下面就為大家介紹下checkbox的一些操作,不了解的朋友不要錯(cuò)過2013-11-11JavaScript中的call方法和apply方法使用對(duì)比
這篇文章主要介紹了JavaScript中的call方法和apply方法使用對(duì)比,需要的朋友可以參考下2015-08-08用JavaScript實(shí)現(xiàn)頁(yè)面重定向功能的教程
這篇文章主要介紹了用JavaScript實(shí)現(xiàn)頁(yè)面重定向功能的教程,是JS入門學(xué)習(xí)中的基礎(chǔ)知識(shí),需要的朋友可以參考下2015-06-06