读书笔记4:变量、作用域和内存问题

这里写图片描述

基本类型和引用类型的值

(1)概念

基本类型的值指的是简单的数据段,而引用类型的值指那些可能由多个值构成的对象。
引用类型的值是保存在内存中的对象。JavaScript 不允许直接操作对象的内存空间,实际上操作的是对象的引用,而不是实际的对象。引用类型的值是按引用访问的。
两种类型的值得在内存中的位置是不一样的,基本类型值在内存中占据固定大小的空间,被保存在栈内存中;引用类型的值时对象,保存在堆内存中。
包含引用类型值得变量实际上包含的并不是对象本身,而是一个指向该对象的指针,也可以理解为该对象在堆内存中的地址,而这个变量是保存在栈内存中的。
这里写图片描述
因此,复制引用类型的值时,实际上复制的是指向储存在堆中的对象的指针。它们会指向同一个对象,改变其中一个变量会影响到另一个变量。

(2)函数传递参数

ECMAScript 中所有函数的参数都是按值传递的。

关于按值和按引用传递:

  • 按值传递(call by value):函数的形参是被调用时所传实参的副本,修改形参的值并不会影响实参,是最常用的求值策略。
  • 按引用传递(call by reference)时,函数的形参接收实参的隐式引用,而不再是副本。这意味着函数形参的值如果被修改,实参也会被修改。同时两者指向相同的值。
  • 按值传递由于每次都需要克隆副本,对一些复杂类型,性能较低;按引用传递会使函数调用的追踪更加困难,有时也会引起一些微妙的BUG。

JS中,函数参数按值传递有什么特点?

  • 传递基本类型的值时,被传递的值会被复制给一个局部变量(即命名参数,或 arguments 对象的一个元素);
  • 传递引用类型的值时,会把这个值在内存中的地址(指针)复制给一个局部变量,因此这个局部变量的变化会反映在函数的外部。

这部分例子可看大神博客,其中提到按共享传递 call by sharing的思想

例1:向函数参数传递引用类型值

var foo = {name:'foo'};
function test(obj){
  obj.name = 'test'; //第一步操作
  obj = {name:'bar'};  //第二步操作
};
test(foo);
console.log(foo); //结果:Object {name: "test"}

首先,这里 foo 保存的是对象的指针,不是对象本身,按值传递给参数 obj 的也就是这个指针,称为对象1
第一步操作,obj 指向的对象仍为对象1,此时修改了对象1的 name 属性;
第二步操作,给 obj 赋了新值,指向了新的对象,称为对象2;相当于执行了obj = new Object(); obj = {name:'bar'}; 这里 obj 获得一个全新的对象。
在全局环境下,变量 foo 没有重新赋值,一直是指向对象1;
所以改变函数内部变量 obj 的值,不会影响外部环境中参数原值 foo
这里写图片描述

(3)检测类型
  • 使用 instanceof 操作符判断一个实例是否属于某种类型;
  • instanceof 可以在继承关系中用来判断一个实例是否属于它的父类型,在多层继承关系中,instanceof 运算符同样适用;
  • 所有引用类型的值都是 Object 的实例,检测一个引用类型值和 Object 构造函数时,instanceof 操作符始终返回 true

要深入理解 instanceof 操作符,会涉及到原型继承机制,后续章节中会提及。此博客中有一些例子。

执行环境和作用域

(1)概念理清
  • 执行环境定义了变量或函数有权访问的其他数据,决定了它们各自的行为,有时也被叫做执行上下文。某个执行环境中的所有代码执行完毕后,该环境被销毁,保存在其中的所有变量和函数也随之销毁。执行环境有全局执行环境函数执行环境之分。
  • 变量对象:环境中定义的所有变量和函数都保存在这个对象中。
  • 宿主环境:宿主环境一般由外壳程序创建和维护,可以为JavaScript等多种脚本语言提供服务。宿主环境一般会创建一套公共对象系统,这套对象系统对所有脚本语言开放,并允许它们自由访问。同时,宿主环境还会提供公共接口,用来装载不同的脚本语言引擎。这样我们可以在同一个宿主环境中装载不同的脚本引擎,并允许它们共享宿主对象。
  • 全局执行环境:全局执行环境是最外围的一个执行环境。在 Web 浏览器中,全局执行环境被认为是 window 对象,因此所有全局变量和函数都是作为window对象的属性和方法创建的。代码载入浏览器时,全局执行环境被创建;当我们关闭网页或者浏览器时全局执行环境才被销毁。
  • 作用域链:作用域链的用途,是保证对执行环境有权访问的所有变量和函数的有序访问。代码在一个环境中执行时,会创建变量对象的一个作用域链。作用域链的前端,始终都是当前执行的代码所在环境的变量对象。内部环境可以通过作用域链访问所有的外部环境,但外部环境不能访问内部环境中的任何变量和函数。
  • 活动对象:此概念针对函数。在一个函数对象被调用的时候,会创建一个活动对象,首先将该函数的每个形参和实参,都添加为该活动对象的属性和值;将该函数体内显示声明的变量和函数,也添加为该活动的的属性。这个活动对象作为该函数执行环境的作用域链的最前端,并将这个函数对象的[[scope]]属性里作用域链接入到该函数执行环境作用域链的后端。
  • 函数对象:在一个函数定义的时候, 会创建一个这个函数对象的 [[scope]] 属性,并将这个 [[scope]] 属性指向定义它的作用域链上。
(2)延长作用域链
  • 在作用域链前端临时添加一个变量对象,该变量对象会在代码执行后被移除。
  • try-catch 语句的 catch 模块:当try代码块中发生错误时,执行过程会跳转到 catch 语句,然后异常对象推入一个可变对象并置于作用域的头部;在 catch 代码块内部,函数的所有局部变量将会被放在第二个作用域链对象中。
  • with 语句with 语句是对象的快捷应用方式,用来避免书写重复代码。width 语句看上去更高效,实际上产生了性能问题,一般不建议使用。
(3)块级作用域

块级作用域和函数作用域
块级作用域:变量在离开定义的块级代码后立即被回收;
函数作用域:变量在定义的环境中以及嵌套的子函数中处处可见;

ES5 及以前版本 JavaScript 都没有块级作用域,只有两种:函数作用域和全局作用域。使用 var 声明的变量会自动被添加到最接近的环境中。
直到 ES6 中定义了块级作用域,使用 let / const声明的变量只能在块级作用域里访问。

let 声明的特点:

  • let 所声明的变量,只在let命令所在的代码块内有效;
  • 不存在变量提升,变量一定要在声明后使用,否则报错;
  • 只要块级作用域内存在let命令,它所声明的变量就“绑定”(binding)这个区域,不再受外部的影响。在代码块内,使用 let 命令声明变量之前,该变量都是不可用的,称为“暂时性死区”(temporal dead zone,简称 TDZ);
  • 由于存在“暂时性死区”,在let命令声明变量之前,typeof 操作符检查此变量也会报错;
  • let 不允许在相同作用域内,重复声明同一个变量。

ES6 语法的学习移步阮一峰老师的《ECMAScript 6 入门》教程。


以上,就是《Javascript高级程序设计》第四章的学习笔记。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值