一、定义函数

在js中,函数也是对象,能赋值给变量,能当作参数,可以设置属性,和调用他拥有的方法。函数的toString()方法,会返回他完整的函数内容。他有两个定义方法,函数表达式和函数声明。当一个函数作为构造函数时,首字母写!函数声明在js运行前和变量声明一样被js提到所处作用域的最前面。函数可以定义在另一个函数的内部,也可以当作另一个的函数的return返回值。由于变量声明的hoisting特性,所以在函数中最好都在函数顶部声明有可能用到的所有变量。

函数声明: ([ ]括起来表示可有可无。)

function 函数名称 ( [ 参数 ] ){ 函数体 }

函数表达式:

function [函数名称] ([参数]) { 函数体 }

关于函数表达式,(function ([参数]) { 函数体 }([参数]))表示立即执行。eg:

(function (){
  alert("1");
}());

因为在js中函数也是对象,所以可以用构造函数来定义函数。

var f = new Function("x","y","z","return x + y - z;");
f(2,3,9);

Function可以输入任意数量的实参,最后一个字符串表示函数体。

二、函数的调用

第一种就是函数名加括号
FunctionName(),当他作为一个对象方法调用,并且返回自身,然后还可以再接着调用,这是函数的链式调用;

  1. var x = {
  2. count:1,
  3. add:function(){
  4. this.count ++;
  5. return this;
  6. }
  7. };
  8.  
  9. x.add().add().add();//x.count = 4;

第二种是用new关键字,作为构造函数条用,new FunctionName();

第三种是利用call(),apply()间接调用。他们的第一个参数都是当前函数执行上下文,也就是this,,如果第一个参数是null或者undefined,那么默认this为window。call后面的参数作为函数的实参,而apply后面的实参都放入一个数组中。

  1. function test(x,y,z){
  2. return x + y + z;
  3. }
  4.  
  5. test.call(null,1,2,3);
  6.  
  7. test.apply(null,[1,2,3]);

他的调用方式影响他的上下文this。如果就是普通函数调用,this就是window;如果是方法调用,this就是调用这个方法的对象;如果是当作构造函数调用,他会创建一个新对象,那么this就是他创建的这个新对象,如果用call或者apply间接调用,this就是他们的第一个参数。

三、函数传参

JavaScript的函数在传参这点做得很高级,但是不够严谨,太过随意。当传入个数少于函数的形参个数时,剩下的形参都是undefined,为了避免出错,最好用 || 给默认值。

而当传入个数多余形参个数时,也没有关系。虽然没法直接获得未命名值的引用,但是参数对象解决了这个问题。在函数体内,arguments指向实参对象的引用,他是一个类数组对象,从0开始,依次是传入参数的顺序,多余的为undefined。在arguments中有个callee属性,他是指当前正在执行的函数,可以调用的,当然乱用会产生死循环,不断的自己执行自己。

  1. function test(){
  2. console.log(arguments[4]);
  3. console.log(arguments.callee);
  4. return arguments[0] + arguments[1] + arguments[2];
  5. }
  6.  
  7. test(1,2,3);
  8.  
  9. var t = test;//函数作为值来使用
  10. t(1,2,3,4);

四、将函数作为命名空间

如果要写一段可复用的js代码,方便以后在其他项目中的使用,那么就应该把他放在一个函数中,因为并不能保证自己定义的变量是否和当前项目的全局变量重名。此时,这个函数名的作用就类似于命名空间,他把全局变量变成了函数的局部变量,并且可以向全局环境中隐藏一些变量。

五、函数作用域及作用域链(Scope Chain)

函数的作用域决定了参数和变量的生命周期,可见性,并且减少了命名冲突,提供了自动内存管理(GC,垃圾回收)。在一个js文件中,包括很多函数,每个函数都有自己的作用域,他们在一起就有了作用域链。内部的一个[[Scope]]属性包含了函数被创建时所处作用域中的对象集合,这个对象就是函数的作用域链。作用域链用来查找变量,需要注意的是,函数里声明变量一定要用var 否则他会变成全局变量的。例如:

  1. var window_x = "window";
  2. function test(){
  3. var parentsArg = arguments;
  4. var test_x = "test";
  5. function inner(){
  6. console.log(test_x);
  7. console.log(window_x);
  8. return parentsArg[0] + parentsArg[1] + parentsArg[2];
  9. }
  10. return inner();
  11. }

