美文网首页
函数(下)

函数(下)

作者: NnnLillian | 来源:发表于2019-10-18 19:10 被阅读0次

上一章节中主要讲解了

  • 处理无命名参数
  • 增强的Function构造函数
  • 展开运算符
  • name属性
  • 明确函数的多重用途
  • 块级函数

本章节内容

  • 箭头函数
  • 尾调用优化

箭头函数

箭头函数有以下几个方面的特点:

  • 没有 this, superm arguments and new.target 绑定。箭头函数中的 this, super, arguments和arguments的值由外围最近一层包含它的非箭头函数定义。
  • 不同通过new关键字调用。箭头函数内部没有 [[construct]]方法, 因此不能当作构造器,使用new操作符;
  • 不存在原型(No prototype),由于不可以通过new关键字调用箭头函数,因而没有构建原型的需求,所以箭头函数不存在prototype这个属性;
  • 不能改变this, 在整个箭头函数生命周期this值保持不变;
  • 不存在arguments对象,不过包含它的函数存在,箭头函数依靠命名参数和rest parameters访问函数的参数;
  • 不能拥有重复的命名参数,ES5只有严格模式下才不允许;

箭头函数语法

var f = v => v;
// 等同于
var f = function (v) {
  return v;
};

// 当要传入多参数时,要在参数两个添加一对小括号
var sum = (num1, num2) => num1 + num2;
// 等同于
var sum = function(num1, num2) {
  return num1 + num2;
};

// 如果箭头函数不需要参数或需要多个参数,就使用一个圆括号代表参数部分
var f = () => 5;
// 等同于
var f = function () { return 5 };

// 如果箭头函数的代码块部分多于一条语句,就要使用大括号将它们括起来,并且使用return语句返回。
var sum = (num1, num2) => {num1++;  return num1 + num2; }
// 等同于
var sum = function(num1, num2) {
  num1++; 
  return num1 + num2;
};

// 如果想创建一个空函数,需要写一对儿没有内容的花括号
let getTempItem = () => { };
// 等同于
let getTempItem = function() = { };

// 如果箭头函数只有一行语句,且不需要返回值,可以采用下面的写法,就不用写大括号了。
let fn = () => void doesNotReturn();

如果箭头函数直接返回一个对象,必须在对象外面加上括号,否则会报错。

// 报错
let getTempItem = id => { id: id, name: "Temp" };

// 不报错
let getTempItem = id => ({ id: id, name: "Temp" });
// 等同于
let getTempItem = function(id) {
  return {
    id : id,
    name : "Temp"
  };
};

下面是一种特殊情况,虽然可以运行,但会得到错误的结果。

let foo = () => { a: 1 };
foo() // undefined

上面代码中,原始意图是返回一个对象{ a: 1 },但是由于引擎认为大括号是代码块,所以执行了一行语句a: 1。这时,a可以被解释为语句的标签,因此实际执行的语句是1;,然后函数就结束了,没有返回值。

const full = ({ first, last }) => first + ' ' + last;

// 等同于
function full(person) {
  return person.first + ' ' + person.last;
}

箭头函数可以与变量解构结合使用。

const full = ({ first, last }) => first + ' ' + last;

// 等同于
function full(person) {
  return person.first + ' ' + person.last;
}

创建立即执行函数表达式

当想创建一个与其他程序隔离的作用域时,可以定义一个匿名函数并立即调用,自始至终不保存对该函数的引用。

let person=function(name){
  
  return{
    getName: function(){
      return name;
    }
  }
}("Baby");

console.log(person.getName());// "Baby"

立即执行函数表达式创建了一个包含getName()方法的新对象,将参数name作为该对象的一个私有成员返回给函数的调用者
只要将箭头函数包裹在小括号里,能够实现相同的行为。

let person=((name) => {
  
  return{
    getName: function(){
      return name;
    }
  };
  
})("Baby");

console.log(person.getName());// "Baby"

箭头函数没有this绑定

箭头函数根本没有自己的this,导致内部的this就是外层代码块的this。所以箭头函数可以让this指向固定化。

function Timer() {
  this.s1 = 0;
  this.s2 = 0;
  // 箭头函数
  setInterval(() => this.s1++, 1000);
  // 普通函数
  setInterval(function () {
    this.s2++;
  }, 1000);
}

var timer = new Timer();

setTimeout(() => console.log('s1: ', timer.s1), 3100);
setTimeout(() => console.log('s2: ', timer.s2), 3100);
// s1: 3
// s2: 0

