【JavaScript】 函数

函数

1.函数简介

经由过程函数能够封装恣意多条语句,而且能够在任何处所、任何时刻挪用。

ECMAScript中的函数运用function关键字来声明,后跟一组参数以及函数体,这些参数在函数体内像部分变量一样事情。

function functionName(arg0, arg1....argN) {
  statements
}

函数挪用会为形参供应实参的值。函数运用它们实参的值来盘算返回值,称为该函数挪用表达式的值。

function test(name){
    return name;
}
test('tg');

在上面的例子中,name就是形参,挪用时的’tg’就是实参。

除了实参以外,每次挪用还会具有另一个值—本次挪用的上下文—这就是this关键字的值。

我们还能够经由过程在函数内增加return语句来完成返回值。

注重:碰到return语句时,会马上退出函数,也就是说,return语句背面的语句不再实行。

function test(){
  return 1;
  alert(1);  //永久不会被实行
}

一个函数中能够包括多个return语句,而且return语句能够不带有任何返回值,最终将返回undefined。

function test(num){
  if(num > 2){
   return num;
  }else{
    return ;
  }
}
test(3);  // 3
test(1);  //undefined

假如函数挂载在一个对象上,将作为对象的一个属性,就称它为对象的要领。

var o = {
  test: function(){}
}

test()就是对象o的要领。

2、函数定义(声明)

JavaScript有三种要领,能够定义一个函数。

(1)function敕令

function name() {}

name是函数称号标识符。函数称号是函数声明语句必须的部份。不过关于函数表达式来讲,称号是可选的:假如存在,该名字只存在于函数体内,并指向该函数对象本身。

圆括号:圆括号内可安排0个或多个用逗号离隔的标识符组成的列表,这些标识符就是函数的参数称号。 花括号:可包括0条或多条JavaScript语句。这些语句组成了函数体。一旦挪用函数,就会实行这些语句。

(2)函数表达式

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

采纳函数表达式声明函数时,function敕令背面不带有函数名。假如加上函数名,该函数名只在函数体内部有用,在函数体外部无效。

(3)Function()

Function()函数定义还能够经由过程Function()组织函数来定义

var f=new Function('x','y','return x+y');

等价于

var f=function(x,y){
  return x+y;
}

除了末了一个参数是函数体外,前面的其他参数都是函数的形参。假如函数不包括任何参数,只须给组织函数简朴的传入一个字符串—函数体—即可。 不过,Function()组织函数在现实编程中很少会用到。

注重点:假如同一个函数被屡次定义(声明),背面的定义(声明)就会掩盖前面的定义(声明)

function f(){
 console.log(1);
}
f()  //1

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

函数能够挪用本身,这就是递归(recursion)

function f(x){
  if(x>2){
    console.log(x);
    return f(x-1);
  }else{
    return 1;
  }
}
f(4); 
// 4
//3

不能在前提语句中声明函数 (在ES6中的块级作用域是许可声明函数的。)

3、函数定名

任何正当的JavaScript标识符都能够用做一个函数的称号。

函数称号平常是动词或以动词为前缀的词组。 平常函数名的第一个字符为小写。当函数名包括多个单词时,可采用下划线法,比方:like_this();也能够采用驼峰法,也就是除了第一个单词以外的单词首字母运用大写字母,比方:likeThis();

4、被提早

就像变量的“被提早”一样,函数声明语句也会“被提早”到外部剧本或外部函数作用域的顶部,所以以这类体式格局声明的函数,能够被在它定义之前涌现的代码所挪用。

f()
function f(){}

实在JavaScript是如许诠释的:

function f(){}
f()

注重:以表达式定义的函数并没有“被提早”,而是以变量的情势“被提早”。

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

变量现实上是分为声明,赋值两部份的,上面的代码等同于下面的情势

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

挪用f的时刻,f只是被声清楚明了,还没有被赋值,即是undefined,所以会报错。

5、嵌套函数

JavaScript的函数能够嵌套在其他函数中定义,如许它们就能够接见它们被定义时所处的作用域中的任何变量,这就是JavaScript的闭包。

function test(){
  var name = 'tg';
  function test2(){
    var age = 10;
    console.log(name);  // "tg"
  }
  console.log(age);  // Uncaught ReferenceError: age is not defined
}
test();

从上面的例子可得,test2()能够接见name,然则假如在test()内,test2()外接见age,就会报错。

6、函数挪用

组成函数主体的JavaScript代码在定义时并不会实行,只需挪用该函数,它们才会实行。有4种体式格局挪用JavaScript函数:

  • 作为函数

  • 作为要领

  • 作为组织函数

  • 经由过程它们的call()和apply()要领间接挪用

6.1函数挪用

函数能够经由过程函数名来挪用,后跟一对圆括号和参数(圆括号中的参数假如有多个,用逗号离隔)

function test(){}
test()

6.2要领挪用

var o = {
 f: function(){}
}
o.f();

6.3组织函数挪用

假如函数或许要领挪用之前带有关键字new,它就组成组织函数挪用。 通常没有形参的组织函数挪用都能够省略圆括号(但不引荐)。

var o=new Object();
var o=new Object;

7、函数的实参和形参

7.1 可选形参

在ECMAScript中的函数在挪用时,通报的参数可少于函数中的参数,没有传入参数的定名参数的值是undefined。
为了坚持好的适应性,平常应该给参数给予一个合理的默认值。

function go(x,y){   
  x = x || 1;   
  y = y || 2;  
}

注重:当用这类可选实参来完成函数时,须要将可选实参放在实参列表的末了。那些挪用你的函数的顺序员是没法省略第一个参数并传入第二个实参的。

7.2 实参对象

当挪用函数时,传入的实参个数凌驾函数定义时的形参个数时,是没有办法直接取得未定名值的援用。 这时候,标识符arguments涌现了,其指向实参对象的援用,实参对象是一个类数组对象,能够经由过程数字下标来接见传入函数的实参值,而没必要非要经由过程名字来获得实参。

function go(x){   
  console.log(arguments[0]);   
  console.log(arguments[1]);  
}  
go(1,2);  
//1
//2

arguments有一个length属性,用以标识其所包括元素的个数。

function f(x){
  console.log(arguments.length);
}
f(1,2)  // 2

注重:arguments并不是真正的数组,它是一个类数组对象。每一个实参对象都包括以数字为索引的一组元素以及length属性。

经由过程实参名字来修正实参值的话,经由过程arguments[]数组也能够猎取到变动后的值。

在函数体内,我们能够经由过程arguments对象来接见这个参数类数组,我们能够运用方括号语法接见它的每一个参数(比方arguments[0]),它还有一个length属性,示意通报进来的参数个数。

arguments类数组中每一个元素的值会与对应的定名参数的值坚持同步,这类影响是单向的,也能够如许说,假如是修正arguments中的值,会影响到定名参数的值,然则修正定名参数的值,并不会转变arguments中对应的值。

function f(x){   
  console.log(x);    // 1
  arguments[0]=null;   
  console.log(x);    // null
}

f(1);

在上面的例子中,arguments[0]和x指代同一个值,修正个中一个的值会影响到另一个。 注重:假如有同名的参数,则取末了涌现的谁人值。

function f(x,x){
 console.log(x);
}
f(1,2)  // 2

callee和caller属性

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

在一个函数挪用另一个函数时,被挪用函数会自动天生一个caller属性,指向挪用它的函数对象。假如该函数当前未被挪用,或并不是被其他函数挪用,则caller为null。

再次提示,arguments并不是真正的数组,它只是类数组对象(有length属性且可运用索引来接见子项)。但我们能够借助Array类的原型对象的slice要领,将其转为真正的数组:

Array.prototype.slice.call(arguments, 0);

//更简约的写法

[].slice.call(arguments, 0);

7.3 按值传参

ECMAScript中一切函数的参数都是按值通报的。也就是说,把函数外部的值复制给函数内部的参数,就和把值从一个变量复制到另一个变量一样。

在向参数通报基础范例的值时,被通报的值会被复制给一个部分变量(即定名参数,或许用ECMAScript的观点来讲,就是arguments对象中的一个元素。)

例子:

var num = 1;
function test(count){
  count += 10;
  return count;
}
var result = test(num);
console.log(num);  // 1
console.log(result);  // 11

在上面的例子中,我们将num作为参数传给了test()函数,即count的值也是1,然后在函数内将count加10,然则由于通报的只是num的值的一个副本,并不会影响num,count和num是自力的,所以末了num的值依旧是1.

在向参数通报援用范例的值时,会先把这个值在内存中的地点复制给一个部分变量,若部分变量变化,则部分变量和复制给部分变量途径的全局变量也会发作转变。

function test(obj){
  obj.name = 'tg';
}
var person = new Object();
test(person);
console.log(person.name);  // "tg"

然则,假如部分变量指向了一个新的堆内地点,再转变部分变量的属性时,不会影响全局变量。

看下面的例子:

function test(obj){
  obj.name = 'tg';
  obj = new Object();
  obj.name = 'tg2';
}

var person = new Object();
test(person);
console.log(person.name);  // "tg"

在上面的例子中,全局的person和函数内部分的obj在初始通报时,二者指向的是内存中的同一个地点,然则,当在函数内建立了一个新的对象,并赋值给obj(赋值的是新对象的地点)。这个时刻,obj指向的就不在是全局对象person,而是指向了新对象的地点,所以给obj增加属性name时,全局对象person的属性不会被转变。

关于上面的例子中的obj,也能够如许说,一旦obj的值发作了变化,那末它就不再指向person在内存中的地点了。

8、将对象属性用做实参

当一个函数包括凌驾三个形参时,要记着挪用函数中实参的准确递次是件让人头疼的事。不过,我们能够经由过程名/值对的情势传入参数,如许就不须要管参数的递次了。

function f(params){ 
  console.log(params.name);
}
f({name:'a'})

9、作为值的函数

在JavaScript中,我们能够将函数赋值给变量。

function f(){} 
var a=f;

10、函数作用域

作用域(scope)指的是变量存在的局限。

Javascript只需两种作用域:一种是全局作用域,变量在全部顺序中一向存在,一切处所都能够读取;另一种是函数作用域,变量只在函数内部存在。 在函数外部声明的变量就是全局变量(global variable),它能够在函数内部读取。

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

上面的代码中,函数f内部能够读取全局变量a。

在函数内部定义的变量,外部没法读取,称为“部分变量”(local variable)。

function f(){
  var a=1;
}
v  //ReferenceError: v is not defined

上面代码中,变量v在函数内部定义,所以是一个部分变量,函数以外就没法读取。

函数内部定义的变量,会在该作用域内掩盖同名全局变量。

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

注重:关于var敕令来讲,部分变量只能在函数内部声明,在其他区块中声明,一概都是全局变量。

函数的实行依赖于变量作用域,这个作用域是在函数定义时决议的,而不是函数挪用时决议的。

11、函数内部的变量提拔

与全局作用域一样,函数作用域内部也会发生“变量提拔”征象。

var敕令声明的变量,不论在什么位置,变量声明都邑被提拔到函数体的头部。

function f(x){
  if(x>10){
    var a = x -1;
  }
}

//等同于

function f(x){
  var a;
  if(x>10){
    a = x - 1;
  }
}

12、没有重载

ECMAScript函数没有重载的定义。

重载是指为一个函数编写两个定义,只需这两个定义的署名(接收的参数的范例和数目)差别即可。

关于ECMAScript函数,假如定义了两个同名的,后定义的函数会掩盖先定义的函数。

13、函数属性、要领和组织函数

13.1 函数的属性、要领

(1)name属性

name属性返回紧跟在function关键字今后的谁人函数名。

function f(){}
f.name   //f

(2)length属性

函数的length属性是只读属性,代表函数形参的数目,也就是在函数定义时给出的形参个数。

function f(x,y){}
f.length  //2

(3)prototype属性

每一个函数都包括一个prototype属性,这个属性指向一个对象的援用,这个对象称做“原型对象”(prototype object)。

(4)call()

语法:

call([thisObj[,arg1[, arg2[, [,.argN]]]]])

定义:挪用一个对象的一个要领,以另一个对象替代当前对象。
申明: call 要领能够用来替代另一个对象挪用一个要领。call 要领可将一个函数的对象上下文从初始的上下文转变为由 thisObj 指定的新对象。

(5)apply()

语法:

apply([thisObj[,argArray]]) 

定义:运用某一对象的一个要领,用另一个对象替代当前对象。
申明: 假如 argArray 不是一个有用的数组或许不是 arguments 对象,那末将致使一个 TypeError。 假如没有供应 argArray 和 thisObj 任何一个参数,那末 Global 对象将被用作 thisObj, 而且没法被通报任何参数。 bind()要领 bind()要领是在ECMAScript 5中新增的要领。 toString()要领
函数的toString要领返回函数的源码。

function f(){
  return 1;
}
f.toString()  
//function f(){
//  return 1;
//}

(6)bind()

bind()要领会建立一个新函数,称为绑定函数,当挪用这个绑定函数时,绑定函数会以建立它时传入 bind()要领的第一个参数作为 this,传入 bind() 要领的第二个以及今后的参数加上绑定函数运行时本身的参数根据递次作为原函数的参数来挪用原函数。

var bar=function(){   
  console.log(this.x);   
}
var foo={ 
     x:3   
}   
bar();  
bar.bind(foo)();
 /*或*/
var func=bar.bind(foo);   
func();

输出:
undefined
3

注重:bind()返回的是函数。

13.2 组织函数

组织函数和一般函数的定义并没有太大区分,不过我们运用new关键字来天生组织函数的实例对象。

function Test(){}
var t = new Test();

关于组织函数,平常首字母大写,便于和一般函数区分开来。

定义每一个函数都邑主动猎取一个prototype属性,该属性具有一个对象–该函数的原型,该原型有一个constructor属性,指向其当前所属的函数。

14、闭包

JavaScript的函数能够嵌套在其他函数中定义,如许它们就能够接见它们被定义时所处的作用域中的任何变量,这就是JavaScript的闭包。

闭包会保留函数作用域中的状况,纵然这个函数已实行终了。

闭包的最大用途有两个,一个是能够读取函数内部的变量,另一个就是让这些变量一直坚持在内存中,即闭包能够使得它降生环境一向存在。

闭包的建立依赖于函数。

function f(a){   
  return function(){   
    return a++;   
  };   
}   
var c=f(1);   
console.log(c());    //1
console.log(c());   //2
console.log(c());  //3

闭包的另一个用途,是封装对象的私有属性和私有要领。

15、马上挪用的函数表达式(IIFE)

在Javascript中,一对圆括号()是一种运算符,跟在函数名今后,示意挪用该函数。

(function(){  
  statement
}())

上面的函数会马上挪用。

注重:上面代码的圆括号的用法,function之前的左圆括号是必须的,由于假如不写这个左圆括号,JavaScript诠释器会试图将关键字function剖析为函数声明语句。而运用圆括号,JavaScript诠释器才会准确地将其剖析为函数定义表达式。

固然,下面的要领也会以表达式来处置惩罚函数定义的要领。

!function(){}();
~function(){}();
-function(){}();
+function(){}();

平常状况下,只对匿名函数运用这类“马上实行的函数表达式”。它的目标有两个:

  • 一是没必要为函数定名,避免了污染全局变量;

  • 二是IIFE内部形成了一个零丁的作用域,能够封装一些外部没法读取的私有变量。

16、eval敕令

eval敕令的作用是,将字符串看成语句实行。

eval('var a=1');
a  //1

eval没有本身的作用域,都在当前作用域内实行

JavaScript划定,假如运用严厉形式,eval内部声明的变量,不会影响到外部作用域。

(function(){

  'use strict';

  eval('var a=1');

  console.log(a);  //ReferenceError: a is not defined

})();

17、严厉形式下的函数

  1. 不能把函数定名为eval或arguments

  2. 不能把参数定名为eval或arguments

  3. 不能涌现两个定名参数同名的状况

  4. 假如涌现上面三种状况,都邑致使语法错误,代码没法实行。

    原文作者:sgosky
    原文地址: https://segmentfault.com/a/1190000008703053
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