面試官想要的 JS 基本類型

面試的時(shí)候我們經(jīng)常會(huì)被問(wèn)答js的數(shù)據(jù)類型。大部分情況我們會(huì)這樣回答包括:

  1. 基本類型(值類型或者原始類型): Number、Boolean、String、NULL、Undefined以及ES6的Symbol;
  2. 引用類型:Object、Array、Function、Date等。

作者曾經(jīng)也是這樣回答的,并且一直覺(jué)得沒(méi)有什么問(wèn)題。那么面試官問(wèn)你 JS 數(shù)據(jù)類型時(shí),他想知道什么呢?接著往下看??????

1. 在內(nèi)存中的位置不同

  • 基本類型: 占用空間固定,保存在棧中;
  • 引用類型:占用空間不固定,保存在堆中;

棧(stack)為自動(dòng)分配的內(nèi)存空間,它由系統(tǒng)自動(dòng)釋放;使用一級(jí)緩存,被調(diào)用時(shí)通常處于存儲(chǔ)空間中,調(diào)用后被立即釋放。
堆(heap)則是動(dòng)態(tài)分配的內(nèi)存,大小不定也不會(huì)自動(dòng)釋放。使用二級(jí)緩存,生命周期與虛擬機(jī)的GC算法有關(guān)

當(dāng)一個(gè)方法執(zhí)行時(shí),每個(gè)方法都會(huì)建立自己的內(nèi)存棧,在這個(gè)方法內(nèi)定義的變量將會(huì)逐個(gè)放入這塊棧內(nèi)存里,隨著方法的執(zhí)行結(jié)束,這個(gè)方法的內(nèi)存棧也將自然銷毀了。因此,所有在方法中定義的變量都是放在棧內(nèi)存中的;棧中存儲(chǔ)的是基礎(chǔ)變量以及一些對(duì)象的引用變量,基礎(chǔ)變量的值是存儲(chǔ)在棧中,而引用變量存儲(chǔ)在棧中的是指向堆中的數(shù)組或者對(duì)象的地址,這就是為何修改引用類型總會(huì)影響到其他指向這個(gè)地址的引用變量。

當(dāng)我們?cè)诔绦蛑袆?chuàng)建一個(gè)對(duì)象時(shí),這個(gè)對(duì)象將被保存到運(yùn)行時(shí)數(shù)據(jù)區(qū)中,以便反復(fù)利用(因?yàn)閷?duì)象的創(chuàng)建成本通常較大),這個(gè)運(yùn)行時(shí)數(shù)據(jù)區(qū)就是堆內(nèi)存。堆內(nèi)存中的對(duì)象不會(huì)隨方法的結(jié)束而銷毀,即使方法結(jié)束后,這個(gè)對(duì)象還可能被另一個(gè)引用變量所引用(方法的參數(shù)傳遞時(shí)很常見(jiàn)),則這個(gè)對(duì)象依然不會(huì)被銷毀,只有當(dāng)一個(gè)對(duì)象沒(méi)有任何引用變量引用它時(shí),系統(tǒng)的垃圾回收機(jī)制才會(huì)在核實(shí)的時(shí)候回收它。

2. 賦值、淺拷貝、深拷貝

  • 對(duì)于基本類型值,賦值、淺拷貝、深拷貝時(shí)都是復(fù)制基本類型的值給新的變量,之后二個(gè)變量之間操作不在相互影響。
  • 對(duì)于引用類型值,分以下三個(gè)方面:
    • 賦值后二個(gè)變量指向同一個(gè)地址,一個(gè)變量改變時(shí),另一個(gè)也同樣改變;
    • 淺拷貝后得到一個(gè)新的變量,這個(gè)與之前的已經(jīng)不是指向同一個(gè)變量,改變時(shí)不會(huì)使原數(shù)據(jù)中的基本類型一同改變,但會(huì)改變會(huì)原數(shù)據(jù)中的引用類型數(shù)據(jù)
    • 深拷貝后得到的是一個(gè)新的變量,她的改變不會(huì)影響元數(shù)據(jù)
