JS 函数

在 JS 中,函数就是一个方法,一般都是为了实现某个功能。

1. 函数的作用和创建

var total = 10;
total += 10;
total = total/2;
total = total.toFixed(2); //=> 保留小数点后面两位,toFixed 时候数字包装对象的方法,用来保留小数点后面的位数

在后续的代码中,依然想实现相同的操作,就需要重新编写代码。这样的方式会导致页面中存在大量冗余的代码,也降低了开发效率。

函数的诞生的目的就是为了实现封装:把实现一个功能的代码封装到一个函数中,以便后期重复利用。起到了低耦合高内聚(减少页面中的冗余代码,提高代码的重复利用率)的作用

创建函数

// ES5 中:
function 函数名([参数]) {
  // 函数体
}
// 表达式创建
var 函数名 = function ([参数]) {
  // 函数体
}

// ES6 箭头函数
let 函数名或者说变量名 = ([参数]) => {
  // 函数体
}

2. 函数的创建执行机制

函数作为引用类型,也是按照引用地址来操作的。

【创建函数】

  1. 首先开辟一个新的堆内存,把函数体中的代码当作字符串存储在内存中(对象存储的是键值对)
  2. 在当前上下文中声明函数(变量),函数声明会提升到最前面
  3. 把开辟的堆内存地址赋值给函数名(变量名)

此时输出函数名 fn(不是 fn()),代表当前函数本身,如果我们要执行函数,就要加上小括号即 fn()。这是两种不同本质的操作。

【函数执行】
目的:把之前存储到堆内存中的代码字符串变为真正的 JS 代码自上而下执行,从而实现应有的功能。

  1. 函数执行,首先会形成一个私有的作用域(一个供代码执行的环境,也是一个栈内存)
  2. 把之前在堆内存中存储的字符串复制一份到新开辟的栈内存中变为真正的 JS 代码
  3. 然后再进行变量提升 (var function 提前声明, 先形参赋值, 再变量提升)
  4. 最后在这个新开辟的作用域中自上而下执行

函数执行的时候,都会形成一个全新的私有作用域(私有栈内存),目的是:

  • 把原有堆内存中存储的字符串变成真正的 JS 代码执行
  • 保护里面的私有变量不受外界的干扰(和外界隔离)

我们把函数执行的这种保护机制,称之为“闭包”

函数内声明的变量都是私有变量。

3. 函数中的参数

参数是函数的入口:当我们在函数中封装一个功能,有一些不确定的因素,需要执行函数的时候由用户传递进来。此时我们就基于参数的机制,提供入口即可。

函数中的参数是按值传递的

//=> 此时的参数叫做形参(命名参数):入口,形参是变量
function sum(n, m) {
  return n + m;
}

//=> 函数执行时传递的值叫做实参:实参是具体的数据值,即使写的是变量或者表达式,也是把变量或者表达式计算的结果作为值传递给形参变量
sum(1, 2); //=> n:1, m:2
sum(1); //=> n:1, m:undefined
sum(); //=> n:undefined, m:undefined
sum(1, 2, 3) //=> n:1, m:2, 3 没有形参变量接收

3.1 理解参数

JavaScript 中函数不介意传递多少个参数,也不在乎传进去的参数是什么数据类型。即使定义时,函数只有两个形参,实际执行的时候,也可以传递任意个参数。

原因是 JavaScript 中的参数在内部是用一个数组来表示的,函数接受到的始终是一个数组。

实际上,在函数内部可以通过 arguments 对象来访问这个参数数组,从而获取传递给函数的每一个参数。

3.2 arguments 对象

arguments 对象是一个类数组对象,可以通过索引访问元素,也有 length 属性访问长度。是函数内置的实参集合(内置:函数天生就存在的机制,不管你是否设置形参,是否传递实参,arguments 始终存在),只能在函数内访问。

命名参数是有局限性的:我们需要具体知道用户执行的时候传递实参数量、顺序等,才可以使用形参变量定义对应入口。

通过 arguments 对象,函数不显式的使用命名参数,也能够实现一样的功能。

function sum() {
  return arguments[0] + arguments[1];
}

因此在 JavaScript 中函数的一个重要特点是:命名参数只提供便利,但不是必须的。


length 属性
可以通过其 length 属性获取传入参数的个数,利用这一点让函数能够接受任意个参数并分别实现适当的功能。

