JavaScript基础篇(四)— — 函数

时间:2022-04-27 17:14:48

一、函数定义

函数重复声明,后一次的函数声明会覆盖了前面一次

  • 函数声明语句
function plus(x ,y) {
}

声明式会让函数前置,所以在声明函数之前调用它也是可以调用成功的
* 函数定义表达式

var plus = function (x, y) {
}
  • Function构造函数
var add = new Function('x', 'y', 'return (x + y)' );
// 等同于
function add(x, y) {
  return (x + y);
}

二、函数调用

  • 作为函数调用
function func(){};
func();
  • 作为方法调用
obj = { };
obj.funX = function() {};
obj.funX();
  • 通过call和apply间接调用函数(改变this)
      call 和 apply带有多个参数,call和apply把当前函数的this指向第一个参数给定的函数或对象中,并传递其余所有的参数作为当前函数的参数。
var O = function () {
    this.foo  = 'hello';
    this.hello = function () {
        return 'world';
    }
};

var fn = function () {
    console.log('call', this);
};

var o = new O();

fn.call(o);//此时fn的this指向o

 call和apply的不同之处,在于call传递的参数是作为arguments依次传入的,例如:
fn.call(o, 1, 2, 3);
而apply传递的参数是以一个数组的方式传入的,例如:
fn.apply(o, [1, 2, 3]);

三、参数

 1、当传入参数少于函数声明的参数时,留空的参数的值是undefined。
 2、JavaScript允许传入参数的个数大于声明时制定的参数个数。可以用arguments来访问这些参数

function f(){
    for(var i = 0; i < arguments.length ; i++) {
        console.log(arguments[i]);
    }
}
f(1,2,3,4,5,6);//打印出1,2,3,4,5,6

 3、默认值

function f(a){
  (a !== undefined && a !== null) ? a = a : a = 1;
  return a;
}

四、作为值的函数

javascript中的函数可以作为值来传递

function square(x) {
    return x * x;
}
var s = square;
s(4);

五、函数名的提升

f();
var f = function (){};
// TypeError: undefined is not a function

上面的代码等同于下面的形式。

var f;
f();
f = function () {};

第二行中调用f的时候,f只是被声明了,还没有被赋值,等于undefined,所以会报错。

六、函数作用域

 Javascript只有两种作用域:一种是全局作用域,变量在整个程序中一直存在,所有地方都可以读取;另一种是函数作用域,变量只在函数内部存在。
  1、函数内部定义的变量,会在该作用域内覆盖同名全局变量。

var v = 1;
function f(){
  var v = 2;
  console.log(v);
}
f() // 2
v // 1

  2、对于var命令来说,局部变量只能在函数内部声明,在其他区块中声明,一律都是全局变量

if (false) {
  var x = 5;
}
console.log(x);  // 5

  3、函数本身的作用域
 函数本身也是一个值,也有自己的作用域。它的作用域与变量一样,就是其声明时所在的作用域,与其运行时所在的作用域无关。

var a = 1;
var x = function () {
  console.log(a);
};
function f() {
  var a = 2;
  x();
}
f() // 1

上面代码中,函数x是在函数f的外部声明的,所以它的作用域绑定外层,内部变量a不会到函数f体内取值,所以输出1,而不是2。

  很容易犯错的一点是,如果函数A调用函数B,却没考虑到函数B不会引用函数A的内部变量。

var x = function () { console.log(a); };
function y(f) { var a = 2; f(); } y(x) // ReferenceError: a is not defined

上面代码将函数x作为参数,传入函数y。但是,函数x是在函数y体外声明的,作用域绑定外层,因此找不到函数y的内部变量a,导致报错。

  同样的,函数体内部声明的函数,作用域绑定函数体内部。

function f1() {
  var x = 1;
  function f2() {
    console.log(x);
  }
  return f2;
}
var x = 2;
var ff = f1();
ff() // 1

上面代码中,函数f1内部声明了一个函数bar,bar的作用域绑定foo。当我们在foo外部取出bar执行时,变量x指向的是foo内部的x,而不是foo外部的x。正是这种机制,构成了下文要讲解的“闭包”现象。

七、闭包

《学习Javascript闭包(Closure)》
接上面的代码
闭包就是函数f2,即能够读取其他函数内部变量的函数。由于在JavaScript语言中,只有函数内部的子函数才能读取内部变量,因此可以把闭包简单理解成“定义在一个函数内部的函数”。闭包最大的特点,就是它可以“记住”诞生的环境,比如f2记住了它诞生的环境f1,所以从f2可以得到f1的内部变量。在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。

闭包的最大用处有两个,一个是可以读取函数内部的变量,另一个就是让这些变量始终保持在内存中,即闭包可以使得它诞生环境一直存在。请看下面的例子,闭包使得内部变量记住上一次调用时的运算结果。

function createIncrementor(start) {
  return function () {
    return start++;
  };
}
var inc = createIncrementor(5);
inc() // 5
inc() // 6
inc() // 7

上面代码中,start是函数createIncrementor的内部变量。通过闭包,start的状态被保留了,每一次调用都是在上一次调用的基础上进行计算。从中可以看到,闭包inc使得函数createIncrementor的内部环境,一直存在。所以,闭包可以看作是函数内部作用域的一个接口。inc始终在内存中,而inc的存在依赖于createIncrementor,因此也始终在内存中,不会在调用结束后,被垃圾回收机制回收。
闭包的另一个用处,是封装对象的私有属性和私有方法。

function Person(name) {
  var _age;
  function setAge(n) {
    _age = n;
  }
  function getAge() {
    return _age;
  }

  return {
    name: name,
    getAge: getAge,
    setAge: setAge
  };
}
var p1 = person('张三');
p1.setAge(25);
p1.getAge() // 25

闭包应用场景
JavaScript基础篇(四)— — 函数
封装
JavaScript基础篇(四)— — 函数
闭包缺点
1、空间浪费 2、内存泄漏 3、性能消耗
解决方法是,在退出函数之前,将不使用的局部变量全部删除。

八、立即调用的函数表达式(IIFE)

只对匿名函数使用这种“立即执行的函数表达式”。它的目的有两个:一是不必为函数命名,避免了污染全局变量;二是IIFE内部形成了一个单独的作用域,可以封装一些外部无法读取的私有变量。

(function() { 
    /* code */ 
}());
// 或者
(function() { 
    /* code */ }
)();

参考链接:
1、http://javascript.ruanyifeng.com/grammar/function.html
2、http://pij.robinqu.me/JavaScript_Core/JavaScript_Basics/Function.html