- 和原數(shù)據(jù)是否指向同一對(duì)象 第一層數(shù)據(jù)為基本數(shù)據(jù)類型 原數(shù)據(jù)中包含子對(duì)象
賦值 改變會(huì)使原數(shù)據(jù)一同改變 改變會(huì)使原數(shù)據(jù)一同改變
淺拷貝 改變不會(huì)使原數(shù)據(jù)一同改變 改變會(huì)使原數(shù)據(jù)一同改變
深拷貝 改變不會(huì)使原數(shù)據(jù)一同改變 改變不會(huì)使原數(shù)據(jù)一同改變
    var obj1 = {
        'name' : 'zhangsan',
        'age' :  '18',
        'language' : [1,[2,3],[4,5]],
    };

    var obj2 = obj1;

    var obj3 = shallowCopy(obj1);
    function shallowCopy(src) {
        var dst = {};
        for (var prop in src) {
            if (src.hasOwnProperty(prop)) {
                dst[prop] = src[prop];
            }
        }
        return dst;
    }

    obj2.name = "lisi";
    obj3.age = "20";

    obj2.language[1] = ["二","三"];
    obj3.language[2] = ["四","五"];

    console.log(obj1);  
    //obj1 = {
    //    'name' : 'lisi',
    //    'age' :  '18',
    //    'language' : [1,["二","三"],["四","五"]],
    //};

    console.log(obj2);
    //obj2 = {
    //    'name' : 'lisi',
    //    'age' :  '18',
    //    'language' : [1,["二","三"],["四","五"]],
    //};

    console.log(obj3);
    //obj3 = {
    //    'name' : 'zhangsan',
    //    'age' :  '20',
    //    'language' : [1,["二","三"],["四","五"]],
    //};

2.1淺拷貝

數(shù)組常用的淺拷貝方法slice(),concat(),Array.from(),以及es6的解構(gòu)

var arr1 = [1, 2,{a:1,b:2,c:3,d:4}];
var arr2 = arr1.slice();
var arr3 = arr1.concat();
var arr4 = Array.from(arr1);
var arr5 = [...arr1];
arr2[0]=2;
arr2[2].a=2;
arr3[0]=3;
arr3[2].b=3;
arr4[0]=4;
arr4[2].c=4;
arr5[0]=5;
arr5[2].d=5;
// arr1[1,2,{a:2,b:3,c:4,d:5}]
// arr2[2,2,{a:2,b:3,c:4,d:5}]
// arr3[3,2,{a:2,b:3,c:4,d:5}]
// arr4[4,2,{a:2,b:3,c:4,d:5}]
// arr5[5,2,{a:2,b:3,c:4,d:5}]

對(duì)象常用的淺拷貝方法Object.assign(),es6解構(gòu)

var obj1 = {
    x: 1, 
    y: {
        m: 1
    }
};
var obj2 = Object.assign({}, obj1);
console.log(obj1) //{x: 1, y: {m: 1}}
console.log(obj2) //{x: 1, y: {m: 1}}
obj2.x=2;
obj2.y.m = 2; //修改obj2.y.m
console.log(obj1) //{x: 1, y: {m: 2}}
console.log(obj2) //{x: 2, y: {m: 2}}

我們自己實(shí)現(xiàn)一個(gè)淺拷貝

var obj = { a:1, arr: [2,3] };
var shallowObj = shallowCopy(obj);
var shallowCopy = function(obj) {
    // 只拷貝對(duì)象
    if (typeof obj !== 'object') return;
    // 根據(jù)obj的類型判斷是新建一個(gè)數(shù)組還是對(duì)象
    var newObj = obj instanceof Array ? [] : {};
    // 遍歷obj,并且判斷是obj的屬性才拷貝
    for (var key in obj) {
        if (obj.hasOwnProperty(key)) {
            newObj[key] = obj[key];
        }
    }
    return newObj;
}

2.2. 深拷貝

比較簡(jiǎn)單粗暴的的做法是使用JSON.parse(JSON.stringify(obj))

var arr = ['old', 1, true, ['old1', 'old2'], {old: 1}]
var new_arr = JSON.parse( JSON.stringify(arr) );
new_arr[4].old=4;
console.log(arr); //['old', 1, true, ['old1', 'old2'], {old: 1}]
console.log(new_arr); //['old', 1, true, ['old1', 'old2'], {old: 4}]

JSON.parse(JSON.stringify(obj)) 看起來(lái)很不錯(cuò),不過(guò) MDN文檔 的描述有句話寫(xiě)的很清楚:

undefined、任意的函數(shù)以及 symbol 值,在序列化過(guò)程中會(huì)被忽略(出現(xiàn)在非數(shù)組對(duì)象的屬性值中時(shí))或者被轉(zhuǎn)換成 null(出現(xiàn)在數(shù)組中時(shí))。

