本文记录了我在学习前端上的笔记,方便以后的复习和巩固。

4.1基本类型和引用类型的值

ECMAScript变量可能包含两种不同数据类型的值:基本类型值和引用类型值。基本类型指的是简单的数据段,而引用类型值指那些可能由多个值构成的对象。

数据类型:

  • 基本类型值:Undefined、Null、Boolean、Number、String;

  • 引用类型值,也就是对象类型:Object、Array、Function、Date等;

声明变量时不同的内存分配

  • 基本类型值:存储在栈(stack)中的简单数据段,它们的值直接存储在变量访问的位置。这是因为这些基本类型占据的空间是固定的,所以可以将它们存储在较小的内存区域 - 栈中。这样存储更便于迅速查寻变量的值。

  • 引用值:存储在堆(heap)中的对象,也就是说,存储在变量处的值是一个指针(point),指向存储对象的内存地址。这是因为:引用值的大小会改变,所以不能把它放在中,否则会降低变量查寻的速度。相反,放在变量的栈空间中的值是该对象存储在中的地址。地址的大小是固定的,所以把它存储在中对变量性能无任何负面影响。

不同的内存分配机制也带来了不同的访问机制

javascript中是不允许直接访问保存在堆内存中的对象的,也就是说不能直接操作对象的内存空间。所以在访问一个对象时,首先得到的是这个对象在堆内存中的地址,然后再按照这个地址去获得这个对象中的值,这就是传说中的按引用访问。而原始类型的值则是可以直接访问到的。

注意:当复制保存着对象的某个变量时,操作的事对象的引用。但在为对象添加属性时,操作的是实际的对象

复制变量的不同

  • 基础类型值:在将一个保存着基础类型值的变量复制给另一个变量时,会将原始值的副本赋值给新变量,此后这两个变量是完全独立的,他们只是拥有相同的value而已。

function addTen(num) {num += 10;return num;
}
var count = 20;
var result = addTen(count);
console.log(count); //20 没有变化
console.log(result); //30
  • 引用值:在将一个保存着对象内存地址的变量复制给另一个变量时,会把这个内存地址赋值给新变量,也就是说这两个变量都指向了堆内存中的同一个对象,他们中任何一个作出的改变都会反映在另一个身上。(这里要理解的一点就是,复制对象时并不会在堆内存中新生成一个一模一样的对象,只是多了一个保存指向这个对象指针的变量罢了)

function setName(obj) {obj.name = "Nicholas";
}
var person = new Object();
setName(person);
console.log(person.name); //"Nicholas"

参数传递的不同

首先我们应该明确一点:ECMAScript中所有函数的参数都是按值来传递的。但是为什么涉及到基础类型与引用类型的值时仍然有区别呢,还不就是因为内存分配时的差别。

  • 基础类型值:只是把变量里的值传递给参数,之后参数和这个变量互不影响。

  • 引用类型值:对象变量它里面的值是这个对象在堆内存中的内存地址,这一点你要时刻铭记在心!因此它传递的值也就是这个内存地址,这也就是为什么函数内部对这个参数的修改会体现在外部的原因了,因为它们都指向同一个对象呀。或许我这么说了以后你对书上的例子还是有点不太理解,那么请看图吧:

所以,如果是按引用传递的话,是把第二格中的内容(也就是变量本身)整个传递进去(就不会有第四格的存在了)。但事实是变量把它里面的值传递(复制)给了参数,让这个参数也指向原对象。因此如果在函数内部给这个参数赋值另一个对象时,这个参数就会更改它的值为新对象的内存地址指向新的对象,但此时原来的变量仍然指向原来的对象,这时候他们是相互独立的;但如果这个参数是改变对象内部的属性的话,这个改变会体现在外部,因为他们共同指向的这个对象被修改了呀!来看下面这个例子吧:(传说中的call by sharing)

var obj1 = {value:'111'
};var obj2 = {value:'222'
};function changeStuff(obj){obj.value = '333';obj = obj2;       return obj.value;
}var foo = changeStuff(obj1);console.log(foo);// '222' 参数obj指向了新的对象obj2
console.log(obj1.value);//'333'

obj1仍然指向原来的对象,之所以value改变了,
是因为changeStuff里的第一条语句,这个时候obj是指向obj1的 .
再啰嗦一句,如果是按引用传递的话,这个时候obj1.value应该是等于'222'