变量的查找

函数inner的作用域链可以看作 window--->test--->inner。例如查找window_x,首先查找inner的作用域是否有该变量,没有就接着作用域链上一级(test)查找,直到该标示符被找到,如果查找到window的全局作用域都没有该标示符,那么就要抛出异常了:xxx is not defined.(goolge chrome)。可以猜到,标示符所处的位置越深,他的查找会越慢,window的全局变量总是最后一个查找的。

作用域链(Scope Chain)的创建:inner为例

1.inner被创建,他的作用域链[[ scope ]]被填充test作用域中的对象集合;test被创建时,[[scope]]被填充了全局作用域的对象集合(window,document…….)。这些[[scope]]中的对象代表此函数的中可访问的数据。

2.inner被执行时,他要建立一个内部对象,称为“运行期上下文”,这个对象定义了函数执行时的环境。函数每次执行都会创建一个新的“运行期上下文”,当函数执行完毕时这个对象会自动销毁。一个运行期上下文有自己的作用域链[[scope]],scope中包括运行函数的scopeChain中包含的对象(复制过去的)。

3.上一步完成后,一个被称做“激活对象”的新对象就为运行期上下文创建好了。他包含所有可访问的局部变量,命名参数,参数集合等等。然后这个激活对象被推到作用域链的最前端,生命周期和作用域链一样。

此时,inner的作用域链包含了3个对象,inner的激活对象,outer的激活对象,window对象。

六、闭包 closure

闭包在《JavaScript权威指南》中的解释:函数对象可以通过作用域链相互关联起来,函数体内部的变量都可以保存在函数作用域内,这种特性在计算机科学文献中称为“闭包”。通俗点说,一个函数就是一个闭包。

1.闭包的作用

在js中,函数内可以随意访问全局变量,函数外想访问函数内的变量就不可以了(可以用这个特性来模仿类的私有变量)。但是闭包的特性解决了这个问题,他可以在外面访问函数内的局部变量

  1. function outer(){
  2. var outer_x = 0;
  3.  
  4. function inner(i){
  5. i = typeof i == "number" ? i : 0;
  6. outer_x += i;
  7. console.log("我要把outer_x + " + i + " = " + outer_x);
  8. }
  9.  
  10. return inner;
  11.  
  12. }
  13.  
  14. var test = outer();
  15. test(1);//我要把outer_x + 1 = 1
  16. test(2);//我要把outer_x + 2 = 3
  17. test(3);//我要把outer_x + 3 = 6
  18. test(4);//我要把outer_x + 4 = 10
  19.  
  20. function outer1(i){
  21. var outer_x = 0;
  22. i = typeof i == "number" ? i : 0;
  23. outer_x += i;
  24. console.log("我要把outer_x + " + i + " = " + outer_x);
  25. }
  26. var test1 = outer1;
  27. test1(1);//我要把outer_x + 1 = 1
  28. test1(2);//我要把outer_x + 2 = 2
  29. test1(3);//我要把outer_x + 3 = 3
  30. test1(4);//我要把outer_x + 4 = 4

在这里例子中,outer返回一个匿名的函数,这个匿名函数可以访问outer的局部变量,并且加一个指定的数字。这样可以通过外部的i来影响outer的局部变量。outer1也实现对局部变量outer_x加一个指定数字。对比一下他们的运行结果,可以发现,在有闭包的函数中,他的变量并没有被垃圾回收机制(GC)回收。那么闭包还可以用来让一些局部变量一直保存在内存中。需要小心内存泄漏。

为什么不会被GC回收?