上面代码中,Timer函数内部设置了两个定时器,分别使用了箭头函数和普通函数。前者的this绑定定义时所在的作用域(即Timer函数),后者的this指向运行时所在的作用域(即全局对象)。所以,3100 毫秒之后,timer.s1被更新了 3 次,而timer.s2一次都没更新。

箭头函数可以让this指向固定化,这种特性很有利于封装回调函数。下面是一个例子,DOM 事件的回调函数封装在一个对象里面。然而,不使用箭头函数的情况下代码会报错。

let PageHandler = {

    id: "123456",

    init: function() {
        document.addEventListener("click", function(event) {
            this.doSomething(event.type); // error
        }, false);
    },

    doSomething: function(type) {
        console.log("Handling " + type + " for " + this.id);
    }
};
// init函数中的this.doSomething,this指向的是函数内部document对象;
// 而不是PageHandler对象,document对象中不存在doSomething,所以无法正常执行

使用bind()方法显式的将函数的this绑定到PageHandler上来修正这个问题。

let PageHandler = {
  
  id : "123",
  
  init : function(){
    document.addEventListener("click", (function(event) {
      this.doSomething(event.type);
    }).bind(this), false);
  },
  
  doSomething : function(type) {
    console.log("Handle "+ type + " for " + this.id);
  }
}
// 调用bind(this)后创建了一个新函数,它的this被绑定到当前的this,也就是PageHandler

为了避免创建一个额外的函数,可以使用箭头函数。

let PageHandler = {

  id: "123456",

  init: function() {
      document.addEventListener("click", 
              event => this.doSomething(evnet.type), false);
    },

  doSomething: function(type) {
      console.log("Handling " + type + " for " + this.id);
  }
};
// 此处箭头函数没有自己this,它的this就是其外部函数的this,即init()的this;
// init为PageHandler的方法,this指向PageHandler对象实例

箭头函数不能使用new

var MyType = () => {};
var obj = new MyType(); // Error,不可以通过new关键字调用箭头函数

箭头函数和数组

诸如sort()、map()、reduce()这些可以接受回调函数的数组方法,可以通过箭头函数简化。

// 正常函数写法
[1,2,3].map(function (x) {
  return x * x;
});
// 箭头函数写法
[1,2,3].map(x => x * x);


// 正常函数写法
var result = values.sort(function (a, b) {
  return a - b;
});
// 箭头函数写法
var result = values.sort((a, b) => a - b);

箭头函数没有arguments绑定

箭头函数没有arguments对象,但是可以使用包含函数中的arguments对象。

function createArrowFunctionReturningFirstArg() {
    // arguments 为 createArrowFunctionReturningFirstArg中的对象
    return  () => arguments[0]; 
}
var arrowFunction = createArrowFunctionReturningFirstArg(10);
arrFunction(); // 10

箭头函数的辨识方法

// 箭头函数使用typeof和instanceof操作符调用 与其他函数一样
var sum = (num1, num2) => num1 + num2;

console.log(typeof sum);// "function"
console.log(sum instanceof Function);// true

可以在箭头函数上调用call()、apply()、bind()方法,与其他函数不同的是,箭头函数的this不会受到这些方法影响。

var sum = (num1, num2) => num1 + num2;

console.log(sum.call(null, 1, 2));
console.log(sum.apply(null, [1, 2]));

var boundSum = sum.bind(null, 1, 2);

console.log(boundSum());

尾调用优化

尾调用(Tail Call)是指某个函数的最后一步是调用另一个函数。

function f(x){
  return g(x); // 尾调用
}

以下三种情况,都不属于尾调用。

// 情况一,因为调用函数g之后,还有赋值操作,所以不属于尾调用
function f(x){
  let y = g(x);
  return y;
}

// 情况二,原因同情况一
function f(x){
  return g(x) + 1;
}

// 情况三,原因是没有返回函数g
function f(x){
  g(x);
}

尾调用不一定出现在函数尾部,只要是最后一步操作即可

function f(x) {
  if (x > 0) {
    return m(x)
  }
  return n(x);
}

上面代码中,函数m和n都属于尾调用,因为它们都是函数f的最后一步操作。

ES6中的尾调用优化

尾调用之所以与其他调用不同,就在于它的特殊的调用位置。

