1,带参数默认值的函数

JS函数有个独特的行为:可以接受任意数量的参数,而无视函数声明的形参数量。未提供的参数会使用默认值来代替。实际传递的参数允许少于或多于正式指定的参数。

在ES6中可以直接在形参里面进行默认值赋值:

 function makeRequest(url,timeout = 200, callback = function(){}){
...
}

在ES5中我们一般使用:

function makeRequest(url,timeout,callback){
timeout = timeout || 2000;
callback = callback || function(){};
}
function makeRequest(url,timeout,callback){
timeout = (typeof timeout !== 'undefined') ? timeout || 2000;
callback = (typeof callback !== 'undefined') ? callback || function(){};
}

在形参未被提供情况下使用默认值。但是如果timeout的值为0,即假值,就会导致timeout被替换为默认值2000;

所以应该更安全的选择typeof来检测参数的类型,很多流行的JS库都是使用上面第二种方法。

那么,参数的默认值如何影响arguments对象?在ES5中

//非严格模式下
function mixAgs(a,b){
console.log(a === arguments[0]);
console.log(b === arguments[1]);
a = 3,b=4;
console.log(a === arguments[0]);
console.log(b === arguments[1]);
}
mixAgs(1,2); // true true true true //严格模式下
function mixAgs(a,b){
"use strict";
console.log(a === arguments[0]);
console.log(b === arguments[1]);
a = 3,b=4;
console.log(a === arguments[0]);
console.log(b === arguments[1]);
}
mixAgs(1,2); // true true false false

function mixAgs(a,b = 5){
  console.log(a === arguments[0]);
  console.log(b === arguments[1]);
  a = 3,b=4;
  console.log(a === arguments[0]);
  console.log(b === arguments[1]);
}
mixAgs(1,2);  //true true false false

可以看出,在非严格模式下,函数的arguments对象可以被更新,严格模式下arguments对象不能被更新。那么ES6的形参默认赋值的函数中,无论是处于严格或是非严格模式,arguments对象都是与ES5的严格模式一致,arguments对象始终能映射出初始调用状态。

函数的参数默认表达式:

参数默认并非只能是基本类型,也可以是一个函数,看下面的例子:

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

ES6的参数默认值的暂时性死区(TDZ):

与let声明相似,函数每个参数都会创建一个新的标识符绑定,在初始化之前不允许被访问,否则会抛出错误。参数初始化会在函数被调用时进行。

函数参数拥有各自的作用域和暂时性死区,与函数体的作用于分离,送一参数的默认值不允许访问在函数内部声明的任意变量。

ES6的剩余参数:

rest parameter由三个点(...)后面紧跟一个具名参数指定,包含传递给函数的其余参数的一个数组,由此得名“剩余”。

 function pick(object,...keys){
let result = Object.create(null);
for(let i = 0,len = keys.length;i<len;i++){
result[keys[i]] = object[keys[i]];
}
return result
} let book = {
title:'Understanding ES6',
author:'Miya',
year:2016
} let bookData = pick(book,'author','year');
console.log(bookData);

上面的keys是个剩余参数,包含object后面所有的参数,与arguments不同之处,剩余参数不包含第一个参数,一个函数必须只能有一个剩余参数,并且它必须放在最后。剩余参数后面不能再放其它形参,对象的setter被限定只能使用单个参数,剩余参数是不限制参数数量的,所以不能用在对象的setter里面。

剩余参数如何影响arguments对象?

设计剩余参数就是为了替代arguments对象。arguments对象在函数被调用时反映了传入的参数,与剩余参数能协同工作。

函数构造器的增强:

函数构造器在JS中并不常用。传给构造器的参数都是字符串形式,它们就是目标函数的参数和函数体,举个栗子:

var add = new Function("first","second","return first + second");
console.log(add(1,1)) //2
  var add = new Function("first","second = first","return first + second");
  console.log(add(1))  //2

  var data = new Function("...args","return args[0]");
  console.log(data(1,2))  //1

而在ES6中呢,增强了Function的能力,允许使用默认参数以及剩余参数,确保它拥有与函数声明形式相同的所有能力。

扩展运算符:

它与剩余参数关联密切。剩余参数是允许将多个独立的参数合并到一个数组,而拓展运算符是允许将一个数组进行分割,并将各个项作为分离的参数传给函数。栗子如下:

 let values = [25,30,24,78,40];
