[转]作用域与作用域链

作用域,作用域链,域解析


转载自 陈玉

执行环境

JavaScript执行环境,也叫作JavaScript执行上下文(我百度翻译的(^-^),英文名叫做execution context)
执行环境:JavaScript代码都是在执行环境中被执行的。执行环境是一个概念,一种机制,用来完成JavaScript运行时在作用域、生命周期等方面的处理,它定义了变量或函数是否有权访问其他数据,决定各自行为。

而且每个执行环境都会有一个就是变量对象VO(variable object),环境中变量和函数声明保存在VO中。
注意,函数表达式(与函数声明相对)不包含在变量对象之中。,在代码执行开始,会进入不同的执行环境,这些执行环境共同构成了一个执行环境栈。

如果这个环境是函数,则将其活动对象AO(activation object)作为变量对象,活动对象最开始的两个属性是arguments和this

在下个图中,拥有一些函数上下文EC1和全局上下文Global EC,当EC1进入和退出全局上下文的时候下面的栈将会发生变化:

JS中一共有三种执行环境:

  1. 全局执行环境(Global code) JS代码开始运行时默认的
    即全局的、不在任何函数里面的代码,例如:一个js文件、嵌入在HTML页面中的js代码等
    注意:
  • 保存在全局执行环境中的所有变量和函数,当其应用程序退出,如:关闭网页或浏览器时被销毁
  • 全局执行环境是window的对象(所有全局函数和变量都是作为window的属性和方法创建的)
    1
    2
    3
    4
    5
    6
    7
    8
    var a=2;
    function setB(){
    return 123;
    }
    console.log(a);
    console.log(setB());
    console.log(window.a);
    console.log(window.setB());
  1. 函数执行环境(Function code) 代码进入了一个函数
    每个函数有自己的执行环境,有相应的执行栈
  • 函数里的局部作用域里的变量替换全局变量,但作用域仅限在函数体内的这个环境
    1
    2
    3
    4
    5
    6
    var a='哈哈哈~';
    function setB(){
    a='啧啧';
    }
    setB();
    console.log(a);
  • 通过传参,可以替换函数体内的局部变量,但作用域仅限在函数体内的这个环境

    1
    2
    3
    4
    5
    6
    var a='哈哈哈~';
    function setB(a){
    console.log(a);
    }
    setB('嗯嗯');
    console.log(a);
  • 函数体内还包含有函数,只有通过外层函数,才能访问内层函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    var a=1;
    function setA(){
    function setB(){
    var b=2;
    console.log(a);
    console.log(b);
    }
    setB();
    }
    setA();
  1. eval()方法执行代码

eval()就像是一个完整的ECMAScript解析器,只接受一个参数,即要执行的ECMAScript(或JavaScript)字符串

1
2
3
eval("console.log('哈哈')");
等价于
console.log('哈哈');

通过eval()执行的代码被认为是包含该次调用的执行环境的一部分,因此被执行的代码具有与该执行环境相同的作用域链

1
2
eval("function sayHi(){console.log('hi');}");
sayHi();

严格模式下,访问不到eval()中创建的任何变量和函数

1
2
3
"use strict";
eval("function sayHi(){console.log('hi');}");
sayHi();

应用:JSON解析器
危险:代码注入!!!(用户输入数据)

作用域

域:空间,范围,区域…
作用:读,写

