ES6学习 第七章 函数的扩展

前言

本章介绍函数的扩展。有些不常用的知识了解即可。
本章原文链接:函数的扩展

函数参数的默认值

ES6 允许为函数的参数设置默认值,即直接写在参数定义的后面。
当函数形参没有被赋值时,才会将默认值赋值给函数参数。

// 默认值直接写在行参后面
function sampleFn(sample = 0, sample1 = 0) {
  return sample + sample1;
}

注意:

  • 参数变量是默认声明的,所以不能用letconst再次声明。
  • 使用参数默认值时,函数不能有同名参数。
  • 参数默认值是惰性求值的。
  • 函数的默认值指定后,函数length属性返回的是没有指定默认值的参数的个数。
  • 参数的默认值一旦设定,函数进行声明初始化时,参数会形成一个单独的作用域(context)。
// 默认值直接写在行参后面
function sampleFn(sample = 0, sample1 = 0,sample = 1) { // 不能有同名参数

  let sample = 1; // 不能再次声明

  return sample + sample1;
}

注意:通常情况下,定义了默认值的参数,应该是函数的尾参数。也就是放在最后面。

解构赋值默认值

// 函数的默认值与结构赋值的默认值可以结合使用
function sampleFn({ sample = 0, sample1 = 0 } = {}) { // 函数参数默认值
  return sample + sample1;
}

console.log(sampleFn({ sample: 23, sample1: 33 })); // 56 参数需对应解构赋值的类型

作用域

当函数参数设置了默认值,函数进行声明初始化时,函数参数会生成一个单独的作用域,等到初始化结束,该作用域就会消失。而且该行为只在函数参数指定了默认值才会出现。

let sample = 1;

/* 
    在声明的时候出现单独作用域
  在这个作用域中,变量没有定义,于是指向外层变量
    函数调用时,函数内部变量影响不到默认值变量
*/
function sampleFn(sample1 = sample) { 
  let sample = 2;
  console.log(sample1);
  return sample1;
}

sampleFn() // 1

rest 参数

ES6 引入 **rest 参数 **,用于获取函数的多余参数。
arguments 对象是类数组,rest 参数是真正的数组。

形式为:...变量名,函数的最后一个命名参数以...为前缀。

// 下面例子中 ...values 为 rest参数 ,用于获取多余参数
const sample = function (title, ...values) {
  let sample = values.filter(
    (item) => {
      return item % 2 === 0;
    }
  )

  return (title + sample);
}

console.log(sample("求偶数", 1, 2, 6, 2, 1));  // 求偶数 2, 6, 2

注意:rest参数 只能是函数的最后一个参数,函数的length不包括rest参数

严格模式

在JavaScript中,只要在函数中的严格模式,会作用于函数参数和函数体。
ES2016 规定只要函数参数使用了默认值、解构赋值、或者扩展运算符,那么函数内部就不能显式设定为严格模式,否则会报错。

function sample() { // 参数使用默认值、解构赋值、扩展运算符 
  'use strict'; // 开启严格模式
}

name 属性

函数的name属性,返回该函数的函数名。

function sample() { };
let sample1 = function () { };
function sample2() {};

console.log(sample.name); // sample
console.log(sample1.name); //  sample1 

// bound sample2  使用了 bind 方法,输出会有bound前缀
console.log(sample2.bind({}).name); 
console.log((new Function).name); // anonymous  构造函数的name值为 anonymous

箭头函数

简单介绍

ES 6 新增一种函数定义方法,使用箭头连接参数列与函数题。
箭头函数相当于匿名函数,并且简化了函数定义,箭头函数没有prototype

// 普通函数
let sample = function (item) {
  return item;
};

// 上面函数等同于下面函数

// 使用箭头函数
let sample = (item) => { return item}; // 箭头函数

箭头函数简写

没错,箭头函数还可以简写

  1. 当参数只有一个时,可以省略箭头左边的括号,但没有参数时,括号不可以省略。
  2. 当函数体只有一个表达式时,可省略箭头右边的大括号,但同时必须省略return语句 并写在一行。
  3. 当函数体分多于一条语句,就要使用大括号将它们括起来,并且使用return语句返回。
// 下面几种函数写法都相同
let sample = function (item) {
  return item;
};

let sample = (item) => { return item}; // 箭头函数 不省略

let sample = item => { return item}; // 省略左边圆括号

let sample = (item) => item; // 省略右边大括号和 return

let sample = item => item; // ✌省略左边圆括号和右边花括号和return

// 如果不需要返回值的特殊情况
let sample = item => void item;
console.log(sample()); // undefined

注意点

  • 箭头函数 的 This 默认指向定义它的作用域的 This
  • 箭头函数 不能用作构造函数。
  • 箭头函数 不可以使用 arguments 对象,该对象在函数体内不存在。
  • 箭头函数 不可以使用yield命令,也就不能作为 Generator 函数。

箭头函数的this

