向内存中连续存入数据_javascript中的数据类型及堆栈内存

daa6142c142a18d4e170c869fd4dc9c1.png

· js中的数据类型

分为两类:

① 基本数据类型(值类型)

number string boolean null undefined

② 引用数据类型 : object function

object比较特殊,它包含其他类型的对象:
普通对象({}) 、 数组对象([])、正则对象(/^$/)、日期对象(new Date())、数学函数对象(Math)...... 其实这也东西全是实例
function(其实它属于object),但是它也有自己独有的地方

③ ES6 中新增的 Symbol

· NaN

其中比较特殊的NaN 是什么类型呢? 我们可以用typeof来检测一下

console.log(typeof NaN) // number 

结果输出的是number,所以NaN是number类型,不过准确的说是字符串number

fc90d3559443e1d7c98bcb059c51cc0d.png

NaN:表示无效的数字,但是它不能用来检测是否为有效数字,因为它跟任何值都不相等。

fd6d9621c572f5ced1d11e8726b682b2.png

想检测是否为有效数字可以用isNaN

9fa18c25183502c50663998e10fb92ce.png

isNaN()是用Number()来做隐式转换的,比如字符串‘12px’转换为number类型是NaN,所以isNaN返回的就是true

b45c5dda9a8a681b54f4cc839a98fd8a.png

什么时候回出现NaN?

当把其他数据类型转换为number的时候,如果不能转换结果就是NaN

· 基本object

一、基本对象

let obj= {
  0:100,
  true: '张三',
}

它的key可以是基本类型值,如果是object或者是function可以吗?

let a={x:100}
let obj= {
  0:100,
  true: '张三',
}
obj[a] = 1000;

7ff42aa580e6e4682bbc7ce3525e1765.png

打印结果看,它会将对象类型的key转换为string类型,key为function也同样如此

let a={x:100}
let b=function(){let aa =100}
let obj= {
  0:100,
  true: '张三',
}
obj[a] = 1000;
obj[b] = 2000;
console.log(obj)

2806de715bd2b039b0136721e95fcfa0.png

所以:对象的属性名一定不能是引用类型值,因为会默认把引用类型值转换为字符串进行处理,也就是用tostring()处理。

({x:100}).toString() // '[object object]'

至于属性名是基本类型值的转没转字符串,这个有的人说转了,有的人说没转,不过都不影响。

二、数组 :它是特殊的对象,特殊在它的key是自增的结构

三、查看一下object和array常用的方法:有一个技巧,在控制台中输入:Object.prototype 和dir(Object) Array.prototype 和dir(Array) ,(dir是congsole.dir()的缩写,查看一个对象的详细信息),要想查看相对应的方法的定义,推荐这个网站:https://developer.mozilla.org/zh-CN/,直接可以在搜索框中搜索

小知识笔记:

数组扁平化 使用flat()
var arr = [21414,754,[2885,[577,786,[5747,[474,747,11221,89898]]]]]
var a= arr.flat(Infinity);
console.log(a); //  [21414, 754, 2885, 577, 786, 5747, 474, 747, 11221, 89898]

· 堆(heap)栈(stack)内存

先看一道面试题
//1
let a=12;
let b=a;
b=13;
console.log(a) //12
//2
let a={
  n:12
};
let b=a;
b['n'] =13;
console.log(a.n);//13
//3
let a={
  n:12
};
let b=a;
b={
 n:13
};
console.log(a.n) //12

先记住几个名词:(浏览器想去执行代码怎么做?)

一、编译器阶段(把代码解析成浏览器看的懂的结构)

① 词法解析
② AST抽象语法树
③ 构建出浏览器能执行的代码(编译和渲染)
这些都是 编译器(他是引擎的好朋友之一) 去做

简单流程:

什么叫词法解析呢?
比如,一行代码 var a = 12 ;
词法解析会把这行代码的每一个字符解析出来,看看哪个有用哪个没用,把有用的组合起来生成AST抽象语法树,从中指明哪一个是顶级节点,相对应的代码对应什么意思,应该生成什么东西,然后,再把它变成一个结构,就是浏览器底层本身能够看得懂的结构,然后放在栈里面执行或者放在堆里面存储,形成作用域等操作。
如果能学好编译器,可以做webpack中的babel:将es6转换为es5

接下来再给js引擎去做(V8 webkit内核)

二、引擎阶段(我们主要探讨的阶段)

变量提升
作用域 / 闭包
变量对象
堆栈内存
GO / VO / AO / EC / ECStack
...

js引擎想要执行代码,一定会创建一个执行栈(ECStack),也就是栈内存,或者叫做ESCtack(执行上下文环境栈)。

栈内存的作用:提供执行代码的环境,用来执行代码

ECStack:Execution(执行) Context(上下文) Stack(栈): 执行环境栈
EC:Execution Context执行环境(执行上下文)
· VO:Varibale Object 变量对象
· AO:Activation Object 活动对象(函数的叫做AO,理解为VO的一个分支)
Scope:作用域,创建的函数的时候就赋予的
Scope Chain : 作用域链

