博客列表 >JavaScript高级知识(一)

JavaScript高级知识(一)

指纹指恋的博客
指纹指恋的博客原创
2017年12月20日 22:04:52638浏览

作用域

<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的污染
*/

作用域链

QQ20171222-141204.png

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);
*/

闭包

QQ20171224-170822.png

  • 在大部分语言中,t1被调用执行,则申请内存并把其局部变量push入栈。t1函数执行完毕,内部的局部变量,随着函数的退出而销毁,因此age = 20的局部变量已经消息。

  • 但是在JS中,age = 20这个变量,却被t2捕捉,即使t1执行完毕,通过t2,依然能访问该变量

  • 像这种情况:返回的函数,并非孤立的函数,甚至把其周围的变量环境,形成了一封闭的“环境包”,共同返回,所以叫闭包。

  • 一句话概括:函数的作用域取决于声明时,而不取决于调用时!


声明:本文内容转载自脚本之家,由网友自发贡献,版权归原作者所有,如您发现涉嫌抄袭侵权,请联系admin@php.cn 核实处理。
全部评论
文明上网理性发言,请遵守新闻评论服务协议