JS堆栈,基本数据类型,引用数据类型,深浅拷贝的整理

按照存储方式区分

基本类型(原始值): 每个位置有一个具体地址存储值

  1. boolean
  2. number
  3. string
  4. undefined
  5. null
  6. symbol(ES6新增)

引用类型:指针指向一个位置,节省空间,存放指针

  1. 对象object
  2. 数组array
  3. 函数function

传值方式不同===>深浅拷贝问题

基本类型按值传递赋值之后单独修改变量互不影响

对象按引用传值,两个变量指向同一地址,地址中存放同一个对象,任何一个修改属性会相互影响。(浅拷贝可以解决一层对象的情况)使用深拷贝处理多层对象的情况。


堆和栈的区别

JavaScript中并没有严格意义上区分栈内存与堆内存。我们可以粗浅的理解为JavaScript的所有数据都保存在堆内存中。JavaScript的执行上下文,需要基于堆栈数据结构的思路进行处理。执行上下文在逻辑上实现了堆栈。

栈(stack)

  • 自动分配内存空间,会自动释放。存放基本数据类型的值,引用类型的地址,按值访问,先进后出;
  • 动态分配的空间一般由程序员分配释放, 若程序员不释放,程序结束时由OS回收;
  • 对象复制的时候:复制栈中的地址而不是堆中的对象,两个地址指向同一个对象;

栈空间存储原理:乒乓球放取

 

堆(heap)

  • 动态分配的内存,大小不定,不会自动释放。队列优先,先进先出;
  • 存放引用数据类型的对象值、函数的参数值,局部变量的值等;
  • 引用数据类型的值是保存在堆内存中的对象,JavaScript不允许直接访问堆内存中的位置,
  • 不能直接操作对象的堆内存空间。在操作对象时,实际上是在操作对象的引用而不是实际的对象。
  • 引用类型的值都是按引用访问的。这里的引用,我们可以粗浅地理解为保存在变量对象(栈内存)中的一个地址,该地址与堆内存的实际值相关联;下图b,c所示

堆存取数据的原理:书整齐的存放在书架上,但是我们只要知道书的名字(地址),我们就可以很方便的取出我们想要的书。好比在JSON格式的数据中,我们存储的key-value是可以无序的,因为顺序的不同并不影响我们的使用,我们只需要关心书的名字key。

 

var b = { m: 20 }; // 变量b(对象的引用)存在于变量对象中,{m: 20} 作为对象存在于堆内存中 
var c = [1, 2, 3]; // 变量c存在于变量对象中,[1, 2, 3] 作为对象存在于堆内存中

因此当我们要访问堆内存中的引用数据类型时,实际上我们首先是从变量对象(栈内存)中获取了该对象的地址引用(或者地址指针),然后再根据地址值从堆内存中取得我们需要的数据。

基本类型和引用类型传值方式不同

  • 基本类型按值传递赋值之后单独修改变量互不影响
var a = 20; 
var b = a; //给b开辟了单独的空间 
b = 30; // 这时a的值是20不会互相影响
  • 对象按引用传值,指向同一地址,地址中存放同一个对象,任何一个修改属性会相互影响。(浅拷贝)使用深拷贝避免这种行为。引出深浅拷贝的概念
var m = { a: 10, b: 20 } 
var n = m;//复制引用类型的操作 
n.a = 15; // 这时m.a的值为15,互相影响

引用类型的复制同样也会为新的变量自动分配一个栈内存保存新的值,只保存引用类型的一个地址指针。当地址指针相同时,尽管他们相互独立,但是在变量对象中访问到的具体对象实际上是同一个。因此当我改变n时,m也发生了变化。这就是引用类型的特性。


JS垃圾回收机制

JavaScript的内存生命周期:1.分配所需要的内存 2. 使用分配的内存(读、写)3.不需要时将内存释放,归还

var a=10;//分配内存,在定义变量时就完成了内存分配 console.log(a);//使用内存 a=null;//释放内存

自动垃圾收集机制,找出那些不再继续使用的值,然后释放其占用的内存,垃圾收集器会每隔固定的时间段就执行一次释放操作。

标记清除算法,找到哪些对象是不再继续使用的,因此a = null其实仅仅只是做了一个释放引用的操作,让 a 原本对应的值失去引用,脱离执行环境,这个值会在下一次垃圾收集器执行操作时被找到并释放。而在适当的时候解除引用,是为页面获得更好性能的一个重要方式。

  • 在局部作用域中,当函数执行完毕,局部变量会被垃圾收集回收。
  • 全局变量什么时候需要自动释放内存空间则很难判断,因此在我们的开发中,需要尽量避免使用全局变量,以确保性能问题。

参考https://www.nowcoder.com/discuss/20969


深浅拷贝

浅拷贝:任何一方修改都互相影响,只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存

var m = { a: 10, b: 20 } 
var n = m;//复制引用类型的操作 
n.a = 15; // 这时m.a的值为15,互相影响

深拷贝:完全拷贝一个新的对象,初始化每个属性名和值相同,互不影响;创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象

let a={ name:1}; let b={name:a[name]};取得属性名相同,值也相同,但是a[name]是值传递 b.name=2; a.name//1修改属性互不影响

浅拷贝的实现方式3种(单层深拷贝,多层对象浅拷贝)

