资讯专栏INFORMATION COLUMN

《javascript高级程序设计》笔记:值类型与引用类型

TerryCai / 789人阅读

摘要:因此,出于性能的考虑,在拷贝的方式选择上,应该结合具体的业务环境来进行选择参考专题之深浅拷贝深入剖析的深复制

基本数据类型是按值访问的,因为可以操作保存在变量中的实际的值;
引用类型的值是保存在内存中的对象,在操作对象时,实际上是在操作对象的引用而不是实际的对象;

值类型

如果一个变量存储的是值的本身那么就是一个值类型number / string / Boolean / Null / Undefined —值类型的变量本身就是含有赋予给它的数值的,它的变量本身及保存的数据都存储在栈的内存块当中,当声明一个值类型时,必须对它初始化(给变量赋值)才能使用

var num1 = 123,
    num2 = num1;
num1 = 456;
console.log(num2);// 123

将值类型复制给另外一个值时(num2=num1),也就是num2重新再栈上开辟了一块空间,然后将num1中的内容复制一份放在num2中,当改变其中一个变量的值时,不会影响另外一个变量的值

引用类型

如果一个变量存储的是引用(地址),那么就是一个引用类型object—引用类型的值的存储与值类型不同,它分别存储在内存的堆和栈中,栈中存放的是指向堆中内容的地址,堆中存放的引用类型的地址(键值对)

var obj1 = {name: "xyc"};
var obj2 = obj1;
obj1.name = "lxy";
console.log(obj2.name); // "lxy"

obj2=obj1表示的是将栈上的地址复制一份给另一个对象,他们同时指向堆中的内容,当修改内容时,两个对象中的值都会发生改变

一个面试题
var o = new Object();
function foo(obj) {
  obj.name = "xyc";
  obj = new Object();
    obj.name = "lxy";
}
foo(o);
console.log(o.name); // ???

图解:
(1)新建对象var o = new Object();

(2)在foo的环境下执行obj.name = "xyc"
由于是参数传递,在局部作用域内相当于执行了obj = o

(3)在局部作用域内新建对象,并赋值相同的属性值

obj = new Object();
obj.name = "lxy";

(4)foo()执行完毕,局部作用域出栈,obj声明周期结束
此时,新建的对象依然存在,等待下一次内存自动回收机制将堆中的无引用对象销毁

4. 变量的深浅拷贝(重点)

什么是深浅拷贝?在mac电脑中我们可以对某个文件夹创建替身或者复制粘贴某个文件/文件夹,这两种方式都实现了对某个文件/文件夹的拷贝,但是,前者在文件中修改文件内容时,源文件也会修改,而后者的操作在修改文件内容时不会对源文件有影响

上面的例子,“创建替身” ==> 浅拷贝,“复制粘贴” ==> 深拷贝

从内存角度说明:浅拷贝只会在栈内存中开辟空间存放指向源文件的变量,而深拷贝会在堆内存也拷贝文件

深浅拷贝仅是针对于数组和对象而言,不严谨的说法,基本类型的拷贝都属于深拷贝

(1)浅拷贝(最为常见)

var obj1 = {name: "xyc"};
var obj2 = obj1;
obj1.name = "lxy";
console.log(obj2.name); // "lxy"

仅将栈内存复制,堆内存中的指向依然相同,obj2对象改变后,会影响obj1对象

(2)拷贝对象及对象下一层的属性和方法

var shallowCopy = function(obj) {
  // 只拷贝对象
  if (typeof obj !== "object") return;
  // 根据obj的类型判断是新建一个数组还是对象
  var newObj = obj instanceof Array ? [] : {};
  // 遍历obj,并且判断是obj的属性才拷贝
  for (var key in obj) {
    if (obj.hasOwnProperty(key)) {
      newObj[key] = obj[key];
    }
  }
  return newObj;
}
var obj1 = {
  name: "xyc",
  features: {
    say: "hello",
    eat: "something"
  }
}
var obj2 = shallowCopy(obj1);
obj2.features.eat = "anything";
console.log(obj1.features.eat); // "anything"

console.log(obj1 === obj2); // false
console.log(obj1.features === obj2.features); // true

上面这个例子可以看出上述方式的复制在对象内嵌套对象是不能够实现“类深拷贝”的,下面有一个进阶型的

(3)递归调用对象中嵌套的对象

var deepCopy = function(obj) {
  // 只拷贝对象
  if (typeof obj !== "object") return;
  // 根据obj的类型判断是新建一个数组还是对象
  var newObj = obj instanceof Array ? [] : {};
  // 遍历obj,并且判断是obj的属性才拷贝
  for (var key in obj) {
    if (obj.hasOwnProperty(key)) {
      // 当obj中嵌套对象时,再次调用该方法
      newObj[key] = typeof obj[key] !== "object" ? obj[key] : deepCopy(obj[key]);
    }
  }
  return newObj;
}

