1. 引用類型有哪些,非引用類型有哪些?
- 基本類型(非引用類型):Number, String, Boolean, Undefined, Null, Symbol,是保存在棧內存中的簡單數據段,基本類型的值按值訪問。
- 引用類型:Object, Array, Date, RegExp, Function, Global, Math,等可能由多個值構成的對象,引用類型的值保存在堆內存中,當復制保存對象的某個變量時操作的是對象的引用,也就是指向該對象在內存中所在位置的指針,為對象添加屬性時操作的是實際的對象,引用類型的值按引用訪問。
-三個特殊的引用類型:Boolean, Number, String,也叫基本包裝類型,每當讀取一個基本類型值時,后臺會創建一個對應的基本包裝類型的對象,從而讓我們能調用一些方法來操作這些數據。既與引用類型相似,也具有與各自的基本類型相應的特殊行為(方法)。
引用類型和基本包裝類型最主要的區別就是對象的生存期,使用 new 操作符創建的引用類型的實例,在執行流離開當前作用域之前都一直保存在內存中。而自動創建的基本包裝類型的對象,則只存在于一行代碼的之心瞬間,然后立即被銷毀。不建議顯式的創建(即用 new 操作符)基本包裝類型的對象。
2. 如下代碼輸出什么,為什么?
var obj1 = {a:1, b:2};
var obj2 = {a:1, b:2};
console.log(obj1 == obj2); //false,obj1 和 obj2 是指向堆內存中兩個不同位置的對象的指針
console.log(obj1 = obj2); //{a:1, b:2},將 obj2 的值賦給了 obj1,obj1 和 obj2 指向堆內的同一個對象
console.log(obj1 == obj2); //true,兩個變量引用同一個對象
3. 如下代碼輸出什么,為什么?
var a = 1
var b = 2
var c = { name: '饑人谷', age: 2 }
var d = [a, b, c]
var aa = a //復制基本類型
var bb = b //復制基本類型
var cc = c //復制指針
var dd = d //復制指針
a = 11
b = 22
c.name = 'hello'
d[2]['age'] = 3 ////修改堆內存中c.age值為3
console.log(aa) //1,基本類型的賦值,a 與 aa 兩個變量不會相互影響
console.log(bb) //2,基本類型的賦值,b 與 bb 兩個變量不會相互影響
console.log(cc) //{name: "hello", age: 3},c 是引用類型的值,將 c 的值賦值給了 cc,所以 cc 與 c 引用的是同一個對象,修改 c 中 name 屬性的值,實際上修改的是對象的屬性值,所以cc 中的 name 屬性值也會隨之改動
console.log(dd) //[1, 2, {name: "hello", age: 3}],d也是引用類型的值,dd和d引用同一對象,a和b是基本類型按值傳遞,dd指針指向的內存中保存的c的值已經改變,使用修改之后的值。
4. 如下代碼輸出什么,為什么?
var a = 1
var c = { name: 'jirengu', age: 2 }
function f1(n){
++n
}
function f2(obj){
++obj.age
}
f1(a)
f2(c)
f1(c.age)
console.log(a) //1,f1中相當于聲明了一個 n=a , a 是基本類型的值,n 和 a 互相不影響
console.log(c) //{name: "jirengu", age: 3},f2中相當于聲明了一個 obj = c ,c 是引用類型的值,obj 發生了變化,c 也會發生變化,所以會輸出{name: "jirengu", age: 3};f1(c.age)中 c.age 也是一個基本類型的值,所以 c 并不會因此發生變化。
5. 過濾如下數組,只保留正數,直接在原數組上操作
var arr = [3, 1, 0, -1, -3, 2, -5]
function filter(arr) {
for(n = 0;n<arr.length; n++){
if(arr[n]<=0){
arr.splice(n,1);
filter(arr)
}
}
}
filter(arr);
console.log(arr); // [3,1,2]
6. 過濾如下數組,只保留正數,原數組不變,生成新數組
var arr = [3, 1, 0, -1, -3, 2, -5];
function filter(arr) {
var newArr = [];
for (var i = 0; i < arr.length; i++) {
if (arr[i] > 0) {
newArr.push(arr[i]);
}
}
return newArr;
}
var arr2 = filter(arr); //arr2 與 filter(arr) 指向同一個對象
console.log(arr2); // [3,1,2]
console.log(arr); // [3,1,0,-1,-3,2,-5]
7. 寫一個深拷貝函數,用兩種方式實現
- 深復制和淺復制只針對引用類型。簡單來說,淺復制只復制一層對象的屬性,而深復制則遞歸復制了所有層級。淺復制會導致兩個指針指向同一塊內存地址,深復制將原對象的各個屬性逐個復制出去,而且將原對象各個屬性所包含的對象也依次采用深復制的方法遞歸復制到新對象上。這就不會存在兩個指針指向同一個對象的問題。
- 方法一,遞歸
function deepCopy(obj) {
var newObj = {};
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
if (typeof obj[key] === 'number' || typeof obj[key] === 'string' || typeof obj[key] === 'boolean'
|| obj[key] === null || obj[key] === undefined) {
newObj[key] = obj[key];
} else {
newObj[key] = deepCopy(obj[key])
}
}
}
return newObj;
}
- 方法二,JSON對象的 stringify 和 parse 方法,這兩個方法分別用于把JavaScript對象序列化為JSON字符串和把JSON字符串解析為原生JavaScript值
b = JSON.parse( JSON.stringify(a) )
缺點是所有函數及原型成員(原型鏈)都會被有意忽略,不體現在結果中,值為 undefined 的任何屬性也會被跳過