javascript函数

假设函数挂载在一个对象上,作为对象的一个属性,就称它为对象的方法。

定义一个函数

函数声明

function print(){
    // ...
}

函数表达式

var print = function (){
    // ...
};

使用Function构造函数

var add = new Function("x","y","return (x+y)");
// 相当于定义了例如以下函数
// function add(x, y) {
//   return (x+y);
// }

除了最后一个參数是add函数的“函数体”,其它參数都是add函数的參数。

假设仅仅有一个參数,该參数就是函数体。

Function构造函数能够不使用new命令。返回结果全然一样。


函数名的提升

JavaScript引擎将函数名视同变量名,所以採用function命令声明函数时。整个函数会被提升到代码头部。所以。以下的代码不会报错。

f();
function f(){}

假设是採用赋值语句定义函数。JavaScript就会报错。

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

当调用f的时候。f仅仅是被声明,还没有被赋值,等于undefined。所以会报错。

假设同一时候採用function命令和赋值语句声明同一个函数。最后总是採用赋值语句的定义。

这与变量声明提前有关。

var f = function() {
  console.log ('1');
}

function f() {
  console.log('2');
}

f()
// 1

不能在条件语句中声明函数

依据ECMAScript的规范,不得在非函数的代码块中声明函数。最常见的情况就是if和try语句。

if (foo) {
  function x() { return; }
}

try {
  function x() {return; }
} catch(e) {
  console.log(e);
}

上面代码分别在if代码块和try代码块中声明了两个函数,依照语言规范,这是不合法的。

可是,实际情况是各家浏览器往往并不报错,能够执行。

可是因为存在函数名的提升,所以在条件语句中声明函数是无效的。这是很easy出错的地方。

if (false){
  function f(){}
}

f()
// 不报错

因为函数f的声明被提升到了if语句的前面,导致if语句无效。所以上面的代码不会报错。要达到在条件语句中定义函数的目的。仅仅有使用函数表达式。

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

f()
// undefined

函数的属性和方法

name

name属性返回紧跟在functionkeyword之后的那个函数名

function f1() {}
f1.name // 'f1'

var f2 = function () {};
f2.name // ''

var f3 = function myName() {};
f3.name // 'myName'

length

length属性返回函数定义中參数的个数

function f(a,b) {}

f.length
// 2

toString

函数的toString方法返回函数的源代码


函数作用域

函数内部的变量提升

与全局作用域一样。函数作用域内部也会产生“变量提升”现象。var命令声明的变量。不管在什么位置,变量声明都会被提升到函数体的头部。

function foo(x) {
    if (x > 100) {
         var tmp = x - 100;
    }
}

上面的代码等同于

function foo(x) {
  var tmp;
  if (x > 100) {
    tmp = x - 100;
  };
}

函数本身的作用域

函数本身也是一个值。也有自己的作用域。它的作用域绑定其声明时所在的作用域。

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

function f(){
  var a = 2;
  x();
}

f() // 1

以下。函数x是在函数y体外声明的,作用域绑定外层。因此找不到函数y的内部变量a。导致报错。

var x = function (){
  console.log(a);
};

function y(f){
  var a = 2;
  f();
}

y(x)
// ReferenceError: a is not defined

參数

不管提供多少个參数(或者不提供參数),JavaScript都不会报错。被省略的參数的值就变为undefined。

须要注意的是,函数的length属性与实际传入的參数个数无关。仅仅反映定义时的參数个数。

没有办法仅仅省略靠前的參数。而保留靠后的參数。假设一定要省略靠前的參数,仅仅有显式传入undefined。

function f(a,b){
    return a;
}

f(,1) // error
f(undefined,1) // undefined

默认值

通过以下的方法,能够为函数的參数设置默认值

function f(a){
    a = a || 1;
    return a;
}

f('') // 1
f(0) // 1

这样的写法会对a进行一次布尔运算。仅仅有为true时,才会返回a。

可是,除了undefined以外,0、空字符、null等的布尔值也是false。也就是说,在上面的函数中,不能让a等于0或空字符串,否则在明明有參数的情况下,也会返回默认值。

function f(a){
    (a !== undefined && a != null)?

(a = a):(a = 1); return a; } f('') // "" f(0) // 0

传递方式

JavaScript的函数參数传递方式仅仅有传值(passes by value),这意味着,在函数体内改动參数值,不会影响到函数外部。

// 改动原始类型的參数值
var p = 2; 

function f(p){
    p = 3;
}

f(p);
p // 2

// 改动复合类型的參数值
var o = [1,2,3];

function f(o){
    o = [2,3,4];
}

f(o);
o // [1, 2, 3]

上面代码分成两段。分别改动原始类型的參数值和复合类型的參数值。两种情况下,函数内部改动參数值,都不会影响到函数外部。

再接着看

// 改动对象的属性值
var o = { p:1 };

function f(obj){
    obj.p = 2;
}

f(o);
o.p // 2

// 改动数组的属性值
var a = [1,2,3];

function f(a){
    a[0]=4;
}

f(a);
a // [4,2,3]