可以把ECMAScript函数的参数想象成局部变量

4.1.4 检测类型

如果变量的值是一个对象null,则typeof操作符会返回"object".

通常我们并不是想知道某个值是对象,而是想知道它是什么类型的对象。为此,ECMAScript提供了instanceof操作符;

如果对象是给定引用类型的实例,那么instanceof操作符就会返回true

console.log(person instanceof Object);  //变量person是Object吗?
console.log(colors instanceof Array);   //变量colors是Array吗?
console.log(pattern instanceof RegExp); //变量pattern是RegExp吗?

根据规定,所有引用类型的值都是Object的实例。在检查一个引用类型值和Object构造函数时,instanceof操作符始终会返回true

4.2执行坏境和作用域

每个函数都有自己的执行环境。当执行流进入一个函数时,函数的环境就会被推入一个环境栈中。而在函数执行后,将其环境弹出,把控制权返回给之前的执行环境。

每个环境都有一个与之关联的变量对象,环境中定义的所有变量和函数都保存在这个对象中。虽然我们编写的代码无法访问这个对象,但解析器在处理数据时会在后台使用它

当代码在一个环境执行时,会创建变量对象的一个作用域链。作用域链的用途,是保证对执行环境有权访问的所有变量和函数的有序访问

4.2.1 延长作用域链

有些语句可以在作用域链的前端临时增加一个变量对象,该变量对象会在代码执行后被移除。有两种情况下会发生这种现象。

  • try-catch 语句中的 catch 块

  • with 语句

对 with 来说,将会指定对象添加到作用域链中。对 catch 来说,会创建一个新的变量对象,其中包含的是被抛出的错误对象的声明。