(4)一维数组技巧性的拷贝

通过slice()concat()来实现

var arr = ["old", 1, true, null, undefined];

var new_arr = arr.concat();
// var new_arr = arr.slice();

new_arr[0] = "new";

console.log(arr) // ["old", 1, true, null, undefined]
console.log(new_arr) // ["new", 1, true, null, undefined]

一如上面的对象嵌套,多维数组使用上面的方式拷贝不彻底

(5)粗暴的拷贝方式

通过JSON.parse( JSON.stringify() )实现

var arr = ["old", 1, true, ["old1", "old2"], {old: 1}]

var new_arr = JSON.parse( JSON.stringify(arr) );

console.log(new_arr === arr);// false
console.log(new_arr[3] === arr[3]);// false

但是这种方式无法实现函数的拷贝

var arr = [function(){
    console.log(a)
}, {
    b: function(){
        console.log(b)
    }
}]

var new_arr = JSON.parse(JSON.stringify(arr));

console.log(new_arr);// [null, object]

函数通过这个方式会被转换成null

(6)补全深拷贝

var deepCopy = function(obj){
    var str, newobj = obj.constructor === Array ? [] : {};
    if(typeof obj !== "object"){
        return;
    } else if(window.JSON){
        str = JSON.stringify(obj), //系列化对象
        newobj = JSON.parse(str); //还原
    } else {
        for(var i in obj){
            newobj[i] = typeof obj[i] === "object" ? 
            cloneObj(obj[i]) : obj[i]; 
        }
    }
    return newobj;
};

彻底的深拷贝理论上是将对象的整个原型链拷贝(无论原型属性是否为enumerable均应拷贝),遍历的次数越多,性能消耗越大。因此,出于性能的考虑,在拷贝的方式选择上,应该结合具体的业务环境来进行选择

参考:
JavaScript专题之深浅拷贝
深入剖析 JavaScript 的深复制

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/87269.html

相关文章

  • javascript高级程序设计笔记:内存执行环境

    摘要:因此,所有在方法中定义的变量都是放在栈内存中的当我们在程序中创建一个对象时,这个对象将被保存到运行时数据区中,以便反复利用因为对象的创建成本通常较大,这个运行时数据区就是堆内存。 上一篇:《javascript高级程序设计》笔记:继承近几篇博客都会围绕着图中的知识点展开 showImg(https://segmentfault.com/img/bVY0C4?w=1330&h=618);...

    fuyi501 评论0 收藏0
  • 《你不知道的javascript笔记_对象&原型

    摘要:上一篇你不知道的笔记写在前面这是年第一篇博客,回顾去年年初列的学习清单,发现仅有部分完成了。当然,这并不影响年是向上的一年在新的城市稳定连续坚持健身三个月早睡早起游戏时间大大缩减,学会生活。 上一篇:《你不知道的javascript》笔记_this 写在前面 这是2019年第一篇博客,回顾去年年初列的学习清单,发现仅有部分完成了。当然,这并不影响2018年是向上的一年:在新的城市稳定、...

    seasonley 评论0 收藏0
  • JavaScript高级程序设计笔记:变量、作用域和内存问题(四)

    摘要:局部变量只在函数执行过程中存在。此时,局部变量就没有存在的必要了,因此可以释放他们所占的内存以供他们使用。引用计数的含义是跟踪记录每个值被引用的次数。这一做法适合于大多数全局变量和局部变量的属性。 基本类型和引用类型的值 ECMAScript变量可能包含两种不同数据类型的值:基本类型值和引用类型值。基本类型值指的是简单的数据段,而引用类型的值指那些可能有多个值构成的对象。 动态的属性 ...

    Zack 评论0 收藏0
  • JavaScript高级程序设计笔记:变量、作用域、内存问题

    摘要:作用域链中的下一个变量对象来自包含外部环境,而再下一个变量对象则来自下一个包含环境。这样,一直延续到全局执行环境全局执行环境的变量对象始终都是作用域链中的最后一个对象标识符解析沿作用域链一级一级搜索标识符。 一、写在前面 最近研究了创建Android虚拟机、vscode结合weex开发Android APP、Vmware装MAC虚拟机的事,看的内容不够多,接下来加油 二、变量、作用域和...

    U2FsdGVkX1x 评论0 收藏0
  • 读书笔记(02) - 可维护性 - JavaScript高级程序设计

    摘要:解耦优势代码复用,单元测试。常用比较误区可同时判断,可用来判断对象属性是否存在。使用作判断无法进行充分的类型检查。文件中应用常量参考文档高级程序设计作者以乐之名本文原创,有不当的地方欢迎指出。 showImg(https://segmentfault.com/img/bVburXw?w=500&h=400); 编写可维护性代码 可维护的代码遵循原则: 可理解性 (方便他人理解) 直观...

    k00baa 评论0 收藏0

发表评论

0条评论

TerryCai

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<