可见性与生命周期,
在java script中变量的作用域有全局作用域
和函数作用域
以及ES6新增加的块级作用域
。
例如,在函数外部通过var
关键字声明的变量就是全局变量,作用域的范围也就是全局作用域,而在函数内部
通过var
声明或者let
声明的就是局部变量,作用域仅限于函数内部,在{}
内部或者流程控制语句或者循环语句内部
通过let
声明的变量作用域范围则仅限于当前作用域。函数的参数在()
内部,只能在函数内部使用,作用域范围也仅限于函数。
同时window
对象的所有属性也拥有全局作用域。
例如:
// 作用域范围
var a = 10; // 全局
function fn1(a,b){ // 函数fn1内
c = 30; // 全局
var x = 30; // 函数fn1内
function fn2(){
var s = "hello"; // 函数fn2内
console.log(x); // 30 函数内部可以访问外层的变量
}
}
for(var i =0;i<10;i++){} // 循环体内声明的计数变量i也是一个全局
console.log(i); // 10
for(let j = 0;i<10;j++){} // let 声明的计数变量j 是一个局部
console.log(j);// 出错,访问不到
- 执行环境上下文
上面我们说到了作用域,下面再来说下执行环境(execution context)。
什么是执行环境呢?
简单点说,执行环境定义了变量或者函数有权访问的其他的数据,并且也决定了他们各自的行为。需要知道的
是,每一个执行环境当中,都有着一个与之关联的变量对象(variable object),执行环境中定义的所有变量和函数都会保存在这个对象中,解析器在处理数据的时候就会访问这个内部对象。
而全局执行环境是最外层的一个执行环境,在web浏览器中最外层的执行环境关联的对象是window
,所以我们
可以这样说,所有的全局变量和函数 都是作为window对象的属性和方法创建的。
我们创建的每一个函数都有自己的执行环境,当执行流进行到函数的时候,函数的环境会被推入到一个函数执行栈
当中,而在函数执行完毕后执行环境出栈并被销毁,保存在其中的所有变量和函数定义随之销毁,控制权返回到之前的执行环境中,全局的执行环境在应用程序退出(浏览器关闭)才会被销毁。
- 作用域链
当代码在一个执行环境执行之时,会创建一个变量对象的一个作用域链(scope chain),来保证在执行环境中
,对执行环境有权访问的变量和函数的有序访问。
作用域第一个也d就是顶层对象始终是当前执行代码所在环境的变量对象(VO)。
例如:
function fn1(){}
fn1在创建的时候作用域链被添加进全局对象,全局对象中拥有所有的全局变量。
例如上面的fn1在创建的时候,所处的环境是全局环境,所以此时的this就指向window。
在函数运行过程中标识符的解析是沿着作用域链一级一级搜索的过程,从第一个对象开始,逐级向后回溯,直到找到同名标识符为止,找到后不再继续遍历,找不到就报错。
如果执行环境是函数,那么将其活动对象(activation object, AO)作为作用域链第一个对象,第二个对象是包含环境,下一个是包含环境上一层的包含环境...
也就是说所谓的作用域链,就是指具体的某个变量或者函数从其第一个对象(活动对象)一直到顶层执行环境。这中间的联系就是作用域链。
被人误解
的闭包函数
谈及闭包函数的概念,经常会有人错误的将其理解为从父上下文中返回内部函数,甚至理解成只有匿名函数才能是闭包。
而实际来说,因为作用域链,使得所有的函数都是闭包(与函数类型无关: 匿名函数,FE,NFE,FD都是闭包)。
注意:这里只有一类函数除外,那就是通过Function构造器创建的函数,因为其[[Scope]]只包含全局对象。
闭包函数的应用
闭包函数是js当中非常重要的概念,在诸多的地方可以应用到闭包,通过闭包,我们可以写出很多优秀的代码,下面是一些常见的内容:
例如:
// 数组排序
[1, 2, 3].sort(function (a, b) {
... // 排序条件
});
// map方法的应用,根据函数中定义的条件将原数组映射到一个新的数组中
[1, 2, 3].map(function (element) {
return element * 2;
}); // [2, 4, 6]
// 常用的 forEach
[1, 2, 3].forEach(function (element) {
if (element % 2 != 0) {
alert(element);
}
}); // 1, 3
例如我们常用的call和apply方法,它们是两个应用函数
,也就是应用到参数中的函数(在apply中是参数列表,在call中是独立的参数):
例如:
(function () {
alert([].join.call(arguments, ';')); // 1;2;3
}).apply(this, [1, 2, 3]);
还有最常使用的写法:
var a = 10;
setTimeout(function () {
alert(a); // 10, after one second
}, 1000);
当然,ajax的写法也就是回调函数其实本质也是闭包:
//...
var x = 10;
// only for example
xmlHttpRequestObject.onreadystatechange = function () {
// 当数据就绪的时候,才会调用;
// 这里,不论是在哪个上下文中创建
// 此时变量“x”的值已经存在了
alert(x); // 10
};
//...
当然也包括我们上边说的封装独立作用域的写法:
例如:
var foo = {};
// 初始化
(function (object) {
var x = 10;
object.getX = function _getX() {
return x;
};
})(foo);
alert(foo.getX()); // 获得闭包 "x" – 10
JSON对象和JS对象直接量
在工作当中,我们总是可以听到人说将数据转换为JSON对象,或者说把JSON对象转换为字符串之类的话,下面是关于JSON的具体说明。
JSON对象并不是java script对象字面量(Object Literals)
很多人错误的将JSON认为是java script当中的对象字面量(object Literals),原因非常简单,就是因为它们的语法是非常相似的,但是在ECMA中明确的说明了。JSON只是一种数据交互语言,只有我们将之用在string上下文的时候它才叫JSON。
序列化与反序列化
2个程序(或服务器、语言等)需要交互通信的时候,他们倾向于使用string字符串因为string在很多语言里解析的方式都差不多。复杂的数据结构经常需要用到,并且通过各种各样的中括号{},小括号(),叫括号<>和空格来组成,这个字符串仅仅是按照要求规范好的字符。
为此,我们为了描述这些复杂的数据结构作为一个string字符串,制定了标准的规则和语法。JSON只是其中一种语法,它可以在string上下文里描述对象,数组,字符串,数字,布尔型和null,然后通