var oMyself = {sFirstname: "Aidan",sLastName: "Dai"
}function create(){var sLastName = "Wen"with(oMyself){//将oMyself作为自己的执行环境sAllName = sFirstname +" " + sLastName;}return sAllName;
}
var sMyName = create();
console.log(sMyName); //Aidan Dai

4.2.2 没有块级作用域

对于有块级作用域的语言来说,for语句初始化变量的表达式所定义的变量,只会存在于循环的环境之中。而对于JavaScript来说,由for语句创建的变量i即使在for循环执行结束后,也依旧会存在于循环外部的执行环境中。

1. 声明变量
使用var声明的变量会自动被添加到最接近的环境中。在函数内部,最接近环境的就是函数的局部环境;在with语句中,最接近的环境就是函数环境。如果初始化变量时没有使用var声明,该变量会自动被添加到全局环境。

注意:在编写JavaScript中,不声明而直接初始化变量时一个错误的做法,因为这样可能会导致意外。在严格模式下,初始化未经声明的变量会导致错误。

2.查询标识符
搜索过程从作用域链的前端开始,向上逐级查询与给定名字匹配的标识符。如果在局部环境找到,搜索过程停止,变量就绪。如果在局部环境中没有找到该变量名,则继续沿作用域向上搜索。搜索过程将一直追溯到全局环境的变量对象。在全局环境也没找到的话则说明该变量尚未声明。

var color = "blue";function getColor() {return color;
}console.log(getColor()); //"blue";

4.3 垃圾收集

JavaScript具有自动垃圾收集机制,也就是说,执行环境会负责管理代码执行过程中使用的内存。
JavaScript垃圾回收的机制很简单:找出不再使用的变量,然后释放掉其占用的内存,但是这个过程不是时时的,因为其开销比较大,所以垃圾回收器会按照固定的时间间隔周期性的执行。

变量生命周期

什么叫不再使用的变量?不再使用的变量也就是生命周期结束的变量,当然只可能是局部变量,全局变量的生命周期直至浏览器卸载页面才会结束。局部变量只在函数的执行过程中存在,而在这个过程中会为局部变量在栈或堆上分配相应的空间,以存储它们的值,然后再函数中使用这些变量,直至函数结束(闭包中由于内部函数的原因,外部函数并不能算是结束

一旦函数结束,局部变量就没有存在必要了,可以释放它们占用的内存。貌似很简单的工作,为什么会有很大开销呢?这仅仅是垃圾回收的冰山一角,就像刚刚提到的闭包,貌似函数结束了,其实还没有,垃圾回收器必须知道哪个变量有用,哪个变量没用,对于不再有用的变量打上标记,以备将来回收。用于标记无用的策略有很多,常见的有两种方式

4.3.1 标记清除

这是JavaScript最常见的垃圾回收方式,当变量进入执行环境的时候,比如函数中声明一个变量,垃圾回收器将其标记为“进入环境”,当变量离开环境的时候(函数执行结束)将其标记为“离开环境”。至于怎么标记有很多种方式,比如特殊位的反转、维护一个列表等,这些并不重要,重要的是使用什么策略,原则上讲不能够释放进入环境的变量所占的内存,它们随时可能会被调用的到。

垃圾回收器会在运行的时候给存储在内存中的所有变量加上标记,然后去掉环境中的变量以及被环境中变量所引用的变量(闭包),在这些完成之后仍存在标记的就是要删除的变量了,因为环境中的变量已经无法访问到这些变量了,最后,垃圾收集器完成内存清除工作,销毁那些带标记的值并回收它们所占用的内存空间。

4.3.2 引用计数

在低版本IE中经常会出现内存泄露,很多时候就是因为其采用引用计数方式进行垃圾回收。引用计数的策略是跟踪记录每个值被使用的次数,当声明了一个变量并将一个引用类型赋值给该变量的时候这个值的引用次数就加1,如果该变量的值变成了另外一个,则这个值得引用次数减1,当这个值的引用次数变为0的时候,说明没有变量在使用,这个值没法被访问了,因此可以将其占用的空间回收,这样垃圾回收器会在运行的时候清理掉引用次数为0的值占用的空间。

4.3.3 性能问题

垃圾收集器是周期性运行的,而且如果为变量分配的内存数量很可观,那么回收工作量也是相当大的。在这种情况下,确定垃圾收集的时间间隔是一个非常重要的问题。

事实上,在有的浏览器中可以触发垃圾收集过程,但我们不建议这样做。在IE中调用window.CollectGarbage()方法会立即执行垃圾收集。在Opera7及更高版本中,调用window.opera.collect()也会启动垃圾收集例程。

4.3.4管理内存

确保占用最少的内存可以让页面获得更好的性能。而优化内存占用的最佳方式,就是为执行中的代码只保存必要数据。一旦数据不再可用,最好通过将其值设置为null来释放其引用——这个方法叫做解除引用(dereferencing)。这一做法适用于大多数全局变量和全局对象属性。局部变量会在它们离开执行环境时自动被解除引用。

function createPerson(name) {var localPerson = new Object();localPerson.name = name;return localPerson;
}
var globalPerson = createPerson("Nicholas");//手工解除globalPerson的引用globalPerson = null;

4.4 小结

基本类型值和引用类型值具有以下特点:

  • 基本类型值在内存中占据固定大小的空间,因此被保存在栈内存中;

  • 从一个变量向另一个变量复制基本类型的值,会创建这个值得一个副本;

  • 引用类型的值是对象,保存在堆内存中;

  • 包含引用类型值得变量实际上包含的并不是对象本身,而是指向该对象的指针;

  • 从一个变量向另一个变量复制引用类型的值,复制的其实是指针,因此两个变量最终都指向同一个对象;

  • 确定一个值是哪种基本类型可以使用typeof操作符,而确定一个值是哪种引用类型可以使用instanceof操作符。
    所有变量(包括基本类型和引用类型)都存在于一个执行环境(也称为作用域)当中,这个执行环境决定了变量的生命周期,以及哪一部分代码可以访问其中的变量。

最后,如有错误和疑惑请指出,多谢各位大哥

JavaScript学习笔记 - 变量、作用域与内存问题相关推荐

  1. JavaScript:学习笔记(7)——VAR、LET、CONST三种变量声明的区别

    JavaScript:学习笔记(7)--VAR.LET.CONST三种变量声明的区别 ES2015(ES6)带来了许多闪亮的新功能,自2017年以来,许多JavaScript开发人员已经熟悉并开始使用 ...

  2. JavaScript高级编程设计(第三版)——第四章:变量作用域和内存问题

    系列文章目录 第二章:在html中使用javaScript 第三章:基本概念 第四章:变量作用域和内存问题 第五章:引用类型 目录 系列文章目录 前言 一.基本数据类型和引用类型的值? 1.数据类型 ...

  3. JavaScript学习笔记(第二部分)总共四部分

    JavaScript学习笔记(第二部分)总共四部分 4 对象(Object) 字符串String.数值Number.布尔值Boolean.空值Null.未定义Undefined是基本的数据类型,这些数 ...

  4. JavaScript学习笔记(四)---闭包、递归、柯里化函数、继承、深浅拷贝、设计模式

    JavaScript学习笔记(四)---闭包.递归.柯里化函数.继承.深浅拷贝.设计模式 1. 匿名函数的使用场景 2.自运行 3.闭包 3.1前提: 3.2闭包 4.函数对象的三种定义方式 5.th ...

  5. 千锋JavaScript学习笔记

    千锋JavaScript学习笔记 文章目录 千锋JavaScript学习笔记 写在前面 1. JS基础 1.1 变量 1.2 数据类型 1.3 数据类型转换 1.4 运算符 1.5 条件 1.6 循环 ...

  6. JavaScript学习笔记(五)

    JavaScript学习笔记(五) ①Array类 本地对象 ②Date类 ①Global对象 对象的类型   内置对象 ②Math对象 宿主对象 今天继续学习JS中的对象,昨天内置对象Global对 ...

  7. JavaScript 学习笔记(1)

    1.     何为 Jscript JScript 是一种解释型的.基于对象的脚本语言. 局限性: 1)        不能使用该语言来编写独立运行的应用程序 2)        没有对读写文件的内置 ...

  8. JavaScript学习笔记01【基础——简介、基础语法、运算符、特殊语法、流程控制语句】

    w3school 在线教程:https://www.w3school.com.cn JavaScript学习笔记01[基础--简介.基础语法.运算符.特殊语法.流程控制语句][day01] JavaS ...

  9. Java程序猿JavaScript学习笔记(4——关闭/getter/setter)

    计划和完成这个例子中,音符的顺序如下: Java程序猿的JavaScript学习笔记(1--理念) Java程序猿的JavaScript学习笔记(2--属性复制和继承) Java程序猿的JavaScr ...