outer函数返回了inner函数的引用给了test。inner的作用域链包括了inner,outer的激活对象,还有window对象,test在外部引用了inner,这个引用不放手,inner的作用域链就不会被GC回收,又因为inner作用域链包含了outer激活对象,那么处于outer作用域链的局部变量自然也不会被GC回收。

2.同一个作用域中的多个闭包

若同一个作用域链中有多个闭包,他们会共享同一个局部变量。并且关联到闭包的作用域链都是活动的。例如我想在一个函数中创建10个闭包。

  1. function test(){
  2. var arr = [];
  3. for(var i = 0; i < 10; i++){
  4. arr[i] = function(){
  5. return i;
  6. }
  7. }
  8. return arr;
  9. }
  10.  
  11. var arr = test();
  12. var i = 0;
  13. while(i<10){
  14. console.log( i + ":" + arr[i++]());
  15. }

可以发现返回的arr数组中,每个都是10。为什么?因为当我执行var arr = test()时,i = 10,所有闭包都会共享这个i。嵌套的函数他不会将作用域内的局部变量复制一份的。arr[i] 的i没有受到影响,是因为这个i属于test的局部变量,并且没有在嵌套函数中。怎么样才能使用闭包达到需要的效果呢?

有2种方法,第一是把变量i变成全局变量,由于这个方法会污染全局变量,并不好。

  1. var i = 0
  2. function test(){
  3. var arr = [];
  4. for(; i < 10; i++){
  5. arr[i] = function(){
  6. return i;
  7. }
  8. }
  9. return arr;
  10. }

第二种方法是让闭包函数立即执行,并且返回当前的值给外部函数的局部变量。

  1. function test(){
  2. var arr = [];
  3. for(var i = 0; i < 10; i++){
  4. arr[i] = (function(){
  5. return i;
  6. })();
  7. }
  8. return arr;
  9. }
  10.  
  11. var arr = test();
  12. var i = 0;
  13. while(i<10){
  14. console.log( i + ":" + arr[i++]);
  15. }

3.闭包的上下文this

下面的这个例子很清楚,一个函数内嵌套的函数无论在普通函数中,还是在方法中,他的上下文都是window。

  1. var name = "The Window";
  2. var myObject = {
  3.   name : "My Object",
  4.   getThis : function(){
  5. console.log("方法:" + this.name);//方法:My Object
  6.     return function(){
  7.       console.log("方法中的闭包:" + this.name); //方法中的闭包:The Window
  8.    };
  9.   }
  10. };
  11.  
  12. myObject.getThis()();
  13.  
  14. function outer(){
  15. var name = "outerName";
  16. function inner(){
  17. //return this.name //The Window
  18. return name; //outerName
  19. }
  20. return inner;
  21. }
  22. console.log("普通函数的闭包:" + outer()());

