变量作用域
var a=123
function test(){
alert(a) //undefined
var a=1
alert(a)//1
}
test()
以上例子中函数作用域会覆盖始终优先于全局作用域,所以局部变量a会覆盖掉全局作用域的a,变量提升不会提升赋值,所以第一个输出undefined,第二次输出时已经被正式定义了。
函数也是一种数据
var f = funtion(){return 1}
typeof f//'function'
- 命名符合变量命名方法,由数字、下划线、字母组成,不能以数字开头
- 匿名函数:
1)可以将匿名函数作为函数参数传递给其他函数
2)可以定义某个匿名函数执行一次性任务
1)匿名回调函数可以节省全局变量
//实现一个函数对数组中的各个元素先乘2再加1
function multi_two(a,b,c,callback){
var arr=[],i
for(i=0;i<3;i++){
arr[i]=callback(arguments[i]*2)
}
return arr
}
alert(multi_two(1,2,3,function(a){return a+1}))
2)自调函数
不会产生任何全局变量,缺点是无法重复执行,一次性
{
function (name){
alert('hello'+name)
}
}('MSR')
- 内部(私有)函数
全局函数a内部定义的局部函数b,在全局函数外不可见执行a时会执行b
functiona(){
var b = function b(){}
}
优点:有助于确保全局名字空间的纯净性
私有性,不必暴露给外部世界
闭包
- 作用域链
js不存在大括号级作用域,但有函数作用域。即变量如果在某代码块中定义,在代码块外可见;在函数内定义的变量函数外不可见。如果在函数f内定义函数n则n不仅可以访问自身作用域中的变量,还能访问其父级作用域,这就形成一条作用域链(scope chain)。 - 词法作用域
在js中,每个函数都有自己的词法作用域。每个函数在被定义(而非执行)时,都会创建一个属于自己的环境(作用域)
function f1(){
var a=1;
f2();
}
function f2(){return a};
f1();//undefined
在以上代码中,当f2被定义(而非执行)时,a是不可见的,f2只能访问自身的和全局的。此时f1和f2不存在共享的词法作用域。【自己理解:f1和f2的定义不存在父子关系,不能形成作用域链,是兄弟关系】
尽管函数在定义时会记录自身所在的环境和作用域链,但不意味着会对作用域中每个变量记录,我们可以在函数中对变量进行添加,修改,移除等操作,但函数只会看到最终状态。这意味着我们可以删除f2并定义一个新的执行体,f1也能正常工作,f1只需知道如何访问自身作用域,不需知道作用域什么时候发生了什么
var a=1;
function f1(){
return f2
}
function f2(){return a}
a=44
delete f2
var f2=function(){return a*2}
f1()();//88
-
利用闭包突破作用域链
N突破作用域链的方法:
1)闭包#1(在函数内返回一个全局函数)
function f(){
var b='b'
return function(){
return b
}
}
b;//not defined
该函数的 局部变量b在全局是不可见的,f的返回值相当于上图中的N,既可以访问f的空间也可以访问全局,所以b对它可见,因为f是全局函数,在全局空间可调用,所以讲它的返回值赋给另一个全局变量,从而生成一个可以访问f私有空间的新全局函数
var n=f()
n()//b
2)闭包#2(在函数内创建一个全局函数)
与上法类似,不再返回函数,直接在函数体内创建一个新的全局函数
var n//声明一个全局函数的占位符,不是必须的
function f(){
var b='b'
n=function(){
return b
}
}
f()//b
调用f()后,由于n的定义没有使用var语句,所以属于全局,n在f内部,可以访问到f的作用域,即使n后来升级到了全局函数也能保留对f的访问权
使用var和不使用的区别
- 在函数内部如果用var声明变量和不用时有很大差别,用var声明的是局部变量,在函数外部访问这个变量是访问不到的,没var声明的是全局变量。在函数外部是可以访问到的。
- 全局作用域内声明变量
在这里用var声明的变量我们之所以认为声明的是全局变量是因为它现在处于的作用域范围是全局,实际上它声明也是局部变量,只是现在它的局部变量是全局而已,所以就相当于起着全局变量的作用。全局作用域中不用var声明的也是全局变量,那么它俩有什么区别呢?
比较var a= 1 跟 a= 1,前者是变量声明,带不可删除属性,因此无法被删除;后者为全局变量的一个属性,因此可以从全局变量中删除
3)相关定义与闭包3#
根据目前讨论发现,如果一个函数需要在其父级函数之后返回后继续保留对父级作用域的链接的话,就要建立一个闭包。函数所绑定的是作用域本身,不是作用域中的变量或变量当前返回的值。
4)循环中的闭包
function f(){
var a=[],i;
for(i=0;i<3;i++){
a[i]=function(){
return i;
}
}
return a
}
var a=f()
console.log(a[0]())
console.log(a[1]())
console.log(a[2]())
结果都是3,创建的3个闭包都指向一个共同的局部变量i,但是闭包不会记录他们的值,他们拥有的只是i的引用,因此只能返回i的当前值。循环结束时i=3,因此三个函数都指向3。
解决方案:
function f(){
var a=[],i;
for(i=0;i<3;i++){
a[i]=(function(x){
return function(){
return x
};
})(i)
}
return a
}
var a=f()
console.log(a[0]())
console.log(a[1]())
console.log(a[2]())
不再直接创建一个返回i的函数,而是将i传递给一个自调函数,在该函数中,i被赋值给局部变量x,每次迭代中x就有不同的值了。
或者不用自调函数,在中间函数内,将i的值本地化。
function f(){
function makeClosure(x){
return function(){
return x
}
}
var a=[],i
for(i=0;i<3;i++){
a[i]=makeClosure(i)
}
return a
}
console.log(a[0]())
console.log(a[1]())
console.log(a[2]())
Getter与Setter
假设现有一个特殊区间的变量,不将其暴露在外部以免被篡改,我们需要将它保护在相关函数内部,再提供两个函数getter和setter用于赋值和访问,setter和getter在共同的函数中,并在该函数定义secret变量,使得两个函数能共享同一作用域。一切操作是通过一个匿名自调函数实现,在其中定义了全局的getValue和setValue,以此确保secret的不可直接访问性。??
var getValue,setValue
(function (){
var secret=0
getValue=function(){
return secret
}
setValue=function(v){
secret=v
}
})()
console.log(getValue());
setValue(1234)
console.log(getValue());
迭代器
闭包实现迭代器
function setup(x){
var i=0
return function(){
return x[i++]
}
}
var next=setup(['m','s','r'])
console.log(next())//m
console.log(next())//s
console.log(next())//r