箭头函数 会继承自己定义时所处的作用域链上一层的this
箭头函数 this在定义的时候已经确定了,所以箭头函数this不会改变。
使用 call()apply() 方法时,也不能重新给箭头函数绑定thisbing()方法无效。

window.sample = "window 内 ";

function sampleFn() {
  let thiz = this;
  let sample = "sampleFn 内 ";

  let sampleObj = {
    sample: "sampleObj 内 ",

    // 普通函数
    sampleFn1: function () {
      console.log(thiz === this);
      console.log(this.sample);
    },

    // 箭头函数
    sampleFn2: () => {
      // 箭头函数的作用域为 sampleObj 上一层为 sampleFn
      console.log(thiz === this); //箭头函数的 this  
      console.log(this.sample);
    }
  }

  sampleObj.sampleFn1();  // false, sampleObj 内 
  sampleObj.sampleFn2();  // true, window 内
}

sampleFn();

尾调用优化

有两个概念

  1. 尾调用
    尾调用(Tail Call)是函数式编程的一个重要概念,就是指某个函数的最后一步是调用另一个函数。

  2. 尾递归
    函数调用自身,称为递归。如果尾调用自身,就称为尾递归。

ES6 明确规定,所有 ECMAScript 的实现,都必须部署“尾调用优化”。
这就是说,ES6 中只要使用尾递归,就不会发生栈溢出(或者层层递归造成的超时),相对节省内存。

这是什么意思呢?

尾调用的作用,在原文中是这样写的:

我们知道,函数调用会在内存形成一个“调用记录”,又称“调用帧”(call frame),保存调用位置和内部变量等信息。如果在函数A的内部调用函数B,那么在A的调用帧上方,还会形成一个B的调用帧。等到B运行结束,将结果返回到A,B的调用帧才会消失。如果函数B内部还调用函数C,那就还有一个C的调用帧,以此类推。所有的调用帧,就形成一个“调用栈”(call stack)。
尾调用由于是函数的最后一步操作,所以不需要保留外层函数的调用帧,因为调用位置、内部变量等信息都不会再用到了,只要直接用内层函数的调用帧,取代外层函数的调用帧就可以了。

换种方式解释吧

函数被调用的时候会有函数执行上下文被压入执行栈中,直到函数执行结束,对应的执行上下文才会出栈。
在函数A的内部调用函数B,如果函数B中有对函数A中变量的引用,那么函数A即使执行结束对应的执行上下文也无法出栈,如果函数B内部还有调用函数C那么要等函数C执行完,函数A、B对应的执行上下文才能出栈,以此类推,执行栈中要上一个函数(内层函数)的执行上下文,这就是尾调用优化。

// 尾递归
function sampleFn(sample) {
  if (sample <= 1) return 1;
  return sampleFn(sample - 1) + sample;
}
sampleFn(2);

注意 :

  • 当内层函数没有用到外层函数的内部变量的时候才可以进行尾调用优化。
  • 目前只有 Safari 浏览器支持尾调用优化,Chrome 和 Firefox 都不支持。

ES 6 的小修改

函数参数尾逗号

ES2017 允许函数的最后一个参数有尾逗号(trailing comma)。
这样的规定也使得,函数参数与数组和对象的尾逗号规则,保持一致了。

function sampleFn(
  sample1,
  sample2,
  sample3, // 可以在最后一个参数后面加 逗号 ','
) {}

toString()修改

Function.prototype.toString()
ES2019 对函数实例的toString()方法做出了修改。明确要求返回一模一样的原始代码。
toString()方法返回函数代码本身,ES6前会省略注释和空格。

function sampleFn() {
  // 注释
}

let sample = sampleFn.toString();
console.log(sample);
//输出 完全一样的原始代码,包括空格与注释
/*
function sampleFn() {
  // 注释
}
*/

catch 修改

ES2019 改变了catch语句后面必须携带参数的要求。允许catch语句省略参数。

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

推荐阅读更多精彩内容

  • 1. 函数的默认值 ES6 允许为函数的参数设置默认值,即直接写在参数定义的后面。 参数变量是默认声明的,所以不能...
    MrZhou_b216阅读 185评论 0 0
  • 参数默认值不是传值的,而是每次都重新计算默认值表达式的值。也就是说,参数默认值是惰性求值的。 使用参数默认值时,函...
    ROBIN2015阅读 321评论 0 0
  • 1 函数默认值 1 惰性解析:参数默认值不传值,每次执行函数的时候重新计算默认值表达式的值 2 解构赋值和参数...
    澳毅奥阅读 134评论 0 0
  • 7.1函数参数的默认值 7.1.1基本用法: 在es6之前,不能直接为函数的参数指定默认值,只能采用变通的方法。e...
    曼珠沙华_521b阅读 188评论 0 0
  • 函数参数的默认值 基本用法 ES6之前,不能直接为函数的参数指定默认值,只能采用变通的方法。 上面代码检查函数lo...
    oWSQo阅读 246评论 0 0