JavaScript总结2--函数的更多相关文章

  1. JavaScript权威指南 - 函数

    函数本身就是一段JavaScript代码,定义一次但可能被调用任意次.如果函数挂载在一个对象上,作为对象的一个属性,通常这种函数被称作对象的方法.用于初始化一个新创建的对象的函数被称作构造函数. 相对 ...

  2. 用javascript 写个函数返回一个页面里共使用了多少种HTML 标签

    今天我无意间看到一个面试题: 如何用javascript 写个函数返回一个页面里共使用了多少种HTML 标签? 不知你看到 是否蒙B了,如果是我 面试,肯定脑子嗡嗡的响.... 网上搜了搜也没有找到答 ...

  3. JavaScript基础学习-函数及作用域

    函数和作用域是JavaScript的重要组成部分,我们在使用JavaScript编写程序的过程中经常要用到这两部分内容,作为初学者,我经常有困惑,借助写此博文来巩固下之前学习的内容. (一)JavaS ...

  4. JavaScript 基础回顾——函数

    在JavaScript中,函数也是一种数据类型,属于 function 类型,所以使用Function关键字标识函数名.函数可以在大括号内编写代码并且被调用,作为其他函数的参数或者对象的属性值. 1. ...

  5. JavaScript正则表达式详解(二)JavaScript中正则表达式函数详解

    二.JavaScript中正则表达式函数详解(exec, test, match, replace, search, split) 1.使用正则表达式的方法去匹配查找字符串 1.1. exec方法详解 ...

  6. javascript立即执行函数

    javascript和其他编程语言相比比较随意,所以javascript代码中充满各种奇葩的写法,有时雾里看花;当然,能理解各型各色的写法也是对javascript语言特性更进一步的深入理解.  ( ...

  7. JavaScript 立即执行函数

    js中(function(){…})()立即执行函数写法理解 javascript和其他编程语言相比比较随意,所以javascript代码中充满各种奇葩的写法,有时雾里看花,当然,能理解各型各色的写法 ...

  8. 前端学习 第六弹: javascript中的函数与闭包

    前端学习 第六弹:  javascript中的函数与闭包 当function里嵌套function时,内部的function可以访问外部function里的变量 function foo(x) {   ...

  9. JavaScript学习09 函数本质及Function对象深入探索

    JavaScript学习09 函数本质及Function对象深入探索 在JavaScript中,函数function就是对象. JS中没有方法重载 在JavaScript中,没有方法(函数)重载的概念 ...

  10. JavaScript中的函数表达式

    在JavaScript中,函数是个非常重要的对象,函数通常有三种表现形式:函数声明,函数表达式和函数构造器创建的函数. 本文中主要看看函数表达式及其相关的知识点. 函数表达式 首先,看看函数表达式的表 ...

随机推荐

  1. Codeforces 628E Zbazi in Zeydabad 树状数组

    题意:一个n*m的矩阵,要么是 . 要么是 z ,问可以形成几个大z 分析:(直接奉上官方题解,我感觉说的实在是太好了) Let's precalculate the values zlij, zri ...

  2. Python脚本控制的WebDriver 常用操作 <五> 访问链接

    下面将使用webdriver来访问一个web链接 测试用例场景 测试中,经常会点击几个链接来进行操作,所以访问链接是基本的常见操作 Python脚本 from selenium import webd ...

  3. [MySQL-1] mysql error 1101 blob/text column can't have a default value

    在MySQL Query Browser上创建一个含有TEXT类型的字段,创建不成功,报错:mysql error 1101 blob/text column can't have a default ...

  4. javaScript动态给下拉列表框添加选项

    方式一: <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title></titl ...

  5. Hadoop中Combiner的作用

    1.Partition 把 Map任务输出的中间结果按 key的范围划分成 R份( R是预先定义的 Reduce任务的个数),划分时通常使用hash函数如: hash(key) mod R,这样可以保 ...

  6. NOIP2009 最优贸易

    3. 最优贸易 (trade.pas/c/cpp) [问题描述] C 国有 n 个大城市和 m 条道路,每条道路连接这 n 个城市中的某两个城市.任意两个城市之间 多只有一条道路直接相连.这 m 条道 ...

  7. Magento 重新安装的方法

    如果之前已经成功安装Magento, 不必再下载Magento进行重新安装,很多朋友删掉所有程序文件然后再上传一个magento程序包进行重新安 装, 这样做很耗时间. 其实只需把magento的根目 ...

  8. 我是如何理解Java抽象类和接口的

    在面试中我们经常被问到:Java中抽象类和接口的区别是什么? 然后,我们就大说一通抽象类可以有方法,接口不能有实际的方法啦:一个类只能继承一个抽象类,却可以继承多个接口啦,balabala一大堆,就好 ...

  9. setResult()设置无效,onActivityResult没有被调用

    情况1 呃,被坑了几个小时,后来发现,在调用setResult的时候,requestCode随便传了个Activity的RESULT_OK,而这个常量的值是-1,导致onActivityResult没 ...

  10. 编写Qt Designer自定义控件(二)——编写自定义控件界面

    接上文:编写Qt Designer自定义控件(一)——如何创建并使用Qt自定义控件 既然是控件,就应该有界面,默认生成的控件类只是一个继承了QWidget的类,如下: #ifndef LOGLATED ...