你真的了解ES6嘛?-函数篇🍄

107 阅读5分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 7 天,点击查看活动详情

ES6 既是一个历史名词,也是一个泛指,含义是 5.1 版本以后的 JavaScript 的下一代标准,涵盖了 ES2015、ES2016、ES2017 等。

1、函数默认参数

定义函数的同时,可以给形参一个默认值,es6 (提供了更加简单强大的代码更能力) 提高我们的开发效率

大家都使用过下面的案例:

//level设置默认值'warning'
function log(message, level = 'warning') { 
  ...
}   

是否应用默认值,取决于对参数初始值的检查结果是否为值 undefined,而对于默认参数 null则是一个合法值;

function detanx(x =  null, y = 6) {
  console.log(x, y);
}
detanx(undefined, null); // null null

下面再说说非原始值传参的用法跟例子:

function getVal(){
   return 5
}
//写成second=getVal(),而不是second=getVal,函数里也可传参
function add (first, second=getVal()){
   return first + second
}
console.log(add(1,1));//2
console.log(add(1));//6

当使用函数调用结果为默认参数的时候,如果忘记写括号(second=getVal),则传入的是对函数的引用,而且不是函数调用结果

默认参数是在函数调用时求值,所以可以使用先定义的参数作为后定义参数的默认值,如下:

function add(first, second = first){
   return first + second
}
console.log(add(1,1));//2
console.log(add(1));//2

注意:先定义的参数不能访问后定义的参数!一个参数不能以自身作为默认值!

如果一个函数没有默认值,它就不会创建这个中间作用域,并且会与一个函数环境中的参数绑定共享,即以 ES5 模式运行。例子如下:

var x = 1;   
function foo(x, y) {   
  if (typeof y == 'undefined') {
       y = function() { x = 2; };   
  }   
  var x = 3;   
  y(); // `x` 被共用了吗?   
  console.log(x); // 是的!2 
}   
foo();   // 外部的 `x` 依然不受影响 
console.log(x); // 1

设置默认值还可以搭配解构赋值一起使用:

// 搭配解构赋值
function detanx({name, age = 10} = {name: 'detanx', age: 10}) {...} 
// 不完全解构
function detanx({name, age = 10} = {name: 'detanx'}) {...} 
detanx({age: 12}) // undefined 12

不完全解构时,即使后面的对象中有对应的键值也不会被赋值。

临时死区(TDZ)

ES6 提到了所谓的 TDZ(表示暂时性死区)—— 这是程序的一部分,在这个区域内变量或者参数在初始化(即接受一个值)之前将无法访问

  • 作用域顶部,到变量真正声明之前的这一段,被称为死区
  • 因为死区是临时的,变量声明了就解除了,所以叫做临时死区

var变量提升的同时赋值undefined,let和const则标记为uninitialization(TDZ状态)

//例子:
function add(first= second, second){
   return first + second
}
console.log(add(1,1));//2
console.log(add(undefined, 1));//报错

2、什么是尾调用优化和尾递归?

尾调用:

函数调用会在内存形成一个"调用记录",又称"调用帧"(call frame),保存调用位置和内部变量等信息.

尾调用由于是函数的最后一步操作,所以不需要保留外层函数的调用记录,只要直接用内层函数的调用记录,取代外层函数的调用记录就可以了。

function f() {  
   let m = 1;    
   let n = 2;      
   return g(m + n);  
}  
f();   
// 等同于
function f() {      
   return g(3);  
}  
g(3);// 等同于 g(3);

这就叫做"尾调用优化",即只保留内层函数的调用记录。

如果所有函数都是尾调用,那么完全可以做到每次执行时,调用记录只有一项,这将大大节省内存。这就是"尾调用优化"的意义。

满足以下条件,尾调用不再创建新的栈帧,而且清除并重用当前栈帧:

  1. 尾调用不访问当前栈帧的变量(函数不是一个闭包);
  2. 在函数内部,尾调用是最后一条语句;
  3. 尾调用结果作为函数值返回;

尾递归: 函数调用自身,称为递归。如果尾调用自身,就称为尾递归。 递归非常耗费内存,因为需要同时保存成千上百个调用记录,很容易发生"栈溢出"错误。但对于尾递归来说,只存在一个调用记录,所以不会发生"栈溢出"错误。

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

函数递归是最主要的应用场景

3、函数的name属性

es6中为函数新增了name属性,方便研发者们追踪难以解读的栈记录;

使用这个属性,我们可以得到这个函数的名字:

var doSomething = function(){···}
console.log(doSomething.name);//doSomething
console.log(doSomething.bind().name);// bound doSomething
console.log(new Function().name);//anonymous

小知识:通过bind()创建的函数,名称带有“bound”前缀,通过Function构造函数创建的函数,名称则是“anonymous”

4、元属性new·target

元属性是指非对象的属性,检测函数或构造方法是否是通过 new 被调用的。在通过 new 被初始化的函数或构造方法中, new.target 返回一个指向构造方法或函数的引用。在普通的函数调用中, new.target 的值是 undefined。

作用:元属性可以帮助你检测函数是通过哪种方式调用的

function Person(name) {
  if (typeof new.target!=='undefined'){
    this.name = name
  }else{
    throw new Error('必须通过new关键词来调用Person')
  }
}
var person=new Person('max')
var notePerson=Person.call(person,'min')//抛出错误

//call的作用
A.call( B,x,y )
//如上,就是把A的函数放到B中运行,x 和 y 是A方法的参数。
//https://blog.csdn.net/q5706503/article/details/82893277

如果在函数外使用new·target 是一个错误语法

有bug?想补充?

感谢大家观看这篇文章,有任何问题或想和我交流,请直接留言,

发现文章有不妥之处,也可指出交流,感谢阅读~

最后,求个三连 ~