这是为什么呢?
复合类型的变量存储的是地址。传给函数时。会有一个副本。在函数内部。当给变量赋值时o = [2,3,4];,原来o的地址就变成指向[2, 3, 4]了。当o[0]=4;时,此时o存储的地址并未改变。

不仅仅是在函数传參这一方面。js就是这样的

var arr1 = [1, 2];
var arr2 = arr1;
console.log(arr1); // [1, 2]
console.log(arr2); // [1, 2]

arr2[0] = 0;
console.log(arr1); // [0, 2]
console.log(arr2); // [0, 2]

arr2 = [3, 4];
console.log(arr1); // [1, 2]
console.log(arr2); // [3, 4]

假设须要对某个变量达到传址传递的效果,能够将它写成全局对象的属性

var a = 1;

function f(p){
    window[p]=2;
}

f('a');

a // 2

上面代码中,变量a本来是传值传递,可是写成window对象的属性,就达到了传址传递的效果。

同名參数

假设有同名的參数。则取最后出现的那个值

function f(a, a){
    console.log(a);
}

f(1,2)
// 2

即使后面的a没有值或被省略。也是以其为准

function f(a, a){
    console.log(a);
}

f(1)
// undefined

调用函数f的时候。没有提供第二个參数,a的取值就变成了undefined。

这时。假设要获得第一个a的值,能够使用arguments对象。

function f(a, a){
    console.log(arguments[0]);
}

f(1)
// 1

arguments对象

因为JavaScript同意函数有不定数目的參数,所以我们须要一种机制。能够在函数体内部读取全部參数。这就是arguments对象的由来。

arguments对象包括了函数执行时的全部參数,arguments[0]就是第一个參数,arguments[1]就是第二个參数。依次类推。这个对象仅仅有在函数体内部,才干够使用。

arguments对象除了能够读取參数,还能够为參数赋值(严格模式不同意这样的使用方法)

var f = function(a,b) {
  arguments[0] = 3;
  arguments[1] = 2;
  return a+b;
}

f(1, 1)
// 5

能够通过arguments对象的length属性,推断函数调用时究竟带几个參数

arguments对象带有一个callee属性。返回它所相应的原函数

var f = function(one) {
  console.log(arguments.callee === f);
}

f()
// true

callee属性在某些时候会很实用,比方在匿名函数中通过callee来递归地调用自身

var factorial = function (x) {
    if (x < 1) return 1;
    return x * arguments.callee(x - 1);
}

闭包

闭包(closure)就是定义在函数体内部的函数。更理论性的表达是,闭包是函数与其生成时所在的作用域对象(scope object)的一种结合。

闭包的特点在于,在函数外部能够读取函数的内部变量

function f() {
    var v = 1;

    var c = function (){
        return v;
    };

    return c;
}

var o = f();

o();
// 1

上面代码表示。原先在函数f外部,我们是没有办法读取内部变量v的。

可是,借助闭包c。能够读到这个变量。

闭包不仅能够读取函数内部变量。还能够使得内部变量记住上一次调用时的运算结果

function createIncrementor(start) {
        return function () { 
            return start++;
        }
}

var inc = createIncrementor(5);

inc() // 5
inc() // 6
inc() // 7

马上调用的函数表达式(IIFE)

有时。我们须要在定义函数之后,马上调用该函数。这时。你不能在函数的定义之后加上圆括号,这会产生语法错误。

function(){ /* code */ }();
// SyntaxError: Unexpected token (

产生这个错误的原因是,Javascript引擎看到functionkeyword之后。觉得后面跟的是函数定义语句,不应该以圆括号结尾。

解决方法就是让引擎知道,圆括号前面的部分不是函数定义语句,而是一个表达式,能够对此进行运算。

你能够这样写:

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

// 或者

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

注意,上面的两种写法的结尾,都必须加上分号。

推而广之,不论什么让解释器以表达式来处理函数定义的方法。都能产生相同的效果。比方以下三种写法。

var i = function(){ return 10; }();

true && function(){ /* code */ }();

0, function(){ /* code */ }();

甚至像这样写

!function(){ /* code */ }();

~function(){ /* code */ }();

-function(){ /* code */ }();

+function(){ /* code */ }();

newkeyword也能达到这个效果

new function(){ /* code */ } // 注意不是Function

new function(){ /* code */ }() // 仅仅有传递參数时。才须要最后那个圆括号。

通常情况下。仅仅对匿名函数使用这样的“马上执行的函数表达式”。

它的目的有两个:一是不必为函数命名,避免了污染全局变量;二是IIFE内部形成了一个单独的作用域。能够封装一些外部无法读取的私有变量。


eval

eval命令的作用是,将字符串当作语句执行

因为eval没有自己的作用域。都在当前作用域内执行。因此可能会改动其它外部变量的值。造成安全问题。

此外,eval的命令字符串不会得到JavaScript引擎的优化,执行速度较慢,也是还有一个不应该使用它的理由。

通常情况下,eval最常见的场合是解析JSON数据字符串,正确的做法是这时应该使用浏览器提供的JSON.parse方法。


參考

http://javascript.ruanyifeng.com/grammar/function.html


原文地址:https://www.cnblogs.com/jhcelue/p/7058197.html