function add() {
  if (arguments.length == 1) {
    return arguments[0] + 10;
  } else if (arguments.length == 2) {
    return arguments[0] + arguments[1];
  }
  ...
}

arguments 同样可以和命名参数一起使用。


arguments 的映射机制

arguments 中的值会与对应命名参数(形参)的值保持同步

function add(n, m) {
  arguments[1] = 10;
  return n + m;
}
add(1,2) //=> 11
// 反过来改变命名参数的值也是一样,同步改变

arguments 对象和形参之间的映射是在函数执行后形参赋值的一瞬间,浏览器通过 arguments 中的索引来完成和对应形参变量中的映射机制搭建。一开始没有建立起来的,即使在后面改变 arguments 对象,也无法形成映射。

如果形参比 arguments 中个数多,那么多出来的形参是无法和 arguments 中对应的索引建立关联的。

function fn(x, y) {
  /* 形参赋值:x = 10, y = undefined
   * 
   * argumenrs
   *  0: 10
   *  length: 1
   */
  var arg = arguments;
  arg[0] = 100;
  console.log(x); //=> 100,存在索引,形成映射
  
  y = 200;
  console.log(arg[1]); //=> undefined

  //=> 后面再改变 arguments,也无法形成映射
  arg[1] = 150;
  console.log(y); //=> 200
}
fn(10);

注意:

  1. 这并不说明两个值访问相同的内存空间,它们的内存空间是相互独立的,只是值会保持同步
  2. arguments 对象的长度由传入的参数个数决定,不是由定义函数时的命名参数个数决定
  3. 没有传递值的命名参数将被自动赋值为 undefined
  4. 严格模式下,不允许改变 arguments 对象,同时与命名参数不同步

callee 属性
存储的是当前函数本身。严格模式下,访问会报错。


callee.caller 属性
存储的是调用函数的环境。全局调用的话,值为 null。严格模式下,访问会报错。


应用
任意数求和:不管函数执行的时候,传递多少实参值进来,我们都可以求和

function sum() {
  var total = 0;
  for (var i = 0; i < arguments.length; i++) {
    total += arguments[i];
  }
  return total;
}

优化:在累加时,把字符串转换为数字,对于非有效数字,不再相加。

function sum() {
  var total = 0;
  for (var i = 0; i < arguments.length; i++) {
    var item = Number(arguments[i]);
    isNaN(item) ? null : total += item;
  }
  return total;
}

//=> ES6
var sum = (...arg) => eval(arg.filter(item => !isNaN(item)).join('+'));

4. 返回值

返回值是函数的出口,把函数运行的结果或者函数体中的部分信息拿到函数外面去使用。

函数在任何时候都可以通过 return 返回一个值,返回之后,函数停止并立即退出,后面代码不再执行。

function fn(n, m) {
  var total = 0;
  total = n + m;
  return total;
  //=> 并不是把 total 变量返回,返回的是变量存储的值,return 返回的永远是一个值
}
fn(1,2) //=> 3

要么让函数始终返回一个值,要么始终不会返回值。

5. 匿名函数

匿名函数:没有函数名的函数

  • 函数表达式:把函数当作值赋值给变量或者元素的事件
  • 自执行函数:创建和执行一起完成(立即执行匿名函数)
  • 回调函数:将匿名函数当作参数传入函数中
//=> 函数表达式
var sum = function() {

};

//=> 自执行函数
(function(){

})();
~function() {

}();
+function() {

}();
!function() {

}();
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容

  • 函数是一段可以反复调用的代码块。函数还能接受输入的参数,不同的参数会返回不同的值。 概述 函数的声明 JavaSc...
    oWSQo阅读 1,113评论 0 4
  • 在js中,函数本身属于对象的一种,因此可以定义、赋值,作为对象的属性或者成为其他函数的参数。函数名只是函数这个对象...
    bjhu电net阅读 512评论 0 5
  • 1. 函数声明和函数表达式有什么区别 (*) 函数在JS中有三种方式来定义:函数声明(function decla...
    进击的阿群阅读 400评论 0 1
  • 今天我和爸爸一起做手工了,有蓝天、白云、房子、鹅、还有小河,爸爸做的蓝天,和白云,我做的小河、大白鹅、还有房子,我...
    一班杨特阅读 122评论 0 0
  • 传说,在天地之外,有一座神秘的仙山,常年弥漫着浓雾,每到七夕之时,仙山上的浓雾就会慢慢退去,周围所有的飞禽走...
    竹鸿初阅读 645评论 0 1