但是在平時(shí)的開(kāi)發(fā)中JSON.parse(JSON.stringify(obj))已經(jīng)滿足90%的使用場(chǎng)景了。

下面我們自己來(lái)實(shí)現(xiàn)一個(gè)

var deepCopy = function(obj) {
    if (typeof obj !== 'object') return;
    var newObj = obj instanceof Array ? [] : {};
    for (var key in obj) {
        if (obj.hasOwnProperty(key)) {
            newObj[key] = typeof obj[key] === 'object' ? deepCopy(obj[key]) : obj[key];
        }
    }
    return newObj;
}

3. 參數(shù)的傳遞

所有的函數(shù)參數(shù)都是按值傳遞。也就是說(shuō)把函數(shù)外面的值賦值給函數(shù)內(nèi)部的參數(shù),就和把一個(gè)值從一個(gè)變量賦值給另一個(gè)一樣;

  • 基本類型
var a = 2;
function add(x) {
 return x = x + 2;
}
var result = add(a);
console.log(a, result); // 2 4
  • 引用類型
function setName(obj) {
  obj.name = 'laowang';
  obj = new Object();
  obj.name = 'Tom';
}
var person = new Object();
setName(person);
console.log(person.name); // laowang

很多人錯(cuò)誤地以為在局部作用域中修改的對(duì)象在全局作用域中反映出來(lái)就是說(shuō)明參數(shù)是按引用傳遞的。
但是通過(guò)上面的例子可以看出如果person是按引用傳遞的最終的person.name應(yīng)該是Tom。
實(shí)際上當(dāng)函數(shù)內(nèi)部重寫(xiě)obj時(shí),這個(gè)變量引用的就是一個(gè)局部變量了。而這個(gè)變量會(huì)在函數(shù)執(zhí)行結(jié)束后銷毀。(這是是在js高級(jí)程序設(shè)計(jì)看到的,還不是很清楚)

4. 判斷方法

基本類型用typeof,引用類型用instanceof

特別注意typeof null"object", null instanceof Objecttrue;

console.log(typeof "Nicholas"); // "string"
console.log(typeof 10);         // "number"
console.log(typeof true);       // "boolean"
console.log(typeof undefined);  // "undefined"
console.log(typeof null);      // "object"

var items = [];
var obj = {};
function reflect(value){
  return value;
}

console.log(items instanceof Array); // true;
console.log(obj instanceof Object); // true;
console.log(reflect instanceof Function); // true;

Object.prototype.toString.call([]).slice(8, -1)有興趣的同學(xué)可以看一下這個(gè)是干什么的

5. 總結(jié)

JS 數(shù)據(jù)類型

閱讀原文

看完文章,還有福利拿哦,往下看??????
感興趣的小伙伴可以在公號(hào)【grain先森】后臺(tái)回復(fù)【190315】獲取【Css 參考規(guī)范】,可以轉(zhuǎn)發(fā)朋友圈和你的朋友分享哦。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Swift1> Swift和OC的區(qū)別1.1> Swift沒(méi)有地址/指針的概念1.2> 泛型1.3> 類型嚴(yán)謹(jǐn) 對(duì)...
    cosWriter閱讀 11,685評(píng)論 1 32
  • 1.設(shè)計(jì)模式是什么? 你知道哪些設(shè)計(jì)模式,并簡(jiǎn)要敘述?設(shè)計(jì)模式是一種編碼經(jīng)驗(yàn),就是用比較成熟的邏輯去處理某一種類型...
    龍飝閱讀 2,305評(píng)論 0 12
  • 面試題參考1 : 面試題[http://www.cocoachina.com/ios/20150803/12872...
    江河_ios閱讀 1,825評(píng)論 0 4
  • 介紹一下Objective-c常用的函數(shù),常數(shù)變量算術(shù)函數(shù)【算術(shù)函數(shù)】 函數(shù)名說(shuō)明 int rand()隨機(jī)數(shù)生成...
    噬尾蛇閱讀 300評(píng)論 0 1
  • 2017.5.12 滿全 打卡 感謝大家的認(rèn)真欣賞! 感謝伙伴們的一起成長(zhǎng)! 今天看到這個(gè)寫(xiě)作要求時(shí),我就想到...
    王者甘閱讀 120評(píng)論 0 0

友情鏈接更多精彩內(nèi)容