最新文章

  1. iPhone开发应用Sqlite使用手册
  2. hdp分享码2020_和平精英2020黄金风衣龙cdk兑换码
  3. box怎么用 latency_换轴记给铝厂 F96 牛油果,换一套 Box 重力蓝
  4. php 数组降维,php 数组去重的方法参考(一维数组去重、二维数组去重)
  5. PS抠图神器: KnockOut 2.0安装汉化和使用教程
  6. SAP License:杂谈-珍爱生命,远离SAP
  7. 程序流程三控制,顺序控制,分支控制,循环控制综合练习题
  8. [Pa2013]Iloczyn
  9. 关于使用电脑命令开启无线热点的那点事。。。
  10. 测试用例以及相关问题
  11. 第七章 为什么巴比伦塔会失败
  12. 长方形与圆最近连线LISP_“认识长方形,正方形和圆”教学实录与评析
  13. protractor 添加测试报告
  14. 计算机专硕考数一英一的学校有哪些,这所211专硕改考数一英一!部分院校初试科目调整...
  15. 榆熙教育:拼多多平台考核如何达成系统考核指标
  16. 多目标追踪——【两阶段】BoT-SORT: Robust Associations Multi-Pedestrian Tracking
  17. 医院计算机中心应急演练,我院开展信息系统故障应急演练
  18. python制作会动的表情包_利用python图片生成,需10几行代码,生成的动态表情包(小黄鸭)...
  19. 我和silly的谈话心得
  20. 手把手实操系列|贷后迁徙率模型开发(上篇)

热门文章

  1. Java 中接口 interface 实例介绍
  2. VulnHub靶机系列:Os-ByteSec
  3. linux socket高性能服务器处理框架
  4. caffe新手常遇到的三个问题
  5. C语言:强大的函数指针
  6. python12-并发编程
  7. 【BZOJ2500】幸福的道路 树形DP+RMQ+双指针法
  8. ubuntu18.04新安装时Unable to locate package问题
  9. 50%企业数据治理失败!这9大要素才是成功关键
  10. 为什么Siri总是像个智障?智能助手背后的技术到底有多难?