作用域
<script> var a = 2; function t1(){ var b = 3; function t2(){ var c = 4; alert(a+b+c); } t2(); } t1(); </script>
在JS中,函数嵌套是非常普遍的,在函数嵌套中,对变量进行如下的寻找方式:首先在函数内寻找,寻找不到,则往函数外层寻找,……,直到全局(window)区域
声明变量var
var是在函数运行的上下文中,声明一个变量,如果不加var,则是一个赋值操作,但不要狭隘的理解为-->声明了一个全局变量【见下例】
<script> function t1(){ var a; function t2(){ a = 2; b = 4; } t2(); } t1(); console.log(a); //输出undefined console.log(b); //输出2 </script>
以window.xxx引用全局变量,寻找不到,作为某个属性不存在,返回undefined 【window.a】
直接以xxx引用某个变量,寻找不到,则是报xxx is not defined错误的 【a】
<script> <script> var str1 = "global"; var str1 = "global"; function t1(){ function t1(){ console.log(str1); //global console.log(str1); //global console.log(str2); // str is not definded console.log(str2); //undefinded str2 = "local"; var str2 = "local"; } } t1(); t1(); </script> </script>
JS代码自上而下执行,但是,JS代码在整体运行时分为【词法分析期】和【运行期】两部分
自上而下执行之前,先有一个【词法分析】过程,以上面的结果为例:
第一步:分析t1() t1(){ var str2; //分析出t1内有str2局部变量,注意此时函数未执行,因此str2的值是undefined } 第一步:执行t1() console.log(str1); //global console.log(str2); //undefinded str2 = "local"; //此时str2的值为local
词法分析
语法分析,分析3样东西:
第一步:先分析参数
第二步:在分析变量声明
第三步:分析函数声明
一个函数能使用的局部变量,就从上面的3步分析而来
具体步骤:
第一步:函数运行前的一瞬间,生成Active Object(活动对象),下称AO
第二步:把函数声明的参数,形成AO的属性,值全是undefined,如果有实参,则接收,并形成AO相应的属性的值
第三步:分析变量声明!如var age,如果AO上还没有age属性,则添加AO属性,值是undefined;如果AO上已经有age属性,则不做任何影响
第四步:分析函数声明,如 function foo(){ },则把函数赋给AO.foo属性,如果此前foo属性已经存在,则被无情的覆盖
<script> function t3(great){ var great = "hello"; alert(great); function great(){ } alert(great); } t3(null); //输出 hello hello </script> /* 分析过程: 1.形成AO = {} 2.分析形参,AO = {great:undefined},接收实参,AO = {b:null} 3.分析great变量声明,发现AO已有age属性,不做任何影响 4.分析great函数声明,AO.great = function great(){},被覆盖成函数 执行过程: great = "hello"; alert(great); alert(great); */ <script> function a(b){ alert(b); function b(){ alert(b); } b(); } a(1); </script> /* 分析过程: 1.形成AO = {} 2.分析形参,AO = {b:undefined},接收实参,AO = {b:1} 3.分析var变量声明,此函数没有var 4.分析函数声明,AO = {b:function b(){alert(b);}},被覆盖成函数 执行过程: alert(b); //function b(); //由作用域寻找到a函数中的b,即function */ <script> function a(b){ alert(b); b = function (){ alert(b); } b(); } a(1); </script> /* 分析过程: 1.形成AO = {} 2.分析形参,AO = {b:undefined},接收实参,AO = {b:1} 3.分析var变量声明,此函数没有var 4.分析函数声明,没有!b = function(){},是一个赋值,在执行期才有用 执行过程: alert(b); // 1 b = function (){ alert(b); } b(); //由作用域寻找到a函数中的b,即function */
函数声明与函数表达式
函数可以赋值给变量,也可以作为参数来传递
JS被称为披着C外衣的lisp语言,lisp是一种强大的函数式语言
function t1(){} t2 = function(){} /* 这两种方式效果是不同的: t1是函数声明,虽然全局内也得到一个t1变量,值是function 而t2只是一个赋值过程,值就是右侧表达式的返回结果,即函数 因此t1,t2两种方式在词法分析时,有着本质去区别: 前者在词法分析阶段就发挥作用,而后者,在运行阶段才发挥作用 */ (function(window,undefined){}) //内层表达式,返回值是函数,包在小括号里,当成表达式来执行 (function(window,undefined){})(window) //立即调用 /* 内层函数没有起名字,称为匿名函数,这种手法,匿名函数,立即执行,不污染全局,称为立即执行匿名函数表达式, 在第二个括号里面传入window,是为了我提升内部查找变量的速度,不传undefined,是为了安全,因为在低版本IE和火狐中, undefined可以被重新赋值,如undefined = 3,声明undefined局部变量(名字是undefined而已), 同时,又不传参,值自然是undefined,防止了外界对undefined的污染 */
作用域链
argumengs详解
是一个长得很像数组的对象
内容是函数运行时的实参列表
//arguments收集“所有”的实参,即使没有与之相对应的形参 (function(d,e,f){ //在此函数内,无法用d,e,f形参来取得“haha”,因为没有与之相应的形参,但我们可以用arguments来获取任意多个实参 console.log(arguments[3]); arguments[3] = "china"; console.log(arguments[d]); //输出 china,形参与对应的arguments单元,其实是相互映射的,互相影响 })("hello","world","!","haha") //arguments可以获取函数运行时,收到的实参个数(在多态里可以用到) (function(d,e,f){ console.log(arguments.length); //输出 3 })("hello","world","!","haha") //arguments.callee属性代表“当前运行的函数” (function(){ //不用函数名,匿名函数,立即执行,完成递归 if(n <= 1){ return 1; }else{ return n + arguments.callee(n-1); } })(100);
函数运行期内,有三个关键的对象:
AO ——> 本函数AO上没有某属性,则继续去外层函数的AO上找,直到全局对象,叫做 作用域链
arguments ——> 每个函数都有自己的callee,但不会向外层函数接着找arguments的相关属性,即不形成链
this ——> 也不形成链
this详解
在JS中函数有4种调用方式
alert(window.x); //输出 undefined function t(){ this.x = 333; } t(); alert(window.x); // 输出 333 /* 作为普通函数来调用时,this的指向window,准确的说,this为null,但被解释成window,但在ECMASCRIPT标准中, 如果this为null则解释成undefined */ //作为对象的方法来调用,this指向方法的调用者,即该对象 var obj = {xx:999,yy:888,t:function(){alert(this.xx);}}; obj.t(); //输出 99 var dog = {xx:"wangwang"}; dog.t = obj.t; dog.t() //输出 wangwang /* 作为方法调用时,this指向其调用那一刻的调用者,即母体对象,不管被调用函数,声明时属于方法还是函数, */ //函数作为构造函数调用时 //JS中没有类的概念,创建对象是用构造函数来完成,或直接用json格式来写对象 function Dog (name,age){ this.name = name; this.age = age; this.bark = function(){ alert('I am ' + this.name + '!'); }; } var dog = new Dog('huzi',2); dog.bark(); /* new Dog发生了以下几个步骤: 1.系统创建空对象{},(空对象construcor属性指向Dog函数,先别管) 2.把函数的this ——> 指向该空对象 3.执行该函数 4.返回该对象 */ function Pig(){ this.age = 99; return 'abc'; } var pig = new Pig(); //返回pig对象,因为函数作为构造函数运行时,return的值是忽略的,还是返回对象 //函数通过call,apply调用 //语法格式:函数.call(对象,参数1,参数2,……,参数N); function t (num){ alert('我的真实年龄是'+ this.age); //输出 我的真实年龄是28 alert('但我告诉别人是'+ (this.age+num)); //输出 但我告诉别人是18 } var human = {name:'lisi',age:28}; human.t = t; human.t(-10); //this指向了human,但human多了一个方法 //接下来我们不把t赋为human的属性,也能把this指向human var wangwu = {name:'wangwu',age:30}; t.call('wangwu',5); //输出 我的真实年龄是30 但我告诉别人是35 /* 解释fn.call(对象,参数1,参数2,……,参数N); 运行如下: 1.fn函数中的this ——> 指向对象obj 2.运行fn(对象,参数1,参数2,……,参数N); */
闭包
在大部分语言中,t1被调用执行,则申请内存并把其局部变量push入栈。t1函数执行完毕,内部的局部变量,随着函数的退出而销毁,因此age = 20的局部变量已经消息。
但是在JS中,age = 20这个变量,却被t2捕捉,即使t1执行完毕,通过t2,依然能访问该变量
像这种情况:返回的函数,并非孤立的函数,甚至把其周围的变量环境,形成了一封闭的“环境包”,共同返回,所以叫闭包。
一句话概括:函数的作用域取决于声明时,而不取决于调用时!