作用域(scope)。在很多语言中(C++,C#,Java),作用域都是通过代码块(由{}包起来的代码)来决定的,但是,在JavaScript作用域是跟函数相关的,也可以说成是function-based。

1
2
3
4
5
6
7
8
9
10
11
12
13
function test(o){
var i=0;
if(typeof o=="object"){
var j=0;
for(var k=0;k<10;k++){
console.log(k);
}
console.log(k);
}
console.log(j);
}
var obj = new Object();
test(obj);

  1. 全局作用域中的对象可以在代码的任何地方访问,一般来说,下面情况的对象会在全局作用域中:

最外层函数和在最外层函数外面定义的变量

1
2
3
4
5
6
7
8
9
10
11
12
13
// 最外层函数和在最外层函数外面定义的变量  
var a = "out"; //最外层变量
function outFn() { //最外层函数
var b = "in";
function inFn() { //内层函数
console.log(b);
}
inFn();
}
console.log(a); //我是最外层变量
outFn();
console.log(b); //in is not defined
inFn(); //inFn is not defined

没有通过关键字”var”声明的变量

1
2
3
4
5
6
7
function outFn2() {
a = "未定义";
var a1 = "内层变量2";
}
outFn2();//要先执行这个函数,否则根本不知道里面是啥
console.log(a); //未定义直接赋值的变量
console.log(a1); //inVariable2 is not defined

浏览器中,window对象的属性

1
2
window对象的内置属性都拥有全局作用域,
如: window.name、window.location、window.top 等

  1. 局部作用域(函数作用域)所有的变量和函数只能在作用域内部使用。函数在声明它们的函数体以及这个函数体嵌套的任意函数内都是有定义的
    1
    2
    3
    4
    5
    6
    7
    8
    var foo = 1; 
    window.bar = 2;
    function baz(){
    a = 3;
    var b = 4;
    }
    // Global scope: foo, bar, baz, a
    // Local scope: b

函数声明提前:
js的函数作用域是指在函数内声明的所有变量在函数内始终都是可以见,意味着变量在声明之前就可以使用了,这个特性被非正式命名为声明提前即:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var a=1;
function fn1(){
console.log(a);//???
var a=2;
console.log(a);
}
fn1();
console.log(a);
-------------------------------------------------
var a=1
function fn1(){
var a;//在函数顶部声明了局部变量
console.log(a);//变量存在,但其值是"undefined"
a=2;
console.log(a);
}
fn1();
console.log(a);

作用域链(scope chain)

当代码在一个环境中执行时,会创建变量对象的一个作用域链(是保证对执行环境有权访问的的所有变量和函数的有序访问),这个作用域链是一个对象列表或者链表,这组对象定义了代码“作用域”中的变量
创建规则:

作用域链的前端,始终都是当前执行的代码
作用域链中的下一个变量对象,来自包含(外部环境)
而再下一个对象来自下一个包含环境
一直延续到全局执行环境(全局执行坏境的对象始终都是作用域链中的最后一个对象)

一个函数创建时,javascript后台(引擎)会默认创建一个仅供后台使用的内部属性[[Scope]],此属性存储函数的作用域链,如果是全局函数,此时则包含一个变量对象(全局变量),如果是嵌套函数(闭包),作用域链还加上了父函数的变量对象。例如下面的这个全局函数:

1
2
3
4
5
6
function add(num1,num2){
var sum = num1 + num2;
return sum;
}
var total=add(5,10);
console.log(total);

b) 函数被调用时–add(5,10),javascript后台会创建一个内部对象(execution context)–“执行环境”或“运行期上下文”,活动对象作为函数执行期的一个变量对象,包含所有局部变量(在函数内定义的)、命名参数、arguments、this,它会被推入到执行环境作用域链的前端(如下图)。每执行一次函数都会创建一个新的执行环境,当函数执行完毕执行环境就会被销毁。

图中矩形表示特定的环境。其中内部环境可以通过作用域链访问所有的外部环境,但外部环境不能访问内部变量环境中的任何变量和函数。这些环境之间的联系是线性的,有次序的。每个环境变量都可以向上搜索作用域链,以查询变量和函数名;反之则是不行。注意:函数参数也被当作变量来对待,因此其访问规则与执行环境中的其他变量相同。

查询标识符(标识符指的是:变量名称,函数声明,形参,等等。):标示符的解析是沿着作用域链一级一级的搜索标示符的过程,搜索过程都是从作用域链的前端开始,然后逐级向后回溯,直到找到标识符为止

1
2
3
4
5
var color="blue";
function getColor(){
return color;
}
console.log(getColor());//"blue"

延长作用域链

try-catch语句的catch块
with语句
这两个语句都会在原本的作用域链的前端添加一个变量对象。对于with语句来说,新添加的变量对象包含着with括号中指定对象的所有属性和方法所作的变量声明。对于catch来说,当try块发生错误时,代码执行流程自动转入到catch块,并将异常对象推入到作用域链的前端。catch块执行完毕后,作用域链就会返回原来的状态。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function initUI(){
with(document){
var bd = body,
links = getElementsByTagName("a"),
i = 0,
len = links.length;
while(i<len){
update(links[i++]);
}
getElementById("go-btn").onclick = function(){
start();
};
bd.className = "active"
}
}

当代码流执行到一个with表达式时,执行环境的作用域链会被临时改变,此时with的变量对象会被创建添加到作用域链的前端,这就意味着此时函数的所有局部变量都被推入到第二个作用域链中的变量对象,如下图:

注意:在执行with语句时,访问局部变量的代价更高了。所以尽可能避免使用with语句,可以使用局部变量代替

1
var doc = document; // 代替with(document){...}

##域解析

  1. 浏览器:

预解析:
“js解析器”
“找一些东西”:var, function
,参数
a=未定义
所有的变量,在正式运行代码之前,都提前赋了一个值(未定义)
fn1=function fn1(){console.log(1);}
所有的函数,在正式运行代码之前,都是整个函数块
注意:遇到重名,变量和函数重名,就留下函数,与上下文没有关系,注只先找var ,function声明的,函数重名,则采用覆盖

1
2
3
4
5
console.log(a);
var a=2;
function fn1(){
console.log(1);
}

  1. 逐行解读代码
    1
    2
    3
    4
    5
    6
    表达式:= + - * / ++ -- !参数
    表达式可以修改预解析的值!
    函数调用:
    同理函数作用域也发生:
    预解析和逐行解读代码
    函数:由里到外(作用域链)
1
2
3
4
5
6
console.log(a);//不会从下面取值,直接从预解析的仓库里取出undefined
var a=2;
console.log(a);
function fn1(){
console.log(1);
}

参考链接:
https://gaohaoyang.github.io/2015/05/20/scope/

http://dmitrysoshnikov.com/ecmascript/javascript-the-core/#execution-context

参考资料:
javaScript高级程序设计
javaScript权威指南

对于redux的学习收集的资料

学习Redux框架是艰辛的

接触React已近一段时间了,但是最近才开始用Redux重构项目,由于是别人帮忙重构的,所以看到刚刚重构的代码我是一脸懵逼的,根本不知道在写些什么。慢慢的多看了些文档和博客才得以了解项目的结构,过程中收集的一些资料在这里也就分享出来:

基本手册


简单的系列教程


一点也不容易搞懂


项目实例


其实我也没有认真去找,但是跟着基本教程里面的做就相当于是有了一个框架了,继续在上面进行开发足矣,这里只附上一个自己的项目。

node学习的几行笔记

Node和Express的一丢丢学习记录


1.node中使用mysql数据库,在createConnection创建连接后就没有必要再使用connect方法进行手动连接了,同样的end方法也不要使用;如果使用了connect方法,node将会在第二次查询的时候抛出一个错误

Cannot enqueue Handshake after invoking quit

2.node中使用app.all("*");可以拦截任何路由活动,过后所有的请求都会被拦截,也不能将app.get("/room");类似的路由嵌套在里面。

3.如果把index.html放在了项目的根目录下面,那么访问项目时将不会进入app.get("/")的路由,而是直接读取index的文件,这一点很关键!!简直是没有人性的设计。

4.说了好多次了,空的对象转换为布尔值也是为真(可以理解成自身带有toString等方法);在用mysql毁掉函数处理查询的result值时,千万不能用!result来判断未查询到结果。

5.这个数字2147483647是一个美妙的数字,它是三十二位系统中所能表示的最大的有符号整型数字,在用node向mysql数据库中写入的日期的时候不要将时间戳以数字的形式传入存储,不然他会转变成这个值的。(难过脸)

6.node中需要用到非常多的回调函数,所以要注意好好控制代码的结构,有必要时使用一些库(sync等);另外,回调函数的作用域都是在调用处的上下文中的,这意味着不用传入大量参数。

7.千万不要将node的项目名称设置为与依赖的包重名,这里指的项目名称是package.json里面设置的项目名称,如果设置成了一样的话将会报错(项目依赖名称冲突)。

8.redis是只能够存储键值对的,所以如果要存储多个属性的对象的话,需要将他转换为JSON字符串,取出来的时候一定要转换回去。

9.标准JSON的键是要用双引号括起来的哦,只是平时在接受里面使用的时候,如果不包含“- & *”等符号可以直接用不带引号或者单引号的表达。

React Native——如果坑,请深坑

2016年7月ReactNative跳坑记录


1.添加组件属性,有一个地方需要注意,就是class属性需要写成 classNamefor 属性需要写成 htmlFor ,这是因为 class 和 for 是 JavaScript 的保留字。

2.在genymotion上进行模拟的时候,一定要关闭电脑的防火墙,并且要把手机的wifi连上 (好像5.0以上的版本不会自动连接wifi),将模拟器的ip和端口设置为可访问本机的ip(10.0.3.2:8081),如果是在手机上的话,也是可以打开网页尝试是否可以访问,可以访问后再将ip添加进genymotion的设置里面。

3.React Native的打包器会首先寻找File.<platform>.js文件,然后再去寻找 File.js 。这就允许我们将Android平台的代码放置到 File.android.js , iOS 的放入到 File.ios.js , 以及Web平台的代码放入到 File.js, 而不需要改变导入的声明 ./File。

4.在ES6中,变量的解构赋值用途很多,交换变量的值可以用这个奇妙的方法[x, y] = [y, x]。

5.当你Image加载一个网络图片时,如果你不给该Image设置widthheight,那你将什么都看不到。这还不算,当你使用ListView时如果该组件同时存在一个兄弟元素,那么此时ListView必须设置height,否则你会发现它不再响应用户的滚动操作。。。这个地方所说的widthheight是指的在style属性中设置的值,如果直接设置height属性的话是不会起作用的。

6.使用fetch时,当你试图提交附件表单数据的时候,请一定要使用FormData对象将数据包裹

如果你只是普通的json提交,就按照官方例子来做:

1
2
3
4
5
6
7
8
9
10
11
fetch(API_LOGIN_URL, {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
account: this.state.account,
password: this.state.password,
})
})

7.FB提供了Dimensions组件可以用来获取屏幕的当前尺寸:
var deviceHeight = Dimensions.get(‘window’).height;
var deviceWidth = Dimensions.get(‘window’).width;

8.当你打算在某个控件上套用设置好的某个样式,但需要单独为其设置一个额外特殊值时,你可以这样:
<View style={[styles.demo, {backgroundColor: "blue"}]}></ View>

9.也就是如果Text元素在Text里边,可以考虑为inline元素, 如果单独在View里边,那就是Block。

10.我把View设置了justifyContent: 'center',alignItems: 'center'的样式,并在里面放上了一个ViewPagerAndroid,这个时候如果不给他设置固定的宽度或者使用padding撑开他的话是看不见的,大概是因为被View的style设置压成了类似inline的属性。

11.只有View组件在设置borderRadius属性过后,才会将自身的border一齐转变成圆角,其他元素比如Image和Touchable类的都不会有任何反应,边角是方的。

12.如果突然出现了,不显示图片的情况,可以给他加一个边框,这样可以确定是没有找到资源还是布局错误,我非常意外的发现只需要设置borderWidth为0就可以让图片显示出来。(改:比较大的图片有时候都不会显示出来,这就很尴尬了)

13.在设置react-native-swiper的点的样式的时候,不能传入函数返回值,需要直接传入一个Element对象,我不知道是哪里出了错:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Dot extends Component{
render(){
return(
<View style={{
backgroundColor: 'rgba(0,0,0,.2)',
width: 2,
height: 2,
borderRadius: 10,
marginLeft: 3,
marginRight: 3,
marginTop: 3,
marginBottom: 3
}}/>
);
}
}

如果我传入Dot.render()系统会报错,提示我是一个空元素,但是直接传入View就可以成功。

14.RN中元素默认的定位方式是 relative ,并且只有relative 和 absolute 两种定位方式。如果为组件加上 position:absolute ,它将会以inline的方式渲染在页面上。并且脱离正常文档流。也就是视觉上会被后面的组件覆盖,但不能通过zIndex方式调整。嵌套的Text不可用。

15.在给控件写style属性的时候,尽量用一个View包起来,因为有很多控件的style样式无法应用,浪费调试时间。

16.在调试代码的时候,要时刻注意this的指向,比如在下面的例子中,console.log中的this就指向了Drawer,变成了调用他的this了,renderNavigationView`有毒,他会调用传入的函数方法,并且把里面的this指针强行指向自己。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class main extends Component{
navigationView(){
console.log(this);
}
render(){
return(
<DrawerLayoutAndroid
renderNavigationView={this.navigationView}
>
.............
</DrawerLayoutAndroid>
);
}
}

17.如果将文字放在图片下方,但是只设置了图片的宽度,可能会导致文字不会紧贴图片下方的问题,如下就会出现此种问题,这个时候设置上height就行了。

1
2
3
4
<View style={{justifyContent:"center",alignItems:"center"}}>
<Image source={require('')} style={{width:120}} resizeMode="contain"/>
<Text>没有找到你的缓存哟</Text>
</View>

18.hairlineWidth:CallExpression用来定义当前平台最细的宽度。该属性用来设置边框或者两个组件之间的分割线。

1
2
3
4
{
borderBottomColor: '#bbb',
borderBottomWidth: StyleSheet.hairlineWidth
}

会根据当前平台信息,自动转换成一根很细的线。

19.推库上这篇文章《React Native 触摸事件处理详解》 关于手势处理的,非常有用!!!!!!!

chrome开发者工具中的HTML与CSS调试

开发者工具


在日常的开发过程中,我们可能需要不断的调试我们的代码,这个时候,如果一直在编辑器和网页中进行切换的话,会极大的降低我们的效率,所以我们要学会使用Chrome的开发者工具来进行调试。(图看不清楚的就拖出来看)

通过点击网页上的空白部分调出菜单,选择检查进入工具或者使用其他快捷键
下图就是默认的开发者工具(所在位置可以点击右上方的三个点进行设置)

图片无法显示请联系管理员

概述

在1的中,右边按钮的作用是进入响应式设计模式,我们可以在这里对不同分辨率的机器进行模拟,这个时候显示内容的部分会变得很小,我们可以在上方输入需要的分辨率数据,或者是直接点击横条上的默认设置进行设计。

enter image description here

在调试HTML和CSS的过程中我们基本只需要使用第一个Element的窗口,后面的几个Tab我们以后学习JS和调试性能等的时候会再用到。

HTML

默认显示的视图中,左边一部分是我们的HTML文档结构,我们可以点击小箭头打开节点或收缩节点,当鼠标知道某一个元素时,在网页中会将对应的部分内容进行浅蓝色背景的高亮显示(paddingmargin等是不一样的颜色),上图就是在我鼠标指到3位置时2显示的高亮效果。

与此同时,当我们需要在网页中找某一个元素,但是并不想在树中一层一层的找的时候,可以使用1左边的按钮或者使用Ctrl+Shift+C的快捷键快速的在网页中选择我们需要的节点,当点击一个高亮元素过后,会在文档中展开节点并获取焦点。

如果我们想要编辑某一个节点的内容的话,我们可以点击右键选择,Edit As HTML或者直接双击,其中的节点都会被当成普通的文本编辑,编辑完成后当作html解析。

在最下方的4是我们此时获取焦点的节点的层级结构,我们可以点击其中任意一个节点,此时选中的焦点便会跳转(所谓选中的焦点,可以理解成我们在右边显示的属性)

CSS

头部

在5这一行中,我们暂时只需要使用前面两个,第一个就是我们默认选中显示的style,当这个选项卡启动时,我们下方将显示选中节点的CSS属性,当然,这里的CSS属性都是设置值。对应的,右边的computed选项卡下就是我们每个元素css属性经过计算过后的具体值,比如我将父元素的宽度设置为1000px,子元素设置width:50%,这个时候,我们经过计算的宽度就是500px

在6的位置
1.:hover按钮可以选择节点的状态,:hoveractive等等,所以我们要观察hover状态的时候就可以解放自己的双手了,将此处的状态设置为hover的话,元素就会一直保持状态,其他的状态也是一样的。
2.第二个是进行动画调试的,我们展示不会用到
旁边的.cls可以用于给节点添加一个class,进行添加过后的class也会添加到DOM的class属性当中。
3.最右边的加号可以依据当前节点的标签和class,id等添加一个样式,注意这个样式是添加到一个叫做inspector-stylesheet的样式表中的。

主体

在7,8这一块是我们调试的重头部分,就是我们节点的css样式,从上到下优先级依次降低,第一个element.style样式就是我们在文档中设置的css,过后是我们添加的inspector-stylesheet样式,在之后的就是我们根据引用和属性覆盖的一层层css样式了,在这些样式里面,被划了横线的就是被注释掉,显示为浅灰色的就是不起作用(存在优先级更高的)的css属性,所以我们在自己写的css中注释掉的一些代码也会显示出来。

在7中,右边显示的是这个样式的来源文件和行数,我们可以通过点击大括号内的属性对他们进行编辑,也可以点击一个属性后面的空白处选择新建一行书写新的属性,在开发工作中,我们需要进行微调的时候,这样做可以极大的提高我们的工作效率。

在8中,左上角显示了一个inherit from,这就是我们从祖辈元素继承的属性,在这里更改属性的话,理所应当的也会改变祖先元素的样式。

好好学习

好好学习,天天向上,第二期调试JS的出来了会在这里加个链接的