一、基本類型VS引用類型
注: 這里的內(nèi)存,為虛擬內(nèi)存
1、引用類型:
- 定義:保存在堆內(nèi)存中的對(duì)象,變量中保存的實(shí)際上只是一個(gè)指針,這個(gè)指針執(zhí)行內(nèi)存中的另一個(gè)位置,由該位置保存對(duì)象
- 包括:對(duì)象、數(shù)組、函數(shù)、正則
假設(shè)變量中有一個(gè)函數(shù),函數(shù)內(nèi)東西特別多(或者有一個(gè)對(duì)象,對(duì)象里的數(shù)據(jù)特別大),這里可選堆的空白處存放函數(shù)、對(duì)象的數(shù)據(jù)(隨機(jī)選擇未使用的空白堆,隨意變大變?。旁诙阎械木鶠橐妙愋?/p>
2、基本類型(值類型):
- 定義:指的是保存在棧內(nèi)存中的簡(jiǎn)單字段(成塊排列,棧,允許放進(jìn)去拿出來)
- 包括:數(shù)值(number)、布爾值(boolean)、
null、undefined、string(在賦值傳遞中會(huì)以引用類型的方式來處理)
棧里面仍存有變量,只不過存放的不是數(shù)據(jù),而是大數(shù)據(jù)地址,比如這個(gè)地址為0x0011,棧內(nèi)存放的東西,均為可控、較小容量。從一個(gè)變量向另一個(gè)變量賦值基本類型時(shí),會(huì)在該變量上創(chuàng)建一個(gè)新值,然后再把該值復(fù)制到為新變量分配的位置上。
3、實(shí)例一:基本類型
var a
var b
var obj
var obj2
a = 1;
b = 2;
var obj = {
name: 'xiaoqin',
sex: 'male',
age: 30,
friend: {
name: 'hello', age: 100
}
}
var newObj = {}
b = a;
console.log(b)
//返回1
如圖:
(1)基本類型的值被賦值給另一個(gè)變量,其實(shí)就是分配內(nèi)存空間
一開始,a的值為 1 ,當(dāng)使用a 來初始化b 時(shí),b 值此時(shí)為1。但b中的1與a中的是完全獨(dú)立的,該值只是a中的值的一個(gè)副本。說明在棧里變量再次變化,但這個(gè)兩個(gè)變量可以參加任何操作而相互不受影響。
總結(jié):
一個(gè)變量賦值給另一個(gè)變量時(shí),其實(shí)是分配了一塊新的內(nèi)存空間。按照以上操作,基本類型在賦值操作后,事實(shí)上就a分配了一塊新內(nèi)存空間給b,兩個(gè)變量是相互不受影響。
(2)基本類型的比較是值的比較 只有在它們的值相等的時(shí)候它們才相等。 當(dāng)比較的兩個(gè)值的類型不同的時(shí)候==運(yùn)算符會(huì)進(jìn)行類型轉(zhuǎn)換,但是當(dāng)兩個(gè)值的類型相同的時(shí)候,即使是==也相當(dāng)于是===。
var a = 1;
var b = true;
console.log(a == b);//true
(3)基本類型的變量其實(shí)就是存放在棧區(qū)。結(jié)合以上,棧區(qū)指內(nèi)存里的棧內(nèi)存,但是棧區(qū)里包括了變量名和變量值。
4、實(shí)例二:(續(xù)上面的例子)引用類型
(1)引用類型的值是可變的
可為引用類型添加屬性和方法,也可以刪除其屬性和方法。
看一下這個(gè)例子:一個(gè)為引用類型的變量賦值給另一個(gè)為引用類型的變量
var obj2 = obj //控制臺(tái)測(cè)試一下二者的值
obj
// {name: "ruoyu", sex: "male", age: 30, friend: {…}}
obj2
// {name: "ruoyu", sex: "male", age: 30, friend: {…}}
值是一樣的。因為var obj2=obj,即通過obj的值(一個(gè)對(duì)象)賦值給obj2,那么obj2的值就是賦值后原本obj對(duì)應(yīng)屬性和值。作為一個(gè)引用類型,它被放在堆中。所以尋找obj2則在堆里找到,只是換了另一個(gè)名字為obj2
如圖:
總結(jié):
原本在棧中的對(duì)象分別指向了同一個(gè)堆,那么存放在堆中即為對(duì)象的內(nèi)存地址。引用類型的賦值其實(shí)是對(duì)象保存在棧區(qū)地址指針的賦值,因此兩個(gè)變量指向同一個(gè)對(duì)象,任何的操作都會(huì)相互影響。
(2)引用類型的比較是引用的比較
A、我們先看一下基本類型值的比較:
var obj3 = '{name: 'hello'}';
var obj4 = '{name: 'hello'}';
console.log( obj3 == obj4 );
// true
總結(jié):
可以得出基本類型的比較:當(dāng)兩個(gè)比較值的類型相同(如字符串)的時(shí)候,相當(dāng)于是用 === ,所以輸出是true。
B、再來看一下引用類型值的比較:
var obj3 = {name: 'hello'}
var obj4 = {name: 'hello'}
obj3 === obj4
//返回false,說明二者并不相等
為什么是false?不相等呢?
放在棧中的變量 obj3、obj4,聲明前置均為undefined,當(dāng)兩者均被被聲明值的時(shí)候,是兩個(gè)對(duì)象,引用類型是引用訪問,相當(dāng)于在堆中分別開辟了兩個(gè)空間,堆中會(huì)有對(duì)應(yīng)的屬性+值,此時(shí)這兩個(gè)對(duì)象在堆中存的便是堆的地址。obj4與obj3一樣都開辟了新的堆空間,但是存放的地址也不一樣。判斷obj3是否與obj4相等,看了分析之后,便知道堆存放的地址并不同,二者也就不相等
如圖:
(3)引用類型的值是同時(shí)保存在棧內(nèi)存和堆內(nèi)存中的對(duì)象
function sum(){
console.log('sum...')
}
var sum2 = sum;
sum2()
//返回sum... 二者是相等的
我們可以就此分析,函數(shù)function sum(),分別有變量sum和函數(shù)對(duì)象代碼(為引用類型,已放在堆中)。之后sum賦值給sum2,即sum2事實(shí)上使用的是賦值后sum所指代堆的內(nèi)存地址,即后續(xù)sum和sum2共用了堆里的代碼(變量的內(nèi)存地址就像指針一樣,通過JS自身引擎找到這個(gè)堆),一堆東西起了兩個(gè)不同的名字
如圖:
總結(jié): js不同于其他語言,其不允許直接訪問內(nèi)存中的位置,即不能直接操作對(duì)象的內(nèi)存空間,實(shí)際上,是操作對(duì)象的引用,所以引用類型的值是按引用訪問的。
準(zhǔn)確地說,引用類型的存儲(chǔ)需內(nèi)存的棧區(qū)(棧區(qū)是指內(nèi)存里的棧內(nèi)存)和堆區(qū)(堆區(qū)是指內(nèi)存里的堆內(nèi)存)共同完成,棧區(qū)內(nèi)存保存變量標(biāo)識(shí)符和指向堆內(nèi)存中該對(duì)象的指針,然后,棧區(qū)內(nèi)存地址也可以說是該對(duì)象在堆內(nèi)存的地址。
二、引用類型的實(shí)際應(yīng)用
1、函數(shù)的參數(shù)傳遞
第1個(gè)例子:
function inc(n){
n++;
}
var a = 10;
inc(a)
console.log(a)
//返回10
//等同于
function inc(){
var n = arguments[0]
n++
}
//在函數(shù)的一開始將var a = 10賦值進(jìn)var n = arguments[0], //n=arguments[0]=10,此時(shí)與n++為11并沒有返回,所以與a并無關(guān)系
var a = 10
inc(a)
console.log(a)
//返回10
?第2個(gè)例子:
function incObj(obj){
//var obj = o //0x0001
obj.n++
}
var o = {n: 10} //o = 0x0001 對(duì)其做聲明,為一個(gè)對(duì)象
incObj(o)
console.log(o)
//等同于
function incObj(){
var obj =arguments[0]
obj.n++
}
//incObj(o) 相當(dāng)于function incObj(){var obj =arguments[0];obj.n++},
//可知道obj=arguments[0]=o,相當(dāng)于設(shè)obj為臨時(shí)變量,而o= 0x0001 var o = {n: 10} incObj(o) console.log(o)
如圖:
總結(jié):
引用類型的本質(zhì),變量所存的是這個(gè)對(duì)象的內(nèi)存地址指向堆,當(dāng)去做賦值時(shí)是把這個(gè)地址進(jìn)行一個(gè)賦值;當(dāng)去訪問的時(shí)候是通過這個(gè)地址去訪問這個(gè)對(duì)象
?第3個(gè)例子:
function squireArr( arr ){
//var arr = 0x0011
for(var i = 0; i < arr.length; i++){
arr[i] = arr[i] * arr[i];
}
}
var arr = [2,1,3,6]
squireArr(arr)
console.log(arr)
//(4) [4, 1, 9, 36]
即把function squireArr(arr){}中的數(shù)組squireArr(arr)里的每一項(xiàng)變?yōu)樵瓉淼钠椒剑磪?shù)arr為數(shù)組里的值,用for循環(huán)進(jìn)行操作,外界調(diào)用時(shí),只需調(diào)用一次squireArr(arr),事實(shí)上數(shù)組squireArr(arr)操作就是對(duì)arr的操作
?第4個(gè)例子:
function squireArr2( arr ){
var newArr = [];
for(var i = 0; i < arr.length; i++){
newArr[i] = arr[i] * arr[i];
}
return newArr;
}
var arr2 = squireArr2(arr)
console.log(arr2) //返回(4) [16, 1, 81, 1296]
arr
// (4) [4, 1, 9, 36]
arr2 --> (4) [16, 1, 81, 1296]
2、對(duì)象的深淺拷貝
針對(duì)這個(gè)例子:
var obj;
var obj2;
var obj = {
name: 'ruoyu',
sex: 'male',
age: 30,
friend: {
name: 'hello',
age: 100
}
}
var obj2 = obj;
想要?jiǎng)?chuàng)造一個(gè)新的b,那么就需要遍歷原始a的每一項(xiàng)屬性+值,用來獲取成為新個(gè)體的b所需的東西,并一一對(duì)b進(jìn)行改造,即從一無所有,改造成與a相似的新個(gè)體,此為克隆。
如果在遍歷的時(shí)候,b這個(gè)新個(gè)體只是遍歷a的前半部分或者局部,那么這稱之為淺拷貝,如:
function shallowCopy(oldObj) {
var newObj = {};
for(var i in oldObj) {
if(oldObj.hasOwnProperty(i)) {
newObj[i] = oldObj[i];
}
}
return newObj;
}
而如果b是遍歷原始a的每一項(xiàng)屬性和值,但是b又是一個(gè)獨(dú)立個(gè)體,與a不相關(guān),當(dāng)修改b的時(shí)候,a仍然不會(huì)發(fā)生變化,而這叫做深拷貝,如:
function deepCopy(oldObj) {
var newObj = {};
for(var key in oldObj) {
if(typeof oldObj[key] === 'object') {
newObj[key] = deepCopy(oldObj[key]);
}else{
newObj[key] = oldObj[key];
}
}
return newObj;
}
json——string——對(duì)象