We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
面试经常遇到的深浅拷贝,事件轮询,函数调用栈,闭包等容易出错的题目,究其原因,都是跟JavaScript基础知识不牢固有关,下层地基没打好,上层就是豆腐渣工程,新人小白,踏实踩土才是关键。
打地基第二篇:本篇我们将对JavaScript数据结构的知识点详解一二。
JavaScript中有三种数据结构: 栈(stack) 、堆(heap)、 队列(queue)。
栈的特点是**"LIFO,即后进先出(Last in, first out)"**。数据存储时只能从顶部逐个存入,取出时也需从顶部逐个取出。《前端进击的巨人(一):执行上下文与执行栈,变量对象》中解释执行栈时,举了一个乒乓球盒子的例子,来演示栈的存取方式,这里再举个栗子搭积木。
举个栗子:乒乓球盒子/搭建积木
JavaScript中Array数组模拟栈:
var arr = [1, 2, 3, 4, 5]; arr.push(6); // 存入数据 arr -> [1, 2, 3, 4, 5, 6] arr.pop(); // 取出数据 arr -> [1, 2, 3, 4, 5]
堆的特点是**"无序"的key-value"键值对"**存储方式。
key-value
举个栗子:书架存书
我们想要在书架上找到想要的书,最直接的方式就是通过查找书名,书名就是我们的key。拿着这把key,就可以轻松检索到对应的书籍。
"堆的存取方式跟顺序没有关系,不局限出入口"。
队列的特点是是**"FIFO,即先进先出(First in, first out)"** 。 数据存取时**"从队尾插入,从队头取出"**。
"与栈的区别:栈的存入取出都在顶部一个出入口,而队列分两个,一个出口,一个入口"。
举个栗子:排队取餐
JavaScript中Array数组模拟队列:
var arr = [1, 2, 3, 4, 5]; // 队尾in arr.push(6); // 存入 arr -> [1, 2, 3, 4, 5, 6] // 队头out arr.shift(); // 取出 arr -> [2, 3, 4, 5, 6]
《前端进击的巨人(一):执行上下文与执行栈,变量对象》详解了JavaScript运行时的函数调用过程,而其中执行栈(函数调用栈)就是用到栈的数据结构。
JavaScript中函数的执行过程,其实就是一个入栈出栈的过程: 当脚本要调用一个函数时,JS解析器把该函数推入栈中(push)并执行 当函数运行结束后,JS解释器将它从堆栈中推出(pop)
JavaScript中函数的执行过程,其实就是一个入栈出栈的过程:
具体执行过程可翻阅上篇文章《前端进击的巨人(一):执行上下文与执行栈,变量对象》,这里不再赘述。
JavaScript中变量类型有两种:
Undefined, Null, Boolean, Number, String, Symbol
Object
基础类型的值保存在栈中,这些类型的值有固定大小,"按值来访问";
引用类型的值保存在堆中,栈中存储的是引用类型的引用地址(地址指针),"按引用访问",引用类型的值没有固定大小,可扩展(一个对象我们可以添加多个属性)。
JavaScript中事件轮询(Event Loop)的执行机制,就是采用队列的存取方式,因事件轮询(Event Loop)也是JS基础中的一个比较难理解的知识点,后续另开一篇章再作详细探究。
将一个变量的值赋值给另一个变量,相当于在栈内存中创建了一个新的内存空间,然后从栈中复制值,存储到这个新空间中。对于基本类型,栈中存储的就是它自身的值,所以新内存空间存储的也是一个值。直接改变新变量的值,不会影响到旧变量的值,因为他们值存储的内存空间不同。
// 基本类型复制变量 var a = 10; var b = a; b = 20; a // 10 b // 20
而对于引用类型来说,同样是复制栈中存储的值。但是栈存储的只是其引用地址,其具体的值存储在堆中。变量复制仅复制栈中存储的值,不会复制堆中存储的值,所以新变量在栈中的值是一个地址指针。
// 引用类型复制变量 var a = { age: 27 }; var b = a; b.age = 29; a.age == b.age; // 29
可见,变量复制赋值,都属于栈存储拷贝,因此深浅拷贝可以这样区分分:
深拷贝会同时开辟新的栈内存,堆内存空间。
// 利用JSON对象方法实现深拷贝 var a = { age: 27 }; var b = JSON.parse(JSON.stringify(a)); b.age = 29; a.age // 27 b.age // 29
var person = { age: 27 }; function foo (person) { person.age = 29; } foo(person); person.age // 29;
函数调用时,会对参数赋值。而参数传递过程其实同样是变量复制的过程,所以它是按值传递。var person = person,因为传递参数是对象时,变量复制仅复制的栈存储(浅拷贝),所以修改对象属性会造成外部变量对象的修改。
var person = person
至此,当我们理清栈、堆数据结构,以及JS中数据类型存取方式。深浅拷贝问题也就通顺了。
JavaScript执行过程中内存分配:
内存管理不善,会出现内存泄露,造成浏览器内存占用过多,页面卡顿等问题。(后续性能优化篇章续讲)
JavaScript中有自动垃圾回收机制,会通过标记清除的算法识别哪些变量对象不再使用,对其进行销毁。开发者也可在代码中手动设置变量值为null(a = null)进行标记清除,让其失去引用,以便下一次垃圾回收时进行有效回收。
a = null
局部环境中,函数执行完成后,函数局部环境声明的变量不再需要时,就会被垃圾回收销毁(理想的情况下,闭包会阻止这一过程)。
全局环境只有页面退出时才会出栈,解除变量引用。所以开发者应尽量避免在全局环境中创建全局变量,如需使用,也要在不需要时手动标记清除,将其内存释放掉。
垃圾回收算法除了**"标记清除",还有一种"引用计数"**,不常用,仅作了解。
参考文档:
作者:以乐之名 本文原创,有不当的地方欢迎指出。转载请指明出处。
The text was updated successfully, but these errors were encountered:
No branches or pull requests
面试经常遇到的深浅拷贝,事件轮询,函数调用栈,闭包等容易出错的题目,究其原因,都是跟JavaScript基础知识不牢固有关,下层地基没打好,上层就是豆腐渣工程,新人小白,踏实踩土才是关键。
打地基第二篇:本篇我们将对JavaScript数据结构的知识点详解一二。
JavaScript中有三种数据结构: 栈(stack) 、堆(heap)、 队列(queue)。
栈(stack)
栈的特点是**"LIFO,即后进先出(Last in, first out)"**。数据存储时只能从顶部逐个存入,取出时也需从顶部逐个取出。《前端进击的巨人(一):执行上下文与执行栈,变量对象》中解释执行栈时,举了一个乒乓球盒子的例子,来演示栈的存取方式,这里再举个栗子搭积木。
举个栗子:乒乓球盒子/搭建积木
JavaScript中Array数组模拟栈:
堆(heap)
堆的特点是**"无序"的
key-value
"键值对"**存储方式。举个栗子:书架存书
我们想要在书架上找到想要的书,最直接的方式就是通过查找书名,书名就是我们的key。拿着这把key,就可以轻松检索到对应的书籍。
"堆的存取方式跟顺序没有关系,不局限出入口"。
队列 (queue)
队列的特点是是**"FIFO,即先进先出(First in, first out)"** 。
数据存取时**"从队尾插入,从队头取出"**。
"与栈的区别:栈的存入取出都在顶部一个出入口,而队列分两个,一个出口,一个入口"。
举个栗子:排队取餐
JavaScript中Array数组模拟队列:
栈、堆、队列在JavaScript中的应用
1. 代码运行方式(栈应用/函数调用栈)
《前端进击的巨人(一):执行上下文与执行栈,变量对象》详解了JavaScript运行时的函数调用过程,而其中执行栈(函数调用栈)就是用到栈的数据结构。
具体执行过程可翻阅上篇文章《前端进击的巨人(一):执行上下文与执行栈,变量对象》,这里不再赘述。
2. 内存存储(栈、堆)
JavaScript中变量类型有两种:
Undefined, Null, Boolean, Number, String, Symbol
)一共6种Object
)基础类型的值保存在栈中,这些类型的值有固定大小,"按值来访问";
引用类型的值保存在堆中,栈中存储的是引用类型的引用地址(地址指针),"按引用访问",引用类型的值没有固定大小,可扩展(一个对象我们可以添加多个属性)。
3. 事件轮询(队列)
JavaScript中事件轮询(Event Loop)的执行机制,就是采用队列的存取方式,因事件轮询(Event Loop)也是JS基础中的一个比较难理解的知识点,后续另开一篇章再作详细探究。
深浅拷贝
将一个变量的值赋值给另一个变量,相当于在栈内存中创建了一个新的内存空间,然后从栈中复制值,存储到这个新空间中。对于基本类型,栈中存储的就是它自身的值,所以新内存空间存储的也是一个值。直接改变新变量的值,不会影响到旧变量的值,因为他们值存储的内存空间不同。
而对于引用类型来说,同样是复制栈中存储的值。但是栈存储的只是其引用地址,其具体的值存储在堆中。变量复制仅复制栈中存储的值,不会复制堆中存储的值,所以新变量在栈中的值是一个地址指针。
可见,变量复制赋值,都属于栈存储拷贝,因此深浅拷贝可以这样区分分:
深拷贝会同时开辟新的栈内存,堆内存空间。
函数传参数是按值传递?按引用传递?
函数调用时,会对参数赋值。而参数传递过程其实同样是变量复制的过程,所以它是按值传递。
var person = person
,因为传递参数是对象时,变量复制仅复制的栈存储(浅拷贝),所以修改对象属性会造成外部变量对象的修改。至此,当我们理清栈、堆数据结构,以及JS中数据类型存取方式。深浅拷贝问题也就通顺了。
内存空间管理
JavaScript执行过程中内存分配:
内存管理不善,会出现内存泄露,造成浏览器内存占用过多,页面卡顿等问题。(后续性能优化篇章续讲)
垃圾回收机制
JavaScript中有自动垃圾回收机制,会通过标记清除的算法识别哪些变量对象不再使用,对其进行销毁。开发者也可在代码中手动设置变量值为null(
a = null
)进行标记清除,让其失去引用,以便下一次垃圾回收时进行有效回收。局部环境中,函数执行完成后,函数局部环境声明的变量不再需要时,就会被垃圾回收销毁(理想的情况下,闭包会阻止这一过程)。
全局环境只有页面退出时才会出栈,解除变量引用。所以开发者应尽量避免在全局环境中创建全局变量,如需使用,也要在不需要时手动标记清除,将其内存释放掉。
垃圾回收算法除了**"标记清除",还有一种"引用计数"**,不常用,仅作了解。
参考文档:
The text was updated successfully, but these errors were encountered: