js函数总结

1.函数声明提升

函数声明与变量声明会被JavaScript引擎隐式地提升到当前作用域的顶部,但是只提升名称不会提升赋值部分。

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <title>函数声明提升</title>
    </head>
    <body>
    <script type="text/javascript">
        fn();//我是函数声明
        var fn=function(){
            console.log("我是函数表达式");
        };
        function fn(){
            console.log("我是函数声明");
        }
        fn();//我是函数表达式
        /*
        实际执行顺序:
        var fn;
        function fn(){
           console.log("我是函数声明");
        }
        fn();//我是函数声明
        fn=function(){
            console.log("我是函数表达式");
        };
        fn();//我是函数表达式
         */
         /*
         函数表达式最大的问题,在于js会将此代码拆分为两行代码分别执行。
         所以函数声明覆盖了变量声明
          */
    </script>
    </body>
</html>

<pre name="code" class="html">//封装函数
//1、传统方式封装
//该方式允许封装的函数"先调用,后声明",该过程称为函数的"预加载"
//预加载:程序代码没有执行之前,函数的声明先进入内存
//表面上是函数调用在前、函数声明在后,本质上是函数声明在前、调用在后
//预加载条件:"声明和调用"必须在同一个script标记里面
//原因是每个script标记是独立加载、解释和运行的
//getInfo();//也可以成功调用
function getInfo(){
console.log('beijing');
}
getInfo();
//js中一切都是对象
//函数也是对象(数据类型的一种)
//2、变量赋值方式声明函数,就是函数表达式(不支持函数预加载)
//getWeather();//TypeError: getWeather is not a function
var getWeather=function(){
console.log('晴朗');
}

getWeather();//晴朗

 

2.函数-callee

arguments.callee是一个指向正在执行的函数的指针

function jisuan(n){
            if(n<=1){
                return 1;
            }else{
                //return n*jisuan(n-1);//TypeError: jisuan is not a function
                return n*arguments.callee(n-1);
            }
        }
        var js=jisuan;
        jisuan=null;
        console.log(js(5));//120

3.caller属性

caller属性保存着调用当前函数的函数引用

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <title>caller属性</title>
    </head>
    <body>
    <script type="text/javascript">
        function outer(){
            inner();
        }
        function inner(){
            //inner.caller指向outer
            console.log(inner.caller);// outer()
            //alert(inner.caller);//显示outer函数的源代码
            //这样写实现了更松散的耦合
            alert(arguments.callee.caller);//显示outer函数的源代码
        }
        outer();
    </script>
    </body>
</html>

4.匿名函数自执行

//变量赋值声明一个函数
   var getInfo=function(){
    console.log("Today is sunshine");
   }
   //var getInfo="okok";//使用一个同名的getInfo变量把上面的函数给污染覆盖
   getInfo();//Today is sunshine

   //使得以下匿名函数发生执行
   //特点:程序代码没有停顿,立即执行
   //好处:可以避免变量污染
   (function(){
    console.log("I am no name function");
   })();//I am no name function

5.arguments

arguments属性是一个类数组对象,保存着传入函数的所有参数,主要用途就是保存函数参数
arguments有一个callee属性,该属性是一个指针,指向拥有arguments属性的函数

//arguments关键字 在函数内部可以接受传递进来的实参
    //可以通过数字下标形式来访问各个实参信息
    //其length属性可以获得实参个数
    function getInfo(){
         var num=arguments.length;
        //根据传递参数的个数做灵活处理
        if(num==0){
             console.log('个人信息');
        }else if(num==1){
            console.log('名字:'+arguments[0]);
        }else if(num==2){
           console.log('名字:'+arguments[0]+' age:'+arguments[1]);
        }else if(num==3){
         console.log('名字:'+arguments[0]+' age:'+arguments[1]+' job:'+arguments[2]);
        }
        //console.log('名字:'+arguments[0]+' age:'+arguments[1]+' job:'+arguments[2]);
        //console.log(arguments);
    }
    //同一个函数调用的时候,由于传递不同个数的实参,最终导致的结果也不一样
    //其实是模仿方法重载(定义了许多名称一致的方法,这些方法的参数个数或者参数类型不一样
    //这些方法就构成了重载)
    //在php和js中没有重载
    //
    //重写:子类的同名方法去覆盖父类的同名方法
    //重载:一个类里面有许多同名的方法
    getInfo();
    getInfo('liujie');
    getInfo('liujie',23);
    getInfo('liujie',23,'student');

6.apply和call

apply()和call()方法。这两个方法的用途都是在特定的作用域中调用函数,实际上等于设置函数体内this对象的值。

apply()方法接收两个参数:一是在其中运行函数的作用域;二是参数数组。第二个参数可以是Array的实例,也可以是arguments对象。
apply()和call()方法的作用相同,它们的区别仅在于接收参数的方式不同。对于call()方法而言,第一个参数this值没有变化,变化的是其余参数都是直接传递给函数,参数必须逐个列举出来。

第一个参数传null或undefined时,将是JS执行环境的全局变量。浏览器中是window,其它环境(如node)则是global。

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <title>apply和call</title>
    </head>
    <body>
    <script type="text/javascript">
         /*apply()和call()方法真正强大的地方是能够扩充函数赖以运行的作用域
         */
        var color="red";
        var o={color:"blue"};
        function sayColor(){//全局函数
            console.log(this.color);
        }
        sayColor();//red  //这里在全局环境调用,this.color会转化为window.color
        sayColor.call(this);//red   显示的在全局作用域中调用函数
        sayColor.call(window);//red  显示的在全局作用域中调用函数
        sayColor.call(o);//blue  使用call()方法将函数内部的this指向了对象o
    </script>

    </body>
</html>

7.bind方法

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <title>bind方法</title>
    </head>
    <body>
    <script type="text/javascript">
    /*
     bind方法的主要作用是将函数绑定至某个对象
     */
    function f(y){//待绑定的函数
        return this.x+y;
    }
    var o={x:1};//将要绑定的对象
    var g=f.bind(o);//通过调用g(x)来调用o.f(x)
    console.log(g(2));//3

    var color="red";
    var o={color:"blue"};
    function sayColor(){
        console.log(this.color);//blue
    }
    var objectSayColor=sayColor.bind(o);
    //sayColor()调用bind()并传入对象o,创建了objectSayColor()函数
    //objectSayColor()函数的this值等于o
    objectSayColor();
    </script>
    </body>
</html>

8.私有变量

严格来讲,JavaScript中没有私有成员概念, 所有对象属性都是公有的。不过, 倒是有一个私有变量的概念。任何在函数中定义的变量, 都可以认为是私有变量, 因为外部不能访问它们。 

私有变量包括:函数参数、局部变量以及函数内部定义的其他函数。

function fn(a,b){
	var sum = a+b;
	return sum;
}

在上面代码中包含了3个私有变量a,b和sum.在函数内部都可以访问它们,但是在函数外部无法访问。

但是, 我们可以通过在函数内部创建一个闭包, 那么闭包通过自己的作用域链也可以访问这些变量, 而利用这一点, 就可以创建用于访问私有变量的公有方法.我们把有权访问私有变量和私有方法的公有方法称为特权方法.

第一种,在构造函数中定义特权方法:

function MyObject(){
         var privateVariable = 10 ;//私有变量
           function privateFunction() {
              return false ;
           }
           this.publicMethod = function() { // 特权方法
                     privateVariable++;
                     return PrivateFunction();
           } ;
   }
这个模式在构造函数内部定义了所有私有变量和函数。然后,又创建了能够访问这些私有成员的特权方法。能够在构造函数中定义特权方法,是因为 特权方法作为闭包有权访问在构造函数中定义的所有变量和函数

在上面这个例子中,私有变量privateVariable和私有函数privateFunction只能通过特权方法publicMethod()来访问。在创建了MyObject的实例后,除了使用publicMethod()方法外,没有其他途径可以访问私有变量privateVariable和私有函数privateFunction.

利用私有变量和特权方法可以隐藏那些不应该被直接修改的数据。

另一种方法:静态私有变量

(function(){
        //私有变量和私有函数
        var privateVariable = 10;
        function privateFunction(){
            return false;
        }
        //构造函数
        MyObject = function(){};
        //公有特权方法
        MyObject.prototype.publicMethod = function(){
            privateVariable++;
            return privateFunction();
        };
      }
    )();//匿名函数自执行

通过在私有作用域中定义私有变量或函数,同样也可以创建特权方法。

上面的例子中就创建了一个私有作用域,并在其中封装了一个构造函数及相应的方法。在私有作用域中,首先定义了私有变量和私有函数,然后又定义了构造函数及其公有方法。公有方法是在原型上定义的,这一点体现了典型的原型模式。

注意:这个模式在定义构造函数时并没有使用函数声明,而是使用了函数表达式。函数声明只能创建局部函数,但那并不是我们想要的。出于同样的原因(不想创建局部函数),我们在声明MyObject时并没有使用var关键字。

记住:初始化未经声明的变量,总是会创建一个全局变量。这里MyObject就成了一个全局变量,能够在私有作用域之外被访问到。但也要注意,在严格模式下给未经声明的变量赋值会导致错误。

Demo1:

function Person(name){
        //在构造函数内部定义了两个特权方法:getName()和setName()。这两个方法都可以在构造函数外部使用,而且都有权访问私有变量name
        this.getName=function(){
            return name;
        };
        this.setName=function(value){
            name=value;
        };
    }
    var person1=new Person("liujie");
    console.log(person1.getName());//liujie
    person1.setName("liujiejie");
    console.log(person1.getName());//liujiejie

Demo2:

<script type="text/javascript">
		(function(){
			var name = "";
			Person = function(value){
				name = value;
			}
			Person.prototype.getName = function(){
				return name;
			}
			Person.prototype.setName = function(value){
				name = value;
			}
			var person1 = new Person("lisi");
			console.log(person1.getName());//lisi
			person1.setName("wangwu");
			console.log(person1.getName());//wangwu
			var person2 = new Person("lisisi");
			console.log(person2.getName());//lisisi
			console.log(person1.getName());//lisisi
		})();
	</script>

这个例子中的Person构造函数与getName()和setName()方法一样,都有权访问私有变量name.在这种模式下,变量name就变成了一个静态的、由所有实例共享的属性。也就是说,在一个实例上调用setName()会影响所有实例。而调用setName()或新建一个Person实例都会赋予name属性一个新值。结果就是所有实例都会返回相同的值。

构造函数模式的缺点:针对每个实例都会创建同样一组新方法,而使用静态私有变量来实现特权方法可以避免这个问题。

通过创建静态私有变量会因为使用原型而增加代码复用性,但是每个实例都没有自己的私有变量。

9.块级作用域

 function outputNumbers(count){
        //由于JS中没有块级作用域,所以这里的i实际是在包含函数outputNumbers创建的,而不是在块语句中创建的
        //在java、C++等语言中,变量i只会在for循环的块语句中有定义,循环一旦结束,变量i就会被销毁
        //而在js中,变量i是定义在outputNumbers()函数的活动对象中的,因此,从它开始有定义起,就可以在函数内部随处访问它
        for(var i=0;i<count;i++){
            console.log(i);//0、1、2、3、4
        }
        console.log("块语句外:"+i);//5
    }
    outputNumbers(5);

10.模仿块级作用域

 /*
    匿名函数可以用来模仿块级作用域

    (function(){
        //这里是块级作用域,通常称为私有作用域
        //将函数声明放在圆括号中,表示它是一个函数表达式,而紧随其后的另一对圆括号会立即调用这个函数
    })();
     */
    function outputNumbers(count){
        (function(){
            //这里在for循环的外部插入了一个私有作用域,在匿名函数中定义的任何变量,都会在执行结束时被销毁。因此,变量i只能在循环中使用,使用后即被销毁。
                for(var i=0;i<count;i++){
                    console.log(i);//0、1、2、3、4
                }
        })();
        console.log(i);//ReferenceError: i is not defined
    }
    outputNumbers(5);

11.静态方法

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <title>静态方法</title>
    </head>
    <body>
    <script type="text/javascript">
        function Person(name){
            this.name=name;
            this.sayName=function(){
                console.log(this.name);
            }
        }
        Person.sayAge=function(){//静态方法
                console.log(1);
            };
        var person1=new Person('lisi');
        person1.sayName();//lisi
        Person.sayAge();//1
    </script>
    </body>
</html>

静态方法可以通过构造函数名来调用

12.用apply改变函数作用域

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <title>用apply改变函数作用域</title>
    </head>
    <body>
    <script type="text/javascript">
    /*
    apply、call、bind都有个作用就是改变作用域,这里用apply将foo函数的作用域指向obj对象,同时传入参数。
再简单分析一下bind函数内部的嵌套,执行bind函数的时候返回的是一个匿名函数,所以执行bar(3)的时候实际上是执行的bind内部的匿名函数,返回的是之前传入的foo函数的执行结果。
函数没有返回值的情况下默认返回undefined。
     */
        function foo(somthing){
            console.log("一:"+this.a, somthing);//一:2 3
        }
        function bind(fn, obj){
            return function(){
                return fn.apply(obj, arguments);
            }
        }
        var obj = {
            a:2
        }
        var bar = bind(foo, obj);//foo中的this指向了obj
        /*
        bar=function(){
                return fn.apply(obj, arguments);
        }
         */
        var b = bar(3);
        console.log("二:"+b);//二:undefined
    </script>
    </body>
</html>


this对象

this对象是在运行时基于函数的执行环境绑定的, 在全局函数中,this等于window,在函数被作为某个对象的方法调用时,this等于那个对象。不过, 匿名函数的执行环境具有全局性, 因此 this 对象通常指向 windows.
<script type="text/javascript">
var name = "lisi";
var obj = {
	name:"wangwu",
	getName:function(){
		return function(){
			return this.name;
		}
	}
};
console.log(obj.getName()());//lisi
</script>
在这个例子中,先创建了全局变量 name, 又创建了一个包含 name 属性的对象, 这个对象还包含一个方法getName(), 它返回一个匿名函数, 而匿名函数又返回 this.name
为什么上面的结果是lisi,而不是wangwu?
总结:因为闭包会将它所在的函数的作用域链全部保存起来, 所有就有类似的事情, 闭包->外部函数->再外部函数->全局, 这是闭包的作用域链, 因为闭包全部保存了, 而又由于闭包只能取得以上作用域链的最后值, 所以当你定义的变量名字 name 与环境变量同名时, 闭包要搜索到作用域链最后的这个 name, 所以就后这个例子就返回了全局 name 属性.
前面曾经提过, 每个函数在被调用时, 其活动对象都会自动取得两个特殊变量 this 和 arguments, 内部函数在搜索这两个变量时, 只会搜索到活动对象为止, 因此永远不可能访问到外部函数中的这两个变量. 不过, 把外部作用域中的 this对象保存在一个闭包能够访问的到的变量里, 就可以让闭包访问到该变量了.

<script type="text/javascript">
var name = "lisi";
var obj = {
	name:"wangwu",
	getName:function(){
		var that = this;
		return function(){
			return that.name;
		}
	}
};
console.log(obj.getName()());//wangwu
</script>
之所以这个会显示我们想要的结果, 是因为在这个作用域链上没有 that 同名的属性, 所以当闭包搜索 that 属性时, 这个 Object 里的 that 就是最顶端了, 换句话说是最后了, 所以自然就要显示这个 that 的结果了.



评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值