jian1、.函数的概念:
函数是被设计为执行特定任务的代码块,函数会在某代码调用它时被执行。在es6出现前,js的函数语法一直没太大的变化,从而遗留了很多问题和隐晦的做法,导致实现一些基本的功能经常要编写很多代码,es6终于大力度的更新了函数特效,在es5的基础上进行了许多改进,让使用js的编程可以更少的出错,同时也更加灵活
2.函数形参的默认值
js函数有个特别的地方,无论在函数定义中声明了多少形参,都可以传入任意数量的参数,也可以在定义函数时添加针对参数数量的处理逻辑,当已定义的形参无对应的传入参数时为其指定一个默认值。这一节讲解在es6前后添加默认参数的方式,以及一些arguments对象。如何使用表达式作为参数,参数临时死区的重要内容。
2-1-1 在es5中模拟默认参数
案例1:
这个示例中,timeout和callback为可选参数,如果不传入相应的参数会给它们赋予一个默认值
注这里有个小bug,如果我们给timeout传入值0,即使这个值时合法的,也会被视为一个价值,将timeout赋值2000
function makeRequest(url,timeout,callback){
timeout=timeout || 2000;
callback= callback || function(){}
console.log(url,timeout,callback)
}
makeRequest('/foo',0, function(){ })//index1.html:14 /foo 2000 ƒ (){}
es5修改方案1
//可以通过typeof检查参数类型的方式来修改这个
function makeRequest(url,timeout,callback){
timeout = (typeof timeout !== 'undefined') ? timeout : 2000;
callback = (typeof callback !== 'undefined') ? callback :function(){};
console.log(url,timeout,callback)
}
makeRequest('/foo',0);///foo 0 function
2-1-2 es6中的默认参数值
es6简化了为形参提供默认值的过程,如果没为参数传入值则为其提供一个初始值
//es6修改方案2
//在这个函数中,只有第一个参数被认为总是要为其传入值,其他两个都有默认值,而且不需要添加任何校验值是否缺失代码
function makeRequest(url,timeout = 2000,callback = function(){}){
console.log(url,timeout,callback)
}
makeRequest('/foo',0 ,function(){console.log(1)})//foo 0 ƒ (){console.log(1)}
makeRequest('/foo')//使用默认值
makeRequest('/foo',null)//不使用timeout默认值
2-1-3默认值对arguments的影响
使用默认参数值时,arguments对象的行为与以往的不同,
es5非严格模式下,函数 命名的参数的变化体现在arguments,
以下这段代码解释了这种运行机制
注:1.在非严格模式下,命名参数的变化会更新到arguments
2.在严格模式下,无论参数如何变化,arguments对象不在随之改变
3.在es6中,如果一个函数使用默认参数,则无论是否显示定义了严格模式,都会 按着严格模式走
4.只传入一个参数时,例如 函数mixArgs3,正如你期待的那个样arguments.lengths的值为1,arguments[1]的值为undefind,所有arguments[0]全等,改变first和second并不会影响arguments,可以通过arguments的参数 恢复初始值,无论当前是否时严格迷失
function mixArgs(first,second){
console.log(first==arguments[0]);//true
console.log(second==arguments[1]);//true
first = 'c';
second = 'd';
console.log(first==arguments[0]);//true
console.log(second==arguments[1]);//true
}
mixArgs(2,3);//true,true,true,true
function mixArgs1(first,second){
"use strict"
console.log(first==arguments[0]);//true
console.log(second==arguments[1]);//true
first = 'c';
second = 'd';
console.log(first==arguments[0]);//false
console.log(second==arguments[1]);//false
}
mixArgs1(3,5);//true,true,false,false
function mixArgs2(first,second='b'){
console.log(first==arguments[0]);
console.log(second==arguments[1]);
first = 'c';
second = 'd';
console.log(first==arguments[0]);
console.log(second==arguments[1]);
}
mixArgs2(3,5); //true,true,false,false
function mixArgs3(first,second='b'){
console.log(arguments.length)
console.log(first==arguments[0]);
console.log(second==arguments[1]);
first = 'c';
second = 'd';
console.log(first==arguments[0]);
console.log(second==arguments[1]);
}
mixArgs3(3);//1,true,false,false,false
2-1-4默认参数表达式
案例1:关于参数的默认值,最有趣的特性可能是非原始值的传参了 。举个例子可以通过函数执行来得到默认参数的值,就行这样, 这个函数传递一个参数时,不传入第二个参数时,函数体才会调用(案例如下)
function getValue(){
return 5;
}
function add(first,second = getValue()){
return first + second;
}
console.log(add(1,1));//2
console.log(add(1));//6
案例2:
在此函数中,如果只传入一个参数,会调用getValue,value+1,第一次调用add(1)时返回了6,value值改变了,第二次调用add(1)得到的结果是7
let value=1;
function getValue(){
return value++;
}
function add(first,second=getValue){
return first + second
}
console.log(add(1,1));//2
console.log(add(1));//6
console.log(add(1));//7
案例三
引用参数默认值,如果只传入一个参数,这两个参数相等,所以 add(1)为2
function add(first,second = first){
return first + second;
}
console.log(add(1,1));//2
console.log(add(1));//2
案例四
我们也可以将参数first传入一个函数来获得参数second的值,在这个函数中声明second=getValue(first);只有一分参数时,add(1) 返回的时(1+6)也就是7
function getValue(value){
return value+5;
}
function add(first,second = getValue(first)){
return first + second;
}
console.log(add(1,1));//2
console.log(add(1));//7
案例五
在引用函数默认值时,只允许引用前面参数的值,即先定义的参数可以访问后定义的参数,add(undefined,1)会抛出错误,因为second比first晚定义,不能当做first的默认值所以会报错
function add(first=second,second){
return first + seconds
}
console.log(add(1,1));//2
console.log(add(undefined,1));//抛出错误
3.处理无命名参数
3.1.1 不定参数(rest parameters)的特性
注:1.不定参数的加入,不会影响arguments.length
2.不定参数后不能有其他的命名参数
3.不定参数不能用于对象字面量setter之中
4.如果声明函数定义了不定参数,则函数被调用时,arguments对象包含了所有传入 函数的参数
这个函数模仿了Underscore.js库中的pick()方法,返回一个给定对象的副本,包含了原始对象属性的特定子集。在这个事例中只定义了一个参数, 第一个参数传入的是被赋值的源对象,其他参数时被赋值的属性名关于这个函数我们要注意到这几件事
首先,并不容易发现这个函数可以接受任意数量的参数,当然,可以定义更多的参数,但是怎么的也达不到要求;
其次第一个参数为命名参数,已被使用,当你查找需要拷贝的属性名称时,不得不从索引1遍历arguments对象,而不是从0开始,牢记真正的线索引位置不难,但是这总归使我们需要牵挂的问题
而在es6中,通过引入不定参数(rest parameters)的特性来解决这个问题
案例1
function pick(object) {
const result = Object.create(null);
console.log(result)
for (let i = 1; i < arguments.length; i++) {
result[arguments[i]] = object[arguments[i]]
}
return result;
console.log(object)
}
let book = {
title: 'Understanding ECMAScript 6',
author: 'Nicholas C. Zakas',
year: 2016
}
let bookdata = pick(book, 'author', 'year')
//修改案例
function pick1(object, ...args) {
const result = Object.create(null);
for (let i = 0; i < args.length; i++) {
result[arguments[i]] = object[arguments[i]]
}
return result;
}
let bookdata1 = pick(book, 'author', 'year');
console.log(bookdata1.author)
案例2
不定参数后,不能有其他的命名参数,此案例程序会爆出语法错误
let book = {
title: 'Understanding ECMAScript 6',
author: 'Nicholas C. Zakas',
year: 2016
}
function pick(object,...args,last){
console.log(args)
}
pick(book,'title')//Rest parameter must be last formal parameter
案例三
如果声明函数定义了不定参数,则函数被调用时,arguments对象包含了所有传入函数的参数,就像这样
function checkArgs(...args){
console.log(args.length);//2
console.log(arguments.length);//2
console.log(args[0],arguments[0]);//a,a
console.log(args[1],arguments[1]);//b,b
}
checkArgs('a','b');
4.展开运算符
在所有的新功能中,与不定参数最相似的是展开运算符。不定参数可以让你指定多个独立的参数,并通过整合后的数组来访问;而展开运算符可以让你一个指定的数组,将他们打散后作为独立的参数传入函数,展开运算符...可以将数组分割成独立的参数
let values=[25,50,75,100];
console.log(values,...values)//[25, 50, 75, 100], 25 50 75 100
5.name属性
由于在js中多种定义函数的方式,因而辨别函数就是一项具有挑战性的任务。此外,匿名函数表达式的广泛使用更加大了调试难度,开发者经常要追踪难以解读的栈记录。为了解决这些问题,es6中为所有的函数新增了name属性,es6中所有函数的name属性都有一个合适的值
5-1-1
function doSomething(){
}
let doAnotherThing=function(){
}
console.log(doSomething.name);//doSomething
console.log(doAnotherThing.name);//doAnotherThing
5-1-2 name属性的特殊情况
尽管确定函数声明和函数表达式的名称很容易,es6还是做了更多的改进来确保所有的函数都有合适的名称,通过Function() 构造创建的函数,其名称将是‘anonymous’
eg
var dosomething=function doSomethingElse(){
}
var person={
get firstName(){
return 'Nicholas'
},
sayName:function(){
console.log(this.name)
}
}
console.log(dosomething.name);//doSomethingElse
console.log(person.sayName.name);//sayName
console.log(person.firstName.name);
console.log(dosomething.bind().name);//bound doSomethingElse
console.log((new Function()).name);//anonymous
5.明确函数的多种用途
es5级早期的版本中的函数具有多重功能,可以结合new使用,函数内的this值将指向一个新对象,函数最终会返回这个新对象
function Person(name){
this.name=name;
}
var person = new Person('Nicholas');
var notAPerson=Person('Nicholas');
console.log(person);//{"name": "Nicholas"}
console.log(notAPerson)//undefined
6.在es5中判断函数被调用的方法
在es5中,如果想确定一个函数是否通过new关键字来调用(或者说判断这个函数是否为构造函数所调用),最流行的方法instanceof,调用Person.call()时将变量person传入作为第一个参数,相当于在Person函数里将this设为了person实例。对于函数本身,无法区分是通过Person.call() (或者是Person.applay())还是new 关键字调用得到的Person实例
function Person(name){
if(this instanceof Person){
this.name=name;
console.log(name)
}else{
throw new Error('必须通过new关键字来调用Person')
}
}
var person=new Person('Nicholas');
//var notAPerson=Person('Nicholas');//抛出错误
var notAPerson=Person.call(person,'jiaojiao')
7.元属性(Metaproperty) new.target
7-1为了解决函数是否通过new关键字调用的问题,es6引用了new.target原属性,可以通过new.target是否定义安全的检测这个函数是否通过new 关键字调用
function Person(name){
if(typeof new.target !== 'undefined'){
this.name=name;
}else{
throw new Error('必须通过new关键字来调用Person')
}
}
var person=new Person('Nicholas');
var notAPerson=Person.call(person,'xiaoxin');//抛出错误
7-2 在这段代码中,如果要让程序正确的运行,new.target一定是Person。当调用new AnotherPerson('Nicholas')时,真正调用的是 Person.call(this,name),没有使用new关键字,因此new.target的值时undefined会抛出语法错误
function Person(name){
if(typeof new.target !== 'undefined'){
this.name=name;
}else{
throw new Error('必须通过new关键字来调用Person')
}
}
function AnotherPerson(name){
Person.call(this,name)
}
var person=new Person('Nicholas');
var anotherPerson=new AnotherPerson('Nicholas');//抛出语法错误
8.块级函数
8-1 在严格模式下,在定义函数代码块内,块级函数会被提升至顶部,所以typeof doSomething的值为‘function’,这样佐证来了,即使你在函数定义的位置使用他,还是能返回正确的结果,在严格模式下一旦if代码块结束执行,doSomething函数将不复存在,
'use strict'
if(true){
console.log(typeof doSomething)//function
function doSomething(){
console.log(1)
}
}
console.log(typeof doSomething)//undefined
8-2 在非严格模式下,这些函数不在提升至代码块的顶部,而是提升至外围函数或全局作用域的顶部
if(true){
console.log(typeof doSomething)//function
function doSomething(){
console.log(1)
}
}
console.log(typeof doSomething)//function
8-3块级函数的使用场景
块级函数与let函数的表达式类似,一旦执行过程流出代码块,函数定义立即被移除。二者的区别是,在该代码块中,用let 定义的函数表达式不会被提升。
if(true){
console.log(typeof doSomething)//会抛出语法错误
let doSomething=function(){
}
doSomething();
}
9.箭头函数
在es6中,箭头函数时其中最有趣的新特性。顾明思义,箭头函数时一种使用箭头(=>)定义函数的新语法,但是它与传统的js函数有些许不同,主要集中在以下节方面
1.没有this,super , arguments和new.target绑定,箭头函数的this,super,arguments,new.target这些值由外围最近一层非箭头函数决定
2.不能通过new关键字调用 箭头函数Construct方法,所以不能被用作构造函数,如果通过new 关键字调用箭头函数,程序会抛出错误
3.没有原型 由于不能通过new关键字调用箭头函数,因而没有构建原型的需求,所以箭头函数不存在prototype这个属性
4.不可以改变this的绑定 函数内部的this值不可被改变,在函数的生命周期内始终保持一致
5.不支持arguments对象 箭头函数没有arguments绑定,所以不能通过命名参数和不定参数这两种形式访问函数的参数
6.不支持重复命名参数 无论在严格还是非严格模式下,箭头函数都不支持重复命名的参数
9-1 箭头函数的语法
9.1.1当箭头函数只有一个参数时,可以直接写参数名,箭头紧随其后箭头右侧表达式被求值后立即返回
let reflect=value => value;
等价于
let reflect=function(value){
return value;
}
9.1.2如果要传入两个或者两个以上的参数,要在参数的两侧添加一对小括号
let sum = (num1,num2) => num1+num2
等价于
let sum =function(num1,num2){
return num1+num2;
}
9.1.3
//如果函数没有参数,也要在声明的时候写一组没有内容的小括号
let getName = () => 'xiaoming';
let getName=function(){
return 'xiaoming'
}
9.1.4
//如果你想创建一个新的对象,需要写一对没有内容的花括号
let doNothing=()=>{};
//相当于
let doNothing=function(){
}
9.1.5
如果想函数向外返回一个对象字面量,则需要将字面量包裹在小括号里
let getTempItem = id =>({id:id,name:'Temp'});
let getTempItem = function(id){
return {id:id,name:'Temp'};
}
9.1.5创建立即执行表达式
let person = ((name) =>{
return {
getName:function(){
return name;
}
}
})('xiaoming');
console.log(person.getName())
9.1.6箭头函数没有this绑定
案例一:this.doSomething会报错 this指的是ducument,而不是pageHandler,所以会报错
let pageHandler={
id:'123456',
init:function(){
document.addEventListener('click',function(event){
console.log(this)
this.doSomething(event.type)
},false)
},
doSomething:function(type){
console.log('Handling'+type+"for"+this.id)
}
}
pageHandler.init();
案例二:es5解决方法 可以用bind()的方法将函数的this帮到到pageHandler
let pageHandler={
id:'123456',
init:function(){
document.addEventListener('click',(function(event){
console.log(this)
this.doSomething(event.type)
}).bind(this),false)
},
doSomething:function(type){
console.log('Handling'+type+"for"+this.id)
}
}
pageHandler.init();
案例三:箭头函数的用法,箭头函数没有this绑定,必须通过查找作用域链来决定其值。如果箭头函数被非箭头函数所包含,则this绑定的是最近一层非箭头函数的this;否则this值会被设置为全局对象
let pageHandler={
id:'123456',
init:function(){
document.addEventListener('click',(event)=>{
this.doSomething(event.type)
},false)
},
doSomething:function(type){
console.log('Handling'+type+"for"+this.id)
}
}
pageHandler.init();
案例四:箭头函数缺少正常函数所拥有的prototype属性,他的设计初衷是“即用即弃”,所以不能退用它来定义新的类型
var myType = ()=>{
}
object = new myType();//错误,不可以通过new关键字调用箭头函数
案例五:箭头函数和数组,箭头函数的语法非常简洁 非常适用于数组的处理
let result=values.sort((a,b) => a-b);
案例六:箭头函数没有arguments绑定
var myType = (name) => {
return arguments[0]
}
myType(111);//会报错Uncaught ReferenceError: arguments is not defined at myType
案例七:箭头函数的辨识方法
var comparator=(a,b)=>a-b;
console.log(typeof comparator);//function
console.log(comparator instanceof Function)//true
案例八:在箭头函数上可以调用 call() applay 及bind的方法,箭头函数的this值不会受这些方法所影响
var sum =(num1,num2) => num1+num2;
console.log(sum.call(null,1,2));//3
console.log(sum.apply(null,[1,2]));//3
var boundsum=sum.bind(null,1,2);
console.log(boundsum());//3