v8引擎工作原理 数据存储

前言

前面我已经讲完了js基本的一些运行原理,以及js的一些设计缺陷,这经常导致我们写js代码的时候出现我们直觉难以预料到的错误.
但是对于前端开发者来说,仅仅认识这些是不够的,如果单纯只是想写一些业务代码,或许够用了,但是对于一个追求高性能环境,甚至想成为行业专家的人来说,这是不够的
必须要搞清楚js引擎内存机制甚至js引擎工作的原理,当然这需要很多基本功,如果大家真的想成为行业专家,建议先从底层数据结构,编译原理甚至计算机系统组成结构这些东西看起,当然,这必然是一个漫长的过程,需要毅力和恒心
好了,现在我们正式进入主题

让人疑惑的代码

function foo() {
	var a = 1
	var b = a
	a = 2
	console.log(a)
	console.log(b)
}
foo()

function bar() {
	var a = { name: '凯隐' }
	var b = a
	a.name = '拉亚斯特'
	console.log(a)
	console.log(b)
}
bar()

若执行以上代码,你知道他们输出的结果是什么吗?如果是有一定计算机基础知识的人,肯定能回答的出来,对于一些初学者没有基础的人来说,这就是一段让人看着疑惑的代码了
我们来分析一下:
foo函数打印出来a是2,b是1,这没什么难以理解的
接着执行第二段代码,你就会发现,仅仅改变了a中name的值,但是最终输出来的a和b都是一样的{name:'拉亚斯特},
这就和我们预期的不一样了,因为我们是想仅仅改变a内容的值,但是b内容的值也跟着改变了
为什么呢?想要搞清楚这个问题,就要从js的内存机制说起了

js是什么类型的语言

每种编程语言都有内建的数据类型,但他们的数据类型常有不同之处,使用方式也不一样,比如java语言在定义变量之前,需要确定变量类型

public class {
	int a;
	a = 1
}

这些代码在使用之前要先定义其类型,我们称这种类型的语言为静态语言

相反地,我们在运行过程中需要检查数据类型的语言,我们成为动态语言,因为在声明变量之前不需要确认其类型,js在运行过程中会自动确定它的类型

而且还有一种说法是,在编译期间,会数据类型会做隐式转换的语言我们称之为弱类型语言,很显然,js也是弱类型语言.

js的数据类型

现在我们知道js是一门动态的,弱类型语言,这意味着

  • 弱类型,意味着你不需要告诉js引擎这个变量那个变量的数据类型,js引擎会在运行代码时自己计算出来
  • 动态,意味着你可以使用同一个变量保存不同类型的数据

那么我们再来看看js的数据类型

var bar
bar = 12
bar = '123'
bar = true
bar = null
bar = {name:'123'}

从上面来看,我们声明一个bar变量,可以用来赋值各种数据类型,在js中,想要查看一个变量到底是什么类型,
可以使用typeof 运算符

var bar
console.log(typeof bar) //undefined
bar = 12
console.log(typeof bar) //number
bar = '123'
console.log(typeof bar) //string
bar = true
console.log(typeof bar) //Boolean
bar = null
console.log(typeof bar) //object
bar = {name:'123'}
console.log(typeof bar) //object

null为什么是object,这是js诞生以来一个历史悠久的bug,简单的来说,object的数据类型在保存时,它的机器码是以前3位000来判断的,而null全为0,所以错误的将null判定位object

接下来我们看看js的8中数据类型

boolean:只有truefalse两个值
null:只有一个null
undefined:使用var声明的或者往对象上追加一个没有赋值的变量,有个默认值undefined
Number:根据ECMA标准,js只有一种数字类型,基于IEEE754标准的双精度64位二进制格式的值
BigInt:新的数字类型,可以表示用任意精度表示证书,使用bigInt,即时数字超过number的安全范围,也可以安全的存储
String:用于表示文本数据,不同于c语言,js的String字符串不可更改
Symbol:es6之后新加入的类型,表示唯一的不可修改的,常被用来做对象的key
Object:一组属性的集合

object类型比较特殊,object是由key-value组成的,value可以是任何类型,包括函数,object中的函数又称方法
我们把前面7中类型叫做引用类型,之所以把他们区分位两种不同的类型,是因为他们在内存中存放的位置不一样.到底怎么个不一样法呢?接下来我们就讲讲

内存空间

js可以分为3个空间
代码空间,栈空间,堆空间
代码空间说白了就是存储我们可执行代码的地方,
栈空间和堆空间呢?

栈空间和堆空间

栈就是我们反复提及的调用栈了,是用来存储执行上下文的.为了搞清楚栈空间是如何存储数据的,我们还是先看下面这段代码

function foo(){
  var a = '凯隐'
  var b = a
  var c = {name:"拉亚斯特"}
}
foo()

前面我们已经讲过了当执行一段代码时,需要编译,并创建执行上下文,然后再按照顺序执行可执行代码
从上面代码流程来看

  • 执行到foo()时.foo函数入栈
  • 然后变量环境中存放了a,b,c

需要注意的是,基本类型的a,b存放再变量环境,这是没错的
c中存放的就不是完整的对象了
而是存放了一个引用地址,这个地址指向堆空间,
而响应的,堆空间中保存了这个对象

所以说,基本类型是保存在栈空间的,引用类型对象是保存在堆空间的
嗯,这就完事了?

你有没有想过为什么要分堆空间和栈空间呢?
直接保存在栈空间不就完事了吗?

答案是不可以的,因为js引擎需要栈来维护程序执行上下文之间的关系,如果栈空间保存的数据过大,那么程序的执行效率是会大大降低的,会影响执行上下文的切换.

所以通常情况下,栈只用来保存一些原始类型的小数据,不会设置的过大,
而堆空间很大,能存放很大的数据,不过缺点就是分配内存和回收内存的时候会占用一定的空间.

所以说
我们之前的代码

var a = { name: '凯隐' }
	var b = a
	a.name = '拉亚斯特'
	console.log(a)
	console.log(b)

实际b = a 是将a中保存的引用地址给了b,这是a,b指向的是同一个对象.
归根揭底他们两是一回事
所以修改a,b也跟着变

那么我们如果想创建一个新的对象跟原来之前的对象一样呢?

经典,太经典了,这不就是深拷贝和浅拷贝的问题吗
首先分析,我们需要let newObj = {}
开辟一个新的对象把,然后呢,
就是依葫芦画瓢,将oldObj中的key,value塞进去呗
我们可以递归实现,如果key-value的value是一个基本类型,我们就可以直接赋值使用=
如果value是个引用类型,那么我们需要重复之前的操作

代码如下:

function deepclone(src, map = new WeakMap()) {
	if (src == null) return src
	if (src instanceof Date) return new Date(src)
	if (src instanceof RegExp) return new RegExp(src)
	if (typeof src !== 'object') return src
	if (map.get(src)) return map.get(src)
	let target = new src.constructor()
	map.set(src, target)
	for (const k in src) {
		if (Object.hasOwnProperty.call(src, k)) {
			target[k] = deepclone(src[k], map)
		}
	}
	return target
}

weakMap的作用是解决循环引用,好啦,今天写到这里了

你可能感兴趣的:(前端,javascript,开发语言)