console.log(Math.max.apply(Math,values)); //78
console.log(Math.max(...values)); //78

虽然用apply是可行的,但是这样代码混淆真的好么?拓展运算符就让这种情况变的很简单。在数组前面加上(...)。大部分场景中,拓展运算符都是apply()方法的合适替代品。

ES6的名称(name)属性:

定义函数的方式各种各样,在JS中识别函数比价困难。而且匿名函数表达式的流行使得调试有点苦难,经常导致堆栈跟踪难以被阅读与理解。所以ES6中给所有函数添加了name属性。

 function doA(){

 }
var doB = function(){ }
console.log(doA.name); //doA
console.log(doB.name); //doB

函数的双重用途:

function Person(name){
this.name = name;
}
var person = new Person("Miya"); //Person {name: "Miya"}name: "Miya"__proto__: Object
var notAPerson = Person("John"); //undefined
 console.log(person,notAPerson);
window.name //John

在调用Person()来创建notAPerson时候输出undefined,在非严格模式下给window添加了name属性。在JS中惯用首字母大写的形式创建构造函数。JS为函数提供两个内部方法:Call和Construct,未使用new进行函数调用时,call方法执行,运行的是代码的函数体,当使用new进行函数调用,Construct方法执行,负责创建一个被称为新目标的新对象,将该新目标作为this去执行函数。拥有Construct方法的函数被称为构造器。

并不是所有的函数都拥有Construct方法,因此并不是所有的函数都可以用new去调用。箭头函数就是个例外。

判断函数如何被调用的?

function Person(name){
if(this instanceof Person){
this.name = name;
}else{
throw new Error("You must use new with Person.")
}
}

在ES5中用instanceof进行判断其是否为构造器的一个实例,但是这种方法并不靠谱,看下面的例子(书籍P60代码有误)。

使用new.target元属性进行判断函数被调用时是否使用了new,元属性指的是“非对象”,1,使用new.target判断是否使用特定构造器进行了调用。2,通过检查new.target这个元属性是否被定义。

 function Person(name){
if(typeof new.target !== 'undefined'){
this.name = name;
}
//或者使用new.target判断是否使用特定构造器进行调用
if(new.target === Person){
this.name = name;
}
}

function AnotherPerson(name){
    Person.call(this,name);
   }

var person = new Person("miya");

var anotherPerson = new AnotherPerson('john');  //出错

这两种方法都可以进行检测。ES6通过新增new.target消除函数调用方式的不确定性。还解决了一个不确定的部分——在代码块内部声明函数。

块级函数:

在ES3或者更早的版本中,块级函数(在代码块中声明函数)严格来说应当是一个语法错误,但是所有的浏览器却都支持该语法。但是浏览器有差异,最佳实践是切勿在代码块中声明函数,更好的选择是使用函数表达式。在ES6中将代码块中的函数视为块级声明,会被提升到所在代码块的顶部。外部不能被访问。与let的区别在于,let的函数表达式不能被提升到所在代码块的顶部。

箭头函数:

ES6中最为有趣的就是arrow function了,用箭头("=>”),箭头函数与传统函数的不同:

1,没有this,super,arguments,new.target绑定

2,不能使用new去调用,箭头函数没有Construct方法,因此不能被用为构造函数,使用new调用箭头函数会抛出错误。

3,没有原型

4,不能更改this,this的值在函数的整个生命周期内值不会改变。

5,没有arguments对象,必须依赖具名参数或者剩余参数来访问函数的参数。

6,不允许重复的具名参数,传统的函数只有在严格模式下才会禁止这种重复。

在JS中,this的绑定是发生错误的常见根源之一,尤其嵌套函数,或者常规函数作为构造函数来使用,都是可以导致this发生变化而不利优化的原因。箭头函数也拥有name塑形,并且遵循与其他函数相同的规则。

var reflect = value => value;
var sum = (a,b) => a+b;
var getName = () => "Miya";
var doSomething = () => {};
//花括号别用于 表示函数的主体,如果要返回一个对象字面量,必须将该字面量用圆括号包裹起来。
var getTempItem = id => ({
id:id,name:'Temp'
})
var getTempItem = function(id){
return {
id:id,name:'Temp'
}
}

创建立即调用函数表达式(immediately-invoked function expression,IIFE):

let person = function(name){
return {
getName : function(){
return name;
}
}
}("Miya");
console.log(person.getName()); //Miya

let person = (name => {
  return {
    getName: () => name
  }
})("Miya");
console.log(person.getName()); //Miya

person对象包含getName()方法,该方法使用name参数作为返回值。箭头函数方法如上:

在JS中,this的绑定跟根据调用该函数时的上下文而改变,因此完全可能违背本意地影响预期外的对象。

var myType = () => {};
var obj = new myType();
VM91:2 Uncaught TypeError: myType is not a constructor

箭头函数prototype属性缺失,使用new方法会报错,因为箭头函数不存在construct方法,同理,箭头函数的this值由包含它的函数据决定,所以不能使用call(),apply()或者bind()方法来改变this的值。

箭头函数与数组:

能使用回调函数的数组方法有:sort(),map(),reduce()等方法,让其传参function更加简洁。

没有arguments绑定:

虽然arrow function没有自己的arguments对象,但是仍然能访问包含它的函数的arguments对象。看下面的栗子:

function arrowArgs(){
return () => arguments[0];
}
var a = arrowArgs(5,6,7);
console.log(a()); //

识别箭头函数:

var comparator = (a,b) => a-b;
console.log(typeof comparator); //function
console.log(comparator instanceof Function) //true
  console.log(comparator.call(null,2,1)) //1
  console.log(comparator.apply(null,[2,1])) //1
  var bindC = comparator.bind(null,2,1);
  console.log(bindC()); //1

箭头函数本质还是function,仍然可以对箭头函数使用call,apply,bind方法,只是箭头函数的this绑定不会受到影响。看上面的栗子:

尾调用(tail call)优化:

尾调用:指的是调用函数的语句是另一个函数的最后语句,ES6中对函数最为有趣的改动或许就是一项引擎优化,改变了尾部调用的系统。就像下面的tailFun方法那样。ES5引擎中实现的尾调用,其处理与其他函数调用一致:一个新的栈帧(stake frame)被创建被并被推到调用栈之上,用于表示该次函数调用,这意味着之前每个栈帧都被保留在内存中,当调用栈过大时会出问题。

ES6在严格模式下力图为特定尾调用减少调用栈的大小,非严格模式的尾调用则保持不变。尾调用优化不会创建新的栈帧,而是清除当前栈帧并再次利用它:

1,尾调用不能引用当前栈帧中的变量(意味着该函数不能是闭包);

2,进行尾调用的函数用返回结果后不能作额外的操作;

3,尾调用的结果作为当前函数的返回值。

尾调用优化允许某些函数的调用被优化,以保持更小的调用栈,使用更少的内存,并防止堆栈溢出。当能进行安全优化时,它由引擎自动应用,例如递归调用factorial()方法的优化:

function tailFun(){
    return doSomething(); //尾调用
  }
function factorial(n){
if(n <= 1){
return 1;
}else{
//未被优化:在返回之后还要执行乘法
return n*factorial(n-1);
}
} function factorial(n,p=1){
if(n <= 1){
return 1*p;
}else{
let result = n*p;
//被优化
return factorial(n-1,result);
}
}

添加了默认值为1的第二个参数p,p保留着前一次乘法的结果,因此下一次的结果就能在进行函数调用之前被算出。尾调用优化是在书写任意递归函数时需要考虑的因素,因为它能显著提升性能,尤其在被应用到计算复杂度很高的函数时,有时候这就是中级程序员和高级程序员的差别所在了。

本篇的关键字:【函数的默认参数】【剩余参数】【拓展运算符】【name属性】【new.target】【箭头函数】【尾调用优化】

ES6的函数的更多相关文章

  1. es6箭头函数讲解

    es6箭头函数的用法 箭头函数是es6的一种函数的简写方法. 如下: var f = v = > v; //等同于 var f = function(v){ return v; } var su ...

  2. es6箭头函数 this 指向问题

    es5中 this 的指向 var factory = function(){ this.a = 'a'; this.b = 'b'; this.c = { a:'a+', b:function(){ ...

  3. ES6学习 --函数参数默认值与解构赋值默认值

    1. ES6的解构ES6中引入了解构赋值的操作,其作用是:将值从数组Array或属性从对象Object提取到不同的变量中 即分为两种情况:从数组Array中解构,以及从对象Object中解构 ①.从数 ...

  4. ES6生成器函数generator

    ES6生成器函数generator generator是ES6新增的一个特殊函数,通过 function* 声明,函数体内通过 yield 来指明函数的暂停点,该函数返回一个迭代器,并且函数执行到 y ...

  5. ES6 — 箭头函数

    一 为什么要有箭头函数 我们在日常开发中,可能会需要写类似下面的代码 const Person = { 'name': 'little bear', 'age': 18, 'sayHello': fu ...

  6. 前端分享----JS异步编程+ES6箭头函数

    前端分享----JS异步编程+ES6箭头函数 ##概述Javascript语言的执行环境是"单线程"(single thread).所谓"单线程",就是指一次只 ...

  7. ES6 箭头函数 this 指向

    ES6 箭头函数 this 指向 箭头函数有几个使用注意点: 函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象. 不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个 ...

  8. ES6 箭头函数(Arrow Functions)

    ES6 箭头函数(Arrow Functions) ES6 可以使用 "箭头"(=>)定义函数,注意是函数,不要使用这种方式定义类(构造器). 一.语法 具有一个参数的简单函 ...

  9. 深入理解ES6之函数

    一:关于函数的参数: 可以接受任意数量的参数而无视函数声明的参数数量是js函数的独特之处. 1:参数默认值 ES6之前做法: function makeRequest(url,timeout,call ...

  10. ES6箭头函数基本用法

    ES6箭头函数基本用法 ``` window.onload = function(){ alert(abc); } //箭头函数 window.onload = ()=>{ alert(&quo ...

随机推荐

  1. js - 观察者模式与订阅发布模式

    零.序言 转载&参考: 1.JavaScript 设计模式系列 - 观察者模式 2.JavaScript 设计模式(六):观察者模式与发布订阅模式 一.观察者模式(observer) 概要: ...

  2. SpringBoot常见面试题

    什么是SpringBootSpringBoot的作用SpringBoot的优点SpringBoot的核心配置文件是什么,有何区别?SpringBoot的配置文件有几种格式,区别是什么?SpringBo ...

  3. windows下redis的配置和jedis api的最基本的使用

    redis的安装直接跳过 1.注册redis服务 在DOM窗口下,进入redis的安装目录(可以先进入安装目录,然后shift+右键,选择在此处打开powershell窗口), 输入命令: redis ...

  4. jQuery选择器的效率问题

    jQuery提供了功能强大,并兼容多种css版本的选择器,不过发现很多同学在使用选择器时并未注重效率的问题. a) 尽量使用Id选择器,jQuery选择器使用的API都是基于getElementByI ...

  5. php配置memcached的扩展。

    (一)安装memcached服务器 1根据系统下载相应版本的memcached服务器版本:如win7(64位=====>memcached-win64/memcached.exe 2.解压到目录 ...

  6. OpenCV Sobel 导数

    #include "opencv2/imgproc/imgproc.hpp" #include "opencv2/highgui/highgui.hpp" #i ...

  7. the Uneducated are|anymore|that| so as to |die from|die of|

    定冠词加上某些形容词可以泛指一类人,谓语动词一般用复数形式,the uneducated泛指未受过教育的人, the Uneducated are more to be pitied than bla ...

  8. when|nobody|hazard|lane|circuit|

    How can I help them  they won't listen to me? 题目解析 考查从句.此句意为:如果他们要是不听我的话,我怎么帮助他们?此处,when引导的状语从句表示假设事 ...

  9. 3DMAX卸载/完美解决安装失败/如何彻底卸载清除干净3DMAX各种残留注册表和文件的方法

    在卸载3dmax重装3dmax时发现安装失败,提示是已安装3dmax或安装失败.这是因为上一次卸载3dmax没有清理干净,系统会误认为已经安装3dmax了.有的同学是新装的系统也会出现3dmax安装失 ...

  10. leetcode第23题:合并K个排序链表

    首先我想到的是采用一般递归法,将K个链表合并化为(k-1)两个链表合并 class Solution: def mergeKLists(self, lists: List[ListNode]) -&g ...