现在我们简单说一下在执行栈中执行代码的理论流程

以上面的面试代码为例,在栈中执行代码,一开始会先在外部创建出一个东西,也就是EC(执行上下文),可以这样理解,在执行栈中又开辟出了专门用来执行这一块代码的东西。
比如,上面中,在全局中定义:var a=12; 那么执行栈中就会创建一个全局下的EC(执行上下文)
名词术语扫盲: EC:执行上下文,某个域下的代码执行都有自己的执行上下文。 全局的叫做:EC(G),G是global 函数执行的叫做:EC(...某某),这属于自己私有的执行上下文
形成执行上下文以后咋办呢?它会把执行的上下文压进栈里面,也就是先形成一个环境然后去栈里面执行,也就是进栈执行,当执行完成以后,有的没用了就会执行出栈,并将执行上下文销毁,但是如果有用的话,就会被压入执行栈的最底层(闭包),等待下一次被调取执行。
为什么要创建执行上下文呢?是因为在我们平时写代码的时候既有全局变量也有函数体,全局变量在全局的执行上下文,而函数体执行时也有相对应的执行上下文,这就相当于加了一个保护层,防止代码之间相互冲突。
这里面注意,执行上下文严格来说并不是作用域。 GO:全局对象(global object),在浏览器端(node不是)会把全局对象赋值给window,也就是window指向了GO。
GO中除了内置属性(setTimeout),还有自定义的。

a8dff26d024739b30ec082c9e93bee82.png
也就是在执行上下文中一定会有一个GO,里面存了全局中定义的东西,比如上面全局定义的 var a=12, 因为它是基本类型,所以会直接存起来,因为 执行栈除了执行代码以外,还能存储基本类型值
所以问题来了,var a =12 是分几步完成的呢?
变量赋值的三步操作:① 创建变量(声明,declare) ② 创建值 :基本数据值直接在栈中创建和存储即可 ③ 让变量和值关联起来(赋值defined),没有赋值就是undefined
然后执行 b = a ;
继续在GO中创建变量b,右侧a是个变量,所以第二步中的创建值就不用执行了,然后进行第三步,因为a是12,所以b就会跟12关联起来

a9f1974db861c1f347478ea5a6f7d4a1.png
继续执行第四行,b=13
变量b有了,所以不用声明了,然后创建值,将13放到栈中,然后关联,b又指向了13,因为 一个变量只能指向一个值(一夫一妻制),所以以前那个指针就没了。

f8ffbb14070a7d4e248dce9d1835d174.png
我们继续做第二道题, let a={ n :12 };
还是那三步,先声明变量a,然后创建值,可是现在这个值是一个对象,属于引用类型,由于引用值是复杂的结构,需要特殊处理,所以它需要开辟一个存储对象中键值对(存储函数中的代码)的内存空间,而它就是 堆内存,它的作用就是,存储引用类型的值
所有的堆内存都有一个可被后续查找的16进制地址,这个地址就代表了相应的堆;然后进行第三步,关联值,而这个值就是这个堆的16进制地址(引用地址),将引用地址赋值给变量

7c57efe996cd9510b72fefde7bd08640.png
继续执行 let b = a; 此时 b 也指向了这个地址

0b8e1f03226b097953cf6a6855e7f046.png
执行 b['n'] = 13 ,因为a、b指向的是同一个引用地址,所以b改变了n的值,所以a同样变化。
再看接下来的第三道题
let a={ n:12 }; let b=a; b={ n:13 }; console.log(a.n) //12
前两行跟第二道题的思路一样,当执行 b = { n :13}的时候,又开辟了一个堆内存,指向AAAFFF000的指针就销毁了,当在改变b中指向的堆中的值的时候,不会影响到a指向的堆中的值。所以会输出12

32f9200ce2cb3aa355be4a77937f27fc.png
问题来了,AAAFFF000和AAAFFF111这两个堆我可以销毁吗?
答案:不能,因为全局变量a和b占用着他们呢,被占用就不能销毁。如果想销毁的话,不占用就可以了,就比如,我想销毁 AAAFFF000这个堆,我可以将 a =0; 这样AAAFFF000不被占用了,那么就会被销毁了。但是 a= 0,这样也会占用栈内存,我们可以将a =null,
null其实就是空对象指针,他不会占用任何内存,指向一个空指针

· 练习题:

第一题:

let a ={
  n:10
};
let b=a;
b.m = b ={
  n:20
}
console.log(a);//{m:{n:20}, n:10}
console.log(b); //{n:20}

这道题的难点就是:连等于

举个例子:
var a=b=[12,23];
步骤:
创建一个数组(地址)
先,x=地址
后,y=地址

记住:let声明的变量一定是全局变量,只不过不是全局属性

js运算符的优先级(当有多个运算符的时候):同等级下 是从左到右执行,比如连等于:var a=b=0

第二题:

let x =[12,23];
function fn(y){
 y[0] = 100;
 y = [100];
 y[1] = 200;
 console.log(y)
}
fn(x)
console.log(x)
结果
{100,200}
{100,23}

