前言
本来想把这个与上篇博客写到一起的,但是考虑到是两个知识点还是分开算了,于是我们继续今天的学习吧。
基本类型与引用类型
ECMAScript的的变量有两种类型:
基本类型(值类型):简单数据段
引用类型:多个值构成的对象
在变量赋值结束后,解析器必须知道这个变量时基本数据类型还是引用类型,需要注意的是string在js中是值类型。
复制的差异
值类型的复制会在内存中创建副本,所以彼此间不会影响,但是引用类型只是将变量的引用复制,其指向的仍然是一个对象,会相互影响:
1 var a = {};
2 a.a = 6;
3 var b = a;
4 b.a = 66;
5 alert(a.a);//66
这是一个典型的例子,值类型的就不举例了。
执行环境与作用域
PS:作用域是面试官的最爱,也是我们经常栽跟头的地方,这块我们有必要搞清楚
执行环境(execution)是javascript中非常重要的一个概念。
执行环境定义了变量或者函数有权限访问其他数据,决定了他们各自的行为。
每一个执行环境都有一个与之关联的变量对象(variable object),环境中定义的所有变量和函数都保存到这个对象中,虽然代码无法访问,但是解析器会用到他。
在web浏览器中window对象便是我们的全局执行环境(最外围的执行环境,宿主不同便有差异,比如node.js可能就不一样),因此所有全局变量和函数都是作为window对象的属性或者方法创建的。
销毁
某个执行环境中的所有代码执行完毕,该环境便销毁,变量对象中的属性和函数也就完蛋了(闭包会让情况有所不同)。
函数作用域
每个函数都有自己的“作用域”范围(执行环境),当执行流进入一个函数时,函数的环境就会进入一个环境栈中,在函数执行完毕后,栈会将其环境弹出,控制权又重新交回之前的环境。ECMAScript程序的执行流就是这个机制在控制。
作用域链
当代码在一个环境中执行时会创建变量对象(variable object),并且该对象拥有一个作用域链,作用域链的用途是保证对执行环境有权限访问的所有变量与函数的有序访问。
作用域的最前端便是当前执行代码所在环境的变量对象,若这个变量时函数,就将其活动对象(activation object)作为变量对象,
活动对象最开始只包含一个变量arguments,整个作用域链会一直向上延伸直到window,这里上一个经典的例子:
1 var color = 'blue';
2 function changeColor() {
3 var anotherColor = 'red';
4 function swapColor() {
5 var tmpColor = anotherColor;
6 anotherColor = color;
7 color = tmpColor;
8 }
9 swapColor();
10 }
11 changeColor();
以上代码涉及三个执行环境(execution context):
① 全局环境
② changeColor局部环境
③ swapColor局部环境
全局变量中有一个color和一个函数changeColor
changeColor中有一个anotherColor变量和swapColor函数
swapColor函数中有tmpColor,但是他可以访问以上所有的变量

