开启掘金成长之旅!这是我参与「掘金日新计划 · 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);
这就叫做"尾调用优化",即只保留内层函数的调用记录。
如果所有函数都是尾调用,那么完全可以做到每次执行时,调用记录只有一项,这将大大节省内存。这就是"尾调用优化"的意义。
满足以下条件,尾调用不再创建新的栈帧,而且清除并重用当前栈帧:
- 尾调用不访问当前栈帧的变量(函数不是一个闭包);
- 在函数内部,尾调用是最后一条语句;
- 尾调用结果作为函数值返回;
尾递归: 函数调用自身,称为递归。如果尾调用自身,就称为尾递归。 递归非常耗费内存,因为需要同时保存成千上百个调用记录,很容易发生"栈溢出"错误。但对于尾递归来说,只存在一个调用记录,所以不会发生"栈溢出"错误。
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?想补充?
感谢大家观看这篇文章,有任何问题或想和我交流,请直接留言,
发现文章有不妥之处,也可指出交流,感谢阅读~
最后,求个三连 ~