解析这道题之前,先说一下新东西

注意有新知识点了!!

运行这段代码一开始,先创建执行栈,然后创建全局执行上下文(EC(G)),压入到执行栈中,全局执行上下文中一定有一个GO(全局对象,window指向它),除了GO以外,还有一个VO(变量对象)

在全局执行上下文中,会创建很多变量,这些变量就存到了VO中。

VO的作用:存储当前执行上下文中的变量

注意,函数也是变量,跟let 、var本质是一样的,区别就是存储的值是一个函数类型的值,属于引用类型,所以得开辟堆。

我们先讲解一下这道题的流程

第一行,先声明变量x,然后开辟一个堆,假设名叫AAAFFF000,存储[12,23],然后x指向地址,也就是AAAFFF000,
第二行,声明变量fn,(函数也是变量),然后开辟一个堆名叫AAAFFF111,函数中的堆会存两种东西:
第一部分,存储代码字符串(函数体):“ y[0] = 100; y = [100]; y[1] = 200; console.log(y)”;
第二部分,它会像一个对象一样,存储键值对,其中它会存① length :1,1:代表形参个数; ② name:"fn" fn:函数名; ③ prototype:原型 ④ _ proto_ 等
然后,变量fn指向AAAFFF111
执行 fn(x)函数, x是实参,已经存在, 等价于 fn(AAAFFF000)
因为 每一个函数执行都会形成一个全新的执行上下文,因为它要包起来,让里面内部的代码执行。
当前是fn(x)函数执行,所以会形成一个全新的执行上下文(EC(FN)),但是EC(fn)必须放在执行栈中才能执行,可是现在执行栈中,全局执行上下文正在执行(EC(G)),这个时候怎么办呢?
答:因为js是单线程,一次只能做一件事,所以, 它会将未执行完的EC(G)压缩到执行栈的底部,然后把新的执行上下文(EC(FN))压到执行栈中执行
新形成的这个EC(FN)中也应该有个存放变量的VO,不过它不叫VO,叫AO(活动对象),我们可以这样理解,全局中的变量对象叫VO,函数中的变量对象叫AO。
函数的执行上下文中,
首先, 初始化arguments(实参集合),他是一个类数组集合:函数fn中传入了一个实参x,因此,arguments={0:AAAFFF000};注意:箭头函数中没有arguments。
第二步: 创建形参并赋值:y=AAAFFF000; 在非严格模式下,形参变量与arguments会产生映射机制,改变其中一个值,另一个也会改变。
第三步: 代码执行
执行完fn函数以后,出栈,位于底部的全局执行上下文(就像弹簧一样,弹上来)继续执行。
如果不出栈(比如里面的值被其他外部变量引用,比如闭包),那么它会被压下去,全局执行上下文文依然弹上来执行。

第三题

var x=10;
~function(x){ //自执行函数,~/!/()() 都是自执行函数
  console.log(x);
  x=x || 20 &&30 || 40;
  console.log(x)
}()
console.log(x);
结果
undefined
30
10

这道题的难点: x =x || 20 && 30 || 40

先说一下知识点
a || b : a为真,返回a,否则返回b
a&&b : a为真,返回b,否则返回a
&&的优先级高于 ||

所以,x || 20 && 30 || 40 这行代码先执行20&&30 因为20是真所以返回30 ,然后就相当于x || 30 || 40 ,最后返回30,所以 x=30

现在我们说一下这道题的流程

①开辟执行栈 ② 形成全局执行上下文压入执行栈中 ③ x=10存储 ④ 因为是自执行函数,所以会做两件事:创建 + 执行
首先,创建:开辟堆内存,保存代码字符串和对象键值对(上面已述)
然后,执行: 先形成一个私有的执行上下文(EC(自执行函数)),压入执行栈中,全局执行上下文被压入栈底部。
在函数的执行上下文中有一个AO,在AO中,先初始化 arguments,因为没有参数所以,arguments={}
然后,形参变量赋值,因为没有参数所以,x=undefined
下一步,代码执行 : console.log(x), =>x=undeifend ; x =x || 20 && 30 || 40 结果是x=30 ;下一行的console.log(x) 就是 打印30
函数执行完以后,出栈,销毁
最后一行,console.log(x) ,打印的是全局的变量x,结果是10
所以结果是:undefined 30 10

第四题

let x=[1,2],
    y=[3,4];
~function(x){
  x.push('A');
  x = x.slice(0);
  x.push('B');
  x = y;
  x.push('C');
  console.log(x,y); 
}(x);
console.log(x,y);

结果
[3,4,C] [3,4,C] 
[1,2,A] [3,4,C]

注意:

let x=10,y=20 ===> 相当于 let x;let y;
let x=y=20 ====> 相当于 let x;y; 这里的y没有let

此题难点:x.slice(0) ,slice属于浅克隆,返回新数组,所以要新创建一个堆

55add04e966f05bf01f51ca7f54c89c3.png
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值