我们看到三个框了,内部环境可以通过作用域链访问所有外部环境,但是外部环境不能访问内部的,js中只有函数能产生局部作用域。
延长作用域链
有些语句可以在作用域链顶端临时增加一个变量对象,该变量对象会在代码执行后被移除,以下情况会引起作用域链加长:
① try-catch的catch块
② with语句
这两个语句都会在作用域链前端增加一个变量对象,对with语句来说,会将指定对象添加到作用域链中,对catch来说,会创建一个新的变量对象,其中包含被抛出的错误对象的声明。
1 function buildUrl() {
2 var q = '?p=true';
3 with (location) {
4 var url = href + q;
5 }
6 return url;
7 }
在此with语句接收的是location对象,因此其变量对象就包含了location对象的属性和方法,而且被放到作用域链的前端了,我们的href就是有意义的了,相当于location.href。
闭包
作用域链与闭包在一起后情况又会有所不同,在此我们再来看看闭包是什么:
闭包是指有权限访问另一个函数作用域中变量的函数:
在函数内部创建函数,使用到了外部的变量,并且将此函数返回,就形成了闭包。
我认为闭包产生的原因是因为想私有化变量,但是提供一个对外的访问接口,所以提出了闭包的概念
1 function Klass() {
2 var name;
3 var getName = function () {
4 return name;
5 };
6 var setName = function (n) {
7 name = n;
8 };
9 return { getName: getName, setName: setInterval };
10 }
比如这个应用,是比较常见的,name应该是私有的,但是我们要将其接口公布出来。
这里,当Klass被调用了,即使他返回了,但是内部函数仍然可以访问name变量,内部函数包含了外部Klass函数的作用域,我们来理一理这个东西:
当某个函数调用时会创建一个执行环境(execution context)以及相关的作用域链。
然后,使用arguments和其它命名参数的值来初始化函数的活动对象(activation object)。
在作用域链中,外部函数的活动对象始终处于第二位,可能还有第三位第四位。。。
在函数执行过程中,为了读取和写入变量的值,就需要在作用域链中查找变量
1 function compare(v1, v2) {
2 if (v1 < v2) {
3 return -1;
4 } else if (v1 > v2) {
5 return 1;
6 } else {
7 return 0;
8 }
9 }
10 var r = compare(5, 10);
以上定义了compare函数,然后在全局下调用了他,第一次调用时,会创建this、arguments、v1、v2的活动对象。
全局执行环境的变量this、r、compare在compare执行环境作用域链的第二位:

后台的每一个执行环境都有一个表示变量的对象——变量对象。
全局环境的变量对象始终都在,像compare这种局部变量的变量对象只在函数执行时候存在
在创建compare函数时,会创建一个预先包含全局变量的作用域链,这个作用域链被保存在内部的[[Scope]]属性中
当调用compare函数时,会为函数创建一个执行环境,然后复制函数的[[Scope]]属性中的对象构建新的执行环境的作用域链
为此又有一个活动对象(在此作为变量对象使用)被创建并推入执行环境作用域链的最前端。
上例中,对于compare执行环境而言,其作用域链包含两个变量对象:
1 本地活动对象
2 全局变量对象
一般来说,函数执行完毕后,局部活动对象就会被销毁,内存中仅保存全局作用域,但是闭包让情况有所转变:
在函数内部定义的函数将会包含函数(外部函数)的活动对象添加到它自己的作用域链中。
所以在Klass内部定义的getName与setName的作用域链中事实上是包含了外部Klass的活动对象的。
在函数Klass返回后,其中的getName作用域链被初始化为包含Klass函数的活动对象和全局变量对象
这样getName就可以访问Klass函数中的所有变量,这样做的结果就是阻止了Klass执行结束后销毁其name变量。
换句话说,Klass的执行环境与其作用域链会被回收,但是其activation object会被保留在内存中,知道getName被销毁后才回收,最后Klass的活动对象才被销毁
PS:显然闭包占有更多的资源,若是不及时回收会对性能造成影响,这里各位要小心才行。
闭包与变量
作用域链的这种配置机制也产生了一个影响不到的问题:
闭包只能取得包含函数(外部函数)中变量的最后一个值。
因为闭包所保存的是函数的活动对象,是整个变量对象,而不是某一个值
1 function createFunc() {
2 var r = [];
3 for (var i = 0; i < 10; i++) {
4 r[i] = function () {
5 return i;
6 };
7 }
8 return r;
9 }
这是一有趣的代码,我们这里返回了一个数组,数组里面装了10函数,但是我们这是个函数保存的是同一个外部函数的活动对象,而i最后的值是10,所以所有函数最后打印出来的都是10。这里要怎么处理各位都知道了,我还是贴个代码吧:
1 function createFunc() {
2 var r = [];
3 for (var i = 0; i < 10; i++) {
4 r[i] = (function (num) {
5 return function () {
6 return num;
7 }
8 })(i);
9 }
10 return r;
11 }
这里把各个闭包函数保存的活动对象接不相同,所以产生了不同的结果。
写到此处我感觉我所知道的东西都差不多了,若是您有什么补充,或者我这里有什么问题,请提出来。下面我们来演练一番
做几道题吧
好了,说了这么多虚的,我们来一点干货作为本文的结束吧,完了我就休息了,等晚上再写点东西就结束了。
settimeout与setInterval
1 var a = 6;
2 setTimeout(function () {
3 alert(a);
4 a = 666;
5 }, 1000);
6 a = 66;
7
8
9 var a = 6;
10 setInterval(function () {
11 alert(a);
12 a = 666;
13 }, 1000);
14 a = 66;
我这里稍微修改了一下代码,我们来看看:
第一段代码是上次寒冬老师问我的问题,因为延时函数会后面点执行,所以打印的是66
而第二段代码有所不同的是,先打印的是66,后面便一直是666了
第二题
1 var tt = 'aa';
2 function test(){
3 alert(tt);
4 var tt = 'dd';
5 alert(tt);
6 }
7 test();
这道题有点意思哦,他还考察了变量声明提前的问题。
其中var tt的定义会提前,这道题相当于:
1 var tt = 'aa';
2 function test(){
3 var tt;
4 alert(tt);
5 tt = 'dd';
6 alert(tt);
7 }
8 test();
在互动变量中应该使用内部tt,所以第一次是undefined第二次是dd,这道题本身不难,但是容易引起混淆:
1 var a = '11';
2 var a;
3 alert(a);//11
我们这个重复定义不会覆盖其值的约定可能会给你带来迷糊,这里要将之看着两个国家的人。
我们稍稍变形:
1 var a = 10;
2 function test() {
3 a = 100;
4 alert(a);
5 alert(this.a);
6 var a;
7 alert(a);
8 }
9 test();
这个代码相当于:
1 var a = 10;
2 function test() {
3 var a;
4 a = 100;
5 alert(a);//100
6 alert(this.a);//10
7 alert(a);//100
8 }
9 test();
我们只看this.a,这里this指向的是window,至于this的东西,我们后面点再说。
第三题
1 f = function () { return true; };
2 g = function () { return false; };
3 (function () {
4 if (g() && [] == ![]) {
5 f = function f() { return false; };
6 function g() { return true; }
7 }
8 })();
9 alert(f()); // true or false ?
这道题看上去有点乱。。。考察了很多东西,我们先说说其中的一个有问题的地方:
不要在if里面定义function请使用函数表达式,在if中定义function会让结果难以估计
javascript可能会修正其中的程序而忽略if判断,但是这不是我们这里要研究的,但是他直接影响我做题的思路啊,这里果断给把题改了:
1 f = function () { return true; };
2 g = function () { return false; };
3 (function () {
4 if (g() && [] == ![]) {
5 f = function f() { return false; };
6 g = function() { return true; }
7 }
8 })();
9 alert(f()); // true or false ?
题目愿意是什么我们不管了,现在就这样做。其中第五行的function f()中的f会被忽略掉,他还有个坑爹的地方是:
var s1 = g();//false
var s2 = g() && []; //false
var s3 = ![]; //false
var s4 = g() && [] == ![]; //false
var s5 = (g() && []) == ![]; //true
这道题稍不注意就要完蛋,s4为什么是false我都猜不透。。。所以答案是true了,这道题我没有摸透,各位自己去看看吧。
第四题
1 function say667() {
2 // Local variable that ends up within closure
3 var num = 666;
4 var sayAlert = function () { alert(num); }
5 num++;
6 return sayAlert;
7 }
8
9 var sayAlert = say667();
10 sayAlert();
这道题有点意思,与前面的循环闭包的例子类似,答案是667
结语
本文暂时到这里了,请各位也上两道与作用域有关的面试题呗!
分享到:
相关推荐
总结来说,基金经理的学习能力是影响基金业绩的重要变量,通过构建和测试各种能力指标,可以更全面地评估基金经理的综合能力,并据此制定投资策略。这种评估方法对于投资者选择合适的基金产品以及基金公司选拔和培养...
这份名为“追寻券商并购之路:历史的机遇与自我的机会”的行业报告,深入探讨了券商并购的历史背景、驱动因素以及未来可能的发展方向。 首先,我们要理解券商并购的历史机遇。中国的金融市场在过去几十年中经历了...
个人特征因子概况:基金经理是影响基金业绩的重要因素之一,本文从基金经理角度切入,依次从基本信息、教育背景、领导经历、研究经历、工作经历、获奖经历六个维度选取 17 个因子指标衡量基金经理个人特征。...
在开发Laya项目时,使用TypeScript作为源代码语言,然后将其编译为JavaScript,有时可能会遇到一个棘手的问题:bundle.js文件没有更新。这个问题在初看之下可能让人困惑,尤其是当你尝试各种方法如新建工程、拷贝...
HANA介绍有梦想, 敢追寻
- **作用**: 允许用户自定义变量,在测试脚本中重用这些变量。 - **示例**: - 变量名: `username` - 默认值: `testuser` **2.2.3 用户参数** - **功能**: 类似于用户定义的变量,但可以设置更多的属性,例如是否...
战争的荒诞——《追寻卡西亚托》的主题研究.pdf
PCB(印刷电路板)行业是...高等教育可能有其失败之处,但接受教育所带来的知识和能力可以用来改变自己的命运。通过不断学习和适应,以及找到合适的平台,每个人都有可能实现自己的价值,甚至有朝一日能够改变世界。
根据提供的文档内容,我们可以了解到以下知识点: 1. 券商并购的背景及原因:券商行业在经济转型中,为了构建多层次资本市场,对券商的规模提出了更高的要求,这成为券商青睐并购重组的首要原因。...
* 弗兰克认为人类的存在有三个特征使之与动物区分开, 其一就是精神性(存在性),第二个特征是自由,第三个特征是责任。 * 精神性是弗兰克最有特色,也是最有争议性的概念。精神性并无宗教含义,它与生理、心理一样,...
【证券行业并购分析】 在当前的证券行业中,券商的并购活动频繁,这主要源于多重因素。首先,随着中国经济转型的深化,多层次资本市场的建设对于券商的规模和能力提出了更高的要求。通过并购,券商能够迅速扩充资金...
《梦里花落知多少》的深刻之处就在于它没有回避现实的残酷,而是选择直面它。它让我们认识到,生活中的悲欢离合是不可避免的,我们需要学会在痛苦中寻找希望,在失望中寻找勇气。这部作品是青春的一次深刻反思,也是...
《延安,我把你追寻》是新人教统编版五四制四年级上册语文教材中的一篇课文,这是一首充满深情的新体诗,诗人通过诗歌表达了对延安精神的热烈追寻和赞美。这首诗分为六个小节,分别讲述了对延安精神的向往、现代化...
标题:“精通Dojo JavaScript与Ajax工具,打造卓越的网络体验” 描述:“Dojo,众人追寻的宝藏!Ajax爱好者的首选!” 从以上标题和描述中,我们可以深入挖掘一系列关于Dojo框架及其在JavaScript和Ajax技术中的...
安第斯山脉秘境之旅:追寻绚烂,感受落寞
基础设施公募REITs深度研究报告:配置公募REITs,追寻长期收益(44页).pdf
标题中的“带着困惑去追寻”暗示了一种对知识和理解的不懈追求,即使面临困难和迷茫也要坚持探索。描述中的内容主要围绕“困惑”这一主题展开,讲述了个人、社会乃至时代的困窘,以及如何在困窘中寻找突破、实现成长...
儿童语文深度学习是当前教育领域关注的热点话题之一,尤其在小学阶段的语文教育中,深度学习不仅仅是一种教学方式的转变,更是一种对学生核心素养培养的深刻追求。儿童语文深度学习的价值审视、学理追寻及实践探索,...
高中历史之历史百科追寻民国往事探访何应钦的传奇人生素材
在本项目中,我们主要探讨如何使用STM...总之,"追寻颜色_OV7725摄像头追寻特定颜色_STM32F103_"项目涉及了嵌入式系统、图像处理和颜色识别等多个技术领域,对于提升物联网和自动化设备的设计能力具有很大的实践价值。