我们知道,函数调用会在内存形成一个“调用记录”,又称“调用帧”(call frame),保存调用位置和内部变量等信息。如果在函A的内部调用函数B,那么在A的调用帧上方,还会形成一个B的调用帧。等到B运行结束,将结果返回到AB的调用帧才会消失。如果函数B内部还调用函数C,那就还有一个C的调用帧,以此类推。所有的调用帧,就形成一个“调用栈”(call stack)。

尾调用由于是函数的最后一步操作,所以不需要保留外层函数的调用帧,因为调用位置、内部变量等信息都不会再用到了,只要直接用内层函数的调用帧,取代外层函数的调用帧就可以了。

function f() {
  let m = 1;
  let n = 2;
  return g(m + n);
}
f();

// 等同于
function f() {
  return g(3);
}
f();

// 等同于
g(3);

上面代码中,如果函数g不是尾调用,函数f就需要保存内部变量mn的值、g的调用位置等信息。但由于调用g之后,函数f就结束了,所以执行到最后一步,完全可以删除f(x)的调用帧,只保留g(3)的调用帧。

这就叫做“尾调用优化”(Tail call optimization),即只保留内层函数的调用帧。如果所有函数都是尾调用,那么完全可以做到每次执行时,调用帧只有一项,这将大大节省内存。这就是“尾调用优化”的意义。

注意,只有不再用到外层函数的内部变量,内层函数的调用帧才会取代外层函数的调用帧,否则就无法进行“尾调用优化”。

function addOne(a){
  var one = 1;
  function inner(b){
    return b + one;
  }
  return inner(a);
}

上面的函数不会进行尾调用优化,因为内层函数inner用到了外层函数addOne的内部变量one

利用尾调优化--尾递归

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

递归非常耗费内存,因为需要同时保存成千上百个调用帧,很容易发生“栈溢出”错误(stack overflow)。但对于尾递归来说,由于只存在一个调用帧,所以永远不会发生“栈溢出”错误。

function factorial(n) {
  if (n === 1) return 1;
  return n * factorial(n - 1);
}

factorial(5) // 120

上面代码是一个阶乘函数,计算n的阶乘,最多需要保存n个调用记录,复杂度 O(n) 。

如果改写成尾递归,只保留一个调用记录,复杂度 O(1) 。

function factorial(n, total) {
  if (n === 1) return total;
  return factorial(n - 1, n * total);
}

factorial(5, 1) // 120

ES6 的尾调用优化只在严格模式下开启,正常模式是无效的。

这是因为在正常模式下,函数内部有两个变量,可以跟踪函数的调用栈。

  • func.arguments:返回调用时函数的参数。
  • func.caller:返回调用当前函数的那个函数。
    尾调用优化发生时,函数的调用栈会改写,因此上面两个变量就会失真。严格模式禁用这两个变量,所以尾调用模式仅在严格模式下生效。
function restricted() {
  'use strict';
  restricted.caller;    // 报错
  restricted.arguments; // 报错
}
restricted();

相关文章

  • 函数(下)

    楔子 假如有一个函数,实现返回两个数中的较大值: 之前是不是我告诉你们要把结果return回来你们就照做了?可...

  • 函数(下)

    上一章节中主要讲解了 处理无命名参数 增强的Function构造函数 展开运算符 name属性 明确函数的多重用途...

  • this指向(箭头函数)

    以this的使用场景进行分类: 1.普通函数下,this 的指向 2.构造函数下,this 的指向 3.箭头函数下...

  • static对函数作用

    0. 函数分类 外部函数: 可以被 其它文件 访问的函数, 默认情况下所有函数 都是 外部函数 内部函数: 只能在...

  • 每天学习总结

    function 函数名() { 函数体 } 函数的调用(); 今天讲解了函数的使用,回顾一下吧,函数刻分为命名函...

  • 关于this指向

    关于this指向 1.在全局函数下,函数内的this默认指向window2.在严格模式下,函数内的this指向un...

  • Python函数入门(下)

    一、四种函数的类型 函数根据有没有参数,有没有返回值,可以相互组合,一共有4种 无参数,无返回值 无参数,有返回值...

  • Linux下Kill()函数

    函数说明 The kill() system call can be used to send any signa...

  • Excel查找引用函数-Index函数(下)

    对于一个区域,我们给定一个坐标(相对行号和列号),可以直接使用index的数组形式,返回其单元格或是区域中的值,但...

  • Python内建函数

    在python2中一下函数为内置函数,在python3中,一下部分函数已从内置函数中移除,变为内置类 1、map(...

网友评论

      本文标题:函数(下)

      本文链接:https://www.haomeiwen.com/subject/muqimctx.html