属性值还是对象时,拷贝的是对象的属性的引用,而不是对象本身,导致修改属性互相影响

  • 简单的遍历每项复制(单层对象可以实现深拷贝,在深一层还是浅拷贝)

function simpleCopy(obj){ var cloneObj={}; for(let i in obj){属性名在对象中 cloneObj[i]=obj[i];拷贝对象也取i这个属性名,属性值相等,如果是对象的话是浅拷贝 } return cloneObj; }

属性值是数组函数的,拷贝之后两者互不影响互不影响

对于对象,两个指向同一地址,实际为同一对象,修改互相影响

  • Object.assign(ES6)可以把任意多个的源对象自身的可枚举属性拷贝给目标对象,然后返回目标对象

Object.assign(targetObj,...sources)//target:目标对象,sources:任意多个源对象。返回值:目标对象会被返回

let a={name:1} let b=Object.assign({},a); b.name=2; a.name//1 属性值是基本类型,不会导致互相影响

  • 通过展开运算符(…)来解决

let a={name:1}; let b=[...a]; b.name=2; a.name//1不会互相影响

通常浅拷贝就能解决大部分问题了,但是当我们遇到如下情况就需要使用到深拷贝

浅拷贝只解决了第一层的问题,如果接下去的值中还有对象的话,两者享有相同的引用。要解决这个问题,我们需要引入深拷贝

 let a={ name:1; age:{ first:10; } } let b=[...a]; a.age和b.age共享同一个对象,引用传值 b.age.first=11; a.age.first//11互相影响了,只能通过深拷贝解决问题


深拷贝实现方式5种(对象属性值是对象)

  • 通过 JSON.parse(JSON.stringify(object)) 来解决,用JSON.stringify把对象转成字符串,再用JSON.parse把字符串转成新的对象。

该方法的局限性:

  • 抛弃对象的constructor:不管这个对象原来的构造函数是什么,在深拷贝之后constructor变成Object
  • 可以转成JSON格式的对象(Number, String, Boolean, Array, 扁平对象)才可使用这个方法,像(RegExp对象、function、undefined和symbol)无法转成JSON是无法通过这种方式深拷贝

let a={ age:undefined, sex:Symbol('male), jobs:function(){},//前三个都无法转成json字符串,不能使用此法 name:'li'//传递给b的只有可以转成JSON对象的name属性 } let b = JSON.parse(JSON.stringify(a))//{name:'li'}

在通常情况下,复杂数据都是可以序列化,

此方法可以解决大部分问题,该函数是内置函数中处理深拷贝性能最快

  • 递归拷贝(对于多层对象)(加入对循环引用的排除)循环引用对象,报错

遍历原来的对象属性,判断属性值的类型, 对于基本和函数类型直接相等,对于object中的数组和对象,递归 function deepClone(targetObj,sourceObj){ var objClone=targetObj||{}; for(let i in sourceObj){ let prop=sourceObj[i];//获取原对象的每个属性值 if(prop===objClone){ continue;//不复制循环引用自身的这个属性,去遍历下一属性值i } if(typeof prop==='object'){ objClone[i]=(prop instanceof Array)?[]:{}; deepClone(objClone[i],prop);//prop如果是对象, //作为新的sorceObj进行新一轮遍历,如果是数组,那么每一项都是基本类型值 }else{ objClon[i]=prop;//基本类型的赋值 } return objClone; } }

arguments.callee的用法

argument为函数内部对象,包含传入函数的所有参数,arguments.callee代表函数名,多用于递归调用,防止函数执行与函数名紧紧耦合的现象,对于没有函数名的匿名函数也非常起作用。

递归时候最好使用arguments.callee function func(n){ if(n<=1){return 1;} else{ return n*arguments.callee(n-1); //f(n)=n*f(n-1),调用过程中不直接使用函数名 } }

  • Object.create()

使用现有的对象来创建一个新对象,现有对象为新创建对象的__proto__,新对象继承先有对象的属性,并可以覆盖继承的属性

返回值:一个新对象,带着指定的原型对象和属性

const oldObj={ name:'lisi', printName: function(){ console.log(this.name); } } var newObj = Object.create(oldObj)//可以达到深拷贝的效果 newObj.age=20;//可以新添加自己的属性 newObj.name='zhangsan';//覆盖继承的属性 newObj.printName();//继承的方法可以使用,输出重写后的name属性 zhangsan

  1. jquery
  2. lodash

很热门的函数库lodash,也有提供_.cloneDeep用来做深拷贝


 typeof返回值是表示类型的字符串

只可以区分值类型中的具体类型,对于引用类型是数组还是对象无法区分,但可以区分函数

无法区分对象类型是,可以使用Object.prototype.toString.call(obj)得到对象的字符串表示

为[object Array][object Object]...

  • 可以使用typeof判断变量是否存在,if(typeof a!=='undefined')不能使用if(a)如果a未声明,会报错,typeof是安全地操作,就算a没有声明也没事

类型

原始值/引用值

typeof结果

symbol es6新增符号

原始

symbol

boolean

原始

boolean

number

原始

number= typeof NaN

string

原始

string

undefined

原始

undefined

null

原始

object

对象

引用

object无法区分

数组

引用

object无法区分

函数

引用

function=typeof console.log

instanceof请看原型原型链那一块

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值